* [PATCH net-next] vsock/virtio: remove unnecessary call to `virtio_transport_get_ops`
From: Luigi Leonardi @ 2026-04-07 14:31 UTC (permalink / raw)
To: Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Stefan Hajnoczi, Stefano Garzarella, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Arseniy Krasnov
Cc: kvm, virtualization, netdev, linux-kernel, Luigi Leonardi
`virtio_transport_send_pkt_info` gets all the transport information
from the parameter `t_ops`. There is no need to call
`virtio_transport_get_ops()`.
Remove it.
Fixes: 581512a6dc93 ("vsock/virtio: MSG_ZEROCOPY flag support")
Signed-off-by: Luigi Leonardi <leonardi@redhat.com>
---
I marked this as net-next material, but honsetly I'm not sure if I
should have targeted net. It's not a bug after all, it's just a cleanup.
---
net/vmw_vsock/virtio_transport_common.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 8a9fb23c6e85..a152a9e208d0 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -60,8 +60,6 @@ static bool virtio_transport_can_zcopy(const struct virtio_transport *t_ops,
return false;
/* Check that transport can send data in zerocopy mode. */
- t_ops = virtio_transport_get_ops(info->vsk);
-
if (t_ops->can_msgzerocopy) {
int pages_to_send = iov_iter_npages(iov_iter, MAX_SKB_FRAGS);
---
base-commit: bfe62a454542cfad3379f6ef5680b125f41e20f4
change-id: 20260407-remove_parameter-f61a3e40cf90
Best regards,
--
Luigi Leonardi <leonardi@redhat.com>
^ permalink raw reply related
* Re: [RFC bpf] Ingress RX queue provenance across cpumap redirect
From: Alexei Starovoitov @ 2026-04-07 14:47 UTC (permalink / raw)
To: Adith-Joshua
Cc: bpf, Network Development, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Jakub Kicinski, Jesper Dangaard Brouer,
John Fastabend
In-Reply-To: <20260407060203.3391-1-adithalex29@gmail.com>
On Mon, Apr 6, 2026 at 11:02 PM Adith-Joshua <adithalex29@gmail.com> wrote:
>
>
> ## Follow-up
>
> If this aligns with intended design, I would be happy to explore a concrete
> proposal or implementation.
No "follow up" please.
STOP sending AI slop to the mailing list.
^ permalink raw reply
* [PATCH 5/8] net: define IPPROTO_SMBDIRECT and SOL_SMBDIRECT constants
From: Stefan Metzmacher @ 2026-04-07 14:46 UTC (permalink / raw)
To: linux-cifs, samba-technical
Cc: metze, Steve French, Tom Talpey, Long Li, Namjae Jeon,
David Howells, Henrique Carvalho, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
Willem de Bruijn, netdev, Xin Long, quic, linux-rdma,
linux-kernel
In-Reply-To: <cover.1775571957.git.metze@samba.org>
This patch adds IPPROTO_SMBDIRECT and SOL_SMBDIRECT constants to the
networking subsystem. These definitions are essential for applications
to set socket options and protocol identifiers related to the SMBDIRECT
protocol, defined in [MS-SMBD] by Microsoft. It is used as wrapper
around RDMA in order to provide a transport for SMB3, but Microsoft also
uses it as transport for other protocols.
SMBDIRECT works over Infiniband, RoCE and iWarp.
RoCEv2 is based on IP/UDP and iWarp is based on IP/TCP,
so these use IP addresses natively.
Infiniband and RoCEv1 require IPOIB in order to be used for
SMBDIRECT.
So instead of adding a PF_SMBDIRECT, which would only use AF_INET[6],
we use IPPROTO_SMBDIRECT instead, this uses a number not
allocated from IANA, as it would not appear in an IP header.
This is similar to IPPROTO_SMC, IPPROTO_MPTCP and IPPROTO_QUIC,
which are linux specific values for the socket() syscall.
socket(AF_INET, SOCK_STREAM, IPPROTO_SMBDIRECT);
socket(AF_INET6, SOCK_STREAM, IPPROTO_SMBDIRECT);
This will allow the existing smbdirect code used by
cifs.ko and ksmbd.ko to be moved behind the socket layer,
so that there's less special handling. Only sock_sendmsg()
sock_recvmsg() are used, so that the main stream handling
is done all the same for tcp, smbdirect and later also quic.
The special RDMA read/write handling will be via direct
function calls as they are currently done for the in kernel
consumers.
As a start __sock_create(kern=0)/sk->sk_kern_sock == 0 will
still cause a -EPROTONOSUPPORT. So only in kernel consumers
will be supported for now.
Once I have developed a stable interface for the RDMA read/write
handling using sendmsg/recvmsg with MSG_OOB and msg_control,
it will also exposed to userspace in order to allow Samba to
use it.
As the numbers of IPPROTO_QUIC (261) and SOL_QUIC (288) [1]
are already used in various (released) userspace applications,
I used 289 for SOL_SMBDIRECT instead of 288.
[1]
https://lore.kernel.org/quic/0cb58f6fcf35ac988660e42704dae9960744a0a7.1763994509.git.lucien.xin@gmail.com/T/#u
Cc: Steve French <smfrench@gmail.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Long Li <longli@microsoft.com>
Cc: Namjae Jeon <linkinjeon@kernel.org>
Cc: David Howells <dhowells@redhat.com>
Cc: Henrique Carvalho <henrique.carvalho@suse.com>
Cc: linux-cifs@vger.kernel.org
Cc: samba-technical@lists.samba.org
Cc: David S. Miller <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>
Cc: Kuniyuki Iwashima <kuniyu@google.com>
Cc: Willem de Bruijn <willemb@google.com>
Cc: netdev@vger.kernel.org
Cc: Xin Long <lucien.xin@gmail.com>
Cc: quic@lists.linux.dev
Cc: linux-rdma@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
---
include/linux/socket.h | 2 ++
include/uapi/linux/in.h | 2 ++
2 files changed, 4 insertions(+)
diff --git a/include/linux/socket.h b/include/linux/socket.h
index ec715ad4bf25..e00cbfdaa8d6 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -401,6 +401,8 @@ struct ucred {
#define SOL_MCTP 285
#define SOL_SMC 286
#define SOL_VSOCK 287
+/* 288 reserved for SOL_QUIC */
+#define SOL_SMBDIRECT 289
/* IPX options */
#define IPX_TYPE 1
diff --git a/include/uapi/linux/in.h b/include/uapi/linux/in.h
index ced0fc3c3aa5..933d243b1780 100644
--- a/include/uapi/linux/in.h
+++ b/include/uapi/linux/in.h
@@ -85,6 +85,8 @@ enum {
#define IPPROTO_RAW IPPROTO_RAW
IPPROTO_SMC = 256, /* Shared Memory Communications */
#define IPPROTO_SMC IPPROTO_SMC
+ IPPROTO_SMBDIRECT = 257, /* RDMA based transport (mostly used by SMB3) */
+#define IPPROTO_SMBDIRECT IPPROTO_SMBDIRECT
IPPROTO_MPTCP = 262, /* Multipath TCP connection */
#define IPPROTO_MPTCP IPPROTO_MPTCP
IPPROTO_MAX
--
2.43.0
^ permalink raw reply related
* [PATCH 6/8] smb: smbdirect: add in kernel only support for IPPROTO_SMBDIRECT
From: Stefan Metzmacher @ 2026-04-07 14:46 UTC (permalink / raw)
To: linux-cifs, samba-technical
Cc: metze, Steve French, Tom Talpey, Long Li, Namjae Jeon,
David Howells, Henrique Carvalho, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
Willem de Bruijn, netdev, Xin Long, quic, linux-rdma,
linux-kernel
In-Reply-To: <cover.1775571957.git.metze@samba.org>
For userspace callers of socket() still get -EPROTONOSUPPORT,
so we are sure we'll only have in kernel callers, cifs.ko and
ksmbd.ko, for now. This makes it possible to relax the
constrains generic stream socket consumers would otherwise
assume.
There's a prototype for userspace sockets on top of
this and there's working userspace code for Samba as
client and server, so this is just the first step,
but a very important one.
The SMBDIRECT protocol is defined in [MS-SMBD] by Microsoft.
It is used as wrapper around RDMA in order to provide a transport for SMB3,
but Microsoft also uses it as transport for other protocols.
SMBDIRECT works over Infiniband, RoCE and iWarp.
RoCEv2 is based on IP/UDP and iWarp is based on IP/TCP,
so these use IP addresses natively.
Infiniband and RoCEv1 require IPOIB in order to be used for
SMBDIRECT.
So instead of adding a PF_SMBDIRECT, which would only use AF_INET[6],
we use IPPROTO_SMBDIRECT instead, this uses a number not
allocated from IANA, as it would not appear in an IP header.
This is similar to IPPROTO_SMC, IPPROTO_MPTCP and IPPROTO_QUIC,
which are linux specific values for the socket() syscall.
socket(AF_INET, SOCK_STREAM, IPPROTO_SMBDIRECT);
socket(AF_INET6, SOCK_STREAM, IPPROTO_SMBDIRECT);
This will allow the existing smbdirect code used by
cifs.ko and ksmbd.ko to be moved behind the socket layer,
so that there's less special handling. Only sock_sendmsg()
sock_recvmsg() are used, so that the main stream handling
is done all the same for tcp, smbdirect and later also quic.
The special RDMA read/write handling will be via direct
function calls as they are currently done for the in kernel
consumers.
For now the core smbdirect code still supports both
modes, direct calls in indirect via the socket layer.
The core code uses if (sc->sk.sk_family) as indication
for the new socket mode. Once cifs.ko and ksmbd.ko
are converted we can remove the old mode slowly,
but I'll deferr that to a future patchset.
There's still a way to go in order to make this
as generic as tcp and quic e.g. adding MSG_SPLICE_PAGES support or
splice_read/read_sock/read_skb.
But it's a good start, which will make changes
much easier.
Cc: Steve French <smfrench@gmail.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Long Li <longli@microsoft.com>
Cc: Namjae Jeon <linkinjeon@kernel.org>
Cc: David Howells <dhowells@redhat.com>
Cc: Henrique Carvalho <henrique.carvalho@suse.com>
Cc: linux-cifs@vger.kernel.org
Cc: samba-technical@lists.samba.org
Cc: David S. Miller <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>
Cc: Kuniyuki Iwashima <kuniyu@google.com>
Cc: Willem de Bruijn <willemb@google.com>
Cc: netdev@vger.kernel.org
Cc: Xin Long <lucien.xin@gmail.com>
Cc: quic@lists.linux.dev
Cc: linux-rdma@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
---
fs/smb/common/smbdirect/Makefile | 1 +
fs/smb/common/smbdirect/smbdirect.h | 62 +
fs/smb/common/smbdirect/smbdirect_accept.c | 14 +-
.../common/smbdirect/smbdirect_connection.c | 58 +
fs/smb/common/smbdirect/smbdirect_devices.c | 2 +-
fs/smb/common/smbdirect/smbdirect_internal.h | 59 +-
fs/smb/common/smbdirect/smbdirect_listen.c | 49 +-
fs/smb/common/smbdirect/smbdirect_main.c | 45 +
fs/smb/common/smbdirect/smbdirect_mr.c | 10 +
fs/smb/common/smbdirect/smbdirect_proto.c | 1549 +++++++++++++++++
fs/smb/common/smbdirect/smbdirect_public.h | 3 +
fs/smb/common/smbdirect/smbdirect_rw.c | 29 +-
fs/smb/common/smbdirect/smbdirect_socket.c | 147 ++
fs/smb/common/smbdirect/smbdirect_socket.h | 26 +-
14 files changed, 2039 insertions(+), 15 deletions(-)
create mode 100644 fs/smb/common/smbdirect/smbdirect_proto.c
diff --git a/fs/smb/common/smbdirect/Makefile b/fs/smb/common/smbdirect/Makefile
index 423f533e1002..fcff485d7c45 100644
--- a/fs/smb/common/smbdirect/Makefile
+++ b/fs/smb/common/smbdirect/Makefile
@@ -10,6 +10,7 @@ smbdirect-y := \
smbdirect_connection.o \
smbdirect_mr.o \
smbdirect_rw.o \
+ smbdirect_proto.o \
smbdirect_debug.o \
smbdirect_connect.o \
smbdirect_listen.o \
diff --git a/fs/smb/common/smbdirect/smbdirect.h b/fs/smb/common/smbdirect/smbdirect.h
index bbab5f7f7cc9..cf3d4957f94c 100644
--- a/fs/smb/common/smbdirect/smbdirect.h
+++ b/fs/smb/common/smbdirect/smbdirect.h
@@ -6,7 +6,10 @@
#ifndef __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__
#define __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__
+#include <linux/stddef.h>
#include <linux/types.h>
+#include <linux/socket.h>
+#include <asm/ioctls.h>
/* SMB-DIRECT buffer descriptor V1 structure [MS-SMBD] 2.2.3.1 */
struct smbdirect_buffer_descriptor_v1 {
@@ -49,4 +52,63 @@ struct smbdirect_socket_parameters {
SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB | \
SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW)
+enum {
+ __SMBDIRECT_BUFFER_REMOTE_INVALIDATE = 0x20,
+};
+
+struct smbdirect_cmsg_buffer {
+ uint8_t msg_control[CMSG_SPACE(24)];
+};
+
+static __always_inline
+void __smbdirect_cmsg_prepare(struct msghdr *msg,
+ struct smbdirect_cmsg_buffer *cbuffer,
+ int cmsg_type,
+ const void *payload,
+ size_t payloadlen)
+{
+ size_t cmsg_space = CMSG_SPACE(payloadlen);
+ size_t cmsg_len = CMSG_LEN(payloadlen);
+ struct cmsghdr *cmsg = NULL;
+ void *dataptr = NULL;
+
+ BUILD_BUG_ON(cmsg_space > sizeof(cbuffer->msg_control));
+
+ memset(cbuffer, 0, sizeof(*cbuffer));
+
+ msg->msg_control = cbuffer->msg_control;
+ msg->msg_controllen = cmsg_space;
+
+ cmsg = CMSG_FIRSTHDR(msg);
+ cmsg->cmsg_level = SOL_SMBDIRECT;
+ cmsg->cmsg_type = cmsg_type;
+ cmsg->cmsg_len = cmsg_len;
+ dataptr = CMSG_DATA(cmsg);
+ memcpy(dataptr, payload, payloadlen);
+ msg->msg_controllen = cmsg->cmsg_len;
+}
+
+struct smbdirect_buffer_remote_invalidate_args {
+ __u32 remote_token;
+} __packed;
+#define SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE \
+ _IOW('S', __SMBDIRECT_BUFFER_REMOTE_INVALIDATE, \
+ struct smbdirect_buffer_remote_invalidate_args)
+
+static __always_inline
+void smbdirect_buffer_remote_invalidate_cmsg_prepare(struct msghdr *msg,
+ struct smbdirect_cmsg_buffer *cbuffer,
+ const __u32 *remote_token)
+{
+ if (remote_token) {
+ struct smbdirect_buffer_remote_invalidate_args args = {
+ .remote_token = *remote_token,
+ };
+
+ __smbdirect_cmsg_prepare(msg, cbuffer,
+ SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE,
+ &args, sizeof(args));
+ }
+}
+
#endif /* __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__ */
diff --git a/fs/smb/common/smbdirect/smbdirect_accept.c b/fs/smb/common/smbdirect/smbdirect_accept.c
index d6d5e6a3f5de..6d7d869cdbc3 100644
--- a/fs/smb/common/smbdirect/smbdirect_accept.c
+++ b/fs/smb/common/smbdirect/smbdirect_accept.c
@@ -6,7 +6,6 @@
*/
#include "smbdirect_internal.h"
-#include <net/sock.h>
#include "../../common/smb2status.h"
static int smbdirect_accept_rdma_event_handler(struct rdma_cm_id *id,
@@ -460,6 +459,12 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work)
spin_lock_irqsave(&lsc->listen.lock, flags);
list_del(&sc->accept.list);
list_add_tail(&sc->accept.list, &lsc->listen.ready);
+ if (lsc->sk.sk_family) {
+ struct sock *lsk = &lsc->sk;
+
+ if (!sock_flag(lsk, SOCK_DEAD) && lsk->sk_socket)
+ lsk->sk_data_ready(lsk);
+ }
wake_up(&lsc->listen.wait_queue);
spin_unlock_irqrestore(&lsc->listen.lock, flags);
@@ -774,11 +779,13 @@ static long smbdirect_socket_wait_for_accept(struct smbdirect_socket *lsc, long
{
long ret;
+ smbdirect_socket_sk_unlock(lsc);
ret = wait_event_interruptible_timeout(lsc->listen.wait_queue,
!list_empty_careful(&lsc->listen.ready) ||
lsc->status != SMBDIRECT_SOCKET_LISTENING ||
lsc->first_error,
timeo);
+ smbdirect_socket_sk_lock(lsc);
if (lsc->status != SMBDIRECT_SOCKET_LISTENING)
return -EINVAL;
if (lsc->first_error)
@@ -850,6 +857,11 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc,
* order to grant credits to the peer.
*/
nsc->status = SMBDIRECT_SOCKET_CONNECTED;
+ if (nsc->sk.sk_family) {
+ struct sock *nsk = &nsc->sk;
+
+ inet_sk_set_state(nsk, TCP_ESTABLISHED);
+ }
smbdirect_accept_negotiate_finish(nsc, 0);
return nsc;
diff --git a/fs/smb/common/smbdirect/smbdirect_connection.c b/fs/smb/common/smbdirect/smbdirect_connection.c
index 1e946f78e935..2c426aefd16d 100644
--- a/fs/smb/common/smbdirect/smbdirect_connection.c
+++ b/fs/smb/common/smbdirect/smbdirect_connection.c
@@ -153,6 +153,15 @@ void smbdirect_connection_rdma_established(struct smbdirect_socket *sc)
sc->rdma.cm_id->event_handler = smbdirect_connection_rdma_event_handler;
sc->rdma.expected_event = RDMA_CM_EVENT_DISCONNECTED;
+
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ smbdirect_socket_sync_saddr_to_sk(sc, NULL);
+ smbdirect_socket_sync_daddr_to_sk(sc);
+
+ inet_sk_set_state(sk, TCP_SYN_RECV);
+ }
}
void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
@@ -189,6 +198,13 @@ void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
smbdirect_socket_status_string(sc->status),
SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
sc->status = SMBDIRECT_SOCKET_CONNECTED;
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ inet_sk_set_state(sk, TCP_ESTABLISHED);
+ if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+ sk->sk_socket->state = SS_CONNECTED;
+ }
/*
* We need to setup the refill and send immediate work
@@ -203,6 +219,13 @@ void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
&sc->rdma.cm_id->route.addr.src_addr,
&sc->rdma.cm_id->route.addr.dst_addr);
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+ sk->sk_state_change(sk);
+ }
+
wake_up(&sc->status_wait);
}
@@ -739,10 +762,12 @@ int smbdirect_connection_wait_for_connected(struct smbdirect_socket *sc)
"waiting for connection: device: %.*s local: %pISpsfc remote: %pISpsfc\n",
IB_DEVICE_NAME_MAX, devname, src, dst);
+ smbdirect_socket_sk_unlock(sc);
ret = wait_event_interruptible_timeout(sc->status_wait,
sc->status == SMBDIRECT_SOCKET_CONNECTED ||
sc->first_error,
msecs_to_jiffies(sp->negotiate_timeout_msec));
+ smbdirect_socket_sk_lock(sc);
if (sc->rdma.cm_id) {
/*
* Maybe src and dev are updated in the meantime.
@@ -954,6 +979,12 @@ int smbdirect_connection_send_batch_flush(struct smbdirect_socket *sc,
atomic_add(batch->credit, &sc->send_io.bcredits.count);
batch->credit = 0;
wake_up(&sc->send_io.bcredits.wait_queue);
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+ sk->sk_write_space(sk);
+ }
}
return ret;
@@ -1091,6 +1122,8 @@ int smbdirect_connection_send_single_iter(struct smbdirect_socket *sc,
u32 data_length = 0;
int ret;
+ smbdirect_socket_sk_owned_by_me(sc);
+
if (WARN_ON_ONCE(flags))
return -EINVAL; /* no flags support for now */
@@ -1150,10 +1183,12 @@ int smbdirect_connection_send_single_iter(struct smbdirect_socket *sc,
* wait until either the refill work or the peer
* granted new credits
*/
+ smbdirect_socket_sk_unlock(sc);
ret = wait_event_interruptible(sc->send_io.credits.wait_queue,
atomic_read(&sc->send_io.credits.count) >= 1 ||
atomic_read(&sc->recv_io.credits.available) >= 1 ||
sc->status != SMBDIRECT_SOCKET_CONNECTED);
+ smbdirect_socket_sk_lock(sc);
if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
ret = -ENOTCONN;
if (ret < 0)
@@ -1268,9 +1303,11 @@ int smbdirect_connection_send_wait_zero_pending(struct smbdirect_socket *sc)
* that means all the I/Os have been out and we are good to return
*/
+ smbdirect_socket_sk_unlock(sc);
wait_event(sc->send_io.pending.zero_wait_queue,
atomic_read(&sc->send_io.pending.count) == 0 ||
sc->status != SMBDIRECT_SOCKET_CONNECTED);
+ smbdirect_socket_sk_lock(sc);
if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
smbdirect_log_write(sc, SMBDIRECT_LOG_ERR,
"status=%s first_error=%1pe => %1pe\n",
@@ -1297,6 +1334,8 @@ int smbdirect_connection_send_iter(struct smbdirect_socket *sc,
int error = 0;
__be32 hdr;
+ smbdirect_socket_sk_owned_by_me(sc);
+
if (WARN_ONCE(flags, "unexpected flags=0x%x\n", flags))
return -EINVAL; /* no flags support for now */
@@ -1448,7 +1487,9 @@ static void smbdirect_connection_send_immediate_work(struct work_struct *work)
smbdirect_log_keep_alive(sc, SMBDIRECT_LOG_INFO,
"send an empty message\n");
sc->statistics.send_empty++;
+ smbdirect_socket_sk_lock(sc);
ret = smbdirect_connection_send_single_iter(sc, NULL, NULL, 0, 0);
+ smbdirect_socket_sk_unlock(sc);
if (ret < 0) {
smbdirect_log_write(sc, SMBDIRECT_LOG_ERR,
"smbdirect_connection_send_single_iter ret=%1pe\n",
@@ -1632,6 +1673,12 @@ void smbdirect_connection_recv_io_done(struct ib_cq *cq, struct ib_wc *wc)
* If any sender is waiting for credits, unblock it
*/
wake_up(&sc->send_io.credits.wait_queue);
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+ sk->sk_write_space(sk);
+ }
}
/* Send an immediate response right away if requested */
@@ -1652,6 +1699,12 @@ void smbdirect_connection_recv_io_done(struct ib_cq *cq, struct ib_wc *wc)
smbdirect_connection_reassembly_append_recv_io(sc, recv_io, data_length);
wake_up(&sc->recv_io.reassembly.wait_queue);
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+ sk->sk_data_ready(sk);
+ }
} else
smbdirect_connection_put_recv_io(recv_io);
@@ -1735,6 +1788,9 @@ int smbdirect_connection_recv_io_refill(struct smbdirect_socket *sc)
/*
* If the last send credit is waiting for credits
* it can grant we need to wake it up
+ *
+ * This needs to wake up smbdirect_connection_send_single_iter()
+ * only, so we don't need sk->sk_write_space() here.
*/
if (atomic_read(&sc->send_io.bcredits.count) == 0 &&
atomic_read(&sc->send_io.credits.count) == 0)
@@ -1922,9 +1978,11 @@ int smbdirect_connection_recvmsg(struct smbdirect_socket *sc,
smbdirect_log_read(sc, SMBDIRECT_LOG_INFO,
"wait_event on more data\n");
+ smbdirect_socket_sk_unlock(sc);
ret = wait_event_interruptible(sc->recv_io.reassembly.wait_queue,
sc->recv_io.reassembly.data_length >= size ||
sc->status != SMBDIRECT_SOCKET_CONNECTED);
+ smbdirect_socket_sk_lock(sc);
/* Don't return any data if interrupted */
if (ret)
return ret;
diff --git a/fs/smb/common/smbdirect/smbdirect_devices.c b/fs/smb/common/smbdirect/smbdirect_devices.c
index aaab99e9c045..da0edc104e48 100644
--- a/fs/smb/common/smbdirect/smbdirect_devices.c
+++ b/fs/smb/common/smbdirect/smbdirect_devices.c
@@ -257,7 +257,7 @@ __init int smbdirect_devices_init(void)
return 0;
}
-__exit void smbdirect_devices_exit(void)
+__cold void smbdirect_devices_exit(void)
{
struct smbdirect_device *sdev, *tmp;
diff --git a/fs/smb/common/smbdirect/smbdirect_internal.h b/fs/smb/common/smbdirect/smbdirect_internal.h
index 30a1b8643657..517ff0533032 100644
--- a/fs/smb/common/smbdirect/smbdirect_internal.h
+++ b/fs/smb/common/smbdirect/smbdirect_internal.h
@@ -12,8 +12,6 @@
#include "smbdirect_pdu.h"
#include "smbdirect_public.h"
-#include <linux/mutex.h>
-
struct smbdirect_module_state {
struct mutex mutex;
@@ -30,6 +28,8 @@ struct smbdirect_module_state {
rwlock_t lock;
struct list_head list;
} devices;
+
+ struct smbdirect_socket_parameters default_parameters;
};
extern struct smbdirect_module_state smbdirect_globals;
@@ -46,10 +46,58 @@ struct smbdirect_device {
char ib_name[IB_DEVICE_NAME_MAX];
};
+static __always_inline void smbdirect_socket_sk_owned_by_me(struct smbdirect_socket *sc)
+{
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ /* assert it is already locked */
+ sock_owned_by_me(sk);
+ }
+}
+
+static __always_inline void smbdirect_socket_sk_not_owned_by_me(struct smbdirect_socket *sc)
+{
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+ }
+}
+
+static __always_inline void smbdirect_socket_sk_lock(struct smbdirect_socket *sc)
+{
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ lock_sock(sk);
+ }
+}
+
+static __always_inline void smbdirect_socket_sk_unlock(struct smbdirect_socket *sc)
+{
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ /* assert it is already locked */
+ sock_owned_by_me(sk);
+
+ release_sock(sk);
+ }
+}
+
int smbdirect_socket_init_new(struct net *net, struct smbdirect_socket *sc);
int smbdirect_socket_init_accepting(struct rdma_cm_id *id, struct smbdirect_socket *sc);
+int smbdirect_socket_sync_saddr_to_sk(struct smbdirect_socket *sc, bool *_is_any_addr);
+
+int smbdirect_socket_sync_daddr_to_sk(struct smbdirect_socket *sc);
+
void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
const char *macro_name,
unsigned int lvl,
@@ -135,7 +183,12 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
void smbdirect_accept_negotiate_finish(struct smbdirect_socket *sc, u32 ntstatus);
+void smbdirect_sk_reclassify(struct sock *sk);
+
__init int smbdirect_devices_init(void);
-__exit void smbdirect_devices_exit(void);
+__cold void smbdirect_devices_exit(void);
+
+__init int smbdirect_proto_init(void);
+__exit void smbdirect_proto_exit(void);
#endif /* __FS_SMB_COMMON_SMBDIRECT_INTERNAL_H__ */
diff --git a/fs/smb/common/smbdirect/smbdirect_listen.c b/fs/smb/common/smbdirect/smbdirect_listen.c
index 05c7902e7020..a6e08d82dc73 100644
--- a/fs/smb/common/smbdirect/smbdirect_listen.c
+++ b/fs/smb/common/smbdirect/smbdirect_listen.c
@@ -74,6 +74,12 @@ int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog)
*/
sc->listen.backlog = backlog;
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ inet_sk_set_state(sk, TCP_LISTEN);
+ }
+
if (sc->rdma.cm_id->device)
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"listening on addr: %pISpsfc dev: %.*s\n",
@@ -209,6 +215,7 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
const struct rdma_cm_event *event)
{
const struct smbdirect_socket_parameters *lsp = &lsc->parameters;
+ struct sock *nsk = NULL;
struct smbdirect_socket *nsc;
unsigned long flags;
size_t backlog = max_t(size_t, 1, lsc->listen.backlog);
@@ -265,9 +272,39 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
return -EBUSY;
}
- ret = smbdirect_socket_create_accepting(new_id, &nsc);
- if (ret)
- goto socket_init_failed;
+ if (lsc->sk.sk_family) {
+ struct sock *lsk = &lsc->sk;
+
+ ret = -ENOMEM;
+ nsk = sk_clone(lsk, lsk->sk_allocation, false);
+ if (!nsk)
+ goto sk_clone_failed;
+ /* sk_clone_lock() increments refcnt to 2; drop the extra. */
+ __sock_put(nsk);
+ /* sk_clone() already called sk_sockets_allocated_inc(sk); */
+ sock_prot_inuse_add(sock_net(nsk), nsk->sk_prot, 1);
+
+ smbdirect_sk_reclassify(nsk);
+ inet_sk_set_state(nsk, TCP_SYN_RECV);
+ nsc = smbdirect_socket_from_sk(nsk);
+
+ ret = smbdirect_socket_init_accepting(new_id, nsc);
+ if (ret)
+ goto socket_init_failed;
+
+ /*
+ * Note that smbdirect_sock_accept() will set
+ * SOCK_CUSTOM_SOCKOPT once [__]inet_accept()
+ * called sk_set_socket() via sock_graft().
+ */
+ WARN_ON_ONCE(nsc->orig_sk_destruct != lsc->orig_sk_destruct);
+ WARN_ON_ONCE(nsk->sk_destruct != lsk->sk_destruct);
+ WARN_ON_ONCE(nsk->sk_ipv6only != lsk->sk_ipv6only);
+ } else {
+ ret = smbdirect_socket_create_accepting(new_id, &nsc);
+ if (ret)
+ goto socket_init_failed;
+ }
nsc->logging = lsc->logging;
ret = smbdirect_socket_set_initial_parameters(nsc, &lsc->parameters);
@@ -302,7 +339,11 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
*/
nsc->ib.dev = NULL;
nsc->rdma.cm_id = NULL;
- smbdirect_socket_release(nsc);
+ if (!nsk)
+ smbdirect_socket_release(nsc);
socket_init_failed:
+ if (nsk)
+ sk_free(nsk);
+sk_clone_failed:
return ret;
}
diff --git a/fs/smb/common/smbdirect/smbdirect_main.c b/fs/smb/common/smbdirect/smbdirect_main.c
index fe6e8d93c34c..ccbe979332af 100644
--- a/fs/smb/common/smbdirect/smbdirect_main.c
+++ b/fs/smb/common/smbdirect/smbdirect_main.c
@@ -12,6 +12,7 @@ struct smbdirect_module_state smbdirect_globals = {
static __init int smbdirect_module_init(void)
{
+ struct smbdirect_socket_parameters *sp;
int ret = -ENOMEM;
pr_notice("subsystem loading...\n");
@@ -73,10 +74,52 @@ static __init int smbdirect_module_init(void)
if (ret)
goto devices_init_failed;
+ /*
+ * Create the global default parameters
+ */
+ sp = &smbdirect_globals.default_parameters;
+ sp->resolve_addr_timeout_msec = 5 * 1000;
+ sp->resolve_route_timeout_msec = 5 * 1000;
+ sp->rdma_connect_timeout_msec = 5 * 1000;
+ sp->negotiate_timeout_msec = 120 * 1000;
+ sp->initiator_depth = 1; /* the server should change this */
+ sp->responder_resources = 1; /* the client should change this */
+ sp->recv_credit_max = 255;
+ sp->send_credit_target = 255;
+ sp->max_send_size = 1364;
+ /*
+ * The maximum fragmented upper-layer payload receive size supported
+ *
+ * Assume max_payload_per_credit is
+ * smbd_max_receive_size - 24 = 1340
+ *
+ * The maximum number would be
+ * smbd_receive_credit_max * max_payload_per_credit
+ *
+ * 1340 * 255 = 341700 (0x536C4)
+ *
+ * The minimum value from the spec is 131072 (0x20000)
+ *
+ * For now we use the logic we used before:
+ * (1364 * 255) / 2 = 173910 (0x2A756)
+ */
+ sp->max_fragmented_recv_size = (1364 * 255) / 2;
+ sp->max_recv_size = 1364;
+ sp->max_read_write_size = 0; /* the server should change this */
+ sp->max_frmr_depth = 0; /* the client should change this */
+ sp->keepalive_interval_msec = 120 * 1000;
+ sp->keepalive_timeout_msec = 5 * 1000;
+
+ ret = smbdirect_proto_init();
+ if (ret)
+ goto proto_init_failed;
+
mutex_unlock(&smbdirect_globals.mutex);
pr_notice("subsystem loaded\n");
return 0;
+proto_init_failed:
+ smbdirect_devices_exit();
devices_init_failed:
destroy_workqueue(smbdirect_globals.workqueues.cleanup);
alloc_cleanup_wq_failed:
@@ -101,6 +144,8 @@ static __exit void smbdirect_module_exit(void)
pr_notice("subsystem unloading...\n");
mutex_lock(&smbdirect_globals.mutex);
+ smbdirect_proto_exit();
+
smbdirect_devices_exit();
destroy_workqueue(smbdirect_globals.workqueues.accept);
diff --git a/fs/smb/common/smbdirect/smbdirect_mr.c b/fs/smb/common/smbdirect/smbdirect_mr.c
index fa9be8089925..86bb72ed10ae 100644
--- a/fs/smb/common/smbdirect/smbdirect_mr.c
+++ b/fs/smb/common/smbdirect/smbdirect_mr.c
@@ -167,9 +167,11 @@ smbdirect_connection_get_mr_io(struct smbdirect_socket *sc)
int ret;
again:
+ smbdirect_socket_sk_unlock(sc);
ret = wait_event_interruptible(sc->mr_io.ready.wait_queue,
atomic_read(&sc->mr_io.ready.count) ||
sc->status != SMBDIRECT_SOCKET_CONNECTED);
+ smbdirect_socket_sk_lock(sc);
if (ret) {
smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
"wait_event_interruptible ret=%d (%1pe)\n",
@@ -281,7 +283,9 @@ smbdirect_connection_register_mr_io(struct smbdirect_socket *sc,
return NULL;
}
+ smbdirect_socket_sk_lock(sc);
mr = smbdirect_connection_get_mr_io(sc);
+ smbdirect_socket_sk_unlock(sc);
if (!mr) {
smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
"smbdirect_connection_get_mr_io returning NULL\n");
@@ -415,6 +419,12 @@ void smbdirect_connection_deregister_mr_io(struct smbdirect_mr_io *mr)
if (mr->state == SMBDIRECT_MR_DISABLED)
goto put_kref;
+ /*
+ * We are protected by mr->mutex
+ * without lock_sock().
+ */
+ smbdirect_socket_sk_not_owned_by_me(sc);
+
if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
smbdirect_mr_io_disable_locked(mr);
goto put_kref;
diff --git a/fs/smb/common/smbdirect/smbdirect_proto.c b/fs/smb/common/smbdirect/smbdirect_proto.c
new file mode 100644
index 000000000000..1a832d52eb89
--- /dev/null
+++ b/fs/smb/common/smbdirect/smbdirect_proto.c
@@ -0,0 +1,1549 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2025 Stefan Metzmacher
+ */
+
+#include "smbdirect_internal.h"
+#include <net/protocol.h>
+#include <net/inet_common.h>
+#include <linux/bpf-cgroup.h>
+#include <linux/errname.h>
+
+#define SMBDIRECT_FN_GENERIC(__sk, __fmt, __args...) do { \
+ struct smbdirect_socket *__sc = smbdirect_socket_from_sk(__sk); \
+ __smbdirect_log_generic(__sc, SMBDIRECT_LOG_INFO, SMBDIRECT_LOG_SK, \
+ __fmt " sc=%p %s first_error=%1pe kern=%u locked=%u refs=%u dead=%u mrefs=%u\n", \
+ ##__args, __sc, \
+ smbdirect_socket_status_string(__sc->status), \
+ SMBDIRECT_DEBUG_ERR_PTR(__sc->first_error), \
+ (__sk)->sk_kern_sock, \
+ sock_owned_by_user_nocheck(__sk), \
+ refcount_read(&((__sk)->sk_refcnt)), \
+ sock_flag(__sk, SOCK_DEAD), \
+ module_refcount(THIS_MODULE)); \
+} while (0)
+
+#define SMBDIRECT_FN_COMMENT(__sk, __comment) \
+ SMBDIRECT_FN_GENERIC(__sk, "%s with", __comment)
+
+#define SMBDIRECT_FN_CALLED(__sk) \
+ SMBDIRECT_FN_GENERIC(__sk, "Called for")
+
+#define SMBDIRECT_FN_RETURN_VOID(__sk) \
+ SMBDIRECT_FN_GENERIC(__sk, "Returning for")
+
+#define SMBDIRECT_FN_RETURN_POLL(__sk, __mask) \
+ SMBDIRECT_FN_GENERIC(__sk, "Returning mask=0x%x for", __mask)
+
+#define SMBDIRECT_FN_RETURN_INT(__sk, __ret) do { \
+ bool __is_err = IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(__ret)); \
+ SMBDIRECT_FN_GENERIC(__sk, "Returning ret=%d%s%s%s for", \
+ (__ret), \
+ __is_err ? " (" : "", \
+ __is_err ? errname(__ret) : "", \
+ __is_err ? ")" : ""); \
+} while (0)
+
+static bool smbdirect_sk_logging_needed(struct smbdirect_socket *sc,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls)
+{
+ /*
+ * Only errors by default.
+ */
+ if (lvl <= SMBDIRECT_LOG_ERR)
+ return true;
+ return false;
+}
+
+static void smbdirect_sk_logging_vaprintf(struct smbdirect_socket *sc,
+ const char *func,
+ unsigned int line,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls,
+ struct va_format *vaf)
+{
+ if (lvl <= SMBDIRECT_LOG_ERR)
+ pr_err("%s:%u %pV", func, line, vaf);
+ else
+ pr_info("%s:%u %pV", func, line, vaf);
+}
+
+void smbdirect_sk_reclassify(struct sock *sk)
+{
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+ static struct lock_class_key sk_key[2];
+ static struct lock_class_key slock_key[2];
+
+ if (WARN_ON_ONCE(!sock_allow_reclassification(sk)))
+ return;
+
+ switch (sk->sk_family) {
+ case AF_INET:
+ /*
+ * Before we reset the owner we
+ * need to drop the reference of the
+ * existing module, this is only
+ * really relevant for AF_INET,
+ * as that is always builtin
+ * there's no potential leak
+ * of module references. We do it
+ * mainly in order to match the
+ * AF_INET6 case.
+ */
+ sk_owner_put(sk);
+ sk_owner_clear(sk);
+
+ sock_lock_init_class_and_name(sk,
+ "slock-AF_INET-IPPROTO-SMBDIRECT",
+ &slock_key[0],
+ "sk_lock-AF_INET-IPPROTO-SMBDIRECT",
+ &sk_key[0]);
+
+ /*
+ * Now that we reclassified the socket
+ * we're also the new sk_owner, but that's
+ * not needed as there's still a reference
+ * on sk->sk_prot->owner, which is dropped
+ * in sk_prot_free(). But in order to
+ * avoid module reference leaks to our
+ * own module we need to put and clear
+ * sk_owner, in order to allow callers
+ * to do their own reclassification.
+ */
+ sk_owner_put(sk);
+ sk_owner_clear(sk);
+ break;
+ case AF_INET6:
+ /*
+ * Before we reset the owner we
+ * need to drop the reference of the
+ * existing module.
+ *
+ * As we also use inet6_register_protosw()
+ * and other symbols from a possible
+ * ipv6.ko, we already have enough
+ * module references in order to avoid
+ * unloading of ipv6.ko, while smbdirect.ko
+ * is loaded.
+ *
+ * However when smbdirect.ko is unloaded
+ * we should not leak references in order
+ * to allow ipv6.ko to be unloaded as well.
+ */
+ sk_owner_put(sk);
+ sk_owner_clear(sk);
+
+ sock_lock_init_class_and_name(sk,
+ "slock-AF_INET6-IPPROTO-SMBDIRECT",
+ &slock_key[1],
+ "sk_lock-AF_INET6-IPPROTO-SMBDIRECT",
+ &sk_key[1]);
+
+ /*
+ * Now that we reclassified the socket
+ * we're also the new sk_owner, but that's
+ * not needed as there's still a reference
+ * on sk->sk_prot->owner, which is dropped
+ * in sk_prot_free(). But in order to
+ * avoid module reference leaks to our
+ * own module we need to put and clear
+ * sk_owner, in order to allow callers
+ * to do their own reclassification.
+ */
+ sk_owner_put(sk);
+ sk_owner_clear(sk);
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ }
+#endif /* CONFIG_DEBUG_LOCK_ALLOC */
+}
+
+static void smbdirect_sk_destruct(struct sock *sk)
+{
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+
+ /*
+ * Called by sk_free()
+ */
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ if (WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_DESTROYED)) {
+ pr_err("Attempt to release SMBDIRECT socket in status %s sc %p\n",
+ smbdirect_socket_status_string(sc->status), sc);
+ SMBDIRECT_FN_RETURN_VOID(sk);
+ return;
+ }
+
+ SMBDIRECT_FN_COMMENT(sk, "calling orig_sk_destruct");
+ smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+ "sc[%p]->orig_sk_destruct[%ps]\n",
+ sc, sc->orig_sk_destruct);
+ sc->orig_sk_destruct(sk);
+ SMBDIRECT_FN_RETURN_VOID(sk);
+}
+
+static int smbdirect_sk_init(struct sock *sk)
+{
+ struct socket *sock = sk->sk_socket;
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+ const struct smbdirect_socket_parameters *sp = &smbdirect_globals.default_parameters;
+ void (*orig_sk_destruct)(struct sock *sk);
+ int ret;
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ smbdirect_sk_reclassify(sk);
+
+ smbdirect_socket_init(sc);
+ smbdirect_socket_set_logging(sc,
+ NULL,
+ smbdirect_sk_logging_needed,
+ smbdirect_sk_logging_vaprintf);
+
+ smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+ "Called for sk=%p family=%u protocol=%u type=%u\n",
+ sk, sk->sk_family, sk->sk_protocol, sk->sk_type);
+
+ sk_sockets_allocated_inc(sk);
+ sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
+
+ orig_sk_destruct = sk->sk_destruct;
+ SMBDIRECT_FN_COMMENT(sk, "remembered orig_sk_destruct");
+ smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+ "sc[%p]->orig_sk_destruct[%ps]\n",
+ sc, orig_sk_destruct);
+ sc->orig_sk_destruct = orig_sk_destruct;
+ sk->sk_destruct = smbdirect_sk_destruct;
+
+ /*
+ * We want to handle all sockopts explicitly
+ * and only support what we really support.
+ */
+ set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags);
+ /*
+ * There are no legacy callers, so we are strict
+ * regarding ipv4 vs. ipv6.
+ */
+ sk->sk_ipv6only = true;
+
+ /*
+ * No userspace sockets yet...
+ */
+ if (!sk->sk_kern_sock) {
+ sc->first_error = -EPROTONOSUPPORT;
+ SMBDIRECT_FN_COMMENT(sk, "No userspace sockets");
+ return -EPROTONOSUPPORT;
+ }
+
+ ret = smbdirect_socket_init_new(sock_net(sk), sc);
+ if (ret)
+ goto socket_init_failed;
+ /*
+ * smbdirect_socket_init_new() called smbdirect_socket_init() again,
+ * so we need to call smbdirect_socket_set_logging() again!
+ */
+ smbdirect_socket_set_logging(sc,
+ NULL,
+ smbdirect_sk_logging_needed,
+ smbdirect_sk_logging_vaprintf);
+
+ WARN_ON_ONCE(sc->orig_sk_destruct != orig_sk_destruct);
+ WARN_ON_ONCE(sk->sk_destruct != smbdirect_sk_destruct);
+
+ ret = smbdirect_socket_set_initial_parameters(sc, sp);
+ if (ret)
+ goto set_params_failed;
+
+ ret = smbdirect_socket_set_kernel_settings(sc, IB_POLL_SOFTIRQ, sk->sk_allocation);
+ if (ret)
+ goto set_settings_failed;
+
+ SMBDIRECT_FN_RETURN_INT(sk, 0);
+ return 0;
+
+set_settings_failed:
+set_params_failed:
+socket_init_failed:
+ sc->first_error = ret;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static void smbdirect_sk_destroy(struct sock *sk)
+{
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is already locked */
+ sock_owned_by_me(sk);
+
+ /*
+ * For now do a sync disconnect/destroy
+ *
+ * SMBDIRECT_LOG_INFO is enough here
+ * as this is the typical case where
+ * we terminate the connection ourself.
+ */
+ smbdirect_socket_schedule_cleanup_lvl(sc,
+ SMBDIRECT_LOG_INFO,
+ -ESHUTDOWN);
+ smbdirect_socket_destroy_sync(sc);
+
+ sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
+ sk_sockets_allocated_dec(sk);
+
+ SMBDIRECT_FN_RETURN_VOID(sk);
+}
+
+static int smbdirect_sk_hash(struct sock *sk)
+{
+ SMBDIRECT_FN_CALLED(sk);
+ return 0;
+}
+
+static void smbdirect_sk_unhash(struct sock *sk)
+{
+ SMBDIRECT_FN_CALLED(sk);
+}
+
+static void smbdirect_sk_release_cb(struct sock *sk)
+{
+ /*
+ * Called from release_sock()
+ */
+ SMBDIRECT_FN_CALLED(sk);
+}
+
+static int smbdirect_sk_pre_bind(struct sock *sk,
+ struct sockaddr_unsized *uaddr,
+ int *addr_len,
+ u32 *flags,
+ u16 *port)
+{
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ if (*addr_len < sizeof(uaddr->sa_family))
+ return -EINVAL;
+
+ /* AF_UNSPEC is not allowed */
+ if (sk->sk_family != uaddr->sa_family)
+ return -EAFNOSUPPORT;
+
+ /*
+ * BPF prog is run before any checks are done so that if the prog
+ * changes context in a wrong way it will be caught.
+ */
+ switch (sk->sk_family) {
+ case AF_INET:
+ if (*addr_len < sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ *port = ntohs(((struct sockaddr_in *)uaddr)->sin_port);
+
+ return BPF_CGROUP_RUN_PROG_INET_BIND_LOCK(sk, uaddr, addr_len,
+ CGROUP_INET4_BIND,
+ flags);
+ case AF_INET6:
+ /*
+ * We require a full struct sockaddr_in6 (28 bytes) instead of a
+ * minimal size of SIN6_LEN_RFC2133 (24 bytes), as we don't
+ * have any legacy callers in userspace and the
+ * rdma layer also expects that.
+ */
+ if (*addr_len < sizeof(struct sockaddr_in6))
+ return -EINVAL;
+
+ *port = ntohs(((struct sockaddr_in6 *)uaddr)->sin6_port);
+
+ return BPF_CGROUP_RUN_PROG_INET_BIND_LOCK(sk, uaddr, addr_len,
+ CGROUP_INET6_BIND,
+ flags);
+ }
+
+ return -EAFNOSUPPORT;
+}
+
+static int smbdirect_sk_do_bind(struct sock *sk,
+ struct sockaddr_unsized *uaddr,
+ const u32 flags,
+ const u16 port)
+{
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+ bool is_any_addr = true;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ if (flags & BIND_WITH_LOCK)
+ sock_owned_by_me(sk);
+ else
+ sock_not_owned_by_me(sk);
+
+ ret = smbdirect_socket_bind(sc, (struct sockaddr *)uaddr);
+ if (ret) {
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ ret = smbdirect_socket_sync_saddr_to_sk(sc, &is_any_addr);
+ if (ret) {
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ /* Make sure we are allowed to bind here. */
+ if (sk->sk_num && !(flags & BIND_FROM_BPF)) {
+ switch (sk->sk_family) {
+ case AF_INET:
+ ret = BPF_CGROUP_RUN_PROG_INET4_POST_BIND(sk);
+ if (ret) {
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+ break;
+
+ case AF_INET6:
+ ret = BPF_CGROUP_RUN_PROG_INET6_POST_BIND(sk);
+ if (ret) {
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+ break;
+ }
+ }
+
+ if (!is_any_addr)
+ sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
+ if (port)
+ sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
+
+ ret = 0;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sk_bind(struct sock *sk, struct sockaddr_unsized *addr, int addr_len)
+{
+ struct net *net = sock_net(sk);
+ u32 flags = BIND_WITH_LOCK;
+ u16 port = 0;
+ u16 check_port = 0;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ ret = smbdirect_sk_pre_bind(sk, addr, &addr_len, &flags, &port);
+ if (ret) {
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ /*
+ * treat the iwarp tcp port for
+ * smb (5445) as the main smb port (445)
+ * and only allow the bind if 445
+ * would be allowed.
+ */
+ if (port == 5445)
+ check_port = 445;
+ else
+ check_port = port;
+
+ if (!(flags & BIND_NO_CAP_NET_BIND_SERVICE) &&
+ check_port && inet_port_requires_bind_service(net, check_port) &&
+ !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE)) {
+ ret = -EACCES;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ if (flags & BIND_WITH_LOCK)
+ lock_sock(sk);
+
+ ret = smbdirect_sk_do_bind(sk, addr, flags, port);
+
+ if (flags & BIND_WITH_LOCK)
+ release_sock(sk);
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static struct sock *smbdirect_sk_accept(struct sock *lsk, struct proto_accept_arg *arg)
+{
+ struct smbdirect_socket *lsc = smbdirect_socket_from_sk(lsk);
+ long timeo = sock_rcvtimeo(lsk, arg->flags & O_NONBLOCK);
+ struct smbdirect_socket *nsc;
+ struct sock *nsk;
+
+ SMBDIRECT_FN_CALLED(lsk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(lsk);
+
+ lock_sock(lsk);
+ nsc = smbdirect_socket_accept(lsc, timeo, arg);
+ release_sock(lsk);
+ if (!nsc) {
+ SMBDIRECT_FN_RETURN_INT(lsk, arg->err);
+ return NULL;
+ }
+ nsk = &nsc->sk;
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(nsk);
+
+ SMBDIRECT_FN_RETURN_INT(lsk, 0);
+ return nsk;
+}
+
+static int smbdirect_sk_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len)
+{
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is already locked */
+ sock_owned_by_me(sk);
+
+ if (addr_len < sizeof(uaddr->sa_family))
+ return -EINVAL;
+
+ if (sk->sk_family != uaddr->sa_family)
+ return -EAFNOSUPPORT;
+
+ switch (sk->sk_family) {
+ case AF_INET:
+ if (addr_len < sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ return BPF_CGROUP_RUN_PROG_INET4_CONNECT(sk, uaddr, &addr_len);
+ case AF_INET6:
+ /*
+ * We require a full struct sockaddr_in6 (28 bytes) instead of a
+ * minimal size of SIN6_LEN_RFC2133 (24 bytes), as we don't
+ * have any legacy callers in userspace and the
+ * rdma layer also expects that.
+ */
+ if (addr_len < sizeof(struct sockaddr_in6))
+ return -EINVAL;
+
+ return BPF_CGROUP_RUN_PROG_INET6_CONNECT(sk, uaddr, &addr_len);
+ }
+
+ return -EAFNOSUPPORT;
+}
+
+static int smbdirect_sk_connect(struct sock *sk, struct sockaddr_unsized *addr, int addr_len)
+{
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is already locked */
+ sock_owned_by_me(sk);
+
+ ret = smbdirect_connect(sc, (struct sockaddr *)addr);
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sk_setsockopt(struct sock *sk, int level, int optname,
+ sockptr_t optval, unsigned int optlen)
+{
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ switch (level) {
+ default:
+ SMBDIRECT_FN_COMMENT(sk, "default");
+ smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+ "level=%d optname=%d for sk=%p\n",
+ level, optname, sk);
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sk_getsockopt(struct sock *sk, int level, int optname,
+ char __user *optval, int __user *optlen)
+{
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ switch (level) {
+ default:
+ SMBDIRECT_FN_COMMENT(sk, "default");
+ smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+ "level=%d optname=%d for sk=%p\n",
+ level, optname, sk);
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sk_ioctl(struct sock *sk, int cmd, int *karg)
+{
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ switch (cmd) {
+ default:
+ SMBDIRECT_FN_COMMENT(sk, "default");
+ smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+ "cmd=%d (0x%x) for sk=%p\n",
+ cmd, cmd, sk);
+ ret = -ENOIOCTLCMD;
+ break;
+ }
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static inline size_t smbdirect_cmsg_count(const struct msghdr *_msg,
+ int *first_sol_smbdirect_type)
+{
+ struct msghdr *msg = (struct msghdr *)(uintptr_t)(const void *)_msg;
+ struct cmsghdr *cmsg = NULL;
+ size_t count = 0;
+
+ if (first_sol_smbdirect_type != NULL)
+ *first_sol_smbdirect_type = -1;
+
+ for (cmsg = CMSG_FIRSTHDR(msg);
+ cmsg != NULL;
+ cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ count++;
+ if (cmsg->cmsg_level != SOL_SMBDIRECT)
+ continue;
+ if (first_sol_smbdirect_type != NULL) {
+ *first_sol_smbdirect_type = cmsg->cmsg_type;
+ first_sol_smbdirect_type = NULL;
+ }
+ }
+
+ return count;
+}
+
+static __always_inline
+ssize_t __smbdirect_cmsg_extract(const struct msghdr *_msg,
+ int cmsg_type,
+ void *_payload,
+ size_t payloadmin,
+ size_t payloadmax)
+{
+ struct msghdr *msg = (struct msghdr *)(uintptr_t)(const void *)_msg;
+ size_t cmsg_len_min = CMSG_LEN(payloadmin);
+ size_t cmsg_len_max = CMSG_LEN(payloadmax);
+ const size_t cmsg_len_hdr = CMSG_LEN(0);
+ uint8_t *payload = (uint8_t *)_payload;
+ struct cmsghdr *cmsg = NULL;
+ size_t payloadlen;
+
+ BUILD_BUG_ON(cmsg_len_min > cmsg_len_max);
+ if (WARN_ON_ONCE(cmsg_len_min > cmsg_len_max))
+ return -EBADMSG;
+
+ for (cmsg = CMSG_FIRSTHDR(msg);
+ cmsg != NULL;
+ cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level != SOL_SMBDIRECT)
+ continue;
+
+ if (cmsg->cmsg_type != cmsg_type)
+ continue;
+
+ if (cmsg->cmsg_len < cmsg_len_min)
+ return -EBADMSG;
+
+ if (cmsg->cmsg_len > cmsg_len_max)
+ return -EMSGSIZE;
+
+ payloadlen = cmsg->cmsg_len - cmsg_len_hdr;
+ if (payloadlen > 0)
+ memcpy(payload, CMSG_DATA(cmsg), payloadlen);
+ if (payloadlen < payloadmax)
+ memset(payload + payloadlen, 0, payloadmax - payloadlen);
+ return payloadlen;
+ }
+
+ return -ENOMSG;
+}
+
+static __always_inline
+int smbdirect_buffer_remote_invalidate_cmsg_extract(const struct msghdr *msg,
+ u32 *remote_token)
+{
+ struct smbdirect_buffer_remote_invalidate_args args = {
+ .remote_token = 0,
+ };
+ ssize_t ret;
+
+ ret = __smbdirect_cmsg_extract(msg,
+ SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE,
+ &args, sizeof(args), sizeof(args));
+ if (ret < 0)
+ return ret;
+
+ *remote_token = args.remote_token;
+ return 0;
+}
+
+static int smbdirect_sk_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t msg_len)
+{
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+ struct iov_iter *iter = &msg->msg_iter;
+ unsigned int flags = msg->msg_flags;
+ size_t cmsg_count = 0;
+ int cmsg_type = -1;
+ bool need_invalidate = false;
+ u32 remote_key = 0;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is already locked */
+ sock_owned_by_me(sk);
+
+ cmsg_count = smbdirect_cmsg_count(msg, &cmsg_type);
+ if (cmsg_count > 1) {
+ ret = -EINVAL;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ if (flags & ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL)) {
+ ret = -EINVAL;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ if (cmsg_type == SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE) {
+ ret = smbdirect_buffer_remote_invalidate_cmsg_extract(msg, &remote_key);
+ if (!ret)
+ need_invalidate = true; /* remote_key is valid */
+ else if (ret != -ENOMSG) {
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+ } else if (cmsg_count) {
+ ret = -EINVAL;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ if (WARN_ON_ONCE(iov_iter_rw(iter) != ITER_SOURCE)) {
+ ret = -EINVAL;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ if (WARN_ON_ONCE(iov_iter_count(iter) != msg_len)) {
+ ret = -EINVAL;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ if (flags & MSG_DONTWAIT) {
+ if (!sc->first_error && msg_len && atomic_read(&sc->send_io.credits.count) == 0) {
+ ret = -EAGAIN;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+ }
+ flags &= ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL);
+
+ ret = smbdirect_connection_send_iter(sc,
+ iter,
+ flags,
+ need_invalidate,
+ remote_key);
+ if (ret < 0)
+ /* Handle error and possibly send SIGPIPE. */
+ ret = sk_stream_error(sk, msg->msg_flags, ret);
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sk_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len)
+{
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ lock_sock(sk);
+ ret = smbdirect_sk_sendmsg_locked(sk, msg, msg_len);
+ release_sock(sk);
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sk_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
+{
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+ struct iov_iter *iter = &msg->msg_iter;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ if (flags & ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL)) {
+ ret = -EINVAL;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ if (WARN_ON_ONCE(iov_iter_rw(iter) != ITER_DEST)) {
+ ret = -EINVAL;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ /*
+ * For now smbdirect_connection_recvmsg() relies
+ * on this assertion and the current in kernel
+ * users are working that way.
+ */
+ if (WARN_ON_ONCE(iov_iter_count(iter) != len)) {
+ ret = -EINVAL;
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+
+ lock_sock(sk);
+ if (flags & MSG_DONTWAIT) {
+ if (!sc->first_error && len && sc->recv_io.reassembly.data_length == 0) {
+ ret = -EAGAIN;
+ release_sock(sk);
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+ }
+ }
+ flags &= ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL);
+ ret = smbdirect_connection_recvmsg(sc, msg, flags);
+ if (msg->msg_get_inq && ret >= 0)
+ msg->msg_inq = sc->recv_io.reassembly.data_length;
+ release_sock(sk);
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static void smbdirect_sk_shutdown(struct sock *sk, int how)
+{
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is already locked */
+ sock_owned_by_me(sk);
+
+ smbdirect_socket_schedule_cleanup(sc, -ESHUTDOWN);
+
+ SMBDIRECT_FN_RETURN_VOID(sk);
+}
+
+static int smbdirect_sk_disconnect(struct sock *sk, int flags)
+{
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is already locked */
+ sock_owned_by_me(sk);
+
+ smbdirect_socket_schedule_cleanup(sc, -ESHUTDOWN);
+
+ if (flags & O_NONBLOCK) {
+ if (sc->status >= SMBDIRECT_SOCKET_DISCONNECTED) {
+ SMBDIRECT_FN_RETURN_INT(sk, 0);
+ return 0;
+ }
+
+ /*
+ * This will cause SS_DISCONNECTING in
+ * smbdirect_sock_connect_locked().
+ */
+ SMBDIRECT_FN_RETURN_INT(sk, sc->first_error);
+ return sc->first_error;
+ }
+
+ smbdirect_socket_destroy_sync(sc);
+
+ SMBDIRECT_FN_RETURN_INT(sk, 0);
+ return 0;
+}
+
+static void smbdirect_sk_close(struct sock *sk, long timeout)
+{
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ /*
+ * We hold an additional reference so
+ * that the sock_put() in sk_common_release()
+ * doesn't call sk_free(), that is potentially
+ * deferred to our sock_put() after release_sock().
+ *
+ * Note that sk_common_release() calls
+ * smbdirect_sk_destroy() as the first thing.
+ */
+ sock_hold(sk);
+ lock_sock(sk);
+ sk_common_release(sk);
+ release_sock(sk);
+ SMBDIRECT_FN_COMMENT(sk, "before sock_put()");
+ sock_put(sk);
+}
+
+static struct percpu_counter smbdirect_sockets_allocated;
+
+static struct proto smbdirect_prot = {
+ .name = "smbdirect",
+ .owner = THIS_MODULE,
+ .obj_size = sizeof(struct smbdirect_socket),
+ .ipv6_pinfo_offset = offsetof(struct smbdirect_socket, inet6),
+ .init = smbdirect_sk_init,
+ .destroy = smbdirect_sk_destroy,
+ .hash = smbdirect_sk_hash,
+ .unhash = smbdirect_sk_unhash,
+ .release_cb = smbdirect_sk_release_cb,
+ .bind = smbdirect_sk_bind,
+ .accept = smbdirect_sk_accept,
+ .pre_connect = smbdirect_sk_pre_connect,
+ .connect = smbdirect_sk_connect,
+ .setsockopt = smbdirect_sk_setsockopt,
+ .getsockopt = smbdirect_sk_getsockopt,
+ .ioctl = smbdirect_sk_ioctl,
+ .sendmsg = smbdirect_sk_sendmsg,
+ .recvmsg = smbdirect_sk_recvmsg,
+ .shutdown = smbdirect_sk_shutdown,
+ .disconnect = smbdirect_sk_disconnect,
+ .close = smbdirect_sk_close,
+ .sockets_allocated = &smbdirect_sockets_allocated,
+};
+
+static int smbdirect_sock_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not locked */
+ sock_not_owned_by_me(sk);
+ WARN_ON_ONCE(sock_owned_by_user_nocheck(sk));
+
+ switch (sk->sk_family) {
+ case AF_INET:
+ SMBDIRECT_FN_COMMENT(sk, "calling inet_release()");
+ ret = inet_release(sock);
+ break;
+ case AF_INET6:
+#if IS_ENABLED(CONFIG_IPV6)
+ SMBDIRECT_FN_COMMENT(sk, "calling inet6_release()");
+ ret = inet6_release(sock);
+#else
+ ret = -EAFNOSUPPORT;
+#endif
+ break;
+ default:
+ ret = -EAFNOSUPPORT;
+ break;
+ }
+
+ return ret;
+}
+
+static int smbdirect_sock_bind(struct socket *sock, struct sockaddr_unsized *saddr, int len)
+{
+ struct sock *sk = sock->sk;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ switch (sk->sk_family) {
+ case AF_INET:
+ ret = inet_bind(sock, saddr, len);
+ break;
+ case AF_INET6:
+#if IS_ENABLED(CONFIG_IPV6)
+ ret = inet6_bind(sock, saddr, len);
+#else
+ ret = -EAFNOSUPPORT;
+#endif
+ break;
+ default:
+ ret = -EAFNOSUPPORT;
+ break;
+ }
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sock_connect_locked(struct socket *sock,
+ struct sockaddr_unsized *uaddr,
+ int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is already locked */
+ sock_owned_by_me(sk);
+
+ if (addr_len < sizeof(uaddr->sa_family))
+ return -EINVAL;
+
+ if (sk->sk_family != uaddr->sa_family)
+ return -EAFNOSUPPORT;
+
+ switch (sk->sk_family) {
+ case AF_INET:
+ if (addr_len < sizeof(struct sockaddr_in))
+ return -EINVAL;
+ break;
+ case AF_INET6:
+ /*
+ * We require a full struct sockaddr_in6 (28 bytes) instead of a
+ * minimal size of SIN6_LEN_RFC2133 (24 bytes), as we don't
+ * have any legacy callers in userspace and the
+ * rdma layer also expects that.
+ */
+ if (addr_len < sizeof(struct sockaddr_in6))
+ return -EINVAL;
+ break;
+ default:
+ return -EAFNOSUPPORT;
+ }
+
+ switch (sock->state) {
+ case SS_CONNECTED:
+ return -EISCONN;
+ case SS_CONNECTING:
+ return -EALREADY;
+ case SS_UNCONNECTED:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (sc->status == SMBDIRECT_SOCKET_CONNECTED)
+ return -EISCONN;
+
+ if (sc->status != SMBDIRECT_SOCKET_CREATED)
+ return -EINVAL;
+
+ if (BPF_CGROUP_PRE_CONNECT_ENABLED(sk)) {
+ ret = sk->sk_prot->pre_connect(sk, uaddr, addr_len);
+ if (ret)
+ return ret;
+ }
+
+ ret = sk->sk_prot->connect(sk, uaddr, addr_len);
+ if (ret < 0)
+ return ret;
+
+ inet_sk_set_state(sk, TCP_SYN_SENT);
+ sock->state = SS_CONNECTING;
+
+ if (flags & O_NONBLOCK)
+ return -EINPROGRESS;
+
+ ret = smbdirect_connection_wait_for_connected(sc);
+ if (ret)
+ goto sock_error;
+
+ return 0;
+
+sock_error:
+ sock->state = SS_UNCONNECTED;
+ sk->sk_disconnects++;
+ if (sk->sk_prot->disconnect(sk, flags))
+ sock->state = SS_DISCONNECTING;
+ return ret;
+}
+
+static int smbdirect_sock_connect(struct socket *sock,
+ struct sockaddr_unsized *uaddr,
+ int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ lock_sock(sk);
+ ret = smbdirect_sock_connect_locked(sock, uaddr, addr_len, flags);
+ release_sock(sk);
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sock_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ lock_sock(sk);
+ ret = smbdirect_socket_listen(sc, backlog);
+ release_sock(sk);
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sock_accept(struct socket *lsock, struct socket *nsock,
+ struct proto_accept_arg *arg)
+{
+ struct sock *lsk = lsock->sk;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(lsk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(lsk);
+
+ ret = inet_accept(lsock, nsock, arg);
+ if (!ret)
+ /*
+ * We want to handle all sockopts explicitly
+ * and only support what we really support.
+ */
+ set_bit(SOCK_CUSTOM_SOCKOPT, &nsock->flags);
+
+ SMBDIRECT_FN_RETURN_INT(lsk, ret);
+ return ret;
+}
+
+static int smbdirect_sock_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
+{
+ struct sock *sk = sock->sk;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ switch (sk->sk_family) {
+ case AF_INET:
+ ret = inet_getname(sock, uaddr, peer);
+ break;
+ case AF_INET6:
+#if IS_ENABLED(CONFIG_IPV6)
+ ret = inet6_getname(sock, uaddr, peer);
+#else
+ ret = -EAFNOSUPPORT;
+#endif
+ break;
+ default:
+ ret = -EAFNOSUPPORT;
+ break;
+ }
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static __poll_t smbdirect_sock_poll(struct file *file, struct socket *sock, poll_table *wait)
+{
+ struct sock *sk = sock->sk;
+ struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+ __poll_t mask = 0;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ sock_poll_wait(file, sock, wait);
+
+ if (sc->status == SMBDIRECT_SOCKET_LISTENING) {
+ if (!list_empty_careful(&sc->listen.ready))
+ mask |= EPOLLIN | EPOLLRDNORM;
+ SMBDIRECT_FN_RETURN_POLL(sk, mask);
+ return mask;
+ }
+
+ if (sc->first_error) {
+ /*
+ * A broken connection should report almost everything in order to let
+ * applications to detect it reliable.
+ */
+ mask |= EPOLLHUP;
+ mask |= EPOLLERR;
+ mask |= EPOLLIN | EPOLLRDNORM | EPOLLRDHUP;
+ mask |= EPOLLOUT | EPOLLWRNORM;
+ SMBDIRECT_FN_RETURN_POLL(sk, mask);
+ return mask;
+ }
+
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
+ /*
+ * A just created socket.
+ */
+ SMBDIRECT_FN_RETURN_POLL(sk, mask);
+ return mask;
+ }
+
+ if (sc->recv_io.reassembly.data_length > 0)
+ mask |= EPOLLIN | EPOLLRDNORM;
+
+ if (atomic_read(&sc->send_io.bcredits.count) > 0 &&
+ atomic_read(&sc->send_io.lcredits.count) > 0 &&
+ atomic_read(&sc->send_io.credits.count) > 0)
+ mask |= EPOLLOUT | EPOLLWRNORM;
+ else {
+ sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
+ set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
+
+ /*
+ * Race breaker. If space is freed after
+ * wspace test but before the flags are set,
+ * IO signal will be lost. Memory barrier
+ * pairs with the input side.
+ */
+ smp_mb__after_atomic();
+ if (atomic_read(&sc->send_io.bcredits.count) > 0 &&
+ atomic_read(&sc->send_io.lcredits.count) > 0 &&
+ atomic_read(&sc->send_io.credits.count) > 0)
+ mask |= EPOLLOUT | EPOLLWRNORM;
+ }
+
+ SMBDIRECT_FN_RETURN_POLL(sk, mask);
+ return mask;
+}
+
+static int smbdirect_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ /*
+ * We may need to handle some here as
+ * smbirect_sk_ioctl() only gets a kernel
+ * int pointer as arg, but we may
+ * need to the whole struct
+ */
+ switch (cmd) {
+ default:
+ /*
+ * Note this has some special handling for
+ * sk->sk_type == SOCK_RAW, in case we ever
+ * implement SOCK_RAW...
+ *
+ * It calls smbdirect_sk_ioctl()...
+ */
+ ret = sk_ioctl(sk, cmd, (void __user *)arg);
+ break;
+ }
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sock_shutdown(struct socket *sock, int how)
+{
+ struct sock *sk = sock->sk;
+ int ret = 0;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ /*
+ * We have these from userspace:
+ * SHUT_RD = 0, SHUT_WR = 1 and SHUT_RDWR = 2
+ *
+ * And we map them to SHUTDOWN_MASK = 3
+ * RCV_SHUTDOWN = 1, SEND_SHUTDOWN = 2, BOTH = 3
+ */
+ how++;
+ if ((how & ~SHUTDOWN_MASK) || !how) /* MAXINT->0 */
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ switch (sk->sk_state) {
+ case TCP_CLOSE:
+ ret = -ENOTCONN;
+ fallthrough;
+ default:
+ WRITE_ONCE(sk->sk_shutdown, sk->sk_shutdown | how);
+ sk->sk_prot->shutdown(sk, how);
+ break;
+
+ case TCP_SYN_SENT:
+ case TCP_SYN_RECV:
+ ret = sk->sk_prot->disconnect(sk, O_NONBLOCK);
+ break;
+ }
+
+ /* Wake up anyone sleeping in poll. */
+ sk->sk_state_change(sk);
+ release_sock(sk);
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sock_setsockopt(struct socket *sock, int level, int optname,
+ sockptr_t optval, unsigned int optlen)
+{
+ struct sock *sk = sock->sk;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ ret = sock_common_setsockopt(sock, level, optname, optval, optlen);
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sock_getsockopt(struct socket *sock, int level, int optname,
+ char __user *optval, int __user *optlen)
+{
+ struct sock *sk = sock->sk;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ ret = sock_common_getsockopt(sock, level, optname, optval, optlen);
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
+{
+ struct sock *sk = sock->sk;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ ret = sk->sk_prot->sendmsg(sk, msg, len);
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static int smbdirect_sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
+ int flags)
+{
+ struct sock *sk = sock->sk;
+ int ret;
+
+ SMBDIRECT_FN_CALLED(sk);
+
+ /* assert it is not already locked */
+ sock_not_owned_by_me(sk);
+
+ ret = sock_common_recvmsg(sock, msg, size, flags);
+
+ SMBDIRECT_FN_RETURN_INT(sk, ret);
+ return ret;
+}
+
+static const struct proto_ops smbdirect_inet_proto_ops = {
+ .family = PF_INET,
+ .owner = THIS_MODULE,
+ .release = smbdirect_sock_release,
+ .bind = smbdirect_sock_bind,
+ .connect = smbdirect_sock_connect,
+ .socketpair = sock_no_socketpair,
+ .listen = smbdirect_sock_listen,
+ .accept = smbdirect_sock_accept,
+ .getname = smbdirect_sock_getname,
+ .poll = smbdirect_sock_poll,
+ .ioctl = smbdirect_sock_ioctl,
+ .shutdown = smbdirect_sock_shutdown,
+ .setsockopt = smbdirect_sock_setsockopt,
+ .getsockopt = smbdirect_sock_getsockopt,
+ .sendmsg = smbdirect_sock_sendmsg,
+ .sendmsg_locked = smbdirect_sk_sendmsg_locked,
+ .recvmsg = smbdirect_sock_recvmsg,
+ .mmap = sock_no_mmap,
+};
+
+#if IS_ENABLED(CONFIG_IPV6)
+static const struct proto_ops smbdirect_inet6_proto_ops = {
+ .family = PF_INET6,
+ .owner = THIS_MODULE,
+ .release = smbdirect_sock_release,
+ .bind = smbdirect_sock_bind,
+ .connect = smbdirect_sock_connect,
+ .socketpair = sock_no_socketpair,
+ .listen = smbdirect_sock_listen,
+ .accept = smbdirect_sock_accept,
+ .getname = smbdirect_sock_getname,
+ .poll = smbdirect_sock_poll,
+ .ioctl = smbdirect_sock_ioctl,
+ .shutdown = smbdirect_sock_shutdown,
+ .setsockopt = smbdirect_sock_setsockopt,
+ .getsockopt = smbdirect_sock_getsockopt,
+ .sendmsg = smbdirect_sock_sendmsg,
+ .sendmsg_locked = smbdirect_sk_sendmsg_locked,
+ .recvmsg = smbdirect_sock_recvmsg,
+ .mmap = sock_no_mmap,
+};
+#endif
+
+static struct inet_protosw smbdirect_inet_stream_protosw = {
+ .type = SOCK_STREAM,
+ .protocol = IPPROTO_SMBDIRECT,
+ .prot = &smbdirect_prot,
+ .ops = &smbdirect_inet_proto_ops,
+};
+
+#if IS_ENABLED(CONFIG_IPV6)
+static struct inet_protosw smbdirect_inet6_stream_protosw = {
+ .type = SOCK_STREAM,
+ .protocol = IPPROTO_SMBDIRECT,
+ .prot = &smbdirect_prot,
+ .ops = &smbdirect_inet6_proto_ops,
+};
+#endif
+
+struct smbdirect_socket *smbdirect_socket_from_sock(const struct socket *sock)
+{
+ if (!sock ||
+ !sock->sk ||
+ sock->sk->sk_protocol != IPPROTO_SMBDIRECT)
+ return NULL;
+
+ if (WARN_ON_ONCE(sock->sk->sk_destruct != smbdirect_sk_destruct))
+ return NULL;
+
+ return smbdirect_socket_from_sk(sock->sk);
+}
+__SMBDIRECT_EXPORT_SYMBOL__(smbdirect_socket_from_sock);
+
+static __init int smbdirect_protosw_init(void)
+{
+ int err;
+
+ err = proto_register(&smbdirect_prot, 1);
+ if (err)
+ return err;
+
+ inet_register_protosw(&smbdirect_inet_stream_protosw);
+#if IS_ENABLED(CONFIG_IPV6)
+ inet6_register_protosw(&smbdirect_inet6_stream_protosw);
+#endif
+
+ return 0;
+}
+
+static __exit void smbdirect_protosw_exit(void)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+ inet6_unregister_protosw(&smbdirect_inet6_stream_protosw);
+#endif
+ inet_unregister_protosw(&smbdirect_inet_stream_protosw);
+
+ proto_unregister(&smbdirect_prot);
+}
+
+__init int smbdirect_proto_init(void)
+{
+ int err;
+
+ err = percpu_counter_init(&smbdirect_sockets_allocated, 0, GFP_KERNEL);
+ if (err)
+ goto err_percpu_counter;
+
+ err = smbdirect_protosw_init();
+ if (err)
+ goto err_protosw;
+
+ return 0;
+
+err_protosw:
+ percpu_counter_destroy(&smbdirect_sockets_allocated);
+err_percpu_counter:
+ return err;
+}
+
+__exit void smbdirect_proto_exit(void)
+{
+ smbdirect_protosw_exit();
+ percpu_counter_destroy(&smbdirect_sockets_allocated);
+}
+
+MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_INET, 257 /* IPPROTO_SMBDIRECT */, SOCK_STREAM);
+MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_INET6, 257 /* IPPROTO_SMBDIRECT */, SOCK_STREAM);
diff --git a/fs/smb/common/smbdirect/smbdirect_public.h b/fs/smb/common/smbdirect/smbdirect_public.h
index 50088155e7c3..9f96c66bbe32 100644
--- a/fs/smb/common/smbdirect/smbdirect_public.h
+++ b/fs/smb/common/smbdirect/smbdirect_public.h
@@ -49,6 +49,7 @@ int smbdirect_socket_set_kernel_settings(struct smbdirect_socket *sc,
#define SMBDIRECT_LOG_RDMA_MR 0x100
#define SMBDIRECT_LOG_RDMA_RW 0x200
#define SMBDIRECT_LOG_NEGOTIATE 0x400
+#define SMBDIRECT_LOG_SK 0x800
void smbdirect_socket_set_logging(struct smbdirect_socket *sc,
void *private_ptr,
bool (*needed)(struct smbdirect_socket *sc,
@@ -145,4 +146,6 @@ void smbdirect_connection_legacy_debug_proc_show(struct smbdirect_socket *sc,
unsigned int rdma_readwrite_threshold,
struct seq_file *m);
+struct smbdirect_socket *smbdirect_socket_from_sock(const struct socket *sock);
+
#endif /* __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_PUBLIC_H__ */
diff --git a/fs/smb/common/smbdirect/smbdirect_rw.c b/fs/smb/common/smbdirect/smbdirect_rw.c
index 3b2eb8c48efc..154339955617 100644
--- a/fs/smb/common/smbdirect/smbdirect_rw.c
+++ b/fs/smb/common/smbdirect/smbdirect_rw.c
@@ -105,11 +105,11 @@ static void smbdirect_connection_rdma_write_done(struct ib_cq *cq, struct ib_wc
smbdirect_connection_rdma_rw_done(cq, wc, DMA_TO_DEVICE);
}
-int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
- void *buf, size_t buf_len,
- struct smbdirect_buffer_descriptor_v1 *desc,
- size_t desc_len,
- bool is_read)
+static int smbdirect_connection_rdma_xmit_locked(struct smbdirect_socket *sc,
+ void *buf, size_t buf_len,
+ struct smbdirect_buffer_descriptor_v1 *desc,
+ size_t desc_len,
+ bool is_read)
{
const struct smbdirect_socket_parameters *sp = &sc->parameters;
enum dma_data_direction direction = is_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
@@ -123,6 +123,8 @@ int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
int credits_needed;
size_t desc_buf_len, desc_num = 0;
+ smbdirect_socket_sk_owned_by_me(sc);
+
if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
return -ENOTCONN;
@@ -235,7 +237,9 @@ int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
}
msg = list_last_entry(&msg_list, struct smbdirect_rw_io, list);
+ smbdirect_socket_sk_unlock(sc);
wait_for_completion(&completion);
+ smbdirect_socket_sk_lock(sc);
ret = msg->error;
out:
list_for_each_entry_safe(msg, next_msg, &msg_list, list) {
@@ -252,4 +256,19 @@ int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
kfree(msg);
goto out;
}
+
+int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
+ void *buf, size_t buf_len,
+ struct smbdirect_buffer_descriptor_v1 *desc,
+ size_t desc_len,
+ bool is_read)
+{
+ int ret;
+
+ smbdirect_socket_sk_lock(sc);
+ ret = smbdirect_connection_rdma_xmit_locked(sc, buf, buf_len, desc, desc_len, is_read);
+ smbdirect_socket_sk_unlock(sc);
+
+ return ret;
+}
__SMBDIRECT_EXPORT_SYMBOL__(smbdirect_connection_rdma_xmit);
diff --git a/fs/smb/common/smbdirect/smbdirect_socket.c b/fs/smb/common/smbdirect/smbdirect_socket.c
index 9153e1dbf53d..76e406999588 100644
--- a/fs/smb/common/smbdirect/smbdirect_socket.c
+++ b/fs/smb/common/smbdirect/smbdirect_socket.c
@@ -5,6 +5,7 @@
*/
#include "smbdirect_internal.h"
+#include <net/transp_v6.h>
bool smbdirect_frwr_is_supported(const struct ib_device_attr *attrs)
{
@@ -217,6 +218,7 @@ int smbdirect_socket_set_kernel_settings(struct smbdirect_socket *sc,
sc->send_io.mem.gfp_mask = gfp_mask;
sc->recv_io.mem.gfp_mask = gfp_mask;
sc->rw_io.mem.gfp_mask = gfp_mask;
+ sc->sk.sk_allocation = gfp_mask;
return 0;
}
@@ -242,6 +244,106 @@ void smbdirect_socket_set_logging(struct smbdirect_socket *sc,
}
__SMBDIRECT_EXPORT_SYMBOL__(smbdirect_socket_set_logging);
+int smbdirect_socket_sync_saddr_to_sk(struct smbdirect_socket *sc, bool *_is_any_addr)
+{
+ struct sock *sk = &sc->sk;
+ const struct sockaddr_storage *saddr;
+ const struct sockaddr_in *sin;
+ const struct sockaddr_in6 *sin6;
+ struct in_addr sin_addr = { .s_addr = htonl(INADDR_ANY), };
+ struct in6_addr sin6_addr = in6addr_any;
+ __be32 sin6_flowinfo = 0;
+ bool is_any_addr = true;
+ u16 sport = 0;
+ int ret;
+
+ saddr = &sc->rdma.cm_id->route.addr.src_addr;
+
+ if (WARN_ON_ONCE(saddr->ss_family != sk->sk_family)) {
+ ret = -EINVAL;
+ return ret;
+ }
+
+ switch (saddr->ss_family) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)saddr;
+ sport = ntohs(sin->sin_port);
+ sin_addr = sin->sin_addr;
+ is_any_addr = (sin_addr.s_addr == htonl(INADDR_ANY));
+ break;
+
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)saddr;
+ sport = ntohs(sin6->sin6_port);
+ sin_addr.s_addr = LOOPBACK4_IPV6;
+ sin6_addr = sin6->sin6_addr;
+ is_any_addr = ipv6_addr_any(&sin6_addr);
+ sin6_flowinfo = sin6->sin6_flowinfo;
+ break;
+ }
+
+ sk->sk_bound_dev_if = sc->rdma.cm_id->route.addr.dev_addr.bound_dev_if;
+ sk->sk_rcv_saddr = sc->inet.inet_saddr = sin_addr.s_addr;
+#if IS_ENABLED(CONFIG_IPV6)
+ sk->sk_v6_rcv_saddr = sc->inet6.saddr = sin6_addr;
+#else
+ sc->inet6.saddr = sin6_addr;
+#endif
+ sc->inet6.flow_label = sin6_flowinfo;
+ sk->sk_num = sport;
+ sc->inet.inet_sport = htons(sport);
+
+ if (_is_any_addr)
+ *_is_any_addr = is_any_addr;
+ return 0;
+}
+
+int smbdirect_socket_sync_daddr_to_sk(struct smbdirect_socket *sc)
+{
+ struct sock *sk = &sc->sk;
+ const struct sockaddr_storage *daddr;
+ const struct sockaddr_in *sin;
+ const struct sockaddr_in6 *sin6;
+ struct in_addr sin_addr = { .s_addr = htonl(INADDR_ANY), };
+#if IS_ENABLED(CONFIG_IPV6)
+ struct in6_addr sin6_addr = in6addr_any;
+#endif
+ u16 dport = 0;
+ int ret;
+
+ daddr = &sc->rdma.cm_id->route.addr.dst_addr;
+
+ if (WARN_ON_ONCE(daddr->ss_family != sk->sk_family)) {
+ ret = -EINVAL;
+ return ret;
+ }
+
+ switch (daddr->ss_family) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)daddr;
+ dport = ntohs(sin->sin_port);
+ sin_addr = sin->sin_addr;
+ break;
+
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)daddr;
+ dport = ntohs(sin6->sin6_port);
+ sin_addr.s_addr = LOOPBACK4_IPV6;
+#if IS_ENABLED(CONFIG_IPV6)
+ sin6_addr = sin6->sin6_addr;
+#endif
+ break;
+ }
+
+ sk->sk_daddr = sc->inet.inet_daddr = sin_addr.s_addr;
+#if IS_ENABLED(CONFIG_IPV6)
+ sk->sk_v6_daddr = sin6_addr;
+#endif
+ sk->sk_dport = sc->inet.inet_dport = htons(dport);
+
+ return 0;
+}
+
static void smbdirect_socket_wake_up_all(struct smbdirect_socket *sc)
{
/*
@@ -257,6 +359,38 @@ static void smbdirect_socket_wake_up_all(struct smbdirect_socket *sc)
wake_up_all(&sc->recv_io.reassembly.wait_queue);
wake_up_all(&sc->rw_io.credits.wait_queue);
wake_up_all(&sc->mr_io.ready.wait_queue);
+
+ if (sc->sk.sk_family) {
+ struct sock *sk = &sc->sk;
+
+ WRITE_ONCE(sk->sk_shutdown, SHUTDOWN_MASK);
+
+ WARN_ON_ONCE(sc->first_error == 0);
+ if (sc->first_error < 0)
+ WRITE_ONCE(sk->sk_err, -sc->first_error);
+ else
+ WRITE_ONCE(sk->sk_err, sc->first_error);
+
+ if (sc->status >= SMBDIRECT_SOCKET_DISCONNECTED) {
+ inet_sk_set_state(sk, TCP_CLOSE);
+ if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+ sk->sk_socket->state = SS_UNCONNECTED;
+ } else {
+ inet_sk_set_state(sk, TCP_CLOSING);
+ if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+ sk->sk_socket->state = SS_DISCONNECTING;
+ }
+
+ /*
+ * Note tcp_done_with_error() also calls both
+ * sk->sk_state_change(sk) via tcp_done()
+ * and sk_error_report() directly.
+ */
+ if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+ sk->sk_state_change(sk);
+ if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+ sk_error_report(sk);
+ }
}
void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
@@ -510,11 +644,13 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
*/
smbdirect_socket_wake_up_all(sc);
+ smbdirect_socket_sk_unlock(sc);
disable_work_sync(&sc->disconnect_work);
disable_work_sync(&sc->connect.work);
disable_work_sync(&sc->recv_io.posted.refill_work);
disable_work_sync(&sc->idle.immediate_work);
disable_delayed_work_sync(&sc->idle.timer_work);
+ smbdirect_socket_sk_lock(sc);
if (sc->rdma.cm_id)
rdma_lock_handler(sc->rdma.cm_id);
@@ -600,6 +736,8 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
*/
WARN_ON_ONCE(in_interrupt());
+ smbdirect_socket_sk_owned_by_me(sc);
+
/*
* First we try to disable the work
* without disable_work_sync() in a
@@ -625,7 +763,9 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"cancelling and disable disconnect_work\n");
+ smbdirect_socket_sk_unlock(sc);
disable_work_sync(&sc->disconnect_work);
+ smbdirect_socket_sk_lock(sc);
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"destroying rdma session\n");
@@ -634,7 +774,9 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) {
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"wait for transport being disconnected\n");
+ smbdirect_socket_sk_unlock(sc);
wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED);
+ smbdirect_socket_sk_lock(sc);
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
"waited for transport being disconnected\n");
}
@@ -723,6 +865,8 @@ int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc,
{
int ret;
+ smbdirect_socket_sk_owned_by_me(sc);
+
if (WARN_ON_ONCE(needed < 0))
return -EINVAL;
@@ -731,9 +875,12 @@ int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc,
return 0;
atomic_add(needed, total_credits);
+
+ smbdirect_socket_sk_unlock(sc);
ret = wait_event_interruptible(*waitq,
atomic_read(total_credits) >= needed ||
sc->status != expected_status);
+ smbdirect_socket_sk_lock(sc);
if (sc->status != expected_status)
return unexpected_errno;
diff --git a/fs/smb/common/smbdirect/smbdirect_socket.h b/fs/smb/common/smbdirect/smbdirect_socket.h
index c09eddd8ad16..6bb201683259 100644
--- a/fs/smb/common/smbdirect/smbdirect_socket.h
+++ b/fs/smb/common/smbdirect/smbdirect_socket.h
@@ -104,6 +104,18 @@ enum smbdirect_keepalive_status {
};
struct smbdirect_socket {
+ union {
+ struct sock sk;
+ struct inet_sock inet;
+ };
+ /* needed by inet6_create() */
+ struct ipv6_pinfo inet6;
+ void (*orig_sk_destruct)(struct sock *sk);
+
+ /*
+ * This is the first element that is
+ * initialized in smbdirect_socket_init()
+ */
enum smbdirect_socket_status status;
wait_queue_head_t status_wait;
int first_error;
@@ -548,14 +560,18 @@ static void __smbdirect_log_printf(struct smbdirect_socket *sc,
__smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_RW, fmt, ##args)
#define smbdirect_log_negotiate(sc, lvl, fmt, args...) \
__smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_NEGOTIATE, fmt, ##args)
+#define smbdirect_log_sk(sc, lvl, fmt, args...) \
+ __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_SK, fmt, ##args)
static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
{
+ const size_t status_offset = offsetof(struct smbdirect_socket, status);
+
/*
* This also sets status = SMBDIRECT_SOCKET_CREATED
*/
BUILD_BUG_ON(SMBDIRECT_SOCKET_CREATED != 0);
- memset(sc, 0, sizeof(*sc));
+ memset(((u8 *)sc)+status_offset, 0, sizeof(*sc)-status_offset);
init_waitqueue_head(&sc->status_wait);
@@ -700,6 +716,14 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
__SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, \
__SMBDIRECT_SOCKET_DISCONNECT(__sc);)
+static __always_inline struct smbdirect_socket *
+smbdirect_socket_from_sk(const struct sock *sk)
+{
+ WARN_ON_ONCE(!sk);
+ BUILD_BUG_ON(offsetof(struct smbdirect_socket, sk) != 0);
+ return container_of(sk, struct smbdirect_socket, sk);
+}
+
struct smbdirect_send_io {
struct smbdirect_socket *socket;
struct ib_cqe cqe;
--
2.43.0
^ permalink raw reply related
* [PATCH 7/8] smb: client: make use of IPPROTO_SMBDIRECT sockets
From: Stefan Metzmacher @ 2026-04-07 14:46 UTC (permalink / raw)
To: linux-cifs, samba-technical
Cc: metze, Steve French, Tom Talpey, Long Li, Namjae Jeon,
David Howells, Henrique Carvalho, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
Willem de Bruijn, netdev, Xin Long, quic, linux-rdma,
linux-kernel
In-Reply-To: <cover.1775571957.git.metze@samba.org>
This reduces the function calls for special smbdirect features to
- smbdirect_socket_from_sock()
- smbdirect_socket_set_logging()
- smbdirect_socket_set_initial_parameters()
- smbdirect_socket_set_kernel_settings()
- smbdirect_socket_get_current_parameters()
- smbdirect_connection_is_connected()
- smbdirect_connection_send_single_iter()
- smbdirect_connection_send_wait_zero_pending()
- smbdirect_connection_register_mr_io()
- smbdirect_mr_io_fill_buffer_descriptor()
- smbdirect_connection_deregister_mr_io()
- smbdirect_connection_legacy_debug_proc_show()
In future with modifications from David Howells
we'll also be able to use sock_sendmsg() and avoid
direct calls to smbdirect_connection_send_single_iter() and
smbdirect_connection_send_wait_zero_pending().
This will also make it easier to implement the QUIC
transport Henrique Carvalho is working on.
In the end the core smb handling should just make
use of a SOCK_STREAM socket, after the initial setup.
There's still a way to go, but almost all changes
will be done within the smbdirect code, e.g.
adding MSG_SPLICE_PAGES support or
splice_read/read_sock/read_skb.
But it's a good start, which will make changes
much easier.
Cc: Steve French <smfrench@gmail.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Long Li <longli@microsoft.com>
Cc: Namjae Jeon <linkinjeon@kernel.org>
Cc: David Howells <dhowells@redhat.com>
Cc: Henrique Carvalho <henrique.carvalho@suse.com>
Cc: linux-cifs@vger.kernel.org
Cc: samba-technical@lists.samba.org
Cc: David S. Miller <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>
Cc: Kuniyuki Iwashima <kuniyu@google.com>
Cc: Willem de Bruijn <willemb@google.com>
Cc: netdev@vger.kernel.org
Cc: Xin Long <lucien.xin@gmail.com>
Cc: quic@lists.linux.dev
Cc: linux-rdma@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
---
fs/smb/client/cifs_debug.c | 2 +-
fs/smb/client/cifsfs.c | 2 +-
fs/smb/client/cifsglob.h | 7 +-
fs/smb/client/connect.c | 123 +++++++++--------
fs/smb/client/file.c | 8 +-
fs/smb/client/sess.c | 4 +-
fs/smb/client/smb2ops.c | 8 +-
fs/smb/client/smb2pdu.c | 10 +-
fs/smb/client/smbdirect.c | 275 +++++++++++--------------------------
fs/smb/client/smbdirect.h | 27 +---
fs/smb/client/transport.c | 2 +-
11 files changed, 170 insertions(+), 298 deletions(-)
diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
index 0691d2a3e04b..70f30b1f8cda 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -481,7 +481,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
seq_printf(m, "\nServer capabilities: 0x%x", server->capabilities);
- if (server->rdma)
+ if (cifs_rdma_enabled(server))
seq_printf(m, "\nRDMA ");
seq_printf(m, "\nTCP status: %d Instance: %d"
"\nLocal Users To Server: %d SecMode: 0x%x Req On Wire: %d",
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index 32d0305a1239..12986050e16c 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -529,7 +529,7 @@ cifs_show_address(struct seq_file *s, struct TCP_Server_Info *server)
default:
seq_puts(s, "(unknown)");
}
- if (server->rdma)
+ if (cifs_rdma_enabled(server))
seq_puts(s, ",rdma");
}
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 709e96e07791..f7b62bcaadd9 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -35,6 +35,7 @@
#define SMB_PATH_MAX 260
#define CIFS_PORT 445
#define RFC1001_PORT 139
+#define SMBD_IWARP_PORT 5445
/*
* The sizes of various internal tables and strings
@@ -759,10 +760,8 @@ struct TCP_Server_Info {
bool sec_mskerberos; /* supports legacy MS Kerberos */
bool sec_iakerb; /* supports pass-through auth for Kerberos (krb5 proxy) */
bool large_buf; /* is current buffer large? */
- /* use SMBD connection instead of socket */
- bool rdma;
- /* point to the SMBD connection if RDMA is used instead of socket */
- struct smbd_connection *smbd_conn;
+ int ipproto; /* IPPROTO_TCP or IPOROTO_SMBDIRECT */
+#define cifs_rdma_enabled(__server) ((__server)->ipproto == IPPROTO_SMBDIRECT)
struct delayed_work echo; /* echo ping workqueue job */
char *smallbuf; /* pointer to current "small" buffer */
char *bigbuf; /* pointer to current "big" buffer */
diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
index 69b38f0ccf2b..d42abe1d3772 100644
--- a/fs/smb/client/connect.c
+++ b/fs/smb/client/connect.c
@@ -313,8 +313,6 @@ cifs_abort_connection(struct TCP_Server_Info *server)
server->ssocket->flags);
sock_release(server->ssocket);
server->ssocket = NULL;
- } else if (cifs_rdma_enabled(server)) {
- smbd_destroy(server);
}
server->sequence_number = 0;
server->session_estab = false;
@@ -408,10 +406,7 @@ static int __cifs_reconnect(struct TCP_Server_Info *server,
cifs_dbg(FYI, "%s: reconn_set_ipaddr_from_hostname: rc=%d\n", __func__, rc);
}
- if (cifs_rdma_enabled(server))
- rc = smbd_reconnect(server);
- else
- rc = generic_ip_connect(server);
+ rc = generic_ip_connect(server);
if (rc) {
cifs_server_unlock(server);
cifs_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
@@ -468,10 +463,7 @@ static int __reconnect_target_locked(struct TCP_Server_Info *server,
cifs_dbg(FYI, "%s: reconn_set_ipaddr_from_hostname: rc=%d\n", __func__, rc);
}
/* Reconnect the socket */
- if (cifs_rdma_enabled(server))
- rc = smbd_reconnect(server);
- else
- rc = generic_ip_connect(server);
+ rc = generic_ip_connect(server);
return rc;
}
@@ -746,10 +738,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg)
if (server_unresponsive(server))
return -ECONNABORTED;
- if (cifs_rdma_enabled(server) && server->smbd_conn)
- length = smbd_recv(server->smbd_conn, smb_msg);
- else
- length = sock_recvmsg(server->ssocket, smb_msg, 0);
+ length = sock_recvmsg(server->ssocket, smb_msg, 0);
spin_lock(&server->srv_lock);
if (server->tcpStatus == CifsExiting) {
@@ -1088,8 +1077,6 @@ clean_demultiplex_info(struct TCP_Server_Info *server)
wake_up_all(&server->request_q);
/* give those requests time to exit */
msleep(125);
- if (cifs_rdma_enabled(server))
- smbd_destroy(server);
if (server->ssocket) {
sock_release(server->ssocket);
server->ssocket = NULL;
@@ -1542,7 +1529,7 @@ match_port(struct TCP_Server_Info *server, struct sockaddr *addr)
__be16 port, *sport;
/* SMBDirect manages its own ports, don't match it here */
- if (server->rdma)
+ if (cifs_rdma_enabled(server))
return true;
switch (addr->sa_family) {
@@ -1649,7 +1636,7 @@ static int match_server(struct TCP_Server_Info *server,
if (server->echo_interval != ctx->echo_interval * HZ)
return 0;
- if (server->rdma != ctx->rdma)
+ if (cifs_rdma_enabled(server) != ctx->rdma)
return 0;
if (server->ignore_signature != ctx->ignore_signature)
@@ -1791,7 +1778,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
tcp_ses->noblocksnd = ctx->noblocksnd || ctx->rootfs;
tcp_ses->noautotune = ctx->noautotune;
tcp_ses->tcp_nodelay = ctx->sockopt_tcp_nodelay;
- tcp_ses->rdma = ctx->rdma;
+ tcp_ses->ipproto = ctx->rdma ? IPPROTO_SMBDIRECT : IPPROTO_TCP;
tcp_ses->in_flight = 0;
tcp_ses->max_in_flight = 0;
tcp_ses->credits = 1;
@@ -1844,29 +1831,18 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
++tcp_ses->srv_count;
tcp_ses->echo_interval = ctx->echo_interval * HZ;
- if (tcp_ses->rdma) {
-#ifndef CONFIG_CIFS_SMB_DIRECT
- cifs_dbg(VFS, "CONFIG_CIFS_SMB_DIRECT is not enabled\n");
- rc = -ENOENT;
- goto out_err_crypto_release;
-#endif
- tcp_ses->smbd_conn = smbd_get_connection(
- tcp_ses, (struct sockaddr *)&ctx->dstaddr);
- if (tcp_ses->smbd_conn) {
- cifs_dbg(VFS, "RDMA transport established\n");
- rc = 0;
- goto smbd_connected;
- } else {
+ rc = ip_connect(tcp_ses);
+ if (rc < 0) {
+ if (cifs_rdma_enabled(tcp_ses) && rc == -EPROTONOSUPPORT) {
+ cifs_dbg(VFS, "CONFIG_CIFS_SMB_DIRECT is not enabled\n");
rc = -ENOENT;
goto out_err_crypto_release;
}
- }
- rc = ip_connect(tcp_ses);
- if (rc < 0) {
cifs_dbg(VFS, "Error connecting to socket. Aborting operation.\n");
goto out_err_crypto_release;
}
-smbd_connected:
+ if (cifs_rdma_enabled(tcp_ses))
+ cifs_dbg(VFS, "RDMA transport established\n");
/*
* since we're in a cifs function already, we know that
* this will succeed. No need for try_module_get().
@@ -3331,6 +3307,7 @@ generic_ip_connect(struct TCP_Server_Info *server)
struct sockaddr *saddr;
struct socket *socket;
int slen, sfamily;
+ const char *pname = cifs_rdma_enabled(server) ? "smbdirect" : "tcp";
__be16 sport;
int rc = 0;
@@ -3342,28 +3319,32 @@ generic_ip_connect(struct TCP_Server_Info *server)
sport = ipv6->sin6_port;
slen = sizeof(struct sockaddr_in6);
sfamily = AF_INET6;
- cifs_dbg(FYI, "%s: connecting to [%pI6]:%d\n", __func__, &ipv6->sin6_addr,
- ntohs(sport));
+ cifs_dbg(FYI, "%s: connecting with %s to [%pI6]:%d\n",
+ __func__, pname, &ipv6->sin6_addr, ntohs(sport));
} else {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)&server->dstaddr;
sport = ipv4->sin_port;
slen = sizeof(struct sockaddr_in);
sfamily = AF_INET;
- cifs_dbg(FYI, "%s: connecting to %pI4:%d\n", __func__, &ipv4->sin_addr,
- ntohs(sport));
+ cifs_dbg(FYI, "%s: connecting with %s to %pI4:%d\n",
+ __func__, pname, &ipv4->sin_addr, ntohs(sport));
}
if (server->ssocket) {
- socket = server->ssocket;
- } else {
+ sock_release(server->ssocket);
+ server->ssocket = NULL;
+ }
+
+ {
struct net *net = cifs_net_ns(server);
struct sock *sk;
rc = sock_create_kern(net, sfamily, SOCK_STREAM,
- IPPROTO_TCP, &server->ssocket);
+ server->ipproto, &server->ssocket);
if (rc < 0) {
- cifs_server_dbg(VFS, "Error %d creating socket\n", rc);
+ cifs_server_dbg(VFS, "Error %d creating %s socket\n",
+ rc, pname);
return rc;
}
@@ -3371,7 +3352,7 @@ generic_ip_connect(struct TCP_Server_Info *server)
sk_net_refcnt_upgrade(sk);
/* BB other socket options to set KEEPALIVE, NODELAY? */
- cifs_dbg(FYI, "Socket created\n");
+ cifs_dbg(FYI, "%s socket created\n", pname);
socket = server->ssocket;
socket->sk->sk_allocation = GFP_NOFS;
socket->sk->sk_use_task_frag = false;
@@ -3382,8 +3363,10 @@ generic_ip_connect(struct TCP_Server_Info *server)
}
rc = bind_socket(server);
- if (rc < 0)
- return rc;
+ if (rc < 0) {
+ cifs_dbg(FYI, "Error %d bind_socket() %s\n", rc, pname);
+ goto close_socket;
+ }
/*
* Eventually check for other socket options to change from
@@ -3404,6 +3387,17 @@ generic_ip_connect(struct TCP_Server_Info *server)
if (server->tcp_nodelay)
tcp_sock_set_nodelay(socket->sk);
+ switch (server->ipproto) {
+ case IPPROTO_SMBDIRECT:
+ rc = smbd_prepare_socket(server, ntohs(sport));
+ if (rc < 0) {
+ cifs_dbg(FYI, "Error %d from smbd_prepare_socket(port=%u)\n",
+ rc, ntohs(sport));
+ goto close_socket;
+ }
+ break;
+ }
+
cifs_dbg(FYI, "sndbuf %d rcvbuf %d rcvtimeo 0x%lx\n",
socket->sk->sk_sndbuf,
socket->sk->sk_rcvbuf, socket->sk->sk_rcvtimeo);
@@ -3418,11 +3412,8 @@ generic_ip_connect(struct TCP_Server_Info *server)
if (server->noblockcnt && rc == -EINPROGRESS)
rc = 0;
if (rc < 0) {
- cifs_dbg(FYI, "Error %d connecting to server\n", rc);
- trace_smb3_connect_err(server->hostname, server->conn_id, &server->dstaddr, rc);
- sock_release(socket);
- server->ssocket = NULL;
- return rc;
+ cifs_dbg(FYI, "Error %d connecting with %s to server\n", rc, pname);
+ goto close_socket;
}
trace_smb3_connect_done(server->hostname, server->conn_id, &server->dstaddr);
@@ -3434,9 +3425,20 @@ generic_ip_connect(struct TCP_Server_Info *server)
*/
if (server->with_rfc1001 ||
server->rfc1001_sessinit == 1 ||
- (server->rfc1001_sessinit == -1 && sport == htons(RFC1001_PORT)))
+ (server->rfc1001_sessinit == -1 && sport == htons(RFC1001_PORT))) {
rc = ip_rfc1001_connect(server);
+ if (rc < 0) {
+ cifs_dbg(FYI, "Error %d from ip_rfc1001_connect\n", rc);
+ goto close_socket;
+ }
+ }
+
+ return rc;
+close_socket:
+ trace_smb3_connect_err(server->hostname, server->conn_id, &server->dstaddr, rc);
+ sock_release(socket);
+ server->ssocket = NULL;
return rc;
}
@@ -3446,6 +3448,13 @@ ip_connect(struct TCP_Server_Info *server)
__be16 *sport;
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr;
struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr;
+ u16 port1 = CIFS_PORT;
+ u16 port2 = RFC1001_PORT;
+
+ if (cifs_rdma_enabled(server)) {
+ port1 = SMBD_IWARP_PORT;
+ port2 = CIFS_PORT;
+ }
if (server->dstaddr.ss_family == AF_INET6)
sport = &addr6->sin6_port;
@@ -3455,15 +3464,15 @@ ip_connect(struct TCP_Server_Info *server)
if (*sport == 0) {
int rc;
- /* try with 445 port at first */
- *sport = htons(CIFS_PORT);
+ /* try with port1 at first */
+ *sport = htons(port1);
rc = generic_ip_connect(server);
if (rc >= 0)
return rc;
- /* if it failed, try with 139 port */
- *sport = htons(RFC1001_PORT);
+ /* if it failed, try with port2 */
+ *sport = htons(port2);
}
return generic_ip_connect(server);
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index a69e05f86d7e..8ca9f60ff429 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -97,9 +97,9 @@ static void cifs_prepare_write(struct netfs_io_subrequest *subreq)
cifs_trace_rw_credits_write_prepare);
#ifdef CONFIG_CIFS_SMB_DIRECT
- if (server->smbd_conn) {
+ if (cifs_rdma_enabled(server)) {
const struct smbdirect_socket_parameters *sp =
- smbd_get_parameters(server->smbd_conn);
+ smbd_get_parameters(server);
stream->sreq_max_segs = sp->max_frmr_depth;
}
@@ -191,9 +191,9 @@ static int cifs_prepare_read(struct netfs_io_subrequest *subreq)
cifs_trace_rw_credits_read_submit);
#ifdef CONFIG_CIFS_SMB_DIRECT
- if (server->smbd_conn) {
+ if (cifs_rdma_enabled(server)) {
const struct smbdirect_socket_parameters *sp =
- smbd_get_parameters(server->smbd_conn);
+ smbd_get_parameters(server);
rreq->io_streams[0].sreq_max_segs = sp->max_frmr_depth;
}
diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c
index 698bd27119ae..9e79439002e6 100644
--- a/fs/smb/client/sess.c
+++ b/fs/smb/client/sess.c
@@ -207,7 +207,7 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
list_for_each_entry_safe_from(iface, niface, &ses->iface_list,
iface_head) {
/* do not mix rdma and non-rdma interfaces */
- if (iface->rdma_capable != ses->server->rdma)
+ if (iface->rdma_capable != cifs_rdma_enabled(ses->server))
continue;
/* skip ifaces that are unusable */
@@ -395,7 +395,7 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server)
}
/* do not mix rdma and non-rdma interfaces */
- if (iface->rdma_capable != server->rdma)
+ if (iface->rdma_capable != cifs_rdma_enabled(server))
continue;
if (!iface->is_active ||
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 509fcea28a42..208797de0c31 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -503,9 +503,9 @@ smb3_negotiate_wsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
wsize = ctx->got_wsize ? ctx->vol_wsize : SMB3_DEFAULT_IOSIZE;
wsize = min_t(unsigned int, wsize, server->max_write);
#ifdef CONFIG_CIFS_SMB_DIRECT
- if (server->rdma) {
+ if (cifs_rdma_enabled(server)) {
const struct smbdirect_socket_parameters *sp =
- smbd_get_parameters(server->smbd_conn);
+ smbd_get_parameters(server);
if (server->sign)
/*
@@ -554,9 +554,9 @@ smb3_negotiate_rsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
rsize = ctx->got_rsize ? ctx->vol_rsize : SMB3_DEFAULT_IOSIZE;
rsize = min_t(unsigned int, rsize, server->max_read);
#ifdef CONFIG_CIFS_SMB_DIRECT
- if (server->rdma) {
+ if (cifs_rdma_enabled(server)) {
const struct smbdirect_socket_parameters *sp =
- smbd_get_parameters(server->smbd_conn);
+ smbd_get_parameters(server);
if (server->sign)
/*
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index 957aca2222b5..e954c75b36da 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -4472,7 +4472,7 @@ static inline bool smb3_use_rdma_offload(struct cifs_io_parms *io_parms)
return false;
/* we can only offload on an rdma connection */
- if (!server->rdma || !server->smbd_conn)
+ if (!cifs_rdma_enabled(server))
return false;
/* we don't support signed offload yet */
@@ -4483,6 +4483,10 @@ static inline bool smb3_use_rdma_offload(struct cifs_io_parms *io_parms)
if (smb3_encryption_required(tcon))
return false;
+ /* this implicitly sets up server->rdma_readwrite_threshold */
+ if (!smbd_get_parameters(server))
+ return false;
+
/* offload also has its overhead, so only do it if desired */
if (io_parms->length < server->rdma_readwrite_threshold)
return false;
@@ -4540,7 +4544,7 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
struct smbdirect_buffer_descriptor_v1 *v1;
bool need_invalidate = server->dialect == SMB30_PROT_ID;
- rdata->mr = smbd_register_mr(server->smbd_conn, &rdata->subreq.io_iter,
+ rdata->mr = smbd_register_mr(server, &rdata->subreq.io_iter,
true, need_invalidate);
if (!rdata->mr)
return -EAGAIN;
@@ -5134,7 +5138,7 @@ smb2_async_writev(struct cifs_io_subrequest *wdata)
struct smbdirect_buffer_descriptor_v1 *v1;
bool need_invalidate = server->dialect == SMB30_PROT_ID;
- wdata->mr = smbd_register_mr(server->smbd_conn, &wdata->subreq.io_iter,
+ wdata->mr = smbd_register_mr(server, &wdata->subreq.io_iter,
false, need_invalidate);
if (!wdata->mr) {
rc = -EAGAIN;
diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c
index 9e67adcdc7d3..7d4fa896efc0 100644
--- a/fs/smb/client/smbdirect.c
+++ b/fs/smb/client/smbdirect.c
@@ -11,10 +11,6 @@
#include "smb2proto.h"
#include "../common/smbdirect/smbdirect_public.h"
-/* Port numbers for SMBD transport */
-#define SMB_PORT 445
-#define SMBD_PORT 5445
-
/* Address lookup and resolve timeout in ms */
#define RDMA_RESOLVE_TIMEOUT 5000
@@ -81,18 +77,9 @@ int rdma_readwrite_threshold = 4096;
/* Transport logging functions
* Logging are defined as classes. They can be OR'ed to define the actual
* logging level via module parameter smbd_logging_class
- * e.g. cifs.smbd_logging_class=0xa0 will log all log_rdma_recv() and
- * log_rdma_event()
+ * e.g. cifs.smbd_logging_class=0xa0 will log all SMBDIRECT_LOG_RDMA_RECV and
+ * SMBDIRECT_LOG_RDMA_EVENT
*/
-#define LOG_OUTGOING 0x1
-#define LOG_INCOMING 0x2
-#define LOG_READ 0x4
-#define LOG_WRITE 0x8
-#define LOG_RDMA_SEND 0x10
-#define LOG_RDMA_RECV 0x20
-#define LOG_KEEP_ALIVE 0x40
-#define LOG_RDMA_EVENT 0x80
-#define LOG_RDMA_MR 0x100
static unsigned int smbd_logging_class;
module_param(smbd_logging_class, uint, 0644);
MODULE_PARM_DESC(smbd_logging_class,
@@ -114,17 +101,6 @@ static bool smbd_logging_needed(struct smbdirect_socket *sc,
BUILD_BUG_SAME(ERR);
BUILD_BUG_SAME(INFO);
#undef BUILD_BUG_SAME
-#define BUILD_BUG_SAME(x) BUILD_BUG_ON(x != SMBDIRECT_ ##x)
- BUILD_BUG_SAME(LOG_OUTGOING);
- BUILD_BUG_SAME(LOG_INCOMING);
- BUILD_BUG_SAME(LOG_READ);
- BUILD_BUG_SAME(LOG_WRITE);
- BUILD_BUG_SAME(LOG_RDMA_SEND);
- BUILD_BUG_SAME(LOG_RDMA_RECV);
- BUILD_BUG_SAME(LOG_KEEP_ALIVE);
- BUILD_BUG_SAME(LOG_RDMA_EVENT);
- BUILD_BUG_SAME(LOG_RDMA_MR);
-#undef BUILD_BUG_SAME
if (lvl <= smbd_logging_level || cls & smbd_logging_class)
return true;
@@ -148,22 +124,7 @@ do { \
cifs_dbg(VFS, "%s:%d " fmt, __func__, __LINE__, ##args);\
} while (0)
-#define log_outgoing(level, fmt, args...) \
- log_rdma(level, LOG_OUTGOING, fmt, ##args)
-#define log_incoming(level, fmt, args...) \
- log_rdma(level, LOG_INCOMING, fmt, ##args)
-#define log_read(level, fmt, args...) log_rdma(level, LOG_READ, fmt, ##args)
-#define log_write(level, fmt, args...) log_rdma(level, LOG_WRITE, fmt, ##args)
-#define log_rdma_send(level, fmt, args...) \
- log_rdma(level, LOG_RDMA_SEND, fmt, ##args)
-#define log_rdma_recv(level, fmt, args...) \
- log_rdma(level, LOG_RDMA_RECV, fmt, ##args)
-#define log_keep_alive(level, fmt, args...) \
- log_rdma(level, LOG_KEEP_ALIVE, fmt, ##args)
-#define log_rdma_event(level, fmt, args...) \
- log_rdma(level, LOG_RDMA_EVENT, fmt, ##args)
-#define log_rdma_mr(level, fmt, args...) \
- log_rdma(level, LOG_RDMA_MR, fmt, ##args)
+#define log_write(level, fmt, args...) log_rdma(level, SMBDIRECT_LOG_WRITE, fmt, ##args)
static int smbd_post_send_full_iter(struct smbdirect_socket *sc,
struct smbdirect_send_batch *batch,
@@ -195,82 +156,24 @@ static int smbd_post_send_full_iter(struct smbdirect_socket *sc,
return bytes;
}
-/*
- * Destroy the transport and related RDMA and memory resources
- * Need to go through all the pending counters and make sure on one is using
- * the transport while it is destroyed
- */
-void smbd_destroy(struct TCP_Server_Info *server)
-{
- struct smbd_connection *info = server->smbd_conn;
-
- if (!info) {
- log_rdma_event(INFO, "rdma session already destroyed\n");
- return;
- }
-
- smbdirect_socket_release(info->socket);
-
- kfree(info);
- server->smbd_conn = NULL;
-}
-
-/*
- * Reconnect this SMBD connection, called from upper layer
- * return value: 0 on success, or actual error code
- */
-int smbd_reconnect(struct TCP_Server_Info *server)
-{
- log_rdma_event(INFO, "reconnecting rdma session\n");
-
- if (!server->smbd_conn) {
- log_rdma_event(INFO, "rdma session already destroyed\n");
- goto create_conn;
- }
-
- /*
- * This is possible if transport is disconnected and we haven't received
- * notification from RDMA, but upper layer has detected timeout
- */
- log_rdma_event(INFO, "disconnecting transport\n");
- smbd_destroy(server);
-
-create_conn:
- log_rdma_event(INFO, "creating rdma session\n");
- server->smbd_conn = smbd_get_connection(
- server, (struct sockaddr *) &server->dstaddr);
-
- if (server->smbd_conn) {
- cifs_dbg(VFS, "RDMA transport re-established\n");
- trace_smb3_smbd_connect_done(server->hostname, server->conn_id, &server->dstaddr);
- return 0;
- }
- trace_smb3_smbd_connect_err(server->hostname, server->conn_id, &server->dstaddr);
- return -ENOENT;
-}
-
/* Create a SMBD connection, called by upper layer */
-static struct smbd_connection *_smbd_get_connection(
- struct TCP_Server_Info *server, struct sockaddr *dstaddr, int port)
+int smbd_prepare_socket(struct TCP_Server_Info *server, int port)
{
- struct net *net = cifs_net_ns(server);
- struct smbd_connection *info;
- struct smbdirect_socket *sc;
+ struct smbdirect_socket *sc = NULL;
struct smbdirect_socket_parameters init_params = {};
struct smbdirect_socket_parameters *sp;
- __be16 *sport;
u64 port_flags = 0;
int ret;
switch (port) {
- case SMBD_PORT:
+ case SMBD_IWARP_PORT:
/*
* only allow iWarp devices
* for port 5445.
*/
port_flags |= SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW;
break;
- case SMB_PORT:
+ case CIFS_PORT:
/*
* only allow InfiniBand, RoCEv1 or RoCEv2
* devices for port 445.
@@ -281,6 +184,12 @@ static struct smbd_connection *_smbd_get_connection(
break;
}
+ if (server && cifs_rdma_enabled(server) && server->ssocket)
+ sc = smbdirect_socket_from_sock(server->ssocket);
+
+ if (WARN_ON_ONCE(!sc))
+ return -ENOTCONN;
+
/*
* Create the initial parameters
*/
@@ -301,107 +210,40 @@ static struct smbd_connection *_smbd_get_connection(
sp->keepalive_interval_msec = smbd_keep_alive_interval * 1000;
sp->keepalive_timeout_msec = KEEPALIVE_RECV_TIMEOUT * 1000;
- info = kzalloc_obj(*info);
- if (!info)
- return NULL;
- ret = smbdirect_socket_create_kern(net, &sc);
- if (ret)
- goto socket_init_failed;
smbdirect_socket_set_logging(sc, NULL, smbd_logging_needed, smbd_logging_vaprintf);
ret = smbdirect_socket_set_initial_parameters(sc, sp);
if (ret)
- goto set_params_failed;
+ return ret;
ret = smbdirect_socket_set_kernel_settings(sc, IB_POLL_SOFTIRQ, GFP_KERNEL);
if (ret)
- goto set_settings_failed;
-
- if (dstaddr->sa_family == AF_INET6)
- sport = &((struct sockaddr_in6 *)dstaddr)->sin6_port;
- else
- sport = &((struct sockaddr_in *)dstaddr)->sin_port;
-
- *sport = htons(port);
+ return ret;
- ret = smbdirect_connect_sync(sc, dstaddr);
- if (ret) {
- log_rdma_event(ERR, "connect to %pISpsfc failed: %1pe\n",
- dstaddr, ERR_PTR(ret));
- goto connect_failed;
- }
-
- info->socket = sc;
- return info;
-
-connect_failed:
-set_settings_failed:
-set_params_failed:
- smbdirect_socket_release(sc);
-socket_init_failed:
- kfree(info);
- return NULL;
+ return 0;
}
-const struct smbdirect_socket_parameters *smbd_get_parameters(struct smbd_connection *conn)
+const struct smbdirect_socket_parameters *smbd_get_parameters(struct TCP_Server_Info *server)
{
- if (unlikely(!conn->socket)) {
- static const struct smbdirect_socket_parameters zero_params;
+ struct smbdirect_socket *sc = NULL;
+ const struct smbdirect_socket_parameters *sp = NULL;
- return &zero_params;
- }
-
- return smbdirect_socket_get_current_parameters(conn->socket);
-}
+ if (server && cifs_rdma_enabled(server) && server->ssocket)
+ sc = smbdirect_socket_from_sock(server->ssocket);
-struct smbd_connection *smbd_get_connection(
- struct TCP_Server_Info *server, struct sockaddr *dstaddr)
-{
- struct smbd_connection *ret;
- const struct smbdirect_socket_parameters *sp;
- int port = SMBD_PORT;
-
-try_again:
- ret = _smbd_get_connection(server, dstaddr, port);
+ if (!sc) {
+ static const struct smbdirect_socket_parameters zero_params;
- /* Try SMB_PORT if SMBD_PORT doesn't work */
- if (!ret && port == SMBD_PORT) {
- port = SMB_PORT;
- goto try_again;
+ return &zero_params;
}
- if (!ret)
- return NULL;
- sp = smbd_get_parameters(ret);
+ sp = smbdirect_socket_get_current_parameters(sc);
- server->rdma_readwrite_threshold =
- rdma_readwrite_threshold > sp->max_fragmented_send_size ?
- sp->max_fragmented_send_size :
- rdma_readwrite_threshold;
+ if (server->rdma_readwrite_threshold == 0)
+ server->rdma_readwrite_threshold =
+ rdma_readwrite_threshold > sp->max_fragmented_send_size ?
+ sp->max_fragmented_send_size :
+ rdma_readwrite_threshold;
- return ret;
-}
-
-/*
- * Receive data from the transport's receive reassembly queue
- * All the incoming data packets are placed in reassembly queue
- * iter: the buffer to read data into
- * size: the length of data to read
- * return value: actual data read
- *
- * Note: this implementation copies the data from reassembly queue to receive
- * buffers used by upper layer. This is not the optimal code path. A better way
- * to do it is to not have upper layer allocate its receive buffers but rather
- * borrow the buffer from reassembly queue, and return it after data is
- * consumed. But this will require more changes to upper layer code, and also
- * need to consider packet boundaries while they still being reassembled.
- */
-int smbd_recv(struct smbd_connection *info, struct msghdr *msg)
-{
- struct smbdirect_socket *sc = info->socket;
-
- if (!smbdirect_connection_is_connected(sc))
- return -ENOTCONN;
-
- return smbdirect_connection_recvmsg(sc, msg, 0);
+ return sp;
}
/*
@@ -410,12 +252,11 @@ int smbd_recv(struct smbd_connection *info, struct msghdr *msg)
* rqst: the data to write
* return value: 0 if successfully write, otherwise error code
*/
-int smbd_send(struct TCP_Server_Info *server,
+static int smbd_send_locked(struct TCP_Server_Info *server,
int num_rqst, struct smb_rqst *rqst_array)
{
- struct smbd_connection *info = server->smbd_conn;
- struct smbdirect_socket *sc = info->socket;
- const struct smbdirect_socket_parameters *sp = smbd_get_parameters(info);
+ struct smbdirect_socket *sc = NULL;
+ const struct smbdirect_socket_parameters *sp = smbd_get_parameters(server);
struct smb_rqst *rqst;
struct iov_iter iter;
struct smbdirect_send_batch_storage bstorage;
@@ -424,6 +265,9 @@ int smbd_send(struct TCP_Server_Info *server,
int rc, i, rqst_idx;
int error = 0;
+ if (server && cifs_rdma_enabled(server) && server->ssocket)
+ sc = smbdirect_socket_from_sock(server->ssocket);
+
if (!smbdirect_connection_is_connected(sc))
return -EAGAIN;
@@ -508,6 +352,28 @@ int smbd_send(struct TCP_Server_Info *server,
return 0;
}
+int smbd_send(struct TCP_Server_Info *server,
+ int num_rqst, struct smb_rqst *rqst_array)
+{
+ struct smbdirect_socket *sc = NULL;
+ struct sock *sk = NULL;
+ int ret;
+
+ if (server && cifs_rdma_enabled(server) && server->ssocket) {
+ sc = smbdirect_socket_from_sock(server->ssocket);
+ sk = server->ssocket->sk;
+ }
+
+ if (!smbdirect_connection_is_connected(sc))
+ return -EAGAIN;
+
+ lock_sock(sk);
+ ret = smbd_send_locked(server, num_rqst, rqst_array);
+ release_sock(sk);
+
+ return ret;
+}
+
/*
* Register memory for RDMA read/write
* iter: the buffer to register memory with
@@ -515,11 +381,14 @@ int smbd_send(struct TCP_Server_Info *server,
* need_invalidate: true if this MR needs to be locally invalidated after I/O
* return value: the MR registered, NULL if failed.
*/
-struct smbdirect_mr_io *smbd_register_mr(struct smbd_connection *info,
+struct smbdirect_mr_io *smbd_register_mr(struct TCP_Server_Info *server,
struct iov_iter *iter,
bool writing, bool need_invalidate)
{
- struct smbdirect_socket *sc = info->socket;
+ struct smbdirect_socket *sc = NULL;
+
+ if (server && cifs_rdma_enabled(server) && server->ssocket)
+ sc = smbdirect_socket_from_sock(server->ssocket);
if (!smbdirect_connection_is_connected(sc))
return NULL;
@@ -546,15 +415,25 @@ void smbd_deregister_mr(struct smbdirect_mr_io *mr)
void smbd_debug_proc_show(struct TCP_Server_Info *server, struct seq_file *m)
{
- if (!server->rdma)
+ struct smbdirect_socket *sc = NULL;
+
+ if (!cifs_rdma_enabled(server))
return;
- if (!server->smbd_conn) {
+ if (server->ssocket) {
+ sc = smbdirect_socket_from_sock(server->ssocket);
+ /*
+ * setup server->rdma_readwrite_threshold is required
+ */
+ (void) smbd_get_parameters(server);
+ }
+
+ if (!sc) {
seq_puts(m, "\nSMBDirect transport not available");
return;
}
- smbdirect_connection_legacy_debug_proc_show(server->smbd_conn->socket,
+ smbdirect_connection_legacy_debug_proc_show(sc,
server->rdma_readwrite_threshold,
m);
}
diff --git a/fs/smb/client/smbdirect.h b/fs/smb/client/smbdirect.h
index 0017d5b2de44..44b37234d87c 100644
--- a/fs/smb/client/smbdirect.h
+++ b/fs/smb/client/smbdirect.h
@@ -8,7 +8,6 @@
#define _SMBDIRECT_H
#ifdef CONFIG_CIFS_SMB_DIRECT
-#define cifs_rdma_enabled(server) ((server)->rdma)
#include "cifsglob.h"
@@ -23,29 +22,18 @@ extern int smbd_max_send_size;
extern int smbd_send_credit_target;
extern int smbd_receive_credit_max;
-struct smbd_connection {
- struct smbdirect_socket *socket;
-};
+/* prepare a SMBDirect session */
+int smbd_prepare_socket(struct TCP_Server_Info *server, int port);
-/* Create a SMBDirect session */
-struct smbd_connection *smbd_get_connection(
- struct TCP_Server_Info *server, struct sockaddr *dstaddr);
-
-const struct smbdirect_socket_parameters *smbd_get_parameters(struct smbd_connection *conn);
-
-/* Reconnect SMBDirect session */
-int smbd_reconnect(struct TCP_Server_Info *server);
-/* Destroy SMBDirect session */
-void smbd_destroy(struct TCP_Server_Info *server);
+const struct smbdirect_socket_parameters *smbd_get_parameters(struct TCP_Server_Info *server);
/* Interface for carrying upper layer I/O through send/recv */
-int smbd_recv(struct smbd_connection *info, struct msghdr *msg);
int smbd_send(struct TCP_Server_Info *server,
int num_rqst, struct smb_rqst *rqst);
/* Interfaces to register and deregister MR for RDMA read/write */
struct smbdirect_mr_io *smbd_register_mr(
- struct smbd_connection *info, struct iov_iter *iter,
+ struct TCP_Server_Info *server, struct iov_iter *iter,
bool writing, bool need_invalidate);
void smbd_mr_fill_buffer_descriptor(struct smbdirect_mr_io *mr,
struct smbdirect_buffer_descriptor_v1 *v1);
@@ -54,13 +42,6 @@ void smbd_deregister_mr(struct smbdirect_mr_io *mr);
void smbd_debug_proc_show(struct TCP_Server_Info *server, struct seq_file *m);
#else
-#define cifs_rdma_enabled(server) 0
-struct smbd_connection {};
-static inline void *smbd_get_connection(
- struct TCP_Server_Info *server, struct sockaddr *dstaddr) {return NULL;}
-static inline int smbd_reconnect(struct TCP_Server_Info *server) {return -1; }
-static inline void smbd_destroy(struct TCP_Server_Info *server) {}
-static inline int smbd_recv(struct smbd_connection *info, struct msghdr *msg) {return -1; }
static inline int smbd_send(struct TCP_Server_Info *server, int num_rqst, struct smb_rqst *rqst) {return -1; }
#endif
diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c
index 05f8099047e1..e64becee928f 100644
--- a/fs/smb/client/transport.c
+++ b/fs/smb/client/transport.c
@@ -253,7 +253,7 @@ int __smb_send_rqst(struct TCP_Server_Info *server, int num_rqst,
if (cifs_rdma_enabled(server)) {
/* return -EAGAIN when connecting or reconnecting */
rc = -EAGAIN;
- if (server->smbd_conn)
+ if (ssocket)
rc = smbd_send(server, num_rqst, rqst);
goto smbd_done;
}
--
2.43.0
^ permalink raw reply related
* [PATCH 8/8] smb: server: make use of IPPROTO_SMBDIRECT sockets
From: Stefan Metzmacher @ 2026-04-07 14:46 UTC (permalink / raw)
To: linux-cifs, samba-technical
Cc: metze, Steve French, Tom Talpey, Long Li, Namjae Jeon,
David Howells, Henrique Carvalho, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
Willem de Bruijn, netdev, Xin Long, quic, linux-rdma,
linux-kernel
In-Reply-To: <cover.1775571957.git.metze@samba.org>
This reduces the function calls for special smbdirect features to
- smbdirect_socket_from_sock()
- smbdirect_socket_set_logging()
- smbdirect_socket_set_initial_parameters()
- smbdirect_socket_set_kernel_settings()
- smbdirect_socket_get_current_parameters()
- smbdirect_connection_is_connected()
- smbdirect_connection_rdma_xmit()
This will also make it easier to implement the QUIC
transport Henrique Carvalho is working on.
In the end the core smb handling should just make
use of a SOCK_STREAM socket, after the initial setup.
There's still a way to go, but almost all changes
will be done within the smbdirect code, e.g.
adding MSG_SPLICE_PAGES support or
splice_read/read_sock/read_skb.
But it's a good start, which will make changes
much easier.
Cc: Steve French <smfrench@gmail.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Long Li <longli@microsoft.com>
Cc: Namjae Jeon <linkinjeon@kernel.org>
Cc: David Howells <dhowells@redhat.com>
Cc: Henrique Carvalho <henrique.carvalho@suse.com>
Cc: linux-cifs@vger.kernel.org
Cc: samba-technical@lists.samba.org
Cc: David S. Miller <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>
Cc: Kuniyuki Iwashima <kuniyu@google.com>
Cc: Willem de Bruijn <willemb@google.com>
Cc: netdev@vger.kernel.org
Cc: Xin Long <lucien.xin@gmail.com>
Cc: quic@lists.linux.dev
Cc: linux-rdma@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
---
fs/smb/server/transport_rdma.c | 119 +++++++++++++++++++--------------
1 file changed, 70 insertions(+), 49 deletions(-)
diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c
index 706a2c897948..64be48165cda 100644
--- a/fs/smb/server/transport_rdma.c
+++ b/fs/smb/server/transport_rdma.c
@@ -82,13 +82,13 @@ static struct smb_direct_listener {
struct task_struct *thread;
- struct smbdirect_socket *socket;
+ struct socket *sock;
} smb_direct_ib_listener, smb_direct_iw_listener;
struct smb_direct_transport {
struct ksmbd_transport transport;
- struct smbdirect_socket *socket;
+ struct socket *sock;
};
static bool smb_direct_logging_needed(struct smbdirect_socket *sc,
@@ -165,18 +165,23 @@ void init_smbd_max_io_size(unsigned int sz)
unsigned int get_smbd_max_read_write_size(struct ksmbd_transport *kt)
{
struct smb_direct_transport *t;
+ struct smbdirect_socket *sc;
const struct smbdirect_socket_parameters *sp;
if (kt->ops != &ksmbd_smb_direct_transport_ops)
return 0;
t = SMBD_TRANS(kt);
- sp = smbdirect_socket_get_current_parameters(t->socket);
+ sc = smbdirect_socket_from_sock(t->sock);
+
+ if (!smbdirect_connection_is_connected(sc))
+ return 0;
+ sp = smbdirect_socket_get_current_parameters(sc);
return sp->max_read_write_size;
}
-static struct smb_direct_transport *alloc_transport(struct smbdirect_socket *sc)
+static struct smb_direct_transport *alloc_transport(struct socket *client_sock)
{
struct smb_direct_transport *t;
struct ksmbd_conn *conn;
@@ -184,7 +189,7 @@ static struct smb_direct_transport *alloc_transport(struct smbdirect_socket *sc)
t = kzalloc_obj(*t, KSMBD_DEFAULT_GFP);
if (!t)
return NULL;
- t->socket = sc;
+ t->sock = client_sock;
conn = ksmbd_conn_alloc();
if (!conn)
@@ -209,13 +214,13 @@ static void smb_direct_free_transport(struct ksmbd_transport *kt)
{
struct smb_direct_transport *t = SMBD_TRANS(kt);
- smbdirect_socket_release(t->socket);
+ sock_release(t->sock);
kfree(t);
}
static void free_transport(struct smb_direct_transport *t)
{
- smbdirect_socket_shutdown(t->socket);
+ kernel_sock_shutdown(t->sock, SHUT_RDWR);
ksmbd_conn_free(KSMBD_TRANS(t)->conn);
}
@@ -223,7 +228,6 @@ static int smb_direct_read(struct ksmbd_transport *t, char *buf,
unsigned int size, int unused)
{
struct smb_direct_transport *st = SMBD_TRANS(t);
- struct smbdirect_socket *sc = st->socket;
struct msghdr msg = { .msg_flags = 0, };
struct kvec iov = {
.iov_base = buf,
@@ -231,9 +235,7 @@ static int smb_direct_read(struct ksmbd_transport *t, char *buf,
};
int ret;
- iov_iter_kvec(&msg.msg_iter, ITER_DEST, &iov, 1, size);
-
- ret = smbdirect_connection_recvmsg(sc, &msg, 0);
+ ret = kernel_recvmsg(st->sock, &msg, &iov, 1, size, 0);
if (ret == -ERESTARTSYS)
ret = -EINTR;
return ret;
@@ -244,13 +246,14 @@ static int smb_direct_writev(struct ksmbd_transport *t,
bool need_invalidate, unsigned int remote_key)
{
struct smb_direct_transport *st = SMBD_TRANS(t);
- struct smbdirect_socket *sc = st->socket;
- struct iov_iter iter;
+ struct msghdr msg = { .msg_flags = MSG_NOSIGNAL, };
+ struct smbdirect_cmsg_buffer cbuffer = { .msg_control = {0, }, };
+ u32 _remote_token = remote_key;
+ const u32 *remote_token = need_invalidate ? &_remote_token : NULL;
- iov_iter_kvec(&iter, ITER_SOURCE, iov, niovs, buflen);
+ smbdirect_buffer_remote_invalidate_cmsg_prepare(&msg, &cbuffer, remote_token);
- return smbdirect_connection_send_iter(sc, &iter, 0,
- need_invalidate, remote_key);
+ return kernel_sendmsg(st->sock, &msg, iov, niovs, buflen);
}
static int smb_direct_rdma_write(struct ksmbd_transport *t,
@@ -259,7 +262,10 @@ static int smb_direct_rdma_write(struct ksmbd_transport *t,
unsigned int desc_len)
{
struct smb_direct_transport *st = SMBD_TRANS(t);
- struct smbdirect_socket *sc = st->socket;
+ struct smbdirect_socket *sc = smbdirect_socket_from_sock(st->sock);
+
+ if (!smbdirect_connection_is_connected(sc))
+ return -ENOTCONN;
return smbdirect_connection_rdma_xmit(sc, buf, buflen,
desc, desc_len, false);
@@ -271,7 +277,10 @@ static int smb_direct_rdma_read(struct ksmbd_transport *t,
unsigned int desc_len)
{
struct smb_direct_transport *st = SMBD_TRANS(t);
- struct smbdirect_socket *sc = st->socket;
+ struct smbdirect_socket *sc = smbdirect_socket_from_sock(st->sock);
+
+ if (!smbdirect_connection_is_connected(sc))
+ return -ENOTCONN;
return smbdirect_connection_rdma_xmit(sc, buf, buflen,
desc, desc_len, true);
@@ -280,7 +289,7 @@ static int smb_direct_rdma_read(struct ksmbd_transport *t,
static void smb_direct_disconnect(struct ksmbd_transport *t)
{
struct smb_direct_transport *st = SMBD_TRANS(t);
- struct smbdirect_socket *sc = st->socket;
+ struct smbdirect_socket *sc = smbdirect_socket_from_sock(st->sock);
ksmbd_debug(RDMA, "Disconnecting sc=%p\n", sc);
@@ -290,23 +299,23 @@ static void smb_direct_disconnect(struct ksmbd_transport *t)
static void smb_direct_shutdown(struct ksmbd_transport *t)
{
struct smb_direct_transport *st = SMBD_TRANS(t);
- struct smbdirect_socket *sc = st->socket;
+ struct smbdirect_socket *sc = smbdirect_socket_from_sock(st->sock);
ksmbd_debug(RDMA, "smb-direct shutdown sc=%p\n", sc);
- smbdirect_socket_shutdown(sc);
+ kernel_sock_shutdown(st->sock, SHUT_RDWR);
}
static int smb_direct_new_connection(struct smb_direct_listener *listener,
- struct smbdirect_socket *client_sc)
+ struct socket *client_sock)
{
struct smb_direct_transport *t;
struct task_struct *handler;
int ret;
- t = alloc_transport(client_sc);
+ t = alloc_transport(client_sock);
if (!t) {
- smbdirect_socket_release(client_sc);
+ sock_release(client_sock);
return -ENOMEM;
}
@@ -328,22 +337,21 @@ static int smb_direct_new_connection(struct smb_direct_listener *listener,
static int smb_direct_listener_kthread_fn(void *p)
{
struct smb_direct_listener *listener = (struct smb_direct_listener *)p;
- struct smbdirect_socket *client_sc = NULL;
+ struct socket *client_sock = NULL;
while (!kthread_should_stop()) {
- struct proto_accept_arg arg = { .err = -EINVAL, };
- long timeo = MAX_SCHEDULE_TIMEOUT;
+ int ret;
- if (!listener->socket)
+ if (!listener->sock)
break;
- client_sc = smbdirect_socket_accept(listener->socket, timeo, &arg);
- if (!client_sc && arg.err == -EINVAL)
+ ret = kernel_accept(listener->sock, &client_sock, 0);
+ if (ret == -EINVAL)
break;
- if (!client_sc)
+ if (ret)
continue;
ksmbd_debug(CONN, "connect success: accepted new connection\n");
- smb_direct_new_connection(listener, client_sc);
+ smb_direct_new_connection(listener, client_sock);
}
ksmbd_debug(CONN, "releasing socket\n");
@@ -354,8 +362,12 @@ static void smb_direct_listener_destroy(struct smb_direct_listener *listener)
{
int ret;
- if (listener->socket)
- smbdirect_socket_shutdown(listener->socket);
+ if (listener->sock) {
+ ret = kernel_sock_shutdown(listener->sock, SHUT_RDWR);
+ if (ret)
+ pr_err("Failed to shutdown socket: %d %1pe\n",
+ ret, ERR_PTR(ret));
+ }
if (listener->thread) {
ret = kthread_stop(listener->thread);
@@ -364,9 +376,9 @@ static void smb_direct_listener_destroy(struct smb_direct_listener *listener)
listener->thread = NULL;
}
- if (listener->socket) {
- smbdirect_socket_release(listener->socket);
- listener->socket = NULL;
+ if (listener->sock) {
+ sock_release(listener->sock);
+ listener->sock = NULL;
}
listener->port = 0;
@@ -376,6 +388,7 @@ static int smb_direct_listen(struct smb_direct_listener *listener,
int port)
{
struct net *net = current->nsproxy->net_ns;
+ struct socket *ksmbd_socket;
struct task_struct *kthread;
struct sockaddr_in sin = {
.sin_family = AF_INET,
@@ -410,13 +423,21 @@ static int smb_direct_listen(struct smb_direct_listener *listener,
return -ENODEV;
}
- ret = smbdirect_socket_create_kern(net, &sc);
+ ret = sock_create_kern(net, PF_INET, SOCK_STREAM, IPPROTO_SMBDIRECT,
+ &ksmbd_socket);
if (ret) {
- pr_err("smbdirect_socket_create_kern() failed: %d %1pe\n",
+ pr_err("Can't create IPPROTO_SMBDIRECT socket: %d %1pe\n",
ret, ERR_PTR(ret));
return ret;
}
+ sc = smbdirect_socket_from_sock(ksmbd_socket);
+ if (WARN_ON_ONCE(!sc)) {
+ ret = -EINVAL;
+ pr_err("smbdirect_socket_from_sock returned NULL\n");
+ goto err;
+ }
+
/*
* Create the initial parameters
*/
@@ -450,22 +471,22 @@ static int smb_direct_listen(struct smb_direct_listener *listener,
goto err;
}
- ret = smbdirect_socket_bind(sc, (struct sockaddr *)&sin);
+ ret = kernel_bind(ksmbd_socket, (struct sockaddr_unsized *)&sin, sizeof(sin));
if (ret) {
- pr_err("smbdirect_socket_bind() failed: %d %1pe\n",
+ pr_err("Failed to bind IPPROTO_SMBDIRECT socket: %d %1pe\n",
ret, ERR_PTR(ret));
goto err;
}
- ret = smbdirect_socket_listen(sc, 10);
+ ret = kernel_listen(ksmbd_socket, 10);
if (ret) {
- pr_err("Port[%d] smbdirect_socket_listen() failed: %d %1pe\n",
+ pr_err("Port[%d] kernel_listen() error: %d %1pe\n",
port, ret, ERR_PTR(ret));
goto err;
}
listener->port = port;
- listener->socket = sc;
+ listener->sock = ksmbd_socket;
kthread = kthread_run(smb_direct_listener_kthread_fn,
listener,
@@ -489,7 +510,7 @@ int ksmbd_rdma_init(void)
int ret;
smb_direct_ib_listener = smb_direct_iw_listener = (struct smb_direct_listener) {
- .socket = NULL,
+ .sock = NULL,
};
ret = smb_direct_listen(&smb_direct_ib_listener,
@@ -499,8 +520,8 @@ int ksmbd_rdma_init(void)
goto err;
}
- ksmbd_debug(RDMA, "InfiniBand/RoCEv1/RoCEv2 RDMA listener. socket=%p\n",
- smb_direct_ib_listener.socket);
+ ksmbd_debug(RDMA, "InfiniBand/RoCEv1/RoCEv2 RDMA listener. sock=%p\n",
+ smb_direct_ib_listener.sock);
ret = smb_direct_listen(&smb_direct_iw_listener,
SMB_DIRECT_PORT_IWARP);
@@ -509,8 +530,8 @@ int ksmbd_rdma_init(void)
goto err;
}
- ksmbd_debug(RDMA, "iWarp RDMA listener. socket=%p\n",
- smb_direct_iw_listener.socket);
+ ksmbd_debug(RDMA, "iWarp RDMA listener. sock=%p\n",
+ smb_direct_iw_listener.sock);
return 0;
err:
--
2.43.0
^ permalink raw reply related
* [PATCH 0/8] smb: add kernel internal IPPROTO_SMBDIRECT
From: Stefan Metzmacher @ 2026-04-07 14:46 UTC (permalink / raw)
To: linux-cifs, samba-technical
Cc: metze, Steve French, Tom Talpey, Long Li, Namjae Jeon,
David Howells, Henrique Carvalho, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
Willem de Bruijn, netdev, Xin Long, quic, linux-rdma,
linux-kernel, Mark Brown, linux-next
Hi,
as the work to unify the smbdirect code
between cifs.ko and ksmbd.ko into an smbdirect.ko
is in linux-next for a while this is the next
step to also share the code with userspace
e.g. Samba as client and server.
The SMBDIRECT protocol, defined in [MS-SMBD] by Microsoft.
It is used as wrapper around RDMA in order to provide a transport for SMB3,
but Microsoft also uses it as transport for other protocols.
SMBDIRECT works over Infiniband, RoCE and iWarp. RoCEv2 is based on IP/UDP
and iWarp is based on IP/TCP, so these use IP addresses natively.
Infiniband and RoCEv1 require IPOIB in order to be used for SMBDIRECT.
So instead of adding a PF_SMBDIRECT, which would only use AF_INET[6],
we use IPPROTO_SMBDIRECT instead, this uses a number not
allocated from IANA, as it would not appear in an IP header.
This is similar to IPPROTO_SMC, IPPROTO_MPTCP and IPPROTO_QUIC,
which are linux specific values for the socket() syscall.
socket(AF_INET, SOCK_STREAM, IPPROTO_SMBDIRECT);
socket(AF_INET6, SOCK_STREAM, IPPROTO_SMBDIRECT);
This will allow the existing smbdirect code used by
cifs.ko and ksmbd.ko to be moved behind the socket layer [1],
so that there's less special handling. Only sock_sendmsg()
sock_recvmsg() are used, so that the main stream handling
is done all the same for tcp, smbdirect and later also quic.
The special RDMA read/write handling will be via direct
function calls as they are currently done for the in kernel
consumers.
As a start __sock_create(kern=0)/sk->sk_kern_sock == 0 will
still cause a -EPROTONOSUPPORT. So only in kernel consumers
will be supported for now.
For now the core smbdirect code still supports both
modes, direct calls in indirect via the socket layer.
The core code uses if (sc->sk.sk_family) as indication
for the new socket mode. Once cifs.ko and ksmbd.ko
are converted we can remove the old mode slowly,
but I'll deferr that to a future patchset.
There's still a way to go in order to make this
as generic as tcp and quic e.g. adding MSG_SPLICE_PAGES support or
splice_read/read_sock/read_skb.
But it's a good start, which will make changes much easier.
This patchset is based on top of the smbdirect.ko patches
in ksmbd-for-next and it's also based on netdev-next
because it needs this commit from there:
8341c989ac77d712c7d6e2bce29e8a4bcb2eeae4
"net: remove addr_len argument of recvmsg() handlers"
Patches 1-4 are some preparation fixes for the
existing smbdirect.ko patchset. We may
squash them into the existing patches before
sending them to Linus. But for now I kept
them separately. I also don't cc the network and rdma
people on these...
Patch 5 defines IPPROTO_SMBDIRECT and SOL_SMBDIRECT
constants (and also reserve the number 288 for SOL_QUIC
as applications are already using that and we don't
want to conflict)
Patch 6 adds basic IPPROTO_SMBDIRECT layering
on top of the existing smbdirect code.
This is just enough in order to let cifs.ko
and ksmbd.ko use it in the following commits,
so userspace still sees -EPROTONOSUPPORT.
Patch 7 converts cifs.ko to use IPPROTO_SMBDIRECT
as much as currently possible.
Using sock_sendmsg is not yet possible, because of the
tcp_sock_set_cork usage, but that will change in future.
Patch 8 converts ksmbd.ko to use IPPROTO_SMBDIRECT.
Because of the need for netdev-next commit
8341c989ac77d712c7d6e2bce29e8a4bcb2eeae4
"net: remove addr_len argument of recvmsg() handlers"
it's a bit tricky to prepare something that would not
cause problems in linux-next.
We could merge in netdev-next completely,
but I saw commit 00f03539e3d97af925abf42992d8c46167d54243
in next-20260406, indicates some resolved conflicts
with (at least) the rdma tree, which comes
just before netdev-next, while ksmbd-for-next is merged
before all of them. So merging netdev-next into
ksmbd-for-next changes the order of netdev-next vs. rdma.
Or we add a new branch smbdirect-for-next that's
merged into linux-next after netdev-next.
Or a bit hacky but likely easier, ksmbd-for-next
just cherry-picks 8341c989ac77d712c7d6e2bce29e8a4bcb2eeae4
"net: remove addr_len argument of recvmsg() handlers"
and that cherry picked commit is reverted as
last commit in ksmbd-for-next, or only in linux-next
right before netdev-next is merged.
For now I have the patches in a branch with a
cherry picked version of the needed commit only, see
for-7.1/ipproto-smbdirect-20260407-v1 at commit:
e1972e6f1fda9842c5724b7daf4a2aa7779901a5
git fetch https://git.samba.org/metze/linux/wip.git for-7.1/ipproto-smbdirect-20260407-v1
https://git.samba.org/?p=metze/linux/wip.git;a=shortlog;h=refs/heads/for-7.1/ipproto-smbdirect-20260407-v1
It would be great to get this somehow into linux-next soon :-)
Stefan Metzmacher (8):
smb: smbdirect: change
smbdirect_socket_parameters.{initiator_depth,responder_resources} to
__u16
smb: smbdirect: fix copyright header of smbdirect.h
smb: smbdirect: fix the logic in smbdirect_socket_destroy_sync()
without an error
smb: smbdirect: let smbdirect_connection_deregister_mr_io unlock while
waiting
net: define IPPROTO_SMBDIRECT and SOL_SMBDIRECT constants
smb: smbdirect: add in kernel only support for IPPROTO_SMBDIRECT
smb: client: make use of IPPROTO_SMBDIRECT sockets
smb: server: make use of IPPROTO_SMBDIRECT sockets
fs/smb/client/cifs_debug.c | 2 +-
fs/smb/client/cifsfs.c | 2 +-
fs/smb/client/cifsglob.h | 7 +-
fs/smb/client/connect.c | 123 +-
fs/smb/client/file.c | 8 +-
fs/smb/client/sess.c | 4 +-
fs/smb/client/smb2ops.c | 8 +-
fs/smb/client/smb2pdu.c | 10 +-
fs/smb/client/smbdirect.c | 275 +--
fs/smb/client/smbdirect.h | 27 +-
fs/smb/client/transport.c | 2 +-
fs/smb/common/smbdirect/Makefile | 1 +
fs/smb/common/smbdirect/smbdirect.h | 69 +-
fs/smb/common/smbdirect/smbdirect_accept.c | 14 +-
.../common/smbdirect/smbdirect_connection.c | 58 +
fs/smb/common/smbdirect/smbdirect_devices.c | 2 +-
fs/smb/common/smbdirect/smbdirect_internal.h | 59 +-
fs/smb/common/smbdirect/smbdirect_listen.c | 49 +-
fs/smb/common/smbdirect/smbdirect_main.c | 45 +
fs/smb/common/smbdirect/smbdirect_mr.c | 18 +
fs/smb/common/smbdirect/smbdirect_proto.c | 1549 +++++++++++++++++
fs/smb/common/smbdirect/smbdirect_public.h | 3 +
fs/smb/common/smbdirect/smbdirect_rw.c | 29 +-
fs/smb/common/smbdirect/smbdirect_socket.c | 180 +-
fs/smb/common/smbdirect/smbdirect_socket.h | 26 +-
fs/smb/server/transport_rdma.c | 119 +-
include/linux/socket.h | 2 +
include/uapi/linux/in.h | 2 +
28 files changed, 2320 insertions(+), 373 deletions(-)
create mode 100644 fs/smb/common/smbdirect/smbdirect_proto.c
--
2.43.0
^ permalink raw reply
* Re: [PATCH net-next v4 1/2] net: hsr: require valid EOT supervision TLV
From: Felix Maurer @ 2026-04-07 15:04 UTC (permalink / raw)
To: Fernando Fernandez Mancera, Luka Gejak
Cc: davem, edumazet, kuba, pabeni, netdev, horms
In-Reply-To: <4cce92eb-61a0-4869-8a30-b86d65e8a675@suse.de>
On Thu, Apr 02, 2026 at 01:53:14AM +0200, Fernando Fernandez Mancera wrote:
> On 4/1/26 6:59 PM, Luka Gejak wrote:
> > On Wed Apr 1, 2026 at 4:47 PM CEST, Fernando Fernandez Mancera wrote:
> > > On 4/1/26 11:23 AM, luka.gejak@linux.dev wrote:
> > > > From: Luka Gejak <luka.gejak@linux.dev>
> > > >
> > > > Supervision frames are only valid if terminated with a zero-length EOT
> > > > TLV. The current check fails to reject non-EOT entries as the terminal
> > > > TLV, potentially allowing malformed supervision traffic.
> > > >
> > > > Fix this by strictly requiring the terminal TLV to be HSR_TLV_EOT
> > > > with a length of zero.
> > > >
> > > > Reviewed-by: Felix Maurer <fmaurer@redhat.com>
> > > > Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
> > > > ---
> > > > net/hsr/hsr_forward.c | 41 ++++++++++++++++++++++-------------------
> > > > 1 file changed, 22 insertions(+), 19 deletions(-)
> > > >
> > > > diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
> > > > index 0aca859c88cb..17b705235c4a 100644
> > > > --- a/net/hsr/hsr_forward.c
> > > > +++ b/net/hsr/hsr_forward.c
> > > > @@ -82,39 +82,42 @@ static bool is_supervision_frame(struct hsr_priv *hsr, struct sk_buff *skb)
> > > > hsr_sup_tag->tlv.HSR_TLV_length != sizeof(struct hsr_sup_payload))
> > > > return false;
> > > > - /* Get next tlv */
> > > > + /* Advance past the first TLV payload to reach next TLV header */
> > > > total_length += hsr_sup_tag->tlv.HSR_TLV_length;
> > > > - if (!pskb_may_pull(skb, total_length))
> > > > + /* Linearize next TLV header before access */
> > > > + if (!pskb_may_pull(skb, total_length + sizeof(struct hsr_sup_tlv)))
> > > > return false;
> > > > skb_pull(skb, total_length);
> > > > hsr_sup_tlv = (struct hsr_sup_tlv *)skb->data;
> > > > skb_push(skb, total_length);
> > > > - /* if this is a redbox supervision frame we need to verify
> > > > - * that more data is available
> > > > + /* Walk through TLVs to find end-of-TLV marker, skipping any unknown
> > > > + * extension TLVs to maintain forward compatibility.
> > > > */
> > > > - if (hsr_sup_tlv->HSR_TLV_type == PRP_TLV_REDBOX_MAC) {
> > > > - /* tlv length must be a length of a mac address */
> > > > - if (hsr_sup_tlv->HSR_TLV_length != sizeof(struct hsr_sup_payload))
> > > > - return false;
> > > > + for (;;) {
> > > > + if (hsr_sup_tlv->HSR_TLV_type == HSR_TLV_EOT &&
> > > > + hsr_sup_tlv->HSR_TLV_length == 0)
> > > > + return true;
> > >
> > > I do not follow this approach, why a loop? From IEC 62439-3, I do not
> > > understand that supervision frames could have multiple
> > > PRP_TLV_REDBOX_MAC TLVs. The current code handles the TLVs correctly.
> > >
> > > Which makes me wonder, how are you testing this? Do you have some
> > > hardware with HSR/PRP support that is sending these frames? If so, which
> > > one? Are you testing this using a HSR/PRP environment with purely Linux
> > > devices?
> > >
> > > Thanks,
> > > Fernando.
> > >
> > > > - /* make sure another tlv follows */
> > > > - total_length += sizeof(struct hsr_sup_tlv) + hsr_sup_tlv->HSR_TLV_length;
> > > > - if (!pskb_may_pull(skb, total_length))
> > > > + /* Validate known TLV types */
> > > > + if (hsr_sup_tlv->HSR_TLV_type == PRP_TLV_REDBOX_MAC) {
> > > > + if (hsr_sup_tlv->HSR_TLV_length !=
> > > > + sizeof(struct hsr_sup_payload))
> > > > + return false;
> > > > + }
> > > > +
> > > > + /* Advance past current TLV: header + payload */
> > > > + total_length += sizeof(struct hsr_sup_tlv) +
> > > > + hsr_sup_tlv->HSR_TLV_length;
> > > > + /* Linearize next TLV header before access */
> > > > + if (!pskb_may_pull(skb,
> > > > + total_length + sizeof(struct hsr_sup_tlv)))
> > > > return false;
> > > > - /* get next tlv */
> > > > skb_pull(skb, total_length);
> > > > hsr_sup_tlv = (struct hsr_sup_tlv *)skb->data;
> > > > skb_push(skb, total_length);
> > > > }
> >
> > Hi Fernando,
> >
> > You are right that IEC 62439-3 does not specify multiple
> > PRP_TLV_REDBOX_MAC TLVs. My intention with the loop was not to handle
> > multiple RedBox MACs, but rather to make the parser robust against
> > unknown TLV types. If a future revision of the standard or a vendor
> > extension introduces a new TLV, the loop allows the kernel to safely
> > skip over unrecognized TLVs by reading their length, ensuring it can
> > still validate the HSR_TLV_EOT marker at the end.
Hi Fernando, Luka,
> AFAIU, the TLVs must be in the right order. I don't know, it doesn't sound
> very convincing to me that we are anticipating to new TLVs. HSR/PRP isn't a
> very active protocol and it has few users in Kernel probably compare to
> other protocols because it is used in a very specific industry domain.
I agree with this. IMHO, the supervision frames should look the way they
are described in the standard, i.e., the TLVs ordered as the current
code expects them.
Luka, I'm not sure what your stance is to that? On one hand, you are
proposing a patch making the checks more strict (TLV_END && length ==
0), on the other hand you are proposing a patch accepting supervision
frames with random new or proprietary TLVs.
> If a new revision of the protocol specs is released we can always update our
> implementation.
This, especially because the supervision frames have an explicit version
number field (something we don't check at the moment, but probably
should in the future).
> Anyway, since Felix reviewed the initial patch let's wait for his review.
I'll add more notes in another reply, directly to the patch.
> > However, if the preference for the HSR subsystem is strict adherence to
> > only currently defined TLVs over forward compatibility, I completely
> > understand.
> >
> > Furthermore, I am testing this using a purely Linux environment by
> > using a virtual HSR environment on Arch Linux. I set up two network
> > namespaces connected via veth pairs and instantiated HSR interfaces.
> >
> > The nodes successfully synchronized and maintained the connection. I
> > confirmed this by observing the expected duplicate packets (DUP!)
> > during ping tests between namespaces and by verifying that supervision
> > frames were correctly parsed, allowing the nodes to populate their
> > remote node tables.
Side note: hsr exposes the node table through debugfs, that's maybe a
better way to test/verify such things.
Thanks,
Felix
^ permalink raw reply
* [PATCH v2 net-next] net: dropreason: add MACVLAN_BROADCAST_BACKLOG and IPVLAN_MULTICAST_BACKLOG
From: Eric Dumazet @ 2026-04-07 15:07 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Joe Damato, netdev, eric.dumazet, Eric Dumazet
ipvlan and macvlan use queues to process broadcast/multicast packets
from a work queue.
Under attack these queues can drop packets.
Add MACVLAN_BROADCAST_BACKLOG drop_reason for macvlan broadcast queue.
Add IPVLAN_MULTICAST_BACKLOG drop_reason for ipvlan multicast queue.
Use different reasons as some deployments use both ipvlan and macvlan.
Also change ipvlan_rcv_frame() to use SKB_DROP_REASON_DEV_READY
when the device is not UP.
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
drivers/net/ipvlan/ipvlan_core.c | 4 ++--
drivers/net/macvlan.c | 2 +-
include/net/dropreason-core.h | 12 ++++++++++++
3 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c
index 68fdb42068d0060c67d751b869a059e8cb1b6bd1..5131d722c476eaa6149c114bcfc7267ea31d7242 100644
--- a/drivers/net/ipvlan/ipvlan_core.c
+++ b/drivers/net/ipvlan/ipvlan_core.c
@@ -337,7 +337,7 @@ static int ipvlan_rcv_frame(struct ipvl_addr *addr, struct sk_buff **pskb,
*/
if (local) {
if (unlikely(!(dev->flags & IFF_UP))) {
- kfree_skb(skb);
+ kfree_skb_reason(skb, SKB_DROP_REASON_DEV_READY);
goto out;
}
@@ -596,7 +596,7 @@ static void ipvlan_multicast_enqueue(struct ipvl_port *port,
} else {
spin_unlock(&port->backlog.lock);
dev_core_stats_rx_dropped_inc(skb->dev);
- kfree_skb(skb);
+ kfree_skb_reason(skb, SKB_DROP_REASON_IPVLAN_MULTICAST_BACKLOG);
}
}
diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c
index 54c514acacc5e24fcdb88904f41fd1d29b1e34d0..9f90c598649d548ee12b0a9806c825f96a4a4d63 100644
--- a/drivers/net/macvlan.c
+++ b/drivers/net/macvlan.c
@@ -386,7 +386,7 @@ static void macvlan_broadcast_enqueue(struct macvlan_port *port,
return;
free_nskb:
- kfree_skb(nskb);
+ kfree_skb_reason(nskb, SKB_DROP_REASON_MACVLAN_BROADCAST_BACKLOG);
err:
dev_core_stats_rx_dropped_inc(skb->dev);
}
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index de61dd5dbfd9dc7d91d22d79a510d42fb69eb60a..5ad4a34b527486d3302238634f5a1a0af26db3db 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -69,6 +69,8 @@
FN(QDISC_DROP) \
FN(QDISC_BURST_DROP) \
FN(CPU_BACKLOG) \
+ FN(MACVLAN_BROADCAST_BACKLOG) \
+ FN(IPVLAN_MULTICAST_BACKLOG) \
FN(XDP) \
FN(TC_INGRESS) \
FN(UNHANDLED_PROTO) \
@@ -382,6 +384,16 @@ enum skb_drop_reason {
* netdev_max_backlog in net.rst) or RPS flow limit
*/
SKB_DROP_REASON_CPU_BACKLOG,
+ /**
+ * @SKB_DROP_REASON_MACVLAN_BROADCAST_BACKLOG: failed to enqueue the skb
+ * to macvlan broadcast queue.
+ */
+ SKB_DROP_REASON_MACVLAN_BROADCAST_BACKLOG,
+ /**
+ * @SKB_DROP_REASON_IPVLAN_MULTICAST_BACKLOG: failed to enqueue the skb
+ * to ipvlan multicast queue.
+ */
+ SKB_DROP_REASON_IPVLAN_MULTICAST_BACKLOG,
/** @SKB_DROP_REASON_XDP: dropped by XDP in input path */
SKB_DROP_REASON_XDP,
/** @SKB_DROP_REASON_TC_INGRESS: dropped in TC ingress HOOK */
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* Re: [PATCH net v6 4/4] macsec: Support VLAN-filtering lower devices
From: Cosmin Ratiu @ 2026-04-07 15:07 UTC (permalink / raw)
To: sd@queasysnail.net, pabeni@redhat.com
Cc: andrew+netdev@lunn.ch, davem@davemloft.net,
linux-kselftest@vger.kernel.org, Dragos Tatulea, shuah@kernel.org,
sdf@fomichev.me, kuba@kernel.org, horms@kernel.org,
edumazet@google.com, netdev@vger.kernel.org
In-Reply-To: <ac6BsG4IhAajip1s@krikkit>
On Thu, 2026-04-02 at 16:48 +0200, Sabrina Dubroca wrote:
> 2026-03-30, 16:01:30 +0300, Cosmin Ratiu wrote:
> > @@ -2616,14 +2616,22 @@ static int macsec_update_offload(struct
> > net_device *dev, enum macsec_offload off
> > if (!ops)
> > return -EOPNOTSUPP;
> >
> > - macsec->offload = offload;
> > -
> > ctx.secy = &macsec->secy;
> > ret = offload == MACSEC_OFFLOAD_OFF ? macsec_offload(ops-
> > >mdo_del_secy, &ctx)
> > : macsec_offload(ops-
> > >mdo_add_secy, &ctx);
> > - if (ret) {
> > - macsec->offload = prev_offload;
> > + if (ret)
> > return ret;
> > +
> > + /* Remove VLAN filters when disabling offload. */
> > + if (offload == MACSEC_OFFLOAD_OFF) {
> > + vlan_drop_rx_ctag_filter_info(dev);
> > + vlan_drop_rx_stag_filter_info(dev);
> > + }
> > + macsec->offload = offload;
> > + /* Add VLAN filters when enabling offload. */
> > + if (prev_offload == MACSEC_OFFLOAD_OFF) {
> > + vlan_get_rx_ctag_filter_info(dev);
> > + vlan_get_rx_stag_filter_info(dev);
>
> Paolo pointed me to the sashiko review for this patch
> https://sashiko.dev/#/patchset/20260330130130.989236-1-cratiu%40nvidia.com
I should make it a habit to go and look up these reports myself.
>
> A simple way to trigger this is to do s/VLAN_N_VID/500/ in
> nsim_vlan_rx_*_vid.
>
> For example:
>
> echo 1 > /sys/bus/netdevsim/new_device
> ip link add link eni1np1 macsec0 type macsec
> ip link add link macsec0 macsec0.1 type vlan id 1
> ip link add link macsec0 macsec0.1000 type vlan id 1000
> ip link set macsec0 type macsec offload mac
> cat /sys/kernel/debug/netdevsim/netdevsim1/ports/0/vlan # empty
>
>
> If this happens on a real device, the VLAN filters will be broken.
> I'm
> not sure what the right behavior would be:
>
> 1. reject the request to enable offload
> 2. switch to promiscuous mode
I implemented and tested option 1. In the unlikely scenario adding VLAN
filters prevents offloading, it's better for the driver to be explicit
and let the user turn on promisc mode themselves. Keeping track of
whether VLAN filters failed and promisc was used as a fallback adds
some extra complexity. Plus, I am not sure it is the right place for
this decision. What would be the point of IFF_UNICAST_FLT then?
Please let me know if you agree with this approach, so I can send v8
with it.
> OTOH maybe we don't need to care, since __netdev_update_features also
> (kind of) ignores those errors:
>
> echo 1 > /sys/bus/netdevsim/new_device
> ethtool -K eni1np1 rx-vlan-filter off
> ip link add link eni1np1 eni1np1.1 type vlan id 1
> ip link add link eni1np1 eni1np1.1000 type vlan id 1000
> cat /sys/kernel/debug/netdevsim/netdevsim1/ports/0/vlan # empty
> as expected
> ethtool -K eni1np1 rx-vlan-filter on #
> succeeds
> ethtool -k eni1np1 | grep rx-vlan-filter # "rx-
> vlan-filter: on"
> cat /sys/kernel/debug/netdevsim/netdevsim1/ports/0/vlan # still
> empty because id=1000 was rejected
> # and
> everything got rolled back
> ip link add link eni1np1 eni1np1.123 type vlan id 123 #
> succeeds
> cat /sys/kernel/debug/netdevsim/netdevsim1/ports/0/vlan # only
> "ctag 123"
>
>
> [at this point running
> ip link del eni1np1.1
> or
> ethtool -K eni1np1 rx-vlan-filter off
> will splat because vlan_filter_push_vids did a rollback/never added
> id=1, and now we call vlan_kill_rx_filter_info, but that's specific
> to
> this vid limit]
>
Well, in this case we have the chance to do something nicer (even
proper error message back to the user via extack) for a small
complexity cost. Perhaps the VLAN filter handling could be improved
separately.
Cosmin.
^ permalink raw reply
* [PATCH v4 net-next] net: use get_random_u{16,32,64}() where appropriate
From: David Carlier @ 2026-04-07 15:07 UTC (permalink / raw)
To: Jakub Kicinski, David S . Miller, Eric Dumazet, Paolo Abeni
Cc: Andrew Lunn, Simon Horman, Ilya Dryomov, Johannes Berg,
Matthieu Baerts, Mat Martineau, Geliang Tang, Aaron Conole,
Ilya Maximets, Marcelo Ricardo Leitner, Xin Long, Jon Maloy,
netdev, linux-wireless, mptcp, dev, linux-sctp, tipc-discussion,
linux-kernel, David Carlier
Use the typed random integer helpers instead of
get_random_bytes() when filling a single integer variable.
The helpers return the value directly, require no pointer
or size argument, and better express intent.
Skipped sites writing into __be16 (netdevsim) and __le64
(ceph) fields where a direct assignment would trigger
sparse endianness warnings.
Signed-off-by: David Carlier <devnexen@gmail.com>
Reviewed-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---
Notes:
Changes v3 -> v4:
- Dropped net/ceph/auth_x.c site: client_challenge is __le64,
direct assignment triggers sparse endianness warning
(Matthieu Baerts)
- Added Reviewed-by from Matthieu Baerts for net/mptcp changes
v3: https://lore.kernel.org/netdev/20260405154816.4774-1-devnexen@gmail.com/
v2: https://lore.kernel.org/netdev/Z/BfE0zn+DJxhBH7@debian/
drivers/net/netdevsim/psample.c | 4 ++--
net/core/net_namespace.c | 2 +-
net/mac80211/mesh_plink.c | 2 +-
net/mptcp/subflow.c | 4 ++--
net/openvswitch/flow_table.c | 2 +-
net/sctp/sm_make_chunk.c | 4 ++--
net/tipc/node.c | 2 +-
7 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/net/netdevsim/psample.c b/drivers/net/netdevsim/psample.c
index 47d24bc64ee4..717d157c3ae2 100644
--- a/drivers/net/netdevsim/psample.c
+++ b/drivers/net/netdevsim/psample.c
@@ -94,7 +94,7 @@ static void nsim_dev_psample_md_prepare(const struct nsim_dev_psample *psample,
if (psample->out_tc_occ_max) {
u64 out_tc_occ;
- get_random_bytes(&out_tc_occ, sizeof(u64));
+ out_tc_occ = get_random_u64();
md->out_tc_occ = out_tc_occ & (psample->out_tc_occ_max - 1);
md->out_tc_occ_valid = 1;
}
@@ -102,7 +102,7 @@ static void nsim_dev_psample_md_prepare(const struct nsim_dev_psample *psample,
if (psample->latency_max) {
u64 latency;
- get_random_bytes(&latency, sizeof(u64));
+ latency = get_random_u64();
md->latency = latency & (psample->latency_max - 1);
md->latency_valid = 1;
}
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index 1057d16d5dd2..deb8b2ec5674 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -411,7 +411,7 @@ static __net_init int preinit_net(struct net *net, struct user_namespace *user_n
ref_tracker_dir_init(&net->refcnt_tracker, 128, "net_refcnt");
ref_tracker_dir_init(&net->notrefcnt_tracker, 128, "net_notrefcnt");
- get_random_bytes(&net->hash_mix, sizeof(u32));
+ net->hash_mix = get_random_u32();
net->dev_base_seq = 1;
net->user_ns = user_ns;
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 803106fc3134..7cbab90c8784 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -712,7 +712,7 @@ void mesh_plink_timer(struct timer_list *t)
"Mesh plink for %pM (retry, timeout): %d %d\n",
sta->sta.addr, sta->mesh->plink_retries,
sta->mesh->plink_timeout);
- get_random_bytes(&rand, sizeof(u32));
+ rand = get_random_u32();
sta->mesh->plink_timeout = sta->mesh->plink_timeout +
rand % sta->mesh->plink_timeout;
++sta->mesh->plink_retries;
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index 5cfe19990f31..1a7736145dbc 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -72,7 +72,7 @@ static void subflow_req_create_thmac(struct mptcp_subflow_request_sock *subflow_
struct mptcp_sock *msk = subflow_req->msk;
u8 hmac[SHA256_DIGEST_SIZE];
- get_random_bytes(&subflow_req->local_nonce, sizeof(u32));
+ subflow_req->local_nonce = get_random_u32();
subflow_generate_hmac(READ_ONCE(msk->local_key),
READ_ONCE(msk->remote_key),
@@ -1639,7 +1639,7 @@ int __mptcp_subflow_connect(struct sock *sk, const struct mptcp_pm_local *local,
ssk = sf->sk;
subflow = mptcp_subflow_ctx(ssk);
do {
- get_random_bytes(&subflow->local_nonce, sizeof(u32));
+ subflow->local_nonce = get_random_u32();
} while (!subflow->local_nonce);
/* if 'IPADDRANY', the ID will be set later, after the routing */
diff --git a/net/openvswitch/flow_table.c b/net/openvswitch/flow_table.c
index 61c6a5f77c2e..67d5b8c0fe79 100644
--- a/net/openvswitch/flow_table.c
+++ b/net/openvswitch/flow_table.c
@@ -167,7 +167,7 @@ static struct table_instance *table_instance_alloc(int new_size)
ti->n_buckets = new_size;
ti->node_ver = 0;
- get_random_bytes(&ti->hash_seed, sizeof(u32));
+ ti->hash_seed = get_random_u32();
return ti;
}
diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c
index 2c0017d058d4..de86ac088289 100644
--- a/net/sctp/sm_make_chunk.c
+++ b/net/sctp/sm_make_chunk.c
@@ -2727,7 +2727,7 @@ __u32 sctp_generate_tag(const struct sctp_endpoint *ep)
__u32 x;
do {
- get_random_bytes(&x, sizeof(__u32));
+ x = get_random_u32();
} while (x == 0);
return x;
@@ -2738,7 +2738,7 @@ __u32 sctp_generate_tsn(const struct sctp_endpoint *ep)
{
__u32 retval;
- get_random_bytes(&retval, sizeof(__u32));
+ retval = get_random_u32();
return retval;
}
diff --git a/net/tipc/node.c b/net/tipc/node.c
index af442a5ef8f3..97aa970a0d83 100644
--- a/net/tipc/node.c
+++ b/net/tipc/node.c
@@ -1275,7 +1275,7 @@ void tipc_node_check_dest(struct net *net, u32 addr,
goto exit;
if_name = strchr(b->name, ':') + 1;
- get_random_bytes(&session, sizeof(u16));
+ session = get_random_u16();
if (!tipc_link_create(net, if_name, b->identity, b->tolerance,
b->net_plane, b->mtu, b->priority,
b->min_win, b->max_win, session,
--
2.53.0
^ permalink raw reply related
* Re: [PATCH net-next] net: hsr: emit notification for PRP slave2 changed hw addr on port deletion
From: Paolo Abeni @ 2026-04-07 15:08 UTC (permalink / raw)
To: Fernando Fernandez Mancera, netdev
Cc: acsjakub, liuhangbin, kees, horms, kuba, edumazet, davem, fmaurer,
Luka Gejak
In-Reply-To: <d38ea95d-98d2-42ab-aea0-3099ab05082d@suse.de>
On 4/7/26 3:37 PM, Fernando Fernandez Mancera wrote:
> On 4/7/26 3:29 PM, Paolo Abeni wrote:
>> On 4/3/26 2:39 PM, Fernando Fernandez Mancera wrote:
>>> On PRP protocol, when deleting the port the MAC address change
>>> notification was missing. In addition to that, make sure to only perform
>>> the MAC address change on slave2 deletion and PRP protocol as the
>>> operation isn't necessary for HSR nor slave1.
>>>
>>> Note that the eth_hw_addr_set() is correct on PRP context as the slaves
>>> are either in promiscuous mode or forward offload enabled.
>>>
>>> Fixes: b65999e7238e ("net: hsr: sync hw addr of slave2 according to slave1 hw addr on PRP")
>>> Reported-by: Luka Gejak <luka.gejak@linux.dev>
>>> Closes: https://lore.kernel.org/netdev/DHFCZEM93FTT.1RWFBIE32K7OT@linux.dev/
>>> Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
>>> ---
>>> Note: routed through net-next tree as the next net tree as rc6 batch is
>>> already out and I do not think this is worth to be included in a last
>>> call batch. Anyway, if maintainer thinks it is, feel free to apply it to
>>> net tree.
>>
>> The general guidance is to avoid fixes tag for net-next patches that do
>> not fix net-next specific code. In this case I assume you want the patch
>> landing into stable right?
>>
>
> Hi Paolo,
>
> Noted. I wanted to reduce the number of patches on net tree given we are
> at the end of the development cycle. To be honest, the stable backport
> isn't that important here, this is a trivial fix that I doubt it was
> breaking anyone.
>
> Maybe drop Fixes tag and get it into net-next then? Fine for me anyway,
> I trust your judgement on this.
I'm seconding such option, as I think too much stuff is already landing
into stable.
/P
^ permalink raw reply
* Re: [RFC net-next 2/4] selftests: drv-net: tso: add helpers for double tunneling GSO
From: Jakub Kicinski @ 2026-04-07 15:08 UTC (permalink / raw)
To: Xu Du
Cc: davem, edumazet, pabeni, horms, shuah, netdev, linux-kselftest,
linux-kernel
In-Reply-To: <b65f95793e03c1cb3ed3af4e1b2bee89fcf7e648.1775527362.git.xudu@redhat.com>
On Tue, 7 Apr 2026 10:45:09 +0800 Xu Du wrote:
> As the YNL Python module cannot be invoked across different devices or
> environments directly in its current form, the helper abstracts the
> YNL CLI calls to ensure proper configuration of the tunneling device
> features.
Can you explain more? Why can't you use class RtnlFamily?
^ permalink raw reply
* Re: [PATCH net-next] net: hsr: emit notification for PRP slave2 changed hw addr on port deletion
From: patchwork-bot+netdevbpf @ 2026-04-07 15:10 UTC (permalink / raw)
To: Fernando Fernandez Mancera
Cc: netdev, acsjakub, liuhangbin, kees, horms, pabeni, kuba, edumazet,
davem, fmaurer, luka.gejak
In-Reply-To: <20260403123928.4249-2-fmancera@suse.de>
Hello:
This patch was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Fri, 3 Apr 2026 14:39:29 +0200 you wrote:
> On PRP protocol, when deleting the port the MAC address change
> notification was missing. In addition to that, make sure to only perform
> the MAC address change on slave2 deletion and PRP protocol as the
> operation isn't necessary for HSR nor slave1.
>
> Note that the eth_hw_addr_set() is correct on PRP context as the slaves
> are either in promiscuous mode or forward offload enabled.
>
> [...]
Here is the summary with links:
- [net-next] net: hsr: emit notification for PRP slave2 changed hw addr on port deletion
https://git.kernel.org/netdev/net-next/c/2ce8a41113ed
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [PATCH v4 net-next] net: use get_random_u{16,32,64}() where appropriate
From: Eric Dumazet @ 2026-04-07 15:14 UTC (permalink / raw)
To: David Carlier
Cc: Jakub Kicinski, David S . Miller, Paolo Abeni, Andrew Lunn,
Simon Horman, Ilya Dryomov, Johannes Berg, Matthieu Baerts,
Mat Martineau, Geliang Tang, Aaron Conole, Ilya Maximets,
Marcelo Ricardo Leitner, Xin Long, Jon Maloy, netdev,
linux-wireless, mptcp, dev, linux-sctp, tipc-discussion,
linux-kernel
In-Reply-To: <20260407150758.5889-1-devnexen@gmail.com>
On Tue, Apr 7, 2026 at 8:08 AM David Carlier <devnexen@gmail.com> wrote:
>
> Use the typed random integer helpers instead of
> get_random_bytes() when filling a single integer variable.
> The helpers return the value directly, require no pointer
> or size argument, and better express intent.
>
> Skipped sites writing into __be16 (netdevsim) and __le64
> (ceph) fields where a direct assignment would trigger
> sparse endianness warnings.
>
> Signed-off-by: David Carlier <devnexen@gmail.com>
> Reviewed-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Reviewed-by: Eric Dumazet <edumazet@google.com>
^ permalink raw reply
* [PATCH net-next] selftests: net: py: add test case filtering and listing
From: Jakub Kicinski @ 2026-04-07 15:17 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, Jakub Kicinski,
shuah, petrm, willemb, linux-kselftest
When developing new test cases and reproducing failures in
existing ones we currently have to run the entire test which
can take minutes to finish.
Add command line options for test selection, modeled after
kselftest_harness.h:
-l list tests (all or filtered)
-t name include test
-T name exclude test
Since we don't have as clean separation into fixture / variant /
test as kselftest_harness this is not really a 1 to 1 match.
We have to lean on glob patterns instead.
Like in kselftest_harness filters are evaluated in order, first
match wins. If only exclusions are specified everything else is
included and vice versa.
Glob patterns (*, ?, [) are supported in addition to exact
matching.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
Building on the support for colored output.. :)
CC: shuah@kernel.org
CC: petrm@nvidia.com
CC: willemb@google.com
CC: linux-kselftest@vger.kernel.org
---
tools/testing/selftests/net/lib/py/ksft.py | 65 +++++++++++++++++++++-
1 file changed, 62 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/net/lib/py/ksft.py b/tools/testing/selftests/net/lib/py/ksft.py
index 7b8af463e35d..abde32874c17 100644
--- a/tools/testing/selftests/net/lib/py/ksft.py
+++ b/tools/testing/selftests/net/lib/py/ksft.py
@@ -1,6 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
+import fnmatch
import functools
+import getopt
import inspect
import os
import signal
@@ -32,6 +34,34 @@ KSFT_DISRUPTIVE = True
pass
+class _KsftArgs:
+ def __init__(self):
+ self.list_tests = False
+ self.filters = []
+
+ try:
+ opts, _ = getopt.getopt(sys.argv[1:], 'hlt:T:')
+ except getopt.GetoptError as e:
+ print(e, file=sys.stderr)
+ sys.exit(1)
+
+ for opt, val in opts:
+ if opt == '-h':
+ print(f"Usage: {sys.argv[0]} [-h|-l] [-t|-T name]\n"
+ f"\t-h print help\n"
+ f"\t-l list all tests\n"
+ f"\t-t name include test\n"
+ f"\t-T name exclude test",
+ file=sys.stderr)
+ sys.exit(0)
+ elif opt == '-l':
+ self.list_tests = True
+ elif opt == '-t':
+ self.filters.append((True, val))
+ elif opt == '-T':
+ self.filters.append((False, val))
+
+
@functools.lru_cache()
def _ksft_supports_color():
if os.environ.get("NO_COLOR") is not None:
@@ -298,8 +328,26 @@ KsftCaseFunction = namedtuple("KsftCaseFunction",
ksft_pr(f"Ignoring SIGTERM (cnt: {term_cnt}), already exiting...")
-def _ksft_generate_test_cases(cases, globs, case_pfx, args):
- """Generate a flat list of (func, args, name) tuples"""
+def _ksft_name_matches(name, pattern):
+ if '*' in pattern or '?' in pattern or '[' in pattern:
+ return fnmatch.fnmatchcase(name, pattern)
+ return name == pattern
+
+
+def _ksft_test_enabled(name, filters):
+ has_positive = False
+ for include, pattern in filters:
+ has_positive |= include
+ if _ksft_name_matches(name, pattern):
+ return include
+ return not has_positive
+
+
+def _ksft_generate_test_cases(cases, globs, case_pfx, args, cli_args):
+ """Generate a filtered list of (func, args, name) tuples.
+
+ If -l is given, prints matching test names and exits.
+ """
cases = cases or []
test_cases = []
@@ -329,11 +377,22 @@ KsftCaseFunction = namedtuple("KsftCaseFunction",
else:
test_cases.append((func, args, func.__name__))
+ if cli_args.filters:
+ test_cases = [tc for tc in test_cases
+ if _ksft_test_enabled(tc[2], cli_args.filters)]
+
+ if cli_args.list_tests:
+ for _, _, name in test_cases:
+ print(name)
+ sys.exit(0)
+
return test_cases
def ksft_run(cases=None, globs=None, case_pfx=None, args=()):
- test_cases = _ksft_generate_test_cases(cases, globs, case_pfx, args)
+ cli_args = _KsftArgs()
+ test_cases = _ksft_generate_test_cases(cases, globs, case_pfx, args,
+ cli_args)
global term_cnt
term_cnt = 0
--
2.53.0
^ permalink raw reply related
* Re: [PATCH] net/tls: fix use-after-free in -EBUSY error path of tls_do_encryption
From: patchwork-bot+netdevbpf @ 2026-04-07 15:20 UTC (permalink / raw)
To: Muhammad Alifa Ramdhan
Cc: netdev, kuba, sd, davem, edumazet, pabeni, john.fastabend, info,
stable
In-Reply-To: <20260403013617.2838875-1-ramdhan@starlabs.sg>
Hello:
This patch was applied to netdev/net.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Fri, 3 Apr 2026 09:36:17 +0800 you wrote:
> The -EBUSY handling in tls_do_encryption(), introduced by commit
> 859054147318 ("net: tls: handle backlogging of crypto requests"), has
> a use-after-free due to double cleanup of encrypt_pending and the
> scatterlist entry.
>
> When crypto_aead_encrypt() returns -EBUSY, the request is enqueued to
> the cryptd backlog and the async callback tls_encrypt_done() will be
> invoked upon completion. That callback unconditionally restores the
> scatterlist entry (sge->offset, sge->length) and decrements
> ctx->encrypt_pending. However, if tls_encrypt_async_wait() returns an
> error, the synchronous error path in tls_do_encryption() performs the
> same cleanup again, double-decrementing encrypt_pending and
> double-restoring the scatterlist.
>
> [...]
Here is the summary with links:
- net/tls: fix use-after-free in -EBUSY error path of tls_do_encryption
https://git.kernel.org/netdev/net/c/a9b8b18364ff
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* [ANN] netdev foundation TSC meeting notes - Apr 7th
From: Jakub Kicinski @ 2026-04-07 15:29 UTC (permalink / raw)
To: netdev
Present: Andrew, Eric, Jakub, Johannes, Kuniyuki, Simon, Willem, Paolo
CI development and Lab maintenance
- Contractor has signed paperwork (yesterday)
PHY development
- Work began about a month ago, initially 1 day/week
Lab Status
- OS updated over the weekend -> more flakes than usual
- HW testing is running
- i40e gets wedged, need to resolve
- Added a bot to remove patchsets that turn (sw) CI red
Research grants
- Drafted document: very closely based on BPF foundation document,
will contact them about using it
Logo
- Jakub to go ahead with contact
^ permalink raw reply
* [PATCH net-next v6 00/14] net: sleepable ndo_set_rx_mode
From: Stanislav Fomichev @ 2026-04-07 15:30 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni
This series adds a new ndo_set_rx_mode_async callback that enables
drivers to handle address list updates in a sleepable context. The
current ndo_set_rx_mode is called under the netif_addr_lock spinlock
with BHs disabled, which prevents drivers from sleeping. This is
problematic for ops-locked drivers that need to sleep.
The approach:
1. Add snapshot/reconcile infrastructure for address lists
2. Introduce dev_rx_mode_work that takes snapshots under the lock,
drops the lock, calls the driver, then reconciles changes back
3. Move promiscuity handling into the scheduled work as well
4. Convert existing ops-locked drivers to ndo_set_rx_mode_async
5. Add a warning for ops-locked drivers still using ndo_set_rx_mode
6. Add a selftest exercising the team+bridge+macvlan topology that
triggers the addr_lock -> ops_lock ordering issue
v6:
- relink ref_ha in __hw_addr_list_reconcile (AI Review)
- set real_ha->sync_cnt to delta, not 1 (AI Review)
- s/KUNIT_ASSERT_EQ/KUNIT_EXPECT_EQ/ (AI Review)
- drop netif_rx_mode_clean from free_netdev (AI Review)
- clarify deep hierarchy flush in commit message (AI Review)
- use dev trackers (AI Review)
- drop mc argument from bnxt_cfg_rx_mode (AI Review)
- keep uc_update, but as an argument in bnxt (AI Review)
- add BNXT_STATE_OPEN check after re-lock in bnxt (AI Review)
- add addr lock around iavf_set_rx_mode (AI Review)
v5:
- resolve 32 bit failure (Jakub)
v4:
- rebase on https://lore.kernel.org/netdev/20260319005456.82745-1-saeed@kernel.org/T/#u (Cosmin)
- reword ndo_set_rx_mode_async kdoc (Jakub)
- s/EXPORT_SYMBOL/EXPORT_SYMBOL_IF_KUNIT/ (Jakub)
- remove netif_up_and_present (Jakub)
- netif_addr_lists_snapshot + netif_addr_lists_reconcile to better
explain mix-and-match between
ndo_set_rx_mode/ndo_set_rx_mode_async/ndo_change_rx_flags (Jakub)
- s/cancel_work_sync/flush_work/ (Jakub)
- separate commit to cache snapshot entries (Jakub)
- add dev_addr_test_snapshot_benchmark (Jakub)
- dev_addr_test_snapshot_benchmark: 1024 addrs x 1000 snapshots: 89872802 ns total, 89872 ns/iter
- remove redundant bnxt_uc_list_updated (Michael)
- switch to linkwatch-like work stealing (Jakub)
v3:
- module_export(__rtnl_unlock) (nipa)
- s/netdev_uc_count/netdev_hw_addr_list_count/ in bnxt (Aleksandr)
v2:
- wifi: cfg80211: use __rtnl_unlock in nl80211_pre_doit (syzbot)
- simplify mlx5e_sync_netdev_addr for !uc (Cosmin)
- switch to snapshot in bnxt_cfg_rx_mode (Michael)
- add team to net/config (Jakub)
Stanislav Fomichev (14):
net: add address list snapshot and reconciliation infrastructure
net: introduce ndo_set_rx_mode_async and netdev_rx_mode_work
net: cache snapshot entries for ndo_set_rx_mode_async
net: move promiscuity handling into netdev_rx_mode_work
fbnic: convert to ndo_set_rx_mode_async
mlx5: convert to ndo_set_rx_mode_async
bnxt: convert to ndo_set_rx_mode_async
bnxt: use snapshot in bnxt_cfg_rx_mode
iavf: convert to ndo_set_rx_mode_async
netdevsim: convert to ndo_set_rx_mode_async
dummy: convert to ndo_set_rx_mode_async
net: warn ops-locked drivers still using ndo_set_rx_mode
selftests: net: add team_bridge_macvlan rx_mode test
selftests: net: use ip commands instead of teamd in team rx_mode test
Documentation/networking/netdevices.rst | 13 +
drivers/net/dummy.c | 6 +-
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 58 +--
drivers/net/ethernet/intel/iavf/iavf_main.c | 16 +-
.../net/ethernet/mellanox/mlx5/core/en/fs.h | 5 +-
.../net/ethernet/mellanox/mlx5/core/en_fs.c | 32 +-
.../net/ethernet/mellanox/mlx5/core/en_main.c | 13 +-
.../net/ethernet/meta/fbnic/fbnic_netdev.c | 20 +-
.../net/ethernet/meta/fbnic/fbnic_netdev.h | 4 +-
drivers/net/ethernet/meta/fbnic/fbnic_pci.c | 4 +-
drivers/net/ethernet/meta/fbnic/fbnic_rpc.c | 2 +-
drivers/net/netdevsim/netdev.c | 8 +-
include/linux/netdevice.h | 28 ++
net/core/dev.c | 67 +--
net/core/dev.h | 4 +
net/core/dev_addr_lists.c | 370 ++++++++++++++++-
net/core/dev_addr_lists_test.c | 387 +++++++++++++++++-
net/core/dev_api.c | 3 +
net/core/dev_ioctl.c | 6 +-
net/core/rtnetlink.c | 1 +
.../selftests/drivers/net/bonding/lag_lib.sh | 17 +-
.../drivers/net/team/dev_addr_lists.sh | 2 -
tools/testing/selftests/net/config | 3 +
tools/testing/selftests/net/rtnetlink.sh | 44 ++
24 files changed, 975 insertions(+), 138 deletions(-)
--
2.52.0
^ permalink raw reply
* [PATCH net-next v6 02/14] net: introduce ndo_set_rx_mode_async and netdev_rx_mode_work
From: Stanislav Fomichev @ 2026-04-07 15:30 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni
In-Reply-To: <20260407153101.3694714-1-sdf@fomichev.me>
Add ndo_set_rx_mode_async callback that drivers can implement instead
of the legacy ndo_set_rx_mode. The legacy callback runs under the
netif_addr_lock spinlock with BHs disabled, preventing drivers from
sleeping. The async variant runs from a work queue with rtnl_lock and
netdev_lock_ops held, in fully sleepable context.
When __dev_set_rx_mode() sees ndo_set_rx_mode_async, it schedules
netdev_rx_mode_work instead of calling the driver inline. The work
function takes two snapshots of each address list (uc/mc) under
the addr_lock, then drops the lock and calls the driver with the
work copies. After the driver returns, it reconciles the snapshots
back to the real lists under the lock.
Add netif_rx_mode_sync() to opportunistically execute the pending
workqueue update inline, so that rx mode changes are committed
before returning to userspace:
- dev_change_flags (SIOCSIFFLAGS / RTM_NEWLINK)
- dev_set_promiscuity
- dev_set_allmulti
- dev_ifsioc SIOCADDMULTI / SIOCDELMULTI
- do_setlink (RTM_SETLINK)
Note that some deep hierarchies still do skip the lower updates via:
- dev_uc_sync
- dev_mc_sync
If we do end up hitting user-visible issues, we can add more calls to
netif_rx_mode_sync in specific places. But hopefully we should not,
the actual user-visible lists are still synced, it's that just HW state
that might be lagging.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
Documentation/networking/netdevices.rst | 9 ++
include/linux/netdevice.h | 18 +++
net/core/dev.c | 43 +-----
net/core/dev.h | 3 +
net/core/dev_addr_lists.c | 194 ++++++++++++++++++++++++
net/core/dev_api.c | 3 +
net/core/dev_ioctl.c | 6 +-
net/core/rtnetlink.c | 1 +
8 files changed, 234 insertions(+), 43 deletions(-)
diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst
index 35704d115312..8a488c21fd7c 100644
--- a/Documentation/networking/netdevices.rst
+++ b/Documentation/networking/netdevices.rst
@@ -289,6 +289,15 @@ struct net_device synchronization rules
ndo_set_rx_mode:
Synchronization: netif_addr_lock spinlock.
Context: BHs disabled
+ Notes: Deprecated in favor of ndo_set_rx_mode_async which runs
+ in process context.
+
+ndo_set_rx_mode_async:
+ Synchronization: rtnl_lock() semaphore. In addition, netdev instance
+ lock if the driver implements queue management or shaper API.
+ Context: process (from a work queue)
+ Notes: Async version of ndo_set_rx_mode which runs in process
+ context. Receives snapshots of the unicast and multicast address lists.
ndo_setup_tc:
``TC_SETUP_BLOCK`` and ``TC_SETUP_FT`` are running under NFT locks
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 151d6f4fd9b3..0a41b216cbcf 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1119,6 +1119,16 @@ struct netdev_net_notifier {
* This function is called device changes address list filtering.
* If driver handles unicast address filtering, it should set
* IFF_UNICAST_FLT in its priv_flags.
+ * Cannot sleep, called with netif_addr_lock_bh held.
+ * Deprecated in favor of ndo_set_rx_mode_async.
+ *
+ * void (*ndo_set_rx_mode_async)(struct net_device *dev,
+ * struct netdev_hw_addr_list *uc,
+ * struct netdev_hw_addr_list *mc);
+ * Async version of ndo_set_rx_mode which runs in process context
+ * with rtnl_lock and netdev_lock_ops(dev) held. The uc/mc parameters
+ * are snapshots of the address lists - iterate with
+ * netdev_hw_addr_list_for_each(ha, uc).
*
* int (*ndo_set_mac_address)(struct net_device *dev, void *addr);
* This function is called when the Media Access Control address
@@ -1439,6 +1449,10 @@ struct net_device_ops {
void (*ndo_change_rx_flags)(struct net_device *dev,
int flags);
void (*ndo_set_rx_mode)(struct net_device *dev);
+ void (*ndo_set_rx_mode_async)(
+ struct net_device *dev,
+ struct netdev_hw_addr_list *uc,
+ struct netdev_hw_addr_list *mc);
int (*ndo_set_mac_address)(struct net_device *dev,
void *addr);
int (*ndo_validate_addr)(struct net_device *dev);
@@ -1903,6 +1917,8 @@ enum netdev_reg_state {
* has been enabled due to the need to listen to
* additional unicast addresses in a device that
* does not implement ndo_set_rx_mode()
+ * @rx_mode_node: List entry for rx_mode work processing
+ * @rx_mode_tracker: Refcount tracker for rx_mode work
* @uc: unicast mac addresses
* @mc: multicast mac addresses
* @dev_addrs: list of device hw addresses
@@ -2294,6 +2310,8 @@ struct net_device {
unsigned int promiscuity;
unsigned int allmulti;
bool uc_promisc;
+ struct list_head rx_mode_node;
+ netdevice_tracker rx_mode_tracker;
#ifdef CONFIG_LOCKDEP
unsigned char nested_level;
#endif
diff --git a/net/core/dev.c b/net/core/dev.c
index 4519f0e59beb..fe33feacc4f3 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -9579,7 +9579,7 @@ static void dev_change_rx_flags(struct net_device *dev, int flags)
ops->ndo_change_rx_flags(dev, flags);
}
-static int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify)
+int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify)
{
unsigned int old_flags = dev->flags;
unsigned int promiscuity, flags;
@@ -9683,46 +9683,6 @@ int netif_set_allmulti(struct net_device *dev, int inc, bool notify)
return 0;
}
-/*
- * Upload unicast and multicast address lists to device and
- * configure RX filtering. When the device doesn't support unicast
- * filtering it is put in promiscuous mode while unicast addresses
- * are present.
- */
-void __dev_set_rx_mode(struct net_device *dev)
-{
- const struct net_device_ops *ops = dev->netdev_ops;
-
- /* dev_open will call this function so the list will stay sane. */
- if (!(dev->flags&IFF_UP))
- return;
-
- if (!netif_device_present(dev))
- return;
-
- if (!(dev->priv_flags & IFF_UNICAST_FLT)) {
- /* Unicast addresses changes may only happen under the rtnl,
- * therefore calling __dev_set_promiscuity here is safe.
- */
- if (!netdev_uc_empty(dev) && !dev->uc_promisc) {
- __dev_set_promiscuity(dev, 1, false);
- dev->uc_promisc = true;
- } else if (netdev_uc_empty(dev) && dev->uc_promisc) {
- __dev_set_promiscuity(dev, -1, false);
- dev->uc_promisc = false;
- }
- }
-
- if (ops->ndo_set_rx_mode)
- ops->ndo_set_rx_mode(dev);
-}
-
-void dev_set_rx_mode(struct net_device *dev)
-{
- netif_addr_lock_bh(dev);
- __dev_set_rx_mode(dev);
- netif_addr_unlock_bh(dev);
-}
/**
* netif_get_flags() - get flags reported to userspace
@@ -12113,6 +12073,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
#endif
mutex_init(&dev->lock);
+ INIT_LIST_HEAD(&dev->rx_mode_node);
dev->priv_flags = IFF_XMIT_DST_RELEASE | IFF_XMIT_DST_RELEASE_PERM;
setup(dev);
diff --git a/net/core/dev.h b/net/core/dev.h
index acc925b7b337..50edb380ca94 100644
--- a/net/core/dev.h
+++ b/net/core/dev.h
@@ -146,6 +146,9 @@ int netif_change_carrier(struct net_device *dev, bool new_carrier);
int dev_change_carrier(struct net_device *dev, bool new_carrier);
void __dev_set_rx_mode(struct net_device *dev);
+int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify);
+bool netif_rx_mode_clean(struct net_device *dev);
+void netif_rx_mode_sync(struct net_device *dev);
void __dev_notify_flags(struct net_device *dev, unsigned int old_flags,
unsigned int gchanges, u32 portid,
diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c
index bb4851bc55ce..477392127e8a 100644
--- a/net/core/dev_addr_lists.c
+++ b/net/core/dev_addr_lists.c
@@ -11,10 +11,18 @@
#include <linux/rtnetlink.h>
#include <linux/export.h>
#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
#include <kunit/visibility.h>
#include "dev.h"
+static void netdev_rx_mode_work(struct work_struct *work);
+
+static LIST_HEAD(rx_mode_list);
+static DEFINE_SPINLOCK(rx_mode_lock);
+static DECLARE_WORK(rx_mode_work, netdev_rx_mode_work);
+
/*
* General list handling functions
*/
@@ -1156,3 +1164,189 @@ void dev_mc_init(struct net_device *dev)
__hw_addr_init(&dev->mc);
}
EXPORT_SYMBOL(dev_mc_init);
+
+static int netif_addr_lists_snapshot(struct net_device *dev,
+ struct netdev_hw_addr_list *uc_snap,
+ struct netdev_hw_addr_list *mc_snap,
+ struct netdev_hw_addr_list *uc_ref,
+ struct netdev_hw_addr_list *mc_ref)
+{
+ int err;
+
+ err = __hw_addr_list_snapshot(uc_snap, &dev->uc, dev->addr_len);
+ if (!err)
+ err = __hw_addr_list_snapshot(uc_ref, &dev->uc, dev->addr_len);
+ if (!err)
+ err = __hw_addr_list_snapshot(mc_snap, &dev->mc,
+ dev->addr_len);
+ if (!err)
+ err = __hw_addr_list_snapshot(mc_ref, &dev->mc, dev->addr_len);
+
+ if (err) {
+ __hw_addr_flush(uc_snap);
+ __hw_addr_flush(uc_ref);
+ __hw_addr_flush(mc_snap);
+ }
+
+ return err;
+}
+
+static void netif_addr_lists_reconcile(struct net_device *dev,
+ struct netdev_hw_addr_list *uc_snap,
+ struct netdev_hw_addr_list *mc_snap,
+ struct netdev_hw_addr_list *uc_ref,
+ struct netdev_hw_addr_list *mc_ref)
+{
+ __hw_addr_list_reconcile(&dev->uc, uc_snap, uc_ref, dev->addr_len);
+ __hw_addr_list_reconcile(&dev->mc, mc_snap, mc_ref, dev->addr_len);
+}
+
+static void netif_rx_mode_run(struct net_device *dev)
+{
+ struct netdev_hw_addr_list uc_snap, mc_snap, uc_ref, mc_ref;
+ const struct net_device_ops *ops = dev->netdev_ops;
+ int err;
+
+ might_sleep();
+ netdev_ops_assert_locked(dev);
+
+ __hw_addr_init(&uc_snap);
+ __hw_addr_init(&mc_snap);
+ __hw_addr_init(&uc_ref);
+ __hw_addr_init(&mc_ref);
+
+ if (!(dev->flags & IFF_UP) || !netif_device_present(dev))
+ return;
+
+ netif_addr_lock_bh(dev);
+ err = netif_addr_lists_snapshot(dev, &uc_snap, &mc_snap,
+ &uc_ref, &mc_ref);
+ if (err) {
+ netdev_WARN(dev, "failed to sync uc/mc addresses\n");
+ netif_addr_unlock_bh(dev);
+ return;
+ }
+ netif_addr_unlock_bh(dev);
+
+ ops->ndo_set_rx_mode_async(dev, &uc_snap, &mc_snap);
+
+ netif_addr_lock_bh(dev);
+ netif_addr_lists_reconcile(dev, &uc_snap, &mc_snap,
+ &uc_ref, &mc_ref);
+ netif_addr_unlock_bh(dev);
+}
+
+static void netdev_rx_mode_work(struct work_struct *work)
+{
+ struct net_device *dev;
+
+ rtnl_lock();
+
+ while (true) {
+ spin_lock_bh(&rx_mode_lock);
+ if (list_empty(&rx_mode_list)) {
+ spin_unlock_bh(&rx_mode_lock);
+ break;
+ }
+ dev = list_first_entry(&rx_mode_list, struct net_device,
+ rx_mode_node);
+ list_del_init(&dev->rx_mode_node);
+ spin_unlock_bh(&rx_mode_lock);
+
+ netdev_lock_ops(dev);
+ netif_rx_mode_run(dev);
+ netdev_unlock_ops(dev);
+ netdev_put(dev, &dev->rx_mode_tracker);
+ }
+
+ rtnl_unlock();
+}
+
+static void netif_rx_mode_queue(struct net_device *dev)
+{
+ spin_lock_bh(&rx_mode_lock);
+ if (list_empty(&dev->rx_mode_node)) {
+ list_add_tail(&dev->rx_mode_node, &rx_mode_list);
+ netdev_hold(dev, &dev->rx_mode_tracker, GFP_ATOMIC);
+ }
+ spin_unlock_bh(&rx_mode_lock);
+ schedule_work(&rx_mode_work);
+}
+
+/**
+ * __dev_set_rx_mode() - upload unicast and multicast address lists to device
+ * and configure RX filtering.
+ * @dev: device
+ *
+ * When the device doesn't support unicast filtering it is put in promiscuous
+ * mode while unicast addresses are present.
+ */
+void __dev_set_rx_mode(struct net_device *dev)
+{
+ const struct net_device_ops *ops = dev->netdev_ops;
+
+ /* dev_open will call this function so the list will stay sane. */
+ if (!(dev->flags & IFF_UP))
+ return;
+
+ if (!netif_device_present(dev))
+ return;
+
+ if (ops->ndo_set_rx_mode_async) {
+ netif_rx_mode_queue(dev);
+ return;
+ }
+
+ if (!(dev->priv_flags & IFF_UNICAST_FLT)) {
+ if (!netdev_uc_empty(dev) && !dev->uc_promisc) {
+ __dev_set_promiscuity(dev, 1, false);
+ dev->uc_promisc = true;
+ } else if (netdev_uc_empty(dev) && dev->uc_promisc) {
+ __dev_set_promiscuity(dev, -1, false);
+ dev->uc_promisc = false;
+ }
+ }
+
+ if (ops->ndo_set_rx_mode)
+ ops->ndo_set_rx_mode(dev);
+}
+
+void dev_set_rx_mode(struct net_device *dev)
+{
+ netif_addr_lock_bh(dev);
+ __dev_set_rx_mode(dev);
+ netif_addr_unlock_bh(dev);
+}
+
+bool netif_rx_mode_clean(struct net_device *dev)
+{
+ bool clean = false;
+
+ spin_lock_bh(&rx_mode_lock);
+ if (!list_empty(&dev->rx_mode_node)) {
+ list_del_init(&dev->rx_mode_node);
+ clean = true;
+ }
+ spin_unlock_bh(&rx_mode_lock);
+
+ return clean;
+}
+
+/**
+ * netif_rx_mode_sync() - sync rx mode inline
+ * @dev: network device
+ *
+ * Drivers implementing ndo_set_rx_mode_async() have their rx mode callback
+ * executed from a workqueue. This allows the callback to sleep, but means
+ * the hardware update is deferred and may not be visible to userspace
+ * by the time the initiating syscall returns. netif_rx_mode_sync() steals
+ * workqueue update and executes it inline. This preserves the atomicity of
+ * operations to the userspace.
+ */
+void netif_rx_mode_sync(struct net_device *dev)
+{
+ if (netif_rx_mode_clean(dev)) {
+ netif_rx_mode_run(dev);
+ netdev_put(dev, &dev->rx_mode_tracker);
+ }
+}
diff --git a/net/core/dev_api.c b/net/core/dev_api.c
index f28852078aa6..437947dd08ed 100644
--- a/net/core/dev_api.c
+++ b/net/core/dev_api.c
@@ -66,6 +66,7 @@ int dev_change_flags(struct net_device *dev, unsigned int flags,
netdev_lock_ops(dev);
ret = netif_change_flags(dev, flags, extack);
+ netif_rx_mode_sync(dev);
netdev_unlock_ops(dev);
return ret;
@@ -285,6 +286,7 @@ int dev_set_promiscuity(struct net_device *dev, int inc)
netdev_lock_ops(dev);
ret = netif_set_promiscuity(dev, inc);
+ netif_rx_mode_sync(dev);
netdev_unlock_ops(dev);
return ret;
@@ -311,6 +313,7 @@ int dev_set_allmulti(struct net_device *dev, int inc)
netdev_lock_ops(dev);
ret = netif_set_allmulti(dev, inc, true);
+ netif_rx_mode_sync(dev);
netdev_unlock_ops(dev);
return ret;
diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c
index 7a8966544c9d..f3979b276090 100644
--- a/net/core/dev_ioctl.c
+++ b/net/core/dev_ioctl.c
@@ -586,24 +586,26 @@ static int dev_ifsioc(struct net *net, struct ifreq *ifr, void __user *data,
return err;
case SIOCADDMULTI:
- if (!ops->ndo_set_rx_mode ||
+ if ((!ops->ndo_set_rx_mode && !ops->ndo_set_rx_mode_async) ||
ifr->ifr_hwaddr.sa_family != AF_UNSPEC)
return -EINVAL;
if (!netif_device_present(dev))
return -ENODEV;
netdev_lock_ops(dev);
err = dev_mc_add_global(dev, ifr->ifr_hwaddr.sa_data);
+ netif_rx_mode_sync(dev);
netdev_unlock_ops(dev);
return err;
case SIOCDELMULTI:
- if (!ops->ndo_set_rx_mode ||
+ if ((!ops->ndo_set_rx_mode && !ops->ndo_set_rx_mode_async) ||
ifr->ifr_hwaddr.sa_family != AF_UNSPEC)
return -EINVAL;
if (!netif_device_present(dev))
return -ENODEV;
netdev_lock_ops(dev);
err = dev_mc_del_global(dev, ifr->ifr_hwaddr.sa_data);
+ netif_rx_mode_sync(dev);
netdev_unlock_ops(dev);
return err;
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index fae8034efbff..f4e5ac70709d 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -3431,6 +3431,7 @@ static int do_setlink(const struct sk_buff *skb, struct net_device *dev,
dev->name);
}
+ netif_rx_mode_sync(dev);
netdev_unlock_ops(dev);
return err;
--
2.52.0
^ permalink raw reply related
* [PATCH net-next v6 01/14] net: add address list snapshot and reconciliation infrastructure
From: Stanislav Fomichev @ 2026-04-07 15:30 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni, Aleksandr Loktionov
In-Reply-To: <20260407153101.3694714-1-sdf@fomichev.me>
Introduce __hw_addr_list_snapshot() and __hw_addr_list_reconcile()
for use by the upcoming ndo_set_rx_mode_async callback.
The async rx_mode path needs to snapshot the device's unicast and
multicast address lists under the addr_lock, hand those snapshots
to the driver (which may sleep), and then propagate any sync_cnt
changes back to the real lists. Two identical snapshots are taken:
a work copy for the driver to pass to __hw_addr_sync_dev() and a
reference copy to compute deltas against.
__hw_addr_list_reconcile() walks the reference snapshot comparing
each entry against the work snapshot to determine what the driver
synced or unsynced. It then applies those deltas to the real list,
handling concurrent modifications:
- If the real entry was concurrently removed but the driver synced
it to hardware (delta > 0), re-insert a stale entry so the next
work run properly unsyncs it from hardware.
- If the entry still exists, apply the delta normally. An entry
whose refcount drops to zero is removed.
# dev_addr_test_snapshot_benchmark: 1024 addrs x 1000 snapshots: 89872802 ns total, 89872 ns/iter
# dev_addr_test_snapshot_benchmark.speed: slow
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
include/linux/netdevice.h | 7 +
net/core/dev.h | 1 +
net/core/dev_addr_lists.c | 109 +++++++++-
net/core/dev_addr_lists_test.c | 363 ++++++++++++++++++++++++++++++++-
4 files changed, 477 insertions(+), 3 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index e15367373f7c..151d6f4fd9b3 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -4995,6 +4995,13 @@ void __hw_addr_unsync_dev(struct netdev_hw_addr_list *list,
int (*unsync)(struct net_device *,
const unsigned char *));
void __hw_addr_init(struct netdev_hw_addr_list *list);
+void __hw_addr_flush(struct netdev_hw_addr_list *list);
+int __hw_addr_list_snapshot(struct netdev_hw_addr_list *snap,
+ const struct netdev_hw_addr_list *list,
+ int addr_len);
+void __hw_addr_list_reconcile(struct netdev_hw_addr_list *real_list,
+ struct netdev_hw_addr_list *work,
+ struct netdev_hw_addr_list *ref, int addr_len);
/* Functions used for device addresses handling */
void dev_addr_mod(struct net_device *dev, unsigned int offset,
diff --git a/net/core/dev.h b/net/core/dev.h
index 781619e76b3e..acc925b7b337 100644
--- a/net/core/dev.h
+++ b/net/core/dev.h
@@ -69,6 +69,7 @@ void linkwatch_run_queue(void);
void dev_addr_flush(struct net_device *dev);
int dev_addr_init(struct net_device *dev);
void dev_addr_check(struct net_device *dev);
+void __hw_addr_flush(struct netdev_hw_addr_list *list);
#if IS_ENABLED(CONFIG_NET_SHAPER)
void net_shaper_flush_netdev(struct net_device *dev);
diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c
index 76c91f224886..bb4851bc55ce 100644
--- a/net/core/dev_addr_lists.c
+++ b/net/core/dev_addr_lists.c
@@ -11,6 +11,7 @@
#include <linux/rtnetlink.h>
#include <linux/export.h>
#include <linux/list.h>
+#include <kunit/visibility.h>
#include "dev.h"
@@ -481,7 +482,7 @@ void __hw_addr_unsync_dev(struct netdev_hw_addr_list *list,
}
EXPORT_SYMBOL(__hw_addr_unsync_dev);
-static void __hw_addr_flush(struct netdev_hw_addr_list *list)
+void __hw_addr_flush(struct netdev_hw_addr_list *list)
{
struct netdev_hw_addr *ha, *tmp;
@@ -492,6 +493,7 @@ static void __hw_addr_flush(struct netdev_hw_addr_list *list)
}
list->count = 0;
}
+EXPORT_SYMBOL_IF_KUNIT(__hw_addr_flush);
void __hw_addr_init(struct netdev_hw_addr_list *list)
{
@@ -501,6 +503,111 @@ void __hw_addr_init(struct netdev_hw_addr_list *list)
}
EXPORT_SYMBOL(__hw_addr_init);
+/**
+ * __hw_addr_list_snapshot - create a snapshot copy of an address list
+ * @snap: destination snapshot list (needs to be __hw_addr_init-initialized)
+ * @list: source address list to snapshot
+ * @addr_len: length of addresses
+ *
+ * Creates a copy of @list with individually allocated entries suitable
+ * for use with __hw_addr_sync_dev() and other list manipulation helpers.
+ * Each entry is allocated with GFP_ATOMIC; must be called under a spinlock.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int __hw_addr_list_snapshot(struct netdev_hw_addr_list *snap,
+ const struct netdev_hw_addr_list *list,
+ int addr_len)
+{
+ struct netdev_hw_addr *ha, *entry;
+
+ list_for_each_entry(ha, &list->list, list) {
+ entry = __hw_addr_create(ha->addr, addr_len, ha->type,
+ false, false);
+ if (!entry) {
+ __hw_addr_flush(snap);
+ return -ENOMEM;
+ }
+ entry->sync_cnt = ha->sync_cnt;
+ entry->refcount = ha->refcount;
+
+ list_add_tail(&entry->list, &snap->list);
+ __hw_addr_insert(snap, entry, addr_len);
+ snap->count++;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(__hw_addr_list_snapshot);
+
+/**
+ * __hw_addr_list_reconcile - sync snapshot changes back and free snapshots
+ * @real_list: the real address list to update
+ * @work: the working snapshot (modified by driver via __hw_addr_sync_dev)
+ * @ref: the reference snapshot (untouched copy of original state)
+ * @addr_len: length of addresses
+ *
+ * Walks the reference snapshot and compares each entry against the work
+ * snapshot to compute sync_cnt deltas. Applies those deltas to @real_list.
+ * Frees both snapshots when done.
+ * Caller must hold netif_addr_lock_bh.
+ */
+void __hw_addr_list_reconcile(struct netdev_hw_addr_list *real_list,
+ struct netdev_hw_addr_list *work,
+ struct netdev_hw_addr_list *ref, int addr_len)
+{
+ struct netdev_hw_addr *ref_ha, *tmp, *work_ha, *real_ha;
+ int delta;
+
+ list_for_each_entry_safe(ref_ha, tmp, &ref->list, list) {
+ work_ha = __hw_addr_lookup(work, ref_ha->addr, addr_len,
+ ref_ha->type);
+ if (work_ha)
+ delta = work_ha->sync_cnt - ref_ha->sync_cnt;
+ else
+ delta = -1;
+
+ if (delta == 0)
+ continue;
+
+ real_ha = __hw_addr_lookup(real_list, ref_ha->addr, addr_len,
+ ref_ha->type);
+ if (!real_ha) {
+ /* The real entry was concurrently removed. If the
+ * driver synced this addr to hardware (delta > 0),
+ * re-insert it as a stale entry so the next work
+ * run unsyncs it from hardware.
+ */
+ if (delta > 0) {
+ rb_erase(&ref_ha->node, &ref->tree);
+ list_del(&ref_ha->list);
+ ref->count--;
+ ref_ha->sync_cnt = delta;
+ ref_ha->refcount = delta;
+ list_add_tail_rcu(&ref_ha->list,
+ &real_list->list);
+ __hw_addr_insert(real_list, ref_ha,
+ addr_len);
+ real_list->count++;
+ }
+ continue;
+ }
+
+ real_ha->sync_cnt += delta;
+ real_ha->refcount += delta;
+ if (!real_ha->refcount) {
+ rb_erase(&real_ha->node, &real_list->tree);
+ list_del_rcu(&real_ha->list);
+ kfree_rcu(real_ha, rcu_head);
+ real_list->count--;
+ }
+ }
+
+ __hw_addr_flush(work);
+ __hw_addr_flush(ref);
+}
+EXPORT_SYMBOL_IF_KUNIT(__hw_addr_list_reconcile);
+
/*
* Device addresses handling functions
*/
diff --git a/net/core/dev_addr_lists_test.c b/net/core/dev_addr_lists_test.c
index 8e1dba825e94..fba926d5ec0d 100644
--- a/net/core/dev_addr_lists_test.c
+++ b/net/core/dev_addr_lists_test.c
@@ -2,22 +2,31 @@
#include <kunit/test.h>
#include <linux/etherdevice.h>
+#include <linux/math64.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
static const struct net_device_ops dummy_netdev_ops = {
};
+#define ADDR_A 1
+#define ADDR_B 2
+#define ADDR_C 3
+
struct dev_addr_test_priv {
u32 addr_seen;
+ u32 addr_synced;
+ u32 addr_unsynced;
};
static int dev_addr_test_sync(struct net_device *netdev, const unsigned char *a)
{
struct dev_addr_test_priv *datp = netdev_priv(netdev);
- if (a[0] < 31 && !memchr_inv(a, a[0], ETH_ALEN))
+ if (a[0] < 31 && !memchr_inv(a, a[0], ETH_ALEN)) {
datp->addr_seen |= 1 << a[0];
+ datp->addr_synced |= 1 << a[0];
+ }
return 0;
}
@@ -26,11 +35,22 @@ static int dev_addr_test_unsync(struct net_device *netdev,
{
struct dev_addr_test_priv *datp = netdev_priv(netdev);
- if (a[0] < 31 && !memchr_inv(a, a[0], ETH_ALEN))
+ if (a[0] < 31 && !memchr_inv(a, a[0], ETH_ALEN)) {
datp->addr_seen &= ~(1 << a[0]);
+ datp->addr_unsynced |= 1 << a[0];
+ }
return 0;
}
+static void dev_addr_test_reset(struct net_device *netdev)
+{
+ struct dev_addr_test_priv *datp = netdev_priv(netdev);
+
+ datp->addr_seen = 0;
+ datp->addr_synced = 0;
+ datp->addr_unsynced = 0;
+}
+
static int dev_addr_test_init(struct kunit *test)
{
struct dev_addr_test_priv *datp;
@@ -225,6 +245,339 @@ static void dev_addr_test_add_excl(struct kunit *test)
rtnl_unlock();
}
+/* Snapshot test: basic sync with no concurrent modifications.
+ * Add one address, snapshot, driver syncs it, reconcile propagates
+ * sync_cnt delta back to real list.
+ */
+static void dev_addr_test_snapshot_sync(struct kunit *test)
+{
+ struct net_device *netdev = test->priv;
+ struct netdev_hw_addr_list snap, ref;
+ struct dev_addr_test_priv *datp;
+ struct netdev_hw_addr *ha;
+ u8 addr[ETH_ALEN];
+
+ datp = netdev_priv(netdev);
+
+ rtnl_lock();
+
+ memset(addr, ADDR_A, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+
+ /* Snapshot: ADDR_A has sync_cnt=0, refcount=1 (new) */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_init(&snap);
+ __hw_addr_init(&ref);
+ KUNIT_EXPECT_EQ(test, 0,
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ KUNIT_EXPECT_EQ(test, 0,
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ netif_addr_unlock_bh(netdev);
+
+ /* Driver syncs ADDR_A to hardware */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&snap, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+
+ /* Reconcile: delta=+1 applied to real entry */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ netif_addr_unlock_bh(netdev);
+
+ /* Real entry should now reflect the sync: sync_cnt=1, refcount=2 */
+ KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
+ ha = list_first_entry(&netdev->uc.list, struct netdev_hw_addr, list);
+ KUNIT_EXPECT_MEMEQ(test, ha->addr, addr, ETH_ALEN);
+ KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 2, ha->refcount);
+
+ /* Second work run: already synced, nothing to do */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+ KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
+
+ rtnl_unlock();
+}
+
+/* Snapshot test: ADDR_A synced to hardware, then concurrently removed
+ * from the real list before reconcile runs. Reconcile re-inserts ADDR_A as
+ * a stale entry so the next work run unsyncs it from hardware.
+ */
+static void dev_addr_test_snapshot_remove_during_sync(struct kunit *test)
+{
+ struct net_device *netdev = test->priv;
+ struct netdev_hw_addr_list snap, ref;
+ struct dev_addr_test_priv *datp;
+ struct netdev_hw_addr *ha;
+ u8 addr[ETH_ALEN];
+
+ datp = netdev_priv(netdev);
+
+ rtnl_lock();
+
+ memset(addr, ADDR_A, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+
+ /* Snapshot: ADDR_A is new (sync_cnt=0, refcount=1) */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_init(&snap);
+ __hw_addr_init(&ref);
+ KUNIT_EXPECT_EQ(test, 0,
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ KUNIT_EXPECT_EQ(test, 0,
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ netif_addr_unlock_bh(netdev);
+
+ /* Driver syncs ADDR_A to hardware */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&snap, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+
+ /* Concurrent removal: user deletes ADDR_A while driver was working */
+ memset(addr, ADDR_A, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_del(netdev, addr));
+ KUNIT_EXPECT_EQ(test, 0, netdev->uc.count);
+
+ /* Reconcile: ADDR_A gone from real list but driver synced it,
+ * so it gets re-inserted as stale (sync_cnt=1, refcount=1).
+ */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ netif_addr_unlock_bh(netdev);
+
+ KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
+ ha = list_first_entry(&netdev->uc.list, struct netdev_hw_addr, list);
+ KUNIT_EXPECT_MEMEQ(test, ha->addr, addr, ETH_ALEN);
+ KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 1, ha->refcount);
+
+ /* Second work run: stale entry gets unsynced from HW and removed */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_unsynced);
+ KUNIT_EXPECT_EQ(test, 0, netdev->uc.count);
+
+ rtnl_unlock();
+}
+
+/* Snapshot test: ADDR_A was stale (unsynced from hardware by driver),
+ * but concurrently re-added by the user. The re-add bumps refcount of
+ * the existing stale entry. Reconcile applies delta=-1, leaving ADDR_A
+ * as a fresh entry (sync_cnt=0, refcount=1) for the next work run.
+ */
+static void dev_addr_test_snapshot_readd_during_unsync(struct kunit *test)
+{
+ struct net_device *netdev = test->priv;
+ struct netdev_hw_addr_list snap, ref;
+ struct dev_addr_test_priv *datp;
+ struct netdev_hw_addr *ha;
+ u8 addr[ETH_ALEN];
+
+ datp = netdev_priv(netdev);
+
+ rtnl_lock();
+
+ memset(addr, ADDR_A, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+
+ /* Sync ADDR_A to hardware: sync_cnt=1, refcount=2 */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+
+ /* User removes ADDR_A: refcount=1, sync_cnt=1 -> stale */
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_del(netdev, addr));
+
+ /* Snapshot: ADDR_A is stale (sync_cnt=1, refcount=1) */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_init(&snap);
+ __hw_addr_init(&ref);
+ KUNIT_EXPECT_EQ(test, 0,
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ KUNIT_EXPECT_EQ(test, 0,
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ netif_addr_unlock_bh(netdev);
+
+ /* Driver unsyncs stale ADDR_A from hardware */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&snap, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_unsynced);
+
+ /* Concurrent: user re-adds ADDR_A. dev_uc_add finds the existing
+ * stale entry and bumps refcount from 1 -> 2. sync_cnt stays 1.
+ */
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+ KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
+
+ /* Reconcile: ref sync_cnt=1 matches real sync_cnt=1, delta=-1
+ * applied. Result: sync_cnt=0, refcount=1 (fresh).
+ */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ netif_addr_unlock_bh(netdev);
+
+ /* Entry survives as fresh: needs re-sync to HW */
+ KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
+ ha = list_first_entry(&netdev->uc.list, struct netdev_hw_addr, list);
+ KUNIT_EXPECT_MEMEQ(test, ha->addr, addr, ETH_ALEN);
+ KUNIT_EXPECT_EQ(test, 0, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 1, ha->refcount);
+
+ /* Second work run: fresh entry gets synced to HW */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+
+ rtnl_unlock();
+}
+
+/* Snapshot test: ADDR_A is new (synced by driver), and independent ADDR_B
+ * is concurrently removed from the real list. A's sync delta propagates
+ * normally; B's absence doesn't interfere.
+ */
+static void dev_addr_test_snapshot_add_and_remove(struct kunit *test)
+{
+ struct net_device *netdev = test->priv;
+ struct netdev_hw_addr_list snap, ref;
+ struct dev_addr_test_priv *datp;
+ struct netdev_hw_addr *ha;
+ u8 addr[ETH_ALEN];
+
+ datp = netdev_priv(netdev);
+
+ rtnl_lock();
+
+ /* Add ADDR_A and ADDR_B (will be synced then removed) */
+ memset(addr, ADDR_A, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+ memset(addr, ADDR_B, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+
+ /* Sync both to hardware: sync_cnt=1, refcount=2 */
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+
+ /* Add ADDR_C (new, will be synced by snapshot) */
+ memset(addr, ADDR_C, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+
+ /* Snapshot: A,B synced (sync_cnt=1,refcount=2); C new (0,1) */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_init(&snap);
+ __hw_addr_init(&ref);
+ KUNIT_EXPECT_EQ(test, 0,
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ KUNIT_EXPECT_EQ(test, 0,
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ netif_addr_unlock_bh(netdev);
+
+ /* Driver syncs snapshot: ADDR_C is new -> synced; A,B already synced */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&snap, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_C, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+
+ /* Concurrent: user removes addr B while driver was working */
+ memset(addr, ADDR_B, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_del(netdev, addr));
+
+ /* Reconcile: ADDR_C's delta=+1 applied to real list.
+ * ADDR_B's delta=0 (unchanged in snapshot),
+ * so nothing to apply to ADDR_B.
+ */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ netif_addr_unlock_bh(netdev);
+
+ /* ADDR_A: unchanged (sync_cnt=1, refcount=2)
+ * ADDR_B: refcount went from 2->1 via dev_uc_del (still present, stale)
+ * ADDR_C: sync propagated (sync_cnt=1, refcount=2)
+ */
+ KUNIT_EXPECT_EQ(test, 3, netdev->uc.count);
+ netdev_hw_addr_list_for_each(ha, &netdev->uc) {
+ u8 id = ha->addr[0];
+
+ if (!memchr_inv(ha->addr, id, ETH_ALEN)) {
+ if (id == ADDR_A) {
+ KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 2, ha->refcount);
+ } else if (id == ADDR_B) {
+ /* B: still present but now stale */
+ KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 1, ha->refcount);
+ } else if (id == ADDR_C) {
+ KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 2, ha->refcount);
+ }
+ }
+ }
+
+ /* Second work run: ADDR_B is stale, gets unsynced and removed */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_B, datp->addr_unsynced);
+ KUNIT_EXPECT_EQ(test, 2, netdev->uc.count);
+
+ rtnl_unlock();
+}
+
+static void dev_addr_test_snapshot_benchmark(struct kunit *test)
+{
+ struct net_device *netdev = test->priv;
+ struct netdev_hw_addr_list snap;
+ u8 addr[ETH_ALEN];
+ s64 duration = 0;
+ ktime_t start;
+ int i, iter;
+
+ rtnl_lock();
+
+ for (i = 0; i < 1024; i++) {
+ memset(addr, 0, sizeof(addr));
+ addr[0] = (i >> 8) & 0xff;
+ addr[1] = i & 0xff;
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+ }
+
+ for (iter = 0; iter < 1000; iter++) {
+ netif_addr_lock_bh(netdev);
+ __hw_addr_init(&snap);
+
+ start = ktime_get();
+ KUNIT_EXPECT_EQ(test, 0,
+ __hw_addr_list_snapshot(&snap, &netdev->uc,
+ ETH_ALEN));
+ duration += ktime_to_ns(ktime_sub(ktime_get(), start));
+
+ netif_addr_unlock_bh(netdev);
+ __hw_addr_flush(&snap);
+ }
+
+ kunit_info(test,
+ "1024 addrs x 1000 snapshots: %lld ns total, %lld ns/iter",
+ duration, div_s64(duration, 1000));
+
+ rtnl_unlock();
+}
+
static struct kunit_case dev_addr_test_cases[] = {
KUNIT_CASE(dev_addr_test_basic),
KUNIT_CASE(dev_addr_test_sync_one),
@@ -232,6 +585,11 @@ static struct kunit_case dev_addr_test_cases[] = {
KUNIT_CASE(dev_addr_test_del_main),
KUNIT_CASE(dev_addr_test_add_set),
KUNIT_CASE(dev_addr_test_add_excl),
+ KUNIT_CASE(dev_addr_test_snapshot_sync),
+ KUNIT_CASE(dev_addr_test_snapshot_remove_during_sync),
+ KUNIT_CASE(dev_addr_test_snapshot_readd_during_unsync),
+ KUNIT_CASE(dev_addr_test_snapshot_add_and_remove),
+ KUNIT_CASE_SLOW(dev_addr_test_snapshot_benchmark),
{}
};
@@ -243,5 +601,6 @@ static struct kunit_suite dev_addr_test_suite = {
};
kunit_test_suite(dev_addr_test_suite);
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
MODULE_DESCRIPTION("KUnit tests for struct netdev_hw_addr_list");
MODULE_LICENSE("GPL");
--
2.52.0
^ permalink raw reply related
* [PATCH net-next v6 03/14] net: cache snapshot entries for ndo_set_rx_mode_async
From: Stanislav Fomichev @ 2026-04-07 15:30 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni
In-Reply-To: <20260407153101.3694714-1-sdf@fomichev.me>
Add a per-device netdev_hw_addr_list cache (rx_mode_addr_cache) that
allows __hw_addr_list_snapshot() and __hw_addr_list_reconcile() to
reuse previously allocated entries instead of hitting GFP_ATOMIC on
every snapshot cycle.
snapshot pops entries from the cache when available, falling back to
__hw_addr_create(). reconcile splices both snapshot lists back into
the cache via __hw_addr_splice(). The cache is flushed in
free_netdev().
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
(cherry picked from commit ba3ab1832a511f660fdc6231245b14bf610c05bd)
---
include/linux/netdevice.h | 7 ++--
net/core/dev.c | 3 ++
net/core/dev_addr_lists.c | 66 ++++++++++++++++++++++++----------
net/core/dev_addr_lists_test.c | 60 +++++++++++++++++++++----------
4 files changed, 97 insertions(+), 39 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 0a41b216cbcf..0c11bfd716a9 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1919,6 +1919,7 @@ enum netdev_reg_state {
* does not implement ndo_set_rx_mode()
* @rx_mode_node: List entry for rx_mode work processing
* @rx_mode_tracker: Refcount tracker for rx_mode work
+ * @rx_mode_addr_cache: Recycled snapshot entries for rx_mode work
* @uc: unicast mac addresses
* @mc: multicast mac addresses
* @dev_addrs: list of device hw addresses
@@ -2312,6 +2313,7 @@ struct net_device {
bool uc_promisc;
struct list_head rx_mode_node;
netdevice_tracker rx_mode_tracker;
+ struct netdev_hw_addr_list rx_mode_addr_cache;
#ifdef CONFIG_LOCKDEP
unsigned char nested_level;
#endif
@@ -5016,10 +5018,11 @@ void __hw_addr_init(struct netdev_hw_addr_list *list);
void __hw_addr_flush(struct netdev_hw_addr_list *list);
int __hw_addr_list_snapshot(struct netdev_hw_addr_list *snap,
const struct netdev_hw_addr_list *list,
- int addr_len);
+ int addr_len, struct netdev_hw_addr_list *cache);
void __hw_addr_list_reconcile(struct netdev_hw_addr_list *real_list,
struct netdev_hw_addr_list *work,
- struct netdev_hw_addr_list *ref, int addr_len);
+ struct netdev_hw_addr_list *ref, int addr_len,
+ struct netdev_hw_addr_list *cache);
/* Functions used for device addresses handling */
void dev_addr_mod(struct net_device *dev, unsigned int offset,
diff --git a/net/core/dev.c b/net/core/dev.c
index fe33feacc4f3..23a832c9facc 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -12074,6 +12074,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
mutex_init(&dev->lock);
INIT_LIST_HEAD(&dev->rx_mode_node);
+ __hw_addr_init(&dev->rx_mode_addr_cache);
dev->priv_flags = IFF_XMIT_DST_RELEASE | IFF_XMIT_DST_RELEASE_PERM;
setup(dev);
@@ -12178,6 +12179,8 @@ void free_netdev(struct net_device *dev)
kfree(rcu_dereference_protected(dev->ingress_queue, 1));
+ __hw_addr_flush(&dev->rx_mode_addr_cache);
+
/* Flush device addresses */
dev_addr_flush(dev);
diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c
index 477392127e8a..88e995db15dd 100644
--- a/net/core/dev_addr_lists.c
+++ b/net/core/dev_addr_lists.c
@@ -511,30 +511,50 @@ void __hw_addr_init(struct netdev_hw_addr_list *list)
}
EXPORT_SYMBOL(__hw_addr_init);
+static void __hw_addr_splice(struct netdev_hw_addr_list *dst,
+ struct netdev_hw_addr_list *src)
+{
+ src->tree = RB_ROOT;
+ list_splice_init(&src->list, &dst->list);
+ dst->count += src->count;
+ src->count = 0;
+}
+
/**
* __hw_addr_list_snapshot - create a snapshot copy of an address list
* @snap: destination snapshot list (needs to be __hw_addr_init-initialized)
* @list: source address list to snapshot
* @addr_len: length of addresses
+ * @cache: entry cache to reuse entries from; falls back to GFP_ATOMIC
*
- * Creates a copy of @list with individually allocated entries suitable
- * for use with __hw_addr_sync_dev() and other list manipulation helpers.
- * Each entry is allocated with GFP_ATOMIC; must be called under a spinlock.
+ * Creates a copy of @list reusing entries from @cache when available.
+ * Must be called under a spinlock.
*
* Return: 0 on success, -errno on failure.
*/
int __hw_addr_list_snapshot(struct netdev_hw_addr_list *snap,
const struct netdev_hw_addr_list *list,
- int addr_len)
+ int addr_len, struct netdev_hw_addr_list *cache)
{
struct netdev_hw_addr *ha, *entry;
list_for_each_entry(ha, &list->list, list) {
- entry = __hw_addr_create(ha->addr, addr_len, ha->type,
- false, false);
- if (!entry) {
- __hw_addr_flush(snap);
- return -ENOMEM;
+ if (cache->count) {
+ entry = list_first_entry(&cache->list,
+ struct netdev_hw_addr, list);
+ list_del(&entry->list);
+ cache->count--;
+ memcpy(entry->addr, ha->addr, addr_len);
+ entry->type = ha->type;
+ entry->global_use = false;
+ entry->synced = 0;
+ } else {
+ entry = __hw_addr_create(ha->addr, addr_len, ha->type,
+ false, false);
+ if (!entry) {
+ __hw_addr_flush(snap);
+ return -ENOMEM;
+ }
}
entry->sync_cnt = ha->sync_cnt;
entry->refcount = ha->refcount;
@@ -554,15 +574,17 @@ EXPORT_SYMBOL_IF_KUNIT(__hw_addr_list_snapshot);
* @work: the working snapshot (modified by driver via __hw_addr_sync_dev)
* @ref: the reference snapshot (untouched copy of original state)
* @addr_len: length of addresses
+ * @cache: entry cache to return snapshot entries to for reuse
*
* Walks the reference snapshot and compares each entry against the work
* snapshot to compute sync_cnt deltas. Applies those deltas to @real_list.
- * Frees both snapshots when done.
+ * Returns snapshot entries to @cache for reuse; frees both snapshots.
* Caller must hold netif_addr_lock_bh.
*/
void __hw_addr_list_reconcile(struct netdev_hw_addr_list *real_list,
struct netdev_hw_addr_list *work,
- struct netdev_hw_addr_list *ref, int addr_len)
+ struct netdev_hw_addr_list *ref, int addr_len,
+ struct netdev_hw_addr_list *cache)
{
struct netdev_hw_addr *ref_ha, *tmp, *work_ha, *real_ha;
int delta;
@@ -611,8 +633,8 @@ void __hw_addr_list_reconcile(struct netdev_hw_addr_list *real_list,
}
}
- __hw_addr_flush(work);
- __hw_addr_flush(ref);
+ __hw_addr_splice(cache, work);
+ __hw_addr_splice(cache, ref);
}
EXPORT_SYMBOL_IF_KUNIT(__hw_addr_list_reconcile);
@@ -1173,14 +1195,18 @@ static int netif_addr_lists_snapshot(struct net_device *dev,
{
int err;
- err = __hw_addr_list_snapshot(uc_snap, &dev->uc, dev->addr_len);
+ err = __hw_addr_list_snapshot(uc_snap, &dev->uc, dev->addr_len,
+ &dev->rx_mode_addr_cache);
if (!err)
- err = __hw_addr_list_snapshot(uc_ref, &dev->uc, dev->addr_len);
+ err = __hw_addr_list_snapshot(uc_ref, &dev->uc, dev->addr_len,
+ &dev->rx_mode_addr_cache);
if (!err)
err = __hw_addr_list_snapshot(mc_snap, &dev->mc,
- dev->addr_len);
+ dev->addr_len,
+ &dev->rx_mode_addr_cache);
if (!err)
- err = __hw_addr_list_snapshot(mc_ref, &dev->mc, dev->addr_len);
+ err = __hw_addr_list_snapshot(mc_ref, &dev->mc, dev->addr_len,
+ &dev->rx_mode_addr_cache);
if (err) {
__hw_addr_flush(uc_snap);
@@ -1197,8 +1223,10 @@ static void netif_addr_lists_reconcile(struct net_device *dev,
struct netdev_hw_addr_list *uc_ref,
struct netdev_hw_addr_list *mc_ref)
{
- __hw_addr_list_reconcile(&dev->uc, uc_snap, uc_ref, dev->addr_len);
- __hw_addr_list_reconcile(&dev->mc, mc_snap, mc_ref, dev->addr_len);
+ __hw_addr_list_reconcile(&dev->uc, uc_snap, uc_ref, dev->addr_len,
+ &dev->rx_mode_addr_cache);
+ __hw_addr_list_reconcile(&dev->mc, mc_snap, mc_ref, dev->addr_len,
+ &dev->rx_mode_addr_cache);
}
static void netif_rx_mode_run(struct net_device *dev)
diff --git a/net/core/dev_addr_lists_test.c b/net/core/dev_addr_lists_test.c
index fba926d5ec0d..260e71a2399f 100644
--- a/net/core/dev_addr_lists_test.c
+++ b/net/core/dev_addr_lists_test.c
@@ -251,8 +251,8 @@ static void dev_addr_test_add_excl(struct kunit *test)
*/
static void dev_addr_test_snapshot_sync(struct kunit *test)
{
+ struct netdev_hw_addr_list snap, ref, cache;
struct net_device *netdev = test->priv;
- struct netdev_hw_addr_list snap, ref;
struct dev_addr_test_priv *datp;
struct netdev_hw_addr *ha;
u8 addr[ETH_ALEN];
@@ -268,10 +268,13 @@ static void dev_addr_test_snapshot_sync(struct kunit *test)
netif_addr_lock_bh(netdev);
__hw_addr_init(&snap);
__hw_addr_init(&ref);
+ __hw_addr_init(&cache);
KUNIT_EXPECT_EQ(test, 0,
- __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN,
+ &cache));
KUNIT_EXPECT_EQ(test, 0,
- __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN,
+ &cache));
netif_addr_unlock_bh(netdev);
/* Driver syncs ADDR_A to hardware */
@@ -283,7 +286,8 @@ static void dev_addr_test_snapshot_sync(struct kunit *test)
/* Reconcile: delta=+1 applied to real entry */
netif_addr_lock_bh(netdev);
- __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN,
+ &cache);
netif_addr_unlock_bh(netdev);
/* Real entry should now reflect the sync: sync_cnt=1, refcount=2 */
@@ -301,6 +305,7 @@ static void dev_addr_test_snapshot_sync(struct kunit *test)
KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
+ __hw_addr_flush(&cache);
rtnl_unlock();
}
@@ -310,8 +315,8 @@ static void dev_addr_test_snapshot_sync(struct kunit *test)
*/
static void dev_addr_test_snapshot_remove_during_sync(struct kunit *test)
{
+ struct netdev_hw_addr_list snap, ref, cache;
struct net_device *netdev = test->priv;
- struct netdev_hw_addr_list snap, ref;
struct dev_addr_test_priv *datp;
struct netdev_hw_addr *ha;
u8 addr[ETH_ALEN];
@@ -327,10 +332,13 @@ static void dev_addr_test_snapshot_remove_during_sync(struct kunit *test)
netif_addr_lock_bh(netdev);
__hw_addr_init(&snap);
__hw_addr_init(&ref);
+ __hw_addr_init(&cache);
KUNIT_EXPECT_EQ(test, 0,
- __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN,
+ &cache));
KUNIT_EXPECT_EQ(test, 0,
- __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN,
+ &cache));
netif_addr_unlock_bh(netdev);
/* Driver syncs ADDR_A to hardware */
@@ -349,7 +357,8 @@ static void dev_addr_test_snapshot_remove_during_sync(struct kunit *test)
* so it gets re-inserted as stale (sync_cnt=1, refcount=1).
*/
netif_addr_lock_bh(netdev);
- __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN,
+ &cache);
netif_addr_unlock_bh(netdev);
KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
@@ -366,6 +375,7 @@ static void dev_addr_test_snapshot_remove_during_sync(struct kunit *test)
KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_unsynced);
KUNIT_EXPECT_EQ(test, 0, netdev->uc.count);
+ __hw_addr_flush(&cache);
rtnl_unlock();
}
@@ -376,8 +386,8 @@ static void dev_addr_test_snapshot_remove_during_sync(struct kunit *test)
*/
static void dev_addr_test_snapshot_readd_during_unsync(struct kunit *test)
{
+ struct netdev_hw_addr_list snap, ref, cache;
struct net_device *netdev = test->priv;
- struct netdev_hw_addr_list snap, ref;
struct dev_addr_test_priv *datp;
struct netdev_hw_addr *ha;
u8 addr[ETH_ALEN];
@@ -403,10 +413,13 @@ static void dev_addr_test_snapshot_readd_during_unsync(struct kunit *test)
netif_addr_lock_bh(netdev);
__hw_addr_init(&snap);
__hw_addr_init(&ref);
+ __hw_addr_init(&cache);
KUNIT_EXPECT_EQ(test, 0,
- __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN,
+ &cache));
KUNIT_EXPECT_EQ(test, 0,
- __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN,
+ &cache));
netif_addr_unlock_bh(netdev);
/* Driver unsyncs stale ADDR_A from hardware */
@@ -426,7 +439,8 @@ static void dev_addr_test_snapshot_readd_during_unsync(struct kunit *test)
* applied. Result: sync_cnt=0, refcount=1 (fresh).
*/
netif_addr_lock_bh(netdev);
- __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN,
+ &cache);
netif_addr_unlock_bh(netdev);
/* Entry survives as fresh: needs re-sync to HW */
@@ -443,6 +457,7 @@ static void dev_addr_test_snapshot_readd_during_unsync(struct kunit *test)
KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_synced);
KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+ __hw_addr_flush(&cache);
rtnl_unlock();
}
@@ -452,8 +467,8 @@ static void dev_addr_test_snapshot_readd_during_unsync(struct kunit *test)
*/
static void dev_addr_test_snapshot_add_and_remove(struct kunit *test)
{
+ struct netdev_hw_addr_list snap, ref, cache;
struct net_device *netdev = test->priv;
- struct netdev_hw_addr_list snap, ref;
struct dev_addr_test_priv *datp;
struct netdev_hw_addr *ha;
u8 addr[ETH_ALEN];
@@ -480,10 +495,13 @@ static void dev_addr_test_snapshot_add_and_remove(struct kunit *test)
netif_addr_lock_bh(netdev);
__hw_addr_init(&snap);
__hw_addr_init(&ref);
+ __hw_addr_init(&cache);
KUNIT_EXPECT_EQ(test, 0,
- __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN,
+ &cache));
KUNIT_EXPECT_EQ(test, 0,
- __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN,
+ &cache));
netif_addr_unlock_bh(netdev);
/* Driver syncs snapshot: ADDR_C is new -> synced; A,B already synced */
@@ -502,7 +520,8 @@ static void dev_addr_test_snapshot_add_and_remove(struct kunit *test)
* so nothing to apply to ADDR_B.
*/
netif_addr_lock_bh(netdev);
- __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN,
+ &cache);
netif_addr_unlock_bh(netdev);
/* ADDR_A: unchanged (sync_cnt=1, refcount=2)
@@ -536,13 +555,14 @@ static void dev_addr_test_snapshot_add_and_remove(struct kunit *test)
KUNIT_EXPECT_EQ(test, 1 << ADDR_B, datp->addr_unsynced);
KUNIT_EXPECT_EQ(test, 2, netdev->uc.count);
+ __hw_addr_flush(&cache);
rtnl_unlock();
}
static void dev_addr_test_snapshot_benchmark(struct kunit *test)
{
struct net_device *netdev = test->priv;
- struct netdev_hw_addr_list snap;
+ struct netdev_hw_addr_list snap, cache;
u8 addr[ETH_ALEN];
s64 duration = 0;
ktime_t start;
@@ -557,6 +577,8 @@ static void dev_addr_test_snapshot_benchmark(struct kunit *test)
KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
}
+ __hw_addr_init(&cache);
+
for (iter = 0; iter < 1000; iter++) {
netif_addr_lock_bh(netdev);
__hw_addr_init(&snap);
@@ -564,13 +586,15 @@ static void dev_addr_test_snapshot_benchmark(struct kunit *test)
start = ktime_get();
KUNIT_EXPECT_EQ(test, 0,
__hw_addr_list_snapshot(&snap, &netdev->uc,
- ETH_ALEN));
+ ETH_ALEN, &cache));
duration += ktime_to_ns(ktime_sub(ktime_get(), start));
netif_addr_unlock_bh(netdev);
__hw_addr_flush(&snap);
}
+ __hw_addr_flush(&cache);
+
kunit_info(test,
"1024 addrs x 1000 snapshots: %lld ns total, %lld ns/iter",
duration, div_s64(duration, 1000));
--
2.52.0
^ permalink raw reply related
* [PATCH net-next v6 05/14] fbnic: convert to ndo_set_rx_mode_async
From: Stanislav Fomichev @ 2026-04-07 15:30 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, Alexander Duyck, kernel-team,
Aleksandr Loktionov
In-Reply-To: <20260407153101.3694714-1-sdf@fomichev.me>
Convert fbnic from ndo_set_rx_mode to ndo_set_rx_mode_async. The
driver's __fbnic_set_rx_mode() now takes explicit uc/mc list
parameters and uses __hw_addr_sync_dev() on the snapshots instead
of __dev_uc_sync/__dev_mc_sync on the netdev directly.
Update callers in fbnic_up, fbnic_fw_config_after_crash,
fbnic_bmc_rpc_check and fbnic_set_mac to pass the real address
lists calling __fbnic_set_rx_mode outside the async work path.
Cc: Alexander Duyck <alexanderduyck@fb.com>
Cc: kernel-team@meta.com
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
.../net/ethernet/meta/fbnic/fbnic_netdev.c | 20 ++++++++++++-------
.../net/ethernet/meta/fbnic/fbnic_netdev.h | 4 +++-
drivers/net/ethernet/meta/fbnic/fbnic_pci.c | 4 ++--
drivers/net/ethernet/meta/fbnic/fbnic_rpc.c | 2 +-
4 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
index b4b396ca9bce..c406a3b56b37 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
@@ -183,7 +183,9 @@ static int fbnic_mc_unsync(struct net_device *netdev, const unsigned char *addr)
return ret;
}
-void __fbnic_set_rx_mode(struct fbnic_dev *fbd)
+void __fbnic_set_rx_mode(struct fbnic_dev *fbd,
+ struct netdev_hw_addr_list *uc,
+ struct netdev_hw_addr_list *mc)
{
bool uc_promisc = false, mc_promisc = false;
struct net_device *netdev = fbd->netdev;
@@ -213,10 +215,10 @@ void __fbnic_set_rx_mode(struct fbnic_dev *fbd)
}
/* Synchronize unicast and multicast address lists */
- err = __dev_uc_sync(netdev, fbnic_uc_sync, fbnic_uc_unsync);
+ err = __hw_addr_sync_dev(uc, netdev, fbnic_uc_sync, fbnic_uc_unsync);
if (err == -ENOSPC)
uc_promisc = true;
- err = __dev_mc_sync(netdev, fbnic_mc_sync, fbnic_mc_unsync);
+ err = __hw_addr_sync_dev(mc, netdev, fbnic_mc_sync, fbnic_mc_unsync);
if (err == -ENOSPC)
mc_promisc = true;
@@ -238,18 +240,21 @@ void __fbnic_set_rx_mode(struct fbnic_dev *fbd)
fbnic_write_tce_tcam(fbd);
}
-static void fbnic_set_rx_mode(struct net_device *netdev)
+static void fbnic_set_rx_mode(struct net_device *netdev,
+ struct netdev_hw_addr_list *uc,
+ struct netdev_hw_addr_list *mc)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
/* No need to update the hardware if we are not running */
if (netif_running(netdev))
- __fbnic_set_rx_mode(fbd);
+ __fbnic_set_rx_mode(fbd, uc, mc);
}
static int fbnic_set_mac(struct net_device *netdev, void *p)
{
+ struct fbnic_net *fbn = netdev_priv(netdev);
struct sockaddr *addr = p;
if (!is_valid_ether_addr(addr->sa_data))
@@ -257,7 +262,8 @@ static int fbnic_set_mac(struct net_device *netdev, void *p)
eth_hw_addr_set(netdev, addr->sa_data);
- fbnic_set_rx_mode(netdev);
+ if (netif_running(netdev))
+ __fbnic_set_rx_mode(fbn->fbd, &netdev->uc, &netdev->mc);
return 0;
}
@@ -551,7 +557,7 @@ static const struct net_device_ops fbnic_netdev_ops = {
.ndo_features_check = fbnic_features_check,
.ndo_set_mac_address = fbnic_set_mac,
.ndo_change_mtu = fbnic_change_mtu,
- .ndo_set_rx_mode = fbnic_set_rx_mode,
+ .ndo_set_rx_mode_async = fbnic_set_rx_mode,
.ndo_get_stats64 = fbnic_get_stats64,
.ndo_bpf = fbnic_bpf,
.ndo_hwtstamp_get = fbnic_hwtstamp_get,
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h
index 9129a658f8fa..eded20b0e9e4 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h
@@ -97,7 +97,9 @@ void fbnic_time_init(struct fbnic_net *fbn);
int fbnic_time_start(struct fbnic_net *fbn);
void fbnic_time_stop(struct fbnic_net *fbn);
-void __fbnic_set_rx_mode(struct fbnic_dev *fbd);
+void __fbnic_set_rx_mode(struct fbnic_dev *fbd,
+ struct netdev_hw_addr_list *uc,
+ struct netdev_hw_addr_list *mc);
void fbnic_clear_rx_mode(struct fbnic_dev *fbd);
void fbnic_phylink_get_pauseparam(struct net_device *netdev,
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
index e3aebbe3656d..6b139cf54256 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
@@ -135,7 +135,7 @@ void fbnic_up(struct fbnic_net *fbn)
fbnic_rss_reinit_hw(fbn->fbd, fbn);
- __fbnic_set_rx_mode(fbn->fbd);
+ __fbnic_set_rx_mode(fbn->fbd, &fbn->netdev->uc, &fbn->netdev->mc);
/* Enable Tx/Rx processing */
fbnic_napi_enable(fbn);
@@ -180,7 +180,7 @@ static int fbnic_fw_config_after_crash(struct fbnic_dev *fbd)
}
fbnic_rpc_reset_valid_entries(fbd);
- __fbnic_set_rx_mode(fbd);
+ __fbnic_set_rx_mode(fbd, &fbd->netdev->uc, &fbd->netdev->mc);
return 0;
}
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c b/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c
index 42a186db43ea..fe95b6f69646 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c
@@ -244,7 +244,7 @@ void fbnic_bmc_rpc_check(struct fbnic_dev *fbd)
if (fbd->fw_cap.need_bmc_tcam_reinit) {
fbnic_bmc_rpc_init(fbd);
- __fbnic_set_rx_mode(fbd);
+ __fbnic_set_rx_mode(fbd, &fbd->netdev->uc, &fbd->netdev->mc);
fbd->fw_cap.need_bmc_tcam_reinit = false;
}
--
2.52.0
^ permalink raw reply related
* [PATCH net-next v6 04/14] net: move promiscuity handling into netdev_rx_mode_work
From: Stanislav Fomichev @ 2026-04-07 15:30 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni, Aleksandr Loktionov
In-Reply-To: <20260407153101.3694714-1-sdf@fomichev.me>
Move unicast promiscuity tracking into netdev_rx_mode_work so it runs
under netdev_ops_lock instead of under the addr_lock spinlock. This
is required because __dev_set_promiscuity calls dev_change_rx_flags
and __dev_notify_flags, both of which may need to sleep.
Change ASSERT_RTNL() to netdev_ops_assert_locked() in
__dev_set_promiscuity, netif_set_allmulti and __dev_change_flags
since these are now called from the work queue under the ops lock.
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
Documentation/networking/netdevices.rst | 4 ++
net/core/dev.c | 16 ++---
net/core/dev_addr_lists.c | 82 ++++++++++++++++++-------
3 files changed, 68 insertions(+), 34 deletions(-)
diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst
index 8a488c21fd7c..390537323457 100644
--- a/Documentation/networking/netdevices.rst
+++ b/Documentation/networking/netdevices.rst
@@ -299,6 +299,10 @@ struct net_device synchronization rules
Notes: Async version of ndo_set_rx_mode which runs in process
context. Receives snapshots of the unicast and multicast address lists.
+ndo_change_rx_flags:
+ Synchronization: rtnl_lock() semaphore. In addition, netdev instance
+ lock if the driver implements queue management or shaper API.
+
ndo_setup_tc:
``TC_SETUP_BLOCK`` and ``TC_SETUP_FT`` are running under NFT locks
(i.e. no ``rtnl_lock`` and no device instance lock). The rest of
diff --git a/net/core/dev.c b/net/core/dev.c
index 23a832c9facc..d2e72fdf3dfe 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -9586,7 +9586,7 @@ int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify)
kuid_t uid;
kgid_t gid;
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
promiscuity = dev->promiscuity + inc;
if (promiscuity == 0) {
@@ -9622,16 +9622,8 @@ int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify)
dev_change_rx_flags(dev, IFF_PROMISC);
}
- if (notify) {
- /* The ops lock is only required to ensure consistent locking
- * for `NETDEV_CHANGE` notifiers. This function is sometimes
- * called without the lock, even for devices that are ops
- * locked, such as in `dev_uc_sync_multiple` when using
- * bonding or teaming.
- */
- netdev_ops_assert_locked(dev);
+ if (notify)
__dev_notify_flags(dev, old_flags, IFF_PROMISC, 0, NULL);
- }
return 0;
}
@@ -9653,7 +9645,7 @@ int netif_set_allmulti(struct net_device *dev, int inc, bool notify)
unsigned int old_flags = dev->flags, old_gflags = dev->gflags;
unsigned int allmulti, flags;
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
allmulti = dev->allmulti + inc;
if (allmulti == 0) {
@@ -9721,7 +9713,7 @@ int __dev_change_flags(struct net_device *dev, unsigned int flags,
unsigned int old_flags = dev->flags;
int ret;
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
/*
* Set the flags on our device.
diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c
index 88e995db15dd..49346d0cbc8a 100644
--- a/net/core/dev_addr_lists.c
+++ b/net/core/dev_addr_lists.c
@@ -1229,10 +1229,34 @@ static void netif_addr_lists_reconcile(struct net_device *dev,
&dev->rx_mode_addr_cache);
}
+/**
+ * netif_uc_promisc_update() - evaluate whether uc_promisc should be toggled.
+ * @dev: device
+ *
+ * Must be called under netif_addr_lock_bh.
+ * Return: +1 to enter promisc, -1 to leave, 0 for no change.
+ */
+static int netif_uc_promisc_update(struct net_device *dev)
+{
+ if (dev->priv_flags & IFF_UNICAST_FLT)
+ return 0;
+
+ if (!netdev_uc_empty(dev) && !dev->uc_promisc) {
+ dev->uc_promisc = true;
+ return 1;
+ }
+ if (netdev_uc_empty(dev) && dev->uc_promisc) {
+ dev->uc_promisc = false;
+ return -1;
+ }
+ return 0;
+}
+
static void netif_rx_mode_run(struct net_device *dev)
{
struct netdev_hw_addr_list uc_snap, mc_snap, uc_ref, mc_ref;
const struct net_device_ops *ops = dev->netdev_ops;
+ int promisc_inc;
int err;
might_sleep();
@@ -1246,22 +1270,39 @@ static void netif_rx_mode_run(struct net_device *dev)
if (!(dev->flags & IFF_UP) || !netif_device_present(dev))
return;
- netif_addr_lock_bh(dev);
- err = netif_addr_lists_snapshot(dev, &uc_snap, &mc_snap,
- &uc_ref, &mc_ref);
- if (err) {
- netdev_WARN(dev, "failed to sync uc/mc addresses\n");
+ if (ops->ndo_set_rx_mode_async) {
+ netif_addr_lock_bh(dev);
+ err = netif_addr_lists_snapshot(dev, &uc_snap, &mc_snap,
+ &uc_ref, &mc_ref);
+ if (err) {
+ netdev_WARN(dev, "failed to sync uc/mc addresses\n");
+ netif_addr_unlock_bh(dev);
+ return;
+ }
+
+ promisc_inc = netif_uc_promisc_update(dev);
+ netif_addr_unlock_bh(dev);
+ } else {
+ netif_addr_lock_bh(dev);
+ promisc_inc = netif_uc_promisc_update(dev);
netif_addr_unlock_bh(dev);
- return;
}
- netif_addr_unlock_bh(dev);
- ops->ndo_set_rx_mode_async(dev, &uc_snap, &mc_snap);
+ if (promisc_inc)
+ __dev_set_promiscuity(dev, promisc_inc, false);
- netif_addr_lock_bh(dev);
- netif_addr_lists_reconcile(dev, &uc_snap, &mc_snap,
- &uc_ref, &mc_ref);
- netif_addr_unlock_bh(dev);
+ if (ops->ndo_set_rx_mode_async) {
+ ops->ndo_set_rx_mode_async(dev, &uc_snap, &mc_snap);
+
+ netif_addr_lock_bh(dev);
+ netif_addr_lists_reconcile(dev, &uc_snap, &mc_snap,
+ &uc_ref, &mc_ref);
+ netif_addr_unlock_bh(dev);
+ } else if (ops->ndo_set_rx_mode) {
+ netif_addr_lock_bh(dev);
+ ops->ndo_set_rx_mode(dev);
+ netif_addr_unlock_bh(dev);
+ }
}
static void netdev_rx_mode_work(struct work_struct *work)
@@ -1312,6 +1353,7 @@ static void netif_rx_mode_queue(struct net_device *dev)
void __dev_set_rx_mode(struct net_device *dev)
{
const struct net_device_ops *ops = dev->netdev_ops;
+ int promisc_inc;
/* dev_open will call this function so the list will stay sane. */
if (!(dev->flags & IFF_UP))
@@ -1320,20 +1362,16 @@ void __dev_set_rx_mode(struct net_device *dev)
if (!netif_device_present(dev))
return;
- if (ops->ndo_set_rx_mode_async) {
+ if (ops->ndo_set_rx_mode_async || ops->ndo_change_rx_flags) {
netif_rx_mode_queue(dev);
return;
}
- if (!(dev->priv_flags & IFF_UNICAST_FLT)) {
- if (!netdev_uc_empty(dev) && !dev->uc_promisc) {
- __dev_set_promiscuity(dev, 1, false);
- dev->uc_promisc = true;
- } else if (netdev_uc_empty(dev) && dev->uc_promisc) {
- __dev_set_promiscuity(dev, -1, false);
- dev->uc_promisc = false;
- }
- }
+ /* Legacy path for non-ops-locked HW devices. */
+
+ promisc_inc = netif_uc_promisc_update(dev);
+ if (promisc_inc)
+ __dev_set_promiscuity(dev, promisc_inc, false);
if (ops->ndo_set_rx_mode)
ops->ndo_set_rx_mode(dev);
--
2.52.0
^ permalink raw reply related
* [PATCH net-next v6 07/14] bnxt: convert to ndo_set_rx_mode_async
From: Stanislav Fomichev @ 2026-04-07 15:30 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, Michael Chan, Pavan Chebbi,
Aleksandr Loktionov
In-Reply-To: <20260407153101.3694714-1-sdf@fomichev.me>
Convert bnxt from ndo_set_rx_mode to ndo_set_rx_mode_async.
bnxt_set_rx_mode, bnxt_mc_list_updated and bnxt_uc_list_updated
now take explicit uc/mc list parameters and iterate with
netdev_hw_addr_list_for_each instead of netdev_for_each_{uc,mc}_addr.
The bnxt_cfg_rx_mode internal caller passes the real lists under
netif_addr_lock_bh.
BNXT_RX_MASK_SP_EVENT is still used here, next patch converts to
the direct call.
Cc: Michael Chan <michael.chan@broadcom.com>
Cc: Pavan Chebbi <pavan.chebbi@broadcom.com>
Reviewed-by: Michael Chan <michael.chan@broadcom.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 31 +++++++++++++----------
1 file changed, 17 insertions(+), 14 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index fe8b886ff82e..569c7de7c5e4 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -11035,7 +11035,8 @@ static int bnxt_setup_nitroa0_vnic(struct bnxt *bp)
}
static int bnxt_cfg_rx_mode(struct bnxt *);
-static bool bnxt_mc_list_updated(struct bnxt *, u32 *);
+static bool bnxt_mc_list_updated(struct bnxt *, u32 *,
+ const struct netdev_hw_addr_list *);
static int bnxt_init_chip(struct bnxt *bp, bool irq_re_init)
{
@@ -11125,7 +11126,7 @@ static int bnxt_init_chip(struct bnxt *bp, bool irq_re_init)
} else if (bp->dev->flags & IFF_MULTICAST) {
u32 mask = 0;
- bnxt_mc_list_updated(bp, &mask);
+ bnxt_mc_list_updated(bp, &mask, &bp->dev->mc);
vnic->rx_mask |= mask;
}
@@ -13523,17 +13524,17 @@ void bnxt_get_ring_drv_stats(struct bnxt *bp,
bnxt_get_one_ring_drv_stats(bp, stats, &bp->bnapi[i]->cp_ring);
}
-static bool bnxt_mc_list_updated(struct bnxt *bp, u32 *rx_mask)
+static bool bnxt_mc_list_updated(struct bnxt *bp, u32 *rx_mask,
+ const struct netdev_hw_addr_list *mc)
{
struct bnxt_vnic_info *vnic = &bp->vnic_info[BNXT_VNIC_DEFAULT];
- struct net_device *dev = bp->dev;
struct netdev_hw_addr *ha;
u8 *haddr;
int mc_count = 0;
bool update = false;
int off = 0;
- netdev_for_each_mc_addr(ha, dev) {
+ netdev_hw_addr_list_for_each(ha, mc) {
if (mc_count >= BNXT_MAX_MC_ADDRS) {
*rx_mask |= CFA_L2_SET_RX_MASK_REQ_MASK_ALL_MCAST;
vnic->mc_list_count = 0;
@@ -13557,17 +13558,17 @@ static bool bnxt_mc_list_updated(struct bnxt *bp, u32 *rx_mask)
return update;
}
-static bool bnxt_uc_list_updated(struct bnxt *bp)
+static bool bnxt_uc_list_updated(struct bnxt *bp,
+ const struct netdev_hw_addr_list *uc)
{
- struct net_device *dev = bp->dev;
struct bnxt_vnic_info *vnic = &bp->vnic_info[BNXT_VNIC_DEFAULT];
struct netdev_hw_addr *ha;
int off = 0;
- if (netdev_uc_count(dev) != (vnic->uc_filter_count - 1))
+ if (netdev_hw_addr_list_count(uc) != (vnic->uc_filter_count - 1))
return true;
- netdev_for_each_uc_addr(ha, dev) {
+ netdev_hw_addr_list_for_each(ha, uc) {
if (!ether_addr_equal(ha->addr, vnic->uc_list + off))
return true;
@@ -13576,7 +13577,9 @@ static bool bnxt_uc_list_updated(struct bnxt *bp)
return false;
}
-static void bnxt_set_rx_mode(struct net_device *dev)
+static void bnxt_set_rx_mode(struct net_device *dev,
+ struct netdev_hw_addr_list *uc,
+ struct netdev_hw_addr_list *mc)
{
struct bnxt *bp = netdev_priv(dev);
struct bnxt_vnic_info *vnic;
@@ -13597,7 +13600,7 @@ static void bnxt_set_rx_mode(struct net_device *dev)
if (dev->flags & IFF_PROMISC)
mask |= CFA_L2_SET_RX_MASK_REQ_MASK_PROMISCUOUS;
- uc_update = bnxt_uc_list_updated(bp);
+ uc_update = bnxt_uc_list_updated(bp, uc);
if (dev->flags & IFF_BROADCAST)
mask |= CFA_L2_SET_RX_MASK_REQ_MASK_BCAST;
@@ -13605,7 +13608,7 @@ static void bnxt_set_rx_mode(struct net_device *dev)
mask |= CFA_L2_SET_RX_MASK_REQ_MASK_ALL_MCAST;
vnic->mc_list_count = 0;
} else if (dev->flags & IFF_MULTICAST) {
- mc_update = bnxt_mc_list_updated(bp, &mask);
+ mc_update = bnxt_mc_list_updated(bp, &mask, mc);
}
if (mask != vnic->rx_mask || uc_update || mc_update) {
@@ -13624,7 +13627,7 @@ static int bnxt_cfg_rx_mode(struct bnxt *bp)
bool uc_update;
netif_addr_lock_bh(dev);
- uc_update = bnxt_uc_list_updated(bp);
+ uc_update = bnxt_uc_list_updated(bp, &dev->uc);
netif_addr_unlock_bh(dev);
if (!uc_update)
@@ -15881,7 +15884,7 @@ static const struct net_device_ops bnxt_netdev_ops = {
.ndo_start_xmit = bnxt_start_xmit,
.ndo_stop = bnxt_close,
.ndo_get_stats64 = bnxt_get_stats64,
- .ndo_set_rx_mode = bnxt_set_rx_mode,
+ .ndo_set_rx_mode_async = bnxt_set_rx_mode,
.ndo_eth_ioctl = bnxt_ioctl,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = bnxt_change_mac_addr,
--
2.52.0
^ 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