* [PATCH bpf v4 2/4] bpf, sockmap: Reject unhashed UDP sockets on sockmap update
From: Michal Luczaj @ 2026-07-07 4:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
John Fastabend, Jakub Sitnicki, Jiayuan Chen, Eric Dumazet,
Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn, David S. Miller,
Jakub Kicinski, Simon Horman, Cong Wang
Cc: Michal Luczaj, bpf, linux-kselftest, linux-kernel, netdev
In-Reply-To: <20260707-sockmap-lookup-udp-leak-v4-0-f878346f27ab@rbox.co>
UDP sockets get SOCK_RCU_FREE set when (auto-)bound. This means
sk_is_refcounted(unbound) = true, while sk_is_refcounted(bound) = false.
Because sockmap accepts unbound UDP sockets, a BPF program can increment a
socket's refcount via lookup. If the socket is subsequently bound, the
transition from unbound to bound causes bpf_sk_release() to skip the
decrement of the refcount, causing a memory leak.
unreferenced object 0xffff88810bc2eb40 (size 1984):
comm "test_progs", pid 2451, jiffies 4295320596
hex dump (first 32 bytes):
7f 00 00 01 7f 00 00 01 d2 04 1b b7 04 d2 00 00 ................
02 00 01 40 00 00 00 00 00 00 00 00 00 00 00 00 ...@............
backtrace (crc bdee079d):
kmem_cache_alloc_noprof+0x557/0x660
sk_prot_alloc+0x69/0x240
sk_alloc+0x30/0x460
inet_create+0x2ce/0xf80
__sock_create+0x25b/0x5c0
__sys_socket+0x119/0x1d0
__x64_sys_socket+0x72/0xd0
do_syscall_64+0xa1/0x5f0
entry_SYSCALL_64_after_hwframe+0x76/0x7e
Instead of special-casing for refcounted sockets, reject unhashed UDP
sockets during sockmap updates, as there is no benefit to supporting those.
This effectively reverts the commit under Fixes, with two exceptions:
1. sock_map_sk_state_allowed() maintains a fall-through `return true`.
2. In the spirit of commit b8b8315e39ff ("bpf, sockmap: Remove unhash
handler for BPF sockmap usage"), the proto::unhash BPF handler is not
reintroduced.
Historical note: this issue is related to commit 67312adc96b5 ("bpf: reject
unhashed sockets in bpf_sk_assign").
Fixes: 0c48eefae712 ("sock_map: Lift socket state restriction for datagram sockets")
Suggested-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com>
Reviewed-by: John Fastabend <john.fastabend@gmail.com>
Signed-off-by: Michal Luczaj <mhal@rbox.co>
---
net/core/sock_map.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/net/core/sock_map.c b/net/core/sock_map.c
index c60ba6d292f9..9efbd8ca7db8 100644
--- a/net/core/sock_map.c
+++ b/net/core/sock_map.c
@@ -542,6 +542,8 @@ static bool sock_map_sk_state_allowed(const struct sock *sk)
{
if (sk_is_tcp(sk))
return (1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_LISTEN);
+ if (sk_is_udp(sk))
+ return sk_hashed(sk);
if (sk_is_stream_unix(sk))
return (1 << READ_ONCE(sk->sk_state)) & TCPF_ESTABLISHED;
if (sk_is_vsock(sk) &&
--
2.55.0
^ permalink raw reply related
* [PATCH bpf v4 3/4] selftests/bpf: Adapt sockmap update error handling
From: Michal Luczaj @ 2026-07-07 4:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
John Fastabend, Jakub Sitnicki, Jiayuan Chen, Eric Dumazet,
Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn, David S. Miller,
Jakub Kicinski, Simon Horman, Cong Wang
Cc: Michal Luczaj, bpf, linux-kselftest, linux-kernel, netdev
In-Reply-To: <20260707-sockmap-lookup-udp-leak-v4-0-f878346f27ab@rbox.co>
Update sockmap_listen to accommodate the recent change in sockmap that
rejects unbound UDP sockets.
TCP: Reject unbound and bound (unless established or listening).
UDP: Accept only bound sockets.
While at it, migrate to ASSERT_* and enforce reverse xmas tree.
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com>
Signed-off-by: Michal Luczaj <mhal@rbox.co>
---
.../selftests/bpf/prog_tests/sockmap_listen.c | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
index cc0c68bab907..1c96a3cf4b97 100644
--- a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
+++ b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c
@@ -53,8 +53,8 @@ static void test_insert_opened(struct test_sockmap_listen *skel __always_unused,
int family, int sotype, int mapfd)
{
u32 key = 0;
- u64 value;
int err, s;
+ u64 value;
s = xsocket(family, sotype, 0);
if (s == -1)
@@ -63,11 +63,8 @@ static void test_insert_opened(struct test_sockmap_listen *skel __always_unused,
errno = 0;
value = s;
err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST);
- if (sotype == SOCK_STREAM) {
- if (!err || errno != EOPNOTSUPP)
- FAIL_ERRNO("map_update: expected EOPNOTSUPP");
- } else if (err)
- FAIL_ERRNO("map_update: expected success");
+ ASSERT_ERR(err, "map_update");
+ ASSERT_EQ(errno, EOPNOTSUPP, "errno");
xclose(s);
}
@@ -77,8 +74,8 @@ static void test_insert_bound(struct test_sockmap_listen *skel __always_unused,
struct sockaddr_storage addr;
socklen_t len = 0;
u32 key = 0;
- u64 value;
int err, s;
+ u64 value;
init_addr_loopback(family, &addr, &len);
@@ -93,8 +90,12 @@ static void test_insert_bound(struct test_sockmap_listen *skel __always_unused,
errno = 0;
value = s;
err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST);
- if (!err || errno != EOPNOTSUPP)
- FAIL_ERRNO("map_update: expected EOPNOTSUPP");
+ if (sotype == SOCK_STREAM) {
+ ASSERT_ERR(err, "map_update");
+ ASSERT_EQ(errno, EOPNOTSUPP, "errno");
+ } else {
+ ASSERT_OK(err, "map_update");
+ }
close:
xclose(s);
}
@@ -1289,7 +1290,7 @@ static void test_ops(struct test_sockmap_listen *skel, struct bpf_map *map,
/* insert */
TEST(test_insert_invalid),
TEST(test_insert_opened),
- TEST(test_insert_bound, SOCK_STREAM),
+ TEST(test_insert_bound),
TEST(test_insert),
/* delete */
TEST(test_delete_after_insert),
--
2.55.0
^ permalink raw reply related
* [PATCH bpf v4 1/4] selftests/bpf: Ensure UDP sockets are bound
From: Michal Luczaj @ 2026-07-07 4:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
John Fastabend, Jakub Sitnicki, Jiayuan Chen, Eric Dumazet,
Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn, David S. Miller,
Jakub Kicinski, Simon Horman, Cong Wang
Cc: Michal Luczaj, bpf, linux-kselftest, linux-kernel, netdev
In-Reply-To: <20260707-sockmap-lookup-udp-leak-v4-0-f878346f27ab@rbox.co>
Update sockmap_basic tests to bind sockets before they are used. This
accommodates the recent change in sockmap that rejects unbound UDP sockets.
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com>
Signed-off-by: Michal Luczaj <mhal@rbox.co>
---
tools/testing/selftests/bpf/prog_tests/sockmap_basic.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
index cb3229711f93..2d22a9058a8e 100644
--- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
+++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
@@ -853,7 +853,7 @@ static void test_sockmap_many_socket(void)
return;
}
- udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+ udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK);
if (udp < 0) {
close(dgram);
close(tcp);
@@ -922,7 +922,7 @@ static void test_sockmap_many_maps(void)
return;
}
- udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+ udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK);
if (udp < 0) {
close(dgram);
close(tcp);
@@ -993,7 +993,7 @@ static void test_sockmap_same_sock(void)
return;
}
- udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+ udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK);
if (udp < 0) {
close(dgram);
close(tcp);
--
2.55.0
^ permalink raw reply related
* [PATCH bpf v4 4/4] selftests/bpf: Fail unbound UDP on sockmap update
From: Michal Luczaj @ 2026-07-07 4:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
John Fastabend, Jakub Sitnicki, Jiayuan Chen, Eric Dumazet,
Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn, David S. Miller,
Jakub Kicinski, Simon Horman, Cong Wang
Cc: Michal Luczaj, bpf, linux-kselftest, linux-kernel, netdev
In-Reply-To: <20260707-sockmap-lookup-udp-leak-v4-0-f878346f27ab@rbox.co>
sockmap now rejects unbound UDP sockets. Adjust test_maps. While at it,
check socket()'s return value.
This effectively reverts commit c39aa2159974 ("bpf, selftests: Fix
test_maps now that sockmap supports UDP").
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com>
Signed-off-by: Michal Luczaj <mhal@rbox.co>
---
tools/testing/selftests/bpf/test_maps.c | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/tools/testing/selftests/bpf/test_maps.c b/tools/testing/selftests/bpf/test_maps.c
index c32da7bd8be2..6a2641ee7897 100644
--- a/tools/testing/selftests/bpf/test_maps.c
+++ b/tools/testing/selftests/bpf/test_maps.c
@@ -759,16 +759,15 @@ static void test_sockmap(unsigned int tasks, void *data)
goto out_sockmap;
}
- /* Test update with unsupported UDP socket */
+ /* Test update with unsupported unbound UDP socket */
udp = socket(AF_INET, SOCK_DGRAM, 0);
- i = 0;
- err = bpf_map_update_elem(fd, &i, &udp, BPF_ANY);
- if (err) {
- printf("Failed socket update SOCK_DGRAM '%i:%i'\n",
- i, udp);
+ CHECK(udp < 0, "socket(AF_INET, SOCK_DGRAM)", "errno:%d\n", errno);
+ err = bpf_map_update_elem(fd, &(int){0}, &udp, BPF_ANY);
+ close(udp);
+ if (!err) {
+ printf("Unexpectedly succeeded unbound UDP update '0:%i'\n", udp);
goto out_sockmap;
}
- close(udp);
/* Test update without programs */
for (i = 0; i < 6; i++) {
--
2.55.0
^ permalink raw reply related
* [PATCH bpf v4 0/4] bpf, sockmap: Fix sockmap leaking UDP socks
From: Michal Luczaj @ 2026-07-07 4:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
John Fastabend, Jakub Sitnicki, Jiayuan Chen, Eric Dumazet,
Kuniyuki Iwashima, Paolo Abeni, Willem de Bruijn, David S. Miller,
Jakub Kicinski, Simon Horman, Cong Wang
Cc: Michal Luczaj, bpf, linux-kselftest, linux-kernel, netdev
Fix for UDP sockets getting leaked during sockmap lookup/release.
Accompanied by selftests updates.
Two Sashiko's concerns to be addressed separately:
https://lore.kernel.org/bpf/20260626205814.BAC3C1F000E9@smtp.kernel.org/
Signed-off-by: Michal Luczaj <mhal@rbox.co>
---
Changes in v4:
- selftest: drop redundant `if (err)` [Sashiko]
- Link to v3: https://patch.msgid.link/20260702-sockmap-lookup-udp-leak-v3-0-ff8de8782468@rbox.co
Changes in v3:
- selftest: better error handling, ASSERT_*() macros [Sashiko]
- selftest: fix grammar, reorder patches [Kuniyuki]
- Link to v2: https://patch.msgid.link/20260626-sockmap-lookup-udp-leak-v2-0-7e7e201c951a@rbox.co
Changes in v2:
- selftest: drop the original, adapt old tests
- fix: change approach to rejecting unbound UDP [Kuniyuki]
- Link to v1: https://patch.msgid.link/20260623-sockmap-lookup-udp-leak-v1-0-05804f9308e4@rbox.co
To: Alexei Starovoitov <ast@kernel.org>
To: Daniel Borkmann <daniel@iogearbox.net>
To: Andrii Nakryiko <andrii@kernel.org>
To: Eduard Zingerman <eddyz87@gmail.com>
To: Kumar Kartikeya Dwivedi <memxor@gmail.com>
To: Martin KaFai Lau <martin.lau@linux.dev>
To: Song Liu <song@kernel.org>
To: Yonghong Song <yonghong.song@linux.dev>
To: Jiri Olsa <jolsa@kernel.org>
To: Emil Tsalapatis <emil@etsalapatis.com>
To: Shuah Khan <shuah@kernel.org>
To: John Fastabend <john.fastabend@gmail.com>
To: Jakub Sitnicki <jakub@cloudflare.com>
To: Jiayuan Chen <jiayuan.chen@linux.dev>
To: Eric Dumazet <edumazet@google.com>
To: Kuniyuki Iwashima <kuniyu@google.com>
To: Paolo Abeni <pabeni@redhat.com>
To: Willem de Bruijn <willemb@google.com>
To: "David S. Miller" <davem@davemloft.net>
To: Jakub Kicinski <kuba@kernel.org>
To: Simon Horman <horms@kernel.org>
To: Cong Wang <cong.wang@bytedance.com>
Cc: bpf@vger.kernel.org
Cc: linux-kselftest@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: netdev@vger.kernel.org
---
Michal Luczaj (4):
selftests/bpf: Ensure UDP sockets are bound
bpf, sockmap: Reject unhashed UDP sockets on sockmap update
selftests/bpf: Adapt sockmap update error handling
selftests/bpf: Fail unbound UDP on sockmap update
net/core/sock_map.c | 2 ++
.../selftests/bpf/prog_tests/sockmap_basic.c | 6 +++---
.../selftests/bpf/prog_tests/sockmap_listen.c | 21 +++++++++++----------
tools/testing/selftests/bpf/test_maps.c | 13 ++++++-------
4 files changed, 22 insertions(+), 20 deletions(-)
---
base-commit: 17566290dc802b03a8127729b19c5962d63a7947
change-id: 20260617-sockmap-lookup-udp-leak-bc4e5c5481d7
Best regards,
--
Michal Luczaj <mhal@rbox.co>
^ permalink raw reply
* [PATCH net-next] net: ipv4: clear dev->ip_ptr before destroying inetdev
From: Yuyang Huang @ 2026-07-07 4:30 UTC (permalink / raw)
To: Yuyang Huang
Cc: David S. Miller, David Ahern, Eric Dumazet, Ido Schimmel,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel, netdev,
Kuniyuki Iwashima
To prevent RCU readers from accessing a partially destroyed in_device,
clear dev->ip_ptr early in inetdev_destroy() before freeing the
multicast list and individual IP addresses. This aligns the IPv4 teardown
sequence with the IPv6 implementation.
Cc: Ido Schimmel <idosch@nvidia.com>
Cc: Kuniyuki Iwashima <kuniyu@google.com>
Signed-off-by: Yuyang Huang <yuyanghuang@google.com>
---
net/ipv4/devinet.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c
index a35b72662e43..3b31f4bec30e 100644
--- a/net/ipv4/devinet.c
+++ b/net/ipv4/devinet.c
@@ -322,6 +322,8 @@ static void inetdev_destroy(struct in_device *in_dev)
in_dev->dead = 1;
+ RCU_INIT_POINTER(dev->ip_ptr, NULL);
+
ip_mc_destroy_dev(in_dev);
while ((ifa = rtnl_dereference(in_dev->ifa_list)) != NULL) {
@@ -329,8 +331,6 @@ static void inetdev_destroy(struct in_device *in_dev)
inet_free_ifa(ifa);
}
- RCU_INIT_POINTER(dev->ip_ptr, NULL);
-
devinet_sysctl_unregister(in_dev);
neigh_parms_release(&arp_tbl, in_dev->arp_parms);
arp_ifdown(dev);
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related
* [PATCH 1/1] sunrpc: fix use-after-free in __rpc_clnt_handle_event and __rpc_clnt_remove_pipedir
From: Ren Wei @ 2026-07-07 5:20 UTC (permalink / raw)
To: linux-nfs, netdev
Cc: cel, jlayton, neil, okorniev, Dai.Ngo, tom, trondmy, anna, davem,
edumazet, pabeni, horms, fuzhen5, yuantan098, dstsmallbird,
enjou1224z, rakukuip
In-Reply-To: <cover.1783393213.git.rakukuip@gmail.com>
From: Luxiao Xu <rakukuip@gmail.com>
Normal client creation goes through rpc_setup_pipedir(), which records
clnt->pipefs_sb, but the mount-event path in __rpc_clnt_handle_event()
calls rpc_setup_pipedir_sb() directly and never refreshes that field.
The umount path also removes the directory without clearing
clnt->pipefs_sb.
After a late pipefs mount or any remount, rpc_clnt_remove_pipedir()
compares the current superblock against a stale pipefs_sb pointer and
skips cleanup, leaving pipefs dentries whose inode private data still
points at a freed rpc_clnt, leading to a potential use-after-free during
subsequent rpc_info_open() or rpc_show_info() calls.
Fix this by properly updating clnt->pipefs_sb upon mount events and
clearing it during unmount or failure paths.
Fixes: bfca5fb4e97c ("SUNRPC: Fix RPC client cleaned up the freed pipefs dentries")
Cc: stable@vger.kernel.org
Reported-by: Yuan Tan <yuantan098@gmail.com>
Reported-by: Xin Liu <dstsmallbird@foxmail.com>
Reviewed-by: Ren Wei <enjou1224z@gmail.com>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Luxiao Xu <rakukuip@gmail.com>
---
net/sunrpc/clnt.c | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c
index bc8ca470718b..85fdd8ba94a4 100644
--- a/net/sunrpc/clnt.c
+++ b/net/sunrpc/clnt.c
@@ -96,7 +96,10 @@ static void rpc_unregister_client(struct rpc_clnt *clnt)
static void __rpc_clnt_remove_pipedir(struct rpc_clnt *clnt)
{
- rpc_remove_client_dir(clnt);
+ if (clnt->pipefs_sb) {
+ rpc_remove_client_dir(clnt);
+ clnt->pipefs_sb = NULL;
+ }
}
static void rpc_clnt_remove_pipedir(struct rpc_clnt *clnt)
@@ -177,19 +180,28 @@ static int rpc_clnt_skip_event(struct rpc_clnt *clnt, unsigned long event)
}
static int __rpc_clnt_handle_event(struct rpc_clnt *clnt, unsigned long event,
- struct super_block *sb)
+ struct super_block *sb)
{
+ int err = 0;
+
switch (event) {
case RPC_PIPEFS_MOUNT:
- return rpc_setup_pipedir_sb(sb, clnt);
+ clnt->pipefs_sb = sb;
+ err = rpc_setup_pipedir_sb(sb, clnt);
+ if (err)
+ clnt->pipefs_sb = NULL;
+ break;
case RPC_PIPEFS_UMOUNT:
- __rpc_clnt_remove_pipedir(clnt);
+ if (clnt->pipefs_sb == sb) {
+ __rpc_clnt_remove_pipedir(clnt);
+ clnt->pipefs_sb = NULL;
+ }
break;
default:
printk(KERN_ERR "%s: unknown event: %ld\n", __func__, event);
return -ENOTSUPP;
}
- return 0;
+ return err;
}
static int __rpc_pipefs_event(struct rpc_clnt *clnt, unsigned long event,
--
2.43.0
^ permalink raw reply related
* Re: [PATCH nf] ipvs: skip IPv6 extension headers in SCTP state lookup
From: Yizhou Zhao @ 2026-07-07 5:38 UTC (permalink / raw)
To: Julian Anastasov
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: <bcd7f0e6-b2fc-4e54-f9ac-e1c8b1b0c497@ssi.bg>
Hi Julian,
> On Jul 6, 2026, at 22:17, Julian Anastasov <ja@ssi.bg> wrote:
>
>
> Hello,
>
> On Mon, 6 Jul 2026, Yizhou Zhao wrote:
>
>>> On Jul 6, 2026, at 01:35, Julian Anastasov <ja@ssi.bg> wrote:
>>>
>>> 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.
>>
>> I agree that the already parsed transport-header offset should be
>> passed from ip_vs_set_state() down to the protocol state_transition()
>> callbacks, instead of reparsing the skb in set_sctp_state(). We will
>> send a v2 that does this for SCTP, TCP and the other IPVS protocols
>> in one combined fix.
>>
>>> 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.
>>
>> For the ICMP path, I agree that the missing ip_vs_set_state() call is
>> worth looking at, but using ICMP errors to drive the upper L4 state
>> needs some care, because spoofed ICMP packets can match an
>> existing embedded tuple before the endpoint TCP/SCTP stack
>> performs its own validation. Maybe this change needs further
>> discussion?
>
> May be only for the schedule_icmp case while the other
> ICMP replies should not change it:
>
> diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
> index 7f93239898ff..05fdcf4ce2c0 100644
> --- a/net/netfilter/ipvs/ip_vs_core.c
> +++ b/net/netfilter/ipvs/ip_vs_core.c
> @@ -1971,6 +1971,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
>
> /* do the statistics and put it back */
> ip_vs_in_stats(cp, skb);
> + if (new_cp)
> + ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd, offset);
> if (IPPROTO_TCP == cih->protocol || IPPROTO_UDP == cih->protocol ||
> IPPROTO_SCTP == cih->protocol)
> offset += 2 * sizeof(__u16);
>
> Here is why schedule_icmp was added:
>
> https://archive.linuxvirtualserver.org/html/lvs-devel/2015-08/msg00015.html
>
> But the inner TCP header should be generated by the
> real server, not from the client, so things can go wrong. We can
> leave it as it is now - we will forward the ICMP to the right real
> server by using short conn timeout...
I agree. The embedded TCP/SCTP header in this path may not have the same
meaning as a normal client packet reaching the virtual service, so I do not
see a good fix for the ICMP state update part at the moment.
BTW, we have kept the ICMP handling unchanged and submitted a v2 series
for the parsed transport-offset issue only, following your earlier suggestion
to pass the already parsed offset from ip_vs_set_state() down to the protocol
state handlers. The v2 also puts the TCP and SCTP fixes in the same thread:
https://lore.kernel.org/netdev/20260706101624.69471-1-zhaoyz24@mails.tsinghua.edu.cn/
Thanks for the review.
>
> Regards
>
> --
> Julian Anastasov <ja@ssi.bg>
Thanks,
Yizhou
^ permalink raw reply
* [PATCH v2 net] bnx2x: fix null pointer dereference in bnx2x_free_mem_bp()
From: Abdun Nihaal @ 2026-07-07 5:46 UTC (permalink / raw)
To: skalluru
Cc: Abdun Nihaal, manishc, andrew+netdev, davem, edumazet, kuba,
pabeni, netdev, linux-kernel, horms, maciej.fijalkowski, stable,
Sashiko AI Review
In one of the error path in bnx2x_alloc_mem_bp(), bnx2x_free_mem_bp()
may be called with bp->fp uninitialized. And so, there could be a null
pointer dereference in bnx2x_free_mem_bp(). Fix that by initializing the
fp_array_size after the bp->fp pointer is correctly initialized.
Fixes: c3146eb676e7 ("bnx2x: Correct memory preparation and release")
Cc: stable@vger.kernel.org
Reported-by: Sashiko AI Review <sashiko-bot@kernel.org>
Reviewed-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Signed-off-by: Abdun Nihaal <nihaal@cse.iitm.ac.in>
---
Compile tested only.
Thanks to Simon Horman for pointing out the Sashiko review.
v1->v2:
- Add the correct Reported-by tag for Sashiko as suggested by Maciej
Fijalkowski. Also added Maciej's Reviewed-by tag.
- Simplify the fix by initializing the fp_array_size later, as suggested
by Paolo Abeni.
Link to v1: https://patchwork.kernel.org/project/netdevbpf/patch/20260701065030.381836-1-nihaal@cse.iitm.ac.in/
drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c
index 5b2640bd31c3..5a9742fd3ddf 100644
--- a/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c
+++ b/drivers/net/ethernet/broadcom/bnx2x/bnx2x_cmn.c
@@ -4742,13 +4742,13 @@ int bnx2x_alloc_mem_bp(struct bnx2x *bp)
/* fp array: RSS plus CNIC related L2 queues */
fp_array_size = BNX2X_MAX_RSS_COUNT(bp) + CNIC_SUPPORT(bp);
- bp->fp_array_size = fp_array_size;
- BNX2X_DEV_INFO("fp_array_size %d\n", bp->fp_array_size);
-
- fp = kzalloc_objs(*fp, bp->fp_array_size);
+ BNX2X_DEV_INFO("fp_array_size %d\n", fp_array_size);
+ fp = kzalloc_objs(*fp, fp_array_size);
if (!fp)
goto alloc_err;
bp->fp = fp;
+ bp->fp_array_size = fp_array_size;
+
for (i = 0; i < bp->fp_array_size; i++) {
fp[i].tpa_info =
kzalloc_objs(struct bnx2x_agg_info,
--
2.43.0
^ permalink raw reply related
* [PATCH ethtool-next] netlink: rss: Add set support
From: Gal Pressman @ 2026-07-07 6:00 UTC (permalink / raw)
To: netdev; +Cc: Michal Kubecek, Jakub Kicinski, Gal Pressman, Nimrod Oren
The kernel has exposed ETHTOOL_MSG_RSS_SET, RSS_CREATE_ACT and
RSS_DELETE_ACT since v6.17, but ethtool's -X has remained ioctl only.
Add nl_srss() so the command goes through netlink by default, the ioctl
path remains as fallback for older kernels.
All CLI keywords accepted by the ioctl path are handled. nl_srss()
dispatches by parsed args:
context new -> ETHTOOL_MSG_RSS_CREATE_ACT
context N delete -> ETHTOOL_MSG_RSS_DELETE_ACT
otherwise -> ETHTOOL_MSG_RSS_SET
Reviewed-by: Nimrod Oren <noren@nvidia.com>
Signed-off-by: Gal Pressman <gal@nvidia.com>
---
ethtool.c | 10 +-
internal.h | 5 +
netlink/extapi.h | 2 +
| 510 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 522 insertions(+), 5 deletions(-)
diff --git a/ethtool.c b/ethtool.c
index 3076c128bb34..c8b6a38c288d 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -1207,8 +1207,7 @@ err:
return 2;
}
-static int parse_hkey(char **rss_hkey, u32 key_size,
- const char *rss_hkey_string)
+int parse_hkey(char **rss_hkey, u32 key_size, const char *rss_hkey_string)
{
if (!key_size) {
fprintf(stderr,
@@ -4207,9 +4206,9 @@ out:
return 0;
}
-static int fill_indir_table(u32 *indir_size, u32 *indir, int rxfhindir_default,
- int rxfhindir_start, int rxfhindir_equal,
- char **rxfhindir_weight, u32 num_weights)
+int fill_indir_table(u32 *indir_size, u32 *indir, int rxfhindir_default,
+ int rxfhindir_start, int rxfhindir_equal,
+ char **rxfhindir_weight, u32 num_weights)
{
u32 i;
@@ -6215,6 +6214,7 @@ static const struct option args[] = {
{
.opts = "-X|--set-rxfh-indir|--rxfh",
.func = do_srxfh,
+ .nlfunc = nl_srss,
.help = "Set Rx flow hash indirection table and/or RSS hash key",
.xhelp = " [ context %d|new ]\n"
" [ equal N | weight W0 W1 ... | default ]\n"
diff --git a/internal.h b/internal.h
index 19fff417ab0f..50d43ccd844a 100644
--- a/internal.h
+++ b/internal.h
@@ -286,6 +286,11 @@ int test_fclose(FILE *fh);
int ioctl_init(struct cmd_context *ctx, bool no_dev);
int send_ioctl(struct cmd_context *ctx, void *cmd);
+int parse_hkey(char **rss_hkey, u32 key_size, const char *rss_hkey_string);
+int fill_indir_table(u32 *indir_size, u32 *indir, int rxfhindir_default,
+ int rxfhindir_start, int rxfhindir_equal,
+ char **rxfhindir_weight, u32 num_weights);
+
void dump_hex(FILE *f, const u8 *data, int len, int offset);
/* National Semiconductor DP83815, DP83816 */
diff --git a/netlink/extapi.h b/netlink/extapi.h
index b0e458804297..e10af6230bb3 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -50,6 +50,7 @@ int nl_smodule(struct cmd_context *ctx);
int nl_monitor(struct cmd_context *ctx);
int nl_getmodule(struct cmd_context *ctx);
int nl_grss(struct cmd_context *ctx);
+int nl_srss(struct cmd_context *ctx);
int nl_plca_get_cfg(struct cmd_context *ctx);
int nl_plca_set_cfg(struct cmd_context *ctx);
int nl_plca_get_status(struct cmd_context *ctx);
@@ -130,6 +131,7 @@ nl_get_eeprom_page(struct cmd_context *ctx __maybe_unused,
#define nl_gmodule NULL
#define nl_smodule NULL
#define nl_grss NULL
+#define nl_srss NULL
#define nl_plca_get_cfg NULL
#define nl_plca_set_cfg NULL
#define nl_plca_get_status NULL
--git a/netlink/rss.c b/netlink/rss.c
index 83cc50416dc7..c0fbba8bc2ac 100644
--- a/netlink/rss.c
+++ b/netlink/rss.c
@@ -4,7 +4,10 @@
* Implementation of "ethtool -x <dev>"
*/
+#include <ctype.h>
#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
#include <string.h>
#include <stdio.h>
@@ -246,3 +249,510 @@ int nl_grss(struct cmd_context *ctx)
err:
return nlctx->exit_code ?: 1;
}
+
+/* RSS_SET */
+enum {
+ SRSS_PARAM_EQUAL,
+ SRSS_PARAM_WEIGHT,
+ SRSS_PARAM_START,
+ SRSS_PARAM_DEFAULT,
+ SRSS_PARAM_HKEY,
+ SRSS_PARAM_HFUNC,
+ SRSS_PARAM_XFRM,
+ SRSS_PARAM_CONTEXT,
+ SRSS_PARAM_DELETE,
+};
+
+struct srss_cmd {
+ unsigned long present;
+ u32 equal;
+ u32 start;
+ char **weight;
+ u32 num_weights;
+ char *hkey;
+ char *hfunc;
+ u32 input_xfrm;
+ u32 context;
+};
+
+#define SRSS_HAS(cmd, p) ((cmd)->present & (1UL << (p)))
+
+struct rss_sizes {
+ u32 indir_size;
+ u32 key_size;
+};
+
+static int rss_sizes_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_RSS_MAX + 1] = {};
+ struct rss_sizes *sizes = data;
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+
+ if (tb[ETHTOOL_A_RSS_INDIR])
+ sizes->indir_size =
+ mnl_attr_get_payload_len(tb[ETHTOOL_A_RSS_INDIR]) /
+ sizeof(u32);
+
+ if (tb[ETHTOOL_A_RSS_HKEY])
+ sizes->key_size =
+ mnl_attr_get_payload_len(tb[ETHTOOL_A_RSS_HKEY]);
+
+ return MNL_CB_OK;
+}
+
+static int rss_get_sizes(struct nl_context *nlctx, u32 *indir_size,
+ u32 *key_size)
+{
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+ struct rss_sizes sizes = {};
+ int ret;
+
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_RSS_GET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return ret;
+
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_RSS_HEADER,
+ nlctx->devname, 0))
+ return -EMSGSIZE;
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ return ret;
+
+ ret = nlsock_process_reply(nlsk, rss_sizes_reply_cb, &sizes);
+ if (ret < 0)
+ return ret;
+
+ *indir_size = sizes.indir_size;
+ *key_size = sizes.key_size;
+ return 0;
+}
+
+static int rss_resolve_hfunc(struct nl_context *nlctx, const char *name,
+ u32 *hfunc)
+{
+ const struct stringset *hash_funcs;
+ unsigned int i, count;
+ int ret;
+
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0) {
+ fprintf(stderr, "Cannot get hash function names\n");
+ return ret;
+ }
+
+ hash_funcs = global_stringset(ETH_SS_RSS_HASH_FUNCS,
+ nlctx->ethnl2_socket);
+ if (!hash_funcs) {
+ fprintf(stderr, "Cannot get hash function names\n");
+ return -ENOENT;
+ }
+
+ count = get_count(hash_funcs);
+ for (i = 0; i < count; i++) {
+ if (!strcmp(get_string(hash_funcs, i), name)) {
+ *hfunc = (u32)1 << i;
+ return 0;
+ }
+ }
+
+ fprintf(stderr, "Unknown hash function: %s\n", name);
+ return -EINVAL;
+}
+
+static int rss_create_reply_cb(const struct nlmsghdr *nlhdr,
+ void *data __maybe_unused)
+{
+ const struct nlattr *tb[ETHTOOL_A_RSS_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ int ret;
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+
+ if (tb[ETHTOOL_A_RSS_CONTEXT])
+ printf("New RSS context is %u\n",
+ mnl_attr_get_u32(tb[ETHTOOL_A_RSS_CONTEXT]));
+
+ return MNL_CB_OK;
+}
+
+static int srss_parse_weight(struct nl_context *nlctx,
+ uint16_t type __maybe_unused,
+ const void *data __maybe_unused,
+ struct nl_msg_buff *msgbuff __maybe_unused,
+ void *dest)
+{
+ struct srss_cmd *cmd = dest;
+
+ cmd->weight = nlctx->argp;
+ while (nlctx->argc > 0 &&
+ isdigit((unsigned char)(*nlctx->argp)[0])) {
+ nlctx->argp++;
+ nlctx->argc--;
+ cmd->num_weights++;
+ }
+
+ if (cmd->num_weights == 0) {
+ fprintf(stderr, "'weight' requires at least one value\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct lookup_entry_u32 srss_xfrm_values[] = {
+ { .arg = "none", .val = 0 },
+ { .arg = "symmetric-xor", .val = RXH_XFRM_SYM_XOR },
+ { .arg = "symmetric-or-xor", .val = RXH_XFRM_SYM_OR_XOR },
+ {}
+};
+
+static int srss_parse_context(struct nl_context *nlctx,
+ uint16_t type __maybe_unused,
+ const void *data __maybe_unused,
+ struct nl_msg_buff *msgbuff __maybe_unused,
+ void *dest)
+{
+ struct srss_cmd *cmd = dest;
+ const char *arg = *nlctx->argp;
+
+ nlctx->argp++;
+ nlctx->argc--;
+
+ if (!strcmp(arg, "new")) {
+ cmd->context = ETH_RXFH_CONTEXT_ALLOC;
+ } else {
+ u32 v;
+
+ if (parse_u32(arg, &v) ||
+ v < 1 || v > ETH_RXFH_CONTEXT_ALLOC - 1) {
+ fprintf(stderr, "Invalid 'context' value\n");
+ return -EINVAL;
+ }
+ cmd->context = v;
+ }
+
+ return 0;
+}
+
+/* "default" and "delete" are flags with no extra arg, just being seen
+ * (recorded in cmd->present) is enough.
+ */
+static int srss_parse_flag(struct nl_context *nlctx __maybe_unused,
+ uint16_t type __maybe_unused,
+ const void *data __maybe_unused,
+ struct nl_msg_buff *msgbuff __maybe_unused,
+ void *dest __maybe_unused)
+{
+ return 0;
+}
+
+static const struct param_parser srss_params[] = {
+ [SRSS_PARAM_EQUAL] = {
+ .arg = "equal",
+ .handler = nl_parse_direct_u32,
+ .dest_offset = offsetof(struct srss_cmd, equal),
+ .min_argc = 1,
+ .alt_group = 1,
+ },
+ [SRSS_PARAM_WEIGHT] = {
+ .arg = "weight",
+ .handler = srss_parse_weight,
+ .min_argc = 1,
+ .alt_group = 1,
+ },
+ [SRSS_PARAM_START] = {
+ .arg = "start",
+ .handler = nl_parse_direct_u32,
+ .dest_offset = offsetof(struct srss_cmd, start),
+ .min_argc = 1,
+ },
+ [SRSS_PARAM_DEFAULT] = {
+ .arg = "default",
+ .handler = srss_parse_flag,
+ .alt_group = 1,
+ },
+ [SRSS_PARAM_HKEY] = {
+ .arg = "hkey",
+ .handler = nl_parse_string,
+ .dest_offset = offsetof(struct srss_cmd, hkey),
+ .min_argc = 1,
+ },
+ [SRSS_PARAM_HFUNC] = {
+ .arg = "hfunc",
+ .handler = nl_parse_string,
+ .dest_offset = offsetof(struct srss_cmd, hfunc),
+ .min_argc = 1,
+ },
+ [SRSS_PARAM_XFRM] = {
+ .arg = "xfrm",
+ .handler = nl_parse_lookup_u32,
+ .handler_data = srss_xfrm_values,
+ .dest_offset = offsetof(struct srss_cmd, input_xfrm),
+ .min_argc = 1,
+ },
+ [SRSS_PARAM_CONTEXT] = {
+ .arg = "context",
+ .handler = srss_parse_context,
+ .min_argc = 1,
+ },
+ [SRSS_PARAM_DELETE] = {
+ .arg = "delete",
+ .handler = srss_parse_flag,
+ .alt_group = 1,
+ },
+ {}
+};
+
+static int srss_validate_cmd(const struct srss_cmd *cmd)
+{
+ if (SRSS_HAS(cmd, SRSS_PARAM_EQUAL) &&
+ (cmd->equal < 1 || cmd->equal > INT_MAX)) {
+ fprintf(stderr, "Invalid 'equal' value\n");
+ return -EINVAL;
+ }
+
+ if (SRSS_HAS(cmd, SRSS_PARAM_START) && cmd->start > INT_MAX) {
+ fprintf(stderr, "Invalid 'start' value\n");
+ return -EINVAL;
+ }
+
+ if (SRSS_HAS(cmd, SRSS_PARAM_DELETE)) {
+ static const struct {
+ unsigned int param;
+ const char *name;
+ } delete_forbidden[] = {
+ { SRSS_PARAM_HKEY, "hkey" },
+ { SRSS_PARAM_HFUNC, "hfunc" },
+ { SRSS_PARAM_XFRM, "xfrm" },
+ { SRSS_PARAM_START, "start" },
+ };
+ unsigned int i;
+
+ if (!SRSS_HAS(cmd, SRSS_PARAM_CONTEXT)) {
+ fprintf(stderr,
+ "Delete option requires context option\n");
+ return -EINVAL;
+ }
+ if (cmd->context == ETH_RXFH_CONTEXT_ALLOC) {
+ fprintf(stderr,
+ "Delete and 'context new' are mutually exclusive\n");
+ return -EINVAL;
+ }
+ for (i = 0; i < ARRAY_SIZE(delete_forbidden); i++) {
+ if (SRSS_HAS(cmd, delete_forbidden[i].param)) {
+ fprintf(stderr,
+ "Delete and %s options are mutually exclusive\n",
+ delete_forbidden[i].name);
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (SRSS_HAS(cmd, SRSS_PARAM_START) &&
+ SRSS_HAS(cmd, SRSS_PARAM_DEFAULT)) {
+ fprintf(stderr,
+ "Start and default options are mutually exclusive\n");
+ return -EINVAL;
+ }
+
+ if (SRSS_HAS(cmd, SRSS_PARAM_START) &&
+ !(SRSS_HAS(cmd, SRSS_PARAM_EQUAL) ||
+ SRSS_HAS(cmd, SRSS_PARAM_WEIGHT))) {
+ fprintf(stderr,
+ "Start must be used with equal or weight options\n");
+ return -EINVAL;
+ }
+
+ if (SRSS_HAS(cmd, SRSS_PARAM_DEFAULT) &&
+ SRSS_HAS(cmd, SRSS_PARAM_CONTEXT)) {
+ fprintf(stderr,
+ "Default and context options are mutually exclusive\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int nl_srss(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ u32 indir_size = 0, key_size = 0;
+ struct nl_msg_buff *msgbuff;
+ struct srss_cmd cmd = {};
+ struct nl_socket *nlsk;
+ unsigned int msg_type;
+ char *hkey = NULL;
+ u32 *indir = NULL;
+ u32 hfunc_val = 0;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_RSS_SET, false))
+ return -EOPNOTSUPP;
+
+ if (!ctx->argc) {
+ fprintf(stderr, "ethtool (-X): parameters missing\n");
+ ret = 1;
+ goto out;
+ }
+
+ nlctx->cmd = "-X";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->devname = ctx->devname;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ ret = nl_parser(nlctx, srss_params, &cmd, PARSER_GROUP_NONE, NULL);
+ if (ret < 0)
+ goto out;
+
+ ret = srss_validate_cmd(&cmd);
+ if (ret)
+ goto out;
+
+ if (SRSS_HAS(&cmd, SRSS_PARAM_DELETE))
+ msg_type = ETHTOOL_MSG_RSS_DELETE_ACT;
+ else if (cmd.context == ETH_RXFH_CONTEXT_ALLOC)
+ msg_type = ETHTOOL_MSG_RSS_CREATE_ACT;
+ else
+ msg_type = ETHTOOL_MSG_RSS_SET;
+
+ if (msg_type != ETHTOOL_MSG_RSS_SET &&
+ netlink_cmd_check(ctx, msg_type, false))
+ return -EOPNOTSUPP;
+
+ if (SRSS_HAS(&cmd, SRSS_PARAM_HKEY) ||
+ SRSS_HAS(&cmd, SRSS_PARAM_EQUAL) ||
+ SRSS_HAS(&cmd, SRSS_PARAM_WEIGHT) ||
+ SRSS_HAS(&cmd, SRSS_PARAM_DEFAULT)) {
+ ret = rss_get_sizes(nlctx, &indir_size, &key_size);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (indir_size == 0 && (SRSS_HAS(&cmd, SRSS_PARAM_EQUAL) ||
+ SRSS_HAS(&cmd, SRSS_PARAM_WEIGHT) ||
+ SRSS_HAS(&cmd, SRSS_PARAM_DEFAULT))) {
+ fprintf(stderr,
+ "Device does not support RX indirection table\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = msg_init(nlctx, msgbuff, msg_type, NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ goto out;
+
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_RSS_HEADER,
+ ctx->devname, 0)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ if (msg_type == ETHTOOL_MSG_RSS_DELETE_ACT) {
+ if (ethnla_put_u32(msgbuff, ETHTOOL_A_RSS_CONTEXT,
+ cmd.context)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ goto send;
+ }
+
+ /* For SET: include context id if targeting a non-default context.
+ * For CREATE: omit context (kernel auto-allocates).
+ */
+ if (msg_type == ETHTOOL_MSG_RSS_SET &&
+ SRSS_HAS(&cmd, SRSS_PARAM_CONTEXT)) {
+ if (ethnla_put_u32(msgbuff, ETHTOOL_A_RSS_CONTEXT,
+ cmd.context)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ }
+
+ if (SRSS_HAS(&cmd, SRSS_PARAM_HKEY)) {
+ ret = parse_hkey(&hkey, key_size, cmd.hkey);
+ if (ret)
+ goto out;
+
+ if (ethnla_put(msgbuff, ETHTOOL_A_RSS_HKEY, key_size, hkey)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ }
+
+ if (SRSS_HAS(&cmd, SRSS_PARAM_HFUNC)) {
+ ret = rss_resolve_hfunc(nlctx, cmd.hfunc, &hfunc_val);
+ if (ret < 0)
+ goto out;
+
+ if (ethnla_put_u32(msgbuff, ETHTOOL_A_RSS_HFUNC, hfunc_val)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ }
+
+ if (SRSS_HAS(&cmd, SRSS_PARAM_XFRM)) {
+ if (ethnla_put_u32(msgbuff, ETHTOOL_A_RSS_INPUT_XFRM,
+ cmd.input_xfrm)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ }
+
+ if (SRSS_HAS(&cmd, SRSS_PARAM_EQUAL) ||
+ SRSS_HAS(&cmd, SRSS_PARAM_WEIGHT) ||
+ SRSS_HAS(&cmd, SRSS_PARAM_DEFAULT)) {
+ u32 table_size = indir_size;
+
+ indir = calloc(indir_size, sizeof(*indir));
+ if (!indir) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = fill_indir_table(
+ &table_size, indir, SRSS_HAS(&cmd, SRSS_PARAM_DEFAULT),
+ SRSS_HAS(&cmd, SRSS_PARAM_START) ? cmd.start : 0,
+ SRSS_HAS(&cmd, SRSS_PARAM_EQUAL) ? cmd.equal : 0,
+ SRSS_HAS(&cmd, SRSS_PARAM_WEIGHT) ? cmd.weight : NULL,
+ cmd.num_weights);
+ if (ret)
+ goto out;
+
+ /* fill_indir_table() sets table_size to 0 to signal "reset to
+ * default" (sent as a zero-length INDIR attribute, which the
+ * kernel only accepts on the default context).
+ */
+ if (ethnla_put(msgbuff, ETHTOOL_A_RSS_INDIR,
+ table_size * sizeof(*indir), indir)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ }
+
+send:
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0)
+ goto out;
+
+ if (msg_type == ETHTOOL_MSG_RSS_CREATE_ACT)
+ ret = nlsock_process_reply(nlsk, rss_create_reply_cb, nlctx);
+ else
+ ret = nlsock_process_reply(nlsk, nomsg_reply_cb, nlctx);
+
+out:
+ free(indir);
+ free(hkey);
+ return ret < 0 ? 1 : ret;
+}
--
2.54.0
^ permalink raw reply related
* [PATCH v2 0/4] Add Intel Agilex5 SoCDK TSN Config2 board
From: muhammad.nazim.amirul.nazle.asmade @ 2026-07-07 6:13 UTC (permalink / raw)
To: dinguyen, maxime.chevallier
Cc: rmk+kernel, krzk+dt, conor+dt, robh, davem, edumazet, kuba,
pabeni, andrew+netdev, devicetree, linux-arm-kernel, netdev,
linux-kernel
From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
v2:
- Replace mac-mode DT property with a board-specific compatible string
"altr,socfpga-stmmac-agilex5-tsn" to identify gmac1 on the TSN Config2
board, per Andrew Lunn's feedback.
- Change phy-mode from "rgmii" to "rgmii-id" on both gmac1 and gmac2,
since the FPGA GMII-to-RGMII converter provides the TX/RX clock delays
(acting as MAC-side delays).
- Add a new patch (3/4) documenting the new MAC compatible string in
the altr,socfpga-stmmac binding.
- Fix DTS Makefile indentation (Krzysztof Kozlowski).
---
This series adds DT binding, device tree, and driver support for the
Intel SoCFPGA Agilex5 SoCDK TSN Config2 board.
The board has two Ethernet ports: gmac2 is a standard RGMII port, while
gmac1 is the TSN port. On gmac1, an FPGA GMII-to-RGMII converter sits
between the MAC and the PHY. This converter provides the RGMII TX/RX
clock delays, so phy-mode is set to "rgmii-id". The driver uses the
new "altr,socfpga-stmmac-agilex5-tsn" compatible to detect this setup
and configure the MAC interface selector for GMII while ensuring the
PHY is not asked to add delays a second time.
Nazim Amirul (4):
dt-bindings: arm: altera: Add Agilex5 SoCDK TSN Config2 board
arm64: dts: socfpga: agilex5: Add SoCDK TSN Config2 board
dt-bindings: net: altr,socfpga-stmmac: Add
altr,socfpga-stmmac-agilex5-tsn compatible
net: stmmac: dwmac-socfpga: Add support for Agilex5 TSN GMAC with FPGA
converter
.../devicetree/bindings/arm/altera.yaml | 1 +
.../bindings/net/altr,socfpga-stmmac.yaml | 5 +
arch/arm64/boot/dts/intel/Makefile | 1 +
.../intel/socfpga_agilex5_socdk_tsn_cfg2.dts | 131 ++++++++++++++++++
.../ethernet/stmicro/stmmac/dwmac-socfpga.c | 13 +-
5 files changed, 150 insertions(+), 1 deletion(-)
create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_tsn_cfg2.dts
--
2.43.7
^ permalink raw reply
* [PATCH v2 1/4] dt-bindings: arm: altera: Add Agilex5 SoCDK TSN Config2 board
From: muhammad.nazim.amirul.nazle.asmade @ 2026-07-07 6:13 UTC (permalink / raw)
To: dinguyen, maxime.chevallier
Cc: rmk+kernel, krzk+dt, conor+dt, robh, davem, edumazet, kuba,
pabeni, andrew+netdev, devicetree, linux-arm-kernel, netdev,
linux-kernel
In-Reply-To: <20260707061305.27580-1-muhammad.nazim.amirul.nazle.asmade@altera.com>
From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
Add compatible string for the Intel SoCFPGA Agilex5 SoCDK TSN Config2
board variant. This board enables gmac1 as a TSN port with an FPGA
GMII-to-RGMII converter providing the RGMII clock delays.
Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
---
Documentation/devicetree/bindings/arm/altera.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/arm/altera.yaml b/Documentation/devicetree/bindings/arm/altera.yaml
index 3030cf46fe74..e431469a7175 100644
--- a/Documentation/devicetree/bindings/arm/altera.yaml
+++ b/Documentation/devicetree/bindings/arm/altera.yaml
@@ -114,6 +114,7 @@ properties:
- intel,socfpga-agilex5-socdk-debug
- intel,socfpga-agilex5-socdk-modular
- intel,socfpga-agilex5-socdk-nand
+ - intel,socfpga-agilex5-socdk-tsn-cfg2
- const: intel,socfpga-agilex5
- description: Agilex72 boards
--
2.43.7
^ permalink raw reply related
* [PATCH v2 2/4] arm64: dts: socfpga: agilex5: Add SoCDK TSN Config2 board
From: muhammad.nazim.amirul.nazle.asmade @ 2026-07-07 6:13 UTC (permalink / raw)
To: dinguyen, maxime.chevallier
Cc: rmk+kernel, krzk+dt, conor+dt, robh, davem, edumazet, kuba,
pabeni, andrew+netdev, devicetree, linux-arm-kernel, netdev,
linux-kernel
In-Reply-To: <20260707061305.27580-1-muhammad.nazim.amirul.nazle.asmade@altera.com>
From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
Add device tree for the Intel SoCFPGA Agilex5 SoCDK TSN Config2 board
variant. This configuration enables gmac1 as a TSN port alongside the
standard gmac2 Ethernet port.
The TSN port (gmac1) connects to its PHY through a GMII-to-RGMII
converter implemented as FPGA soft IP. This converter provides the
RGMII TX/RX clock delays, so phy-mode is set to "rgmii-id" to reflect
MAC-side delays. A board-specific compatible string is used so the
driver can detect the converter and strip the delay bits before
configuring the PHY.
Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
---
arch/arm64/boot/dts/intel/Makefile | 1 +
.../intel/socfpga_agilex5_socdk_tsn_cfg2.dts | 131 ++++++++++++++++++
2 files changed, 132 insertions(+)
create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_tsn_cfg2.dts
diff --git a/arch/arm64/boot/dts/intel/Makefile b/arch/arm64/boot/dts/intel/Makefile
index 270c70fdf084..ce7cf3a63275 100644
--- a/arch/arm64/boot/dts/intel/Makefile
+++ b/arch/arm64/boot/dts/intel/Makefile
@@ -8,6 +8,7 @@ dtb-$(CONFIG_ARCH_INTEL_SOCFPGA) += socfpga_agilex_n6000.dtb \
socfpga_agilex5_socdk_013b.dtb \
socfpga_agilex5_socdk_modular.dtb \
socfpga_agilex5_socdk_nand.dtb \
+ socfpga_agilex5_socdk_tsn_cfg2.dtb \
socfpga_agilex72_socdk.dtb \
socfpga_agilex7m_socdk.dtb \
socfpga_n5x_socdk.dtb
diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_tsn_cfg2.dts b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_tsn_cfg2.dts
new file mode 100644
index 000000000000..bf13ccfc1faa
--- /dev/null
+++ b/arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_tsn_cfg2.dts
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026, Altera Corporation
+ */
+#include "socfpga_agilex5.dtsi"
+
+/ {
+ model = "SoCFPGA Agilex5 SoCDK TSN Config2";
+ compatible = "intel,socfpga-agilex5-socdk-tsn-cfg2", "intel,socfpga-agilex5";
+
+ aliases {
+ serial0 = &uart0;
+ ethernet1 = &gmac1;
+ ethernet2 = &gmac2;
+ };
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ led-0 {
+ label = "hps_led0";
+ gpios = <&porta 11 GPIO_ACTIVE_HIGH>;
+ };
+
+ };
+
+ memory@80000000 {
+ device_type = "memory";
+ /* We expect the bootloader to fill in the reg */
+ reg = <0x0 0x80000000 0x0 0x0>;
+ };
+};
+
+&gpio0 {
+ status = "okay";
+};
+
+&gpio1 {
+ status = "okay";
+};
+
+&gmac1 {
+ status = "okay";
+ compatible = "altr,socfpga-stmmac-agilex5-tsn",
+ "altr,socfpga-stmmac-agilex5",
+ "snps,dwxgmac-2.10";
+ phy-mode = "rgmii-id";
+ phy-handle = <&emac1_phy0>;
+ max-frame-size = <9000>;
+
+ mdio0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "snps,dwmac-mdio";
+
+ emac1_phy0: ethernet-phy@0 {
+ reg = <0>;
+ };
+ };
+};
+
+&gmac2 {
+ status = "okay";
+ phy-mode = "rgmii-id";
+ phy-handle = <&emac2_phy0>;
+ max-frame-size = <9000>;
+
+ mdio0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "snps,dwmac-mdio";
+
+ emac2_phy0: ethernet-phy@0 {
+ reg = <0>;
+ };
+ };
+};
+
+&osc1 {
+ clock-frequency = <25000000>;
+};
+
+&qspi {
+ status = "okay";
+ flash@0 {
+ compatible = "micron,mt25qu02g", "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <100000000>;
+ m25p,fast-read;
+ cdns,read-delay = <2>;
+ cdns,tshsl-ns = <50>;
+ cdns,tsd2d-ns = <50>;
+ cdns,tchsh-ns = <4>;
+ cdns,tslch-ns = <4>;
+ spi-tx-bus-width = <4>;
+ spi-rx-bus-width = <4>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ qspi_boot: partition@0 {
+ label = "u-boot";
+ reg = <0x0 0x04200000>;
+ };
+
+ root: partition@4200000 {
+ label = "root";
+ reg = <0x04200000 0x0be00000>;
+ };
+ };
+ };
+};
+
+&uart0 {
+ status = "okay";
+};
+
+&usb0 {
+ status = "okay";
+ disable-over-current;
+};
+
+&watchdog0 {
+ status = "okay";
+};
--
2.43.7
^ permalink raw reply related
* [PATCH net-next v2 4/4] net: stmmac: dwmac-socfpga: Add support for Agilex5 TSN GMAC with FPGA converter
From: muhammad.nazim.amirul.nazle.asmade @ 2026-07-07 6:13 UTC (permalink / raw)
To: dinguyen, maxime.chevallier
Cc: rmk+kernel, krzk+dt, conor+dt, robh, davem, edumazet, kuba,
pabeni, andrew+netdev, devicetree, linux-arm-kernel, netdev,
linux-kernel
In-Reply-To: <20260707061305.27580-1-muhammad.nazim.amirul.nazle.asmade@altera.com>
From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
The Agilex5 SoCDK TSN Config2 board uses a GMII-to-RGMII converter
implemented as FPGA soft IP between gmac1 and its PHY. This converter
provides the RGMII TX/RX clock delays, so the MAC interface selector
must be configured for GMII while the PHY is configured without delays.
Add the "altr,socfpga-stmmac-agilex5-tsn" compatible to the match table
and detect it in probe to force GMII for the MAC interface selector and
strip the delay bits from phy_interface so the PHY is not configured to
add delays already provided by the FPGA converter.
Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
---
drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
index 1d7f0a57d288..bf591a68502f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
@@ -69,12 +69,13 @@ struct socfpga_dwmac {
void __iomem *tse_pcs_base;
void __iomem *sgmii_adapter_base;
bool f2h_ptp_ref_clk;
+ phy_interface_t mac_interface;
const struct socfpga_dwmac_ops *ops;
};
static phy_interface_t socfpga_get_plat_phymode(struct socfpga_dwmac *dwmac)
{
- return dwmac->plat_dat->phy_interface;
+ return dwmac->mac_interface;
}
static void socfpga_sgmii_config(struct socfpga_dwmac *dwmac, bool enable)
@@ -650,6 +651,15 @@ static int socfpga_dwmac_probe(struct platform_device *pdev)
plat_dat->pcs_exit = socfpga_dwmac_pcs_exit;
plat_dat->select_pcs = socfpga_dwmac_select_pcs;
+ dwmac->mac_interface = plat_dat->phy_interface;
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "altr,socfpga-stmmac-agilex5-tsn")) {
+ dwmac->mac_interface = PHY_INTERFACE_MODE_GMII;
+ if (phy_interface_mode_is_rgmii(plat_dat->phy_interface))
+ plat_dat->phy_interface = PHY_INTERFACE_MODE_RGMII;
+ }
+
ops->setup_plat_dat(dwmac);
return devm_stmmac_pltfr_probe(pdev, plat_dat, &stmmac_res);
@@ -674,6 +684,7 @@ static const struct of_device_id socfpga_dwmac_match[] = {
{ .compatible = "altr,socfpga-stmmac", .data = &socfpga_gen5_ops },
{ .compatible = "altr,socfpga-stmmac-a10-s10", .data = &socfpga_gen10_ops },
{ .compatible = "altr,socfpga-stmmac-agilex5", .data = &socfpga_agilex5_ops },
+ { .compatible = "altr,socfpga-stmmac-agilex5-tsn", .data = &socfpga_agilex5_ops },
{ }
};
MODULE_DEVICE_TABLE(of, socfpga_dwmac_match);
--
2.43.7
^ permalink raw reply related
* [PATCH v2 3/4] dt-bindings: net: altr,socfpga-stmmac: Add altr,socfpga-stmmac-agilex5-tsn compatible
From: muhammad.nazim.amirul.nazle.asmade @ 2026-07-07 6:13 UTC (permalink / raw)
To: dinguyen, maxime.chevallier
Cc: rmk+kernel, krzk+dt, conor+dt, robh, davem, edumazet, kuba,
pabeni, andrew+netdev, devicetree, linux-arm-kernel, netdev,
linux-kernel
In-Reply-To: <20260707061305.27580-1-muhammad.nazim.amirul.nazle.asmade@altera.com>
From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
The Agilex5 SoCDK TSN Config2 board uses a GMII-to-RGMII converter
implemented as FPGA soft IP between gmac1 and its PHY. This converter
provides the RGMII TX/RX clock delays. Document a new compatible string
for this MAC variant so the driver can detect it and configure the
interface accordingly.
Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
---
.../devicetree/bindings/net/altr,socfpga-stmmac.yaml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml b/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml
index 63084f762373..81f030986fa3 100644
--- a/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml
+++ b/Documentation/devicetree/bindings/net/altr,socfpga-stmmac.yaml
@@ -24,6 +24,7 @@ select:
- altr,socfpga-stmmac
- altr,socfpga-stmmac-a10-s10
- altr,socfpga-stmmac-agilex5
+ - altr,socfpga-stmmac-agilex5-tsn
required:
- compatible
@@ -46,6 +47,10 @@ properties:
- items:
- const: altr,socfpga-stmmac-agilex5
- const: snps,dwxgmac-2.10
+ - items:
+ - const: altr,socfpga-stmmac-agilex5-tsn
+ - const: altr,socfpga-stmmac-agilex5
+ - const: snps,dwxgmac-2.10
clocks:
minItems: 1
--
2.43.7
^ permalink raw reply related
* [PATCH net-next v10 3/5] net: wangxun: add reinit parameter to wx->do_reset callback
From: Jiawen Wu @ 2026-07-07 6:14 UTC (permalink / raw)
To: netdev
Cc: Mengyuan Lou, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Richard Cochran, Russell King,
Aleksandr Loktionov, Jacob Keller, Michal Swiatkowski,
Simon Horman, Kees Cook, Joe Damato, Breno Leitao, Jijie Shao,
Uwe Kleine-König (The Capable Hub), Rongguang Wei,
Larysa Zaremba, Fabio Baltieri, Jiawen Wu
In-Reply-To: <20260707061455.39564-1-jiawenwu@trustnetic.com>
To implement a simple hardware reset without tearing down the network
interface state, introduce a boolean 'reinit' parameter to wx->do_reset
callback.
Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/wangxun/libwx/wx_err.c | 2 +-
drivers/net/ethernet/wangxun/libwx/wx_ethtool.c | 2 +-
drivers/net/ethernet/wangxun/libwx/wx_lib.c | 4 ++--
drivers/net/ethernet/wangxun/libwx/wx_type.h | 2 +-
drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 4 ++--
drivers/net/ethernet/wangxun/ngbe/ngbe_type.h | 2 +-
drivers/net/ethernet/wangxun/txgbe/txgbe_main.c | 4 ++--
drivers/net/ethernet/wangxun/txgbe/txgbe_type.h | 2 +-
8 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.c b/drivers/net/ethernet/wangxun/libwx/wx_err.c
index b6e2d16d4a16..ee27f96735dc 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_err.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_err.c
@@ -16,7 +16,7 @@ static void wx_pf_reset_subtask(struct wx *wx)
wx_warn(wx, "Reset adapter.\n");
if (wx->do_reset)
- wx->do_reset(wx->netdev);
+ wx->do_reset(wx->netdev, true);
}
static void wx_reset_task(struct work_struct *work)
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
index 5df971aca9e3..d1356ff5d69b 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
@@ -395,7 +395,7 @@ static void wx_update_rsc(struct wx *wx)
/* reset the device to apply the new RSC setting */
if (need_reset && wx->do_reset)
- wx->do_reset(netdev);
+ wx->do_reset(netdev, true);
}
int wx_set_coalesce(struct net_device *netdev,
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
index 3a6db657c685..c8a4822d3819 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
@@ -3149,7 +3149,7 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features)
netdev->features = features;
if (changed & NETIF_F_HW_VLAN_CTAG_RX && wx->do_reset)
- wx->do_reset(netdev);
+ wx->do_reset(netdev, true);
else if (changed & (NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_FILTER))
wx_set_rx_mode(netdev);
@@ -3199,7 +3199,7 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features)
out:
if (need_reset && wx->do_reset)
- wx->do_reset(netdev);
+ wx->do_reset(netdev, true);
return 0;
}
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
index e12a59305193..edcb8d121b86 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
@@ -1409,7 +1409,7 @@ struct wx {
void (*atr)(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype);
void (*configure_fdir)(struct wx *wx);
int (*setup_tc)(struct net_device *netdev, u8 tc);
- void (*do_reset)(struct net_device *netdev);
+ void (*do_reset)(struct net_device *netdev, bool reinit);
int (*ptp_setup_sdp)(struct wx *wx);
void (*set_num_queues)(struct wx *wx);
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
index 996c48da52d7..92895f503511 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
@@ -633,11 +633,11 @@ static void ngbe_reinit_locked(struct wx *wx)
mutex_unlock(&wx->reset_lock);
}
-void ngbe_do_reset(struct net_device *netdev)
+void ngbe_do_reset(struct net_device *netdev, bool reinit)
{
struct wx *wx = netdev_priv(netdev);
- if (netif_running(netdev))
+ if (netif_running(netdev) && reinit)
ngbe_reinit_locked(wx);
else
ngbe_reset(wx);
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h
index 4f648f272c08..c9233dc7ae50 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h
@@ -125,6 +125,6 @@ extern char ngbe_driver_name[];
void ngbe_down(struct wx *wx);
void ngbe_up(struct wx *wx);
int ngbe_setup_tc(struct net_device *dev, u8 tc);
-void ngbe_do_reset(struct net_device *netdev);
+void ngbe_do_reset(struct net_device *netdev, bool reinit);
#endif /* _NGBE_TYPE_H_ */
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
index b1615f82a265..a8773712cff8 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
@@ -610,11 +610,11 @@ static void txgbe_reinit_locked(struct wx *wx)
mutex_unlock(&wx->reset_lock);
}
-void txgbe_do_reset(struct net_device *netdev)
+void txgbe_do_reset(struct net_device *netdev, bool reinit)
{
struct wx *wx = netdev_priv(netdev);
- if (netif_running(netdev))
+ if (netif_running(netdev) && reinit)
txgbe_reinit_locked(wx);
else
txgbe_reset(wx);
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
index 877234e3fdc2..3e93a3f309c1 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_type.h
@@ -313,7 +313,7 @@ extern char txgbe_driver_name[];
void txgbe_down(struct wx *wx);
void txgbe_up(struct wx *wx);
int txgbe_setup_tc(struct net_device *dev, u8 tc);
-void txgbe_do_reset(struct net_device *netdev);
+void txgbe_do_reset(struct net_device *netdev, bool reinit);
#define DECLARE_PHY_INTERFACE_MASK_ZERO(name) \
unsigned long name[PHY_INTERFACE_MODE_MAX] = { 0, }
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v10 0/5] net: wangxun: timeout and error
From: Jiawen Wu @ 2026-07-07 6:14 UTC (permalink / raw)
To: netdev
Cc: Mengyuan Lou, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Richard Cochran, Russell King,
Aleksandr Loktionov, Jacob Keller, Michal Swiatkowski,
Simon Horman, Kees Cook, Joe Damato, Breno Leitao, Jijie Shao,
Uwe Kleine-König (The Capable Hub), Rongguang Wei,
Larysa Zaremba, Fabio Baltieri, Jiawen Wu
It is about adding the Tx timeout process and pci_error_handlers.
When a PCIe error occurs, the txgbe device is able to recover on platform
that support AER interrupt. And for Tx timeout, the txgbe driver can
recover the device by reset process.
For ngbe devices, due to the absence of the current function, it cannot
br fully recovered once there is a PCIe error or Tx timeout. Its
function will be completed in the future.
---
Changes log:
v10:
- Remove pci_aer_clear_nonfatal_status().
v9: https://lore.kernel.org/all/20260701072357.33984-1-jiawenwu@trustnetic.com
- Add calling ptp_cancel_worker_sync() in wx_ptp_quiesce().
- Fix the typo of 'out' for wx_control_hw().
v8: https://lore.kernel.org/all/20260630031016.19820-1-jiawenwu@trustnetic.com
- Not destroying PTP clock in wx_soft_quiesce(), and keeping the PTP worker
alive but idle during PCIe recovery.
- Move wx_soft_quiesce() after wx_napi_disable_all().
- Use PCI_COMMAND_MEMORY and U16_MAX instead of magic number.
- Fix the leak of wx_control_hw() when WX_STATE_RES_FREED is set.
v7: https://lore.kernel.org/all/20260615065016.21672-1-jiawenwu@trustnetic.com
- Move ptp_clock_unregister() to be executed before free wx->ptp_tx_skb.
v6: https://lore.kernel.org/all/20260610060917.23980-1-jiawenwu@trustnetic.com
- Move the check of device status inside wx_soft_quiesce().
- Reverse the error return of txgbe_disable_device().
- Add PCIe error check in tx_timeout.
- Add WX_STATE_RES_FREED flag to avoid double-free of resources.
v5: https://lore.kernel.org/all/20260604085631.12720-1-jiawenwu@trustnetic.com
- Avoid the same name on two functions.
- Encode the device identity into the name of reset work queue.
- Change pr_err() to wx_err().
- Check WX_STATE_DOWN and WX_STATE_RESETTING at the entry of every work item.
- Implement wx_ptp_quiesce().
- Add netif_carrier_off() and netif_tx_disable() in soft_quiesce.
- Move resource free operations after PCIe recovery.
- Return error code in down path.
v4: https://lore.kernel.org/all/20260601072221.2952-1-jiawenwu@trustnetic.com
- Create a separate work queue for the reset task.
- Gate wx_watchdog_flush_tx() on netif_running().
- Add rtnl_lock() around wx->do_reset() in wx_io_slot_reset().
- Change .close_suspend() to .soft_quiesce() to avoid MMIO when PCI
channel is frozen.
v3: https://lore.kernel.org/all/20260509100540.32612-1-jiawenwu@trustnetic.com
- Merge the multiple string line into one in wx_handle_tx_hang().
- Remove the redundant warn messages.
- Use test_and_clear_bit() instead of checking the flag bit then clear it.
- Drop the Tx hang check in tx_timeout.
- Call wx_update_stats() before wx_check_tx_hang().
- Add Tx flush when link lost.
- Move wx_ptp_stop() into wx->close_suspend().
- Drop V2 patch 5/6 because WOL packets are handled before DMA ring.
- Check wx NULL pointer in wx_io_error_detected().
- Check perm failure before hardware teardown.
v2: https://lore.kernel.org/all/20260430082517.19612-1-jiawenwu@trustnetic.com
- Add the missing rtnl_unlock() at early return in wx_reset_subtask().
- Replace ngbe_close() with ngbe_close_suspend() in ngbe_dev_shutdown().
- Add a patch to clear stored DMA addresses.
v1: https://lore.kernel.org/r/20260428021156.13564-1-jiawenwu@trustnetic.com
---
Jiawen Wu (5):
net: ngbe: implement libwx reset ops
net: wangxun: add Tx timeout process
net: wangxun: add reinit parameter to wx->do_reset callback
net: wangxun: implement soft quiesce for PCIe error recovery
net: wangxun: add pcie error handler
drivers/net/ethernet/wangxun/libwx/Makefile | 2 +-
drivers/net/ethernet/wangxun/libwx/wx_err.c | 317 ++++++++++++++++++
drivers/net/ethernet/wangxun/libwx/wx_err.h | 18 +
.../net/ethernet/wangxun/libwx/wx_ethtool.c | 2 +-
drivers/net/ethernet/wangxun/libwx/wx_hw.c | 17 +-
drivers/net/ethernet/wangxun/libwx/wx_lib.c | 59 +++-
drivers/net/ethernet/wangxun/libwx/wx_lib.h | 1 +
drivers/net/ethernet/wangxun/libwx/wx_ptp.c | 21 ++
drivers/net/ethernet/wangxun/libwx/wx_ptp.h | 1 +
drivers/net/ethernet/wangxun/libwx/wx_type.h | 25 +-
.../net/ethernet/wangxun/ngbe/ngbe_ethtool.c | 1 -
drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 83 ++++-
drivers/net/ethernet/wangxun/ngbe/ngbe_type.h | 1 +
.../net/ethernet/wangxun/txgbe/txgbe_main.c | 57 +++-
.../net/ethernet/wangxun/txgbe/txgbe_type.h | 2 +-
15 files changed, 588 insertions(+), 19 deletions(-)
create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_err.c
create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_err.h
--
2.51.0
^ permalink raw reply
* [PATCH net-next v10 5/5] net: wangxun: add pcie error handler
From: Jiawen Wu @ 2026-07-07 6:14 UTC (permalink / raw)
To: netdev
Cc: Mengyuan Lou, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Richard Cochran, Russell King,
Aleksandr Loktionov, Jacob Keller, Michal Swiatkowski,
Simon Horman, Kees Cook, Joe Damato, Breno Leitao, Jijie Shao,
Uwe Kleine-König (The Capable Hub), Rongguang Wei,
Larysa Zaremba, Fabio Baltieri, Jiawen Wu
In-Reply-To: <20260707061455.39564-1-jiawenwu@trustnetic.com>
Support AER driver to handle the PCIe errors. Sometimes netdev watchdog
Tx timeout happens before the AER error report when a PCIe error occurs,
CPU blocking would be caused by MMIO during the reset process. To
prevent it, check PCIe error status in .ndo_tx_timeout. The current
function of ngbe is not yet fully developed, it will be completed in the
future.
Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
---
drivers/net/ethernet/wangxun/libwx/wx_err.c | 144 +++++++++++++++++-
drivers/net/ethernet/wangxun/libwx/wx_err.h | 2 +
drivers/net/ethernet/wangxun/libwx/wx_type.h | 4 +
drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 32 +++-
.../net/ethernet/wangxun/txgbe/txgbe_main.c | 31 +++-
5 files changed, 208 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.c b/drivers/net/ethernet/wangxun/libwx/wx_err.c
index ee27f96735dc..0cb99e456a1c 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_err.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_err.c
@@ -4,11 +4,120 @@
#include <linux/netdevice.h>
#include <linux/pci.h>
+#include <linux/aer.h>
#include "wx_type.h"
#include "wx_lib.h"
#include "wx_err.h"
+/**
+ * wx_io_error_detected - called when PCI error is detected
+ * @pdev: Pointer to PCI device
+ * @state: The current pci connection state
+ *
+ * Return: pci_ers_result_t.
+ *
+ * This function is called after a PCI bus error affecting
+ * this device has been detected.
+ */
+static pci_ers_result_t wx_io_error_detected(struct pci_dev *pdev,
+ pci_channel_state_t state)
+{
+ struct wx *wx = pci_get_drvdata(pdev);
+ struct net_device *netdev;
+
+ if (!wx)
+ return PCI_ERS_RESULT_DISCONNECT;
+
+ netdev = wx->netdev;
+ if (!netif_device_present(netdev))
+ return PCI_ERS_RESULT_DISCONNECT;
+
+ if (state == pci_channel_io_perm_failure)
+ return PCI_ERS_RESULT_DISCONNECT;
+
+ rtnl_lock();
+ netif_device_detach(netdev);
+ set_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags);
+ wx_soft_quiesce(wx);
+
+ if (!test_and_set_bit(WX_STATE_DISABLED, wx->state))
+ pci_disable_device(pdev);
+ rtnl_unlock();
+
+ /* Request a slot reset. */
+ return PCI_ERS_RESULT_NEED_RESET;
+}
+
+/**
+ * wx_io_slot_reset - called after the pci bus has been reset.
+ * @pdev: Pointer to PCI device
+ *
+ * Return: pci_ers_result_t.
+ *
+ * Restart the card from scratch, as if from a cold-boot.
+ */
+static pci_ers_result_t wx_io_slot_reset(struct pci_dev *pdev)
+{
+ struct wx *wx = pci_get_drvdata(pdev);
+
+ if (pci_enable_device_mem(pdev)) {
+ wx_err(wx, "Cannot re-enable PCI device after reset.\n");
+ return PCI_ERS_RESULT_DISCONNECT;
+ }
+
+ /* make all memory operations done before clearing the flag */
+ smp_mb__before_atomic();
+ clear_bit(WX_STATE_DISABLED, wx->state);
+ clear_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags);
+ pci_set_master(pdev);
+ pci_restore_state(pdev);
+ pci_wake_from_d3(pdev, false);
+
+ rtnl_lock();
+ if (netif_running(wx->netdev) && wx->down_suspend)
+ wx->down_suspend(wx);
+ if (wx->do_reset)
+ wx->do_reset(wx->netdev, false);
+ rtnl_unlock();
+
+ return PCI_ERS_RESULT_RECOVERED;
+}
+
+/**
+ * wx_io_resume - called when traffic can start flowing again.
+ * @pdev: Pointer to PCI device
+ *
+ * This callback is called when the error recovery driver tells us that
+ * its OK to resume normal operation.
+ */
+static void wx_io_resume(struct pci_dev *pdev)
+{
+ struct wx *wx = pci_get_drvdata(pdev);
+ struct net_device *netdev;
+ int err;
+
+ netdev = wx->netdev;
+ rtnl_lock();
+ if (netif_running(netdev)) {
+ err = netdev->netdev_ops->ndo_open(netdev);
+ if (err) {
+ wx_err(wx, "Failed to open netdev after reset\n");
+ goto out;
+ }
+ }
+ netif_device_attach(netdev);
+out:
+ rtnl_unlock();
+}
+
+const struct pci_error_handlers wx_err_handler = {
+ .error_detected = wx_io_error_detected,
+ .slot_reset = wx_io_slot_reset,
+ .resume = wx_io_resume,
+};
+EXPORT_SYMBOL(wx_err_handler);
+
static void wx_pf_reset_subtask(struct wx *wx)
{
if (!test_and_clear_bit(WX_FLAG_NEED_PF_RESET, wx->flags))
@@ -25,6 +134,9 @@ static void wx_reset_task(struct work_struct *work)
rtnl_lock();
+ if (test_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags))
+ wx_soft_quiesce(wx);
+
if (test_bit(WX_STATE_DOWN, wx->state) ||
test_bit(WX_STATE_RESETTING, wx->state))
goto out;
@@ -139,6 +251,33 @@ void wx_check_hang_subtask(struct wx *wx)
}
EXPORT_SYMBOL(wx_check_hang_subtask);
+static bool wx_check_pcie_error(struct wx *wx)
+{
+ u16 vid, pci_cmd;
+
+ pci_read_config_word(wx->pdev, PCI_VENDOR_ID, &vid);
+ pci_read_config_word(wx->pdev, PCI_COMMAND, &pci_cmd);
+
+ /* PCIe link loss or memory space can't access */
+ if (vid == U16_MAX || !(pci_cmd & PCI_COMMAND_MEMORY))
+ return true;
+
+ return false;
+}
+
+static void wx_tx_timeout_recovery(struct wx *wx)
+{
+ /*
+ * When a PCIe hardware error occurs, the driver should initiate a PCIe
+ * recovery mechanism. However, this recovery flow relies on the AER
+ * driver for current kernel policy. Therefore, a self-contained
+ * recovery mechanism is not implemented yet.
+ */
+ set_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags);
+ wx_err(wx, "PCIe error detected during tx timeout\n");
+ queue_work(wx->reset_wq, &wx->reset_task);
+}
+
static void wx_tx_timeout_reset(struct wx *wx)
{
if (test_bit(WX_STATE_DOWN, wx->state))
@@ -153,7 +292,10 @@ void wx_tx_timeout(struct net_device *netdev, unsigned int __always_unused txque
{
struct wx *wx = netdev_priv(netdev);
- wx_tx_timeout_reset(wx);
+ if (wx_check_pcie_error(wx))
+ wx_tx_timeout_recovery(wx);
+ else
+ wx_tx_timeout_reset(wx);
}
EXPORT_SYMBOL(wx_tx_timeout);
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.h b/drivers/net/ethernet/wangxun/libwx/wx_err.h
index 1eed13e48095..a6a82a263528 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_err.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_err.h
@@ -7,6 +7,8 @@
#ifndef _WX_ERR_H_
#define _WX_ERR_H_
+extern const struct pci_error_handlers wx_err_handler;
+
void wx_check_err_subtask(struct wx *wx);
int wx_init_err_task(struct wx *wx);
void wx_check_hang_subtask(struct wx *wx);
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
index edcb8d121b86..aa357e260c4c 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
@@ -1222,6 +1222,8 @@ enum wx_state {
WX_STATE_PTP_RUNNING,
WX_STATE_PTP_TX_IN_PROGRESS,
WX_STATE_SERVICE_SCHED,
+ WX_STATE_DISABLED,
+ WX_STATE_RES_FREED,
WX_STATE_NBITS /* must be last */
};
@@ -1289,6 +1291,7 @@ enum wx_pf_flags {
WX_FLAG_RX_MERGE_ENABLED,
WX_FLAG_TXHEAD_WB_ENABLED,
WX_FLAG_NEED_PF_RESET,
+ WX_FLAG_NEED_PCIE_RECOVERY,
WX_PF_FLAGS_NBITS /* must be last */
};
@@ -1410,6 +1413,7 @@ struct wx {
void (*configure_fdir)(struct wx *wx);
int (*setup_tc)(struct net_device *netdev, u8 tc);
void (*do_reset)(struct net_device *netdev, bool reinit);
+ void (*down_suspend)(struct wx *wx);
int (*ptp_setup_sdp)(struct wx *wx);
void (*set_num_queues)(struct wx *wx);
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
index 92895f503511..56d4b63387fd 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
@@ -47,6 +47,20 @@ static const struct pci_device_id ngbe_pci_tbl[] = {
{ }
};
+static void ngbe_down_suspend(struct wx *wx)
+{
+ if (test_and_set_bit(WX_STATE_RES_FREED, wx->state))
+ return;
+
+ phylink_stop(wx->phylink);
+ phylink_disconnect_phy(wx->phylink);
+ wx_clean_all_tx_rings(wx);
+ wx_clean_all_rx_rings(wx);
+ wx_free_irq(wx);
+ wx_free_isb_resources(wx);
+ wx_free_resources(wx);
+}
+
/**
* ngbe_init_type_code - Initialize the shared code
* @wx: pointer to hardware structure
@@ -135,6 +149,7 @@ static int ngbe_sw_init(struct wx *wx)
wx->mbx.size = WX_VXMAILBOX_SIZE;
wx->setup_tc = ngbe_setup_tc;
wx->do_reset = ngbe_do_reset;
+ wx->down_suspend = ngbe_down_suspend;
set_bit(0, &wx->fwd_bitmask);
return 0;
@@ -413,6 +428,9 @@ static void ngbe_disable_device(struct wx *wx)
static void ngbe_reset(struct wx *wx)
{
+ if (test_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags))
+ return;
+
wx_flush_sw_mac_table(wx);
wx_mac_set_default_filter(wx, wx->mac.addr);
if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
@@ -435,6 +453,7 @@ static void ngbe_up_complete(struct wx *wx)
/* make sure to complete pre-operations */
smp_mb__before_atomic();
clear_bit(WX_STATE_DOWN, wx->state);
+ clear_bit(WX_STATE_RES_FREED, wx->state);
wx_napi_enable_all(wx);
/* enable transmits */
netif_tx_start_all_queues(wx->netdev);
@@ -529,12 +548,16 @@ static int ngbe_close(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
+ if (test_bit(WX_STATE_RES_FREED, wx->state))
+ goto out;
+
wx_ptp_stop(wx);
ngbe_down(wx);
wx_free_irq(wx);
wx_free_isb_resources(wx);
wx_free_resources(wx);
phylink_disconnect_phy(wx->phylink);
+out:
wx_control_hw(wx, false);
return 0;
@@ -566,7 +589,8 @@ static void ngbe_dev_shutdown(struct pci_dev *pdev, bool *enable_wake)
*enable_wake = !!wufc;
wx_control_hw(wx, false);
- pci_disable_device(pdev);
+ if (!test_and_set_bit(WX_STATE_DISABLED, wx->state))
+ pci_disable_device(pdev);
}
static void ngbe_shutdown(struct pci_dev *pdev)
@@ -855,6 +879,7 @@ static int ngbe_probe(struct pci_dev *pdev,
goto err_register;
pci_set_drvdata(pdev, wx);
+ pci_save_state(pdev);
return 0;
@@ -910,7 +935,8 @@ static void ngbe_remove(struct pci_dev *pdev)
kfree(wx->mac_table);
wx_clear_interrupt_scheme(wx);
- pci_disable_device(pdev);
+ if (!test_and_set_bit(WX_STATE_DISABLED, wx->state))
+ pci_disable_device(pdev);
}
static int ngbe_suspend(struct pci_dev *pdev, pm_message_t state)
@@ -937,6 +963,7 @@ static int ngbe_resume(struct pci_dev *pdev)
wx_err(wx, "Cannot enable PCI device from suspend\n");
return err;
}
+ clear_bit(WX_STATE_DISABLED, wx->state);
pci_set_master(pdev);
device_wakeup_disable(&pdev->dev);
@@ -961,6 +988,7 @@ static struct pci_driver ngbe_driver = {
.resume = ngbe_resume,
.shutdown = ngbe_shutdown,
.sriov_configure = wx_pci_sriov_configure,
+ .err_handler = &wx_err_handler,
};
module_pci_driver(ngbe_driver);
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
index a7bde03a98fe..d85ee83192e4 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
@@ -155,6 +155,7 @@ static void txgbe_up_complete(struct wx *wx)
/* make sure to complete pre-operations */
smp_mb__before_atomic();
clear_bit(WX_STATE_DOWN, wx->state);
+ clear_bit(WX_STATE_RES_FREED, wx->state);
wx_napi_enable_all(wx);
switch (wx->mac.type) {
@@ -198,6 +199,9 @@ static void txgbe_reset(struct wx *wx)
u8 old_addr[ETH_ALEN];
int err;
+ if (test_bit(WX_FLAG_NEED_PCIE_RECOVERY, wx->flags))
+ return;
+
err = txgbe_reset_hw(wx);
if (err != 0)
wx_err(wx, "Hardware Error: %d\n", err);
@@ -304,6 +308,20 @@ void txgbe_up(struct wx *wx)
txgbe_up_complete(wx);
}
+static void txgbe_down_suspend(struct wx *wx)
+{
+ if (test_and_set_bit(WX_STATE_RES_FREED, wx->state))
+ return;
+
+ phylink_stop(wx->phylink);
+ wx_clean_all_tx_rings(wx);
+ wx_clean_all_rx_rings(wx);
+ wx_free_irq(wx);
+ txgbe_free_misc_irq(wx->priv);
+ wx_free_resources(wx);
+ txgbe_fdir_filter_exit(wx);
+}
+
/**
* txgbe_init_type_code - Initialize the shared code
* @wx: pointer to hardware structure
@@ -420,6 +438,7 @@ static int txgbe_sw_init(struct wx *wx)
wx->setup_tc = txgbe_setup_tc;
wx->do_reset = txgbe_do_reset;
+ wx->down_suspend = txgbe_down_suspend;
set_bit(0, &wx->fwd_bitmask);
switch (wx->mac.type) {
@@ -530,12 +549,16 @@ static int txgbe_close(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
+ if (test_bit(WX_STATE_RES_FREED, wx->state))
+ goto out;
+
wx_ptp_stop(wx);
txgbe_down(wx);
wx_free_irq(wx);
txgbe_free_misc_irq(wx->priv);
wx_free_resources(wx);
txgbe_fdir_filter_exit(wx);
+out:
wx_control_hw(wx, false);
return 0;
@@ -556,7 +579,8 @@ static void txgbe_dev_shutdown(struct pci_dev *pdev)
wx_control_hw(wx, false);
- pci_disable_device(pdev);
+ if (!test_and_set_bit(WX_STATE_DISABLED, wx->state))
+ pci_disable_device(pdev);
}
static void txgbe_shutdown(struct pci_dev *pdev)
@@ -907,6 +931,7 @@ static int txgbe_probe(struct pci_dev *pdev,
goto err_remove_phy;
pci_set_drvdata(pdev, wx);
+ pci_save_state(pdev);
netif_tx_stop_all_queues(netdev);
@@ -981,7 +1006,8 @@ static void txgbe_remove(struct pci_dev *pdev)
kfree(wx->mac_table);
wx_clear_interrupt_scheme(wx);
- pci_disable_device(pdev);
+ if (!test_and_set_bit(WX_STATE_DISABLED, wx->state))
+ pci_disable_device(pdev);
}
static struct pci_driver txgbe_driver = {
@@ -991,6 +1017,7 @@ static struct pci_driver txgbe_driver = {
.remove = txgbe_remove,
.shutdown = txgbe_shutdown,
.sriov_configure = wx_pci_sriov_configure,
+ .err_handler = &wx_err_handler,
};
module_pci_driver(txgbe_driver);
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v10 2/5] net: wangxun: add Tx timeout process
From: Jiawen Wu @ 2026-07-07 6:14 UTC (permalink / raw)
To: netdev
Cc: Mengyuan Lou, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Richard Cochran, Russell King,
Aleksandr Loktionov, Jacob Keller, Michal Swiatkowski,
Simon Horman, Kees Cook, Joe Damato, Breno Leitao, Jijie Shao,
Uwe Kleine-König (The Capable Hub), Rongguang Wei,
Larysa Zaremba, Fabio Baltieri, Jiawen Wu
In-Reply-To: <20260707061455.39564-1-jiawenwu@trustnetic.com>
Implement .ndo_tx_timeout to handle Tx side timeout event. When a Tx
timeout event occur, it will trigger driver into reset process. And
allocate a separate work queue for reset process.
The WX_HANG_CHECK_ARMED bit is set to indicate a potential hang. It will
be cleared if a pause frame is received to avoid false hang detection
caused by pause frames.
Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
---
drivers/net/ethernet/wangxun/libwx/Makefile | 2 +-
drivers/net/ethernet/wangxun/libwx/wx_err.c | 175 ++++++++++++++++++
drivers/net/ethernet/wangxun/libwx/wx_err.h | 16 ++
drivers/net/ethernet/wangxun/libwx/wx_hw.c | 17 +-
drivers/net/ethernet/wangxun/libwx/wx_lib.c | 37 ++++
drivers/net/ethernet/wangxun/libwx/wx_type.h | 19 +-
drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 14 ++
.../net/ethernet/wangxun/txgbe/txgbe_main.c | 14 ++
8 files changed, 289 insertions(+), 5 deletions(-)
create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_err.c
create mode 100644 drivers/net/ethernet/wangxun/libwx/wx_err.h
diff --git a/drivers/net/ethernet/wangxun/libwx/Makefile b/drivers/net/ethernet/wangxun/libwx/Makefile
index a71b0ad77de3..c8724bb129aa 100644
--- a/drivers/net/ethernet/wangxun/libwx/Makefile
+++ b/drivers/net/ethernet/wangxun/libwx/Makefile
@@ -4,5 +4,5 @@
obj-$(CONFIG_LIBWX) += libwx.o
-libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o wx_ptp.o wx_mbx.o wx_sriov.o
+libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o wx_ptp.o wx_mbx.o wx_sriov.o wx_err.o
libwx-objs += wx_vf.o wx_vf_lib.o wx_vf_common.o
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.c b/drivers/net/ethernet/wangxun/libwx/wx_err.c
new file mode 100644
index 000000000000..b6e2d16d4a16
--- /dev/null
+++ b/drivers/net/ethernet/wangxun/libwx/wx_err.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2015 - 2026 Beijing WangXun Technology Co., Ltd. */
+/* Copyright (c) 1999 - 2026 Intel Corporation. */
+
+#include <linux/netdevice.h>
+#include <linux/pci.h>
+
+#include "wx_type.h"
+#include "wx_lib.h"
+#include "wx_err.h"
+
+static void wx_pf_reset_subtask(struct wx *wx)
+{
+ if (!test_and_clear_bit(WX_FLAG_NEED_PF_RESET, wx->flags))
+ return;
+
+ wx_warn(wx, "Reset adapter.\n");
+ if (wx->do_reset)
+ wx->do_reset(wx->netdev);
+}
+
+static void wx_reset_task(struct work_struct *work)
+{
+ struct wx *wx = container_of(work, struct wx, reset_task);
+
+ rtnl_lock();
+
+ if (test_bit(WX_STATE_DOWN, wx->state) ||
+ test_bit(WX_STATE_RESETTING, wx->state))
+ goto out;
+
+ wx_pf_reset_subtask(wx);
+
+out:
+ rtnl_unlock();
+}
+
+void wx_check_err_subtask(struct wx *wx)
+{
+ if (test_bit(WX_FLAG_NEED_PF_RESET, wx->flags))
+ queue_work(wx->reset_wq, &wx->reset_task);
+}
+EXPORT_SYMBOL(wx_check_err_subtask);
+
+int wx_init_err_task(struct wx *wx)
+{
+ wx->reset_wq = alloc_workqueue("%s_reset_wq_%x", WQ_UNBOUND | WQ_HIGHPRI,
+ 1, wx->driver_name, pci_dev_id(wx->pdev));
+ if (!wx->reset_wq) {
+ wx_err(wx, "Failed to create wx_reset_wq workqueue\n");
+ return -ENOMEM;
+ }
+
+ INIT_WORK(&wx->reset_task, wx_reset_task);
+ return 0;
+}
+EXPORT_SYMBOL(wx_init_err_task);
+
+static bool wx_ring_tx_pending(struct wx *wx)
+{
+ int i;
+
+ for (i = 0; i < wx->num_tx_queues; i++) {
+ struct wx_ring *tx_ring = wx->tx_ring[i];
+
+ if (tx_ring->next_to_use != tx_ring->next_to_clean)
+ return true;
+ }
+
+ return false;
+}
+
+static bool wx_vf_tx_pending(struct wx *wx)
+{
+ struct wx_ring_feature *vmdq = &wx->ring_feature[RING_F_VMDQ];
+ u32 q_per_pool = __ALIGN_MASK(1, ~vmdq->mask);
+ u32 i, j;
+
+ if (!wx->num_vfs)
+ return false;
+
+ for (i = 0; i < wx->num_vfs; i++) {
+ for (j = 0; j < q_per_pool; j++) {
+ u32 h, t;
+
+ h = rd32(wx, WX_PX_TR_RP_PV(q_per_pool, i, j));
+ t = rd32(wx, WX_PX_TR_WP_PV(q_per_pool, i, j));
+
+ if (h != t)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void wx_watchdog_flush_tx(struct wx *wx)
+{
+ if (!netif_running(wx->netdev))
+ return;
+ if (netif_carrier_ok(wx->netdev))
+ return;
+
+ if (wx_ring_tx_pending(wx) || wx_vf_tx_pending(wx)) {
+ /* We've lost link, so the controller stops DMA,
+ * but we've got queued Tx work that's never going
+ * to get done, so reset controller to flush Tx.
+ * (Do the reset outside of interrupt context).
+ */
+ wx_warn(wx, "initiating reset due to lost link with pending Tx work\n");
+ set_bit(WX_FLAG_NEED_PF_RESET, wx->flags);
+ }
+}
+
+static void wx_detect_tx_hang(struct wx *wx)
+{
+ int i;
+
+ /* If we're down or resetting, just bail */
+ if (!netif_running(wx->netdev) ||
+ test_bit(WX_STATE_RESETTING, wx->state))
+ return;
+
+ /* Force detection of hung controller */
+ if (netif_carrier_ok(wx->netdev)) {
+ for (i = 0; i < wx->num_tx_queues; i++)
+ set_bit(WX_TX_DETECT_HANG, wx->tx_ring[i]->state);
+ }
+}
+
+void wx_check_hang_subtask(struct wx *wx)
+{
+ if (test_bit(WX_STATE_DOWN, wx->state) ||
+ test_bit(WX_STATE_RESETTING, wx->state))
+ return;
+
+ wx_watchdog_flush_tx(wx);
+ wx_detect_tx_hang(wx);
+}
+EXPORT_SYMBOL(wx_check_hang_subtask);
+
+static void wx_tx_timeout_reset(struct wx *wx)
+{
+ if (test_bit(WX_STATE_DOWN, wx->state))
+ return;
+
+ set_bit(WX_FLAG_NEED_PF_RESET, wx->flags);
+ wx_warn(wx, "initiating reset due to tx timeout\n");
+ wx_service_event_schedule(wx);
+}
+
+void wx_tx_timeout(struct net_device *netdev, unsigned int __always_unused txqueue)
+{
+ struct wx *wx = netdev_priv(netdev);
+
+ wx_tx_timeout_reset(wx);
+}
+EXPORT_SYMBOL(wx_tx_timeout);
+
+void wx_handle_tx_hang(struct wx_ring *tx_ring, unsigned int next)
+{
+ struct wx *wx = netdev_priv(tx_ring->netdev);
+
+ wx_warn(wx,
+ "Detected Tx Unit Hang: Queue %d, TDH %x, TDT %x, ntu %x, ntc %x, ntc.time_stamp %lx, jiffies %lx\n",
+ tx_ring->queue_index,
+ rd32(wx, WX_PX_TR_RP(tx_ring->reg_idx)),
+ rd32(wx, WX_PX_TR_WP(tx_ring->reg_idx)),
+ tx_ring->next_to_use, next,
+ tx_ring->tx_buffer_info[next].time_stamp, jiffies);
+
+ netif_stop_subqueue(tx_ring->netdev, tx_ring->queue_index);
+
+ wx_tx_timeout_reset(wx);
+}
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_err.h b/drivers/net/ethernet/wangxun/libwx/wx_err.h
new file mode 100644
index 000000000000..1eed13e48095
--- /dev/null
+++ b/drivers/net/ethernet/wangxun/libwx/wx_err.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * WangXun Gigabit PCI Express Linux driver
+ * Copyright (c) 2015 - 2026 Beijing WangXun Technology Co., Ltd.
+ */
+
+#ifndef _WX_ERR_H_
+#define _WX_ERR_H_
+
+void wx_check_err_subtask(struct wx *wx);
+int wx_init_err_task(struct wx *wx);
+void wx_check_hang_subtask(struct wx *wx);
+void wx_tx_timeout(struct net_device *netdev, unsigned int txqueue);
+void wx_handle_tx_hang(struct wx_ring *tx_ring, unsigned int next);
+
+#endif /* _WX_ERR_H_ */
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
index 260e14d5d541..122c4952d203 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
@@ -1932,6 +1932,7 @@ static void wx_configure_tx_ring(struct wx *wx,
else
ring->atr_sample_rate = 0;
+ bitmap_zero(ring->state, WX_RING_STATE_NBITS);
/* reinitialize tx_buffer_info */
memset(ring->tx_buffer_info, 0,
sizeof(struct wx_tx_buffer) * ring->count);
@@ -2851,16 +2852,26 @@ EXPORT_SYMBOL(wx_fc_enable);
static void wx_update_xoff_rx_lfc(struct wx *wx)
{
struct wx_hw_stats *hwstats = &wx->stats;
+ u64 data;
+ int i;
if (wx->fc.mode != wx_fc_full &&
wx->fc.mode != wx_fc_rx_pause)
return;
if (wx->mac.type >= wx_mac_aml)
- hwstats->lxoffrxc += rd32_wrap(wx, WX_MAC_LXOFFRXC_AML,
- &wx->last_stats.lxoffrxc);
+ data = rd32_wrap(wx, WX_MAC_LXOFFRXC_AML,
+ &wx->last_stats.lxoffrxc);
else
- hwstats->lxoffrxc += rd64(wx, WX_MAC_LXOFFRXC);
+ data = rd64(wx, WX_MAC_LXOFFRXC);
+ hwstats->lxoffrxc += data;
+
+ /* refill credits (no tx hang) if we received xoff */
+ if (!data)
+ return;
+
+ for (i = 0; i < wx->num_tx_queues; i++)
+ clear_bit(WX_HANG_CHECK_ARMED, wx->tx_ring[i]->state);
}
/**
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
index 814d88d2aee4..3a6db657c685 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
@@ -14,6 +14,7 @@
#include "wx_type.h"
#include "wx_lib.h"
+#include "wx_err.h"
#include "wx_ptp.h"
#include "wx_hw.h"
#include "wx_vf_lib.h"
@@ -742,6 +743,37 @@ static struct netdev_queue *wx_txring_txq(const struct wx_ring *ring)
return netdev_get_tx_queue(ring->netdev, ring->queue_index);
}
+static u32 wx_get_tx_pending(struct wx_ring *ring)
+{
+ unsigned int head, tail;
+
+ head = ring->next_to_clean;
+ tail = ring->next_to_use;
+
+ return ((head <= tail) ? tail : tail + ring->count) - head;
+}
+
+static bool wx_check_tx_hang(struct wx_ring *ring)
+{
+ u32 tx_done_old = ring->tx_stats.tx_done_old;
+ u32 tx_pending = wx_get_tx_pending(ring);
+ u32 tx_done = ring->stats.packets;
+
+ if (!test_and_clear_bit(WX_TX_DETECT_HANG, ring->state))
+ return false;
+
+ if (tx_done_old == tx_done && tx_pending)
+ /* make sure it is true for two checks in a row */
+ return test_and_set_bit(WX_HANG_CHECK_ARMED, ring->state);
+
+ /* update completed stats and continue */
+ ring->tx_stats.tx_done_old = tx_done;
+ /* reset the countdown */
+ clear_bit(WX_HANG_CHECK_ARMED, ring->state);
+
+ return false;
+}
+
/**
* wx_clean_tx_irq - Reclaim resources after transmit completes
* @q_vector: structure containing interrupt and ring information
@@ -866,6 +898,11 @@ static bool wx_clean_tx_irq(struct wx_q_vector *q_vector,
netdev_tx_completed_queue(wx_txring_txq(tx_ring),
total_packets, total_bytes);
+ if (wx_check_tx_hang(tx_ring)) {
+ wx_handle_tx_hang(tx_ring, i);
+ return true;
+ }
+
#define TX_WAKE_THRESHOLD (DESC_NEEDED * 2)
if (unlikely(total_packets && netif_carrier_ok(tx_ring->netdev) &&
(wx_desc_unused(tx_ring) >= TX_WAKE_THRESHOLD))) {
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
index 65e3e55db1cf..e12a59305193 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
@@ -450,6 +450,11 @@ enum WX_MSCA_CMD_value {
#define WX_PX_TR_CFG_THRE_SHIFT 8
#define WX_PX_TR_CFG_HEAD_WB BIT(27)
+#define WX_PX_TR_RP_PV(q_per_pool, vf_number, vf_q_index) \
+ (WX_PX_TR_RP((q_per_pool) * (vf_number) + (vf_q_index)))
+#define WX_PX_TR_WP_PV(q_per_pool, vf_number, vf_q_index) \
+ (WX_PX_TR_WP((q_per_pool) * (vf_number) + (vf_q_index)))
+
/* Receive DMA Registers */
#define WX_PX_RR_BAL(_i) (0x01000 + ((_i) * 0x40))
#define WX_PX_RR_BAH(_i) (0x01004 + ((_i) * 0x40))
@@ -1040,6 +1045,7 @@ struct wx_queue_stats {
struct wx_tx_queue_stats {
u64 restart_queue;
u64 tx_busy;
+ u32 tx_done_old;
};
struct wx_rx_queue_stats {
@@ -1055,6 +1061,12 @@ struct wx_rx_queue_stats {
#define wx_for_each_ring(posm, headm) \
for (posm = (headm).ring; posm; posm = posm->next)
+enum wx_ring_state {
+ WX_TX_DETECT_HANG,
+ WX_HANG_CHECK_ARMED,
+ WX_RING_STATE_NBITS
+};
+
struct wx_ring_container {
struct wx_ring *ring; /* pointer to linked list of rings */
unsigned int total_bytes; /* total bytes processed this int */
@@ -1074,6 +1086,7 @@ struct wx_ring {
struct wx_tx_buffer *tx_buffer_info;
struct wx_rx_buffer *rx_buffer_info;
};
+ DECLARE_BITMAP(state, WX_RING_STATE_NBITS);
u8 __iomem *tail;
dma_addr_t dma; /* phys. address of descriptor ring */
dma_addr_t headwb_dma;
@@ -1275,6 +1288,7 @@ enum wx_pf_flags {
WX_FLAG_NEED_DO_RESET,
WX_FLAG_RX_MERGE_ENABLED,
WX_FLAG_TXHEAD_WB_ENABLED,
+ WX_FLAG_NEED_PF_RESET,
WX_PF_FLAGS_NBITS /* must be last */
};
@@ -1423,6 +1437,8 @@ struct wx {
struct timer_list service_timer;
struct work_struct service_task;
+ struct work_struct reset_task;
+ struct workqueue_struct *reset_wq;
struct mutex reset_lock; /* mutex for reset */
};
@@ -1505,7 +1521,8 @@ rd32_wrap(struct wx *wx, u32 reg, u32 *last)
#define wx_err(wx, fmt, arg...) \
dev_err(&(wx)->pdev->dev, fmt, ##arg)
-
+#define wx_warn(wx, fmt, arg...) \
+ dev_warn(&(wx)->pdev->dev, fmt, ##arg)
#define wx_dbg(wx, fmt, arg...) \
dev_dbg(&(wx)->pdev->dev, fmt, ##arg)
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
index bbbec9b43bc2..996c48da52d7 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
@@ -14,6 +14,7 @@
#include "../libwx/wx_type.h"
#include "../libwx/wx_hw.h"
#include "../libwx/wx_lib.h"
+#include "../libwx/wx_err.h"
#include "../libwx/wx_ptp.h"
#include "../libwx/wx_mbx.h"
#include "../libwx/wx_sriov.h"
@@ -148,6 +149,8 @@ static void ngbe_service_task(struct work_struct *work)
struct wx *wx = container_of(work, struct wx, service_task);
wx_update_stats(wx);
+ wx_check_hang_subtask(wx);
+ wx_check_err_subtask(wx);
wx_service_event_complete(wx);
}
@@ -393,6 +396,7 @@ static void ngbe_disable_device(struct wx *wx)
netif_tx_stop_all_queues(netdev);
netif_tx_disable(netdev);
+ clear_bit(WX_FLAG_NEED_PF_RESET, wx->flags);
timer_delete_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
@@ -644,6 +648,7 @@ static const struct net_device_ops ngbe_netdev_ops = {
.ndo_stop = ngbe_close,
.ndo_change_mtu = wx_change_mtu,
.ndo_start_xmit = wx_xmit_frame,
+ .ndo_tx_timeout = wx_tx_timeout,
.ndo_set_rx_mode = wx_set_rx_mode,
.ndo_set_features = wx_set_features,
.ndo_fix_features = wx_fix_features,
@@ -733,6 +738,7 @@ static int ngbe_probe(struct pci_dev *pdev,
wx->driver_name = ngbe_driver_name;
ngbe_set_ethtool_ops(netdev);
netdev->netdev_ops = &ngbe_netdev_ops;
+ netdev->watchdog_timeo = 5 * HZ;
netdev->features = NETIF_F_SG | NETIF_F_IP_CSUM |
NETIF_F_TSO | NETIF_F_TSO6 |
@@ -829,6 +835,10 @@ static int ngbe_probe(struct pci_dev *pdev,
eth_hw_addr_set(netdev, wx->mac.perm_addr);
wx_mac_set_default_filter(wx, wx->mac.perm_addr);
+ err = wx_init_err_task(wx);
+ if (err)
+ goto err_free_mac_table;
+
ngbe_init_service(wx);
err = wx_init_interrupt_scheme(wx);
@@ -856,6 +866,8 @@ static int ngbe_probe(struct pci_dev *pdev,
err_cancel_service:
timer_delete_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
+ cancel_work_sync(&wx->reset_task);
+ destroy_workqueue(wx->reset_wq);
err_free_mac_table:
kfree(wx->rss_key);
kfree(wx->mac_table);
@@ -887,6 +899,8 @@ static void ngbe_remove(struct pci_dev *pdev)
timer_shutdown_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
+ cancel_work_sync(&wx->reset_task);
+ destroy_workqueue(wx->reset_wq);
phylink_destroy(wx->phylink);
pci_release_selected_regions(pdev,
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
index 20c5a295c6c2..b1615f82a265 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
@@ -14,6 +14,7 @@
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
+#include "../libwx/wx_err.h"
#include "../libwx/wx_ptp.h"
#include "../libwx/wx_hw.h"
#include "../libwx/wx_mbx.h"
@@ -123,6 +124,8 @@ static void txgbe_service_task(struct work_struct *work)
txgbe_module_detection_subtask(wx);
txgbe_link_config_subtask(wx);
wx_update_stats(wx);
+ wx_check_hang_subtask(wx);
+ wx_check_err_subtask(wx);
wx_service_event_complete(wx);
}
@@ -224,6 +227,7 @@ static void txgbe_disable_device(struct wx *wx)
wx_irq_disable(wx);
wx_napi_disable_all(wx);
+ clear_bit(WX_FLAG_NEED_PF_RESET, wx->flags);
timer_delete_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
@@ -654,6 +658,7 @@ static const struct net_device_ops txgbe_netdev_ops = {
.ndo_stop = txgbe_close,
.ndo_change_mtu = wx_change_mtu,
.ndo_start_xmit = wx_xmit_frame,
+ .ndo_tx_timeout = wx_tx_timeout,
.ndo_set_rx_mode = wx_set_rx_mode,
.ndo_set_features = wx_set_features,
.ndo_fix_features = wx_fix_features,
@@ -745,6 +750,7 @@ static int txgbe_probe(struct pci_dev *pdev,
wx->driver_name = txgbe_driver_name;
txgbe_set_ethtool_ops(netdev);
netdev->netdev_ops = &txgbe_netdev_ops;
+ netdev->watchdog_timeo = 5 * HZ;
netdev->udp_tunnel_nic_info = &txgbe_udp_tunnels;
/* setup the private structure */
@@ -814,6 +820,10 @@ static int txgbe_probe(struct pci_dev *pdev,
eth_hw_addr_set(netdev, wx->mac.perm_addr);
wx_mac_set_default_filter(wx, wx->mac.perm_addr);
+ err = wx_init_err_task(wx);
+ if (err)
+ goto err_free_mac_table;
+
txgbe_init_service(wx);
err = wx_init_interrupt_scheme(wx);
@@ -916,6 +926,8 @@ static int txgbe_probe(struct pci_dev *pdev,
err_cancel_service:
timer_delete_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
+ cancel_work_sync(&wx->reset_task);
+ destroy_workqueue(wx->reset_wq);
err_free_mac_table:
kfree(wx->rss_key);
kfree(wx->mac_table);
@@ -948,6 +960,8 @@ static void txgbe_remove(struct pci_dev *pdev)
timer_shutdown_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
+ cancel_work_sync(&wx->reset_task);
+ destroy_workqueue(wx->reset_wq);
txgbe_remove_phy(txgbe);
wx_free_isb_resources(wx);
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v10 4/5] net: wangxun: implement soft quiesce for PCIe error recovery
From: Jiawen Wu @ 2026-07-07 6:14 UTC (permalink / raw)
To: netdev
Cc: Mengyuan Lou, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Richard Cochran, Russell King,
Aleksandr Loktionov, Jacob Keller, Michal Swiatkowski,
Simon Horman, Kees Cook, Joe Damato, Breno Leitao, Jijie Shao,
Uwe Kleine-König (The Capable Hub), Rongguang Wei,
Larysa Zaremba, Fabio Baltieri, Jiawen Wu
In-Reply-To: <20260707061455.39564-1-jiawenwu@trustnetic.com>
Function wx_soft_quiesce() provide a lightweight shutdown path during
PCIe error recovery. It avoids MMIO-dependent operations in PCIe error
status.
Waiting for the service task to complete may unnecessarily delay PCIe
error recovery, especially if the work item is already blocked by the
hardware failure that triggered AER. So the service task is not
explicitly cancelled in quiesce path. As a measure to block the service
task, the checking of WX_STATE_DOWN and WX_STATE_RESETTING is added at
the entry of relevant work item.
Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/wangxun/libwx/wx_lib.c | 18 ++++++++++++++++
drivers/net/ethernet/wangxun/libwx/wx_lib.h | 1 +
drivers/net/ethernet/wangxun/libwx/wx_ptp.c | 21 +++++++++++++++++++
drivers/net/ethernet/wangxun/libwx/wx_ptp.h | 1 +
.../net/ethernet/wangxun/txgbe/txgbe_main.c | 8 +++++++
5 files changed, 49 insertions(+)
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
index c8a4822d3819..a65312c8078f 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c
@@ -3383,5 +3383,23 @@ void wx_service_timer(struct timer_list *t)
}
EXPORT_SYMBOL(wx_service_timer);
+void wx_soft_quiesce(struct wx *wx)
+{
+ if (!netif_running(wx->netdev) ||
+ test_and_set_bit(WX_STATE_DOWN, wx->state))
+ return;
+
+ pci_clear_master(wx->pdev);
+ netif_tx_stop_all_queues(wx->netdev);
+ netif_carrier_off(wx->netdev);
+ netif_tx_disable(wx->netdev);
+ wx_napi_disable_all(wx);
+ wx_ptp_quiesce(wx);
+
+ clear_bit(WX_FLAG_NEED_PF_RESET, wx->flags);
+ timer_delete_sync(&wx->service_timer);
+}
+EXPORT_SYMBOL(wx_soft_quiesce);
+
MODULE_DESCRIPTION("Common library for Wangxun(R) Ethernet drivers.");
MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.h b/drivers/net/ethernet/wangxun/libwx/wx_lib.h
index aed6ea8cf0d6..11bd79985e17 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_lib.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.h
@@ -41,5 +41,6 @@ void wx_set_ring(struct wx *wx, u32 new_tx_count,
void wx_service_event_schedule(struct wx *wx);
void wx_service_event_complete(struct wx *wx);
void wx_service_timer(struct timer_list *t);
+void wx_soft_quiesce(struct wx *wx);
#endif /* _WX_LIB_H_ */
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
index 44f3e6505246..a25eb6aed566 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
@@ -321,6 +321,9 @@ static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp)
struct wx *wx = container_of(ptp, struct wx, ptp_caps);
int ts_done;
+ if (!test_bit(WX_STATE_PTP_RUNNING, wx->state))
+ return HZ;
+
ts_done = wx_ptp_tx_hwtstamp_work(wx);
wx_ptp_overflow_check(wx);
@@ -842,6 +845,24 @@ void wx_ptp_stop(struct wx *wx)
}
EXPORT_SYMBOL(wx_ptp_stop);
+void wx_ptp_quiesce(struct wx *wx)
+{
+ if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state))
+ return;
+
+ clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
+
+ if (wx->ptp_clock)
+ ptp_cancel_worker_sync(wx->ptp_clock);
+
+ if (wx->ptp_tx_skb) {
+ dev_kfree_skb_any(wx->ptp_tx_skb);
+ wx->ptp_tx_skb = NULL;
+ }
+ clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
+}
+EXPORT_SYMBOL(wx_ptp_quiesce);
+
/**
* wx_ptp_rx_hwtstamp - utility function which checks for RX time stamp
* @wx: pointer to wx struct
diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.h b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
index 50db90a6e3ee..ad2f824875d5 100644
--- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
+++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.h
@@ -10,6 +10,7 @@ void wx_ptp_reset(struct wx *wx);
void wx_ptp_init(struct wx *wx);
void wx_ptp_suspend(struct wx *wx);
void wx_ptp_stop(struct wx *wx);
+void wx_ptp_quiesce(struct wx *wx);
void wx_ptp_rx_hwtstamp(struct wx *wx, struct sk_buff *skb);
int wx_hwtstamp_get(struct net_device *dev,
struct kernel_hwtstamp_config *cfg);
diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
index a8773712cff8..a7bde03a98fe 100644
--- a/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
+++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_main.c
@@ -94,6 +94,10 @@ static void txgbe_module_detection_subtask(struct wx *wx)
{
int err;
+ if (test_bit(WX_STATE_DOWN, wx->state) ||
+ test_bit(WX_STATE_RESETTING, wx->state))
+ return;
+
if (!test_and_clear_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags))
return;
@@ -107,6 +111,10 @@ static void txgbe_module_detection_subtask(struct wx *wx)
static void txgbe_link_config_subtask(struct wx *wx)
{
+ if (test_bit(WX_STATE_DOWN, wx->state) ||
+ test_bit(WX_STATE_RESETTING, wx->state))
+ return;
+
if (!test_and_clear_bit(WX_FLAG_NEED_LINK_CONFIG, wx->flags))
return;
--
2.51.0
^ permalink raw reply related
* [PATCH net-next v10 1/5] net: ngbe: implement libwx reset ops
From: Jiawen Wu @ 2026-07-07 6:14 UTC (permalink / raw)
To: netdev
Cc: Mengyuan Lou, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Richard Cochran, Russell King,
Aleksandr Loktionov, Jacob Keller, Michal Swiatkowski,
Simon Horman, Kees Cook, Joe Damato, Breno Leitao, Jijie Shao,
Uwe Kleine-König (The Capable Hub), Rongguang Wei,
Larysa Zaremba, Fabio Baltieri, Jiawen Wu
In-Reply-To: <20260707061455.39564-1-jiawenwu@trustnetic.com>
Implement wx->do_reset() for library module calling.
Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
Reviewed-by: Larysa Zaremba <larysa.zaremba@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
.../net/ethernet/wangxun/ngbe/ngbe_ethtool.c | 1 -
drivers/net/ethernet/wangxun/ngbe/ngbe_main.c | 37 ++++++++++++++++++-
drivers/net/ethernet/wangxun/ngbe/ngbe_type.h | 1 +
3 files changed, 36 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
index b2e191982803..1960f7154151 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_ethtool.c
@@ -59,7 +59,6 @@ static int ngbe_set_ringparam(struct net_device *netdev,
wx_set_ring(wx, new_tx_count, new_rx_count, temp_ring);
kvfree(temp_ring);
- wx_configure(wx);
ngbe_up(wx);
clear_reset:
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
index a16221995909..bbbec9b43bc2 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
@@ -133,6 +133,7 @@ static int ngbe_sw_init(struct wx *wx)
wx->mbx.size = WX_VXMAILBOX_SIZE;
wx->setup_tc = ngbe_setup_tc;
+ wx->do_reset = ngbe_do_reset;
set_bit(0, &wx->fwd_bitmask);
return 0;
@@ -423,7 +424,7 @@ void ngbe_down(struct wx *wx)
wx_clean_all_rx_rings(wx);
}
-void ngbe_up(struct wx *wx)
+static void ngbe_up_complete(struct wx *wx)
{
wx_configure_vectors(wx);
@@ -490,7 +491,7 @@ static int ngbe_open(struct net_device *netdev)
wx_ptp_init(wx);
- ngbe_up(wx);
+ ngbe_up_complete(wx);
return 0;
err_dis_phy:
@@ -503,6 +504,12 @@ static int ngbe_open(struct net_device *netdev)
return err;
}
+void ngbe_up(struct wx *wx)
+{
+ wx_configure(wx);
+ ngbe_up_complete(wx);
+}
+
/**
* ngbe_close - Disables a network interface
* @netdev: network interface device structure
@@ -590,6 +597,8 @@ int ngbe_setup_tc(struct net_device *dev, u8 tc)
*/
if (netif_running(dev))
ngbe_close(dev);
+ else
+ ngbe_reset(wx);
wx_clear_interrupt_scheme(wx);
@@ -606,6 +615,30 @@ int ngbe_setup_tc(struct net_device *dev, u8 tc)
return 0;
}
+static void ngbe_reinit_locked(struct wx *wx)
+{
+ netif_trans_update(wx->netdev);
+
+ mutex_lock(&wx->reset_lock);
+ set_bit(WX_STATE_RESETTING, wx->state);
+
+ ngbe_down(wx);
+ ngbe_up(wx);
+
+ clear_bit(WX_STATE_RESETTING, wx->state);
+ mutex_unlock(&wx->reset_lock);
+}
+
+void ngbe_do_reset(struct net_device *netdev)
+{
+ struct wx *wx = netdev_priv(netdev);
+
+ if (netif_running(netdev))
+ ngbe_reinit_locked(wx);
+ else
+ ngbe_reset(wx);
+}
+
static const struct net_device_ops ngbe_netdev_ops = {
.ndo_open = ngbe_open,
.ndo_stop = ngbe_close,
diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h
index 7077a0da4c98..4f648f272c08 100644
--- a/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h
+++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_type.h
@@ -125,5 +125,6 @@ extern char ngbe_driver_name[];
void ngbe_down(struct wx *wx);
void ngbe_up(struct wx *wx);
int ngbe_setup_tc(struct net_device *dev, u8 tc);
+void ngbe_do_reset(struct net_device *netdev);
#endif /* _NGBE_TYPE_H_ */
--
2.51.0
^ permalink raw reply related
* Re: [PATCH net] psp: fix NULL genl_sock deref race with concurrent netns teardown
From: Kiran Kella @ 2026-07-07 6:23 UTC (permalink / raw)
To: Daniel Zahka
Cc: kuba, willemdebruijn.kernel, davem, edumazet, pabeni, horms,
weibunny, netdev, linux-kernel, jayakrishnan.udayavarma,
ajit.khaparde, akhilesh.samineni, Vikas Gupta, Bhargava Marreddy
In-Reply-To: <c2f1e433-99bf-4712-a31b-6b2360f8fb6e@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1627 bytes --]
On Mon, Jul 6, 2026 at 7:16 PM Daniel Zahka <daniel.zahka@gmail.com> wrote:
>
>
> On 7/3/26 7:24 AM, Kiran Kella wrote:
> > The race occurs between network namespace removal and PSP device
> > unregistration. When a netns is deleted while a PSP device associated
> > with that netns is concurrently being removed, psp_dev_unregister()
>
>
> Thanks. I was able to repro myself by inserting a delay after the old:
>
> main_net = dev_net(psd->main_netdev);
>
> and then executing nsim_psp_rereg_write() in parallel with destroying
> the netns that I placed a netdevsim dev into.
>
> Tested-by: Daniel Zahka <daniel.zahka@gmail.com>
>
> Reviewed-by: Daniel Zahka <daniel.zahka@gmail.com>
Thanks Daniel for reviewing and validating the fix.
>
> > ---
> > net/psp/psp_nl.c | 33 +++++++++++++++++++++------------
> > 1 file changed, 21 insertions(+), 12 deletions(-)
> > + ntf = build_ntf(psd, net, ctx);
> > + if (ntf)
> > + genlmsg_multicast_netns(&psp_nl_family, net, ntf, 0,
> > + group, GFP_KERNEL);
> > + put_net(net);
> > }
>
> some optional nits if you wanted to respin:
>
> You could eliminate the extra struct net *net, by just doing something
> like if (!maybe_get_net(assoc_net)) directly.
>
> You could get away with a single put_net(net); call site, if you reorder
> the ops that could fail so that maybe_get_net(assoc_net) happens last
> before the build_ntf(psd, net, ctx)
>
ACK. Will fix in v2.
Thanks,
Kiran
> > xa_destroy(&sent_nets);
> >
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5465 bytes --]
^ permalink raw reply
* [PATCH net-next] net: phylink: reject unsupported speed/duplex in ksettings_set() with PHY
From: muhammad.nazim.amirul.nazle.asmade @ 2026-07-07 6:27 UTC (permalink / raw)
To: linux, andrew, hkallweit1
Cc: davem, edumazet, kuba, pabeni, netdev, linux-kernel
From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
When using ethtool to change speed and duplex on a phylink-managed
interface with a PHY attached, the requested speed/duplex combination
is not validated against the MAC's supported capabilities before being
passed down to the PHY layer.
commit df0acdc59b09 ("net: phylink: fix ksettings_set() ethtool call")
and commit 03c44a21d033 ("net: phylink: actually fix ksettings_set()
ethtool call") introduced masking of the PHY advertising modes against
pl->supported, but did not add an explicit check that the requested
speed/duplex itself is within the MAC's capability set.
The AUTONEG_DISABLE path in the non-PHY case already uses
phy_caps_lookup() to validate speed/duplex against pl->supported.
Extend the same validation to the pl->phydev path so that ethtool
requests for unsupported speed/duplex combinations are rejected with
-EINVAL before reaching the PHY layer.
Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
---
drivers/net/phy/phylink.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 087ac63f9193..22f9bbd381bd 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -2989,6 +2989,10 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
if (pl->phydev) {
struct ethtool_link_ksettings phy_kset = *kset;
+ if (!phy_caps_lookup(kset->base.speed, kset->base.duplex,
+ pl->supported, true))
+ return -EINVAL;
+
linkmode_and(phy_kset.link_modes.advertising,
phy_kset.link_modes.advertising,
pl->supported);
--
2.43.7
^ permalink raw reply related
* Re: [PATCH v4 1/3] drm/drm_ras: Add drm_ras netlink error event
From: Tauro, Riana @ 2026-07-07 6:32 UTC (permalink / raw)
To: intel-xe, aravind.iddamsetty, kuba, rodrigo.vivi
Cc: anshuman.gupta, joonas.lahtinen, simona.vetter, airlied,
pratik.bari, joshua.santosh.ranjan, ashwin.kumar.kulkarni,
shubham.kumar, ravi.kishore.koppuravuri, raag.jadav,
maarten.lankhorst, mallesh.koujalagi, soham.purkait,
Zack McKevitt, Lijo Lazar, Hawking Zhang, David S. Miller,
Paolo Abeni, Eric Dumazet, dri-devel, netdev
In-Reply-To: <20260701094409.129131-6-riana.tauro@intel.com>
Hi Rodrigo/Jakub/Aravind
Please let me know if you have any feedback for this patch or can you please
ack this if it looks good to you.
Thanks
Riana
On 01-07-2026 15:14, Riana Tauro wrote:
> Define a new netlink event 'error-event' and a new multicast group
> 'error-report' in drm_ras. Each event contains device name, node and
> error information to identify the error triggering the event.
>
> Add drm_ras_nl_error_event() to trigger an event from the driver.
> Userspace must subscribe to 'error-report' to receive 'error-event'
> notifications.
>
> Usage:
>
> $ sudo ynl --family drm_ras --subscribe error-report
>
> Cc: Jakub Kicinski <kuba@kernel.org>
> Cc: Zack McKevitt <zachary.mckevitt@oss.qualcomm.com>
> Cc: Lijo Lazar <lijo.lazar@amd.com>
> Cc: Hawking Zhang <Hawking.Zhang@amd.com>
> Cc: David S. Miller <davem@davemloft.net>
> Cc: Paolo Abeni <pabeni@redhat.com>
> Cc: Eric Dumazet <edumazet@google.com>
> Signed-off-by: Riana Tauro <riana.tauro@intel.com>
> Reviewed-by: Raag Jadav <raag.jadav@intel.com>
> ---
> v2: remove redundant initialization
> remove unnecessary space
> use ynl in commit message and doc (Raag)
> simplify doc for error-event attrs
>
> v3: rename error-notify to error-report
> Replace notify with report across the file (Raag)
> ---
> Documentation/gpu/drm-ras.rst | 21 ++++++
> Documentation/netlink/specs/drm_ras.yaml | 48 +++++++++++++
> drivers/gpu/drm/drm_ras.c | 87 ++++++++++++++++++++++++
> drivers/gpu/drm/drm_ras_nl.c | 6 ++
> drivers/gpu/drm/drm_ras_nl.h | 4 ++
> include/drm/drm_ras.h | 5 ++
> include/uapi/drm/drm_ras.h | 15 ++++
> 7 files changed, 186 insertions(+)
>
> diff --git a/Documentation/gpu/drm-ras.rst b/Documentation/gpu/drm-ras.rst
> index 83c21853b74b..406e4c49bac1 100644
> --- a/Documentation/gpu/drm-ras.rst
> +++ b/Documentation/gpu/drm-ras.rst
> @@ -56,6 +56,7 @@ User space tools can:
> ``node-id`` and ``error-id`` as parameters.
> * Clear specific error counters with the ``clear-error-counter`` command, using both
> ``node-id`` and ``error-id`` as parameters.
> +* Subscribe to the ``error-report`` multicast group to receive ``error-event``.
>
> YAML-based Interface
> --------------------
> @@ -111,3 +112,23 @@ Example: Clear an error counter for a given node
>
> sudo ynl --family drm_ras --do clear-error-counter --json '{"node-id":0, "error-id":1}'
> None
> +
> +Example: Subscribe to ``error-report`` multicast group
> +
> +.. code-block:: bash
> +
> + sudo ynl --family drm_ras --output-json --subscribe error-report
> +
> +.. code-block:: json
> +
> + {
> + "name": "error-event",
> + "msg": {
> + "device-name": "0000:03:00.0",
> + "node-id": 1,
> + "node-name": "uncorrectable-errors",
> + "error-id": 1,
> + "error-name": "error_name1",
> + "error-value": 1
> + }
> + }
> diff --git a/Documentation/netlink/specs/drm_ras.yaml b/Documentation/netlink/specs/drm_ras.yaml
> index e113056f8c01..8aed3d4515e5 100644
> --- a/Documentation/netlink/specs/drm_ras.yaml
> +++ b/Documentation/netlink/specs/drm_ras.yaml
> @@ -69,6 +69,33 @@ attribute-sets:
> name: error-value
> type: u32
> doc: Current value of the requested error counter.
> + -
> + name: error-event-attrs
> + attributes:
> + -
> + name: device-name
> + type: string
> + doc: Device (PCI BDF, UUID) that reported the error.
> + -
> + name: node-id
> + type: u32
> + doc: ID of the node that reported the error.
> + -
> + name: node-name
> + type: string
> + doc: Name of the node that reported the error.
> + -
> + name: error-id
> + type: u32
> + doc: ID of the error counter.
> + -
> + name: error-name
> + type: string
> + doc: Name of the error.
> + -
> + name: error-value
> + type: u32
> + doc: Current value of the error counter.
>
> operations:
> list:
> @@ -124,3 +151,24 @@ operations:
> do:
> request:
> attributes: *id-attrs
> + -
> + name: error-event
> + doc: >-
> + Report an error event to userspace.
> + The event includes the device, node and error information
> + of the error that triggered the event.
> + attribute-set: error-event-attrs
> + mcgrp: error-report
> + event:
> + attributes:
> + - device-name
> + - node-id
> + - node-name
> + - error-id
> + - error-name
> + - error-value
> +
> +mcast-groups:
> + list:
> + -
> + name: error-report
> diff --git a/drivers/gpu/drm/drm_ras.c b/drivers/gpu/drm/drm_ras.c
> index d6eab29a1394..77f912a4d101 100644
> --- a/drivers/gpu/drm/drm_ras.c
> +++ b/drivers/gpu/drm/drm_ras.c
> @@ -41,6 +41,11 @@
> * Userspace must provide Node ID, Error ID.
> * Clears specific error counter of a node if supported.
> *
> + * 4. ERROR_REPORT: Subscribe to this multicast group to receive error events
> + *
> + * 5. ERROR_EVENT: Report an error event to userspace. The event contains device, node
> + * and error information that triggered the event.
> + *
> * Node registration:
> *
> * - drm_ras_node_register(): Registers a new node and assigns
> @@ -186,6 +191,34 @@ static int msg_reply_value(struct sk_buff *msg, u32 error_id,
> value);
> }
>
> +static int msg_put_error_event_attrs(struct sk_buff *msg, struct drm_ras_node *node,
> + u32 error_id, const char *error_name, u32 value)
> +{
> + int ret;
> +
> + ret = nla_put_string(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_DEVICE_NAME, node->device_name);
> + if (ret)
> + return ret;
> +
> + ret = nla_put_u32(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_NODE_ID, node->id);
> + if (ret)
> + return ret;
> +
> + ret = nla_put_string(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_NODE_NAME, node->node_name);
> + if (ret)
> + return ret;
> +
> + ret = nla_put_u32(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_ID, error_id);
> + if (ret)
> + return ret;
> +
> + ret = nla_put_string(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_NAME, error_name);
> + if (ret)
> + return ret;
> +
> + return nla_put_u32(msg, DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_VALUE, value);
> +}
> +
> static int doit_reply_value(struct genl_info *info, u32 node_id,
> u32 error_id)
> {
> @@ -222,6 +255,60 @@ static int doit_reply_value(struct genl_info *info, u32 node_id,
> return genlmsg_reply(msg, info);
> }
>
> +/**
> + * drm_ras_nl_error_event() - Report an error event
> + * @node: Node structure
> + * @error_id: ID of the error
> + * @error_name: Name of the error
> + * @value: Value associated with the error
> + * @flags: GFP flags for memory allocation
> + *
> + * Report an error-event to userspace using the error-report multicast group.
> + *
> + * Return: 0 on success, or negative errno on failure.
> + */
> +int drm_ras_nl_error_event(struct drm_ras_node *node, u32 error_id, const char *error_name,
> + u32 value, gfp_t flags)
> +{
> + struct genl_info info;
> + struct sk_buff *msg;
> + struct nlattr *hdr;
> + int ret;
> +
> + if (!error_name)
> + return -EINVAL;
> +
> + if (!genl_has_listeners(&drm_ras_nl_family, &init_net, DRM_RAS_NLGRP_ERROR_REPORT))
> + return 0;
> +
> + genl_info_init_ntf(&info, &drm_ras_nl_family, DRM_RAS_CMD_ERROR_EVENT);
> +
> + msg = genlmsg_new(NLMSG_GOODSIZE, flags);
> + if (!msg)
> + return -ENOMEM;
> +
> + hdr = genlmsg_iput(msg, &info);
> + if (!hdr) {
> + ret = -EMSGSIZE;
> + goto free_msg;
> + }
> +
> + ret = msg_put_error_event_attrs(msg, node, error_id, error_name, value);
> + if (ret)
> + goto cancel_msg;
> +
> + genlmsg_end(msg, hdr);
> + genlmsg_multicast(&drm_ras_nl_family, msg, 0, DRM_RAS_NLGRP_ERROR_REPORT, flags);
> + return 0;
> +
> +cancel_msg:
> + genlmsg_cancel(msg, hdr);
> +free_msg:
> + nlmsg_free(msg);
> + return ret;
> +}
> +EXPORT_SYMBOL(drm_ras_nl_error_event);
> +
> /**
> * drm_ras_nl_get_error_counter_dumpit() - Dump all Error Counters
> * @skb: Netlink message buffer
> diff --git a/drivers/gpu/drm/drm_ras_nl.c b/drivers/gpu/drm/drm_ras_nl.c
> index dea1c1b2494e..9d3123cc9f9c 100644
> --- a/drivers/gpu/drm/drm_ras_nl.c
> +++ b/drivers/gpu/drm/drm_ras_nl.c
> @@ -58,6 +58,10 @@ static const struct genl_split_ops drm_ras_nl_ops[] = {
> },
> };
>
> +static const struct genl_multicast_group drm_ras_nl_mcgrps[] = {
> + [DRM_RAS_NLGRP_ERROR_REPORT] = { "error-report", },
> +};
> +
> struct genl_family drm_ras_nl_family __ro_after_init = {
> .name = DRM_RAS_FAMILY_NAME,
> .version = DRM_RAS_FAMILY_VERSION,
> @@ -66,4 +70,6 @@ struct genl_family drm_ras_nl_family __ro_after_init = {
> .module = THIS_MODULE,
> .split_ops = drm_ras_nl_ops,
> .n_split_ops = ARRAY_SIZE(drm_ras_nl_ops),
> + .mcgrps = drm_ras_nl_mcgrps,
> + .n_mcgrps = ARRAY_SIZE(drm_ras_nl_mcgrps),
> };
> diff --git a/drivers/gpu/drm/drm_ras_nl.h b/drivers/gpu/drm/drm_ras_nl.h
> index a398643572a5..03ec275aca92 100644
> --- a/drivers/gpu/drm/drm_ras_nl.h
> +++ b/drivers/gpu/drm/drm_ras_nl.h
> @@ -21,6 +21,10 @@ int drm_ras_nl_get_error_counter_dumpit(struct sk_buff *skb,
> int drm_ras_nl_clear_error_counter_doit(struct sk_buff *skb,
> struct genl_info *info);
>
> +enum {
> + DRM_RAS_NLGRP_ERROR_REPORT,
> +};
> +
> extern struct genl_family drm_ras_nl_family;
>
> #endif /* _LINUX_DRM_RAS_GEN_H */
> diff --git a/include/drm/drm_ras.h b/include/drm/drm_ras.h
> index 0beede3ddc4e..8abfb7d2077b 100644
> --- a/include/drm/drm_ras.h
> +++ b/include/drm/drm_ras.h
> @@ -80,9 +80,14 @@ struct drm_device;
> #if IS_ENABLED(CONFIG_DRM_RAS)
> int drm_ras_node_register(struct drm_ras_node *node);
> void drm_ras_node_unregister(struct drm_ras_node *node);
> +int drm_ras_nl_error_event(struct drm_ras_node *node, u32 error_id, const char *error_name,
> + u32 value, gfp_t flags);
> #else
> static inline int drm_ras_node_register(struct drm_ras_node *node) { return 0; }
> static inline void drm_ras_node_unregister(struct drm_ras_node *node) { }
> +static inline int drm_ras_nl_error_event(struct drm_ras_node *node, u32 error_id,
> + const char *error_name, u32 value, gfp_t flags)
> +{ return 0; }
> #endif
>
> #endif
> diff --git a/include/uapi/drm/drm_ras.h b/include/uapi/drm/drm_ras.h
> index 218a3ee86805..eab8231aa87c 100644
> --- a/include/uapi/drm/drm_ras.h
> +++ b/include/uapi/drm/drm_ras.h
> @@ -38,13 +38,28 @@ enum {
> DRM_RAS_A_ERROR_COUNTER_ATTRS_MAX = (__DRM_RAS_A_ERROR_COUNTER_ATTRS_MAX - 1)
> };
>
> +enum {
> + DRM_RAS_A_ERROR_EVENT_ATTRS_DEVICE_NAME = 1,
> + DRM_RAS_A_ERROR_EVENT_ATTRS_NODE_ID,
> + DRM_RAS_A_ERROR_EVENT_ATTRS_NODE_NAME,
> + DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_ID,
> + DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_NAME,
> + DRM_RAS_A_ERROR_EVENT_ATTRS_ERROR_VALUE,
> +
> + __DRM_RAS_A_ERROR_EVENT_ATTRS_MAX,
> + DRM_RAS_A_ERROR_EVENT_ATTRS_MAX = (__DRM_RAS_A_ERROR_EVENT_ATTRS_MAX - 1)
> +};
> +
> enum {
> DRM_RAS_CMD_LIST_NODES = 1,
> DRM_RAS_CMD_GET_ERROR_COUNTER,
> DRM_RAS_CMD_CLEAR_ERROR_COUNTER,
> + DRM_RAS_CMD_ERROR_EVENT,
>
> __DRM_RAS_CMD_MAX,
> DRM_RAS_CMD_MAX = (__DRM_RAS_CMD_MAX - 1)
> };
>
> +#define DRM_RAS_MCGRP_ERROR_REPORT "error-report"
> +
> #endif /* _UAPI_LINUX_DRM_RAS_H */
^ permalink raw reply
* Re: [PATCH] xfrm: clear mode callbacks after failed mode setup
From: Steffen Klassert @ 2026-07-07 6:32 UTC (permalink / raw)
To: Cen Zhang
Cc: Herbert Xu, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Christian Hopps, netdev, linux-kernel,
baijiaju1990
In-Reply-To: <CAFRLqsXvrfWE4MWVic+en7KGs6UuxwjDot2ygt2cWH2TCUgmBg@mail.gmail.com>
On Tue, Jul 07, 2026 at 10:32:13AM +0800, Cen Zhang wrote:
> Hi all,
>
> Sorry for the noise, but I wanted to gently follow up on this patch in
> case it got buried.
>
> I would really appreciate any feedback when you have a chance. Please
> let me know if I should make any changes or provide more testing
> results.
It is not forgotten, I had it in my testing branch until now.
This is now applied to the ipsec tree, thanks a lot!
^ permalink raw reply
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