Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH 0/7] rust: Use kernel style vertical imports in various drivers
From: Gary Guo @ 2026-07-05 12:08 UTC (permalink / raw)
  To: Guru Das Srinagesh, Andrew Lunn
  Cc: Miguel Ojeda, rust-for-linux, linux-kernel, Danilo Krummrich,
	Abdiel Janulgue, Daniel Almeida, Robin Murphy, Andreas Hindborg,
	Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
	Alice Ryhl, Trevor Gross, Tamir Duberstein, Alexandre Courbot,
	Onur Özkan, Drew Fustini, Guo Ren, Fu Wei, Michal Wilczynski,
	Uwe Kleine-König, Rafael J. Wysocki, Viresh Kumar,
	Jens Axboe, FUJITA Tomonori, Heiner Kallweit, Russell King,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	David Airlie, Simona Vetter, driver-core, linux-riscv, linux-pwm,
	linux-pm, linux-block, netdev, nova-gpu, dri-devel
In-Reply-To: <akmnh0AKFfu9-OYn@gurudas.dev>

On Sun Jul 5, 2026 at 1:38 AM BST, Guru Das Srinagesh wrote:
> On Mon, Jun 29, 2026 at 04:06:36PM +0200, Andrew Lunn wrote:
>> On Sun, Jun 28, 2026 at 08:38:14PM -0700, Guru Das Srinagesh wrote:
>> > Came across a recent commit bc58905eb07 ("samples: rust_misc_device: use
>> > vertical import style") and found a few more locations that could
>> > benefit from this cleanup. No functional changes.
>> > 
>> > Signed-off-by: Guru Das Srinagesh <linux@gurudas.dev>
>> > ---
>> > Guru Das Srinagesh (7):
>> >       samples: rust_dma: use vertical import style
>> >       pwm: th1520: use vertical import style
>> >       cpufreq: rcpufreq_dt: use vertical import style
>> >       block: rnull: use vertical import style
>> >       net: phy: ax88796b: use vertical import style
>> >       net: phy: qt2025: use vertical import style
>> >       drm/nova: use vertical import style
>> 
>> You have multiple subsystems here, so you need to split this patch
>> setup, per subsystem, and submit them separately. Maintainers only
>> accept patchsets for their own subsystems.
>> 
>> For netdev, please take a read of:
>> 
>> https://www.kernel.org/doc/html/latest/process/maintainer-netdev.html
>> 
>> You need to get the correct tree, and set the Subject: line correctly.
>> 
>>     Andrew
>
> Hi Andrew,
>
> Thanks for the feedback.
>
> I was aware of the per-subsystem rule, but reasoned that since these changes are
> purely about Rust import formatting coding style with no functional impact on any
> subsystem, they might go through the rust-for-linux tree with acks from the
> respective subsystem maintainers. The Rust coding style is independent of any
> subsystem-specific guidelines.

Patches should usually be merged via their usual tree, otherwise you're just
creating unnecessary merge conflicts.

There are obiviously exceptions to this for series that need to touch multiple
subsystems and cannot be split, but it is not the case for this sort of small
cleanups.

Best,
Gary

>
> Is that reasoning off-base, or is the right path to split these out per subsystem
> regardless?

^ permalink raw reply

* Re: [PATCH 3/7] cpufreq: rcpufreq_dt: use vertical import style
From: Miguel Ojeda @ 2026-07-05 12:12 UTC (permalink / raw)
  To: Miguel Ojeda, Zhongqiu Han, Miguel Ojeda, rust-for-linux,
	linux-kernel, Danilo Krummrich, Abdiel Janulgue, Daniel Almeida,
	Robin Murphy, Andreas Hindborg, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Tamir Duberstein, Alexandre Courbot, Onur Özkan,
	Drew Fustini, Guo Ren, Fu Wei, Michal Wilczynski,
	Uwe Kleine-König, Rafael J. Wysocki, Viresh Kumar,
	Jens Axboe, FUJITA Tomonori, Andrew Lunn, Heiner Kallweit,
	Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, David Airlie, Simona Vetter, driver-core,
	linux-riscv, linux-pwm, linux-pm, linux-block, netdev, nova-gpu,
	dri-devel
In-Reply-To: <akmtA7EbVBdhRAOq@gurudas.dev>

On Sun, Jul 5, 2026 at 3:02 AM Guru Das Srinagesh <linux@gurudas.dev> wrote:
>
> But unfortunately, since `imports_layout` is an unstable option currently [1], it
> cannot be used straightaway.

Yeah, we are trying to get that one stabilized -- upstream `rustfmt`
is aware and working on it. More context at:

  https://github.com/rust-lang/rustfmt/issues/6829
  https://github.com/Rust-for-Linux/linux/issues/398

> However, .rustfmt.toml already has a section of commented-out unstable options kept
> as a reference for when they stabilize. Would it make sense to add `#imports_layout =
> "Vertical"` there? If so, I can include it in v2.

That may be a bit confusing, because we still need to add the `//`
comments. And if you add them, then `rustfmt` will format properly,
i.e. you don't need the unstable option.

Put another way: the intended workflow is to add the `//` and then to
run `make ... rustfmt`.

I hope that clarifies!

Cheers,
Miguel

^ permalink raw reply

* Re: [PATCH 0/7] rust: Use kernel style vertical imports in various drivers
From: Miguel Ojeda @ 2026-07-05 12:22 UTC (permalink / raw)
  To: Andrew Lunn, Miguel Ojeda, rust-for-linux, linux-kernel,
	Danilo Krummrich, Abdiel Janulgue, Daniel Almeida, Robin Murphy,
	Andreas Hindborg, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Alice Ryhl, Trevor Gross, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Drew Fustini, Guo Ren, Fu Wei,
	Michal Wilczynski, Uwe Kleine-König, Rafael J. Wysocki,
	Viresh Kumar, Jens Axboe, FUJITA Tomonori, Heiner Kallweit,
	Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, David Airlie, Simona Vetter, driver-core,
	linux-riscv, linux-pwm, linux-pm, linux-block, netdev, nova-gpu,
	dri-devel
In-Reply-To: <akmnh0AKFfu9-OYn@gurudas.dev>

On Sun, Jul 5, 2026 at 2:38 AM Guru Das Srinagesh <linux@gurudas.dev> wrote:
>
> Miguel, could you please indicate if you have a preference here?

Gary is right -- it depends.

Generally speaking, it is best to do changes through each tree;
especially so if it is the kind of thing that may be prone to
conflicts.

In certain cases, yes, I may take treewide changes, with Acked-by's
from maintainers (and very exceptionally I have done it without that
if e.g. it is trivial enough and enough time has passed -- trusting
that people trust me to do so).

It is part of why the Rust tree exists, i.e. as a fallback and also
for convenience in certain cases.

I hope that clarifies.

Cheers,
Miguel

^ permalink raw reply

* [PATCH nf] ipvs: skip IPv6 extension headers in SCTP state lookup
From: Yizhou Zhao @ 2026-07-05 12:30 UTC (permalink / raw)
  To: netdev
  Cc: Yizhou Zhao, Simon Horman, Julian Anastasov, Pablo Neira Ayuso,
	Florian Westphal, Phil Sutter, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, lvs-devel, netfilter-devel, coreteam,
	linux-kernel, Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li, Ke Xu,
	stable

set_sctp_state() reads the SCTP chunk header again in order to drive the
IPVS SCTP state table.  For IPv6 it computes the offset with
sizeof(struct ipv6hdr), while the surrounding IPVS code uses iph->len from
ip_vs_fill_iph_skb(), where ipv6_find_hdr() has already skipped extension
headers and found the real transport header.

This makes the state machine read from the wrong offset for IPv6 SCTP
packets that carry extension headers.  For example, an INIT packet with an
8-byte destination options header can be scheduled correctly by
sctp_conn_schedule(), but set_sctp_state() reads the first byte of the SCTP
verification tag as a DATA chunk type.  The connection then moves from NONE
to ESTABLISHED instead of INIT1, gets the longer established timeout, and
updates the active/inactive destination counters incorrectly.  This happens
even though the SCTP handshake has not completed.

Use ip_vs_fill_iph_skb() in set_sctp_state() and base the chunk-header
offset on iph.len, matching sctp_conn_schedule() and the SCTP NAT handlers.
For IPv4 and IPv6 packets without extension headers this preserves the
existing offset.

Fixes: 2906f66a5682 ("ipvs: SCTP Trasport Loadbalancing Support")
Cc: stable@vger.kernel.org
Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
Reported-by: Ao Wang <wangao@seu.edu.cn>
Reported-by: Xuewei Feng <fengxw06@126.com>
Reported-by: Qi Li <qli01@tsinghua.edu.cn>
Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
Assisted-by: Claude-Code:GLM-5.2
Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
---
 net/netfilter/ipvs/ip_vs_proto_sctp.c | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c
index 63c78a1f3918..6e0fc23be305 100644
--- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
@@ -375,17 +375,15 @@ set_sctp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp,
 		int direction, const struct sk_buff *skb)
 {
 	struct sctp_chunkhdr _sctpch, *sch;
 	unsigned char chunk_type;
+	struct ip_vs_iphdr iph;
 	int event, next_state;
-	int ihl, cofs;
+	int cofs;
 
-#ifdef CONFIG_IP_VS_IPV6
-	ihl = cp->af == AF_INET ? ip_hdrlen(skb) : sizeof(struct ipv6hdr);
-#else
-	ihl = ip_hdrlen(skb);
-#endif
+	if (!ip_vs_fill_iph_skb(cp->af, skb, false, &iph))
+		return;
 
-	cofs = ihl + sizeof(struct sctphdr);
+	cofs = iph.len + sizeof(struct sctphdr);
 	sch = skb_header_pointer(skb, cofs, sizeof(_sctpch), &_sctpch);
 	if (sch == NULL)
 		return;
-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v4 0/3] net: af_unix: useful handling of LSM denials on SCM_RIGHTS
From: Jori Koolstra @ 2026-07-05 12:38 UTC (permalink / raw)
  To: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: netdev, linux-fsdevel, linux-kernel, Jori Koolstra

Right now if some LSM denies an AF_UNIX socket peer to receive a
SCM_RIGHTS fd, the SCM_RIGHTS fd array will be cut short at
that point, and MSG_CTRUNC is set on return of recvmsg(2). This is
highly problematic behaviour, because it leaves the receiver
wondering what happened. As per man page MSG_CTRUNC is supposed to
indicate that the control buffer was sized too short, but suddenly
a permission error might result in the exact same flag being set.
Moreover, the receiver has no chance to determine how many fds got
originally sent and how many were suppressed.[1]

Add a SO_RIGHTS_NOTRUNC option to UNIX sockets to enable more useful
handling of LSM denials when receiving SCM_RIGHTS messages: instead of
truncating the message at the first blocked fd, keep every fd slot
and store the LSM errno in the blocked slot.

[1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights

Changes:
v4:
  - Removed the __receive_fd() helper and moved logic into
    scm_recv_one_fd() directly (suggested by Brauner).
  - Moved selftest from Smack to BPF (LLM assisted).
  - Add arch specific socket option values for SO_RIGHTS_NOTRUNC.
  - Undo patch that replaced copy_from_sockptr() with
    copy_safe_from_sockptr().
v3:
  - Separated net and vfs changes.
  - Use kselftest_harness.h and system() to call the test script.
v2: https://lore.kernel.org/netdev/20260616143020.3458085-2-jkoolstra@xs4all.nl/
  - Reimplemented as a UNIX socket option instead of a per recvmsg(2) flag.
v1: https://lore.kernel.org/netdev/20260428175125.2705296-1-jkoolstra@xs4all.nl/

Jori Koolstra (3):
  net: scm: move scm_detach_fds() from common path to scm_recv_unix()
  net: af_unix: useful handling of LSM denials on SCM_RIGHTS
  selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS

 arch/alpha/include/uapi/asm/socket.h          |   2 +
 arch/mips/include/uapi/asm/socket.h           |   2 +
 arch/parisc/include/uapi/asm/socket.h         |   2 +
 arch/sparc/include/uapi/asm/socket.h          |   2 +
 include/net/af_unix.h                         |   1 +
 include/net/scm.h                             |  13 +-
 include/uapi/asm-generic/socket.h             |   2 +
 net/compat.c                                  |   4 +-
 net/core/scm.c                                |  42 ++-
 net/unix/af_unix.c                            |   9 +
 .../testing/selftests/net/af_unix/.gitignore  |   2 +
 tools/testing/selftests/net/af_unix/Makefile  |   8 +
 .../net/af_unix/scm_rights_denial_lsm.bpf.c   |  36 +++
 .../net/af_unix/scm_rights_denial_lsm.c       | 263 ++++++++++++++++++
 14 files changed, 371 insertions(+), 17 deletions(-)
 create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c
 create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c


base-commit: b73bc9ca3686b78b642fb35dcc1fdf874ecb74a1
-- 
2.55.0


^ permalink raw reply

* [PATCH net-next v4 2/3] net: af_unix: useful handling of LSM denials on SCM_RIGHTS
From: Jori Koolstra @ 2026-07-05 12:38 UTC (permalink / raw)
  To: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: netdev, linux-fsdevel, linux-kernel, Jori Koolstra
In-Reply-To: <20260705123826.3818443-1-jkoolstra@xs4all.nl>

Right now if some LSM such as Smack denies an AF_UNIX socket peer to
receive an SCM_RIGHTS fd, the SCM_RIGHTS fd array will be cut short at
that point, and MSG_CTRUNC is set on return of recvmsg(). This is
highly problematic behaviour, because it leaves the receiver
wondering what happened. As per man page MSG_CTRUNC is supposed to
indicate that the control buffer was sized too short, but suddenly
a permission error might result in the exact same flag being set.
Moreover, the receiver has no chance to determine how many fds got
originally sent and how many were suppressed.[1]

Add a SO_RIGHTS_NOTRUNC option to UNIX sockets to enable more useful
handling of LSM denials when receiving SCM_RIGHTS messages: instead of
truncating the message at the first blocked fd, keep every fd slot
and store the LSM errno in the blocked slot.

[1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights

Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
---
 arch/alpha/include/uapi/asm/socket.h  |  2 ++
 arch/mips/include/uapi/asm/socket.h   |  2 ++
 arch/parisc/include/uapi/asm/socket.h |  2 ++
 arch/sparc/include/uapi/asm/socket.h  |  2 ++
 include/net/af_unix.h                 |  1 +
 include/net/scm.h                     | 13 +++------
 include/uapi/asm-generic/socket.h     |  2 ++
 net/compat.c                          |  4 +--
 net/core/scm.c                        | 40 +++++++++++++++++++++++----
 net/unix/af_unix.c                    |  9 ++++++
 10 files changed, 61 insertions(+), 16 deletions(-)

diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index 5ef57f88df6b..946a5fad2691 100644
--- a/arch/alpha/include/uapi/asm/socket.h
+++ b/arch/alpha/include/uapi/asm/socket.h
@@ -155,6 +155,8 @@
 #define SO_INQ			84
 #define SCM_INQ			SO_INQ
 
+#define SO_RIGHTS_NOTRUNC      85
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h
index 72fb1b006da9..f1641dde135f 100644
--- a/arch/mips/include/uapi/asm/socket.h
+++ b/arch/mips/include/uapi/asm/socket.h
@@ -166,6 +166,8 @@
 #define SO_INQ			84
 #define SCM_INQ			SO_INQ
 
+#define SO_RIGHTS_NOTRUNC      85
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h
index c16ec36dfee6..f3a3815c7dc2 100644
--- a/arch/parisc/include/uapi/asm/socket.h
+++ b/arch/parisc/include/uapi/asm/socket.h
@@ -147,6 +147,8 @@
 #define SO_INQ			0x4052
 #define SCM_INQ			SO_INQ
 
+#define SO_RIGHTS_NOTRUNC	0x4053
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64
diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h
index 71befa109e1c..7907f3b1f0ee 100644
--- a/arch/sparc/include/uapi/asm/socket.h
+++ b/arch/sparc/include/uapi/asm/socket.h
@@ -148,6 +148,8 @@
 #define SO_INQ                   0x005d
 #define SCM_INQ                  SO_INQ
 
+#define SO_RIGHTS_NOTRUNC        0x005e
+
 #if !defined(__KERNEL__)
 
 
diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 34f53dde65ce..bb1b3dee02e8 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -49,6 +49,7 @@ struct unix_sock {
 	struct scm_stat		scm_stat;
 	int			inq_len;
 	bool			recvmsg_inq;
+	bool			scm_rights_notrunc;
 #if IS_ENABLED(CONFIG_AF_UNIX_OOB)
 	struct sk_buff		*oob_skb;
 #endif
diff --git a/include/net/scm.h b/include/net/scm.h
index c52519669349..86ae6bc109ec 100644
--- a/include/net/scm.h
+++ b/include/net/scm.h
@@ -50,8 +50,8 @@ struct scm_cookie {
 #endif
 };
 
-void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm);
-void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm);
+void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc);
+void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc);
 int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm);
 void __scm_destroy(struct scm_cookie *scm);
 struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl);
@@ -107,13 +107,8 @@ void scm_recv(struct socket *sock, struct msghdr *msg,
 void scm_recv_unix(struct socket *sock, struct msghdr *msg,
 		   struct scm_cookie *scm, int flags);
 
-static inline int scm_recv_one_fd(struct file *f, int __user *ufd,
-				  unsigned int flags)
-{
-	if (!ufd)
-		return -EFAULT;
-	return receive_fd(f, ufd, flags);
-}
+int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
+		    bool notrunc);
 
 #endif /* __LINUX_NET_SCM_H */
 
diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index 53b5a8c002b1..84ea7b92936e 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h
@@ -150,6 +150,8 @@
 #define SO_INQ			84
 #define SCM_INQ			SO_INQ
 
+#define SO_RIGHTS_NOTRUNC	85
+
 #if !defined(__KERNEL__)
 
 #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
diff --git a/net/compat.c b/net/compat.c
index d68cf9c3aad5..6bdf4a2c9077 100644
--- a/net/compat.c
+++ b/net/compat.c
@@ -286,7 +286,7 @@ static int scm_max_fds_compat(struct msghdr *msg)
 	return (msg->msg_controllen - sizeof(struct compat_cmsghdr)) / sizeof(int);
 }
 
-void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm)
+void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc)
 {
 	struct compat_cmsghdr __user *cm =
 		(struct compat_cmsghdr __user *)msg->msg_control_user;
@@ -296,7 +296,7 @@ void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm)
 	int err = 0, i;
 
 	for (i = 0; i < fdmax; i++) {
-		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
+		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
 		if (err < 0)
 			break;
 	}
diff --git a/net/core/scm.c b/net/core/scm.c
index a73b1eb30fd2..9454d06ce5ed 100644
--- a/net/core/scm.c
+++ b/net/core/scm.c
@@ -351,7 +351,31 @@ static int scm_max_fds(struct msghdr *msg)
 	return (msg->msg_controllen - sizeof(struct cmsghdr)) / sizeof(int);
 }
 
-void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)
+int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
+		    bool notrunc)
+{
+	int error;
+
+	if (!ufd)
+		return -EFAULT;
+
+	error = security_file_receive(f);
+	if (error)
+		return notrunc ? put_user(error, ufd) : error;
+
+	FD_PREPARE(fdf, flags, get_file(f));
+	if (fdf.err)
+		return fdf.err;
+
+	error = put_user(fd_prepare_fd(fdf), ufd);
+	if (error)
+		return error;
+
+	__receive_sock(fd_prepare_file(fdf));
+	return fd_publish(fdf);
+}
+
+void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc)
 {
 	struct cmsghdr __user *cm =
 		(__force struct cmsghdr __user *)msg->msg_control_user;
@@ -365,12 +389,12 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)
 		return;
 
 	if (msg->msg_flags & MSG_CMSG_COMPAT) {
-		scm_detach_fds_compat(msg, scm);
+		scm_detach_fds_compat(msg, scm, notrunc);
 		return;
 	}
 
 	for (i = 0; i < fdmax; i++) {
-		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
+		err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
 		if (err < 0)
 			break;
 	}
@@ -542,8 +566,14 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
 	if (!__scm_recv_common(sock->sk, msg, scm, flags))
 		return;
 
-	if (scm->fp)
-		scm_detach_fds(msg, scm);
+	if (scm->fp) {
+		struct unix_sock *u;
+		bool notrunc;
+
+		u = unix_sk(sock->sk);
+		notrunc = READ_ONCE(u->scm_rights_notrunc);
+		scm_detach_fds(msg, scm, notrunc);
+	}
 
 	if (sock->sk->sk_scm_pidfd)
 		scm_pidfd_recv(msg, scm);
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index f7a9d55eee8a..83274ce18e06 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -921,6 +921,7 @@ static bool unix_custom_sockopt(int optname)
 {
 	switch (optname) {
 	case SO_INQ:
+	case SO_RIGHTS_NOTRUNC:
 		return true;
 	default:
 		return false;
@@ -956,6 +957,14 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
 
 		WRITE_ONCE(u->recvmsg_inq, val);
 		break;
+
+	case SO_RIGHTS_NOTRUNC:
+		if (val > 1 || val < 0)
+			return -EINVAL;
+
+		WRITE_ONCE(u->scm_rights_notrunc, val);
+		break;
+
 	default:
 		return -ENOPROTOOPT;
 	}
-- 
2.55.0


^ permalink raw reply related

* [PATCH] timekeeping: Document monotonic raw timestamps in snapshots correctly
From: Thomas Gleixner @ 2026-07-05 12:38 UTC (permalink / raw)
  To: Thomas Weißschuh
  Cc: LKML, David Woodhouse, Miroslav Lichvar, John Stultz,
	Stephen Boyd, Anna-Maria Behnsen, Frederic Weisbecker,
	Arthur Kiyanovski, Rodolfo Giometti, Vincent Donnefort,
	Marc Zyngier, Oliver Upton, kvmarm, Oliver Upton, Richard Cochran,
	netdev, Takashi Iwai, Miri Korenblit, Johannes Berg, Jacob Keller,
	Tony Nguyen, Saeed Mahameed, Peter Hilber, Michael S. Tsirkin,
	virtualization, linux-wireless, linux-sound
In-Reply-To: <87bjctsptt.ffs@fw13>

The comments related to raw monotonic timestamps for the various
snapshot mechanisms in code and struct documentation are ambiguous. They
reference them as CLOCK_MONOTONIC_RAW timestamps, but with the arrival
of AUX clocks that's not longer correct.

The raw monotonic timestamps only represent CLOCK_MONOTONIC_RAW for the
system time clock IDs, i.e. REALTIME, MONOTONIC, BOOTTIME, TAI.

For AUX clocks they refer to the monotonic raw clock which is related to
the individual AUX clocks. These monotonic raw timestamps have the same
conversion factor as CLOCK_MONOTONIC_RAW, but differ from that by an
offset:

	MONORAW(AUX$N) = MONORAW(SYSTEM) + OFFSET(AUX$N)

The offset is established when a AUX clock is enabled and stays constant
for the lifetime of the AUX clock.

Update the comments so they reflect reality.

Signed-off-by: Thomas Gleixner <tglx@kernel.org>
Reported-by: Thomas Weißschuh <thomas.weissschuh@linutronix.de>
---
 include/linux/timekeeping.h |   10 +++++++++-
 kernel/time/timekeeping.c   |   16 +++++++++++++++-
 2 files changed, 24 insertions(+), 2 deletions(-)

--- a/include/linux/timekeeping.h
+++ b/include/linux/timekeeping.h
@@ -276,7 +276,7 @@ static inline bool ktime_get_aux_ts64(cl
 #endif
 
 /**
- * struct system_time_snapshot - Simultaneous time capture of CLOCK_MONOTONIC_RAW,
+ * struct system_time_snapshot - Simultaneous time capture of monotonic raw time,
  *				 a selected CLOCK_* and the clocksource counter value
  * @cycles:		Clocksource counter value to produce the system times
  * @hw_cycles:		For derived clocksources, the hardware counter value from
@@ -289,6 +289,10 @@ static inline bool ktime_get_aux_ts64(cl
  * @clock_was_set_seq:	The sequence number of clock-was-set events
  * @cs_was_changed_seq:	The sequence number of clocksource change events
  * @valid:		True if the snapshot is valid
+ *
+ * @monoraw is CLOCK_MONOTONIC_RAW for system time CLOCK ids. For CLOCK_AUX$N
+ * clock ids it's the monotonic raw time related to the AUX clock, which is
+ * CLOCK_MONOTONIC_RAW plus a AUX clock specific offset.
  */
 struct system_time_snapshot {
 	u64			cycles;
@@ -326,6 +330,10 @@ struct system_counterval_t {
  * @sys_counter:	Clocksource counter value simultaneous with device time
  * @sys_systime:	System time for @clock_id
  * @sys_monoraw:	Monotonic raw simultaneous with device time
+ *
+ * @sys_monoraw is CLOCK_MONOTONIC_RAW for system time CLOCK ids. For
+ * CLOCK_AUX$N clock ids it's the monotonic raw time related to the AUX clock,
+ * which is CLOCK_MONOTONIC_RAW plus a AUX clock specific offset.
  */
 struct system_device_crosststamp {
 	clockid_t			clock_id;
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -1202,10 +1202,21 @@ static inline u64 tk_clock_read_snapshot
 
 /**
  * ktime_get_snapshot_id -  Simultaneously snapshot a given clock ID with
- *			    CLOCK_MONOTONIC_RAW and the underlying
+ *			    the corresponding monotonic raw and the underlying
  *			    clocksource counter value.
  * @clock_id:		The clock ID to snapshot
  * @systime_snapshot:	Pointer to struct receiving the system time snapshot
+ *
+ * For the system time keeping clocks (REALTIME, MONOTONIC and BOOTTIME) the
+ * monotonic raw clock is CLOCK_MONOTONIC_RAW. For AUX clocks this is the
+ * monotonic raw clock related to the AUX clock. These AUX clock related
+ * monotonic raw clocks have a strict linear offset to the system time
+ * CLOCK_MONOTONIC_RAW:
+ *
+ *	MONOTONIC_RAW(AUX$N) = CLOCK_MONOTONIC_RAW(system) + offset(AUX$N)
+ *
+ * The offset is established when a AUX clock is initialized, but it is
+ * currently not accessible.
  */
 void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *systime_snapshot)
 {
@@ -1512,6 +1523,9 @@ EXPORT_SYMBOL_GPL(ktime_real_to_base_clo
  * @xtstamp:		Receives simultaneously captured system and device time
  *
  * Reads a timestamp from a device and correlates it to system time
+ *
+ * See documentation for ktime_get_snapshot_id() for information about the raw
+ * monotonic time stamp which is used here.
  */
 int get_device_system_crosststamp(int (*get_time_fn)
 				  (ktime_t *device_time,

^ permalink raw reply

* [PATCH net-next v4 1/3] net: scm: move scm_detach_fds() from common path to scm_recv_unix()
From: Jori Koolstra @ 2026-07-05 12:38 UTC (permalink / raw)
  To: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: netdev, linux-fsdevel, linux-kernel, Jori Koolstra
In-Reply-To: <20260705123826.3818443-1-jkoolstra@xs4all.nl>

scm->fp can only be set when using UNIX sockets, therefore we should
move it out of the common path __scm_recv_common() into
scm_recv_unix().

Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
---
 net/core/scm.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/net/core/scm.c b/net/core/scm.c
index eec13f50ecaf..a73b1eb30fd2 100644
--- a/net/core/scm.c
+++ b/net/core/scm.c
@@ -523,9 +523,6 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,
 
 	scm_passec(sk, msg, scm);
 
-	if (scm->fp)
-		scm_detach_fds(msg, scm);
-
 	return true;
 }
 
@@ -545,6 +542,9 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
 	if (!__scm_recv_common(sock->sk, msg, scm, flags))
 		return;
 
+	if (scm->fp)
+		scm_detach_fds(msg, scm);
+
 	if (sock->sk->sk_scm_pidfd)
 		scm_pidfd_recv(msg, scm);
 
-- 
2.55.0


^ permalink raw reply related

* [PATCH net-next v4 3/3] selftest: Add tests for useful handling of LSM denials on SCM_RIGHTS
From: Jori Koolstra @ 2026-07-05 12:38 UTC (permalink / raw)
  To: Christian Brauner, Aleksa Sarai, Kuniyuki Iwashima,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: netdev, linux-fsdevel, linux-kernel, Jori Koolstra
In-Reply-To: <20260705123826.3818443-1-jkoolstra@xs4all.nl>

Tests SCM_RIGHTS fd passing on a socket with the new socket option
SO_RIGHTS_NOTRUNC turned on. To hook into the security_file_receive()
call, BPF is used. The BPF program shares a hashmap with userspace that
lists the inos to be blocked (of the receiver tgid).

Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
Assisted-by: LLM # used LLM to get skeleton BPF and libbpf code
---
 .../testing/selftests/net/af_unix/.gitignore  |   2 +
 tools/testing/selftests/net/af_unix/Makefile  |   8 +
 .../net/af_unix/scm_rights_denial_lsm.bpf.c   |  36 +++
 .../net/af_unix/scm_rights_denial_lsm.c       | 263 ++++++++++++++++++
 4 files changed, 309 insertions(+)
 create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c
 create mode 100644 tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c

diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore
index 240b26740c9e..5034482f8864 100644
--- a/tools/testing/selftests/net/af_unix/.gitignore
+++ b/tools/testing/selftests/net/af_unix/.gitignore
@@ -3,6 +3,8 @@ msg_oob
 scm_inq
 scm_pidfd
 scm_rights
+scm_rights_denial_lsm
+scm_rights_denial_lsm.bpf.o
 so_peek_off
 unix_connect
 unix_connreset
diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile
index 4c0375e28bbe..594cd26ec398 100644
--- a/tools/testing/selftests/net/af_unix/Makefile
+++ b/tools/testing/selftests/net/af_unix/Makefile
@@ -11,9 +11,17 @@ TEST_GEN_PROGS := \
 	scm_inq \
 	scm_pidfd \
 	scm_rights \
+	scm_rights_denial_lsm \
 	so_peek_off \
 	unix_connect \
 	unix_connreset \
 # end of TEST_GEN_PROGS
 
+TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o
+
 include ../../lib.mk
+include ../bpf.mk
+
+$(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ)
+$(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include
+$(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz
diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c
new file mode 100644
index 000000000000..4f2414465bfd
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.bpf.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <linux/errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+struct inode {
+	unsigned long i_ino;
+} __attribute__((preserve_access_index));
+
+struct file {
+	struct inode *f_inode;
+} __attribute__((preserve_access_index));
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 16);
+	__type(key, __u64);	/* inode number */
+	__type(value, __u32);	/* tgid of the receiver being tested */
+} denied_inodes SEC(".maps");
+
+SEC("lsm/file_receive")
+int BPF_PROG(scm_rights_deny, struct file *file)
+{
+	__u32 tgid = bpf_get_current_pid_tgid() >> 32;
+	__u64 ino = file->f_inode->i_ino;
+	__u32 *owner;
+
+	owner = bpf_map_lookup_elem(&denied_inodes, &ino);
+	if (owner && *owner == tgid)
+		return -EPERM;
+
+	return 0;
+}
diff --git a/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c
new file mode 100644
index 000000000000..b8656de86efe
--- /dev/null
+++ b/tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <bpf/bpf.h>
+#include <bpf/libbpf.h>
+
+#include "kselftest_harness.h"
+
+#ifndef SO_RIGHTS_NOTRUNC
+#define SO_RIGHTS_NOTRUNC 85
+#endif
+
+#define NR_FILES 2
+
+/* Per-file content, so a received fd can be matched to the file sent */
+#define SECRET(n) "secret %d", (n)
+
+/* Indices into the socketpair */
+#define SK_SENDER 0
+#define SK_RECEIVER 1
+
+FIXTURE(scm_rights_denial_bpf)
+{
+	struct bpf_object *obj;
+	struct bpf_link *link;
+	int map_fd;
+	int sk[2];
+	int files[NR_FILES];
+	__u64 inos[NR_FILES];
+	char paths[NR_FILES][64];
+};
+
+FIXTURE_SETUP(scm_rights_denial_bpf)
+{
+	struct bpf_program *prog;
+	char lsms[256] = {};
+	int i, fd;
+
+	if (geteuid() != 0)
+		SKIP(return, "requires root");
+
+	fd = open("/sys/kernel/security/lsm", O_RDONLY);
+	ASSERT_LE(0, fd);
+	ASSERT_LT(0, read(fd, lsms, sizeof(lsms) - 1));
+	close(fd);
+
+	if (!strstr(lsms, "bpf"))
+		SKIP(return, "BPF LSM not active (boot with lsm=...,bpf)");
+
+	self->obj = bpf_object__open_file("scm_rights_denial_lsm.bpf.o", NULL);
+	ASSERT_NE(NULL, self->obj);
+	ASSERT_EQ(0, bpf_object__load(self->obj));
+
+	prog = bpf_object__find_program_by_name(self->obj, "scm_rights_deny");
+	ASSERT_NE(NULL, prog);
+
+	self->link = bpf_program__attach_lsm(prog);
+	ASSERT_NE(NULL, self->link);
+
+	self->map_fd = bpf_object__find_map_fd_by_name(self->obj,
+						       "denied_inodes");
+	ASSERT_LE(0, self->map_fd);
+
+	ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, self->sk));
+
+	for (i = 0; i < NR_FILES; i++) {
+		struct stat st;
+
+		snprintf(self->paths[i], sizeof(self->paths[i]),
+			 "/tmp/scm_rights_denial_bpf.%d.XXXXXX", i);
+		self->files[i] = mkstemp(self->paths[i]);
+		ASSERT_LE(0, self->files[i]);
+
+		ASSERT_LT(0, dprintf(self->files[i], SECRET(i)));
+
+		ASSERT_EQ(0, fstat(self->files[i], &st));
+		self->inos[i] = st.st_ino;
+	}
+}
+
+FIXTURE_TEARDOWN(scm_rights_denial_bpf)
+{
+	bpf_link__destroy(self->link);
+	bpf_object__close(self->obj);
+
+	for (int i = 0; i < NR_FILES; i++) {
+		if (self->files[i] >= 0) {
+			close(self->files[i]);
+			unlink(self->paths[i]);
+		}
+	}
+
+	close(self->sk[SK_SENDER]);
+	close(self->sk[SK_RECEIVER]);
+}
+
+static int deny_inode(int map_fd, __u64 ino)
+{
+	__u32 tgid = getpid();
+	return bpf_map_update_elem(map_fd, &ino, &tgid, BPF_ANY);
+}
+
+static int set_notrunc(int sk)
+{
+	int one = 1;
+	return setsockopt(sk, SOL_SOCKET, SO_RIGHTS_NOTRUNC,
+			  &one, sizeof(one));
+}
+
+static int send_fds(int sk, int *fds, int n)
+{
+	char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))] = {};
+	char data = 'x';
+	struct iovec iov = {
+		.iov_base = &data,
+		.iov_len = sizeof(data),
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+		.msg_control = ctrl,
+		.msg_controllen = CMSG_SPACE(n * sizeof(int)),
+	};
+	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	cmsg->cmsg_len = CMSG_LEN(n * sizeof(int));
+	memcpy(CMSG_DATA(cmsg), fds, n * sizeof(int));
+
+	return sendmsg(sk, &msg, 0);
+}
+
+static int recv_fd_slots(int sk, int *slots, int *msg_flags)
+{
+	int nr_slots;
+	char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))];
+	char data;
+	struct iovec iov = {
+		.iov_base = &data,
+		.iov_len = sizeof(data),
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+		.msg_control = ctrl,
+		.msg_controllen = sizeof(ctrl),
+	};
+	struct cmsghdr *cmsg;
+
+	if (recvmsg(sk, &msg, 0) < 0)
+		return -1;
+
+	*msg_flags = msg.msg_flags;
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	if (!cmsg)
+		return 0;
+
+	nr_slots = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+	memcpy(slots, CMSG_DATA(cmsg), nr_slots * sizeof(int));
+
+	return nr_slots;
+}
+
+/* Prove a received fd works by reading back the file's content. */
+static int check_secret(int fd, int idx)
+{
+	char want[32], got[32] = {};
+
+	snprintf(want, sizeof(want), SECRET(idx));
+	if (pread(fd, got, sizeof(got) - 1, 0) < 0)
+		return -1;
+
+	return strcmp(want, got);
+}
+
+TEST_F(scm_rights_denial_bpf, all_allowed)
+{
+	int slots[NR_FILES], nr_slots, flags, i;
+
+	ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
+	ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
+	nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
+
+	ASSERT_EQ(NR_FILES, nr_slots);
+	EXPECT_EQ(0, flags & MSG_CTRUNC);
+
+	for (i = 0; i < NR_FILES; i++) {
+		ASSERT_LE(0, slots[i]);
+		EXPECT_EQ(0, check_secret(slots[i], i));
+		close(slots[i]);
+	}
+}
+
+TEST_F(scm_rights_denial_bpf, first_denied)
+{
+	int slots[NR_FILES], nr_slots, flags;
+
+	ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[0]));
+
+	ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
+	ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
+	nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
+
+	ASSERT_EQ(NR_FILES, nr_slots);
+	EXPECT_EQ(0, flags & MSG_CTRUNC);
+	EXPECT_EQ(-EPERM, slots[0]);
+
+	ASSERT_LE(0, slots[1]);
+	EXPECT_EQ(0, check_secret(slots[1], 1));
+	close(slots[1]);
+}
+
+TEST_F(scm_rights_denial_bpf, all_denied)
+{
+	int slots[NR_FILES], nr_slots, flags, i;
+
+	for (i = 0; i < NR_FILES; i++)
+		ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[i]));
+
+	ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
+	ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
+	nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
+
+	ASSERT_EQ(NR_FILES, nr_slots);
+	EXPECT_EQ(0, flags & MSG_CTRUNC);
+
+	for (i = 0; i < NR_FILES; i++)
+		EXPECT_EQ(-EPERM, slots[i]);
+}
+
+TEST_F(scm_rights_denial_bpf, denied_without_notrunc)
+{
+	int slots[NR_FILES], nr_slots, flags;
+
+	/*
+	 * Baseline behaviour without SO_RIGHTS_NOTRUNC: the fd array is
+	 * truncated at the first denied fd and MSG_CTRUNC is set.
+	 */
+	ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[1]));
+
+	ASSERT_NE(-1, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
+	nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
+
+	ASSERT_EQ(1, nr_slots);
+	EXPECT_NE(0, flags & MSG_CTRUNC);
+
+	ASSERT_LE(0, slots[0]);
+	EXPECT_EQ(0, check_secret(slots[0], 0));
+	close(slots[0]);
+}
+
+TEST_HARNESS_MAIN
-- 
2.55.0


^ permalink raw reply related

* [PATCH nf] ipvs: skip IPv6 extension headers in TCP state lookup
From: Yizhou Zhao @ 2026-07-05 12:56 UTC (permalink / raw)
  To: netdev
  Cc: Yizhou Zhao, Simon Horman, Julian Anastasov, Pablo Neira Ayuso,
	Florian Westphal, Phil Sutter, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, lvs-devel, netfilter-devel, coreteam,
	linux-kernel, Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li, Ke Xu,
	stable

tcp_state_transition() reads the TCP header again in order to drive the
IPVS TCP state table.  For IPv6 it computes the offset with
sizeof(struct ipv6hdr), while the surrounding IPVS code uses iph->len from
ip_vs_fill_iph_skb(), where ipv6_find_hdr() has already skipped extension
headers and found the real transport header.

This makes the state machine read from the wrong offset for IPv6 TCP
packets that carry extension headers.  For example, a SYN packet with an
8-byte destination options header can be scheduled correctly by
tcp_conn_schedule(), but tcp_state_transition() reads a byte from the real
TCP sequence number as the TCP flags.  An attacker can therefore make an
uncompleted SYN move from NONE to ESTABLISHED or CLOSE, changing the
connection timeout and active/inactive destination counters.  In the
ESTABLISHED case, the half-open connection gets the 15 minute established
timeout instead of the shorter SYN_RECV timeout, which can be used for a
remote denial of service against an IPv6 IPVS TCP service.

Use ip_vs_fill_iph_skb() in tcp_state_transition() and read the TCP header
from iph.len, matching tcp_conn_schedule() and the TCP NAT handlers.  For
IPv4 and IPv6 packets without extension headers this preserves the existing
offset.

Fixes: 0bbdd42b7efa ("IPVS: Extend protocol DNAT/SNAT and state handlers")
Cc: stable@vger.kernel.org
Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
Reported-by: Ao Wang <wangao@seu.edu.cn>
Reported-by: Xuewei Feng <fengxw06@126.com>
Reported-by: Qi Li <qli01@tsinghua.edu.cn>
Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
Assisted-by: Claude-Code:GLM-5.2
Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
---
 net/netfilter/ipvs/ip_vs_proto_tcp.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c
index 8cc0a8ce6241..6fde2165a8d6 100644
--- a/net/netfilter/ipvs/ip_vs_proto_tcp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c
@@ -581,15 +581,13 @@ tcp_state_transition(struct ip_vs_conn *cp, int direction,
 		     const struct sk_buff *skb,
 		     struct ip_vs_proto_data *pd)
 {
 	struct tcphdr _tcph, *th;
+	struct ip_vs_iphdr iph;
 
-#ifdef CONFIG_IP_VS_IPV6
-	int ihl = cp->af == AF_INET ? ip_hdrlen(skb) : sizeof(struct ipv6hdr);
-#else
-	int ihl = ip_hdrlen(skb);
-#endif
+	if (!ip_vs_fill_iph_skb(cp->af, skb, false, &iph))
+		return;
 
-	th = skb_header_pointer(skb, ihl, sizeof(_tcph), &_tcph);
+	th = skb_header_pointer(skb, iph.len, sizeof(_tcph), &_tcph);
 	if (th == NULL)
 		return;
 
-- 
2.47.3


^ permalink raw reply related

* Re: [PATCH net 2/3] ipv6: mcast: Fix potential UAF in MLD delayed work
From: Eric Dumazet @ 2026-07-05 13:58 UTC (permalink / raw)
  To: Ido Schimmel
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Kuniyuki Iwashima, David Ahern, netdev, eric.dumazet
In-Reply-To: <20260705105349.GA172278@shredder>

On Sun, Jul 5, 2026 at 3:54 AM Ido Schimmel <idosch@nvidia.com> wrote:
>
> On Sat, Jul 04, 2026 at 07:43:45PM +0000, Eric Dumazet wrote:
> > A race condition exists between device teardown and incoming MLD query
> > processing, leading to a Use-After-Free in the MLD delayed work.
> >
> > During device destruction, the primary reference to inet6_dev is dropped,
> > which can drop its refcount to 0. The actual freeing of inet6_dev memory
> > is deferred via RCU.
> >
> > Concurrently, the packet receive path runs under RCU read lock and obtains
> > the inet6_dev pointer. Because the memory is RCU-protected, CPU-0 can
> > safely dereference inet6_dev even if its refcount has hit 0.
> >
> > However, if CPU-0 calls igmp6_event_query() and schedules delayed work, it
> > attempts to acquire a reference using in6_dev_hold(). This increments the
> > refcount from 0 to 1, triggering a "refcount_t: addition on 0" warning.
> > Since the inet6_dev memory is still scheduled to be freed after the RCU
> > grace period, the device is freed while the work is still scheduled.
> > When the work runs, it accesses the freed memory, causing a kernel panic.
> >
> > Fix this by using refcount_inc_not_zero() (via a new helper
> > in6_dev_hold_safe()) to prevent acquiring a reference if the device is
> > already being destroyed. If the refcount is 0, we do not schedule the work.
> >
> > Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> > Signed-off-by: Eric Dumazet <edumazet@google.com>
>
> Eric, thanks for taking care of this!
>
> > ---
> >  include/net/addrconf.h |  5 +++++
> >  net/ipv6/mcast.c       | 38 ++++++++++++++++++++++++++++----------
> >  2 files changed, 33 insertions(+), 10 deletions(-)
>
> [...]
>
> > @@ -1395,6 +1401,7 @@ static void mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld,
> >  void igmp6_event_query(struct sk_buff *skb)
> >  {
> >       struct inet6_dev *idev = __in6_dev_get(skb->dev);
> > +     bool put = false;
> >
> >       if (!idev || idev->dead)
> >               goto out;
> > @@ -1402,11 +1409,16 @@ void igmp6_event_query(struct sk_buff *skb)
> >       spin_lock_bh(&idev->mc_query_lock);
> >       if (skb_queue_len(&idev->mc_query_queue) < MLD_MAX_SKBS) {
> >               __skb_queue_tail(&idev->mc_query_queue, skb);
>
> Shouldn't we only enqueue the skb if we managed to take a reference?
> Something like [1] (on top of this patch).
>
> If we failed to take a reference, then ipv6_mc_destroy_dev() already ran
> and the queue will not be purged, thereby leaking this skb.
>
> > -             if (!mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
> > -                     in6_dev_hold(idev);
> > +             if (in6_dev_hold_safe(idev)) {
> > +                     if (mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
> > +                             put = true;
> > +             }
> >               skb = NULL;
> >       }
> >       spin_unlock_bh(&idev->mc_query_lock);
> > +
> > +     if (put)
> > +             in6_dev_put(idev);
> >  out:
> >       kfree_skb(skb);
> >  }
> > @@ -1570,6 +1582,7 @@ static void mld_query_work(struct work_struct *work)
> >  void igmp6_event_report(struct sk_buff *skb)
> >  {
> >       struct inet6_dev *idev = __in6_dev_get(skb->dev);
> > +     bool put = false;
> >
> >       if (!idev || idev->dead)
> >               goto out;
> > @@ -1577,11 +1590,16 @@ void igmp6_event_report(struct sk_buff *skb)
> >       spin_lock_bh(&idev->mc_report_lock);
> >       if (skb_queue_len(&idev->mc_report_queue) < MLD_MAX_SKBS) {
> >               __skb_queue_tail(&idev->mc_report_queue, skb);
>
> Same here
>
> > -             if (!mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
> > -                     in6_dev_hold(idev);
> > +             if (in6_dev_hold_safe(idev)) {
> > +                     if (mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
> > +                             put = true;
> > +             }
> >               skb = NULL;
> >       }
> >       spin_unlock_bh(&idev->mc_report_lock);
> > +
> > +     if (put)
> > +             in6_dev_put(idev);
> >  out:
> >       kfree_skb(skb);
> >  }
>
> [1]
> diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
> index 12d22de6f496..aaba4c2aae23 100644
> --- a/net/ipv6/mcast.c
> +++ b/net/ipv6/mcast.c
> @@ -1408,12 +1408,11 @@ void igmp6_event_query(struct sk_buff *skb)
>                 goto out;
>
>         spin_lock_bh(&idev->mc_query_lock);
> -       if (skb_queue_len(&idev->mc_query_queue) < MLD_MAX_SKBS) {
> +       if (skb_queue_len(&idev->mc_query_queue) < MLD_MAX_SKBS &&
> +           in6_dev_hold_safe(idev)) {
>                 __skb_queue_tail(&idev->mc_query_queue, skb);
> -               if (in6_dev_hold_safe(idev)) {
> -                       if (mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
> -                               put = true;
> -               }
> +               if (mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
> +                       put = true;
>                 skb = NULL;
>         }
>         spin_unlock_bh(&idev->mc_query_lock);
> @@ -1589,12 +1588,11 @@ void igmp6_event_report(struct sk_buff *skb)
>                 goto out;
>
>         spin_lock_bh(&idev->mc_report_lock);
> -       if (skb_queue_len(&idev->mc_report_queue) < MLD_MAX_SKBS) {
> +       if (skb_queue_len(&idev->mc_report_queue) < MLD_MAX_SKBS &&
> +           in6_dev_hold_safe(idev)) {
>                 __skb_queue_tail(&idev->mc_report_queue, skb);
> -               if (in6_dev_hold_safe(idev)) {
> -                       if (mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
> -                               put = true;
> -               }
> +               if (mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
> +                       put = true;
>                 skb = NULL;
>         }
>         spin_unlock_bh(&idev->mc_report_lock);

Great catch, I will include this in V2.

Thanks.

^ permalink raw reply

* Re: [PATCH net 3/3] ipv4: igmp: Fix potential memory leak in igmp_mod_timer()
From: Eric Dumazet @ 2026-07-05 14:03 UTC (permalink / raw)
  To: Ido Schimmel
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Kuniyuki Iwashima, David Ahern, netdev, eric.dumazet
In-Reply-To: <20260705115839.GB194488@shredder>

On Sun, Jul 5, 2026 at 4:58 AM Ido Schimmel <idosch@nvidia.com> wrote:
>
> On Sat, Jul 04, 2026 at 07:43:46PM +0000, Eric Dumazet wrote:
> > When a timer is deleted and not re-armed in igmp_mod_timer(), the code
> > currently decrements the reference counter of the multicast list entry
> > @im using refcount_dec(&im->refcnt).
> >
> > However, igmp_mod_timer() can be called from the RCU reader path (e.g., in
> > igmp_heard_query() via for_each_pmc_rcu()). If the group im was
> > concurrently removed from the list by ip_mc_dec_group(), its reference count
> > might have already been decremented to 1.
> >
> > In this case, timer_delete() succeeds, and refcount_dec() decrements
> > the refcount from 1 to 0. Since refcount_dec() does not free the object
> > when it hits 0 (unlike ip_ma_put()), the im structure is leaked.
> >
> > Fix this by using ip_ma_put(im) instead of refcount_dec(&im->refcnt).
> >
> > Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> > Signed-off-by: Eric Dumazet <edumazet@google.com>
> > ---
> >  net/ipv4/igmp.c | 7 ++++++-
> >  1 file changed, 6 insertions(+), 1 deletion(-)
> >
> > diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
> > index f5f9763895641bf86bfcf9fd7fd7b06012fa4ece..2170b33ba147ce4990e3ee71ba4868e8696b00cb 100644
> > --- a/net/ipv4/igmp.c
> > +++ b/net/ipv4/igmp.c
> > @@ -266,6 +266,8 @@ static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
> >
> >  static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
> >  {
> > +     bool put = false;
> > +
> >       spin_lock_bh(&im->lock);
> >       im->unsolicit_count = 0;
> >       if (timer_delete(&im->timer)) {
> > @@ -275,10 +277,13 @@ static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
> >                       spin_unlock_bh(&im->lock);
> >                       return;
> >               }
> > -             refcount_dec(&im->refcnt);
> > +             put = true;
> >       }
> >       igmp_start_timer(im, max_delay);
> >       spin_unlock_bh(&im->lock);
> > +
> > +     if (put)
> > +             ip_ma_put(im);
> >  }
>
> Don't we also need something similar in igmp_stop_timer() [1]? It can
> also be called from an RCU reader (i.e., igmp_rcv() ->
> igmp_heard_report() -> igmp_stop_timer()).

Hmm, again you are right, thanks!

^ permalink raw reply

* Re: [PATCH net 2/3] net/sched: Handle TC_ACT_REDIRECT from qdisc filter chains
From: Jamal Hadi Salim @ 2026-07-05 14:03 UTC (permalink / raw)
  To: Paolo Abeni
  Cc: Daniel Borkmann, Jakub Kicinski, Sebastian Andrzej Siewior,
	Andrii Nakryiko, Kumar Kartikeya Dwivedi, bpf,
	Linux Kernel Network Developers, Victor Nogueira
In-Reply-To: <c526d9d9-db4c-45e8-96c5-52b2a9dca7e8@redhat.com>

On Sun, Jul 5, 2026 at 3:26 AM Paolo Abeni <pabeni@redhat.com> wrote:
>
> Hi,
>
> On 7/1/26 5:35 PM, Jamal Hadi Salim wrote:
> > On Tue, Jun 30, 2026 at 11:23 AM Daniel Borkmann <daniel@iogearbox.net> wrote:
> >>
> >> On 6/30/26 5:16 PM, Jamal Hadi Salim wrote:
> >>> On Tue, Jun 30, 2026 at 8:33 AM Daniel Borkmann <daniel@iogearbox.net> wrote:
> >>>> From: Jamal Hadi Salim <jhs@mojatatu.com>
> >>>>
> >>>> When a TC filter attached to a qdisc filter chain returns
> >>>> TC_ACT_REDIRECT (ex: via an eBPF program calling bpf_redirect() or an
> >>>> act_bpf action), the redirect was silently lost i.e no qdisc classify
> >>>> function handled TC_ACT_REDIRECT, so the packet fell through the
> >>>> switch and was enqueued normally instead of being redirected.
> >>>>
> >>>> This has been broken since bpf_redirect() was introduced for TC in
> >>>> commit 27b29f63058d ("bpf: add bpf_redirect() helper"). We got lucky
> >>>> for a long time because bpf_net_context was a per-CPU variable that
> >>>> was always available.
> >>>>
> >>>> commit 401cb7dae813 ("net: Reference bpf_redirect_info via task_struct
> >>>> on PREEMPT_RT.") turned bpf_net_context into a task_struct member that
> >>>> is only set up by explicit callers. Without a caller setting it up,
> >>>> bpf_redirect() itself crashes with a NULL pointer dereference in
> >>>> bpf_net_ctx_get_ri(). However, even with bpf_net_context available,
> >>>> TC_ACT_REDIRECT from qdisc filter chains cannot be honored without
> >>>> adding skb_do_redirect() calls to every qdisc classify function, which
> >>>> would require changes across net/sched/. Isolate it to ebpf core where
> >>>> it belongs.
> >>>>
> >>>> Instead, add a tcf_classify_qdisc() inline helper in pkt_cls.h, as a
> >>>> wrapper around tcf_classify() for use by qdisc classify functions and
> >>>> tcf_qevent_handle(). When the classify verdict is TC_ACT_REDIRECT,
> >>>> the wrapper converts it to TC_ACT_SHOT, dropping the packet rather
> >>>> than letting it continue silently. Dropping is preferred over
> >>>> letting the packet through because the user immediately sees packet
> >>>> loss. Silently passing the packet through would hide the problem and
> >>>> leave the user wondering why their redirect is not working.
> >>>>
> >>>> The clsact fast path, tc_run() continues to call tcf_classify() directly
> >>>> and is unaffected: TC_ACT_REDIRECT is returned as-is and handled by
> >>>> sch_handle_egress/ingress() calling skb_do_redirect() as before.
> >>>>
> >>>> Fixes: 27b29f63058d ("bpf: add bpf_redirect() helper")
> >>>> Fixes: 401cb7dae813 ("net: Reference bpf_redirect_info via task_struct on PREEMPT_RT.")
> >>>> Tested-by: Victor Nogueira <victor@mojatatu.com>
> >>>> Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
> >>>> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> >>>> ---
> >>>>   include/net/pkt_cls.h    | 14 +++++++++++++-
> >>>>   net/sched/cls_api.c      |  4 +---
> >>>>   net/sched/sch_cake.c     |  2 +-
> >>>>   net/sched/sch_drr.c      |  2 +-
> >>>>   net/sched/sch_dualpi2.c  |  2 +-
> >>>>   net/sched/sch_ets.c      |  2 +-
> >>>>   net/sched/sch_fq_codel.c |  2 +-
> >>>>   net/sched/sch_fq_pie.c   |  2 +-
> >>>>   net/sched/sch_hfsc.c     |  2 +-
> >>>>   net/sched/sch_htb.c      |  2 +-
> >>>>   net/sched/sch_multiq.c   |  2 +-
> >>>>   net/sched/sch_prio.c     |  2 +-
> >>>>   net/sched/sch_qfq.c      |  2 +-
> >>>>   net/sched/sch_sfb.c      |  2 +-
> >>>>   net/sched/sch_sfq.c      |  2 +-
> >>>>   15 files changed, 27 insertions(+), 17 deletions(-)
> >>>>
> >>>> diff --git a/include/net/pkt_cls.h b/include/net/pkt_cls.h
> >>>> index 3bd08d7f39c1..5f5cb36439fe 100644
> >>>> --- a/include/net/pkt_cls.h
> >>>> +++ b/include/net/pkt_cls.h
> >>>> @@ -156,8 +156,20 @@ static inline int tcf_classify(struct sk_buff *skb,
> >>>>   {
> >>>>          return TC_ACT_UNSPEC;
> >>>>   }
> >>>> -
> >>>>   #endif
> >>>> +static inline int tcf_classify_qdisc(struct sk_buff *skb,
> >>>> +                                    const struct tcf_proto *tp,
> >>>> +                                    struct tcf_result *res, bool compat_mode)
> >>>> +{
> >>>> +       int ret = tcf_classify(skb, NULL, tp, res, compat_mode);
> >>>> +
> >>>> +       /* TC_ACT_REDIRECT from qdisc filter chains is not supported.
> >>>> +        * Use BPF via tcx or mirred redirect instead.
> >>>> +        */
> >>>> +       if (unlikely(ret == TC_ACT_REDIRECT))
> >>>> +               ret = TC_ACT_SHOT;
> >>>> +       return ret;
> >>>> +}
> >>>
> >>> Why did you remove the warning?
> >>> A lesser issue is that you introduced a space above #endif
> >>
> >> I don't think we need to put out an extra warn to spam the log, this
> >> can be easily traced either via bpftrace or qdisc stats etc; plus the
> >> guidance to eBPF with clsact is also obsolete. Given noone has run
> >> into this the last 10y, I don't think it really matters tbh.
> >
> > It's a bit entitled for you to change someone else's patch to fit your
> > philosophy without a mention.
> > It's only one message per qdisc. But fine, let's keep it that way.
> > Sashiko is hallucinating on TC_ACT_CONSUMED for patch 1. Let's ignore it.
>
> @Jamal: I read the above as if you are ok with the series in the current
> form as a whole. Please LMK.
>

I am fine with series.

cheers,
jamal

> FTR, I have a slight preference towards the extra warn message in case
> of misconfiguration, but big deal either way.
>
> /P
>

^ permalink raw reply

* Re: [RFC] xdp: add device context to bpf_xdp_link_attach_failed tracepoint
From: Leon Hwang @ 2026-07-05 14:15 UTC (permalink / raw)
  To: Masashi Honma
  Cc: netdev, bpf, linux-trace-kernel, ast, daniel, kuba, hawk, andrii,
	rostedt, mhiramat, edumazet, pabeni, linux-kernel
In-Reply-To: <CAFk-A4mUijKffHg3QhOxC7Ne2rk_GVbYY2UJ-Ek497avdubzgw@mail.gmail.com>

On 2026/7/5 10:11, Masashi Honma wrote:
> 2026年7月4日(土) 22:28 Leon Hwang <leon.hwang@linux.dev>:
>> Probably, you can get the 'extack->_msg' by tracing dev_xdp_attach using
>> kprobe+kretprobe or kprobe.session, if the extack is not NULL.
> 
> Thanks, that's a nice pointer -- dev_xdp_attach() has both the net_device
> (so the ifindex, which lets us correlate a failure to a specific attach)
> and the extack, and it avoids depending on the tracepoint you want to
> retire.
> 
> The tradeoff is that dev_xdp_attach() is a static internal function, so a
> probe on it can break across kernels (inlining/signature changes). For a
> best-effort error message that's tolerable with a graceful fallback, but
> it's a maintenance cost on our side.
> 
> Since this is ultimately just an error-message improvement, and your
> in-band BPF_LINK_CREATE work would solve it cleanly for all link types, I
> think we'd lean toward waiting for that rather than adding an internal
> kprobe to Cilium. Do you have a rough timeline for the BPF_LINK_CREATE
> series? That would help us decide whether a stopgap is worth it.


I will post the RFC around October.

Thanks,
Leon


^ permalink raw reply

* Re: [PATCH net v2] net/mlx5: free mlx5_st_idx_data on final dealloc
From: Leon Romanovsky @ 2026-07-05 14:19 UTC (permalink / raw)
  To: Zhiping Zhang
  Cc: saeedm, tariqt, mbloch, andrew+netdev, davem, edumazet, kuba,
	pabeni, netdev, linux-rdma, linux-kernel, michaelgur, stable
In-Reply-To: <20260630165324.2859353-1-zhipingz@meta.com>

On Tue, Jun 30, 2026 at 09:53:20AM -0700, Zhiping Zhang wrote:
> Workloads that repeatedly allocate and release mkeys carrying TPH
> steering-tag hints (e.g. churning RDMA MRs) leak one
> struct mlx5_st_idx_data per cycle; kmemleak flags it as unreferenced
> and the kmalloc slab grows over time.
> 
> When the last reference to an ST table entry is dropped,
> mlx5_st_dealloc_index() removed the entry from idx_xa but the backing
> mlx5_st_idx_data allocation was never freed.
> 
> Free idx_data after the xa_erase() so the lifetime of the bookkeeping
> struct matches the lifetime of the ST entry it tracks.
> 
> Cc: stable@vger.kernel.org
> Fixes: 888a7776f4fb ("net/mlx5: Add support for device steering tag")
> Reviewed-by: Michael Gur <michaelgur@nvidia.com>
> Signed-off-by: Zhiping Zhang <zhipingz@meta.com>
> ---
> v2: respin per maintainer-netdev.rst; no code change.
> v1: https://lore.kernel.org/linux-rdma/20260612170406.3339093-1-zhipingz@meta.com/
> 
>  drivers/net/ethernet/mellanox/mlx5/core/lib/st.c | 1 +
>  1 file changed, 1 insertion(+)
> 

Thanks,
Reviewed-by: Leon Romanovsky <leonro@nvidia.com>

^ permalink raw reply

* Re: [PATCH net-next 1/2] dt-bindings: net: nfc: samsung,s3fwrn5: add S3NRN4V and clk-req-gpios
From: Conor Dooley @ 2026-07-05 14:47 UTC (permalink / raw)
  To: Jorijn van der Graaf
  Cc: Krzysztof Kozlowski, David Heidelberg, Andrew Lunn,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Rob Herring, Conor Dooley, oe-linux-nfc, netdev, devicetree,
	linux-kernel
In-Reply-To: <20260703202601.78563-2-jorijnvdgraaf@catcrafts.net>

[-- Attachment #1: Type: text/plain, Size: 2792 bytes --]

On Fri, Jul 03, 2026 at 10:26:00PM +0200, Jorijn van der Graaf wrote:
> The S3NRN4V is an S3FWRN5-family NFC + eSE controller found e.g. on the
> Fairphone 6 (SM7635). Add a compatible for it and document the optional
> clk-req-gpios property: when wired, the controller drives this line to
> request its reference clock (needed to generate the poll carrier), and the
> driver gates the clock on it instead of leaving it always-on.
> 
> The line is modelled as a GPIO rather than an interrupt because the driver
> reads its level to (re)synchronise the clock state, not just react to its
> edges. It is only meaningful on the S3NRN4V, so it is restricted to that
> compatible.
> 
> Assisted-by: Claude:claude-opus-4-8
> Assisted-by: Claude:claude-fable-5
> Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
> ---
>  .../bindings/net/nfc/samsung,s3fwrn5.yaml     | 23 ++++++++++++++++++-
>  1 file changed, 22 insertions(+), 1 deletion(-)
> 
> diff --git a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
> index 12baee457..3ebcd0933 100644
> --- a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
> +++ b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
> @@ -14,12 +14,20 @@ properties:
>      enum:
>        - samsung,s3fwrn5-i2c
>        - samsung,s3fwrn82
> +      - samsung,s3nrn4v-i2c

Why does the compatible contain the bus? The s3frn5 device probably only
has it because it's an old text binding, your new device shouldn't have
that.

pw-bot: changes-requested

Thanks,
Conor.

>  
>    en-gpios:
>      maxItems: 1
>      description:
>        Output GPIO pin used for enabling/disabling the chip
>  
> +  clk-req-gpios:
> +    maxItems: 1
> +    description:
> +      Input GPIO pin connected to the controller's clock-request output. When
> +      present, the reference clock is enabled in response to this signal
> +      instead of being left always-on.
> +
>    interrupts:
>      maxItems: 1
>  
> @@ -58,12 +66,25 @@ allOf:
>        properties:
>          compatible:
>            contains:
> -            const: samsung,s3fwrn5-i2c
> +            enum:
> +              - samsung,s3fwrn5-i2c
> +              - samsung,s3nrn4v-i2c
>      then:
>        required:
>          - interrupts
>          - reg
>  
> +  # The clock-request handshake only exists on the S3NRN4V.
> +  - if:
> +      not:
> +        properties:
> +          compatible:
> +            contains:
> +              const: samsung,s3nrn4v-i2c
> +    then:
> +      properties:
> +        clk-req-gpios: false
> +
>  examples:
>    - |
>      #include <dt-bindings/gpio/gpio.h>
> -- 
> 2.55.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH 0/7] rust: Use kernel style vertical imports in various drivers
From: Andrew Lunn @ 2026-07-05 15:03 UTC (permalink / raw)
  To: Miguel Ojeda, rust-for-linux, linux-kernel, Danilo Krummrich,
	Abdiel Janulgue, Daniel Almeida, Robin Murphy, Andreas Hindborg,
	Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
	Alice Ryhl, Trevor Gross, Tamir Duberstein, Alexandre Courbot,
	Onur Özkan, Drew Fustini, Guo Ren, Fu Wei, Michal Wilczynski,
	Uwe Kleine-König, Rafael J. Wysocki, Viresh Kumar,
	Jens Axboe, FUJITA Tomonori, Heiner Kallweit, Russell King,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	David Airlie, Simona Vetter, driver-core, linux-riscv, linux-pwm,
	linux-pm, linux-block, netdev, nova-gpu, dri-devel
In-Reply-To: <akmnh0AKFfu9-OYn@gurudas.dev>

> Is that reasoning off-base, or is the right path to split these out per subsystem
> regardless?

Adding to what others have said, some subsystems have there own CI
system which tests every patch. Does it build? Does it add new
warning, is sparse happy, are there more self test failures. What do
different AIs think of the patch etc. Cross subsystem patches don't
always hit the per subsystem CI tests.

So in general, it is better to submit per subsystem.

       Andrew

^ permalink raw reply

* Re: [PATCH net] qede: fix off-by-one in BD ring consumption on build_skb failure
From: Shigeru Yoshida @ 2026-07-05 15:53 UTC (permalink / raw)
  To: Jamie Bainbridge
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Matvey Kovalev, Pavel Zhigulin, netdev, linux-kernel
In-Reply-To: <CAAvyFNjLcVRQBuMsd31neQpo6BY4HBP-hNMHh=RtFeiWBo96ZA@mail.gmail.com>

Jamie Bainbridge <jamie.bainbridge@gmail.com> writes:

> On Fri, 3 Jul 2026 at 09:52, Jamie Bainbridge
> <jamie.bainbridge@gmail.com> wrote:
>>
>> On Wed, 1 Jul 2026 at 02:47, Shigeru Yoshida <syoshida@redhat.com> wrote:
>> >
>> > qede_rx_build_skb() and qede_tpa_rx_build_skb() do not check for a
>> > NULL return from qede_build_skb(). When it returns NULL under memory
>> > pressure, the functions still consume a BD from the ring before
>> > returning NULL. The callers then recycle additional BDs, resulting in
>> > one extra BD being consumed (off-by-one). This desynchronizes the BD
>> > ring, which can corrupt DMA page reference counts and lead to SLUB
>> > freelist corruption.
>>
>> Good catch.
>>
>> Reviewed-by: Jamie Bainbridge <jamie.bainbridge@gmail.com>
>
> Sorry for the double mail.
>
> I believe the Fixes: should be against the problematic original code:
>
> Fixes: 8a8633978b842 ("qede: Add build_skb() support.")
>
> because that is what you are fixing.

Thank you for your review.

The problematic code was introduced in commit 8a8633978b84 ("qede: Add
build_skb() support."), so putting this in the Fixes tag would be
correct.

I'll send the v2 patch with the modified Fixes tag.

Thank you,
Shigeru

> Jamie
>
>> > Commit 4e910dbe3650 ("qede: confirm skb is allocated before using")
>> > added a NULL check inside qede_build_skb() to prevent a NULL pointer
>> > dereference, but did not address the missing NULL checks in the
>> > callers, making this off-by-one reachable.
>> >
>> > Fix this by adding NULL checks for the return value of
>> > qede_build_skb() in both qede_rx_build_skb() and
>> > qede_tpa_rx_build_skb(), returning NULL immediately before any BD ring
>> > manipulation.
>> >
>> > Fixes: 4e910dbe3650 ("qede: confirm skb is allocated before using")
>> > Signed-off-by: Shigeru Yoshida <syoshida@redhat.com>
>> > ---
>> >  drivers/net/ethernet/qlogic/qede/qede_fp.c | 5 +++++
>> >  1 file changed, 5 insertions(+)
>> >
>> > diff --git a/drivers/net/ethernet/qlogic/qede/qede_fp.c b/drivers/net/ethernet/qlogic/qede/qede_fp.c
>> > index 33e18bb69774..c11e0d8f98aa 100644
>> > --- a/drivers/net/ethernet/qlogic/qede/qede_fp.c
>> > +++ b/drivers/net/ethernet/qlogic/qede/qede_fp.c
>> > @@ -765,6 +765,9 @@ qede_tpa_rx_build_skb(struct qede_dev *edev,
>> >         struct sk_buff *skb;
>> >
>> >         skb = qede_build_skb(rxq, bd, len, pad);
>> > +       if (unlikely(!skb))
>> > +               return NULL;
>> > +
>> >         bd->page_offset += rxq->rx_buf_seg_size;
>> >
>> >         if (bd->page_offset == PAGE_SIZE) {
>> > @@ -812,6 +815,8 @@ qede_rx_build_skb(struct qede_dev *edev,
>> >         }
>> >
>> >         skb = qede_build_skb(rxq, bd, len, pad);
>> > +       if (unlikely(!skb))
>> > +               return NULL;
>> >
>> >         if (unlikely(qede_realloc_rx_buffer(rxq, bd))) {
>> >                 /* Incr page ref count to reuse on allocation failure so
>> > --
>> > 2.54.0
>> >


^ permalink raw reply

* [PATCH net-next v3 4/8] net: mdio: realtek-rtl9300: Configure hardware polling during probing
From: Markus Stockhausen @ 2026-07-05 16:35 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
	chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
  Cc: Markus Stockhausen
In-Reply-To: <20260705163532.2853959-1-markus.stockhausen@gmx.de>

During PHY probing and configuration complex configuration sequences
might be issued and firmware might be loaded. Hardware polling can
interfere badly with that. E.g. a hardware polling MMD c45 over c22
request might break an ongoing firmware loading sequence.

To avoid such issues the polling of the Realtek Otto switches can be
(de)activated with one or two 32 bit mask registers. Each bit enables
(=1) or disables (=0) the polling of the corresponding port. Make use
of this as follows:

- Disable polling for all ports when the MDIO driver starts.
- Reenable polling just after the PHY has been attached.
- Disable polling just before the PHY is being detached.

This synchronizes the kernel and hardware polling to some extent. It
gracefully handles deferred probing of PHYs in case the driver is
loaded asynchronously during boot. Additionally it brings the hardware
polling into a consistent operation mode for devices where U-Boot does
not take care.

[1] https://github.com/openwrt/openwrt/blob/main/target/linux/realtek/files-6.18/drivers/net/mdio/mdio-realtek-otto.c#L818
[2] https://lore.kernel.org/netdev/680696024a8648535ce6dee771fe4de67802e0e8.1769053496.git.daniel@makrotopia.org/

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
 drivers/net/mdio/mdio-realtek-rtl9300.c | 77 +++++++++++++++++++++++++
 1 file changed, 77 insertions(+)

diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index 562f9c7f2895..c36244cd9a66 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -139,6 +139,7 @@
 #define   RTL9300_PHY_CTRL_INDATA		GENMASK(31, 16)
 #define   RTL9300_PHY_CTRL_DATA			GENMASK(15, 0)
 #define RTL9300_SMI_ACCESS_PHY_CTRL_3		0xcb7c
+#define RTL9300_SMI_POLL_CTRL			0xca90
 #define RTL9300_SMI_PORT0_5_ADDR_CTRL		0xcb80
 
 #define RTL9310_NUM_BUSES			4
@@ -164,6 +165,7 @@
 #define   RTL9310_PHY_CTRL_INDATA		GENMASK(15, 0)
 #define RTL9310_SMI_INDRT_ACCESS_MMD_CTRL	0x0c18
 #define RTL9310_SMI_PORT_ADDR_CTRL		0x0c74
+#define RTL9310_SMI_PORT_POLLING_CTRL		0x0ccc
 #define RTL9310_SMI_PORT_POLLING_SEL		0x0c9c
 
 #define PHY_CTRL_CMD				BIT(0)
@@ -194,6 +196,7 @@ struct otto_emdio_priv {
 	const struct otto_emdio_info *info;
 	struct regmap *regmap;
 	struct mutex lock; /* protect HW access */
+	DECLARE_BITMAP(phy_poll, MAX_PORTS);
 	DECLARE_BITMAP(valid_ports, MAX_PORTS);
 	u8 smi_bus[MAX_PORTS];
 	u8 smi_addr[MAX_PORTS];
@@ -211,6 +214,7 @@ struct otto_emdio_info {
 	u8 num_buses;
 	u8 num_ports;
 	u16 num_pages;
+	u32 poll_ctrl;
 	int (*setup_controller)(struct otto_emdio_priv *priv);
 	int (*read_c22)(struct mii_bus *bus, int port, int regnum, u32 *value);
 	int (*read_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u32 *value);
@@ -246,6 +250,14 @@ static struct otto_emdio_priv *otto_emdio_bus_to_priv(struct mii_bus *bus)
 	return chan->priv;
 }
 
+static int otto_emdio_set_port_polling(struct otto_emdio_priv *priv, int port, bool active)
+{
+	lockdep_assert_held(&priv->lock);
+
+	return regmap_assign_bits(priv->regmap, priv->info->poll_ctrl + (port / 32) * 4,
+				  BIT(port % 32), active);
+}
+
 static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
 			      struct otto_emdio_cmd_regs *cmd_data)
 {
@@ -576,6 +588,47 @@ static int otto_emdio_9310_setup_controller(struct otto_emdio_priv *priv)
 	return 0;
 }
 
+static int otto_emdio_notify_phy_attach(struct phy_device *phydev)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(phydev->mdio.bus);
+	int port = otto_emdio_phy_to_port(phydev->mdio.bus, phydev->mdio.addr);
+	int ret;
+
+	if (port < 0)
+		return port;
+
+	if (test_bit(port, priv->phy_poll))
+		return 0;
+
+	scoped_guard(mutex, &priv->lock) {
+		ret = otto_emdio_set_port_polling(priv, port, true);
+		if (!ret)
+			__set_bit(port, priv->phy_poll);
+	}
+
+	return ret;
+}
+
+static void otto_emdio_notify_phy_detach(struct phy_device *phydev)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(phydev->mdio.bus);
+	int port = otto_emdio_phy_to_port(phydev->mdio.bus, phydev->mdio.addr);
+	struct mii_bus *bus = phydev->mdio.bus;
+	int ret;
+
+	if (port < 0)
+		return;
+
+	scoped_guard(mutex, &priv->lock) {
+		ret = otto_emdio_set_port_polling(priv, port, false);
+		if (!ret)
+			__clear_bit(port, priv->phy_poll);
+	}
+	
+	if (ret)
+		dev_err(bus->parent, "failed to disable polling for port %d\n", port);
+}
+
 static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv,
 				 struct fwnode_handle *node)
 {
@@ -605,6 +658,9 @@ static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv
 		bus->write = otto_emdio_write_c22;
 	}
 	bus->parent = dev;
+	bus->notify_phy_attach = otto_emdio_notify_phy_attach;
+	bus->notify_phy_detach = otto_emdio_notify_phy_detach;
+
 	chan = bus->priv;
 	chan->mdio_bus = mdio_bus;
 	chan->priv = priv;
@@ -721,6 +777,21 @@ static int otto_emdio_map_ports(struct device *dev)
 	return err;
 }
 
+static int otto_emdio_init_polling(struct otto_emdio_priv *priv)
+{
+	int err;
+
+	for (int port = 0; port < priv->info->num_ports; port++) {
+		scoped_guard(mutex, &priv->lock) {
+			err = otto_emdio_set_port_polling(priv, port, false);
+			if (err)
+				return err;
+		}
+	}
+
+	return 0;
+}
+
 static int otto_emdio_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -740,6 +811,10 @@ static int otto_emdio_probe(struct platform_device *pdev)
 	if (IS_ERR(priv->regmap))
 		return PTR_ERR(priv->regmap);
 
+	err = otto_emdio_init_polling(priv);
+	if (err)
+		return err;
+
 	platform_set_drvdata(pdev, priv);
 
 	err = otto_emdio_map_ports(dev);
@@ -780,6 +855,7 @@ static const struct otto_emdio_info otto_emdio_9300_info = {
 	.num_buses = RTL9300_NUM_BUSES,
 	.num_ports = RTL9300_NUM_PORTS,
 	.num_pages = RTL9300_NUM_PAGES,
+	.poll_ctrl = RTL9300_SMI_POLL_CTRL,
 	.setup_controller = otto_emdio_9300_setup_controller,
 	.read_c22 = otto_emdio_9300_read_c22,
 	.read_c45 = otto_emdio_9300_read_c45,
@@ -805,6 +881,7 @@ static const struct otto_emdio_info otto_emdio_9310_info = {
 	.num_buses = RTL9310_NUM_BUSES,
 	.num_pages = RTL9310_NUM_PAGES,
 	.num_ports = RTL9310_NUM_PORTS,
+	.poll_ctrl = RTL9310_SMI_PORT_POLLING_CTRL,
 	.setup_controller = otto_emdio_9310_setup_controller,
 	.read_c22 = otto_emdio_9310_read_c22,
 	.read_c45 = otto_emdio_9310_read_c45,
-- 
2.54.0


^ permalink raw reply related

* [PATCH net-next v3 3/8] net: phy: add (*notify_phy_attach/detach)() hooks to struct mii_bus
From: Markus Stockhausen @ 2026-07-05 16:35 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
	chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
  Cc: Markus Stockhausen
In-Reply-To: <20260705163532.2853959-1-markus.stockhausen@gmx.de>

From: Daniel Golle <daniel@makrotopia.org>

Some MDIO buses require to program PHY polling registers depending on
the PHY type. RealTek switch SoCs are the most prominent example of a
DSA switch which doesn't allow to program MAC speed, duplex and
flow-control settings without using PHY polling to do so [1].

Avoid a half-baked solution in the MDIO bus driver because

- it must reinvent the bus scanning to determine the PHYs and
- it must anticipate the right point in time (e.g. deferred PHYs).

Hence there is a need to inform the MDIO bus driver that a PHY is
being attached or detached. Provide two simple hooks in struct
mii_bus which are called

- right after a PHY has been attached
- just before the PHY is going to be detached

Remark! A slightly different version of this patch was part of a
former series [2]. The discussion already showed that an initialization
hook should be placed somewhere late during the whole setup. This
commit implants it right after phy_init_hw() as suggested. On top of
this it adds the detach hook.

[1] https://github.com/openwrt/openwrt/pull/21515#discussion_r2714069716
[2] https://lore.kernel.org/netdev/cover.1769053496.git.daniel@makrotopia.org/

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
 drivers/net/phy/phy_device.c | 9 +++++++++
 include/linux/phy.h          | 4 ++++
 2 files changed, 13 insertions(+)

diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 0615228459ef..676cbf183350 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1876,6 +1876,12 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
 	if (err)
 		goto error;
 
+	if (phydev->mdio.bus->notify_phy_attach) {
+		err = phydev->mdio.bus->notify_phy_attach(phydev);
+		if (err)
+			goto error;
+	}
+
 	phy_resume(phydev);
 
 	/**
@@ -1919,6 +1925,9 @@ void phy_detach(struct phy_device *phydev)
 	struct module *ndev_owner = NULL;
 	struct mii_bus *bus;
 
+	if (phydev->mdio.bus->notify_phy_detach)
+		phydev->mdio.bus->notify_phy_detach(phydev);
+
 	if (phydev->devlink) {
 		device_link_del(phydev->devlink);
 		phydev->devlink = NULL;
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 199a7aaa341b..3160ca99deab 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -376,6 +376,10 @@ struct mii_bus {
 			 int regnum, u16 val);
 	/** @reset: Perform a reset of the bus */
 	int (*reset)(struct mii_bus *bus);
+	/** @notify_phy_attach: Perform post-attach handling */
+	int (*notify_phy_attach)(struct phy_device *phydev);
+	/** @notify_phy_detach: Perform pre-detach handling */
+	void (*notify_phy_detach)(struct phy_device *phydev);
 
 	/** @stats: Statistic counters per device on the bus */
 	struct mdio_bus_stats stats[PHY_MAX_ADDR];
-- 
2.54.0


^ permalink raw reply related

* [PATCH net-next v3 0/8] net: mdio: realtek-rtl9300: Add RTL83xx support
From: Markus Stockhausen @ 2026-07-05 16:35 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
	chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
  Cc: Markus Stockhausen

The Realtek Otto switch platform consists of four different series

- RTL838x aka maple   : 28 port 1G Switches
- RTL839x aka cypress : 52 port 1G Switches
- RTL930x aka longan  : 28 port 1G/2.5G/10G Switches
- RTL931x aka mango   : 56 port 1G/2.5G/10G Switches

While the MDIO hardware polling unit and its necessity for the MAC 
layer was always well known, no detailed documentation was available.
For this series the MDIO bus was inspected with a logic analyzer for 
a better understanding how polling and kernel access interact on the 
bus. All this will be explained now in the driver comments.

This patch series adds support for the RTL83xx devices. For this

- Enhance device tree binding.
- Add special handling for limitations enforced by hardware polling.
  These already have minor side effects on RTL93xx devices but are even
  more critical for the RTL83xx hardware.
- Add RTL83xx coding.

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---

v2 -> v3:
  - Enhance documentation and make clear that the driver and not the
    kernel must handle the bus mess. (Andrew)
  - Block non-Realtek PHYs on C22 buses to avoid issues with driver
    internal register 31 handling. (Andrew, Chris)
  - Drop C45 over C22 patch. This would need a bus lock/unlock design.
    For all known hardware designs it is not needed. (Andrew)
  - Drop tune_polling() and init_polling() from private structure.
    It is not used in this series and only produces review bot
    questions. (Sashiko)
  - Sort patches for better logical consistency. (Markus)
  - Add device tree patch that was missed in v2. (Markus)
v2: https://lore.kernel.org/netdev/20260629152336.2239826-1-markus.stockhausen@gmx.de/
v2 Sashiko review: https://sashiko.dev/#/patchset/20260629152336.2239826-1-markus.stockhausen%40gmx.de

v1 -> v2:
  - The polling activation logic was refactored. V1 simply activated
    polling after bus probing. Now a dedicated phydev/bus callback
    takes care of this and also handles deferred PHY probing. (Sashiko)
  - Run MMD prefix helper before register 31 (aka Realtek page register) 
    handling. (Jakub's bot)
  - Always run MMD postfix - even if the c22 register access fails. This 
    ensures that the MMD state machine stays consistent. Adapt the error 
    handling inside the postfix function to not overwrite the real MDIO 
    return code (Sashiko, Jakub's bot)
  - Drop unused RTL8390_PHY_CTRL_PARK_PAGE define. Like on RTL931x this 
    field must not be set and thus can be ignored. (Sashiko)
  - Change title in device tree documentation. Because of this do NOT 
    add the Reviewed-by of Krzysztof. (Jakub's bot)
  - Fix wrong use of RTL839x in commit message of patch "c45 over c22 
    mitigation". RTL930x was wrongly named RTL839x in the list of good 
    devices. (Markus)
  - Fix typos (e.g. c22 over c45) in polling documentation (Jakub's bot)
v1: https://lore.kernel.org/netdev/20260613112946.1071411-1-markus.stockhausen@gmx.de/
v1 Sashiko review: https://sashiko.dev/#/patchset/20260613112946.1071411-1-markus.stockhausen@gmx.de


Daniel Golle (1):
  net: phy: add (*notify_phy_attach/detach)() hooks to struct mii_bus

Markus Stockhausen (7):
  dt-bindings: net: realtek,rtl9301-mdio: Add RTL83xx series
  net: mdio: realtek-rtl9300: Add polling documentation
  net: mdio: realtek-rtl9300: Configure hardware polling during probing
  net: mdio: realtek-rtl9300: Add page tracking
  net: mdio: realtek-rtl9300: Increase MDIO timeout
  net: mdio: realtek-rtl9300: Add support for RTL838x
  net: mdio: realtek-rtl9300: Add support for RTL839x

 .../bindings/net/realtek,rtl9301-mdio.yaml    |  14 +-
 drivers/net/mdio/mdio-realtek-rtl9300.c       | 395 +++++++++++++++++-
 drivers/net/phy/phy_device.c                  |   9 +
 include/linux/phy.h                           |   4 +
 4 files changed, 412 insertions(+), 10 deletions(-)

-- 
2.54.0


^ permalink raw reply

* [PATCH net-next v3 6/8] net: mdio: realtek-rtl9300: Increase MDIO timeout
From: Markus Stockhausen @ 2026-07-05 16:35 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
	chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
  Cc: Markus Stockhausen
In-Reply-To: <20260705163532.2853959-1-markus.stockhausen@gmx.de>

RTL838x devices with 28 ports produce PHY access timeout errors during
one of three boots while waiting for MDIO command completion. This is
currently set to 1ms.

Background: Access to the Realtek Otto ethernet MDIO bus must wait for
a free slot between two hardware polls. The polling sequence consists
of at least 17 commands on the RTL838x devices. This delay can be nicely
seen when disabling polling completely. The following times are measured
on a bus running on the default 2.5MHz. Time measured is from the last
register write that sets the command-start-bit until the hardware
responds with the command-finished-bit set.

- average c22 read with polling enabled on all ports: ~380us
- average c22 read with polling enabled on one port: ~380us
- average c22 read with polling completely disabled: ~180us

For this bus frequency the bare hardware runtime for a single command
(32 bit preamble + 32 bit data) is ~25us. So the hardware adds quite
some overhead. On top of this comes the fact that the RTL838x devices
are low on resources (500Mhz 4Kec core with 16K cache).

Increase the timeout to 10ms to be on the safe side.

Remark! In a future patch the bus clock frequency will be made
configurable with a minimum frequency of 1.25MHz. Setting this
(e.g. for debugging purposes) doubles the command run times but
will safely stay below 10ms.

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
 drivers/net/mdio/mdio-realtek-rtl9300.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index a5cac0d04114..c73e370d912a 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -268,9 +268,9 @@ static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
 	u32 cmdstate;
 	int ret;
 
-	/* Defensive pre check just in case something goes horrible wrong */
+	/* Defensive pre check just in case something goes horribly wrong */
 	ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data,
-				       cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000);
+				       cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 10000);
 	if (ret)
 		return ret;
 
@@ -310,7 +310,7 @@ static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
 		return ret;
 
 	ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data,
-				       cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000);
+				       cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 10000);
 	if (ret)
 		return ret;
 
-- 
2.54.0


^ permalink raw reply related

* [PATCH net-next v3 8/8] net: mdio: realtek-rtl9300: Add support for RTL839x
From: Markus Stockhausen @ 2026-07-05 16:35 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
	chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
  Cc: Markus Stockhausen
In-Reply-To: <20260705163532.2853959-1-markus.stockhausen@gmx.de>

The MDIO driver has been prepared for multiple device support. Add all
required bits for the RTL839x (aka cypress) series. This is straightforward
but some things are worth mentioning.

- The device has a lot in common with the RTL931x series. 8192 (Realtek)
  pages and 7 MMIO registers
- There are two SMI buses for 1G PHYs. Neither the bus nor address map
  registers exist.
- The hardware has not much to configure. So the setup_controller()
  function is not needed.
- c22 read/write functions must be called with PARK_PAGE = 0. Keep code
  clean and avoid setting it to zero, matching the behavior of the RTL9310
  logic.

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
 drivers/net/mdio/mdio-realtek-rtl9300.c | 102 ++++++++++++++++++++++++
 1 file changed, 102 insertions(+)

diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index e7c811614fd6..d797d8f01c66 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -141,6 +141,28 @@
 #define RTL8380_SMI_POLL_CTRL			0xa17c
 #define RTL8380_SMI_PORT0_5_ADDR_CTRL		0xa1c8
 
+#define RTL8390_NUM_BUSES			2
+#define RTL8390_NUM_PAGES			8192
+#define RTL8390_NUM_PORTS			52
+#define RTL8390_BCAST_PHYID_CTRL		0x03ec
+#define RTL8390_PHYREG_ACCESS_CTRL		0x03dc
+#define   RTL8390_PHY_CTRL_REG_ADDR		GENMASK(9, 5)
+#define   RTL8390_PHY_CTRL_MAIN_PAGE		GENMASK(22, 10)
+#define   RTL8390_PHY_CTRL_FAIL			BIT(1)
+#define   RTL8390_PHY_CTRL_WRITE		BIT(3)
+#define   RTL8390_PHY_CTRL_READ			0
+#define   RTL8390_PHY_CTRL_TYPE_C45		BIT(2)
+#define   RTL8390_PHY_CTRL_TYPE_C22		0
+#define RTL8390_PHYREG_CTRL			0x03e0
+#define   RTL8390_PHY_CTRL_EXT_PAGE		GENMASK(8, 0)
+#define RTL8390_PHYREG_DATA_CTRL		0x03f0
+#define   RTL8390_PHY_CTRL_INDATA		GENMASK(31, 16)
+#define   RTL8390_PHY_CTRL_DATA			GENMASK(15, 0)
+#define RTL8390_PHYREG_MMD_CTRL			0x03f4
+#define RTL8390_PHYREG_PORT_CTRL_LOW		0x03e4
+#define RTL8390_PHYREG_PORT_CTRL_HIGH		0x03e8
+#define RTL8390_SMI_PORT_POLLING_CTRL		0x03fc
+
 #define RTL9300_NUM_BUSES			4
 #define RTL9300_NUM_PAGES			4096
 #define RTL9300_NUM_PORTS			28
@@ -423,6 +445,62 @@ static int otto_emdio_8380_write_c45(struct mii_bus *bus, int port,
 	return otto_emdio_write_cmd(bus, RTL8380_PHY_CTRL_TYPE_C45, &cmd_data);
 }
 
+static int otto_emdio_8390_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c22_data	= FIELD_PREP(RTL8390_PHY_CTRL_REG_ADDR, regnum) |
+				  FIELD_PREP(RTL8390_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+		.ext_page	= FIELD_PREP(RTL8390_PHY_CTRL_EXT_PAGE, 0x1ff),
+		.io_data	= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, port),
+	};
+
+	return otto_emdio_read_cmd(bus, RTL8390_PHY_CTRL_TYPE_C22, &cmd_data,
+				   RTL8390_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8390_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c22_data	= FIELD_PREP(RTL8390_PHY_CTRL_REG_ADDR, regnum) |
+				  FIELD_PREP(RTL8390_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+		.ext_page	= FIELD_PREP(RTL8390_PHY_CTRL_EXT_PAGE, 0x1ff),
+		.io_data	= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, value),
+		.port_mask_high	= (u32)(BIT_ULL(port) >> 32),
+		.port_mask_low	= (u32)(BIT_ULL(port)),
+	};
+
+	return otto_emdio_write_cmd(bus, RTL8390_PHY_CTRL_TYPE_C22, &cmd_data);
+}
+
+static int otto_emdio_8390_read_c45(struct mii_bus *bus, int port,
+				    int dev_addr, int regnum, u32 *value)
+{
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
+				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
+		.io_data	= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, port),
+	};
+
+	return otto_emdio_read_cmd(bus, RTL8390_PHY_CTRL_TYPE_C45, &cmd_data,
+				   RTL8390_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8390_write_c45(struct mii_bus *bus, int port,
+				     int dev_addr, int regnum, u16 value)
+{
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
+				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
+		.io_data	= FIELD_PREP(RTL8390_PHY_CTRL_INDATA, value),
+		.port_mask_high	= (u32)(BIT_ULL(port) >> 32),
+		.port_mask_low	= (u32)(BIT_ULL(port)),
+	};
+
+	return otto_emdio_write_cmd(bus, RTL8390_PHY_CTRL_TYPE_C45, &cmd_data);
+}
+
 static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
 {
 	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
@@ -969,6 +1047,29 @@ static const struct otto_emdio_info otto_emdio_8380_info = {
 	.write_c45 = otto_emdio_8380_write_c45,
 };
 
+static const struct otto_emdio_info otto_emdio_8390_info = {
+	.cmd_fail = RTL8390_PHY_CTRL_FAIL,
+	.cmd_read = RTL8390_PHY_CTRL_READ,
+	.cmd_write = RTL8390_PHY_CTRL_WRITE,
+	.cmd_regs = {
+		.broadcast = RTL8390_BCAST_PHYID_CTRL,
+		.c22_data = RTL8390_PHYREG_ACCESS_CTRL,
+		.c45_data = RTL8390_PHYREG_MMD_CTRL,
+		.ext_page = RTL8390_PHYREG_CTRL,
+		.io_data = RTL8390_PHYREG_DATA_CTRL,
+		.port_mask_low = RTL8390_PHYREG_PORT_CTRL_LOW,
+		.port_mask_high = RTL8390_PHYREG_PORT_CTRL_HIGH,
+	},
+	.num_buses = RTL8390_NUM_BUSES,
+	.num_pages = RTL8390_NUM_PAGES,
+	.num_ports = RTL8390_NUM_PORTS,
+	.poll_ctrl = RTL8390_SMI_PORT_POLLING_CTRL,
+	.read_c22 = otto_emdio_8390_read_c22,
+	.read_c45 = otto_emdio_8390_read_c45,
+	.write_c22 = otto_emdio_8390_write_c22,
+	.write_c45 = otto_emdio_8390_write_c45,
+};
+
 static const struct otto_emdio_info otto_emdio_9300_info = {
 	.addr_map_base = RTL9300_SMI_PORT0_5_ADDR_CTRL,
 	.bus_map_base = RTL9300_SMI_PORT0_15_POLLING_SEL,
@@ -1020,6 +1121,7 @@ static const struct otto_emdio_info otto_emdio_9310_info = {
 
 static const struct of_device_id otto_emdio_ids[] = {
 	{ .compatible = "realtek,rtl8380-mdio", .data = &otto_emdio_8380_info },
+	{ .compatible = "realtek,rtl8391-mdio", .data = &otto_emdio_8390_info },
 	{ .compatible = "realtek,rtl9301-mdio", .data = &otto_emdio_9300_info },
 	{ .compatible = "realtek,rtl9311-mdio", .data = &otto_emdio_9310_info },
 	{}
-- 
2.54.0


^ permalink raw reply related

* [PATCH net-next v3 7/8] net: mdio: realtek-rtl9300: Add support for RTL838x
From: Markus Stockhausen @ 2026-07-05 16:35 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
	chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
  Cc: Markus Stockhausen
In-Reply-To: <20260705163532.2853959-1-markus.stockhausen@gmx.de>

The MDIO driver has been prepared for multiple device support. Add all
required bits for the RTL838x (aka maple) series. This is straightforward
but some things are worth mentioning.

- The device has a lot in common with the RTL930x series. 28 ports, 4096
  (Realtek) pages, 4 MMIO registers
- The MDIO engine has no fail bit. Thus the mask is set to zero
- There is only one SMI bus for 1G PHYs. No bus_map_base register exists.
- The setup_controller() function needs no c45 setup but must activate
  the PHY access.

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
 drivers/net/mdio/mdio-realtek-rtl9300.c | 108 ++++++++++++++++++++++++
 1 file changed, 108 insertions(+)

diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index c73e370d912a..e7c811614fd6 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -119,6 +119,28 @@
 #include <linux/property.h>
 #include <linux/regmap.h>
 
+#define RTL8380_NUM_BUSES			1
+#define RTL8380_NUM_PAGES			4096
+#define RTL8380_NUM_PORTS			28
+#define RTL8380_SMI_GLB_CTRL			0xa100
+#define   RTL8380_SMI_PHY_PATCH_DONE		BIT(15)
+#define RTL8380_SMI_ACCESS_PHY_CTRL_0		0xa1b8
+#define RTL8380_SMI_ACCESS_PHY_CTRL_1		0xa1bc
+#define   RTL8380_PHY_CTRL_REG_ADDR		GENMASK(24, 20)
+#define   RTL8380_PHY_CTRL_PARK_PAGE		GENMASK(19, 15)
+#define   RTL8380_PHY_CTRL_MAIN_PAGE		GENMASK(14, 3)
+#define   RTL8380_PHY_CTRL_WRITE		BIT(2)
+#define   RTL8380_PHY_CTRL_READ			0
+#define   RTL8380_PHY_CTRL_TYPE_C45		BIT(1)
+#define   RTL8380_PHY_CTRL_TYPE_C22		0
+#define   RTL8380_PHY_CTRL_FAIL			0 /* no fail indicator */
+#define RTL8380_SMI_ACCESS_PHY_CTRL_2		0xa1c0
+#define   RTL8380_PHY_CTRL_INDATA		GENMASK(31, 16)
+#define   RTL8380_PHY_CTRL_DATA			GENMASK(15, 0)
+#define RTL8380_SMI_ACCESS_PHY_CTRL_3		0xa1c4
+#define RTL8380_SMI_POLL_CTRL			0xa17c
+#define RTL8380_SMI_PORT0_5_ADDR_CTRL		0xa1c8
+
 #define RTL9300_NUM_BUSES			4
 #define RTL9300_NUM_PAGES			4096
 #define RTL9300_NUM_PORTS			28
@@ -347,6 +369,60 @@ static int otto_emdio_write_cmd(struct mii_bus *bus, u32 cmd,
 	return otto_emdio_run_cmd(bus, cmd | priv->info->cmd_write, cmd_data);
 }
 
+static int otto_emdio_8380_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c22_data	= FIELD_PREP(RTL8380_PHY_CTRL_REG_ADDR, regnum) |
+				  FIELD_PREP(RTL8380_PHY_CTRL_PARK_PAGE, 0x1f) |
+				  FIELD_PREP(RTL8380_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+		.io_data	= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, port),
+	};
+
+	return otto_emdio_read_cmd(bus, RTL8380_PHY_CTRL_TYPE_C22, &cmd_data,
+				   RTL8380_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8380_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)
+{
+	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c22_data	= FIELD_PREP(RTL8380_PHY_CTRL_REG_ADDR, regnum) |
+				  FIELD_PREP(RTL8380_PHY_CTRL_PARK_PAGE, 0x1f) |
+				  FIELD_PREP(RTL8380_PHY_CTRL_MAIN_PAGE, priv->page[port]),
+		.io_data	= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, value),
+		.port_mask_low	= BIT(port),
+	};
+
+	return otto_emdio_write_cmd(bus, RTL8380_PHY_CTRL_TYPE_C22, &cmd_data);
+}
+
+static int otto_emdio_8380_read_c45(struct mii_bus *bus, int port,
+				    int dev_addr, int regnum, u32 *value)
+{
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
+				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
+		.io_data	= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, port),
+	};
+
+	return otto_emdio_read_cmd(bus, RTL8380_PHY_CTRL_TYPE_C45, &cmd_data,
+				   RTL8380_PHY_CTRL_DATA, value);
+}
+
+static int otto_emdio_8380_write_c45(struct mii_bus *bus, int port,
+				     int dev_addr, int regnum, u16 value)
+{
+	struct otto_emdio_cmd_regs cmd_data = {
+		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
+				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
+		.io_data	= FIELD_PREP(RTL8380_PHY_CTRL_INDATA, value),
+		.port_mask_low	= BIT(port),
+	};
+
+	return otto_emdio_write_cmd(bus, RTL8380_PHY_CTRL_TYPE_C45, &cmd_data);
+}
+
 static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
 {
 	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
@@ -567,6 +643,15 @@ static int otto_emdio_setup_topology(struct otto_emdio_priv *priv)
 	return 0;
 }
 
+static int otto_emdio_8380_setup_controller(struct otto_emdio_priv *priv)
+{
+	/*
+	 * PHY_PATCH_DONE enables PHY control via SoC. This is required for PHY access, including
+	 * patching and must be set before the PHYs are probed.
+	 */
+	return regmap_set_bits(priv->regmap, RTL8380_SMI_GLB_CTRL, RTL8380_SMI_PHY_PATCH_DONE);
+}
+
 static int otto_emdio_9300_setup_controller(struct otto_emdio_priv *priv)
 {
 	u32 glb_ctrl_mask = 0, glb_ctrl_val = 0;
@@ -862,6 +947,28 @@ static int otto_emdio_probe(struct platform_device *pdev)
 	return 0;
 }
 
+static const struct otto_emdio_info otto_emdio_8380_info = {
+	.addr_map_base = RTL8380_SMI_PORT0_5_ADDR_CTRL,
+	.cmd_fail = RTL8380_PHY_CTRL_FAIL,
+	.cmd_read = RTL8380_PHY_CTRL_READ,
+	.cmd_write = RTL8380_PHY_CTRL_WRITE,
+	.cmd_regs = {
+		.c22_data = RTL8380_SMI_ACCESS_PHY_CTRL_1,
+		.c45_data = RTL8380_SMI_ACCESS_PHY_CTRL_3,
+		.io_data = RTL8380_SMI_ACCESS_PHY_CTRL_2,
+		.port_mask_low = RTL8380_SMI_ACCESS_PHY_CTRL_0,
+	},
+	.num_buses = RTL8380_NUM_BUSES,
+	.num_pages = RTL8380_NUM_PAGES,
+	.num_ports = RTL8380_NUM_PORTS,
+	.poll_ctrl = RTL8380_SMI_POLL_CTRL,
+	.setup_controller = otto_emdio_8380_setup_controller,
+	.read_c22 = otto_emdio_8380_read_c22,
+	.read_c45 = otto_emdio_8380_read_c45,
+	.write_c22 = otto_emdio_8380_write_c22,
+	.write_c45 = otto_emdio_8380_write_c45,
+};
+
 static const struct otto_emdio_info otto_emdio_9300_info = {
 	.addr_map_base = RTL9300_SMI_PORT0_5_ADDR_CTRL,
 	.bus_map_base = RTL9300_SMI_PORT0_15_POLLING_SEL,
@@ -912,6 +1019,7 @@ static const struct otto_emdio_info otto_emdio_9310_info = {
 };
 
 static const struct of_device_id otto_emdio_ids[] = {
+	{ .compatible = "realtek,rtl8380-mdio", .data = &otto_emdio_8380_info },
 	{ .compatible = "realtek,rtl9301-mdio", .data = &otto_emdio_9300_info },
 	{ .compatible = "realtek,rtl9311-mdio", .data = &otto_emdio_9310_info },
 	{}
-- 
2.54.0


^ permalink raw reply related

* [PATCH net-next v3 5/8] net: mdio: realtek-rtl9300: Add page tracking
From: Markus Stockhausen @ 2026-07-05 16:35 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
	chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
  Cc: Markus Stockhausen
In-Reply-To: <20260705163532.2853959-1-markus.stockhausen@gmx.de>

The hardware polling unit of the Realtek switches has a very special
handling for PHY register 31 (aka Realtek page register) in place.

- On the RTL838x it is permanently reset to zero.
- On other devices there is some magic saving/restoring (aka parking)
  in the background in place.

This makes access to PHYs a gamble.

As of now all known existing hardware designs have Realtek PHYs for 1G
connectivity. Otherwise the polling engine and the MAC status update
will not work at all and the vendor SDK would fail totally.

This driver differentiates clearly between C22 and C45 buses. During
probing it enables only one of the protocols for a bus. So it is safe
to assume that any C22 access will only target a Realtek based 1G PHY.
For safety reasons block any non-Realtek PHY on a C22 bus.

Intercept access to register 31 and handle it internally. Store the
desired value for each port in the driver. When issuing hardware access
to other registers add the page to the command towards the controller.
This given, the hardware will run two consecutive c22 commands that are
not interrupted by polling.

  ... hardware poll ...
  phy_write(phy, 31, page)
  phy_write(phy, reg, value)
  ... hardware poll ...

Remark! To keep this simple, writes to register 31 are only accepted if
they are lower than the device specific raw page - 0..4094/8190.
Otherwise -EINVAL is returned. Under the above assumption (Only 1G
Realtek PHYs on c22 bus) this is no limitation.

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
 drivers/net/mdio/mdio-realtek-rtl9300.c | 34 ++++++++++++++++++++-----
 1 file changed, 28 insertions(+), 6 deletions(-)

diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index c36244cd9a66..a5cac0d04114 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -171,6 +171,7 @@
 #define PHY_CTRL_CMD				BIT(0)
 #define PHY_CTRL_MMD_DEVAD			GENMASK(20, 16)
 #define PHY_CTRL_MMD_REG			GENMASK(15, 0)
+#define PHY_VENDOR_REALTEK			0x001cc800
 
 #define MAP_ADDRS_PER_REG			6
 #define MAP_BITS_PER_ADDR			5
@@ -198,6 +199,7 @@ struct otto_emdio_priv {
 	struct mutex lock; /* protect HW access */
 	DECLARE_BITMAP(phy_poll, MAX_PORTS);
 	DECLARE_BITMAP(valid_ports, MAX_PORTS);
+	u16 page[MAX_PORTS];
 	u8 smi_bus[MAX_PORTS];
 	u8 smi_addr[MAX_PORTS];
 	bool smi_bus_is_c45[MAX_SMI_BUSSES];
@@ -351,7 +353,7 @@ static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u
 	struct otto_emdio_cmd_regs cmd_data = {
 		.c22_data	= FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |
 				  FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |
-				  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+				  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, priv->page[port]),
 		.io_data	= FIELD_PREP(RTL9300_PHY_CTRL_INDATA, port),
 	};
 
@@ -365,7 +367,7 @@ static int otto_emdio_9300_write_c22(struct mii_bus *bus, int port, int regnum,
 	struct otto_emdio_cmd_regs cmd_data = {
 		.c22_data	= FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |
 				  FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |
-				  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+				  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, priv->page[port]),
 		.io_data	= FIELD_PREP(RTL9300_PHY_CTRL_INDATA, value),
 		.port_mask_low	= BIT(port),
 	};
@@ -405,7 +407,7 @@ static int otto_emdio_9310_read_c22(struct mii_bus *bus, int port, int regnum, u
 	struct otto_emdio_cmd_regs cmd_data = {
 		.broadcast	= FIELD_PREP(RTL9310_BC_PORT_ID, port),
 		.c22_data	= FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |
-				  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+				  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, priv->page[port]),
 	};
 
 	return otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, &cmd_data,
@@ -417,7 +419,7 @@ static int otto_emdio_9310_write_c22(struct mii_bus *bus, int port, int regnum,
 	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
 	struct otto_emdio_cmd_regs cmd_data = {
 		.c22_data	= FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |
-				  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+				  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, priv->page[port]),
 		.io_data	= FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value),
 		.port_mask_high	= (u32)(BIT_ULL(port) >> 32),
 		.port_mask_low	= (u32)(BIT_ULL(port)),
@@ -463,8 +465,12 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
 	if (port < 0)
 		return port;
 
-	scoped_guard(mutex, &priv->lock)
+	scoped_guard(mutex, &priv->lock) {
+		if (regnum == 31)
+			return priv->page[port];
+
 		ret = priv->info->read_c22(bus, port, regnum, &value);
+	}
 
 	return ret ? ret : value;
 }
@@ -478,8 +484,17 @@ static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16
 	if (port < 0)
 		return port;
 
-	scoped_guard(mutex, &priv->lock)
+	scoped_guard(mutex, &priv->lock) {
+		if (regnum == 31) {
+			if (value >= RAW_PAGE(priv))
+				return -EINVAL;
+
+			priv->page[port] = value;
+			return 0;
+		}
+
 		ret = priv->info->write_c22(bus, port, regnum, value);
+	}
 
 	return ret;
 }
@@ -592,6 +607,7 @@ static int otto_emdio_notify_phy_attach(struct phy_device *phydev)
 {
 	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(phydev->mdio.bus);
 	int port = otto_emdio_phy_to_port(phydev->mdio.bus, phydev->mdio.addr);
+	struct otto_emdio_chan *chan = phydev->mdio.bus->priv;
 	int ret;
 
 	if (port < 0)
@@ -600,6 +616,12 @@ static int otto_emdio_notify_phy_attach(struct phy_device *phydev)
 	if (test_bit(port, priv->phy_poll))
 		return 0;
 
+	if (!priv->smi_bus_is_c45[chan->mdio_bus] &&
+	    (!phy_id_compare_vendor(phydev->phy_id, PHY_VENDOR_REALTEK))) {
+		phydev_err(phydev, "Only Realtek PHYs allowed on C22 bus\n");
+		return -EOPNOTSUPP;
+	}
+
 	scoped_guard(mutex, &priv->lock) {
 		ret = otto_emdio_set_port_polling(priv, port, true);
 		if (!ret)
-- 
2.54.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox