* Re: [PATCH net] amt: fix size calculation in amt_get_size()
From: Kuniyuki Iwashima @ 2026-07-02 2:50 UTC (permalink / raw)
To: Eric Dumazet
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, netdev, eric.dumazet
In-Reply-To: <20260701122329.3562825-1-edumazet@google.com>
On Wed, Jul 1, 2026 at 5:23 AM Eric Dumazet <edumazet@google.com> wrote:
>
> amt_get_size() incorrectly used sizeof(struct iphdr) for the sizes of
> IFLA_AMT_DISCOVERY_IP, IFLA_AMT_REMOTE_IP, and IFLA_AMT_LOCAL_IP.
> These attributes contain IPv4 addresses (__be32), not full IP headers.
>
> Replace sizeof(struct iphdr) with sizeof(__be32) to avoid over-allocating
> netlink message space.
>
> Fixes: b9022b53adad ("amt: add control plane of amt interface")
> Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
^ permalink raw reply
* Re: [PATCH iproute2-next v3 2/2] devlink: support u64-array values in devlink param show/set
From: Ratheesh Kannoth @ 2026-07-02 2:51 UTC (permalink / raw)
To: David Ahern
Cc: stephen, kuba, linux-kernel, netdev, andrew+netdev, edumazet,
pabeni, jiri
In-Reply-To: <6c59d8f0-9a70-46d1-bf70-1e69c1869fb5@kernel.org>
On 2026-07-02 at 05:23:33, David Ahern (dsahern@kernel.org) wrote:
> On 6/30/26 9:13 PM, Ratheesh Kannoth wrote:
> > Add support for DEVLINK_VAR_ATTR_TYPE_U64_ARRAY parameters that carry
> > multiple DEVLINK_ATTR_PARAM_VALUE_DATA attributes. Parse and display
> > u64 array values in param show, and accept space- or comma-separated
> > u64 values in devlink and port param set commands.
> >
> > - Show search order
> >
> > devlink dev param show pci/0002:01:00.0 name npc_srch_order
> > pci/0002:01:00.0:
> > name npc_srch_order type driver-specific
> > values:
> > cmode runtime value value 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
>
> "value value"? Is that correct or a typo in the commit message?
it is a typo.
>
> You are dumping it as a string which for json is wrong -- it should be
> an array.
ACK.
^ permalink raw reply
* Re: [PATCH net-next] amt: no longer rely on RTNL in amt_fill_info()
From: Kuniyuki Iwashima @ 2026-07-02 2:52 UTC (permalink / raw)
To: Eric Dumazet
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, netdev, eric.dumazet
In-Reply-To: <20260701125016.3650708-1-edumazet@google.com>
On Wed, Jul 1, 2026 at 5:50 AM Eric Dumazet <edumazet@google.com> wrote:
>
> Update amt_fill_info() to run under RCU read lock instead of RTNL.
>
> The AMT device configuration fields (mode, relay_port, gw_port, local_ip,
> discovery_ip, max_tunnels) and stream_dev pointer are initialized during
> device creation (amt_newlink) and are immutable. Accessing them locklessly
> is safe. The stream_dev net_device structure is protected from being freed
> by RCU.
>
> The only field that can change concurrently is amt->remote_ip, which is
> updated in the packet receive path (amt_advertisement_handler) and
> workqueue (amt_req_work). Add READ_ONCE()/WRITE_ONCE() annotations
> around amt->remote_ip to prevent data races.
>
> Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
^ permalink raw reply
* Re: [syzbot] Monthly virt report (Jun 2026)
From: mawupeng @ 2026-07-02 2:55 UTC (permalink / raw)
To: syzbot+listbf7b8eeeb8dda31d6de1, linux-kernel, syzkaller-bugs,
virtualization, mst, jasowang, xuanzhuo, eperezma, stefanha,
sgarzare, davem, edumazet, kuba, pabeni, horms
Cc: mawupeng1, virtualization, kvm, netdev
In-Reply-To: <6a3c3ed6.80e5668d.5d0ef.0001.GAE@google.com>
On 周四 2026-6-25 04:32, syzbot wrote:
> Hello virt maintainers/developers,
>
> This is a 31-day syzbot report for the virt subsystem.
> All related reports/information can be found at:
> https://syzkaller.appspot.com/upstream/s/virt
>
> During the period, 0 new issues were detected and 0 were fixed.
> In total, 5 issues are still open and 61 have already been fixed.
> There are also 2 low-priority issues.
>
> Some of the still happening issues:
>
> Ref Crashes Repro Title
> <1> 24 No WARNING: refcount bug in call_timer_fn (4)
> https://syzkaller.appspot.com/bug?extid=07dcf509f4c013e25dc5
> <2> 3 Yes memory leak in __vsock_create (2)
> https://syzkaller.appspot.com/bug?extid=1b2c9c4a0f8708082678
Hi,
This is regarding the still-open "memory leak in __vsock_create (2)"
bug (#2 in the monthly virt report, extid 1b2c9c4a0f8708082678):
https://syzkaller.appspot.com/bug?extid=1b2c9c4a0f8708082678
I spent some time analyzing the root cause and the previous fix
attempt; below is a summary and a direction that tested out.
== Root cause ==
The leaked object is the child socket created by
virtio_transport_recv_listen() via __vsock_create() — exactly the
allocation site kmemleak points at. The reason it never gets freed is
in the accept() error path, not in the allocation itself.
When vsock_accept() dequeues a child but the listener carries an error
(listener->sk_err, e.g. set by a failed connect() issued on the socket
before listen()), it sets vconnected->rejected = true, skips
sock_graft(), drops the dequeue reference and *relies on
vsock_pending_work()* to clean the child up.
The catch: vsock_pending_work() is never scheduled on the transports
involved here. It is only ever scheduled by vmci_transport
(vmci_transport.c:1130); virtio_transport and vsock_loopback never
schedule it. So the rejected child sits with an unreleased initial
reference (the one from sk_alloc()) plus the connected-table
reference, vsock_sk_destruct() is never reached, and the cascade —
child socket, struct cred, virtio transport, SELinux blob — all leak.
The earlier commit 3a5cc90a4d17 ("vsock/virtio: remove socket from
connected/bound list on shutdown") adds an unconditional
vsock_remove_sock() in virtio_transport_recv_connected() when a
SHUTDOWN arrives, which drops the connected-table reference for a
child that later receives a SHUTDOWN; but it does not release the
sk_alloc() reference. So the leak is not really a regression
introduced there — rejected children have never been cleaned up on
transports that don't schedule pending_work. 3a5cc90a4d17 mainly
changes whether kmemleak can see the leak: on v6.6 it can (the
cascade shows up), on mainline the smaller struct sock layout leaves a
residual pointer inside the child that kmemleak counts as a reachable
reference, so mainline kmemleak stays silent even though
create/destruct accounting confirms the child never reaches
vsock_sk_destruct().
== Why the previous attempt didn't land ==
Divya's patch [1] tried to fix it by re-locking the parent listener
inside virtio_transport_recv_listen() and re-checking the shutdown
state under that lock before vsock_enqueue_accept(). That re-locks an
already-held lock — virtio_transport_recv_pkt() holds lock_sock(sk)
across the call into recv_listen() — and syzbot ci immediately flagged
"possible recursive locking" [2]. So it was backed out and the bug
stayed open.
== A direction that tests out ==
Instead of re-locking in the receive path, handle the cleanup directly
in vsock_accept(): on reject, instead of setting vconnected->rejected
and relying on pending_work, explicitly release the child's references
there:
if (err) {
vsock_remove_connected(vconnected); /* connected-table ref */
connected->sk_state = TCP_CLOSE;
sock_put(connected); /* enqueue_accept ref */
} else {
sock_graft(connected, newsock);
}
...
sock_put(connected); /* the existing, common put — sk_alloc ref */
This drops exactly the three references the child holds at dequeue
time (sk_alloc + __vsock_insert_connected + vsock_enqueue_accept),
lets refcount reach zero and vsock_sk_destruct() run. The `rejected`
flag and its pending_work handling can then be removed. The receive
path is not touched, so there is no re-locking and no deadlock.
I verified this on ARM64 QEMU. On linux v6.6.y (where kmemleak can
see the leak) with the syzbot reproducer:
- before: 6 creates / 4 destructs (2 leaked); kmemleak reports the
cascade;
- after: 6 creates / 6 destructs (0 leaked); kmemleak clean;
- 50-iter normal server and 50-iter same-port-reconnect tests both
pass 50/50 with zero leaks, no double-put warnings.
On mainline, kmemleak stays silent (see above) but create/destruct
accounting confirms the same leak before the fix; the fix is
code-identical across v6.6.y and mainline (same recv_listen/accept
paths).
I'm not subscribed to follow the list at full volume; happy to send a
formal patch (with the af_vsock.h / pending_work changes folded in)
if the direction looks right to the maintainers.
== Trigger, for completeness ==
The reproducer's atypical-but-legal sequence is what sets
listener->sk_err: a socket is connect()ed (leaving sk_err set, since
vsock_connect() only clears it at the start of a new connect) and then
turned into a listener:
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
bind(fd, ...);
connect(fd, &(VMADDR_CID_LOCAL, ...)); /* leaves sk_err set */
listen(fd, 5);
/* a peer connects to fd; the child created is later rejected */
accept4(fd, ...);
Standard servers (listen before any connect on the same fd) don't hit
it, which is why this went ~2.5 years between the offending commit and
the syzbot report.
[1] https://lore.kernel.org/all/20260605191922.12720-1-divyakm@unc.edu/
[2] https://ci.syzbot.org/series/76f40e62-5a21-46d4-a636-10f0ec9c5040
Thanks.
> <3> 3913 Yes INFO: rcu detected stall in do_idle
> https://syzkaller.appspot.com/bug?extid=385468161961cee80c31
>
> ---
> This report is generated by a bot. It may contain errors.
> See https://goo.gl/tpsmEJ for more information about syzbot.
> syzbot engineers can be reached at syzkaller@googlegroups.com.
>
> To disable reminders for individual bugs, reply with the following command:
> #syz set <Ref> no-reminders
>
> To change bug's subsystems, reply with:
> #syz set <Ref> subsystems: new-subsystem
>
> You may send multiple commands in a single email message.
^ permalink raw reply
* [PATCH] selftests/net: fix EVP_MD_CTX leak in tcp_mmap
From: Wang Yan @ 2026-07-02 2:59 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni, shuah
Cc: horms, lixiaoyan, netdev, linux-kselftest, linux-kernel, Wang Yan
In tcp_mmap.c, both child_thread() and main() allocate an EVP_MD_CTX
via EVP_MD_CTX_new() when integrity checking is enabled, but neither
function releases the context. child_thread() misses the free in its
common cleanup block, and main() returns without freeing the context.
This results in a SHA256 context leak on every run that uses the
‑i (integrity) option. Add the missing EVP_MD_CTX_free() calls to
the appropriate cleanup paths to fix the leak.
Fixes: 5c5945dc695c ("selftests/net: Add SHA256 computation over data sent in tcp_mmap")
Signed-off-by: Wang Yan <wangyan01@kylinos.cn>
---
tools/testing/selftests/net/tcp_mmap.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/tools/testing/selftests/net/tcp_mmap.c b/tools/testing/selftests/net/tcp_mmap.c
index 4fcce5150850..2544ae35d07a 100644
--- a/tools/testing/selftests/net/tcp_mmap.c
+++ b/tools/testing/selftests/net/tcp_mmap.c
@@ -313,6 +313,8 @@ void *child_thread(void *arg)
tcp_info_get_rcv_mss(fd));
}
error:
+ if (ctx)
+ EVP_MD_CTX_free(ctx);
munmap(buffer, buffer_sz);
close(fd);
if (zflg)
@@ -606,6 +608,8 @@ int main(int argc, char *argv[])
EVP_DigestFinal_ex(ctx, digest, &digest_len);
send(fd, digest, (size_t)SHA256_DIGEST_LENGTH, 0);
}
+ if (ctx)
+ EVP_MD_CTX_free(ctx);
close(fd);
munmap(buffer, buffer_sz);
return 0;
--
2.25.1
^ permalink raw reply related
* Re: [PATCH iproute2-next v3 2/2] devlink: support u64-array values in devlink param show/set
From: Ratheesh Kannoth @ 2026-07-02 3:06 UTC (permalink / raw)
To: David Ahern
Cc: stephen, kuba, linux-kernel, netdev, andrew+netdev, edumazet,
pabeni, jiri
In-Reply-To: <6c59d8f0-9a70-46d1-bf70-1e69c1869fb5@kernel.org>
On 2026-07-02 at 05:23:33, David Ahern (dsahern@kernel.org) wrote:
> On 6/30/26 9:13 PM, Ratheesh Kannoth wrote:
> > Add support for DEVLINK_VAR_ATTR_TYPE_U64_ARRAY parameters that carry
> > multiple DEVLINK_ATTR_PARAM_VALUE_DATA attributes. Parse and display
> > u64 array values in param show, and accept space- or comma-separated
> > u64 values in devlink and port param set commands.
> >
> > - Show search order
> >
> > devlink dev param show pci/0002:01:00.0 name npc_srch_order
> > pci/0002:01:00.0:
> > name npc_srch_order type driver-specific
> > values:
> > cmode runtime value value 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
>
> "value value"? Is that correct or a typo in the commit message?
>
> You are dumping it as a string which for json is wrong -- it should be
> an array.
>
Will address in v4.
pw-bot: changes-requested
^ permalink raw reply
* Re: [PATCH] selftests: mptcp: mptcp_diag: fix stack buffer overflow in get_subflow_info()
From: Geliang Tang @ 2026-07-02 3:06 UTC (permalink / raw)
To: Jiangshan Yi, martineau, matttbe
Cc: davem, edumazet, kuba, pabeni, horms, shuah, netdev, mptcp,
linux-kselftest, linux-kernel, 13667453960
In-Reply-To: <20260701103809.4051377-1-yijiangshan@kylinos.cn>
Hi Jiangshan,
On Wed, 2026-07-01 at 18:38 +0800, Jiangshan Yi wrote:
> get_subflow_info() parses the subflow address string with:
>
> char saddr[64], daddr[64];
>
> ret = sscanf(subflow_addrs, "%[^:]:%d %[^:]:%d",
> saddr, &sport, daddr, &dport);
>
> The subflow_addrs buffer holds up to 1024 bytes and is taken directly
> from the command line ("-c" argument). The "%[^:]" conversions have
> no
> maximum field width, so if the address substring before the ':'
> exceeds
> 63 bytes, sscanf() writes past the end of the 64-byte saddr/daddr
> stack
> buffers. This overflows the stack, corrupting adjacent stack data
> such
> as the saved return address, and can crash the tool or lead to
> out-of-bounds writes controlled by user-supplied input.
>
> Bound both string conversions to the destination buffer size by
> adding
> an explicit maximum field width of 63 (leaving room for the
> terminating
> NUL), so at most 63 bytes are written into each 64-byte buffer:
>
> ret = sscanf(subflow_addrs, "%63[^:]:%d %63[^:]:%d",
> saddr, &sport, daddr, &dport);
>
> Signed-off-by: Jiangshan Yi <yijiangshan@kylinos.cn>
> ---
> tools/testing/selftests/net/mptcp/mptcp_diag.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/tools/testing/selftests/net/mptcp/mptcp_diag.c
> b/tools/testing/selftests/net/mptcp/mptcp_diag.c
> index 5e222ba977e4..02ac93f794fe 100644
> --- a/tools/testing/selftests/net/mptcp/mptcp_diag.c
> +++ b/tools/testing/selftests/net/mptcp/mptcp_diag.c
> @@ -377,7 +377,7 @@ static void get_subflow_info(char *subflow_addrs)
> int ret;
> int fd;
>
> - ret = sscanf(subflow_addrs, "%[^:]:%d %[^:]:%d", saddr,
> &sport, daddr, &dport);
> + ret = sscanf(subflow_addrs, "%63[^:]:%d %63[^:]:%d", saddr,
> &sport, daddr, &dport);
Thanks for this patch. MPTCP CI complains:
WARNING: line length of 91 exceeds 80 columns
#44: FILE: tools/testing/selftests/net/mptcp/mptcp_diag.c:380:
Also, for the subject prefix, we usually use "selftests: mptcp: diag:"
instead of "selftests: mptcp: mptcp_diag:". Please consider updating it
if you spin a v2.
Thanks,
-Geliang
> if (ret != 4)
> die_perror("IP PORT Pairs has style problems!");
>
^ permalink raw reply
* Re: [PATCH net-next] tun: no longer rely on RTNL in tun_fill_info()
From: Kuniyuki Iwashima @ 2026-07-02 3:09 UTC (permalink / raw)
To: Eric Dumazet
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, netdev, eric.dumazet
In-Reply-To: <20260701125112.3652880-1-edumazet@google.com>
On Wed, Jul 1, 2026 at 5:51 AM Eric Dumazet <edumazet@google.com> wrote:
>
> Update tun_fill_info() to read device configuration fields (flags, owner,
> group, numqueues, numdisabled) locklessly using READ_ONCE().
>
> Annotate all writes to these fields in the control paths with WRITE_ONCE()
> to prevent data races, as these fields can be modified concurrently via
> ioctls (TUNSETPERSIST, TUNSETOWNER, TUNSETGROUP, TUNSETIFF) or queue
> attaching/detaching.
>
> Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
I left comments but maybe it's just my preference, not sure
if it's worth a respin :)
> @@ -943,8 +943,8 @@ static int tun_net_init(struct net_device *dev)
> NETIF_F_HW_VLAN_STAG_TX);
> dev->lltx = true;
>
> - tun->flags = (tun->flags & ~TUN_FEATURES) |
> - (ifr->ifr_flags & TUN_FEATURES);
> + WRITE_ONCE(tun->flags, (tun->flags & ~TUN_FEATURES) |
> + (ifr->ifr_flags & TUN_FEATURES));
WRITE_ONCE() is not needed here as the dev is not yet published.
[...]
> @@ -2814,8 +2818,8 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
> return 0;
> }
>
> - tun->flags = (tun->flags & ~TUN_FEATURES) |
> - (ifr->ifr_flags & TUN_FEATURES);
> + WRITE_ONCE(tun->flags, (READ_ONCE(tun->flags) & ~TUN_FEATURES) |
> + (ifr->ifr_flags & TUN_FEATURES));
READ_ONCE() is not a must here and __tun_chr_ioctl() as all writers
are under RTNL.
> @@ -3213,13 +3217,13 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
> /* Disable/Enable persist mode. Keep an extra reference to the
> * module to prevent the module being unprobed.
> */
> - if (arg && !(tun->flags & IFF_PERSIST)) {
> - tun->flags |= IFF_PERSIST;
> + if (arg && !(READ_ONCE(tun->flags) & IFF_PERSIST)) {
> + WRITE_ONCE(tun->flags, READ_ONCE(tun->flags) | IFF_PERSIST);
> __module_get(THIS_MODULE);
> do_notify = true;
> }
> - if (!arg && (tun->flags & IFF_PERSIST)) {
> - tun->flags &= ~IFF_PERSIST;
> + if (!arg && (READ_ONCE(tun->flags) & IFF_PERSIST)) {
> + WRITE_ONCE(tun->flags, READ_ONCE(tun->flags) & ~IFF_PERSIST);
> module_put(THIS_MODULE);
> do_notify = true;
> }
^ permalink raw reply
* Re: [PATCH net-next 1/2] ip_tunnel: use WRITE_ONCE in ip_tunnel_encap_setup
From: Kuniyuki Iwashima @ 2026-07-02 3:11 UTC (permalink / raw)
To: Eric Dumazet
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
Ido Schimmel, David Ahern, netdev, eric.dumazet
In-Reply-To: <20260701155135.3962058-2-edumazet@google.com>
On Wed, Jul 1, 2026 at 8:51 AM Eric Dumazet <edumazet@google.com> wrote:
>
> Update ip_tunnel_encap_setup() to use WRITE_ONCE() when writing
> to encap fields (type, sport, dport, flags) and hlen fields.
> This ensures that concurrent lockless readers (like fill_info)
> do not see torn writes.
>
> Also remove the unsafe memset() on t->encap which could cause
> concurrent readers to transiently see zeroed fields.
> Removing it also fixes a bug where t->encap was left cleared
> even if ip_encap_hlen() failed, resulting in partial configuration.
>
> Fixes: 56328486539d ("net: Changes to ip_tunnel to support foo-over-udp encapsulation")
> Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
^ permalink raw reply
* Re: [PATCH net-next 2/2] sit: no longer rely on RTNL in ipip6_fill_info()
From: Kuniyuki Iwashima @ 2026-07-02 3:13 UTC (permalink / raw)
To: Eric Dumazet
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
Ido Schimmel, David Ahern, netdev, eric.dumazet
In-Reply-To: <20260701155135.3962058-3-edumazet@google.com>
On Wed, Jul 1, 2026 at 8:51 AM Eric Dumazet <edumazet@google.com> wrote:
>
> Update ipip6_fill_info() to read configuration fields (link, ttl, tos,
> proto, i_flags, fwmark, 6rd prefix, encap type/sport/dport/flags)
> locklessly using READ_ONCE().
>
> Annotate the bitmap reads for i_flags by copying the first element
> atomically using READ_ONCE() into a local variable, as the whole
> bitmap fits in one unsigned long.
>
> This allows ipip6_fill_info() to run safely without RTNL.
>
> Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
^ permalink raw reply
* [PATCH iproute2-next v4 0/2] devlink: support u64-array devlink parameters
From: Ratheesh Kannoth @ 2026-07-02 3:13 UTC (permalink / raw)
To: stephen, dsahern, kuba, linux-kernel, netdev
Cc: rkannoth, andrew+netdev, edumazet, pabeni, jiri
The kernel gained support for devlink parameters of type
DEVLINK_VAR_ATTR_TYPE_U64_ARRAY. These parameters carry a variable-length
list of u64 values encoded as multiple DEVLINK_ATTR_PARAM_VALUE_DATA
attributes. This is used by drivers that need to expose ordered lists of
configuration values, such as the Marvell CN20K npc_srch_order parameter.
This series updates the devlink tool to handle the new UAPI and adds
show/set support for u64-array parameters on both device and port params.
Patch 1 switches devlink param show/set to use DEVLINK_VAR_ATTR_TYPE_*
constants instead of generic MNL_TYPE_* values when interpreting
DEVLINK_ATTR_PARAM_TYPE. The kernel now reports param types using
devlink_var_attr_type, so userspace must use the matching symbols.
Patch 2 adds parsing, display, and configuration support for
DEVLINK_VAR_ATTR_TYPE_U64_ARRAY. Values are shown as a space-separated
list of u64 elements. Setting accepts a space- or comma-separated list
and emits one DEVLINK_ATTR_PARAM_VALUE_DATA attribute per element.
Tested on CN20K hardware with npc_srch_order:
# show search order
devlink dev param show pci/0002:01:00.0 name npc_srch_order
pci/0002:01:00.0:
name npc_srch_order type driver-specific
values:
cmode runtime value value 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
# set search order
devlink dev param set pci/0002:01:00.0 name npc_srch_order \
value 31,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30 \
cmode runtime
Ratheesh Kannoth (2):
devlink: use DEVLINK_VAR_ATTR_TYPE_* in param show/set
devlink: support u64-array values in devlink param show/set
devlink/devlink.c | 178 ++++++++++++++++++++++++++++++++---
include/uapi/linux/devlink.h | 1 +
2 files changed, 164 insertions(+), 15 deletions(-)
--
v3 -> v4: Addressed David comments
https://lore.kernel.org/netdev/20260701031359.839221-1-rkannoth@marvell.com/
v2 -> v3: Addressed David comments
https://lore.kernel.org/netdev/akSCBN0N_7ug1-Fy@rkannoth-OptiPlex-7090/
v1 -> v2: Addressed David comments
https://lore.kernel.org/netdev/20260615041042.549715-1-rkannoth@marvell.com/
2.43.0
^ permalink raw reply
* [PATCH iproute2-next v4 1/2] devlink: use DEVLINK_VAR_ATTR_TYPE_* in param show/set
From: Ratheesh Kannoth @ 2026-07-02 3:13 UTC (permalink / raw)
To: stephen, dsahern, kuba, linux-kernel, netdev
Cc: rkannoth, andrew+netdev, edumazet, pabeni, jiri
In-Reply-To: <20260702031359.2392868-1-rkannoth@marvell.com>
Replace MNL_TYPE_* constants with DEVLINK_VAR_ATTR_TYPE_* when
handling DEVLINK_ATTR_PARAM_TYPE in param value display and set
paths. The kernel uAPI now exposes these values directly via
devlink_var_attr_type.
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
devlink/devlink.c | 34 +++++++++++++++++-----------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/devlink/devlink.c b/devlink/devlink.c
index 434a91fe..803ea5d7 100644
--- a/devlink/devlink.c
+++ b/devlink/devlink.c
@@ -3526,7 +3526,7 @@ static int pr_out_param_value_print(const char *nla_name, int nla_type,
print_string(PRINT_FP, NULL, " %s ", label);
switch (nla_type) {
- case MNL_TYPE_U8:
+ case DEVLINK_VAR_ATTR_TYPE_U8:
if (conv_exists) {
err = param_val_conv_str_get(param_val_conv,
PARAM_VAL_CONV_LEN,
@@ -3541,7 +3541,7 @@ static int pr_out_param_value_print(const char *nla_name, int nla_type,
mnl_attr_get_u8(val_attr));
}
break;
- case MNL_TYPE_U16:
+ case DEVLINK_VAR_ATTR_TYPE_U16:
if (conv_exists) {
err = param_val_conv_str_get(param_val_conv,
PARAM_VAL_CONV_LEN,
@@ -3556,7 +3556,7 @@ static int pr_out_param_value_print(const char *nla_name, int nla_type,
mnl_attr_get_u16(val_attr));
}
break;
- case MNL_TYPE_U32:
+ case DEVLINK_VAR_ATTR_TYPE_U32:
if (conv_exists) {
err = param_val_conv_str_get(param_val_conv,
PARAM_VAL_CONV_LEN,
@@ -3571,11 +3571,11 @@ static int pr_out_param_value_print(const char *nla_name, int nla_type,
mnl_attr_get_u32(val_attr));
}
break;
- case MNL_TYPE_STRING:
+ case DEVLINK_VAR_ATTR_TYPE_STRING:
print_string(PRINT_ANY, label, "%s",
mnl_attr_get_str(val_attr));
break;
- case MNL_TYPE_FLAG:
+ case DEVLINK_VAR_ATTR_TYPE_FLAG:
if (flag_as_u8)
print_bool(PRINT_ANY, label, "%s",
mnl_attr_get_u8(val_attr));
@@ -3753,22 +3753,22 @@ static int cmd_dev_param_set_cb(const struct nlmsghdr *nlh, void *data)
ctx->cmode_found = true;
val_attr = nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA];
switch (nla_type) {
- case MNL_TYPE_U8:
+ case DEVLINK_VAR_ATTR_TYPE_U8:
ctx->value.vu8 = mnl_attr_get_u8(val_attr);
break;
- case MNL_TYPE_U16:
+ case DEVLINK_VAR_ATTR_TYPE_U16:
ctx->value.vu16 = mnl_attr_get_u16(val_attr);
break;
- case MNL_TYPE_U32:
+ case DEVLINK_VAR_ATTR_TYPE_U32:
ctx->value.vu32 = mnl_attr_get_u32(val_attr);
break;
- case MNL_TYPE_U64:
+ case DEVLINK_VAR_ATTR_TYPE_U64:
ctx->value.vu64 = mnl_attr_get_u64(val_attr);
break;
- case MNL_TYPE_STRING:
+ case DEVLINK_VAR_ATTR_TYPE_STRING:
ctx->value.vstr = mnl_attr_get_str(val_attr);
break;
- case MNL_TYPE_FLAG:
+ case DEVLINK_VAR_ATTR_TYPE_FLAG:
ctx->value.vbool = val_attr ? true : false;
break;
}
@@ -3841,7 +3841,7 @@ static int cmd_dev_param_set(struct dl *dl)
mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_TYPE, ctx.nla_type);
switch (ctx.nla_type) {
- case MNL_TYPE_U8:
+ case DEVLINK_VAR_ATTR_TYPE_U8:
if (conv_exists) {
err = param_val_conv_uint_get(param_val_conv,
PARAM_VAL_CONV_LEN,
@@ -3858,7 +3858,7 @@ static int cmd_dev_param_set(struct dl *dl)
return 0;
mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u8);
break;
- case MNL_TYPE_U16:
+ case DEVLINK_VAR_ATTR_TYPE_U16:
if (conv_exists) {
err = param_val_conv_uint_get(param_val_conv,
PARAM_VAL_CONV_LEN,
@@ -3875,7 +3875,7 @@ static int cmd_dev_param_set(struct dl *dl)
return 0;
mnl_attr_put_u16(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u16);
break;
- case MNL_TYPE_U32:
+ case DEVLINK_VAR_ATTR_TYPE_U32:
if (conv_exists) {
err = param_val_conv_uint_get(param_val_conv,
PARAM_VAL_CONV_LEN,
@@ -3892,7 +3892,7 @@ static int cmd_dev_param_set(struct dl *dl)
return 0;
mnl_attr_put_u32(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u32);
break;
- case MNL_TYPE_U64:
+ case DEVLINK_VAR_ATTR_TYPE_U64:
if (conv_exists)
err = param_val_conv_uint_get(param_val_conv,
PARAM_VAL_CONV_LEN,
@@ -3907,7 +3907,7 @@ static int cmd_dev_param_set(struct dl *dl)
return 0;
mnl_attr_put_u64(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u64);
break;
- case MNL_TYPE_FLAG:
+ case DEVLINK_VAR_ATTR_TYPE_FLAG:
err = str_to_bool(dl->opts.param_value, &val_bool);
if (err)
goto err_param_value_parse;
@@ -3917,7 +3917,7 @@ static int cmd_dev_param_set(struct dl *dl)
mnl_attr_put(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
0, NULL);
break;
- case MNL_TYPE_STRING:
+ case DEVLINK_VAR_ATTR_TYPE_STRING:
mnl_attr_put_strz(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
dl->opts.param_value);
if (!strcmp(dl->opts.param_value, ctx.value.vstr))
--
2.43.0
^ permalink raw reply related
* [PATCH iproute2-next v4 2/2] devlink: support u64-array values in devlink param show/set
From: Ratheesh Kannoth @ 2026-07-02 3:13 UTC (permalink / raw)
To: stephen, dsahern, kuba, linux-kernel, netdev
Cc: rkannoth, andrew+netdev, edumazet, pabeni, jiri
In-Reply-To: <20260702031359.2392868-1-rkannoth@marvell.com>
Add support for DEVLINK_VAR_ATTR_TYPE_U64_ARRAY parameters that carry
multiple DEVLINK_ATTR_PARAM_VALUE_DATA attributes. Parse and display
u64 array values in param show, and accept space- or comma-separated
u64 values in devlink and port param set commands.
- Show search order
devlink dev param show pci/0002:01:00.0 name npc_srch_order
pci/0002:01:00.0:
name npc_srch_order type driver-specific
values:
cmode runtime value 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
- Set search order
devlink dev param set pci/0002:01:00.0 name npc_srch_order value 31,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,\
22,23,24,25,26,27,28,29,30 cmode runtime
Signed-off-by: Ratheesh Kannoth <rkannoth@marvell.com>
---
devlink/devlink.c | 233 +++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 209 insertions(+), 24 deletions(-)
diff --git a/devlink/devlink.c b/devlink/devlink.c
index 803ea5d7..5d092d92 100644
--- a/devlink/devlink.c
+++ b/devlink/devlink.c
@@ -3516,12 +3516,143 @@ static const struct param_val_conv param_val_conv[] = {
#define PARAM_VAL_CONV_LEN ARRAY_SIZE(param_val_conv)
+struct devlink_param_u64_array {
+ uint64_t size;
+ uint64_t *val;
+};
+
+static void param_value_u64_array_free(struct devlink_param_u64_array *arr)
+{
+ free(arr->val);
+ arr->val = NULL;
+ arr->size = 0;
+}
+
+static int param_value_nested_u64_attr_cb(const struct nlattr *attr, void *data)
+{
+ struct devlink_param_u64_array *arr = data;
+ unsigned int len;
+ uint64_t val;
+ uint64_t *new_val;
+
+ if (mnl_attr_get_type(attr) != DEVLINK_ATTR_PARAM_VALUE_DATA)
+ return MNL_CB_OK;
+
+ len = mnl_attr_get_payload_len(attr);
+ if (len == sizeof(uint32_t))
+ val = mnl_attr_get_u32(attr);
+ else if (len == sizeof(uint64_t))
+ val = mnl_attr_get_u64(attr);
+ else
+ return MNL_CB_ERROR;
+
+ new_val = realloc(arr->val, (arr->size + 1) * sizeof(uint64_t));
+ if (!new_val)
+ return MNL_CB_ERROR;
+ arr->val = new_val;
+
+ arr->val[arr->size] = val;
+ arr->size++;
+
+ return MNL_CB_OK;
+}
+
+static int param_value_u64_array_fill(struct nlattr *nl,
+ struct devlink_param_u64_array *arr)
+{
+ int err;
+
+ param_value_u64_array_free(arr);
+ err = mnl_attr_parse_nested(nl, param_value_nested_u64_attr_cb, arr);
+ if (err != MNL_CB_OK) {
+ param_value_u64_array_free(arr);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static bool param_value_u64_array_equal(const struct devlink_param_u64_array *a,
+ const struct devlink_param_u64_array *b)
+{
+ uint64_t i;
+
+ if (a->size != b->size)
+ return false;
+
+ for (i = 0; i < a->size; i++) {
+ if (a->val[i] != b->val[i])
+ return false;
+ }
+
+ return true;
+}
+
+static int param_value_u64_array_put_from_str(struct nlmsghdr *nlh,
+ const char *param_value,
+ const struct devlink_param_u64_array *cur)
+{
+ struct devlink_param_u64_array new_arr = {};
+ char *copy, *token, *saveptr = NULL;
+ uint64_t val, *new_val;
+ char delim[] = " ,";
+ int err, i;
+
+ copy = strdup(param_value);
+ if (!copy)
+ return -ENOMEM;
+
+ token = strtok_r(copy, delim, &saveptr);
+ while (token) {
+ err = get_u64((__u64 *)&val, token, 10);
+ if (err) {
+ free(copy);
+ param_value_u64_array_free(&new_arr);
+ pr_err("Value \"%s\" is not a number or not within range\n",
+ token);
+ return err;
+ }
+
+ new_val = realloc(new_arr.val, (new_arr.size + 1) * sizeof(uint64_t));
+ if (!new_val) {
+ free(copy);
+ param_value_u64_array_free(&new_arr);
+ return -ENOMEM;
+ }
+ new_arr.val = new_val;
+
+ new_arr.val[new_arr.size] = val;
+ new_arr.size++;
+ token = strtok_r(NULL, delim, &saveptr);
+ }
+ free(copy);
+
+ if (!new_arr.size) {
+ param_value_u64_array_free(&new_arr);
+ pr_err("Value must contain at least one element\n");
+ return -EINVAL;
+ }
+
+ /* Check current and new values. If both are equal, bail out */
+ if (cur && param_value_u64_array_equal(&new_arr, cur)) {
+ param_value_u64_array_free(&new_arr);
+ return 1;
+ }
+
+ for (i = 0; i < new_arr.size; i++)
+ mnl_attr_put_u64(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, new_arr.val[i]);
+
+ param_value_u64_array_free(&new_arr);
+ return 0;
+}
+
static int pr_out_param_value_print(const char *nla_name, int nla_type,
struct nlattr *val_attr, bool conv_exists,
- const char *label, bool flag_as_u8)
+ const char *label, bool flag_as_u8, struct nlattr *nl)
{
- const char *vstr;
+ struct devlink_param_u64_array u64_arr = { };
int err;
+ const char *vstr;
print_string(PRINT_FP, NULL, " %s ", label);
@@ -3582,6 +3713,16 @@ static int pr_out_param_value_print(const char *nla_name, int nla_type,
else
print_bool(PRINT_ANY, label, "%s", val_attr);
break;
+ case DEVLINK_VAR_ATTR_TYPE_U64_ARRAY:
+ err = param_value_u64_array_fill(flag_as_u8 ? val_attr : nl, &u64_arr);
+ if (err)
+ return err;
+
+ for (uint64_t i = 0; i < u64_arr.size; i++)
+ print_u64(PRINT_ANY, label, "%llu ", u64_arr.val[i]);
+
+ param_value_u64_array_free(&u64_arr);
+ break;
}
return 0;
@@ -3601,6 +3742,7 @@ static void pr_out_param_value(struct dl *dl, const char *nla_name,
if (!nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE] ||
(nla_type != MNL_TYPE_FLAG &&
+ nla_type != DEVLINK_VAR_ATTR_TYPE_U64_ARRAY &&
!nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA]))
return;
@@ -3614,14 +3756,14 @@ static void pr_out_param_value(struct dl *dl, const char *nla_name,
nla_name);
err = pr_out_param_value_print(nla_name, nla_type, val_attr,
- conv_exists, "value", false);
+ conv_exists, "value", false, nl);
if (err)
return;
val_attr = nla_value[DEVLINK_ATTR_PARAM_VALUE_DEFAULT];
if (val_attr) {
err = pr_out_param_value_print(nla_name, nla_type, val_attr,
- conv_exists, "default", true);
+ conv_exists, "default", true, nl);
if (err)
return;
}
@@ -3704,6 +3846,7 @@ struct param_ctx {
uint64_t vu64;
const char *vstr;
bool vbool;
+ struct devlink_param_u64_array u64arr;
} value;
};
@@ -3745,6 +3888,7 @@ static int cmd_dev_param_set_cb(const struct nlmsghdr *nlh, void *data)
if (!nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE] ||
(nla_type != MNL_TYPE_FLAG &&
+ nla_type != DEVLINK_VAR_ATTR_TYPE_U64_ARRAY &&
!nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA]))
return MNL_CB_ERROR;
@@ -3771,6 +3915,12 @@ static int cmd_dev_param_set_cb(const struct nlmsghdr *nlh, void *data)
case DEVLINK_VAR_ATTR_TYPE_FLAG:
ctx->value.vbool = val_attr ? true : false;
break;
+ case DEVLINK_VAR_ATTR_TYPE_U64_ARRAY:
+ err = param_value_u64_array_fill(param_value_attr,
+ &ctx->value.u64arr);
+ if (err)
+ return MNL_CB_ERROR;
+ break;
}
break;
}
@@ -3818,10 +3968,11 @@ static int cmd_dev_param_set(struct dl *dl)
ctx.dl = dl;
err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_dev_param_set_cb, &ctx);
if (err)
- return err;
+ goto out;
if (!ctx.cmode_found) {
pr_err("Configuration mode not supported\n");
- return -ENOTSUP;
+ err = -ENOTSUP;
+ goto out;
}
if (dl->opts.present & DL_OPT_PARAM_SET_DEFAULT) {
@@ -3829,7 +3980,8 @@ static int cmd_dev_param_set(struct dl *dl)
NLM_F_REQUEST | NLM_F_ACK);
dl_opts_put(nlh, dl);
mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_TYPE, ctx.nla_type);
- return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+ goto out;
}
nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PARAM_SET,
@@ -3855,7 +4007,7 @@ static int cmd_dev_param_set(struct dl *dl)
if (err)
goto err_param_value_parse;
if (val_u8 == ctx.value.vu8)
- return 0;
+ goto out;
mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u8);
break;
case DEVLINK_VAR_ATTR_TYPE_U16:
@@ -3872,7 +4024,7 @@ static int cmd_dev_param_set(struct dl *dl)
if (err)
goto err_param_value_parse;
if (val_u16 == ctx.value.vu16)
- return 0;
+ goto out;
mnl_attr_put_u16(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u16);
break;
case DEVLINK_VAR_ATTR_TYPE_U32:
@@ -3889,7 +4041,7 @@ static int cmd_dev_param_set(struct dl *dl)
if (err)
goto err_param_value_parse;
if (val_u32 == ctx.value.vu32)
- return 0;
+ goto out;
mnl_attr_put_u32(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u32);
break;
case DEVLINK_VAR_ATTR_TYPE_U64:
@@ -3904,7 +4056,7 @@ static int cmd_dev_param_set(struct dl *dl)
if (err)
goto err_param_value_parse;
if (val_u64 == ctx.value.vu64)
- return 0;
+ goto out;
mnl_attr_put_u64(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u64);
break;
case DEVLINK_VAR_ATTR_TYPE_FLAG:
@@ -3912,7 +4064,7 @@ static int cmd_dev_param_set(struct dl *dl)
if (err)
goto err_param_value_parse;
if (val_bool == ctx.value.vbool)
- return 0;
+ goto out;
if (val_bool)
mnl_attr_put(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
0, NULL);
@@ -3921,17 +4073,30 @@ static int cmd_dev_param_set(struct dl *dl)
mnl_attr_put_strz(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
dl->opts.param_value);
if (!strcmp(dl->opts.param_value, ctx.value.vstr))
- return 0;
+ goto out;
+ break;
+ case DEVLINK_VAR_ATTR_TYPE_U64_ARRAY:
+ err = param_value_u64_array_put_from_str(nlh, dl->opts.param_value,
+ &ctx.value.u64arr);
+ if (err == 1)
+ goto out;
+ if (err)
+ goto out;
break;
default:
printf("Value type not supported\n");
- return -ENOTSUP;
+ err = -ENOTSUP;
+ goto out;
}
- return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+ goto out;
err_param_value_parse:
pr_err("Value \"%s\" is not a number or not within range\n",
dl->opts.param_value);
+
+out:
+ param_value_u64_array_free(&ctx.value.u64arr);
return err;
}
@@ -5369,6 +5534,7 @@ static int cmd_port_param_set_cb(const struct nlmsghdr *nlh, void *data)
if (!nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE] ||
(nla_type != MNL_TYPE_FLAG &&
+ nla_type != DEVLINK_VAR_ATTR_TYPE_U64_ARRAY &&
!nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA]))
return MNL_CB_ERROR;
@@ -5391,6 +5557,12 @@ static int cmd_port_param_set_cb(const struct nlmsghdr *nlh, void *data)
case MNL_TYPE_FLAG:
ctx->value.vbool = val_attr ? true : false;
break;
+ case DEVLINK_VAR_ATTR_TYPE_U64_ARRAY:
+ err = param_value_u64_array_fill(param_value_attr,
+ &ctx->value.u64arr);
+ if (err)
+ return MNL_CB_ERROR;
+ break;
}
break;
}
@@ -5426,7 +5598,7 @@ static int cmd_port_param_set(struct dl *dl)
ctx.dl = dl;
err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_port_param_set_cb, &ctx);
if (err)
- return err;
+ goto out;
nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_PARAM_SET,
NLM_F_REQUEST | NLM_F_ACK);
@@ -5451,7 +5623,7 @@ static int cmd_port_param_set(struct dl *dl)
if (err)
goto err_param_value_parse;
if (val_u8 == ctx.value.vu8)
- return 0;
+ goto out;
mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u8);
break;
case MNL_TYPE_U16:
@@ -5468,7 +5640,7 @@ static int cmd_port_param_set(struct dl *dl)
if (err)
goto err_param_value_parse;
if (val_u16 == ctx.value.vu16)
- return 0;
+ goto out;
mnl_attr_put_u16(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u16);
break;
case MNL_TYPE_U32:
@@ -5485,7 +5657,7 @@ static int cmd_port_param_set(struct dl *dl)
if (err)
goto err_param_value_parse;
if (val_u32 == ctx.value.vu32)
- return 0;
+ goto out;
mnl_attr_put_u32(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u32);
break;
case MNL_TYPE_U64:
@@ -5500,7 +5672,7 @@ static int cmd_port_param_set(struct dl *dl)
if (err)
goto err_param_value_parse;
if (val_u64 == ctx.value.vu64)
- return 0;
+ goto out;
mnl_attr_put_u64(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u64);
break;
case MNL_TYPE_FLAG:
@@ -5508,7 +5680,7 @@ static int cmd_port_param_set(struct dl *dl)
if (err)
goto err_param_value_parse;
if (val_bool == ctx.value.vbool)
- return 0;
+ goto out;
if (val_bool)
mnl_attr_put(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
0, NULL);
@@ -5517,17 +5689,30 @@ static int cmd_port_param_set(struct dl *dl)
mnl_attr_put_strz(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
dl->opts.param_value);
if (!strcmp(dl->opts.param_value, ctx.value.vstr))
- return 0;
+ goto out;
+ break;
+ case DEVLINK_VAR_ATTR_TYPE_U64_ARRAY:
+ err = param_value_u64_array_put_from_str(nlh, dl->opts.param_value,
+ &ctx.value.u64arr);
+ if (err == 1)
+ goto out;
+ if (err)
+ goto out;
break;
default:
printf("Value type not supported\n");
- return -ENOTSUP;
+ err = -ENOTSUP;
+ goto out;
}
- return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+ goto out;
err_param_value_parse:
pr_err("Value \"%s\" is not a number or not within range\n",
dl->opts.param_value);
+
+out:
+ param_value_u64_array_free(&ctx.value.u64arr);
return err;
}
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net v3] net/smc: fix out-of-bounds read when sk_user_data holds a sk_psock
From: D. Wythe @ 2026-07-02 3:14 UTC (permalink / raw)
To: Sechang Lim
Cc: D . Wythe, Dust Li, Sidraya Jayagond, Wenjia Zhang,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jiayuan Chen, Mahanta Jambigi, Tony Lu, Wen Gu, Simon Horman,
Karsten Graul, Guvenc Gulce, Ursula Braun, linux-rdma, linux-s390,
netdev, linux-kernel, bpf
In-Reply-To: <20260629095140.679754-1-rhkrqnwk98@gmail.com>
On Mon, Jun 29, 2026 at 09:51:33AM +0000, Sechang Lim wrote:
> A passive-open child inherits the listener's smc_clcsock_data_ready().
> sk_clone_lock() clears its sk_user_data to NULL because the listener tagged
> it SK_USER_DATA_NOCOPY. Until accept restores the callback, a BPF sock_ops
> program can add the established child to a sockmap, and sk_psock_init()
> installs a sk_psock into the NULL sk_user_data. The inherited callback then
> reads it back through smc_clcsock_user_data(), which strips only NOCOPY,
> takes the sk_psock for an smc_sock, and dereferences a clcsk_* field past
> its end:
>
> BUG: KASAN: slab-out-of-bounds in smc_clcsock_data_ready+0x84/0x200 net/smc/af_smc.c:2637
> Read of size 8 at addr ffff8880013b8674 by task syz.6.12484/67930
> <IRQ>
> smc_clcsock_data_ready+0x84/0x200 net/smc/af_smc.c:2637
> tcp_urg+0x24d/0x360 net/ipv4/tcp_input.c:6264
> tcp_rcv_state_process+0x280d/0x4940 net/ipv4/tcp_input.c:7336
> tcp_child_process+0x371/0xa50 net/ipv4/tcp_minisocks.c:1002
> tcp_v4_rcv+0x1eaa/0x2a00 net/ipv4/tcp_ipv4.c:2186
> [...]
> </IRQ>
>
> Allocated by task 67930:
> sk_psock_init+0x142/0x740 net/core/skmsg.c:766
> sock_hash_update_common+0xd3/0x990 net/core/sock_map.c:1010
> bpf_sock_hash_update+0x114/0x170 net/core/sock_map.c:1229
> __cgroup_bpf_run_filter_sock_ops+0x74/0xa0 kernel/bpf/cgroup.c:1727
> tcp_init_transfer+0x1085/0x1100 net/ipv4/tcp_input.c:6693
> [...]
>
> Resolve the conflict on the write path. Reserve the child's sk_user_data
> with a NULL pointer tagged SK_USER_DATA_NOCOPY so sk_psock_init() returns
> -EBUSY, and release it at accept. smc_clcsock_user_data() still strips the
> tag to NULL, so the inherited callback stays a no-op.
>
> Fixes: a60a2b1e0af1 ("net/smc: reduce active tcp_listen workers")
> Signed-off-by: Sechang Lim <rhkrqnwk98@gmail.com>
> ---
> v3:
> - reserve sk_user_data on the write path instead of the read-side check (D. Wythe)
>
> v2:
> - https://lore.kernel.org/netdev/20260619150342.3626224-1-rhkrqnwk98@gmail.com/
>
> v1:
> - https://lore.kernel.org/netdev/20260614120931.4041687-1-rhkrqnwk98@gmail.com/
>
> net/smc/af_smc.c | 10 +++++++++-
> 1 file changed, 9 insertions(+), 1 deletion(-)
>
> diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
> index b5db69073e20..78f162344fe3 100644
> --- a/net/smc/af_smc.c
> +++ b/net/smc/af_smc.c
> @@ -154,7 +154,11 @@ static struct sock *smc_tcp_syn_recv_sock(const struct sock *sk,
> own_req, opt_child_init);
> /* child must not inherit smc or its ops */
> if (child) {
> - rcu_assign_sk_user_data(child, NULL);
> + /* reserve sk_user_data so sockmap cannot claim the slot */
> + write_lock_bh(&child->sk_callback_lock);
> + __rcu_assign_sk_user_data_with_flags(child, NULL,
> + SK_USER_DATA_NOCOPY);
> + write_unlock_bh(&child->sk_callback_lock);
>
> /* v4-mapped sockets don't inherit parent ops. Don't restore. */
> if (inet_csk(child)->icsk_af_ops == inet_csk(sk)->icsk_af_ops)
> @@ -1773,6 +1777,7 @@ static int smc_clcsock_accept(struct smc_sock *lsmc, struct smc_sock **new_smc)
> /* new clcsock has inherited the smc listen-specific sk_data_ready
> * function; switch it back to the original sk_data_ready function
> */
> + write_lock_bh(&new_clcsock->sk->sk_callback_lock);
> new_clcsock->sk->sk_data_ready = lsmc->clcsk_data_ready;
>
> /* if new clcsock has also inherited the fallback-specific callback
> @@ -1786,6 +1791,9 @@ static int smc_clcsock_accept(struct smc_sock *lsmc, struct smc_sock **new_smc)
> if (lsmc->clcsk_error_report)
> new_clcsock->sk->sk_error_report = lsmc->clcsk_error_report;
> }
> + /* release the slot reserved in smc_tcp_syn_recv_sock() */
> + rcu_assign_sk_user_data(new_clcsock->sk, NULL);
> + write_unlock_bh(&new_clcsock->sk->sk_callback_lock);
>
> (*new_smc)->clcsock = new_clcsock;
> out:
> --
I do not think this is the right direction.
You have now sent three versions of essentially the same patch, but I
still do not see a clear explanation of why this approach is supposed to
solve the problem you described. At this point, it looks like you are
repeatedly posting changes without demonstrating that you fully
understand the ownership and lifetime rules involved here.
This is not the approach I suggested. My preference is to keep the
sk_user_data reserved from the time the clcsock is created, by not
setting NOCOPY, so that it remains inherited by the passive socket.
That avoids the release-then-reacquire pattern entirely. The problem
is more complicated than it may appear, and if you choose this direction,
you also need to account for how SMC fallback continues to work with
sockmap.
If you do not think you can fully address that approach, then feel free
to pursue other options that you are able to handle, but I will not ack
any workaround version. You may seek ack from other maintainers.
D. Wythe
> 2.43.0
^ permalink raw reply
* RE: [PATCH net-next v7 4/4] net: phy: realtek: load firmware for RTL8261C_CG
From: Javen @ 2026-07-02 3:16 UTC (permalink / raw)
To: Andrew Lunn
Cc: hkallweit1@gmail.com, linux@armlinux.org.uk, davem@davemloft.net,
edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
顾晓军, nb@tipi-net.de, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, daniel@makrotopia.org,
vladimir.oltean@nxp.com, nic_swsd@realtek.com
In-Reply-To: <98213191-594d-4e26-beeb-fd9b9a838b64@lunn.ch>
>
>On Mon, Jun 29, 2026 at 02:47:18PM +0800, javen wrote:
>> From: Javen Xu <javen_xu@realsil.com.cn>
>>
>> This patch adds support for loading firmware. Download some parameters
>> for RTL8261C_CG.
>>
>> Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
>> ---
>> Changes in v2:
>> - remove __pack, struct rtl8261x_fw_header and rtl8261x_fw_entry will
>> not pad
>> - reverse xmas tree for some definition
>> - add explanation on rtl_phy_write_mmd_bits()
>>
>> Changes in v3:
>> - add struct rtl8261x_priv
>>
>> Changes in v4:
>> - add struct device *dev
>>
>> Changes in v5:
>> - no changes
>>
>> Changes in v6:
>> - replace rtl_phy_write_mmd_bits with phy_modify_mmd, keep mdio lock
>> - check msb and lsb at the beginning of rtl8261x_fw_execute_entry()
>> - add comments on rtl8261x_config_init()
>>
>> Changes in v7:
>> - no changes
>> ---
>> drivers/net/phy/realtek/realtek_main.c | 220
>> +++++++++++++++++++++++++
>> 1 file changed, 220 insertions(+)
>>
>> diff --git a/drivers/net/phy/realtek/realtek_main.c
>> b/drivers/net/phy/realtek/realtek_main.c
>> index ef3700894ebf..bf7bc19fb44c 100644
>> --- a/drivers/net/phy/realtek/realtek_main.c
>> +++ b/drivers/net/phy/realtek/realtek_main.c
>> @@ -8,7 +8,9 @@
>> * Copyright (c) 2004 Freescale Semiconductor, Inc.
>> */
>> #include <linux/bitops.h>
>> +#include <linux/crc32.h>
>> #include <linux/ethtool_netlink.h>
>> +#include <linux/firmware.h>
>> #include <linux/of.h>
>> #include <linux/phy.h>
>> #include <linux/pm_wakeirq.h>
>> @@ -281,6 +283,42 @@
>> RTL8261X_INT_ALDPS_CHG | \
>> RTL8261X_INT_JABBER)
>>
>> +#define FW_MAIN_MAGIC 0x52544C38
>> +#define FW_SUB_MAGIC_8261C 0x32363143
>> +#define RTL8261X_POLL_TIMEOUT_MS 100
>> +
>> +#define RTL8261C_CE_FW_NAME "rtl_nic/rtl8261c.bin"
>> +MODULE_FIRMWARE(RTL8261C_CE_FW_NAME);
>> +
>> +enum rtl8261x_fw_op {
>> + OP_WRITE = 0x00, /* Write */
>> + OP_POLL = 0x02, /* Polling */
>> +};
>> +
>> +struct rtl8261x_fw_header {
>> + __le32 main_magic; /* Main magic number 0x52544C38 ("RTL8") */
>> + __le32 sub_magic; /* Sub magic number */
>> + __le16 version_major; /* Major version */
>> + __le16 version_minor; /* Minor version */
>> + __le16 num_entries; /* Number of entries */
>> + __le16 reserved; /* Reserved */
>> + __le32 crc32; /* CRC32 checksum */
>> +};
>> +
>> +struct rtl8261x_fw_entry {
>> + __u8 type; /* Operation type (OP_*) */
>> + __u8 dev; /* MMD device */
>> + __le16 addr; /* Register address */
>> + __u8 msb; /* MSB bit position */
>> + __u8 lsb; /* LSB bit position */
>> + __le16 value; /* Value to write/compare */
>> + __le16 timeout_ms; /* Poll timeout in milliseconds */
>> + __u8 poll_set; /* Poll for set (1) or clear (0) */
>> + __u8 reserved; /* Reserved */
>> +};
>> +
>> +#define FW_HEADER_SIZE sizeof(struct rtl8261x_fw_header)
>> +#define FW_ENTRY_SIZE sizeof(struct rtl8261x_fw_entry)
>>
>> /* RTL8211E and RTL8211F support up to three LEDs */
>> #define RTL8211x_LED_COUNT 3
>> @@ -300,6 +338,11 @@ struct rtl821x_priv {
>> u16 iner;
>> };
>>
>> +struct rtl8261x_priv {
>> + const char *fw_name;
>> + bool fw_loaded;
>> +};
>> +
>> static int rtl821x_read_page(struct phy_device *phydev) {
>> return __phy_read(phydev, RTL821x_PAGE_SELECT); @@ -342,8
>> +385,16 @@ static int rtl821x_modify_ext_page(struct phy_device
>> *phydev, u16 ext_page,
>>
>> static int rtl8261x_probe(struct phy_device *phydev) {
>> + struct device *dev = &phydev->mdio.dev;
>> + struct rtl8261x_priv *priv;
>> int sub_phy_id, ret;
>>
>> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> + if (!priv)
>> + return -ENOMEM;
>> +
>> + phydev->priv = priv;
>> +
>> ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
>RTL8261X_EXT_ADDR_REG,
>> RTL_8261X_SUB_PHY_ID_ADDR);
>> if (ret < 0)
>> @@ -357,6 +408,7 @@ static int rtl8261x_probe(struct phy_device
>> *phydev)
>>
>> switch (sub_phy_id) {
>> case RTL8261C_CE_MODEL:
>> + priv->fw_name = RTL8261C_CE_FW_NAME;
>> phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n",
>sub_phy_id);
>> break;
>>
>> @@ -407,6 +459,153 @@ static int rtl8261x_read_status(struct phy_device
>*phydev)
>> return 0;
>> }
>>
>> +static int rtl8261x_verify_firmware(struct phy_device *phydev, const
>> +struct firmware *fw) {
>> + const struct rtl8261x_fw_header *hdr;
>> + u32 main_magic, sub_magic;
>> + u32 calc_crc, file_crc;
>> + size_t data_len;
>> + u16 num_entries;
>> +
>> + if (fw->size < FW_HEADER_SIZE) {
>> + phydev_err(phydev, "Firmware too small: %zu bytes\n", fw->size);
>> + return -EINVAL;
>> + }
>> +
>> + hdr = (const struct rtl8261x_fw_header *)fw->data;
>> +
>> + main_magic = le32_to_cpu(hdr->main_magic);
>> + if (main_magic != FW_MAIN_MAGIC) {
>> + phydev_err(phydev, "Invalid firmware magic: 0x%08x\n",
>main_magic);
>> + return -EINVAL;
>> + }
>> +
>> + sub_magic = le32_to_cpu(hdr->sub_magic);
>> + if (sub_magic != FW_SUB_MAGIC_8261C) {
>> + phydev_err(phydev, "Invalid sub magic: 0x%08x\n", sub_magic);
>> + return -EINVAL;
>> + }
>> +
>> + num_entries = le16_to_cpu(hdr->num_entries);
>> + data_len = num_entries * FW_ENTRY_SIZE;
>> +
>> + if (fw->size != sizeof(*hdr) + data_len) {
>> + phydev_err(phydev, "Firmware size mismatch\n");
>> + return -EINVAL;
>> + }
>> +
>> + calc_crc = crc32(~0, fw->data + FW_HEADER_SIZE, data_len) ^ ~0;
>> + file_crc = le32_to_cpu(hdr->crc32);
>> +
>> + if (calc_crc != file_crc) {
>> + phydev_err(phydev, "CRC32 mismatch: calculated=0x%08x
>file=0x%08x\n",
>> + calc_crc, file_crc);
>> + return -EINVAL;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int rtl8261x_fw_execute_entry(struct phy_device *phydev,
>> + const struct rtl8261x_fw_entry
>> +*entry) {
>> + u16 addr, value, timeout_ms;
>> + u8 dev, msb, lsb, poll_set;
>> + u32 bits, expect_val;
>> + int ret = 0;
>> + int val;
>> +
>> + dev = entry->dev;
>> + addr = le16_to_cpu(entry->addr);
>> + msb = entry->msb;
>> + lsb = entry->lsb;
>> + value = le16_to_cpu(entry->value);
>> + timeout_ms = le16_to_cpu(entry->timeout_ms);
>> + poll_set = entry->poll_set;
>> +
>> + if (timeout_ms == 0)
>> + timeout_ms = RTL8261X_POLL_TIMEOUT_MS;
>> +
>> + if (msb > 15 || lsb > msb) {
>> + phydev_err(phydev, "Invalid firmware bits: msb=%d, lsb=%d\n",
>msb, lsb);
>> + return -EINVAL;
>> + }
>> +
>> + switch (entry->type) {
>> + case OP_WRITE:
>> + ret = phy_modify_mmd(phydev, dev, addr,
>> + GENMASK(msb, lsb), (value << lsb) & GENMASK(msb,
>lsb));
>> + if (ret) {
>> + phydev_err(phydev, "WRITE failed: dev=%d addr=0x%04x\n",
>dev, addr);
>> + return ret;
>
>Here you have a return on error.
>
>> + }
>> + break;
>> +
>> + case OP_POLL: {
>> + bits = GENMASK(msb, lsb);
>> + expect_val = (value << lsb) & bits;
>> +
>> + if (poll_set)
>> + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val,
>> + (val & bits) == expect_val,
>> + 1000, timeout_ms * 1000, false);
>> + else
>> + ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val,
>> + (val & bits) != expect_val,
>> + 1000, timeout_ms * 1000, false);
>> + if (ret)
>> + phydev_err(phydev, "POLL timeout: dev=%d addr=0x%04x\n",
>> + dev, addr);
>> + break;
>
>Here you don't. It would be better if the code was consistent.
>
>> + }
>> + default:
>> + phydev_err(phydev, "Unknown firmware operation: %d\n", entry-
>>type);
>> + ret = -EINVAL;
>> + break;
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static int rtl8261x_fw_load(struct phy_device *phydev) {
>> + struct rtl8261x_priv *priv = phydev->priv;
>> + const struct rtl8261x_fw_entry *entry;
>> + const struct rtl8261x_fw_header *hdr;
>> + const struct firmware *fw;
>> + int ret, i;
>> +
>> + if (!priv->fw_name)
>> + return 0;
>> +
>> + ret = request_firmware(&fw, priv->fw_name, &phydev->mdio.dev);
>> + if (ret) {
>> + phydev_err(phydev, "Failed to load firmware %s: %d\n", priv-
>>fw_name, ret);
>> + return ret;
>> + }
>> +
>> + ret = rtl8261x_verify_firmware(phydev, fw);
>> + if (ret)
>> + goto release_fw;
>> +
>> + hdr = (const struct rtl8261x_fw_header *)fw->data;
>> +
>> + entry = (const struct rtl8261x_fw_entry *)(fw->data + FW_HEADER_SIZE);
>> + for (i = 0; i < le16_to_cpu(hdr->num_entries); i++, entry++) {
>> + ret = rtl8261x_fw_execute_entry(phydev, entry);
>> + if (ret) {
>> + phydev_err(phydev, "Entry %d failed: %d\n", i, ret);
>> + goto release_fw;
>> + }
>> + }
>> +
>> + priv->fw_loaded = true;
>> +
>> +release_fw:
>> + release_firmware(fw);
>> + return ret;
>> +}
>> +
>> static int rtl8261x_config_intr(struct phy_device *phydev) {
>> int ret;
>> @@ -484,6 +683,26 @@ static int rtl8261x_config_aneg(struct phy_device
>*phydev)
>> return 0;
>> }
>>
>> +static int rtl8261x_config_init(struct phy_device *phydev) {
>> + struct rtl8261x_priv *priv = phydev->priv;
>> + int ret = 0;
>> +
>> + /* The firmware parameters are preserved across IEEE soft resets and
>> + * suspend/resume cycles. Reloading is only necessary after a power
>> + * cycle or hard reset.
>> + */
>> + if (priv->fw_name && !priv->fw_loaded) {
>> + ret = rtl8261x_fw_load(phydev);
>> + if (ret) {
>> + phydev_err(phydev, "Firmware loading failed:
>> + %d\n", ret);
>
>It seems pretty verbose when things go wrong. Do you need an error message
>at every level?
>
I will remove the lower-level error messages and keep a single error message in rtl8261x_fw_load().
Thanks,
Javen
> Andrew
>
>---
>pw-bot: cr
^ permalink raw reply
* Re: [PATCH net v3] net/smc: fix out-of-bounds read when sk_user_data holds a sk_psock
From: D. Wythe @ 2026-07-02 3:17 UTC (permalink / raw)
To: Paolo Abeni
Cc: Sechang Lim, D . Wythe, Dust Li, Sidraya Jayagond, Wenjia Zhang,
David S . Miller, Eric Dumazet, Jakub Kicinski, Jiayuan Chen,
Mahanta Jambigi, Tony Lu, Wen Gu, Simon Horman, Karsten Graul,
Guvenc Gulce, Ursula Braun, linux-rdma, linux-s390, netdev,
linux-kernel, bpf
In-Reply-To: <c222390f-96c7-4dc3-8f33-f4dd277c3639@redhat.com>
On Wed, Jul 01, 2026 at 05:27:11PM +0200, Paolo Abeni wrote:
> On 6/29/26 11:51 AM, Sechang Lim wrote:
> > A passive-open child inherits the listener's smc_clcsock_data_ready().
> > sk_clone_lock() clears its sk_user_data to NULL because the listener tagged
> > it SK_USER_DATA_NOCOPY. Until accept restores the callback, a BPF sock_ops
> > program can add the established child to a sockmap, and sk_psock_init()
> > installs a sk_psock into the NULL sk_user_data. The inherited callback then
> > reads it back through smc_clcsock_user_data(), which strips only NOCOPY,
> > takes the sk_psock for an smc_sock, and dereferences a clcsk_* field past
> > its end:
> >
> > BUG: KASAN: slab-out-of-bounds in smc_clcsock_data_ready+0x84/0x200 net/smc/af_smc.c:2637
> > Read of size 8 at addr ffff8880013b8674 by task syz.6.12484/67930
> > <IRQ>
> > smc_clcsock_data_ready+0x84/0x200 net/smc/af_smc.c:2637
> > tcp_urg+0x24d/0x360 net/ipv4/tcp_input.c:6264
> > tcp_rcv_state_process+0x280d/0x4940 net/ipv4/tcp_input.c:7336
> > tcp_child_process+0x371/0xa50 net/ipv4/tcp_minisocks.c:1002
> > tcp_v4_rcv+0x1eaa/0x2a00 net/ipv4/tcp_ipv4.c:2186
> > [...]
> > </IRQ>
> >
> > Allocated by task 67930:
> > sk_psock_init+0x142/0x740 net/core/skmsg.c:766
> > sock_hash_update_common+0xd3/0x990 net/core/sock_map.c:1010
> > bpf_sock_hash_update+0x114/0x170 net/core/sock_map.c:1229
> > __cgroup_bpf_run_filter_sock_ops+0x74/0xa0 kernel/bpf/cgroup.c:1727
> > tcp_init_transfer+0x1085/0x1100 net/ipv4/tcp_input.c:6693
> > [...]
> >
> > Resolve the conflict on the write path. Reserve the child's sk_user_data
> > with a NULL pointer tagged SK_USER_DATA_NOCOPY so sk_psock_init() returns
> > -EBUSY, and release it at accept. smc_clcsock_user_data() still strips the
> > tag to NULL, so the inherited callback stays a no-op.
> >
> > Fixes: a60a2b1e0af1 ("net/smc: reduce active tcp_listen workers")
> > Signed-off-by: Sechang Lim <rhkrqnwk98@gmail.com>
> > ---
> > v3:
> > - reserve sk_user_data on the write path instead of the read-side check (D. Wythe)
> >
> > v2:
> > - https://lore.kernel.org/netdev/20260619150342.3626224-1-rhkrqnwk98@gmail.com/
> >
> > v1:
> > - https://lore.kernel.org/netdev/20260614120931.4041687-1-rhkrqnwk98@gmail.com/
> >
> > net/smc/af_smc.c | 10 +++++++++-
> > 1 file changed, 9 insertions(+), 1 deletion(-)
> >
> > diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
> > index b5db69073e20..78f162344fe3 100644
> > --- a/net/smc/af_smc.c
> > +++ b/net/smc/af_smc.c
> > @@ -154,7 +154,11 @@ static struct sock *smc_tcp_syn_recv_sock(const struct sock *sk,
> > own_req, opt_child_init);
> > /* child must not inherit smc or its ops */
> > if (child) {
> > - rcu_assign_sk_user_data(child, NULL);
> > + /* reserve sk_user_data so sockmap cannot claim the slot */
> > + write_lock_bh(&child->sk_callback_lock);
> > + __rcu_assign_sk_user_data_with_flags(child, NULL,
> > + SK_USER_DATA_NOCOPY);
> > + write_unlock_bh(&child->sk_callback_lock);
> >
> > /* v4-mapped sockets don't inherit parent ops. Don't restore. */
> > if (inet_csk(child)->icsk_af_ops == inet_csk(sk)->icsk_af_ops)
> > @@ -1773,6 +1777,7 @@ static int smc_clcsock_accept(struct smc_sock *lsmc, struct smc_sock **new_smc)
> > /* new clcsock has inherited the smc listen-specific sk_data_ready
> > * function; switch it back to the original sk_data_ready function
> > */
> > + write_lock_bh(&new_clcsock->sk->sk_callback_lock);
> > new_clcsock->sk->sk_data_ready = lsmc->clcsk_data_ready;
> >
> > /* if new clcsock has also inherited the fallback-specific callback
> > @@ -1786,6 +1791,9 @@ static int smc_clcsock_accept(struct smc_sock *lsmc, struct smc_sock **new_smc)
> > if (lsmc->clcsk_error_report)
> > new_clcsock->sk->sk_error_report = lsmc->clcsk_error_report;
> > }
> > + /* release the slot reserved in smc_tcp_syn_recv_sock() */
> > + rcu_assign_sk_user_data(new_clcsock->sk, NULL);
> > + write_unlock_bh(&new_clcsock->sk->sk_callback_lock);
>
> Sashiko reports that this still cause problem on fallback.
>
> @Wythe, I understand from previous discussion that you would prefer to
> address such issues separately (and thus you are fine with the patch in
> the current form). Could you please confirm?
No, I do not agree with this patch in any form. As I replied to it, I do
not think this is the right direction.
D. Wythe
>
> /P
> >
> > (*new_smc)->clcsock = new_clcsock;
> > out:
^ permalink raw reply
* [PATCH net-next v8 0/4] Add support for RTL8261C_CG
From: javen @ 2026-07-02 3:20 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
freddy_gu, nb
Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
From: Javen Xu <javen_xu@realsil.com.cn>
Add support for RTL8261C_CG and add support for loading firmware.
Javen Xu (4):
net: phy: c45: add genphy_c45_soft_reset()
net: phy: c45: add setup and read master/slave helpers
net: phy: realtek: add support for RTL8261C_CG
net: phy: realtek: load firmware for RTL8261C_CG
drivers/net/phy/phy-c45.c | 125 ++++++++
drivers/net/phy/realtek/realtek_main.c | 400 +++++++++++++++++++++++++
include/linux/phy.h | 1 +
include/uapi/linux/mdio.h | 5 +
4 files changed, 531 insertions(+)
--
2.43.0
^ permalink raw reply
* [PATCH net-next v8 2/4] net: phy: c45: add setup and read master/slave helpers
From: javen @ 2026-07-02 3:20 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
freddy_gu, nb
Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260702032020.589-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
This patch adds two static helpers in drivers/net/phy/phy-c45.c to
configure and read back master-slave roles for non BASE-T1 Clause 45
PHYs via the 10GBASE-T AN control/status registers.
These helpers are wired into genphy_c45_config_aneg() and
genphy_c45_read_status(). This changes the observable ethtool output
for drivers using the generic c45 read path.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- no changes, new file
Changes in v3:
- re-order function according to the order in phy-c45.c
- add kernel-doc about return value
- add MASTER_SLAVE_CFG_MASTER_PREFERRED,
MASTER_SLAVE_CFG_SLAVE_PREFERRED, MASTER_SLAVE_CFG_UNKNOWN,
MASTER_SLAVE_CFG_UNSUPPORTED, MASTER_SLAVE_CFG_SLAVE_PREFERRED cfg
Changes in v4:
- no changes
Changes in v5:
- move genphy_c45_an_setup_master_slave() to genphy_c45_config_aneg(),
as that C22 does.
Changes in v6:
- add colon in the function description
- add genphy_c45_read_master_slave in read function
Changes in v7:
- when phydev->link is down, just return UNKNOWN
- modify commit message
Changes in v8:
- no changes
---
drivers/net/phy/phy-c45.c | 103 ++++++++++++++++++++++++++++++++++++++
include/uapi/linux/mdio.h | 5 ++
2 files changed, 108 insertions(+)
diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c
index 60d044156a83..df682d3ebd5a 100644
--- a/drivers/net/phy/phy-c45.c
+++ b/drivers/net/phy/phy-c45.c
@@ -406,6 +406,97 @@ int genphy_c45_soft_reset(struct phy_device *phydev)
}
EXPORT_SYMBOL_GPL(genphy_c45_soft_reset);
+/**
+ * genphy_c45_an_setup_master_slave - Configure Master/Slave setting for C45 PHYs
+ * @phydev: target phy_device struct
+ *
+ * Description: Configure the forced or preferred Master/Slave role
+ * 10GBASE-T control register (MMD 7, Register 0x0020) according to
+ * IEEE 802.3 standards.
+ *
+ * Return: negative errno code on failure, 0 if Master/Slave didn't change,
+ * or 1 if Master/Slave modes changed.
+ */
+static int genphy_c45_an_setup_master_slave(struct phy_device *phydev)
+{
+ u16 ctl = 0;
+
+ switch (phydev->master_slave_set) {
+ case MASTER_SLAVE_CFG_MASTER_PREFERRED:
+ ctl = MDIO_AN_10GBT_CTRL_MS_PORT_TYPE;
+ break;
+ case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
+ break;
+ case MASTER_SLAVE_CFG_MASTER_FORCE:
+ ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE | MDIO_AN_10GBT_CTRL_MS_VALUE;
+ break;
+ case MASTER_SLAVE_CFG_SLAVE_FORCE:
+ ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE;
+ break;
+ case MASTER_SLAVE_CFG_UNKNOWN:
+ case MASTER_SLAVE_CFG_UNSUPPORTED:
+ return 0;
+ default:
+ phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+ return -EOPNOTSUPP;
+ }
+
+ return phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+ MDIO_AN_10GBT_CTRL_MS_ENABLE |
+ MDIO_AN_10GBT_CTRL_MS_VALUE |
+ MDIO_AN_10GBT_CTRL_MS_PORT_TYPE, ctl);
+}
+
+/**
+ * genphy_c45_read_master_slave - read master/slave status
+ * @phydev: target phy_device struct
+ *
+ * Description: Read the Master/Slave configuration and status
+ * from 10GBASE-T control/status registers (MMD 7, Reg 0x0020 and 0x0021).
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int genphy_c45_read_master_slave(struct phy_device *phydev)
+{
+ int val;
+
+ phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN;
+ phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_AN_10GBT_CTRL_MS_ENABLE) {
+ if (val & MDIO_AN_10GBT_CTRL_MS_VALUE)
+ phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE;
+ else
+ phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE;
+ } else {
+ if (val & MDIO_AN_10GBT_CTRL_MS_PORT_TYPE)
+ phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_PREFERRED;
+ else
+ phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_PREFERRED;
+ }
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_AN_10GBT_STAT_MS_FAULT) {
+ phydev->master_slave_state = MASTER_SLAVE_STATE_ERR;
+ } else if (phydev->link) {
+ if (val & MDIO_AN_10GBT_STAT_MS_RES)
+ phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
+ else
+ phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
+ } else {
+ phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN;
+ }
+
+ return 0;
+}
+
/**
* genphy_c45_aneg_done - return auto-negotiation complete status
* @phydev: target phy_device struct
@@ -1214,6 +1305,10 @@ int genphy_c45_read_status(struct phy_device *phydev)
ret = genphy_c45_baset1_read_status(phydev);
if (ret < 0)
return ret;
+ } else {
+ ret = genphy_c45_read_master_slave(phydev);
+ if (ret < 0)
+ return ret;
}
phy_resolve_aneg_linkmode(phydev);
@@ -1247,6 +1342,14 @@ int genphy_c45_config_aneg(struct phy_device *phydev)
if (ret > 0)
changed = true;
+ if (!genphy_c45_baset1_able(phydev)) {
+ ret = genphy_c45_an_setup_master_slave(phydev);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+ }
+
return genphy_c45_check_and_restart_aneg(phydev, changed);
}
EXPORT_SYMBOL_GPL(genphy_c45_config_aneg);
diff --git a/include/uapi/linux/mdio.h b/include/uapi/linux/mdio.h
index b2541c948fc1..06f4bc3c20c7 100644
--- a/include/uapi/linux/mdio.h
+++ b/include/uapi/linux/mdio.h
@@ -332,8 +332,13 @@
#define MDIO_AN_10GBT_CTRL_ADV2_5G 0x0080 /* Advertise 2.5GBASE-T */
#define MDIO_AN_10GBT_CTRL_ADV5G 0x0100 /* Advertise 5GBASE-T */
#define MDIO_AN_10GBT_CTRL_ADV10G 0x1000 /* Advertise 10GBASE-T */
+#define MDIO_AN_10GBT_CTRL_MS_ENABLE 0x8000 /* Master/slave manual config enable */
+#define MDIO_AN_10GBT_CTRL_MS_VALUE 0x4000 /* Master/slave config value (1=Master) */
+#define MDIO_AN_10GBT_CTRL_MS_PORT_TYPE 0x2000 /* Master Preferred Type */
/* AN 10GBASE-T status register. */
+#define MDIO_AN_10GBT_STAT_MS_FAULT 0x8000 /* Master/slave fault */
+#define MDIO_AN_10GBT_STAT_MS_RES 0x4000 /* Master/slave resolution (1=Master) */
#define MDIO_AN_10GBT_STAT_LP2_5G 0x0020 /* LP is 2.5GBT capable */
#define MDIO_AN_10GBT_STAT_LP5G 0x0040 /* LP is 5GBT capable */
#define MDIO_AN_10GBT_STAT_LPTRR 0x0200 /* LP training reset req. */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v8 3/4] net: phy: realtek: add support for RTL8261C_CG
From: javen @ 2026-07-02 3:20 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
freddy_gu, nb
Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260702032020.589-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
This patch adds support for Realtek phy chip RTL8261C_CG. Its PHY ID is
0x001cc898.
This patch introduces a distinct family of handlers (probe, get_features,
config_aneg, read_status, config_intr, handle_interrupt).
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- no changes, new file
Changes in v3:
- re-order function according to the order in phy-c45.c
- add kernel-doc about return value
- add MASTER_SLAVE_CFG_MASTER_PREFERRED,
MASTER_SLAVE_CFG_SLAVE_PREFERRED, MASTER_SLAVE_CFG_UNKNOWN,
MASTER_SLAVE_CFG_UNSUPPORTED, MASTER_SLAVE_CFG_SLAVE_PREFERRED cfg
Changes in v4:
- no changes
Changes in v5:
- remove genphy_c45_pma_setup_forced() for this is already done when
calling genphy_c45_config_aneg()
Changes in v6:
- when PHY_INTERRUPT_DISABLE, clear IMR and ISR
- if AUTONEG_DISABLE, nothing need to do in rtl8261x_config_aneg
- add rtl8261x_read_status, support 1G speed
Changes in v7:
- remove RTL8261X_IMR and RTL8261X_ISR, duplicated definition
- modify commit message
- continue with default behavior when meet unknown sub_phy_id
- change the internal order of rtl8261x_read_status
- expand RTL8261X_INT_MASK_DEFAULT
- add the handle for ADVERTISE_1000HALF in rtl8261x_config_aneg
Changes in v8:
- no changes
---
drivers/net/phy/realtek/realtek_main.c | 186 +++++++++++++++++++++++++
1 file changed, 186 insertions(+)
diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 27268811f564..ef3700894ebf 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -141,6 +141,10 @@
#define RTL8211F_PHYSICAL_ADDR_WORD1 17
#define RTL8211F_PHYSICAL_ADDR_WORD2 18
+#define RTL8261X_EXT_ADDR_REG 0xa436
+#define RTL8261X_EXT_DATA_REG 0xa438
+#define RTL_8261X_SUB_PHY_ID_ADDR 0x801d
+
#define RTL822X_VND1_SERDES_OPTION 0x697a
#define RTL822X_VND1_SERDES_OPTION_MODE_MASK GENMASK(5, 0)
#define RTL822X_VND1_SERDES_OPTION_MODE_2500BASEX_SGMII 0
@@ -251,6 +255,32 @@
#define RTL_8221B_VM_CG 0x001cc84a
#define RTL_8251B 0x001cc862
#define RTL_8261C 0x001cc890
+#define RTL_8261C_CG 0x001cc898
+
+#define RTL8261C_CE_MODEL 0x00
+#define RTL8261X_INT_AUTONEG_ERROR BIT(0)
+#define RTL8261X_INT_PAGE_RECV BIT(2)
+#define RTL8261X_INT_AUTONEG_DONE BIT(3)
+#define RTL8261X_INT_LINK_CHG BIT(4)
+#define RTL8261X_INT_PHY_REG_ACCESS BIT(5)
+#define RTL8261X_INT_PME BIT(7)
+#define RTL8261X_INT_ALDPS_CHG BIT(9)
+#define RTL8261X_INT_JABBER BIT(10)
+
+#define RTL8261X_INT_MASK_DEFAULT (RTL8261X_INT_AUTONEG_DONE | \
+ RTL8261X_INT_LINK_CHG | \
+ RTL8261X_INT_AUTONEG_ERROR | \
+ RTL8261X_INT_JABBER)
+
+#define RTL8261X_INT_MASK_ALL (RTL8261X_INT_AUTONEG_ERROR | \
+ RTL8261X_INT_PAGE_RECV | \
+ RTL8261X_INT_AUTONEG_DONE | \
+ RTL8261X_INT_LINK_CHG | \
+ RTL8261X_INT_PHY_REG_ACCESS | \
+ RTL8261X_INT_PME | \
+ RTL8261X_INT_ALDPS_CHG | \
+ RTL8261X_INT_JABBER)
+
/* RTL8211E and RTL8211F support up to three LEDs */
#define RTL8211x_LED_COUNT 3
@@ -310,6 +340,150 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page,
return phy_restore_page(phydev, oldpage, ret);
}
+static int rtl8261x_probe(struct phy_device *phydev)
+{
+ int sub_phy_id, ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG,
+ RTL_8261X_SUB_PHY_ID_ADDR);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_DATA_REG);
+ if (ret < 0)
+ return ret;
+
+ sub_phy_id = (ret >> 8) & 0xff;
+
+ switch (sub_phy_id) {
+ case RTL8261C_CE_MODEL:
+ phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id);
+ break;
+
+ default:
+ phydev_warn(phydev, "Unknown sub_id 0x%02x, default behavior\n", sub_phy_id);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int rtl8261x_get_features(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_c45_pma_read_abilities(phydev);
+ if (ret)
+ return ret;
+ /*
+ * Supplement Multi-Gig speeds that may not be automatically detected
+ * RTL8261X supports 2.5G/5G in addition to standard 10G
+ */
+ linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ phydev->supported);
+ linkmode_set_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT,
+ phydev->supported);
+
+ return 0;
+}
+
+static int rtl8261x_read_status(struct phy_device *phydev)
+{
+ int ret, val;
+
+ if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) {
+ val = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+ RTL822X_VND2_C22_REG(MII_STAT1000));
+ if (val < 0)
+ return val;
+
+ mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
+ }
+
+ ret = genphy_c45_read_status(phydev);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int rtl8261x_config_intr(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER,
+ RTL8261X_INT_MASK_DEFAULT);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INER, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static irqreturn_t rtl8261x_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read_mmd(phydev, MDIO_MMD_VEND2, RTL8221B_VND2_INSR);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & RTL8261X_INT_MASK_ALL))
+ return IRQ_NONE;
+
+ if (irq_status & (RTL8261X_INT_LINK_CHG | RTL8261X_INT_AUTONEG_DONE |
+ RTL8261X_INT_AUTONEG_ERROR | RTL8261X_INT_JABBER))
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
+}
+
+static int rtl8261x_config_aneg(struct phy_device *phydev)
+{
+ u16 adv_1g = 0;
+ int ret;
+
+ ret = genphy_c45_config_aneg(phydev);
+ if (ret < 0)
+ return ret;
+
+ if (phydev->autoneg == AUTONEG_DISABLE)
+ return 0;
+
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ phydev->advertising))
+ adv_1g = ADVERTISE_1000FULL;
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ phydev->advertising))
+ adv_1g |= ADVERTISE_1000HALF;
+
+ ret = phy_modify_mmd_changed(phydev, MDIO_MMD_VEND2,
+ RTL822X_VND2_C22_REG(MII_CTRL1000),
+ ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+ adv_1g);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ return genphy_c45_restart_aneg(phydev);
+
+ return 0;
+}
+
static int rtl821x_probe(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
@@ -3001,6 +3175,18 @@ static struct phy_driver realtek_drvs[] = {
.resume = genphy_resume,
.read_mmd = genphy_read_mmd_unsupported,
.write_mmd = genphy_write_mmd_unsupported,
+ }, {
+ PHY_ID_MATCH_EXACT(RTL_8261C_CG),
+ .name = "Realtek RTL8261C 10Gbps PHY",
+ .probe = rtl8261x_probe,
+ .get_features = rtl8261x_get_features,
+ .config_aneg = rtl8261x_config_aneg,
+ .read_status = rtl8261x_read_status,
+ .config_intr = rtl8261x_config_intr,
+ .handle_interrupt = rtl8261x_handle_interrupt,
+ .soft_reset = genphy_c45_soft_reset,
+ .suspend = genphy_c45_pma_suspend,
+ .resume = genphy_c45_pma_resume,
},
};
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v8 4/4] net: phy: realtek: load firmware for RTL8261C_CG
From: javen @ 2026-07-02 3:20 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
freddy_gu, nb
Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260702032020.589-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
This patch adds support for loading firmware. Download some parameters
for RTL8261C_CG.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- remove __pack, struct rtl8261x_fw_header and rtl8261x_fw_entry will not pad
- reverse xmas tree for some definition
- add explanation on rtl_phy_write_mmd_bits()
Changes in v3:
- add struct rtl8261x_priv
Changes in v4:
- add struct device *dev
Changes in v5:
- no changes
Changes in v6:
- replace rtl_phy_write_mmd_bits with phy_modify_mmd, keep mdio lock
- check msb and lsb at the beginning of rtl8261x_fw_execute_entry()
- add comments on rtl8261x_config_init()
Changes in v7:
- no changes
Changes in v8:
- remove some phydev_err message in rtl8261x_fw_execute_entry() and
rtl8261x_config_init()
---
drivers/net/phy/realtek/realtek_main.c | 214 +++++++++++++++++++++++++
1 file changed, 214 insertions(+)
diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index ef3700894ebf..10c8abfebe84 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -8,7 +8,9 @@
* Copyright (c) 2004 Freescale Semiconductor, Inc.
*/
#include <linux/bitops.h>
+#include <linux/crc32.h>
#include <linux/ethtool_netlink.h>
+#include <linux/firmware.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/pm_wakeirq.h>
@@ -281,6 +283,43 @@
RTL8261X_INT_ALDPS_CHG | \
RTL8261X_INT_JABBER)
+#define FW_MAIN_MAGIC 0x52544C38
+#define FW_SUB_MAGIC_8261C 0x32363143
+#define RTL8261X_POLL_TIMEOUT_MS 100
+#define RTL8261X_MAX_MMD_DEV 31
+
+#define RTL8261C_CE_FW_NAME "rtl_nic/rtl8261c.bin"
+MODULE_FIRMWARE(RTL8261C_CE_FW_NAME);
+
+enum rtl8261x_fw_op {
+ OP_WRITE = 0x00, /* Write */
+ OP_POLL = 0x02, /* Polling */
+};
+
+struct rtl8261x_fw_header {
+ __le32 main_magic; /* Main magic number */
+ __le32 sub_magic; /* Sub magic number */
+ __le16 version_major; /* Major version */
+ __le16 version_minor; /* Minor version */
+ __le16 num_entries; /* Number of entries */
+ __le16 reserved; /* Reserved */
+ __le32 crc32; /* CRC32 checksum */
+};
+
+struct rtl8261x_fw_entry {
+ __u8 type; /* Operation type (OP_*) */
+ __u8 dev; /* MMD device */
+ __le16 addr; /* Register address */
+ __u8 msb; /* MSB bit position */
+ __u8 lsb; /* LSB bit position */
+ __le16 value; /* Value to write/compare */
+ __le16 timeout_ms; /* Poll timeout in milliseconds */
+ __u8 poll_set; /* Poll until equal (1) or not equal (0) */
+ __u8 reserved; /* Reserved */
+};
+
+#define FW_HEADER_SIZE sizeof(struct rtl8261x_fw_header)
+#define FW_ENTRY_SIZE sizeof(struct rtl8261x_fw_entry)
/* RTL8211E and RTL8211F support up to three LEDs */
#define RTL8211x_LED_COUNT 3
@@ -300,6 +339,11 @@ struct rtl821x_priv {
u16 iner;
};
+struct rtl8261x_priv {
+ const char *fw_name;
+ bool fw_loaded;
+};
+
static int rtl821x_read_page(struct phy_device *phydev)
{
return __phy_read(phydev, RTL821x_PAGE_SELECT);
@@ -342,8 +386,16 @@ static int rtl821x_modify_ext_page(struct phy_device *phydev, u16 ext_page,
static int rtl8261x_probe(struct phy_device *phydev)
{
+ struct device *dev = &phydev->mdio.dev;
+ struct rtl8261x_priv *priv;
int sub_phy_id, ret;
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, RTL8261X_EXT_ADDR_REG,
RTL_8261X_SUB_PHY_ID_ADDR);
if (ret < 0)
@@ -357,6 +409,7 @@ static int rtl8261x_probe(struct phy_device *phydev)
switch (sub_phy_id) {
case RTL8261C_CE_MODEL:
+ priv->fw_name = RTL8261C_CE_FW_NAME;
phydev_info(phydev, "RTL8261C detected (sub_id 0x%02x)\n", sub_phy_id);
break;
@@ -407,6 +460,152 @@ static int rtl8261x_read_status(struct phy_device *phydev)
return 0;
}
+static int rtl8261x_verify_firmware(struct phy_device *phydev, const struct firmware *fw)
+{
+ const struct rtl8261x_fw_header *hdr;
+ u32 main_magic, sub_magic;
+ u32 calc_crc, file_crc;
+ size_t data_len;
+ u16 num_entries;
+
+ if (fw->size < FW_HEADER_SIZE) {
+ phydev_err(phydev, "Firmware too small: %zu bytes\n", fw->size);
+ return -EINVAL;
+ }
+
+ hdr = (const struct rtl8261x_fw_header *)fw->data;
+
+ main_magic = le32_to_cpu(hdr->main_magic);
+ if (main_magic != FW_MAIN_MAGIC) {
+ phydev_err(phydev, "Invalid firmware magic: 0x%08x\n", main_magic);
+ return -EINVAL;
+ }
+
+ sub_magic = le32_to_cpu(hdr->sub_magic);
+ if (sub_magic != FW_SUB_MAGIC_8261C) {
+ phydev_err(phydev, "Invalid sub magic: 0x%08x\n", sub_magic);
+ return -EINVAL;
+ }
+
+ num_entries = le16_to_cpu(hdr->num_entries);
+ data_len = num_entries * FW_ENTRY_SIZE;
+
+ if (fw->size != sizeof(*hdr) + data_len) {
+ phydev_err(phydev, "Firmware size mismatch\n");
+ return -EINVAL;
+ }
+
+ calc_crc = crc32(~0, fw->data + FW_HEADER_SIZE, data_len) ^ ~0;
+ file_crc = le32_to_cpu(hdr->crc32);
+
+ if (calc_crc != file_crc) {
+ phydev_err(phydev, "CRC32 mismatch: calculated=0x%08x file=0x%08x\n",
+ calc_crc, file_crc);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rtl8261x_fw_execute_entry(struct phy_device *phydev,
+ const struct rtl8261x_fw_entry *entry)
+{
+ u16 addr, value, timeout_ms;
+ u8 dev, msb, lsb, poll_set;
+ u32 bits, expect_val;
+ int ret, val;
+
+ dev = entry->dev;
+ addr = le16_to_cpu(entry->addr);
+ msb = entry->msb;
+ lsb = entry->lsb;
+ value = le16_to_cpu(entry->value);
+ timeout_ms = le16_to_cpu(entry->timeout_ms);
+ poll_set = entry->poll_set;
+
+ if (timeout_ms == 0)
+ timeout_ms = RTL8261X_POLL_TIMEOUT_MS;
+
+ if (dev > RTL8261X_MAX_MMD_DEV) {
+ phydev_err(phydev, "invalid firmware MMD device: dev=%u\n", dev);
+ return -EINVAL;
+ }
+
+ if (msb > 15 || lsb > msb) {
+ phydev_err(phydev, "invalid firmware bits: msb=%u, lsb=%u\n", msb, lsb);
+ return -EINVAL;
+ }
+
+ switch (entry->type) {
+ case OP_WRITE:
+ ret = phy_modify_mmd(phydev, dev, addr,
+ GENMASK(msb, lsb), (value << lsb) & GENMASK(msb, lsb));
+ if (ret)
+ return ret;
+ break;
+
+ case OP_POLL:
+ bits = GENMASK(msb, lsb);
+ expect_val = (value << lsb) & bits;
+
+ if (poll_set)
+ ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val,
+ (val & bits) == expect_val,
+ 1000, timeout_ms * 1000, false);
+ else
+ ret = phy_read_mmd_poll_timeout(phydev, dev, addr, val,
+ (val & bits) != expect_val,
+ 1000, timeout_ms * 1000, false);
+ if (ret)
+ return ret;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rtl8261x_fw_load(struct phy_device *phydev)
+{
+ struct rtl8261x_priv *priv = phydev->priv;
+ const struct rtl8261x_fw_entry *entry;
+ const struct rtl8261x_fw_header *hdr;
+ const struct firmware *fw;
+ int ret, i;
+
+ if (!priv->fw_name)
+ return 0;
+
+ ret = request_firmware(&fw, priv->fw_name, &phydev->mdio.dev);
+ if (ret) {
+ phydev_err(phydev, "Failed to load firmware %s: %d\n", priv->fw_name, ret);
+ return ret;
+ }
+
+ ret = rtl8261x_verify_firmware(phydev, fw);
+ if (ret)
+ goto release_fw;
+
+ hdr = (const struct rtl8261x_fw_header *)fw->data;
+
+ entry = (const struct rtl8261x_fw_entry *)(fw->data + FW_HEADER_SIZE);
+ for (i = 0; i < le16_to_cpu(hdr->num_entries); i++, entry++) {
+ ret = rtl8261x_fw_execute_entry(phydev, entry);
+ if (ret) {
+ phydev_err(phydev, "Entry %d failed: %d\n", i, ret);
+ goto release_fw;
+ }
+ }
+
+ priv->fw_loaded = true;
+
+release_fw:
+ release_firmware(fw);
+ return ret;
+}
+
static int rtl8261x_config_intr(struct phy_device *phydev)
{
int ret;
@@ -484,6 +683,20 @@ static int rtl8261x_config_aneg(struct phy_device *phydev)
return 0;
}
+static int rtl8261x_config_init(struct phy_device *phydev)
+{
+ struct rtl8261x_priv *priv = phydev->priv;
+
+ /* The firmware parameters are preserved across IEEE soft resets and
+ * suspend/resume cycles. Reloading is only necessary after a power
+ * cycle or hard reset.
+ */
+ if (priv->fw_name && !priv->fw_loaded)
+ return rtl8261x_fw_load(phydev);
+
+ return 0;
+}
+
static int rtl821x_probe(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
@@ -3179,6 +3392,7 @@ static struct phy_driver realtek_drvs[] = {
PHY_ID_MATCH_EXACT(RTL_8261C_CG),
.name = "Realtek RTL8261C 10Gbps PHY",
.probe = rtl8261x_probe,
+ .config_init = rtl8261x_config_init,
.get_features = rtl8261x_get_features,
.config_aneg = rtl8261x_config_aneg,
.read_status = rtl8261x_read_status,
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v8 1/4] net: phy: c45: add genphy_c45_soft_reset()
From: javen @ 2026-07-02 3:20 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
freddy_gu, nb
Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu
In-Reply-To: <20260702032020.589-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
Add a generic Clause 45 software reset helper. The helper sets the reset
bit in the PMA/PMD control register and waits until the bit is cleared by
hardware.
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- no changes, new file
Changes in v3:
- re-order function according to the order in phy-c45.c
Changes in v4:
- no changes
Changes in v5:
- no changes
Changes in v6:
- increase timeout to 600ms
Changes in v7:
- no changes
Changes in v8:
- no changes
---
drivers/net/phy/phy-c45.c | 22 ++++++++++++++++++++++
include/linux/phy.h | 1 +
2 files changed, 23 insertions(+)
diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c
index 126951741428..60d044156a83 100644
--- a/drivers/net/phy/phy-c45.c
+++ b/drivers/net/phy/phy-c45.c
@@ -384,6 +384,28 @@ int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart)
}
EXPORT_SYMBOL_GPL(genphy_c45_check_and_restart_aneg);
+/**
+ * genphy_c45_soft_reset - software reset the PHY via Clause 45 PMA/PMD control register
+ * @phydev: target phy_device struct
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int genphy_c45_soft_reset(struct phy_device *phydev)
+{
+ int ret, val;
+
+ ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1,
+ MDIO_CTRL1_RESET);
+ if (ret < 0)
+ return ret;
+
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD,
+ MDIO_CTRL1, val,
+ !(val & MDIO_CTRL1_RESET),
+ 5000, 600000, true);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_soft_reset);
+
/**
* genphy_c45_aneg_done - return auto-negotiation complete status
* @phydev: target phy_device struct
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 199a7aaa341b..25a66320df56 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -2309,6 +2309,7 @@ int genphy_c37_read_status(struct phy_device *phydev, bool *changed);
/* Clause 45 PHY */
int genphy_c45_restart_aneg(struct phy_device *phydev);
int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart);
+int genphy_c45_soft_reset(struct phy_device *phydev);
int genphy_c45_aneg_done(struct phy_device *phydev);
int genphy_c45_read_link(struct phy_device *phydev);
int genphy_c45_read_lpa(struct phy_device *phydev);
--
2.43.0
^ permalink raw reply related
* Re: [RFC] connectat()/bindat() or an alternative design
From: John Ericson @ 2026-07-02 3:22 UTC (permalink / raw)
To: Cong Wang
Cc: Li Chen, Andy Lutomirski, Christian Brauner, Jens Axboe,
network dev, linux-fsdevel, Sergei Zimmerman
In-Reply-To: <akWxrjOl4Up02Bvq@pop-os.localdomain>
On Wed, Jul 1, 2026, at 8:32 PM, Cong Wang wrote:
> Hm? Why not just setsockopt()? Something like:
>
> struct unix_lookup_ctx {
> int dirfd;
> __u64 resolve_flags; /* RESOLVE_BENEATH, RESOLVE_NO_SYMLINKS, etc. */
> __u64 op_flags; /* maybe future */
> };
>
> setsockopt(fd, SOL_UNIX, UNIX_NEXT_LOOKUP,
> &ctx, sizeof(ctx));
>
> bind(fd, (struct sockaddr *)&addr, addrlen);
> /* or connect(fd, ...) */
>
>
> Zero new syscall is needed.
If this supports the `AT_EMPTY_PATH` no filesystem at all use-case
(which is what I chose to highlight in the previous summary email), I'll
take it --- it hits my desiderata. But implementation-side, I don't
relish the thought of even more state for socket setup --- since we need
to persist the lookup context until the bind.
If setsockopt is the "ioctl of sockets" --- a convenient way to de facto
create syscalls without doing so de jure --- I would think it would be
better to just make it do the bind in one go so there is no state:
struct unix_bind_anon_ctx {
int *connect_fd; /* fd used to connect to this socket
written to this */
__u64 op_flags; /* future usage */
};
setsockopt(fd, SOL_UNIX, UNIX_BIND_ANON,
&ctx, sizeof(ctx));
Regular `linkat` can be used to place the "connect fd" in the filesystem
for regular file-based connecting (e.g. to cope with long paths), in
addition to how the "connect fd" can directly be used in the
`AT_EMPTY_PATH` case.
and for the connecting side:
struct unix_connect_at_ctx {
int dirfd;
const char *path;
__u64 resolve_flags; /* RESOLVE_BENEATH, RESOLVE_NO_SYMLINKS, etc. */
__u64 op_flags; /* SOMETHING_EMPTY_PATH, other future usage */
};
setsockopt(fd, SOL_UNIX, UNIX_CONNECT_AT,
&ctx, sizeof(ctx));
This is just what I had in my previous `summary email`, but with the new
system calls twisted into `setsockopt` variations instead. (I forgot to
mention the `linkat` usage in that email, oops, but it was always
intended to be part of the plan.)
Cheers,
John
^ permalink raw reply
* [PATCH v2 net-next 00/14] net: enetc: cleanups and improvements
From: wei.fang @ 2026-07-02 2:57 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, linux, wei.fang, chleroy
Cc: imx, netdev, linux-kernel, linuxppc-dev, linux-arm-kernel
From: Wei Fang <wei.fang@nxp.com>
The first group of patches (1, 2, 5-7) eliminates code duplication
between the ENETC v1 and v4 drivers. Since both hardware generations
share identical register layouts for MAC promiscuous mode, MAC hash
filters, and VLAN promiscuous mode - differing only in register address
offsets - common helper functions are extracted into enetc_pf_common.c
and shared by both drivers.
Patch 3 converts ndo_set_rx_mode() to ndo_set_rx_mode_async(), removing
the dedicated workqueue that was previously needed to defer MAC address
list updates to a sleepable context.
Patch 4 replaces counter-based MAFT entry tracking with a bitmap, which
keeps hardware and software state in sync and avoids partial failures
during entry allocation.
Patches 8 and 9 fix phylink-related issues: removing invalid code from
enetc4_pl_mac_link_up() and properly differentiating phylink capabilities
between pseudo-MAC and standalone MAC.
The remaining patches (10-14) are minor cleanups: removing a redundant
VLAN promiscuous mode initialization in probe, using the PCI device name
for the debugfs directory, simplifying port speed configuration, removing
a redundant num_vsi field, using alloc_etherdev_mqs() for the VF driver,
and using kzalloc_flex() for a flexible array allocation.
---
v2:
1. Modify the error log when enetc4_init_ntmp_user() fails
2. Only set PHY_INTERFACE_MODE_10GBASER and PHY_INTERFACE_MODE_XGMII for
ENETC v4, including MAC_5000FD and MAC_10000FD speeds
3. Improve the commit message of patch 13
v1 link: https://lore.kernel.org/imx/20260630072036.382761-1-wei.fang@oss.nxp.com/
---
Claudiu Manoil (1):
net: enetc: differentiate phylink capabilities for pseudo-MAC and
standalone MAC
Wei Fang (13):
net: enetc: extract common helpers for MAC promiscuous mode setting
net: enetc: extract common helpers for MAC hash filter configuration
net: enetc: convert ndo_set_rx_mode() to ndo_set_rx_mode_async()
net: enetc: improve MAFT entry management with bitmap tracking
net: enetc: use PCI device name for debugfs directory
net: enetc: simplify enetc4_set_port_speed()
net: enetc: remove invalid code from enetc4_pl_mac_link_up()
net: enetc: remove enetc4_set_default_si_vlan_promisc()
net: enetc: refactor SI VLAN promiscuous mode configuration
net: enetc: move enetc_set_si_vlan_promisc() to enetc_pf_common.c
net: enetc: remove redundant num_vsi field from enetc_port_caps
net: enetc: use alloc_etherdev_mqs() to create netdev for VF driver
net: enetc: use kzalloc_flex() for enetc_psfp_gate allocation
drivers/net/ethernet/freescale/enetc/enetc.h | 4 +-
.../ethernet/freescale/enetc/enetc4_debugfs.c | 42 +-
.../net/ethernet/freescale/enetc/enetc4_hw.h | 6 +-
.../net/ethernet/freescale/enetc/enetc4_pf.c | 412 +++++++-----------
.../ethernet/freescale/enetc/enetc_ethtool.c | 2 +-
.../net/ethernet/freescale/enetc/enetc_hw.h | 12 +-
.../net/ethernet/freescale/enetc/enetc_pf.c | 93 +---
.../net/ethernet/freescale/enetc/enetc_pf.h | 5 -
.../freescale/enetc/enetc_pf_common.c | 153 ++++++-
.../freescale/enetc/enetc_pf_common.h | 5 +
.../net/ethernet/freescale/enetc/enetc_qos.c | 4 +-
.../net/ethernet/freescale/enetc/enetc_vf.c | 3 +-
include/linux/fsl/ntmp.h | 2 +
13 files changed, 353 insertions(+), 390 deletions(-)
--
2.34.1
^ permalink raw reply
* [PATCH v2 net-next 01/14] net: enetc: extract common helpers for MAC promiscuous mode setting
From: wei.fang @ 2026-07-02 2:57 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, linux, wei.fang, chleroy
Cc: imx, netdev, linux-kernel, linuxppc-dev, linux-arm-kernel
In-Reply-To: <20260702025714.456233-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
The PSIPMMR (Port Station Interface Promiscuous MAC Mode Register) in
ENETC v4 has the same bit layout as the PSIPMR register in ENETC v1: bit
n controls unicast promiscuous mode for SI n, and bit (n + 16) controls
multicast promiscuous mode for SI n. The only difference between the two
hardware generations is the register address offset.
Since the register functionality is identical, the MAC promiscuous mode
setting code can be shared between ENETC v1 and v4 drivers.
Rename ENETC_PSIPMR to ENETC_PSIPMMR in enetc_hw.h to match the actual
register name used in the reference manual, and extract two new common
helper functions, enetc_set_si_uc_promisc() and
enetc_set_si_mc_promisc(), into enetc_pf_common.c. These helpers select
the correct register offset based on the hardware revision via
is_enetc_rev1().
Remove the v4-specific enetc4_pf_set_si_mac_promisc() function from
enetc4_pf.c and the duplicate PSIPMMR_SI_MAC_UP/MP macro definitions
from enetc4_hw.h, as they are now superseded by the shared code.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
.../net/ethernet/freescale/enetc/enetc4_hw.h | 2 -
.../net/ethernet/freescale/enetc/enetc4_pf.c | 21 +--------
.../ethernet/freescale/enetc/enetc_ethtool.c | 2 +-
.../net/ethernet/freescale/enetc/enetc_hw.h | 7 +--
.../net/ethernet/freescale/enetc/enetc_pf.c | 11 ++---
.../freescale/enetc/enetc_pf_common.c | 44 +++++++++++++++++++
.../freescale/enetc/enetc_pf_common.h | 2 +
7 files changed, 56 insertions(+), 33 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h
index f18437556a0e..6a8f2ed56017 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h
@@ -69,8 +69,6 @@
/* Port Station interface promiscuous MAC mode register */
#define ENETC4_PSIPMMR 0x200
-#define PSIPMMR_SI_MAC_UP(a) BIT(a) /* a = SI index */
-#define PSIPMMR_SI_MAC_MP(a) BIT((a) + 16)
/* Port Station interface promiscuous VLAN mode register */
#define ENETC4_PSIPVMR 0x204
diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
index 437a15bbb47b..304ec069654d 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
@@ -75,24 +75,6 @@ static void enetc4_pf_get_si_primary_mac(struct enetc_hw *hw, int si,
put_unaligned_le16(lower, addr + 4);
}
-static void enetc4_pf_set_si_mac_promisc(struct enetc_hw *hw, int si,
- bool uc_promisc, bool mc_promisc)
-{
- u32 val = enetc_port_rd(hw, ENETC4_PSIPMMR);
-
- if (uc_promisc)
- val |= PSIPMMR_SI_MAC_UP(si);
- else
- val &= ~PSIPMMR_SI_MAC_UP(si);
-
- if (mc_promisc)
- val |= PSIPMMR_SI_MAC_MP(si);
- else
- val &= ~PSIPMMR_SI_MAC_MP(si);
-
- enetc_port_wr(hw, ENETC4_PSIPMMR, val);
-}
-
static void enetc4_pf_set_si_uc_hash_filter(struct enetc_hw *hw, int si,
u64 hash)
{
@@ -515,7 +497,8 @@ static void enetc4_psi_do_set_rx_mode(struct work_struct *work)
type = ENETC_MAC_FILTER_TYPE_ALL;
}
- enetc4_pf_set_si_mac_promisc(hw, 0, uc_promisc, mc_promisc);
+ enetc_set_si_uc_promisc(si, 0, uc_promisc);
+ enetc_set_si_mc_promisc(si, 0, mc_promisc);
if (uc_promisc) {
enetc4_pf_set_si_uc_hash_filter(hw, 0, 0);
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c b/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c
index 71f376ef1be1..07b7832f2427 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c
@@ -29,7 +29,7 @@ static const u32 enetc_rxbdr_regs[] = {
};
static const u32 enetc_port_regs[] = {
- ENETC_PMR, ENETC_PSR, ENETC_PSIPMR, ENETC_PSIPMAR0(0),
+ ENETC_PMR, ENETC_PSR, ENETC_PSIPMMR, ENETC_PSIPMAR0(0),
ENETC_PSIPMAR1(0), ENETC_PTXMBAR, ENETC_PCAPR0, ENETC_PCAPR1,
ENETC_PSICFGR0(0), ENETC_PRFSCAPR, ENETC_PTCMSDUR(0),
ENETC_PM0_CMD_CFG, ENETC_PM0_MAXFRM, ENETC_PM0_IF_MODE
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_hw.h b/drivers/net/ethernet/freescale/enetc/enetc_hw.h
index bf99b65d7598..66bfda60da9c 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h
@@ -180,9 +180,10 @@ enum enetc_bdr_type {TX, RX};
#define ENETC_PMR_PSPEED_1000M BIT(9)
#define ENETC_PMR_PSPEED_2500M BIT(10)
#define ENETC_PSR 0x0004 /* RO */
-#define ENETC_PSIPMR 0x0018
-#define ENETC_PSIPMR_SET_UP(n) BIT(n) /* n = SI index */
-#define ENETC_PSIPMR_SET_MP(n) BIT((n) + 16)
+#define ENETC_PSIPMMR 0x0018
+#define PSIPMMR_SI_MAC_UP(n) BIT(n) /* n = SI index */
+#define PSIPMMR_SI_MAC_MP(n) BIT((n) + 16)
+
#define ENETC_PSIPVMR 0x001c
#define ENETC_VLAN_PROMISC_MAP_ALL 0x7
#define ENETC_PSIPVMR_SET_VP(simap) ((simap) & 0x7)
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
index 2d687bb8c3a0..a97d2e2dd07b 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
@@ -159,21 +159,17 @@ static void enetc_pf_set_rx_mode(struct net_device *ndev)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
struct enetc_pf *pf = enetc_si_priv(priv->si);
- struct enetc_hw *hw = &priv->si->hw;
bool uprom = false, mprom = false;
struct enetc_mac_filter *filter;
struct netdev_hw_addr *ha;
- u32 psipmr = 0;
bool em;
if (ndev->flags & IFF_PROMISC) {
/* enable promisc mode for SI0 (PF) */
- psipmr = ENETC_PSIPMR_SET_UP(0) | ENETC_PSIPMR_SET_MP(0);
uprom = true;
mprom = true;
} else if (ndev->flags & IFF_ALLMULTI) {
/* enable multi cast promisc mode for SI0 (PF) */
- psipmr = ENETC_PSIPMR_SET_MP(0);
mprom = true;
}
@@ -211,9 +207,8 @@ static void enetc_pf_set_rx_mode(struct net_device *ndev)
/* update PF entries */
enetc_sync_mac_filters(pf);
- psipmr |= enetc_port_rd(hw, ENETC_PSIPMR) &
- ~(ENETC_PSIPMR_SET_UP(0) | ENETC_PSIPMR_SET_MP(0));
- enetc_port_wr(hw, ENETC_PSIPMR, psipmr);
+ enetc_set_si_uc_promisc(priv->si, 0, uprom);
+ enetc_set_si_mc_promisc(priv->si, 0, mprom);
}
static void enetc_set_loopback(struct net_device *ndev, bool en)
@@ -474,7 +469,7 @@ static void enetc_configure_port(struct enetc_pf *pf)
pf->vlan_promisc_simap = ENETC_VLAN_PROMISC_MAP_ALL;
enetc_set_vlan_promisc(hw, pf->vlan_promisc_simap);
- enetc_port_wr(hw, ENETC_PSIPMR, 0);
+ enetc_port_wr(hw, ENETC_PSIPMMR, 0);
/* enable port */
enetc_port_wr(hw, ENETC_PMR, ENETC_PMR_EN);
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
index 6e5d2f869915..b0c0dc668e34 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
@@ -87,6 +87,50 @@ int enetc_setup_mac_addresses(struct device_node *np, struct enetc_pf *pf)
}
EXPORT_SYMBOL_GPL(enetc_setup_mac_addresses);
+void enetc_set_si_uc_promisc(struct enetc_si *si, int si_id, bool promisc)
+{
+ struct enetc_hw *hw = &si->hw;
+ int psipmmr_off;
+ u32 val;
+
+ if (is_enetc_rev1(si))
+ psipmmr_off = ENETC_PSIPMMR;
+ else
+ psipmmr_off = ENETC4_PSIPMMR;
+
+ val = enetc_port_rd(hw, psipmmr_off);
+
+ if (promisc)
+ val |= PSIPMMR_SI_MAC_UP(si_id);
+ else
+ val &= ~PSIPMMR_SI_MAC_UP(si_id);
+
+ enetc_port_wr(hw, psipmmr_off, val);
+}
+EXPORT_SYMBOL_GPL(enetc_set_si_uc_promisc);
+
+void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc)
+{
+ struct enetc_hw *hw = &si->hw;
+ int psipmmr_off;
+ u32 val;
+
+ if (is_enetc_rev1(si))
+ psipmmr_off = ENETC_PSIPMMR;
+ else
+ psipmmr_off = ENETC4_PSIPMMR;
+
+ val = enetc_port_rd(hw, psipmmr_off);
+
+ if (promisc)
+ val |= PSIPMMR_SI_MAC_MP(si_id);
+ else
+ val &= ~PSIPMMR_SI_MAC_MP(si_id);
+
+ enetc_port_wr(hw, psipmmr_off, val);
+}
+EXPORT_SYMBOL_GPL(enetc_set_si_mc_promisc);
+
void enetc_pf_netdev_setup(struct enetc_si *si, struct net_device *ndev,
const struct net_device_ops *ndev_ops)
{
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h
index 57d2e0ebd2b0..a619fb8fed9c 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h
@@ -17,6 +17,8 @@ void enetc_set_default_rss_key(struct enetc_pf *pf);
int enetc_vlan_rx_add_vid(struct net_device *ndev, __be16 prot, u16 vid);
int enetc_vlan_rx_del_vid(struct net_device *ndev, __be16 prot, u16 vid);
int enetc_init_sriov_resources(struct enetc_pf *pf);
+void enetc_set_si_uc_promisc(struct enetc_si *si, int si_id, bool promisc);
+void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc);
static inline u16 enetc_get_ip_revision(struct enetc_hw *hw)
{
--
2.34.1
^ permalink raw reply related
* [PATCH v2 net-next 02/14] net: enetc: extract common helpers for MAC hash filter configuration
From: wei.fang @ 2026-07-02 2:57 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, linux, wei.fang, chleroy
Cc: imx, netdev, linux-kernel, linuxppc-dev, linux-arm-kernel
In-Reply-To: <20260702025714.456233-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp.com>
The PSIUMHFR and PSIMMHFR registers in ENETC v4 have the same bit layout
as in ENETC v1. The only difference between the two hardware generations
is the register address offsets.
Since the register functionality is identical, the MAC hash filter
configuration code can be shared between the ENETC v1 and v4 drivers.
Extract two new common helper functions, enetc_set_si_uc_hash_filter()
and enetc_set_si_mc_hash_filter(), into enetc_pf_common.c. These helpers
select the correct register offset based on the hardware revision via
is_enetc_rev1().
Remove v1-specific enetc_clear_mac_ht_flt() and enetc_set_mac_ht_flt()
from enetc_pf.c, and v4-specific enetc4_pf_set_si_uc_hash_filter() and
enetc4_pf_set_si_mc_hash_filter() from enetc4_pf.c, as they are now
superseded by the shared implementations.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
.../net/ethernet/freescale/enetc/enetc4_pf.c | 43 +++++++---------
.../net/ethernet/freescale/enetc/enetc_pf.c | 51 +++++--------------
.../freescale/enetc/enetc_pf_common.c | 40 +++++++++++++++
.../freescale/enetc/enetc_pf_common.h | 2 +
4 files changed, 73 insertions(+), 63 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
index 304ec069654d..48a74db90ed5 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
@@ -75,20 +75,6 @@ static void enetc4_pf_get_si_primary_mac(struct enetc_hw *hw, int si,
put_unaligned_le16(lower, addr + 4);
}
-static void enetc4_pf_set_si_uc_hash_filter(struct enetc_hw *hw, int si,
- u64 hash)
-{
- enetc_port_wr(hw, ENETC4_PSIUMHFR0(si), lower_32_bits(hash));
- enetc_port_wr(hw, ENETC4_PSIUMHFR1(si), upper_32_bits(hash));
-}
-
-static void enetc4_pf_set_si_mc_hash_filter(struct enetc_hw *hw, int si,
- u64 hash)
-{
- enetc_port_wr(hw, ENETC4_PSIMMHFR0(si), lower_32_bits(hash));
- enetc_port_wr(hw, ENETC4_PSIMMHFR1(si), upper_32_bits(hash));
-}
-
static void enetc4_pf_set_loopback(struct net_device *ndev, bool en)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
@@ -147,11 +133,12 @@ static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf)
int max_num_mfe = pf->caps.mac_filter_num;
struct enetc_mac_filter mac_filter = {};
struct net_device *ndev = pf->si->ndev;
- struct enetc_hw *hw = &pf->si->hw;
struct enetc_mac_addr *mac_tbl;
+ struct enetc_si *si = pf->si;
struct netdev_hw_addr *ha;
int i = 0, err;
int mac_cnt;
+ u64 hash;
netif_addr_lock_bh(ndev);
@@ -159,7 +146,7 @@ static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf)
if (!mac_cnt) {
netif_addr_unlock_bh(ndev);
/* clear both MAC hash and exact filters */
- enetc4_pf_set_si_uc_hash_filter(hw, 0, 0);
+ enetc_set_si_uc_hash_filter(si, 0, 0);
enetc4_pf_clear_maft_entries(pf);
return 0;
@@ -186,11 +173,13 @@ static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf)
/* Set temporary unicast hash filters in case of Rx loss when
* updating MAC address filter table
*/
- enetc4_pf_set_si_uc_hash_filter(hw, 0, *mac_filter.mac_hash_table);
+ bitmap_to_arr64(&hash, mac_filter.mac_hash_table,
+ ENETC_MADDR_HASH_TBL_SZ);
+ enetc_set_si_uc_hash_filter(si, 0, hash);
enetc4_pf_clear_maft_entries(pf);
if (!enetc4_pf_add_maft_entries(pf, mac_tbl, i))
- enetc4_pf_set_si_uc_hash_filter(hw, 0, 0);
+ enetc_set_si_uc_hash_filter(si, 0, 0);
kfree(mac_tbl);
@@ -206,8 +195,9 @@ static void enetc4_pf_set_mac_hash_filter(struct enetc_pf *pf, int type)
{
struct net_device *ndev = pf->si->ndev;
struct enetc_mac_filter *mac_filter;
- struct enetc_hw *hw = &pf->si->hw;
+ struct enetc_si *si = pf->si;
struct netdev_hw_addr *ha;
+ u64 hash;
netif_addr_lock_bh(ndev);
if (type & ENETC_MAC_FILTER_TYPE_UC) {
@@ -216,8 +206,9 @@ static void enetc4_pf_set_mac_hash_filter(struct enetc_pf *pf, int type)
netdev_for_each_uc_addr(ha, ndev)
enetc_add_mac_addr_ht_filter(mac_filter, ha->addr);
- enetc4_pf_set_si_uc_hash_filter(hw, 0,
- *mac_filter->mac_hash_table);
+ bitmap_to_arr64(&hash, mac_filter->mac_hash_table,
+ ENETC_MADDR_HASH_TBL_SZ);
+ enetc_set_si_uc_hash_filter(si, 0, hash);
}
if (type & ENETC_MAC_FILTER_TYPE_MC) {
@@ -226,8 +217,9 @@ static void enetc4_pf_set_mac_hash_filter(struct enetc_pf *pf, int type)
netdev_for_each_mc_addr(ha, ndev)
enetc_add_mac_addr_ht_filter(mac_filter, ha->addr);
- enetc4_pf_set_si_mc_hash_filter(hw, 0,
- *mac_filter->mac_hash_table);
+ bitmap_to_arr64(&hash, mac_filter->mac_hash_table,
+ ENETC_MADDR_HASH_TBL_SZ);
+ enetc_set_si_mc_hash_filter(si, 0, hash);
}
netif_addr_unlock_bh(ndev);
}
@@ -480,7 +472,6 @@ static void enetc4_psi_do_set_rx_mode(struct work_struct *work)
struct enetc_si *si = container_of(work, struct enetc_si, rx_mode_task);
struct enetc_pf *pf = enetc_si_priv(si);
struct net_device *ndev = si->ndev;
- struct enetc_hw *hw = &si->hw;
bool uc_promisc = false;
bool mc_promisc = false;
int type = 0;
@@ -501,12 +492,12 @@ static void enetc4_psi_do_set_rx_mode(struct work_struct *work)
enetc_set_si_mc_promisc(si, 0, mc_promisc);
if (uc_promisc) {
- enetc4_pf_set_si_uc_hash_filter(hw, 0, 0);
+ enetc_set_si_uc_hash_filter(si, 0, 0);
enetc4_pf_clear_maft_entries(pf);
}
if (mc_promisc)
- enetc4_pf_set_si_mc_hash_filter(hw, 0, 0);
+ enetc_set_si_mc_hash_filter(si, 0, 0);
/* Set new MAC filter */
enetc4_pf_set_mac_filter(pf, type);
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
index a97d2e2dd07b..db2a800a7aaf 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c
@@ -80,37 +80,6 @@ static void enetc_add_mac_addr_em_filter(struct enetc_mac_filter *filter,
filter->mac_addr_cnt++;
}
-static void enetc_clear_mac_ht_flt(struct enetc_si *si, int si_idx, int type)
-{
- bool err = si->errata & ENETC_ERR_UCMCSWP;
-
- if (type == UC) {
- enetc_port_wr(&si->hw, ENETC_PSIUMHFR0(si_idx, err), 0);
- enetc_port_wr(&si->hw, ENETC_PSIUMHFR1(si_idx), 0);
- } else { /* MC */
- enetc_port_wr(&si->hw, ENETC_PSIMMHFR0(si_idx, err), 0);
- enetc_port_wr(&si->hw, ENETC_PSIMMHFR1(si_idx), 0);
- }
-}
-
-static void enetc_set_mac_ht_flt(struct enetc_si *si, int si_idx, int type,
- unsigned long hash)
-{
- bool err = si->errata & ENETC_ERR_UCMCSWP;
-
- if (type == UC) {
- enetc_port_wr(&si->hw, ENETC_PSIUMHFR0(si_idx, err),
- lower_32_bits(hash));
- enetc_port_wr(&si->hw, ENETC_PSIUMHFR1(si_idx),
- upper_32_bits(hash));
- } else { /* MC */
- enetc_port_wr(&si->hw, ENETC_PSIMMHFR0(si_idx, err),
- lower_32_bits(hash));
- enetc_port_wr(&si->hw, ENETC_PSIMMHFR1(si_idx),
- upper_32_bits(hash));
- }
-}
-
static void enetc_sync_mac_filters(struct enetc_pf *pf)
{
struct enetc_mac_filter *f = pf->mac_filter;
@@ -122,12 +91,16 @@ static void enetc_sync_mac_filters(struct enetc_pf *pf)
for (i = 0; i < MADDR_TYPE; i++, f++) {
bool em = (f->mac_addr_cnt == 1) && (i == UC);
bool clear = !f->mac_addr_cnt;
+ u64 hash;
if (clear) {
- if (i == UC)
+ if (i == UC) {
enetc_clear_mac_flt_entry(si, pos);
+ enetc_set_si_uc_hash_filter(si, 0, 0);
+ } else {
+ enetc_set_si_mc_hash_filter(si, 0, 0);
+ }
- enetc_clear_mac_ht_flt(si, 0, i);
continue;
}
@@ -135,7 +108,7 @@ static void enetc_sync_mac_filters(struct enetc_pf *pf)
if (em) {
int err;
- enetc_clear_mac_ht_flt(si, 0, UC);
+ enetc_set_si_uc_hash_filter(si, 0, 0);
err = enetc_set_mac_flt_entry(si, pos, f->mac_addr,
BIT(0));
@@ -147,11 +120,15 @@ static void enetc_sync_mac_filters(struct enetc_pf *pf)
err);
}
+ bitmap_to_arr64(&hash, f->mac_hash_table,
+ ENETC_MADDR_HASH_TBL_SZ);
/* hash table filter, clear EM filter for UC entries */
- if (i == UC)
+ if (i == UC) {
enetc_clear_mac_flt_entry(si, pos);
-
- enetc_set_mac_ht_flt(si, 0, i, *f->mac_hash_table);
+ enetc_set_si_uc_hash_filter(si, 0, hash);
+ } else {
+ enetc_set_si_mc_hash_filter(si, 0, hash);
+ }
}
}
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
index b0c0dc668e34..3597cb81a7cc 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
@@ -131,6 +131,46 @@ void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc)
}
EXPORT_SYMBOL_GPL(enetc_set_si_mc_promisc);
+void enetc_set_si_uc_hash_filter(struct enetc_si *si, int si_id, u64 hash)
+{
+ int psiumhfr0_off, psiumhfr1_off;
+ struct enetc_hw *hw = &si->hw;
+
+ if (is_enetc_rev1(si)) {
+ bool err = si->errata & ENETC_ERR_UCMCSWP;
+
+ psiumhfr0_off = ENETC_PSIUMHFR0(si_id, err);
+ psiumhfr1_off = ENETC_PSIUMHFR1(si_id);
+ } else {
+ psiumhfr0_off = ENETC4_PSIUMHFR0(si_id);
+ psiumhfr1_off = ENETC4_PSIUMHFR1(si_id);
+ }
+
+ enetc_port_wr(hw, psiumhfr0_off, lower_32_bits(hash));
+ enetc_port_wr(hw, psiumhfr1_off, upper_32_bits(hash));
+}
+EXPORT_SYMBOL_GPL(enetc_set_si_uc_hash_filter);
+
+void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash)
+{
+ int psimmhfr0_off, psimmhfr1_off;
+ struct enetc_hw *hw = &si->hw;
+
+ if (is_enetc_rev1(si)) {
+ bool err = si->errata & ENETC_ERR_UCMCSWP;
+
+ psimmhfr0_off = ENETC_PSIMMHFR0(si_id, err);
+ psimmhfr1_off = ENETC_PSIMMHFR1(si_id);
+ } else {
+ psimmhfr0_off = ENETC4_PSIMMHFR0(si_id);
+ psimmhfr1_off = ENETC4_PSIMMHFR1(si_id);
+ }
+
+ enetc_port_wr(hw, psimmhfr0_off, lower_32_bits(hash));
+ enetc_port_wr(hw, psimmhfr1_off, upper_32_bits(hash));
+}
+EXPORT_SYMBOL_GPL(enetc_set_si_mc_hash_filter);
+
void enetc_pf_netdev_setup(struct enetc_si *si, struct net_device *ndev,
const struct net_device_ops *ndev_ops)
{
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h
index a619fb8fed9c..bf9029b0a017 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h
@@ -19,6 +19,8 @@ int enetc_vlan_rx_del_vid(struct net_device *ndev, __be16 prot, u16 vid);
int enetc_init_sriov_resources(struct enetc_pf *pf);
void enetc_set_si_uc_promisc(struct enetc_si *si, int si_id, bool promisc);
void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc);
+void enetc_set_si_uc_hash_filter(struct enetc_si *si, int si_id, u64 hash);
+void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash);
static inline u16 enetc_get_ip_revision(struct enetc_hw *hw)
{
--
2.34.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox