* [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
* [PATCH net-next v3 1/8] dt-bindings: net: realtek,rtl9301-mdio: Add RTL83xx series
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 lower end Realtek Otto switches provide 1G only and are divided into
two series:
- Maple : RTL838x up to 28 ports
- Cypress: RTL839x up to 56 ports
The Maple based devices have 3 different SoCs: RTL8380, RTL8381 and
RTL8382. The Cypress series consists of the RTL8391, RTL8392 and
RTL8393 SoCs. The MDIO controller of these switches works like the
existing RTL93xx logic but has different characteristics and different
registers. Add new compatibles in the device tree.
With the extended compatibility list change the title to better reflect
the scope of. Especially add the "Ethernet" tag as these devices have
multiple MDIO controllers.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
.../bindings/net/realtek,rtl9301-mdio.yaml | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml b/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
index 271e05bae9c5..67e0b23a8470 100644
--- a/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
+++ b/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
@@ -4,7 +4,7 @@
$id: http://devicetree.org/schemas/net/realtek,rtl9301-mdio.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
-title: Realtek RTL9300 MDIO Controller
+title: Realtek Otto Switches Ethernet MDIO Controller
maintainers:
- Chris Packham <chris.packham@alliedtelesis.co.nz>
@@ -12,6 +12,16 @@ maintainers:
properties:
compatible:
oneOf:
+ - items:
+ - enum:
+ - realtek,rtl8381-mdio
+ - realtek,rtl8382-mdio
+ - const: realtek,rtl8380-mdio
+ - items:
+ - enum:
+ - realtek,rtl8392-mdio
+ - realtek,rtl8393-mdio
+ - const: realtek,rtl8391-mdio
- items:
- enum:
- realtek,rtl9302b-mdio
@@ -24,6 +34,8 @@ properties:
- realtek,rtl9313-mdio
- const: realtek,rtl9311-mdio
- enum:
+ - realtek,rtl8380-mdio
+ - realtek,rtl8391-mdio
- realtek,rtl9301-mdio
- realtek,rtl9311-mdio
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v3 2/8] net: mdio: realtek-rtl9300: Add polling documentation
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>
Add a detailed explanation how the hardware polling unit in the
Realtek Otto switches works. This simplifies developing future
patches and reviewing them.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 68 +++++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index 892ed3780a65..562f9c7f2895 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -35,6 +35,74 @@
*
* The driver works out the mapping based on the MDIO bus described in device tree and phandles on
* the ethernet-ports property.
+ *
+ * The devices have a hardware polling unit that runs in the background without any CPU load. It
+ * constantly scans the MDIO bus and the attached PHYs and updates the MAC status registers.
+ *
+ * How does the polling work?
+ *
+ * Each device has a SMI_POLL_CTRL register. A per-port bitmask decides if the hardware polling of
+ * the associated bus/address is active or not. The hardware runs a tight loop over this and for
+ * each set polling bit it issues a status check for the PHY. Attaching a logic analyzer to the
+ * MDIO bus of an RTL8380 and RTL8393 gives the following commands (in kernel notation):
+ *
+ * RTL8380 RTL8393
+ * --------------------------- ---------------------------
+ * phy_write(phy, 31, 0x0); phy_read(phy, 0);
+ * phy_write(phy, 13, 0x7); phy_read(phy, 1);
+ * phy_write(phy, 14, 0x3c); phy_read(phy, 4);
+ * phy_write(phy, 13, 0x8007); phy_read(phy, 5);
+ * phy_read(phy, 14); phy_read(phy, 6);
+ * phy_write(phy, 13, 0x7); phy_read(phy, 9);
+ * phy_write(phy, 14, 0x3d); phy_read(phy, 10);
+ * phy_write(phy, 13, 0x8007); phy_read(phy, 15);
+ * phy_read(phy, 14); phy_write(phy, 13, 0x7);
+ * phy_read(phy, 9); phy_write(phy, 14, 0x3c);
+ * phy_read(phy, 10); phy_write(phy, 13, 0x4007);
+ * phy_read(phy, 15); phy_read(phy, 14);
+ * phy_read(phy, 0); phy_write(phy, 13, 0x7);
+ * phy_read(phy, 1); phy_write(phy, 14, 0x3d);
+ * phy_read(phy, 4); phy_write(phy, 13, 0x4007);
+ * phy_read(phy, 5); phy_read(phy, 14);
+ * phy_read(phy, 6);
+ *
+ * From the above snippets it polls MDIO_AN_EEE_ADV and MDIO_AN_EEE_LPABLE via C45 over C22. As
+ * of now it is unclear how the hardware decides if a PHY supports these registers. After one PHY
+ * status is read, the polling engine goes over to the next PHY. Basically the bus is always busy
+ * and the MAC status is updated in real-time.
+ *
+ * How does MDIO access from kernel work?
+ *
+ * When issuing MDIO accesses via an MMIO based interface the final write to the command register
+ * sets a "run command now" bit. Between two polling sequences for different PHYs the hardware
+ * checks if a user command needs to run and sends it onto the bus. Afterwards it simply continues
+ * its polling work. Inspecting the command sequence for a paged read on the logic analyzer gives:
+ *
+ * RTL8380 RTL8393
+ * --------------------------- ---------------------------
+ * phy_write(phy, 31, page); phy_write(phy, 31, page);
+ * phy_write(phy, reg, value); phy_write(phy, reg, value);
+ * phy_write(phy, 31, 0);
+ *
+ * What does this mean?
+ *
+ * There are slight differences in polling and PHY access between the models but the challenge
+ * stays the same. On the one hand that greatly simplifies the MAC layer, on the other hand it
+ * has some implications for the kernel PHY subsystem.
+ *
+ * - Without the polling and a proper MAC status, some of the link handling features do not work.
+ * Especially an unpopulated MAC_LINK_STS register cancels operations to other MAC registers.
+ * - The Realtek page register 31 is magically modified in the background so that polling will
+ * read the right data. On the RTL838x polling simply resets it to zero. Other devices seem
+ * to track the page access "magically" in the background.
+ * - A C45 over C22 kernel access sequence is most likely to fail because chances are high that
+ * the polling engine overwrites registers 13/14 in between.
+ * - PHY firmware loading can have issues. Especially if a PHY is designed to expect a clean
+ * sequence of registers and values without deviation.
+ * - An access to one PHY will need to wait for the next free slot of the polling engine.
+ *
+ * Conclusion: The Realtek MDIO bus driver PHY access must know and handle any interference that
+ * arises from the above described hardware polling.
*/
#include <linux/bitfield.h>
--
2.54.0
^ permalink raw reply related
* [PATCH net-next] net: ethernet: qualcomm: remove unneeded 'fast_io' parameter in regmap_config
From: Wolfram Sang @ 2026-07-05 16:41 UTC (permalink / raw)
To: linux-kernel
Cc: Wolfram Sang, Luo Jie, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev
When using MMIO with regmap, fast_io is implied. No need to set it
again.
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
---
No dependencies, can be applied directly to the subsystem tree. Buildbot is
happy, too.
drivers/net/ethernet/qualcomm/ppe/ppe.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe.c b/drivers/net/ethernet/qualcomm/ppe/ppe.c
index be747510d947..3c301e609d3e 100644
--- a/drivers/net/ethernet/qualcomm/ppe/ppe.c
+++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c
@@ -106,7 +106,6 @@ static const struct regmap_config regmap_config_ipq9574 = {
.rd_table = &ppe_reg_table,
.wr_table = &ppe_reg_table,
.max_register = 0xbef800,
- .fast_io = true,
};
static int ppe_clock_init_and_reset(struct ppe_device *ppe_dev)
--
2.51.0
^ permalink raw reply related
* Re: [PATCH net-next v2] ipv4: hold a consistent view of rt->dst.dev under RCU
From: Ido Schimmel @ 2026-07-05 16:46 UTC (permalink / raw)
To: xuanqiang.luo
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Kuniyuki Iwashima, netdev,
linux-kernel, Xuanqiang Luo
In-Reply-To: <20260701032434.17500-1-xuanqiang.luo@linux.dev>
On Wed, Jul 01, 2026 at 11:24:34AM +0800, xuanqiang.luo@linux.dev wrote:
> From: Xuanqiang Luo <luoxuanqiang@kylinos.cn>
>
> rt_flush_dev() walks the per-CPU uncached route list and rewrites
> rt->dst.dev in-place to blackhole_netdev under spin_lock_bh().
> This lock does not exclude RCU readers, which may load rt->dst.dev
> multiple times within a single rcu_read_lock() region.
>
> ip_rt_send_redirect() is a typical example: it reads rt->dst.dev
> three times to obtain in_dev, the L3 master ifindex, and net.
> A concurrent device unregistration can repoint rt->dst.dev to
> blackhole_netdev between those reads, making the reader combine
> state from two different net_devices — for instance, an in_dev
> from the real device but a netns and peer lookup from the blackhole
> device. ip_rt_get_source() has the same problem: it reads
> rt->dst.dev four times to obtain the output ifindex, the netns,
> and the source address, so a concurrent flush can cause the source
> selection to mix state from different devices.
Why only change ip_rt_send_redirect() and ip_rt_get_source() when the
patch is titled "ipv4: hold a consistent view of rt->dst.dev under RCU"?
What is the criterion?
>
> Take a single dst_dev_rcu() snapshot of rt->dst.dev at the start
> of each affected RCU reader and use that snapshot throughout, so
> concurrent flushes cannot cause mid-function inconsistency.
> Publish the in-place write in rt_flush_dev() with rcu_assign_pointer()
> to match the readers.
The rt_flush_dev() change should be a separate change. Note that
dst_dev_put() was already converted to use rcu_assign_pointer().
>
> Fixes: caacf05e5ad1a ("ipv4: Properly purge netdev references on uncached routes.")
Please remove the Fixes tag given you are targeting net-next.
> Signed-off-by: Xuanqiang Luo <luoxuanqiang@kylinos.cn>
> ---
> v2:
> - Use dst_dev_rcu() and dev_net_rcu() for the RCU readers.
> - Use rcu_assign_pointer() when publishing the uncached route device
> replacement.
> - Slightly adjust the commit message wording because this issue was found
> by inspection, not from an observed user-visible failure.
>
> v1: https://lore.kernel.org/all/20260630094250.29386-1-xuanqiang.luo@linux.dev/
>
> net/ipv4/route.c | 29 +++++++++++++++++------------
> 1 file changed, 17 insertions(+), 12 deletions(-)
>
> diff --git a/net/ipv4/route.c b/net/ipv4/route.c
> index 3f3de5164d6e5..57f38467e6d0c 100644
> --- a/net/ipv4/route.c
> +++ b/net/ipv4/route.c
> @@ -873,6 +873,7 @@ static void ipv4_negative_advice(struct sock *sk,
> void ip_rt_send_redirect(struct sk_buff *skb)
> {
> struct rtable *rt = skb_rtable(skb);
> + struct net_device *dev;
https://docs.kernel.org/process/maintainer-netdev.html#local-variable-ordering-reverse-xmas-tree-rcs
Same in other places.
^ permalink raw reply
* Re: [PATCH net-next v11 0/2] net: mana: add ethtool private flag for full-page RX buffers
From: Dipayaan Roy @ 2026-07-05 17:17 UTC (permalink / raw)
To: Maciej Fijalkowski
Cc: kys, haiyangz, wei.liu, decui, andrew+netdev, davem, edumazet,
kuba, pabeni, leon, longli, kotaranov, horms, shradhagupta,
ssengar, ernis, shirazsaleem, linux-hyperv, netdev, linux-kernel,
linux-rdma, stephen, jacob.e.keller, dipayanroy, leitao, kees,
john.fastabend, hawk, bpf, daniel, ast, sdf, yury.norov,
pavan.chebbi
In-Reply-To: <akUn+nMBrjbUts2N@boxer>
On Wed, Jul 01, 2026 at 04:45:14PM +0200, Maciej Fijalkowski wrote:
> On Wed, Jul 01, 2026 at 07:15:44AM -0700, Dipayaan Roy wrote:
> > On some ARM64 platforms with 4K PAGE_SIZE, utilizing page_pool
> > fragments for allocation in the RX refill path (~2kB buffer per fragment)
> > causes 15-20% throughput regression under high connection counts
> > (>16 TCP streams at 180+ Gbps). Using full-page buffers on these
> > platforms shows no regression and restores line-rate performance.
> >
> > This behavior is observed on a single platform; other platforms
> > perform better with page_pool fragments, indicating this is not a
> > page_pool issue but platform-specific.
> >
> > This series adds an ethtool private flag "full-page-rx" to let the
> > user opt in to one RX buffer per page:
> >
> > ethtool --set-priv-flags eth0 full-page-rx on
> >
> > There is no behavioral change by default. The flag can be persisted
> > via udev rule for affected platforms.
>
> Were you able to track down what is the actual bottleneck on the 'broken'
> platform? What is the performance of full-page approach on healthy
> platforms? On changelog below you mention the frag approach 'outperforms'
> the full-page one.
>
Hi Maciej,
The HW team identified a PCIE root port stall occurring due to a PCIe
errata in a HW IP used for this platform. Using full pages increases the
time in packet refill path and indirectly helping to reduce the back pressure
in NIC pipeline caused due to the stall in root port. As per them it
will be fixed in next version of this hw which is not anytime soon.
On various other healthy platforms with 4k base page size we tested, we see
improvements using page fragments than full pages around anywhere between 5 to 15%.
Regards
Dipayaan Roy
> >
> > This series depends on the following fixes now merged in net-next:
> > commit 17bfe0a8c014 ("net: mana: Add NULL guards in teardown path to prevent panic on attach failure")
> > commit 5b05aa36ee24 ("net: mana: Skip redundant detach on already-detached port")
> >
> > Changes in v11:
> > - Rebased on net-next
> > Changes in v10:
> > - Rebased on net-next which now includes the prerequisite fixes.
> > - Recovery logic in mana_set_priv_flags() leverages the idempotent
> > mana_detach() from the merged fixes.
> > Changes in v9:
> > - Added correct tree.
> > Changes in v8:
> > - Fixed queue_reset_work recovery by restoring port_is_up before
> > scheduling reset so the handler can properly re-attach.
> > - Simplified "err && schedule_port_reset" to "schedule_port_reset".
> > Changes in v7:
> > - Rebased onto net-next.
> > - Retained private flag approach after David Wei's testing on
> > Grace (ARM64) confirmed that fragment mode outperforms
> > full-page mode on other platforms, validating this is a
> > single-platform workaround rather than a generic issue.
> > Changes in v6:
> > - Added missed maintainers.
> > Changes in v5:
> > - Split prep refactor into separate patch (patch 1/2)
> > Changes in v4:
> > - Dropping the smbios string parsing and add ethtool priv flag
> > to reconfigure the queues with full page rx buffers.
> > Changes in v3:
> > - changed u8* to char*
> > Changes in v2:
> > - separate reading string index and the string, remove inline.
> >
> > Dipayaan Roy (2):
> > net: mana: refactor mana_get_strings() and mana_get_sset_count() to
> > use switch
> > net: mana: force full-page RX buffers via ethtool private flag
> >
> > drivers/net/ethernet/microsoft/mana/mana_en.c | 22 ++-
> > .../ethernet/microsoft/mana/mana_ethtool.c | 178 +++++++++++++++---
> > include/net/mana/mana.h | 8 +
> > 3 files changed, 177 insertions(+), 31 deletions(-)
> >
> > --
> > 2.43.0
> >
> >
^ permalink raw reply
* Re: [PATCH nf] ipvs: skip IPv6 extension headers in SCTP state lookup
From: Julian Anastasov @ 2026-07-05 17:35 UTC (permalink / raw)
To: Yizhou Zhao
Cc: netdev, Simon Horman, 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
In-Reply-To: <20260705123040.35755-1-zhaoyz24@mails.tsinghua.edu.cn>
Hello,
On Sun, 5 Jul 2026, Yizhou Zhao wrote:
> 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;
May be it is better starting from ip_vs_set_state()
to provide new arg 'int iph_len/offset' (set to iph.len), down to
state_transition(), sctp_state_transition() and set_sctp_state().
Same for all protos. It should cost less stack and ipv6_find_hdr()
calls and what matters most, correct iph context in case we
have IP+ICMP+TCP (with just two ports or even with TCP flags)
and are scheduling ICMP, i.e. not IP+TCP as usually.
But what I see is that ip_vs_in_icmp*() are missing
the ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd) call just
after ip_vs_in_stats() and before ip_vs_icmp_xmit() where
we should provide ciph.len. That is why we don't reach the
set_tcp_state() calls to set correct cp->state and timeout
when scheduling related ICMP. So, this should be fixed too.
> - 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
Regards
--
Julian Anastasov <ja@ssi.bg>
^ permalink raw reply
* [PATCH v2 net 0/3] ipv4/ipv6: Fix UAF and memory leak in IGMP/MLD
From: Eric Dumazet @ 2026-07-05 18:17 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
This series addresses two potential UAF vulnerabilities
and memory leaks in the IPv4 IGMP and IPv6 MLD subsystems.
The first two patches fix a UAF where the packet receive path races with
device teardown. If the device refcount has already hit 0 (but the memory
is still held by RCU), incoming IGMP/MLD packets trying to schedule delayed
work or timers would call refcount_inc() on the 0 refcount, triggering a
warning and eventually leading to a UAF when the work runs after the device
has been freed. This is fixed by introducing safe hold helpers using
refcount_inc_not_zero(). In MLD, we also ensure we only enqueue the skb
if we successfully acquired the device reference, to avoid leaking skbs
when the device is being destroyed.
The third patch fixes memory leaks in IPv4 IGMP when timers are deleted or
stopped. When a timer is deleted (in igmp_mod_timer) or stopped (in
igmp_stop_timer) and not re-armed, the code dropped the group refcount using
refcount_dec(). However, if the group was concurrently removed from the list,
this decrement could drop the refcount to 0 without triggering the
cleanup/free path, leaking the group structure. This is fixed by using
ip_ma_put() instead, and deferring the put until after the lock is released.
Eric Dumazet (3):
ipv4: igmp: Fix potential UAF in igmp_gq_start_timer()
ipv6: mcast: Fix potential UAF in MLD delayed work
ipv4: igmp: Fix potential memory leaks in igmp_mod_timer() and
igmp_stop_timer()
include/linux/inetdevice.h | 5 +++++
include/net/addrconf.h | 5 +++++
net/ipv4/igmp.c | 28 +++++++++++++++++++-------
net/ipv6/mcast.c | 40 ++++++++++++++++++++++++++------------
4 files changed, 59 insertions(+), 19 deletions(-)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply
* [PATCH v2 net 1/3] ipv4: igmp: Fix potential UAF in igmp_gq_start_timer()
From: Eric Dumazet @ 2026-07-05 18:17 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet, Zero Day Initiative
In-Reply-To: <20260705181756.963063-1-edumazet@google.com>
A race condition exists between device teardown (inetdev_destroy) and
incoming IGMP query processing (igmp_rcv), leading to a Use-After-Free
in the IGMP timer callback.
During device destruction, inetdev_destroy() drops the primary reference
to in_device, which can drop its refcount to 0. The actual freeing of
in_device memory is deferred via RCU (using call_rcu()).
Concurrently, igmp_rcv() runs under RCU read lock and obtains the
in_device pointer. Because the memory is RCU-protected, CPU-0 can safely
dereference in_device even if its refcount has hit 0.
However, if CPU-0 calls igmp_gq_start_timer() and re-arms the timer, it
attempts to acquire a reference using in_dev_hold(). This increments the
refcount from 0 to 1, triggering a "refcount_t: addition on 0" warning.
Since the in_device memory is still scheduled to be freed after the RCU
grace period (as the free callback does not check the refcount again),
the device is freed while the timer is still armed. When the timer
expires, it accesses the freed memory, causing a kernel panic.
Fix this by using refcount_inc_not_zero() (via a new helper
in_dev_hold_safe()) to prevent acquiring a reference if the device is
already being destroyed. If the refcount is 0, we do not arm the timer.
A similar issue in IPv6 MLD is fixed in a subsequent patch.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Zero Day Initiative <zdi-disclosures@trendmicro.com>
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/linux/inetdevice.h | 5 +++++
net/ipv4/igmp.c | 14 +++++++++-----
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h
index dccbeb25f70141982160c776f3cc727296def2b7..6032eea2539a60d0476d85667bda3bfcad8f1425 100644
--- a/include/linux/inetdevice.h
+++ b/include/linux/inetdevice.h
@@ -293,6 +293,11 @@ static inline void in_dev_put(struct in_device *idev)
#define __in_dev_put(idev) refcount_dec(&(idev)->refcnt)
#define in_dev_hold(idev) refcount_inc(&(idev)->refcnt)
+static inline bool in_dev_hold_safe(struct in_device *idev)
+{
+ return refcount_inc_not_zero(&idev->refcnt);
+}
+
#endif /* __KERNEL__ */
static __inline__ __be32 inet_make_mask(int logmask)
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index b6337a47c1418589bf8ef31e00fd98b6187f5271..f5f9763895641bf86bfcf9fd7fd7b06012fa4ece 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -248,16 +248,20 @@ static void igmp_gq_start_timer(struct in_device *in_dev)
return;
in_dev->mr_gq_running = 1;
- if (!mod_timer(&in_dev->mr_gq_timer, exp))
- in_dev_hold(in_dev);
+ if (in_dev_hold_safe(in_dev)) {
+ if (mod_timer(&in_dev->mr_gq_timer, exp))
+ in_dev_put(in_dev);
+ }
}
static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
{
- int tv = get_random_u32_below(delay);
+ if (in_dev_hold_safe(in_dev)) {
+ int tv = get_random_u32_below(delay);
- if (!mod_timer(&in_dev->mr_ifc_timer, jiffies+tv+2))
- in_dev_hold(in_dev);
+ if (mod_timer(&in_dev->mr_ifc_timer, jiffies + tv + 2))
+ in_dev_put(in_dev);
+ }
}
static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox