--- /dev/null
+From dc23806a7c47ec5f1293aba407fb69519f976ee0 Mon Sep 17 00:00:00 2001
+From: Gui-Dong Han <hanguidong02@gmail.com>
+Date: Wed, 14 Jan 2026 00:28:43 +0800
+Subject: driver core: enforce device_lock for driver_match_device()
+
+From: Gui-Dong Han <hanguidong02@gmail.com>
+
+commit dc23806a7c47ec5f1293aba407fb69519f976ee0 upstream.
+
+Currently, driver_match_device() is called from three sites. One site
+(__device_attach_driver) holds device_lock(dev), but the other two
+(bind_store and __driver_attach) do not. This inconsistency means that
+bus match() callbacks are not guaranteed to be called with the lock
+held.
+
+Fix this by introducing driver_match_device_locked(), which guarantees
+holding the device lock using a scoped guard. Replace the unlocked calls
+in bind_store() and __driver_attach() with this new helper. Also add a
+lock assertion to driver_match_device() to enforce this guarantee.
+
+This consistency also fixes a known race condition. The driver_override
+implementation relies on the device_lock, so the missing lock led to the
+use-after-free (UAF) reported in Bugzilla for buses using this field.
+
+Stress testing the two newly locked paths for 24 hours with
+CONFIG_PROVE_LOCKING and CONFIG_LOCKDEP enabled showed no UAF recurrence
+and no lockdep warnings.
+
+Cc: stable@vger.kernel.org
+Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220789
+Suggested-by: Qiu-ji Chen <chenqiuji666@gmail.com>
+Signed-off-by: Gui-Dong Han <hanguidong02@gmail.com>
+Fixes: 49b420a13ff9 ("driver core: check bus->match without holding device lock")
+Reviewed-by: Danilo Krummrich <dakr@kernel.org>
+Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Reviewed-by: Rafael J. Wysocki (Intel) <rafael@kernel.org>
+Link: https://patch.msgid.link/20260113162843.12712-1-hanguidong02@gmail.com
+Signed-off-by: Danilo Krummrich <dakr@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/base/base.h | 9 +++++++++
+ drivers/base/bus.c | 2 +-
+ drivers/base/dd.c | 2 +-
+ 3 files changed, 11 insertions(+), 2 deletions(-)
+
+--- a/drivers/base/base.h
++++ b/drivers/base/base.h
+@@ -182,9 +182,18 @@ void device_set_deferred_probe_reason(co
+ static inline int driver_match_device(const struct device_driver *drv,
+ struct device *dev)
+ {
++ device_lock_assert(dev);
++
+ return drv->bus->match ? drv->bus->match(dev, drv) : 1;
+ }
+
++static inline int driver_match_device_locked(const struct device_driver *drv,
++ struct device *dev)
++{
++ guard(device)(dev);
++ return driver_match_device(drv, dev);
++}
++
+ static inline void dev_sync_state(struct device *dev)
+ {
+ if (dev->bus->sync_state)
+--- a/drivers/base/bus.c
++++ b/drivers/base/bus.c
+@@ -263,7 +263,7 @@ static ssize_t bind_store(struct device_
+ int err = -ENODEV;
+
+ dev = bus_find_device_by_name(bus, NULL, buf);
+- if (dev && driver_match_device(drv, dev)) {
++ if (dev && driver_match_device_locked(drv, dev)) {
+ err = device_driver_attach(drv, dev);
+ if (!err) {
+ /* success */
+--- a/drivers/base/dd.c
++++ b/drivers/base/dd.c
+@@ -1180,7 +1180,7 @@ static int __driver_attach(struct device
+ * is an error.
+ */
+
+- ret = driver_match_device(drv, dev);
++ ret = driver_match_device_locked(drv, dev);
+ if (ret == 0) {
+ /* no match */
+ return 0;
--- /dev/null
+From 4f3a06cc57976cafa8c6f716646be6c79a99e485 Mon Sep 17 00:00:00 2001
+From: Namjae Jeon <linkinjeon@kernel.org>
+Date: Mon, 9 Feb 2026 10:43:19 +0900
+Subject: ksmbd: add chann_lock to protect ksmbd_chann_list xarray
+
+From: Namjae Jeon <linkinjeon@kernel.org>
+
+commit 4f3a06cc57976cafa8c6f716646be6c79a99e485 upstream.
+
+ksmbd_chann_list xarray lacks synchronization, allowing use-after-free in
+multi-channel sessions (between lookup_chann_list() and ksmbd_chann_del).
+
+Adds rw_semaphore chann_lock to struct ksmbd_session and protects
+all xa_load/xa_store/xa_erase accesses.
+
+Cc: stable@vger.kernel.org
+Reported-by: Igor Stepansky <igor.stepansky@orca.security>
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/mgmt/user_session.c | 5 +++++
+ fs/smb/server/mgmt/user_session.h | 1 +
+ fs/smb/server/smb2pdu.c | 12 +++++++++++-
+ 3 files changed, 17 insertions(+), 1 deletion(-)
+
+--- a/fs/smb/server/mgmt/user_session.c
++++ b/fs/smb/server/mgmt/user_session.c
+@@ -32,12 +32,14 @@ static void free_channel_list(struct ksm
+ struct channel *chann;
+ unsigned long index;
+
++ down_write(&sess->chann_lock);
+ xa_for_each(&sess->ksmbd_chann_list, index, chann) {
+ xa_erase(&sess->ksmbd_chann_list, index);
+ kfree(chann);
+ }
+
+ xa_destroy(&sess->ksmbd_chann_list);
++ up_write(&sess->chann_lock);
+ }
+
+ static void __session_rpc_close(struct ksmbd_session *sess,
+@@ -220,7 +222,9 @@ static int ksmbd_chann_del(struct ksmbd_
+ {
+ struct channel *chann;
+
++ down_write(&sess->chann_lock);
+ chann = xa_erase(&sess->ksmbd_chann_list, (long)conn);
++ up_write(&sess->chann_lock);
+ if (!chann)
+ return -ENOENT;
+
+@@ -454,6 +458,7 @@ static struct ksmbd_session *__session_c
+ rwlock_init(&sess->tree_conns_lock);
+ atomic_set(&sess->refcnt, 2);
+ init_rwsem(&sess->rpc_lock);
++ init_rwsem(&sess->chann_lock);
+
+ ret = __init_smb2_session(sess);
+ if (ret)
+--- a/fs/smb/server/mgmt/user_session.h
++++ b/fs/smb/server/mgmt/user_session.h
+@@ -49,6 +49,7 @@ struct ksmbd_session {
+ char sess_key[CIFS_KEY_SIZE];
+
+ struct hlist_node hlist;
++ struct rw_semaphore chann_lock;
+ struct xarray ksmbd_chann_list;
+ struct xarray tree_conns;
+ struct ida tree_conn_ida;
+--- a/fs/smb/server/smb2pdu.c
++++ b/fs/smb/server/smb2pdu.c
+@@ -79,7 +79,13 @@ static inline bool check_session_id(stru
+
+ struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn *conn)
+ {
+- return xa_load(&sess->ksmbd_chann_list, (long)conn);
++ struct channel *chann;
++
++ down_read(&sess->chann_lock);
++ chann = xa_load(&sess->ksmbd_chann_list, (long)conn);
++ up_read(&sess->chann_lock);
++
++ return chann;
+ }
+
+ /**
+@@ -1558,8 +1564,10 @@ binding_session:
+ return -ENOMEM;
+
+ chann->conn = conn;
++ down_write(&sess->chann_lock);
+ old = xa_store(&sess->ksmbd_chann_list, (long)conn, chann,
+ KSMBD_DEFAULT_GFP);
++ up_write(&sess->chann_lock);
+ if (xa_is_err(old)) {
+ kfree(chann);
+ return xa_err(old);
+@@ -1651,8 +1659,10 @@ binding_session:
+ return -ENOMEM;
+
+ chann->conn = conn;
++ down_write(&sess->chann_lock);
+ old = xa_store(&sess->ksmbd_chann_list, (long)conn,
+ chann, KSMBD_DEFAULT_GFP);
++ up_write(&sess->chann_lock);
+ if (xa_is_err(old)) {
+ kfree(chann);
+ return xa_err(old);
--- /dev/null
+From 010eb01ce23b34b50531448b0da391c7f05a72af Mon Sep 17 00:00:00 2001
+From: Namjae Jeon <linkinjeon@kernel.org>
+Date: Sat, 24 Jan 2026 10:55:46 +0900
+Subject: ksmbd: fix infinite loop caused by next_smb2_rcv_hdr_off reset in error paths
+
+From: Namjae Jeon <linkinjeon@kernel.org>
+
+commit 010eb01ce23b34b50531448b0da391c7f05a72af upstream.
+
+The problem occurs when a signed request fails smb2 signature verification
+check. In __process_request(), if check_sign_req() returns an error,
+set_smb2_rsp_status(work, STATUS_ACCESS_DENIED) is called.
+set_smb2_rsp_status() set work->next_smb2_rcv_hdr_off as zero. By resetting
+next_smb2_rcv_hdr_off to zero, the pointer to the next command in the chain
+is lost. Consequently, is_chained_smb2_message() continues to point to
+the same request header instead of advancing. If the header's NextCommand
+field is non-zero, the function returns true, causing __handle_ksmbd_work()
+to repeatedly process the same failed request in an infinite loop.
+This results in the kernel log being flooded with "bad smb2 signature"
+messages and high CPU usage.
+
+This patch fixes the issue by changing the return value from
+SERVER_HANDLER_CONTINUE to SERVER_HANDLER_ABORT. This ensures that
+the processing loop terminates immediately rather than attempting to
+continue from an invalidated offset.
+
+Reported-by: tianshuo han <hantianshuo233@gmail.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/server.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/fs/smb/server/server.c
++++ b/fs/smb/server/server.c
+@@ -126,21 +126,21 @@ static int __process_request(struct ksmb
+ andx_again:
+ if (command >= conn->max_cmds) {
+ conn->ops->set_rsp_status(work, STATUS_INVALID_PARAMETER);
+- return SERVER_HANDLER_CONTINUE;
++ return SERVER_HANDLER_ABORT;
+ }
+
+ cmds = &conn->cmds[command];
+ if (!cmds->proc) {
+ ksmbd_debug(SMB, "*** not implemented yet cmd = %x\n", command);
+ conn->ops->set_rsp_status(work, STATUS_NOT_IMPLEMENTED);
+- return SERVER_HANDLER_CONTINUE;
++ return SERVER_HANDLER_ABORT;
+ }
+
+ if (work->sess && conn->ops->is_sign_req(work, command)) {
+ ret = conn->ops->check_sign_req(work);
+ if (!ret) {
+ conn->ops->set_rsp_status(work, STATUS_ACCESS_DENIED);
+- return SERVER_HANDLER_CONTINUE;
++ return SERVER_HANDLER_ABORT;
+ }
+ }
+
io_uring-io-wq-add-exit-on-idle-state.patch
io_uring-allow-io-wq-workers-to-exit-when-unused.patch
+smb-client-split-cached_fid-bitfields-to-avoid-shared-byte-rmw-races.patch
+ksmbd-fix-infinite-loop-caused-by-next_smb2_rcv_hdr_off-reset-in-error-paths.patch
+ksmbd-add-chann_lock-to-protect-ksmbd_chann_list-xarray.patch
+smb-server-fix-leak-of-active_num_conn-in-ksmbd_tcp_new_connection.patch
+smb-smbdirect-introduce-smbdirect_socket.recv_io.credits.available.patch
+smb-smbdirect-introduce-smbdirect_socket.send_io.bcredits.patch
+smb-server-make-use-of-smbdirect_socket.recv_io.credits.available.patch
+smb-server-let-recv_done-queue-a-refill-when-the-peer-is-low-on-credits.patch
+smb-server-make-use-of-smbdirect_socket.send_io.bcredits.patch
+smb-server-fix-last-send-credit-problem-causing-disconnects.patch
+smb-server-let-send_done-handle-a-completion-without-ib_send_signaled.patch
+smb-client-make-use-of-smbdirect_socket.recv_io.credits.available.patch
+smb-client-let-recv_done-queue-a-refill-when-the-peer-is-low-on-credits.patch
+smb-client-let-smbd_post_send-make-use-of-request-wr.patch
+smb-client-remove-pointless-sc-recv_io.credits.count-rollback.patch
+smb-client-remove-pointless-sc-send_io.pending-handling-in-smbd_post_send_iter.patch
+smb-client-port-and-use-the-wait_for_credits-logic-used-by-server.patch
+smb-client-split-out-smbd_ib_post_send.patch
+smb-client-introduce-and-use-smbd_-alloc-free-_send_io.patch
+smb-client-use-smbdirect_send_batch-processing.patch
+smb-client-make-use-of-smbdirect_socket.send_io.bcredits.patch
+smb-client-fix-last-send-credit-problem-causing-disconnects.patch
+smb-client-let-smbd_post_send_negotiate_req-use-smbd_post_send.patch
+smb-client-let-send_done-handle-a-completion-without-ib_send_signaled.patch
+driver-core-enforce-device_lock-for-driver_match_device.patch
--- /dev/null
+From 93ac432274e1361b4f6cd69e7c5d9b3ac21e13f5 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:58 +0100
+Subject: smb: client: fix last send credit problem causing disconnects
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 93ac432274e1361b4f6cd69e7c5d9b3ac21e13f5 upstream.
+
+When we are about to use the last send credit that was
+granted to us by the peer, we need to wait until
+we are ourself able to grant at least one credit
+to the peer. Otherwise it might not be possible
+for the peer to grant more credits.
+
+The following sections in MS-SMBD are related to this:
+
+3.1.5.1 Sending Upper Layer Messages
+...
+If Connection.SendCredits is 1 and the CreditsGranted field of the
+message is 0, stop processing.
+...
+
+3.1.5.9 Managing Credits Prior to Sending
+...
+If Connection.ReceiveCredits is zero, or if Connection.SendCredits is
+one and the Connection.SendQueue is not empty, the sender MUST allocate
+and post at least one receive of size Connection.MaxReceiveSize and MUST
+increment Connection.ReceiveCredits by the number allocated and posted.
+If no receives are posted, the processing MUST return a value of zero to
+indicate to the caller that no Send message can be currently performed.
+...
+
+This is a similar logic as we have in the server.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 31 +++++++++++++++++++++++++++++--
+ 1 file changed, 29 insertions(+), 2 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -697,6 +697,15 @@ static void smbd_post_send_credits(struc
+
+ atomic_add(posted, &sc->recv_io.credits.available);
+
++ /*
++ * If the last send credit is waiting for credits
++ * it can grant we need to wake it up
++ */
++ if (posted &&
++ atomic_read(&sc->send_io.bcredits.count) == 0 &&
++ atomic_read(&sc->send_io.credits.count) == 0)
++ wake_up(&sc->send_io.credits.wait_queue);
++
+ /* Promptly send an immediate packet as defined in [MS-SMBD] 3.1.1.1 */
+ if (atomic_read(&sc->recv_io.credits.count) <
+ sc->recv_io.credits.target - 1) {
+@@ -1394,6 +1403,26 @@ static int smbd_post_send_iter(struct sm
+ goto err_wait_credit;
+ }
+
++ new_credits = manage_credits_prior_sending(sc);
++ if (new_credits == 0 &&
++ atomic_read(&sc->send_io.credits.count) == 0 &&
++ atomic_read(&sc->recv_io.credits.count) == 0) {
++ queue_work(sc->workqueue, &sc->recv_io.posted.refill_work);
++ rc = 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);
++ if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
++ rc = -ENOTCONN;
++ if (rc < 0) {
++ log_outgoing(ERR, "disconnected not sending on last credit\n");
++ rc = -EAGAIN;
++ goto err_wait_credit;
++ }
++
++ new_credits = manage_credits_prior_sending(sc);
++ }
++
+ request = smbd_alloc_send_io(sc);
+ if (IS_ERR(request)) {
+ rc = PTR_ERR(request);
+@@ -1448,8 +1477,6 @@ static int smbd_post_send_iter(struct sm
+
+ /* Fill in the packet header */
+ packet->credits_requested = cpu_to_le16(sp->send_credit_target);
+-
+- new_credits = manage_credits_prior_sending(sc);
+ packet->credits_granted = cpu_to_le16(new_credits);
+
+ packet->flags = 0;
--- /dev/null
+From dc77da0373529d43175984b390106be2d8f03609 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:55 +0100
+Subject: smb: client: introduce and use smbd_{alloc, free}_send_io()
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit dc77da0373529d43175984b390106be2d8f03609 upstream.
+
+This is basically a copy of smb_direct_{alloc,free}_sendmsg()
+in the server, with just using ib_dma_unmap_page() in all
+cases, which is the same as ib_dma_unmap_single().
+
+We'll use this logic in common code in future.
+(I basically backported it from my branch that
+as already has everything in common).
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 87 ++++++++++++++++++++++++++++++----------------
+ 1 file changed, 58 insertions(+), 29 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -493,10 +493,54 @@ static inline void *smbdirect_recv_io_pa
+ return (void *)response->packet;
+ }
+
++static struct smbdirect_send_io *smbd_alloc_send_io(struct smbdirect_socket *sc)
++{
++ struct smbdirect_send_io *msg;
++
++ msg = mempool_alloc(sc->send_io.mem.pool, GFP_KERNEL);
++ if (!msg)
++ return ERR_PTR(-ENOMEM);
++ msg->socket = sc;
++ INIT_LIST_HEAD(&msg->sibling_list);
++ msg->num_sge = 0;
++
++ return msg;
++}
++
++static void smbd_free_send_io(struct smbdirect_send_io *msg)
++{
++ struct smbdirect_socket *sc = msg->socket;
++ size_t i;
++
++ /*
++ * The list needs to be empty!
++ * The caller should take care of it.
++ */
++ WARN_ON_ONCE(!list_empty(&msg->sibling_list));
++
++ /*
++ * Note we call ib_dma_unmap_page(), even if some sges are mapped using
++ * ib_dma_map_single().
++ *
++ * The difference between _single() and _page() only matters for the
++ * ib_dma_map_*() case.
++ *
++ * For the ib_dma_unmap_*() case it does not matter as both take the
++ * dma_addr_t and dma_unmap_single_attrs() is just an alias to
++ * dma_unmap_page_attrs().
++ */
++ for (i = 0; i < msg->num_sge; i++)
++ ib_dma_unmap_page(sc->ib.dev,
++ msg->sge[i].addr,
++ msg->sge[i].length,
++ DMA_TO_DEVICE);
++
++ mempool_free(msg, sc->send_io.mem.pool);
++}
++
+ /* Called when a RDMA send is done */
+ static void send_done(struct ib_cq *cq, struct ib_wc *wc)
+ {
+- int i;
+ struct smbdirect_send_io *request =
+ container_of(wc->wr_cqe, struct smbdirect_send_io, cqe);
+ struct smbdirect_socket *sc = request->socket;
+@@ -505,12 +549,8 @@ static void send_done(struct ib_cq *cq,
+ log_rdma_send(INFO, "smbdirect_send_io 0x%p completed wc->status=%s\n",
+ request, ib_wc_status_msg(wc->status));
+
+- for (i = 0; i < request->num_sge; i++)
+- ib_dma_unmap_single(sc->ib.dev,
+- request->sge[i].addr,
+- request->sge[i].length,
+- DMA_TO_DEVICE);
+- mempool_free(request, sc->send_io.mem.pool);
++ /* Note this frees wc->wr_cqe, but not wc */
++ smbd_free_send_io(request);
+ lcredits += 1;
+
+ if (wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_SEND) {
+@@ -963,15 +1003,13 @@ static int smbd_post_send_negotiate_req(
+ {
+ struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct ib_send_wr send_wr;
+- int rc = -ENOMEM;
++ int rc;
+ struct smbdirect_send_io *request;
+ struct smbdirect_negotiate_req *packet;
+
+- request = mempool_alloc(sc->send_io.mem.pool, GFP_KERNEL);
+- if (!request)
+- return rc;
+-
+- request->socket = sc;
++ request = smbd_alloc_send_io(sc);
++ if (IS_ERR(request))
++ return PTR_ERR(request);
+
+ packet = smbdirect_send_io_payload(request);
+ packet->min_version = cpu_to_le16(SMBDIRECT_V1);
+@@ -983,7 +1021,6 @@ static int smbd_post_send_negotiate_req(
+ packet->max_fragmented_size =
+ cpu_to_le32(sp->max_fragmented_recv_size);
+
+- request->num_sge = 1;
+ request->sge[0].addr = ib_dma_map_single(
+ sc->ib.dev, (void *)packet,
+ sizeof(*packet), DMA_TO_DEVICE);
+@@ -991,6 +1028,7 @@ static int smbd_post_send_negotiate_req(
+ rc = -EIO;
+ goto dma_mapping_failed;
+ }
++ request->num_sge = 1;
+
+ request->sge[0].length = sizeof(*packet);
+ request->sge[0].lkey = sc->ib.pd->local_dma_lkey;
+@@ -1020,13 +1058,11 @@ static int smbd_post_send_negotiate_req(
+ /* if we reach here, post send failed */
+ log_rdma_send(ERR, "ib_post_send failed rc=%d\n", rc);
+ atomic_dec(&sc->send_io.pending.count);
+- ib_dma_unmap_single(sc->ib.dev, request->sge[0].addr,
+- request->sge[0].length, DMA_TO_DEVICE);
+
+ smbd_disconnect_rdma_connection(sc);
+
+ dma_mapping_failed:
+- mempool_free(request, sc->send_io.mem.pool);
++ smbd_free_send_io(request);
+ return rc;
+ }
+
+@@ -1187,7 +1223,7 @@ static int smbd_post_send_iter(struct sm
+ int *_remaining_data_length)
+ {
+ struct smbdirect_socket_parameters *sp = &sc->parameters;
+- int i, rc;
++ int rc;
+ int header_length;
+ int data_length;
+ struct smbdirect_send_io *request;
+@@ -1208,13 +1244,12 @@ static int smbd_post_send_iter(struct sm
+ goto err_wait_credit;
+ }
+
+- request = mempool_alloc(sc->send_io.mem.pool, GFP_KERNEL);
+- if (!request) {
+- rc = -ENOMEM;
++ request = smbd_alloc_send_io(sc);
++ if (IS_ERR(request)) {
++ rc = PTR_ERR(request);
+ goto err_alloc;
+ }
+
+- request->socket = sc;
+ memset(request->sge, 0, sizeof(request->sge));
+
+ /* Map the packet to DMA */
+@@ -1292,13 +1327,7 @@ static int smbd_post_send_iter(struct sm
+ return 0;
+
+ err_dma:
+- for (i = 0; i < request->num_sge; i++)
+- if (request->sge[i].addr)
+- ib_dma_unmap_single(sc->ib.dev,
+- request->sge[i].addr,
+- request->sge[i].length,
+- DMA_TO_DEVICE);
+- mempool_free(request, sc->send_io.mem.pool);
++ smbd_free_send_io(request);
+
+ err_alloc:
+ atomic_inc(&sc->send_io.credits.count);
--- /dev/null
+From defb3c05fee94b296eebe05aaea16d2664b00252 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:49 +0100
+Subject: smb: client: let recv_done() queue a refill when the peer is low on credits
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit defb3c05fee94b296eebe05aaea16d2664b00252 upstream.
+
+In captures I saw that Windows was granting 191 credits in a batch
+when its peer posted a lot of messages. We are asking for a
+credit target of 255 and 191 is 252*3/4.
+
+So we also use that logic in order to fill the
+recv buffers available to the peer.
+
+Fixes: 02548c477a90 ("smb: client: queue post_recv_credits_work also if the peer raises the credit target")
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -663,6 +663,7 @@ static void recv_done(struct ib_cq *cq,
+ container_of(wc->wr_cqe, struct smbdirect_recv_io, cqe);
+ struct smbdirect_socket *sc = response->socket;
+ struct smbdirect_socket_parameters *sp = &sc->parameters;
++ int current_recv_credits;
+ u16 old_recv_credit_target;
+ u32 data_offset = 0;
+ u32 data_length = 0;
+@@ -747,7 +748,8 @@ static void recv_done(struct ib_cq *cq,
+ }
+
+ atomic_dec(&sc->recv_io.posted.count);
+- atomic_dec(&sc->recv_io.credits.count);
++ current_recv_credits = atomic_dec_return(&sc->recv_io.credits.count);
++
+ old_recv_credit_target = sc->recv_io.credits.target;
+ sc->recv_io.credits.target =
+ le16_to_cpu(data_transfer->credits_requested);
+@@ -783,7 +785,8 @@ static void recv_done(struct ib_cq *cq,
+ * reassembly queue and wake up the reading thread
+ */
+ if (data_length) {
+- if (sc->recv_io.credits.target > old_recv_credit_target)
++ if (current_recv_credits <= (sc->recv_io.credits.target / 4) ||
++ sc->recv_io.credits.target > old_recv_credit_target)
+ queue_work(sc->workqueue, &sc->recv_io.posted.refill_work);
+
+ enqueue_reassembly(sc, response, data_length);
--- /dev/null
+From cf74fcdc43b322b6188a0750b5ee79e38be6d078 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:17:00 +0100
+Subject: smb: client: let send_done handle a completion without IB_SEND_SIGNALED
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit cf74fcdc43b322b6188a0750b5ee79e38be6d078 upstream.
+
+With smbdirect_send_batch processing we likely have requests without
+IB_SEND_SIGNALED, which will be destroyed in the final request
+that has IB_SEND_SIGNALED set.
+
+If the connection is broken all requests are signaled
+even without explicit IB_SEND_SIGNALED.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 27 +++++++++++++++++++++++++++
+ 1 file changed, 27 insertions(+)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -554,6 +554,32 @@ static void send_done(struct ib_cq *cq,
+ log_rdma_send(INFO, "smbdirect_send_io 0x%p completed wc->status=%s\n",
+ request, ib_wc_status_msg(wc->status));
+
++ if (unlikely(!(request->wr.send_flags & IB_SEND_SIGNALED))) {
++ /*
++ * This happens when smbdirect_send_io is a sibling
++ * before the final message, it is signaled on
++ * error anyway, so we need to skip
++ * smbdirect_connection_free_send_io here,
++ * otherwise is will destroy the memory
++ * of the siblings too, which will cause
++ * use after free problems for the others
++ * triggered from ib_drain_qp().
++ */
++ if (wc->status != IB_WC_SUCCESS)
++ goto skip_free;
++
++ /*
++ * This should not happen!
++ * But we better just close the
++ * connection...
++ */
++ log_rdma_send(ERR,
++ "unexpected send completion wc->status=%s (%d) wc->opcode=%d\n",
++ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
++ smbd_disconnect_rdma_connection(sc);
++ return;
++ }
++
+ /*
+ * Free possible siblings and then the main send_io
+ */
+@@ -567,6 +593,7 @@ static void send_done(struct ib_cq *cq,
+ lcredits += 1;
+
+ if (wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_SEND) {
++skip_free:
+ if (wc->status != IB_WC_WR_FLUSH_ERR)
+ log_rdma_send(ERR, "wc->status=%s wc->opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->opcode);
--- /dev/null
+From bf1656e12a9db2add716c7fb57b56967f69599fa Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:50 +0100
+Subject: smb: client: let smbd_post_send() make use of request->wr
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit bf1656e12a9db2add716c7fb57b56967f69599fa upstream.
+
+We don't need a stack variable in addition.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 15 +++++++--------
+ 1 file changed, 7 insertions(+), 8 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -1105,7 +1105,6 @@ static int manage_keep_alive_before_send
+ static int smbd_post_send(struct smbdirect_socket *sc,
+ struct smbdirect_send_io *request)
+ {
+- struct ib_send_wr send_wr;
+ int rc, i;
+
+ for (i = 0; i < request->num_sge; i++) {
+@@ -1121,14 +1120,14 @@ static int smbd_post_send(struct smbdire
+
+ request->cqe.done = send_done;
+
+- send_wr.next = NULL;
+- send_wr.wr_cqe = &request->cqe;
+- send_wr.sg_list = request->sge;
+- send_wr.num_sge = request->num_sge;
+- send_wr.opcode = IB_WR_SEND;
+- send_wr.send_flags = IB_SEND_SIGNALED;
++ request->wr.next = NULL;
++ request->wr.wr_cqe = &request->cqe;
++ request->wr.sg_list = request->sge;
++ request->wr.num_sge = request->num_sge;
++ request->wr.opcode = IB_WR_SEND;
++ request->wr.send_flags = IB_SEND_SIGNALED;
+
+- rc = ib_post_send(sc->ib.qp, &send_wr, NULL);
++ rc = ib_post_send(sc->ib.qp, &request->wr, NULL);
+ if (rc) {
+ log_rdma_send(ERR, "ib_post_send failed rc=%d\n", rc);
+ smbd_disconnect_rdma_connection(sc);
--- /dev/null
+From 5b1c6149657af840a02885135c700ab42e6aa322 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:59 +0100
+Subject: smb: client: let smbd_post_send_negotiate_req() use smbd_post_send()
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 5b1c6149657af840a02885135c700ab42e6aa322 upstream.
+
+The server has similar logic and it makes sure that
+request->wr is used instead of a stack struct ib_send_wr send_wr.
+
+This makes sure send_done can see request->wr.send_flags
+as the next commit will check for IB_SEND_SIGNALED
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 32 +++++++-------------------------
+ 1 file changed, 7 insertions(+), 25 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -35,6 +35,10 @@ static void enqueue_reassembly(
+ static struct smbdirect_recv_io *_get_first_reassembly(
+ struct smbdirect_socket *sc);
+
++static int smbd_post_send(struct smbdirect_socket *sc,
++ struct smbdirect_send_batch *batch,
++ struct smbdirect_send_io *request);
++
+ static int smbd_post_recv(
+ struct smbdirect_socket *sc,
+ struct smbdirect_recv_io *response);
+@@ -1021,7 +1025,6 @@ out1:
+ static int smbd_post_send_negotiate_req(struct smbdirect_socket *sc)
+ {
+ struct smbdirect_socket_parameters *sp = &sc->parameters;
+- struct ib_send_wr send_wr;
+ int rc;
+ struct smbdirect_send_io *request;
+ struct smbdirect_negotiate_req *packet;
+@@ -1052,33 +1055,12 @@ static int smbd_post_send_negotiate_req(
+ request->sge[0].length = sizeof(*packet);
+ request->sge[0].lkey = sc->ib.pd->local_dma_lkey;
+
+- ib_dma_sync_single_for_device(
+- sc->ib.dev, request->sge[0].addr,
+- request->sge[0].length, DMA_TO_DEVICE);
+-
+- request->cqe.done = send_done;
+-
+- send_wr.next = NULL;
+- send_wr.wr_cqe = &request->cqe;
+- send_wr.sg_list = request->sge;
+- send_wr.num_sge = request->num_sge;
+- send_wr.opcode = IB_WR_SEND;
+- send_wr.send_flags = IB_SEND_SIGNALED;
+-
+- log_rdma_send(INFO, "sge addr=0x%llx length=%u lkey=0x%x\n",
+- request->sge[0].addr,
+- request->sge[0].length, request->sge[0].lkey);
+-
+- atomic_inc(&sc->send_io.pending.count);
+- rc = ib_post_send(sc->ib.qp, &send_wr, NULL);
++ rc = smbd_post_send(sc, NULL, request);
+ if (!rc)
+ return 0;
+
+- /* if we reach here, post send failed */
+- log_rdma_send(ERR, "ib_post_send failed rc=%d\n", rc);
+- atomic_dec(&sc->send_io.pending.count);
+-
+- smbd_disconnect_rdma_connection(sc);
++ if (rc == -EAGAIN)
++ rc = -EIO;
+
+ dma_mapping_failed:
+ smbd_free_send_io(request);
--- /dev/null
+From 9911b1ed187a770a43950bf51f340ad4b7beecba Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:48 +0100
+Subject: smb: client: make use of smbdirect_socket.recv_io.credits.available
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 9911b1ed187a770a43950bf51f340ad4b7beecba upstream.
+
+The logic off managing recv credits by counting posted recv_io and
+granted credits is racy.
+
+That's because the peer might already consumed a credit,
+but between receiving the incoming recv at the hardware
+and processing the completion in the 'recv_done' functions
+we likely have a window where we grant credits, which
+don't really exist.
+
+So we better have a decicated counter for the
+available credits, which will be incremented
+when we posted new recv buffers and drained when
+we grant the credits to the peer.
+
+Fixes: 5fb9b459b368 ("smb: client: count the number of posted recv_io messages in order to calculated credits")
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 34 ++++++++++++++++++++++++++++------
+ 1 file changed, 28 insertions(+), 6 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -618,6 +618,7 @@ static void smbd_post_send_credits(struc
+ struct smbdirect_recv_io *response;
+ struct smbdirect_socket *sc =
+ container_of(work, struct smbdirect_socket, recv_io.posted.refill_work);
++ int posted = 0;
+
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
+ return;
+@@ -640,9 +641,12 @@ static void smbd_post_send_credits(struc
+ }
+
+ atomic_inc(&sc->recv_io.posted.count);
++ posted += 1;
+ }
+ }
+
++ atomic_add(posted, &sc->recv_io.credits.available);
++
+ /* Promptly send an immediate packet as defined in [MS-SMBD] 3.1.1.1 */
+ if (atomic_read(&sc->recv_io.credits.count) <
+ sc->recv_io.credits.target - 1) {
+@@ -1033,19 +1037,38 @@ dma_mapping_failed:
+ */
+ static int manage_credits_prior_sending(struct smbdirect_socket *sc)
+ {
++ int missing;
++ int available;
+ int new_credits;
+
+ if (atomic_read(&sc->recv_io.credits.count) >= sc->recv_io.credits.target)
+ return 0;
+
+- new_credits = atomic_read(&sc->recv_io.posted.count);
+- if (new_credits == 0)
++ missing = (int)sc->recv_io.credits.target - atomic_read(&sc->recv_io.credits.count);
++ available = atomic_xchg(&sc->recv_io.credits.available, 0);
++ new_credits = (u16)min3(U16_MAX, missing, available);
++ if (new_credits <= 0) {
++ /*
++ * If credits are available, but not granted
++ * we need to re-add them again.
++ */
++ if (available)
++ atomic_add(available, &sc->recv_io.credits.available);
+ return 0;
++ }
+
+- new_credits -= atomic_read(&sc->recv_io.credits.count);
+- if (new_credits <= 0)
+- return 0;
++ if (new_credits < available) {
++ /*
++ * Readd the remaining available again.
++ */
++ available -= new_credits;
++ atomic_add(available, &sc->recv_io.credits.available);
++ }
+
++ /*
++ * Remember we granted the credits
++ */
++ atomic_add(new_credits, &sc->recv_io.credits.count);
+ return new_credits;
+ }
+
+@@ -1217,7 +1240,6 @@ wait_credit:
+ packet->credits_requested = cpu_to_le16(sp->send_credit_target);
+
+ new_credits = manage_credits_prior_sending(sc);
+- atomic_add(new_credits, &sc->recv_io.credits.count);
+ packet->credits_granted = cpu_to_le16(new_credits);
+
+ packet->flags = 0;
--- /dev/null
+From 21538121efe6c8c5b51c742fa02cbe820bc48714 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:57 +0100
+Subject: smb: client: make use of smbdirect_socket.send_io.bcredits
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 21538121efe6c8c5b51c742fa02cbe820bc48714 upstream.
+
+It turns out that our code will corrupt the stream of
+reassabled data transfer messages when we trigger an
+immendiate (empty) send.
+
+In order to fix this we'll have a single 'batch' credit per
+connection. And code getting that credit is free to use
+as much messages until remaining_length reaches 0, then
+the batch credit it given back and the next logical send can
+happen.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 58 +++++++++++++++++++++++++++++++++++++++++++---
+ 1 file changed, 55 insertions(+), 3 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -657,6 +657,7 @@ static bool process_negotiation_response
+ sp->max_frmr_depth * PAGE_SIZE);
+ sp->max_frmr_depth = sp->max_read_write_size / PAGE_SIZE;
+
++ atomic_set(&sc->send_io.bcredits.count, 1);
+ sc->recv_io.expected = SMBDIRECT_EXPECT_DATA_TRANSFER;
+ return true;
+ }
+@@ -1214,6 +1215,7 @@ static void smbd_send_batch_init(struct
+ batch->wr_cnt = 0;
+ batch->need_invalidate_rkey = need_invalidate_rkey;
+ batch->remote_key = remote_key;
++ batch->credit = 0;
+ }
+
+ static int smbd_send_batch_flush(struct smbdirect_socket *sc,
+@@ -1224,7 +1226,7 @@ static int smbd_send_batch_flush(struct
+ int ret = 0;
+
+ if (list_empty(&batch->msg_list))
+- return 0;
++ goto release_credit;
+
+ first = list_first_entry(&batch->msg_list,
+ struct smbdirect_send_io,
+@@ -1266,6 +1268,13 @@ static int smbd_send_batch_flush(struct
+ smbd_free_send_io(last);
+ }
+
++release_credit:
++ if (is_last && !ret && batch->credit) {
++ atomic_add(batch->credit, &sc->send_io.bcredits.count);
++ batch->credit = 0;
++ wake_up(&sc->send_io.bcredits.wait_queue);
++ }
++
+ return ret;
+ }
+
+@@ -1291,6 +1300,25 @@ static int wait_for_credits(struct smbdi
+ } while (true);
+ }
+
++static int wait_for_send_bcredit(struct smbdirect_socket *sc,
++ struct smbdirect_send_batch *batch)
++{
++ int ret;
++
++ if (batch->credit)
++ return 0;
++
++ ret = wait_for_credits(sc,
++ &sc->send_io.bcredits.wait_queue,
++ &sc->send_io.bcredits.count,
++ 1);
++ if (ret)
++ return ret;
++
++ batch->credit = 1;
++ return 0;
++}
++
+ static int wait_for_send_lcredit(struct smbdirect_socket *sc,
+ struct smbdirect_send_batch *batch)
+ {
+@@ -1338,6 +1366,19 @@ static int smbd_post_send_iter(struct sm
+ struct smbdirect_send_io *request;
+ struct smbdirect_data_transfer *packet;
+ int new_credits = 0;
++ struct smbdirect_send_batch _batch;
++
++ if (!batch) {
++ smbd_send_batch_init(&_batch, false, 0);
++ batch = &_batch;
++ }
++
++ rc = wait_for_send_bcredit(sc, batch);
++ if (rc) {
++ log_outgoing(ERR, "disconnected not sending on wait_bcredit\n");
++ rc = -EAGAIN;
++ goto err_wait_bcredit;
++ }
+
+ rc = wait_for_send_lcredit(sc, batch);
+ if (rc) {
+@@ -1432,8 +1473,14 @@ static int smbd_post_send_iter(struct sm
+ le32_to_cpu(packet->remaining_data_length));
+
+ rc = smbd_post_send(sc, batch, request);
+- if (!rc)
+- return 0;
++ if (!rc) {
++ if (batch != &_batch)
++ return 0;
++
++ rc = smbd_send_batch_flush(sc, batch, true);
++ if (!rc)
++ return 0;
++ }
+
+ err_dma:
+ smbd_free_send_io(request);
+@@ -1447,6 +1494,11 @@ err_wait_credit:
+ wake_up(&sc->send_io.lcredits.wait_queue);
+
+ err_wait_lcredit:
++ atomic_add(batch->credit, &sc->send_io.bcredits.count);
++ batch->credit = 0;
++ wake_up(&sc->send_io.bcredits.wait_queue);
++
++err_wait_bcredit:
+ return rc;
+ }
+
--- /dev/null
+From bb848d205f7ac0141af52a5acb6dd116d9b71177 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:53 +0100
+Subject: smb: client: port and use the wait_for_credits logic used by server
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit bb848d205f7ac0141af52a5acb6dd116d9b71177 upstream.
+
+This simplifies the logic and prepares the use of
+smbdirect_send_batch in order to make sure
+all messages in a multi fragment send are grouped
+together.
+
+We'll add the smbdirect_send_batch processin
+in a later patch.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 70 ++++++++++++++++++++++++++++------------------
+ 1 file changed, 43 insertions(+), 27 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -1137,6 +1137,44 @@ static int smbd_post_send(struct smbdire
+ return rc;
+ }
+
++static int wait_for_credits(struct smbdirect_socket *sc,
++ wait_queue_head_t *waitq, atomic_t *total_credits,
++ int needed)
++{
++ int ret;
++
++ do {
++ if (atomic_sub_return(needed, total_credits) >= 0)
++ return 0;
++
++ atomic_add(needed, total_credits);
++ ret = wait_event_interruptible(*waitq,
++ atomic_read(total_credits) >= needed ||
++ sc->status != SMBDIRECT_SOCKET_CONNECTED);
++
++ if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
++ return -ENOTCONN;
++ else if (ret < 0)
++ return ret;
++ } while (true);
++}
++
++static int wait_for_send_lcredit(struct smbdirect_socket *sc)
++{
++ return wait_for_credits(sc,
++ &sc->send_io.lcredits.wait_queue,
++ &sc->send_io.lcredits.count,
++ 1);
++}
++
++static int wait_for_send_credits(struct smbdirect_socket *sc)
++{
++ return wait_for_credits(sc,
++ &sc->send_io.credits.wait_queue,
++ &sc->send_io.credits.count,
++ 1);
++}
++
+ static int smbd_post_send_iter(struct smbdirect_socket *sc,
+ struct iov_iter *iter,
+ int *_remaining_data_length)
+@@ -1149,41 +1187,19 @@ static int smbd_post_send_iter(struct sm
+ struct smbdirect_data_transfer *packet;
+ int new_credits = 0;
+
+-wait_lcredit:
+- /* Wait for local send credits */
+- rc = wait_event_interruptible(sc->send_io.lcredits.wait_queue,
+- atomic_read(&sc->send_io.lcredits.count) > 0 ||
+- sc->status != SMBDIRECT_SOCKET_CONNECTED);
+- if (rc)
+- goto err_wait_lcredit;
+-
+- if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
+- log_outgoing(ERR, "disconnected not sending on wait_credit\n");
++ rc = wait_for_send_lcredit(sc);
++ if (rc) {
++ log_outgoing(ERR, "disconnected not sending on wait_lcredit\n");
+ rc = -EAGAIN;
+ goto err_wait_lcredit;
+ }
+- if (unlikely(atomic_dec_return(&sc->send_io.lcredits.count) < 0)) {
+- atomic_inc(&sc->send_io.lcredits.count);
+- goto wait_lcredit;
+- }
+-
+-wait_credit:
+- /* Wait for send credits. A SMBD packet needs one credit */
+- rc = wait_event_interruptible(sc->send_io.credits.wait_queue,
+- atomic_read(&sc->send_io.credits.count) > 0 ||
+- sc->status != SMBDIRECT_SOCKET_CONNECTED);
+- if (rc)
+- goto err_wait_credit;
+
+- if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
++ rc = wait_for_send_credits(sc);
++ if (rc) {
+ log_outgoing(ERR, "disconnected not sending on wait_credit\n");
+ rc = -EAGAIN;
+ goto err_wait_credit;
+ }
+- if (unlikely(atomic_dec_return(&sc->send_io.credits.count) < 0)) {
+- atomic_inc(&sc->send_io.credits.count);
+- goto wait_credit;
+- }
+
+ request = mempool_alloc(sc->send_io.mem.pool, GFP_KERNEL);
+ if (!request) {
--- /dev/null
+From 6858531e5e8d68828eec349989cefce3f45a487f Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:51 +0100
+Subject: smb: client: remove pointless sc->recv_io.credits.count rollback
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 6858531e5e8d68828eec349989cefce3f45a487f upstream.
+
+We either reach this code path before we call
+new_credits = manage_credits_prior_sending(sc),
+which means new_credits is still 0
+or the connection is already broken as
+smbd_post_send() already called
+smbd_disconnect_rdma_connection().
+
+This will also simplify further changes.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -1288,9 +1288,6 @@ err_dma:
+ DMA_TO_DEVICE);
+ mempool_free(request, sc->send_io.mem.pool);
+
+- /* roll back the granted receive credits */
+- atomic_sub(new_credits, &sc->recv_io.credits.count);
+-
+ err_alloc:
+ atomic_inc(&sc->send_io.credits.count);
+ wake_up(&sc->send_io.credits.wait_queue);
--- /dev/null
+From 8bfe3fd33f36b987c8200b112646732b5f5cd8b3 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:52 +0100
+Subject: smb: client: remove pointless sc->send_io.pending handling in smbd_post_send_iter()
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 8bfe3fd33f36b987c8200b112646732b5f5cd8b3 upstream.
+
+If we reach this the connection is already broken as
+smbd_post_send() already called
+smbd_disconnect_rdma_connection().
+
+This will also simplify further changes.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 5 -----
+ 1 file changed, 5 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -1274,11 +1274,6 @@ wait_credit:
+ if (!rc)
+ return 0;
+
+- if (atomic_dec_and_test(&sc->send_io.pending.count))
+- wake_up(&sc->send_io.pending.zero_wait_queue);
+-
+- wake_up(&sc->send_io.pending.dec_wait_queue);
+-
+ err_dma:
+ for (i = 0; i < request->num_sge; i++)
+ if (request->sge[i].addr)
--- /dev/null
+From ec306600d5ba7148c9dbf8f5a8f1f5c1a044a241 Mon Sep 17 00:00:00 2001
+From: Henrique Carvalho <henrique.carvalho@suse.com>
+Date: Tue, 27 Jan 2026 13:01:28 -0300
+Subject: smb: client: split cached_fid bitfields to avoid shared-byte RMW races
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Henrique Carvalho <henrique.carvalho@suse.com>
+
+commit ec306600d5ba7148c9dbf8f5a8f1f5c1a044a241 upstream.
+
+is_open, has_lease and on_list are stored in the same bitfield byte in
+struct cached_fid but are updated in different code paths that may run
+concurrently. Bitfield assignments generate byte read–modify–write
+operations (e.g. `orb $mask, addr` on x86_64), so updating one flag can
+restore stale values of the others.
+
+A possible interleaving is:
+ CPU1: load old byte (has_lease=1, on_list=1)
+ CPU2: clear both flags (store 0)
+ CPU1: RMW store (old | IS_OPEN) -> reintroduces cleared bits
+
+To avoid this class of races, convert these flags to separate bool
+fields.
+
+Cc: stable@vger.kernel.org
+Fixes: ebe98f1447bbc ("cifs: enable caching of directories for which a lease is held")
+Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/cached_dir.h | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/fs/smb/client/cached_dir.h
++++ b/fs/smb/client/cached_dir.h
+@@ -36,10 +36,10 @@ struct cached_fid {
+ struct list_head entry;
+ struct cached_fids *cfids;
+ const char *path;
+- bool has_lease:1;
+- bool is_open:1;
+- bool on_list:1;
+- bool file_all_info_is_valid:1;
++ bool has_lease;
++ bool is_open;
++ bool on_list;
++ bool file_all_info_is_valid;
+ unsigned long time; /* jiffies of when lease was taken */
+ unsigned long last_access_time; /* jiffies of when last accessed */
+ struct kref refcount;
--- /dev/null
+From bf30515caec590316e0d08208e4252eed4c160df Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:54 +0100
+Subject: smb: client: split out smbd_ib_post_send()
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit bf30515caec590316e0d08208e4252eed4c160df upstream.
+
+This is like smb_direct_post_send() in the server
+and will simplify porting the smbdirect_send_batch
+and credit related logic from the server.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 33 +++++++++++++++++----------------
+ 1 file changed, 17 insertions(+), 16 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -1101,11 +1101,26 @@ static int manage_keep_alive_before_send
+ return 0;
+ }
+
++static int smbd_ib_post_send(struct smbdirect_socket *sc,
++ struct ib_send_wr *wr)
++{
++ int ret;
++
++ atomic_inc(&sc->send_io.pending.count);
++ ret = ib_post_send(sc->ib.qp, wr, NULL);
++ if (ret) {
++ pr_err("failed to post send: %d\n", ret);
++ smbd_disconnect_rdma_connection(sc);
++ ret = -EAGAIN;
++ }
++ return ret;
++}
++
+ /* Post the send request */
+ static int smbd_post_send(struct smbdirect_socket *sc,
+ struct smbdirect_send_io *request)
+ {
+- int rc, i;
++ int i;
+
+ for (i = 0; i < request->num_sge; i++) {
+ log_rdma_send(INFO,
+@@ -1126,15 +1141,7 @@ static int smbd_post_send(struct smbdire
+ request->wr.num_sge = request->num_sge;
+ request->wr.opcode = IB_WR_SEND;
+ request->wr.send_flags = IB_SEND_SIGNALED;
+-
+- rc = ib_post_send(sc->ib.qp, &request->wr, NULL);
+- if (rc) {
+- log_rdma_send(ERR, "ib_post_send failed rc=%d\n", rc);
+- smbd_disconnect_rdma_connection(sc);
+- rc = -EAGAIN;
+- }
+-
+- return rc;
++ return smbd_ib_post_send(sc, &request->wr);
+ }
+
+ static int wait_for_credits(struct smbdirect_socket *sc,
+@@ -1280,12 +1287,6 @@ static int smbd_post_send_iter(struct sm
+ le32_to_cpu(packet->data_length),
+ le32_to_cpu(packet->remaining_data_length));
+
+- /*
+- * Now that we got a local and a remote credit
+- * we add us as pending
+- */
+- atomic_inc(&sc->send_io.pending.count);
+-
+ rc = smbd_post_send(sc, request);
+ if (!rc)
+ return 0;
--- /dev/null
+From 2c1ac39ce9cd4112f406775c626eef7f3eb4c481 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:56 +0100
+Subject: smb: client: use smbdirect_send_batch processing
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 2c1ac39ce9cd4112f406775c626eef7f3eb4c481 upstream.
+
+This will allow us to use similar logic as we have in
+the server soon, so that we can share common code later.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/client/smbdirect.c | 149 +++++++++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 135 insertions(+), 14 deletions(-)
+
+--- a/fs/smb/client/smbdirect.c
++++ b/fs/smb/client/smbdirect.c
+@@ -544,11 +544,20 @@ static void send_done(struct ib_cq *cq,
+ struct smbdirect_send_io *request =
+ container_of(wc->wr_cqe, struct smbdirect_send_io, cqe);
+ struct smbdirect_socket *sc = request->socket;
++ struct smbdirect_send_io *sibling, *next;
+ int lcredits = 0;
+
+ log_rdma_send(INFO, "smbdirect_send_io 0x%p completed wc->status=%s\n",
+ request, ib_wc_status_msg(wc->status));
+
++ /*
++ * Free possible siblings and then the main send_io
++ */
++ list_for_each_entry_safe(sibling, next, &request->sibling_list, sibling_list) {
++ list_del_init(&sibling->sibling_list);
++ smbd_free_send_io(sibling);
++ lcredits += 1;
++ }
+ /* Note this frees wc->wr_cqe, but not wc */
+ smbd_free_send_io(request);
+ lcredits += 1;
+@@ -1154,7 +1163,8 @@ static int smbd_ib_post_send(struct smbd
+
+ /* Post the send request */
+ static int smbd_post_send(struct smbdirect_socket *sc,
+- struct smbdirect_send_io *request)
++ struct smbdirect_send_batch *batch,
++ struct smbdirect_send_io *request)
+ {
+ int i;
+
+@@ -1170,16 +1180,95 @@ static int smbd_post_send(struct smbdire
+ }
+
+ request->cqe.done = send_done;
+-
+ request->wr.next = NULL;
+- request->wr.wr_cqe = &request->cqe;
+ request->wr.sg_list = request->sge;
+ request->wr.num_sge = request->num_sge;
+ request->wr.opcode = IB_WR_SEND;
++
++ if (batch) {
++ request->wr.wr_cqe = NULL;
++ request->wr.send_flags = 0;
++ if (!list_empty(&batch->msg_list)) {
++ struct smbdirect_send_io *last;
++
++ last = list_last_entry(&batch->msg_list,
++ struct smbdirect_send_io,
++ sibling_list);
++ last->wr.next = &request->wr;
++ }
++ list_add_tail(&request->sibling_list, &batch->msg_list);
++ batch->wr_cnt++;
++ return 0;
++ }
++
++ request->wr.wr_cqe = &request->cqe;
+ request->wr.send_flags = IB_SEND_SIGNALED;
+ return smbd_ib_post_send(sc, &request->wr);
+ }
+
++static void smbd_send_batch_init(struct smbdirect_send_batch *batch,
++ bool need_invalidate_rkey,
++ unsigned int remote_key)
++{
++ INIT_LIST_HEAD(&batch->msg_list);
++ batch->wr_cnt = 0;
++ batch->need_invalidate_rkey = need_invalidate_rkey;
++ batch->remote_key = remote_key;
++}
++
++static int smbd_send_batch_flush(struct smbdirect_socket *sc,
++ struct smbdirect_send_batch *batch,
++ bool is_last)
++{
++ struct smbdirect_send_io *first, *last;
++ int ret = 0;
++
++ if (list_empty(&batch->msg_list))
++ return 0;
++
++ first = list_first_entry(&batch->msg_list,
++ struct smbdirect_send_io,
++ sibling_list);
++ last = list_last_entry(&batch->msg_list,
++ struct smbdirect_send_io,
++ sibling_list);
++
++ if (batch->need_invalidate_rkey) {
++ first->wr.opcode = IB_WR_SEND_WITH_INV;
++ first->wr.ex.invalidate_rkey = batch->remote_key;
++ batch->need_invalidate_rkey = false;
++ batch->remote_key = 0;
++ }
++
++ last->wr.send_flags = IB_SEND_SIGNALED;
++ last->wr.wr_cqe = &last->cqe;
++
++ /*
++ * Remove last from batch->msg_list
++ * and splice the rest of batch->msg_list
++ * to last->sibling_list.
++ *
++ * batch->msg_list is a valid empty list
++ * at the end.
++ */
++ list_del_init(&last->sibling_list);
++ list_splice_tail_init(&batch->msg_list, &last->sibling_list);
++ batch->wr_cnt = 0;
++
++ ret = smbd_ib_post_send(sc, &first->wr);
++ if (ret) {
++ struct smbdirect_send_io *sibling, *next;
++
++ list_for_each_entry_safe(sibling, next, &last->sibling_list, sibling_list) {
++ list_del_init(&sibling->sibling_list);
++ smbd_free_send_io(sibling);
++ }
++ smbd_free_send_io(last);
++ }
++
++ return ret;
++}
++
+ static int wait_for_credits(struct smbdirect_socket *sc,
+ wait_queue_head_t *waitq, atomic_t *total_credits,
+ int needed)
+@@ -1202,16 +1291,35 @@ static int wait_for_credits(struct smbdi
+ } while (true);
+ }
+
+-static int wait_for_send_lcredit(struct smbdirect_socket *sc)
++static int wait_for_send_lcredit(struct smbdirect_socket *sc,
++ struct smbdirect_send_batch *batch)
+ {
++ if (batch && (atomic_read(&sc->send_io.lcredits.count) <= 1)) {
++ int ret;
++
++ ret = smbd_send_batch_flush(sc, batch, false);
++ if (ret)
++ return ret;
++ }
++
+ return wait_for_credits(sc,
+ &sc->send_io.lcredits.wait_queue,
+ &sc->send_io.lcredits.count,
+ 1);
+ }
+
+-static int wait_for_send_credits(struct smbdirect_socket *sc)
++static int wait_for_send_credits(struct smbdirect_socket *sc,
++ struct smbdirect_send_batch *batch)
+ {
++ if (batch &&
++ (batch->wr_cnt >= 16 || atomic_read(&sc->send_io.credits.count) <= 1)) {
++ int ret;
++
++ ret = smbd_send_batch_flush(sc, batch, false);
++ if (ret)
++ return ret;
++ }
++
+ return wait_for_credits(sc,
+ &sc->send_io.credits.wait_queue,
+ &sc->send_io.credits.count,
+@@ -1219,6 +1327,7 @@ static int wait_for_send_credits(struct
+ }
+
+ static int smbd_post_send_iter(struct smbdirect_socket *sc,
++ struct smbdirect_send_batch *batch,
+ struct iov_iter *iter,
+ int *_remaining_data_length)
+ {
+@@ -1230,14 +1339,14 @@ static int smbd_post_send_iter(struct sm
+ struct smbdirect_data_transfer *packet;
+ int new_credits = 0;
+
+- rc = wait_for_send_lcredit(sc);
++ rc = wait_for_send_lcredit(sc, batch);
+ if (rc) {
+ log_outgoing(ERR, "disconnected not sending on wait_lcredit\n");
+ rc = -EAGAIN;
+ goto err_wait_lcredit;
+ }
+
+- rc = wait_for_send_credits(sc);
++ rc = wait_for_send_credits(sc, batch);
+ if (rc) {
+ log_outgoing(ERR, "disconnected not sending on wait_credit\n");
+ rc = -EAGAIN;
+@@ -1322,7 +1431,7 @@ static int smbd_post_send_iter(struct sm
+ le32_to_cpu(packet->data_length),
+ le32_to_cpu(packet->remaining_data_length));
+
+- rc = smbd_post_send(sc, request);
++ rc = smbd_post_send(sc, batch, request);
+ if (!rc)
+ return 0;
+
+@@ -1351,10 +1460,11 @@ static int smbd_post_send_empty(struct s
+ int remaining_data_length = 0;
+
+ sc->statistics.send_empty++;
+- return smbd_post_send_iter(sc, NULL, &remaining_data_length);
++ return smbd_post_send_iter(sc, NULL, NULL, &remaining_data_length);
+ }
+
+ static int smbd_post_send_full_iter(struct smbdirect_socket *sc,
++ struct smbdirect_send_batch *batch,
+ struct iov_iter *iter,
+ int *_remaining_data_length)
+ {
+@@ -1367,7 +1477,7 @@ static int smbd_post_send_full_iter(stru
+ */
+
+ while (iov_iter_count(iter) > 0) {
+- rc = smbd_post_send_iter(sc, iter, _remaining_data_length);
++ rc = smbd_post_send_iter(sc, batch, iter, _remaining_data_length);
+ if (rc < 0)
+ break;
+ }
+@@ -2289,8 +2399,10 @@ int smbd_send(struct TCP_Server_Info *se
+ struct smbdirect_socket_parameters *sp = &sc->parameters;
+ struct smb_rqst *rqst;
+ struct iov_iter iter;
++ struct smbdirect_send_batch batch;
+ unsigned int remaining_data_length, klen;
+ int rc, i, rqst_idx;
++ int error = 0;
+
+ if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
+ return -EAGAIN;
+@@ -2315,6 +2427,7 @@ int smbd_send(struct TCP_Server_Info *se
+ num_rqst, remaining_data_length);
+
+ rqst_idx = 0;
++ smbd_send_batch_init(&batch, false, 0);
+ do {
+ rqst = &rqst_array[rqst_idx];
+
+@@ -2333,20 +2446,28 @@ int smbd_send(struct TCP_Server_Info *se
+ klen += rqst->rq_iov[i].iov_len;
+ iov_iter_kvec(&iter, ITER_SOURCE, rqst->rq_iov, rqst->rq_nvec, klen);
+
+- rc = smbd_post_send_full_iter(sc, &iter, &remaining_data_length);
+- if (rc < 0)
++ rc = smbd_post_send_full_iter(sc, &batch, &iter, &remaining_data_length);
++ if (rc < 0) {
++ error = rc;
+ break;
++ }
+
+ if (iov_iter_count(&rqst->rq_iter) > 0) {
+ /* And then the data pages if there are any */
+- rc = smbd_post_send_full_iter(sc, &rqst->rq_iter,
++ rc = smbd_post_send_full_iter(sc, &batch, &rqst->rq_iter,
+ &remaining_data_length);
+- if (rc < 0)
++ if (rc < 0) {
++ error = rc;
+ break;
++ }
+ }
+
+ } while (++rqst_idx < num_rqst);
+
++ rc = smbd_send_batch_flush(sc, &batch, true);
++ if (unlikely(!rc && error))
++ rc = error;
++
+ /*
+ * As an optimization, we don't wait for individual I/O to finish
+ * before sending the next one.
--- /dev/null
+From 8cf2bbac6281434065f5f3aeab19c9c08ff755a2 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:46 +0100
+Subject: smb: server: fix last send credit problem causing disconnects
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 8cf2bbac6281434065f5f3aeab19c9c08ff755a2 upstream.
+
+When we are about to use the last send credit that was
+granted to us by the peer, we need to wait until
+we are ourself able to grant at least one credit
+to the peer. Otherwise it might not be possible
+for the peer to grant more credits.
+
+The following sections in MS-SMBD are related to this:
+
+3.1.5.1 Sending Upper Layer Messages
+...
+If Connection.SendCredits is 1 and the CreditsGranted field of the
+message is 0, stop processing.
+...
+
+3.1.5.9 Managing Credits Prior to Sending
+...
+If Connection.ReceiveCredits is zero, or if Connection.SendCredits is
+one and the Connection.SendQueue is not empty, the sender MUST allocate
+and post at least one receive of size Connection.MaxReceiveSize and MUST
+increment Connection.ReceiveCredits by the number allocated and posted.
+If no receives are posted, the processing MUST return a value of zero to
+indicate to the caller that no Send message can be currently performed.
+...
+
+This problem was found by running this on Windows 2025
+against ksmbd with required smb signing:
+'frametest.exe -r 4k -t 20 -n 2000' after
+'frametest.exe -w 4k -t 20 -n 2000'.
+
+Link: https://lore.kernel.org/linux-cifs/b58fa352-2386-4145-b42e-9b4b1d484e17@samba.org/
+Cc: <stable@vger.kernel.org> # 6.18.x
+Cc: Namjae Jeon <linkinjeon@kernel.org>
+Cc: Steve French <smfrench@gmail.com>
+Cc: Tom Talpey <tom@talpey.com>
+Cc: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/transport_rdma.c | 32 ++++++++++++++++++++++++++++++--
+ 1 file changed, 30 insertions(+), 2 deletions(-)
+
+--- a/fs/smb/server/transport_rdma.c
++++ b/fs/smb/server/transport_rdma.c
+@@ -1033,6 +1033,15 @@ static void smb_direct_post_recv_credits
+
+ atomic_add(credits, &sc->recv_io.credits.available);
+
++ /*
++ * If the last send credit is waiting for credits
++ * it can grant we need to wake it up
++ */
++ if (credits &&
++ atomic_read(&sc->send_io.bcredits.count) == 0 &&
++ atomic_read(&sc->send_io.credits.count) == 0)
++ wake_up(&sc->send_io.credits.wait_queue);
++
+ if (credits)
+ queue_work(sc->workqueue, &sc->idle.immediate_work);
+ }
+@@ -1306,6 +1315,7 @@ static int calc_rw_credits(struct smbdir
+
+ static int smb_direct_create_header(struct smbdirect_socket *sc,
+ int size, int remaining_data_length,
++ int new_credits,
+ struct smbdirect_send_io **sendmsg_out)
+ {
+ struct smbdirect_socket_parameters *sp = &sc->parameters;
+@@ -1321,7 +1331,7 @@ static int smb_direct_create_header(stru
+ /* Fill in the packet header */
+ packet = (struct smbdirect_data_transfer *)sendmsg->packet;
+ packet->credits_requested = cpu_to_le16(sp->send_credit_target);
+- packet->credits_granted = cpu_to_le16(manage_credits_prior_sending(sc));
++ packet->credits_granted = cpu_to_le16(new_credits);
+
+ packet->flags = 0;
+ if (manage_keep_alive_before_sending(sc))
+@@ -1459,6 +1469,7 @@ static int smb_direct_post_send_data(str
+ int data_length;
+ struct scatterlist sg[SMBDIRECT_SEND_IO_MAX_SGE - 1];
+ struct smbdirect_send_batch _send_ctx;
++ int new_credits;
+
+ if (!send_ctx) {
+ smb_direct_send_ctx_init(&_send_ctx, false, 0);
+@@ -1477,12 +1488,29 @@ static int smb_direct_post_send_data(str
+ if (ret)
+ goto credit_failed;
+
++ new_credits = manage_credits_prior_sending(sc);
++ if (new_credits == 0 &&
++ atomic_read(&sc->send_io.credits.count) == 0 &&
++ atomic_read(&sc->recv_io.credits.count) == 0) {
++ queue_work(sc->workqueue, &sc->recv_io.posted.refill_work);
++ 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);
++ if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
++ ret = -ENOTCONN;
++ if (ret < 0)
++ goto credit_failed;
++
++ new_credits = manage_credits_prior_sending(sc);
++ }
++
+ data_length = 0;
+ for (i = 0; i < niov; i++)
+ data_length += iov[i].iov_len;
+
+ ret = smb_direct_create_header(sc, data_length, remaining_data_length,
+- &msg);
++ new_credits, &msg);
+ if (ret)
+ goto header_failed;
+
--- /dev/null
+From 77ffbcac4e569566d0092d5f22627dfc0896b553 Mon Sep 17 00:00:00 2001
+From: Henrique Carvalho <henrique.carvalho@suse.com>
+Date: Wed, 4 Feb 2026 20:06:43 -0300
+Subject: smb: server: fix leak of active_num_conn in ksmbd_tcp_new_connection()
+
+From: Henrique Carvalho <henrique.carvalho@suse.com>
+
+commit 77ffbcac4e569566d0092d5f22627dfc0896b553 upstream.
+
+On kthread_run() failure in ksmbd_tcp_new_connection(), the transport is
+freed via free_transport(), which does not decrement active_num_conn,
+leaking this counter.
+
+Replace free_transport() with ksmbd_tcp_disconnect().
+
+Fixes: 0d0d4680db22e ("ksmbd: add max connections parameter")
+Cc: stable@vger.kernel.org
+Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/transport_tcp.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/fs/smb/server/transport_tcp.c
++++ b/fs/smb/server/transport_tcp.c
+@@ -40,6 +40,7 @@ static const struct ksmbd_transport_ops
+
+ static void tcp_stop_kthread(struct task_struct *kthread);
+ static struct interface *alloc_iface(char *ifname);
++static void ksmbd_tcp_disconnect(struct ksmbd_transport *t);
+
+ #define KSMBD_TRANS(t) (&(t)->transport)
+ #define TCP_TRANS(t) ((struct tcp_transport *)container_of(t, \
+@@ -202,7 +203,7 @@ static int ksmbd_tcp_new_connection(stru
+ if (IS_ERR(handler)) {
+ pr_err("cannot start conn thread\n");
+ rc = PTR_ERR(handler);
+- free_transport(t);
++ ksmbd_tcp_disconnect(KSMBD_TRANS(t));
+ }
+ return rc;
+ }
--- /dev/null
+From 8106978d400cc88a99fb94927afe8fec7391ca3e Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:44 +0100
+Subject: smb: server: let recv_done() queue a refill when the peer is low on credits
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 8106978d400cc88a99fb94927afe8fec7391ca3e upstream.
+
+In captures I saw that Windows was granting 191 credits in a batch
+when its peer posted a lot of messages. We are asking for a
+credit target of 255 and 191 is 252*3/4.
+
+So we also use that logic in order to fill the
+recv buffers available to the peer.
+
+Fixes: a7eef6144c97 ("smb: server: queue post_recv_credits_work in put_recvmsg() and avoid count_avail_recvmsg")
+Cc: <stable@vger.kernel.org> # 6.18.x
+Cc: Namjae Jeon <linkinjeon@kernel.org>
+Cc: Steve French <smfrench@gmail.com>
+Cc: Tom Talpey <tom@talpey.com>
+Cc: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/transport_rdma.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/fs/smb/server/transport_rdma.c
++++ b/fs/smb/server/transport_rdma.c
+@@ -644,6 +644,7 @@ static void recv_done(struct ib_cq *cq,
+ struct smbdirect_data_transfer *data_transfer =
+ (struct smbdirect_data_transfer *)recvmsg->packet;
+ u32 remaining_data_length, data_offset, data_length;
++ int current_recv_credits;
+ u16 old_recv_credit_target;
+
+ if (wc->byte_len <
+@@ -682,7 +683,7 @@ static void recv_done(struct ib_cq *cq,
+ }
+
+ atomic_dec(&sc->recv_io.posted.count);
+- atomic_dec(&sc->recv_io.credits.count);
++ current_recv_credits = atomic_dec_return(&sc->recv_io.credits.count);
+
+ old_recv_credit_target = sc->recv_io.credits.target;
+ sc->recv_io.credits.target =
+@@ -702,7 +703,8 @@ static void recv_done(struct ib_cq *cq,
+ wake_up(&sc->send_io.credits.wait_queue);
+
+ if (data_length) {
+- if (sc->recv_io.credits.target > old_recv_credit_target)
++ if (current_recv_credits <= (sc->recv_io.credits.target / 4) ||
++ sc->recv_io.credits.target > old_recv_credit_target)
+ queue_work(sc->workqueue, &sc->recv_io.posted.refill_work);
+
+ enqueue_reassembly(sc, recvmsg, (int)data_length);
--- /dev/null
+From 9da82dc73cb03e85d716a2609364572367a5ff47 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:47 +0100
+Subject: smb: server: let send_done handle a completion without IB_SEND_SIGNALED
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 9da82dc73cb03e85d716a2609364572367a5ff47 upstream.
+
+With smbdirect_send_batch processing we likely have requests without
+IB_SEND_SIGNALED, which will be destroyed in the final request
+that has IB_SEND_SIGNALED set.
+
+If the connection is broken all requests are signaled
+even without explicit IB_SEND_SIGNALED.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+Cc: Namjae Jeon <linkinjeon@kernel.org>
+Cc: Steve French <smfrench@gmail.com>
+Cc: Tom Talpey <tom@talpey.com>
+Cc: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/transport_rdma.c | 26 ++++++++++++++++++++++++++
+ 1 file changed, 26 insertions(+)
+
+--- a/fs/smb/server/transport_rdma.c
++++ b/fs/smb/server/transport_rdma.c
+@@ -1059,6 +1059,31 @@ static void send_done(struct ib_cq *cq,
+ ib_wc_status_msg(wc->status), wc->status,
+ wc->opcode);
+
++ if (unlikely(!(sendmsg->wr.send_flags & IB_SEND_SIGNALED))) {
++ /*
++ * This happens when smbdirect_send_io is a sibling
++ * before the final message, it is signaled on
++ * error anyway, so we need to skip
++ * smbdirect_connection_free_send_io here,
++ * otherwise is will destroy the memory
++ * of the siblings too, which will cause
++ * use after free problems for the others
++ * triggered from ib_drain_qp().
++ */
++ if (wc->status != IB_WC_SUCCESS)
++ goto skip_free;
++
++ /*
++ * This should not happen!
++ * But we better just close the
++ * connection...
++ */
++ pr_err("unexpected send completion wc->status=%s (%d) wc->opcode=%d\n",
++ ib_wc_status_msg(wc->status), wc->status, wc->opcode);
++ smb_direct_disconnect_rdma_connection(sc);
++ return;
++ }
++
+ /*
+ * Free possible siblings and then the main send_io
+ */
+@@ -1072,6 +1097,7 @@ static void send_done(struct ib_cq *cq,
+ lcredits += 1;
+
+ if (wc->status != IB_WC_SUCCESS || wc->opcode != IB_WC_SEND) {
++skip_free:
+ pr_err("Send error. status='%s (%d)', opcode=%d\n",
+ ib_wc_status_msg(wc->status), wc->status,
+ wc->opcode);
--- /dev/null
+From 26ad87a2cfb8c1384620d1693a166ed87303046e Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:43 +0100
+Subject: smb: server: make use of smbdirect_socket.recv_io.credits.available
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 26ad87a2cfb8c1384620d1693a166ed87303046e upstream.
+
+The logic off managing recv credits by counting posted recv_io and
+granted credits is racy.
+
+That's because the peer might already consumed a credit,
+but between receiving the incoming recv at the hardware
+and processing the completion in the 'recv_done' functions
+we likely have a window where we grant credits, which
+don't really exist.
+
+So we better have a decicated counter for the
+available credits, which will be incremented
+when we posted new recv buffers and drained when
+we grant the credits to the peer.
+
+This fixes regression Namjae reported with
+the 6.18 release.
+
+Fixes: 89b021a72663 ("smb: server: manage recv credits by counting posted recv_io and granted credits")
+Cc: <stable@vger.kernel.org> # 6.18.x
+Cc: Namjae Jeon <linkinjeon@kernel.org>
+Cc: Steve French <smfrench@gmail.com>
+Cc: Tom Talpey <tom@talpey.com>
+Cc: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/transport_rdma.c | 30 +++++++++++++++++++++++++-----
+ 1 file changed, 25 insertions(+), 5 deletions(-)
+
+--- a/fs/smb/server/transport_rdma.c
++++ b/fs/smb/server/transport_rdma.c
+@@ -1028,6 +1028,8 @@ static void smb_direct_post_recv_credits
+ }
+ }
+
++ atomic_add(credits, &sc->recv_io.credits.available);
++
+ if (credits)
+ queue_work(sc->workqueue, &sc->idle.immediate_work);
+ }
+@@ -1074,19 +1076,37 @@ static void send_done(struct ib_cq *cq,
+
+ static int manage_credits_prior_sending(struct smbdirect_socket *sc)
+ {
++ int missing;
++ int available;
+ int new_credits;
+
+ if (atomic_read(&sc->recv_io.credits.count) >= sc->recv_io.credits.target)
+ return 0;
+
+- new_credits = atomic_read(&sc->recv_io.posted.count);
+- if (new_credits == 0)
++ missing = (int)sc->recv_io.credits.target - atomic_read(&sc->recv_io.credits.count);
++ available = atomic_xchg(&sc->recv_io.credits.available, 0);
++ new_credits = (u16)min3(U16_MAX, missing, available);
++ if (new_credits <= 0) {
++ /*
++ * If credits are available, but not granted
++ * we need to re-add them again.
++ */
++ if (available)
++ atomic_add(available, &sc->recv_io.credits.available);
+ return 0;
++ }
+
+- new_credits -= atomic_read(&sc->recv_io.credits.count);
+- if (new_credits <= 0)
+- return 0;
++ if (new_credits < available) {
++ /*
++ * Readd the remaining available again.
++ */
++ available -= new_credits;
++ atomic_add(available, &sc->recv_io.credits.available);
++ }
+
++ /*
++ * Remember we granted the credits
++ */
+ atomic_add(new_credits, &sc->recv_io.credits.count);
+ return new_credits;
+ }
--- /dev/null
+From 34abd408c8ba24d7c97bd02ba874d8c714f49db1 Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:45 +0100
+Subject: smb: server: make use of smbdirect_socket.send_io.bcredits
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 34abd408c8ba24d7c97bd02ba874d8c714f49db1 upstream.
+
+It turns out that our code will corrupt the stream of
+reassabled data transfer messages when we trigger an
+immendiate (empty) send.
+
+In order to fix this we'll have a single 'batch' credit per
+connection. And code getting that credit is free to use
+as much messages until remaining_length reaches 0, then
+the batch credit it given back and the next logical send can
+happen.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+Cc: Namjae Jeon <linkinjeon@kernel.org>
+Cc: Steve French <smfrench@gmail.com>
+Cc: Tom Talpey <tom@talpey.com>
+Cc: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/server/transport_rdma.c | 53 +++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 51 insertions(+), 2 deletions(-)
+
+--- a/fs/smb/server/transport_rdma.c
++++ b/fs/smb/server/transport_rdma.c
+@@ -221,6 +221,7 @@ static void smb_direct_disconnect_wake_u
+ * in order to notice the broken connection.
+ */
+ wake_up_all(&sc->status_wait);
++ wake_up_all(&sc->send_io.bcredits.wait_queue);
+ wake_up_all(&sc->send_io.lcredits.wait_queue);
+ wake_up_all(&sc->send_io.credits.wait_queue);
+ wake_up_all(&sc->send_io.pending.zero_wait_queue);
+@@ -1152,6 +1153,7 @@ static void smb_direct_send_ctx_init(str
+ send_ctx->wr_cnt = 0;
+ send_ctx->need_invalidate_rkey = need_invalidate_rkey;
+ send_ctx->remote_key = remote_key;
++ send_ctx->credit = 0;
+ }
+
+ static int smb_direct_flush_send_list(struct smbdirect_socket *sc,
+@@ -1159,10 +1161,10 @@ static int smb_direct_flush_send_list(st
+ bool is_last)
+ {
+ struct smbdirect_send_io *first, *last;
+- int ret;
++ int ret = 0;
+
+ if (list_empty(&send_ctx->msg_list))
+- return 0;
++ goto release_credit;
+
+ first = list_first_entry(&send_ctx->msg_list,
+ struct smbdirect_send_io,
+@@ -1204,6 +1206,13 @@ static int smb_direct_flush_send_list(st
+ smb_direct_free_sendmsg(sc, last);
+ }
+
++release_credit:
++ if (is_last && !ret && send_ctx->credit) {
++ atomic_add(send_ctx->credit, &sc->send_io.bcredits.count);
++ send_ctx->credit = 0;
++ wake_up(&sc->send_io.bcredits.wait_queue);
++ }
++
+ return ret;
+ }
+
+@@ -1229,6 +1238,25 @@ static int wait_for_credits(struct smbdi
+ } while (true);
+ }
+
++static int wait_for_send_bcredit(struct smbdirect_socket *sc,
++ struct smbdirect_send_batch *send_ctx)
++{
++ int ret;
++
++ if (send_ctx->credit)
++ return 0;
++
++ ret = wait_for_credits(sc,
++ &sc->send_io.bcredits.wait_queue,
++ &sc->send_io.bcredits.count,
++ 1);
++ if (ret)
++ return ret;
++
++ send_ctx->credit = 1;
++ return 0;
++}
++
+ static int wait_for_send_lcredit(struct smbdirect_socket *sc,
+ struct smbdirect_send_batch *send_ctx)
+ {
+@@ -1430,6 +1458,16 @@ static int smb_direct_post_send_data(str
+ struct smbdirect_send_io *msg;
+ int data_length;
+ struct scatterlist sg[SMBDIRECT_SEND_IO_MAX_SGE - 1];
++ struct smbdirect_send_batch _send_ctx;
++
++ if (!send_ctx) {
++ smb_direct_send_ctx_init(&_send_ctx, false, 0);
++ send_ctx = &_send_ctx;
++ }
++
++ ret = wait_for_send_bcredit(sc, send_ctx);
++ if (ret)
++ goto bcredit_failed;
+
+ ret = wait_for_send_lcredit(sc, send_ctx);
+ if (ret)
+@@ -1482,6 +1520,13 @@ static int smb_direct_post_send_data(str
+ ret = post_sendmsg(sc, send_ctx, msg);
+ if (ret)
+ goto err;
++
++ if (send_ctx == &_send_ctx) {
++ ret = smb_direct_flush_send_list(sc, send_ctx, true);
++ if (ret)
++ goto err;
++ }
++
+ return 0;
+ err:
+ smb_direct_free_sendmsg(sc, msg);
+@@ -1490,6 +1535,9 @@ header_failed:
+ credit_failed:
+ atomic_inc(&sc->send_io.lcredits.count);
+ lcredit_failed:
++ atomic_add(send_ctx->credit, &sc->send_io.bcredits.count);
++ send_ctx->credit = 0;
++bcredit_failed:
+ return ret;
+ }
+
+@@ -1961,6 +2009,7 @@ static int smb_direct_send_negotiate_res
+ resp->max_fragmented_size =
+ cpu_to_le32(sp->max_fragmented_recv_size);
+
++ atomic_set(&sc->send_io.bcredits.count, 1);
+ sc->recv_io.expected = SMBDIRECT_EXPECT_DATA_TRANSFER;
+ sc->status = SMBDIRECT_SOCKET_CONNECTED;
+ }
--- /dev/null
+From 6e3c5052f9686192e178806e017b7377155f4bab Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:41 +0100
+Subject: smb: smbdirect: introduce smbdirect_socket.recv_io.credits.available
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 6e3c5052f9686192e178806e017b7377155f4bab upstream.
+
+The logic off managing recv credits by counting posted recv_io and
+granted credits is racy.
+
+That's because the peer might already consumed a credit,
+but between receiving the incoming recv at the hardware
+and processing the completion in the 'recv_done' functions
+we likely have a window where we grant credits, which
+don't really exist.
+
+So we better have a decicated counter for the
+available credits, which will be incremented
+when we posted new recv buffers and drained when
+we grant the credits to the peer.
+
+Fixes: 5fb9b459b368 ("smb: client: count the number of posted recv_io messages in order to calculated credits")
+Fixes: 89b021a72663 ("smb: server: manage recv credits by counting posted recv_io and granted credits")
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/common/smbdirect/smbdirect_socket.h | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/fs/smb/common/smbdirect/smbdirect_socket.h
++++ b/fs/smb/common/smbdirect/smbdirect_socket.h
+@@ -239,6 +239,7 @@ struct smbdirect_socket {
+ */
+ struct {
+ u16 target;
++ atomic_t available;
+ atomic_t count;
+ } credits;
+
+@@ -387,6 +388,7 @@ static __always_inline void smbdirect_so
+ INIT_WORK(&sc->recv_io.posted.refill_work, __smbdirect_socket_disabled_work);
+ disable_work_sync(&sc->recv_io.posted.refill_work);
+
++ atomic_set(&sc->recv_io.credits.available, 0);
+ atomic_set(&sc->recv_io.credits.count, 0);
+
+ INIT_LIST_HEAD(&sc->recv_io.reassembly.list);
--- /dev/null
+From 8e94268b21c8235d430ce1aa6dc0b15952744b9b Mon Sep 17 00:00:00 2001
+From: Stefan Metzmacher <metze@samba.org>
+Date: Thu, 22 Jan 2026 18:16:42 +0100
+Subject: smb: smbdirect: introduce smbdirect_socket.send_io.bcredits.*
+
+From: Stefan Metzmacher <metze@samba.org>
+
+commit 8e94268b21c8235d430ce1aa6dc0b15952744b9b upstream.
+
+It turns out that our code will corrupt the stream of
+reassabled data transfer messages when we trigger an
+immendiate (empty) send.
+
+In order to fix this we'll have a single 'batch' credit per
+connection. And code getting that credit is free to use
+as much messages until remaining_length reaches 0, then
+the batch credit it given back and the next logical send can
+happen.
+
+Cc: <stable@vger.kernel.org> # 6.18.x
+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: linux-cifs@vger.kernel.org
+Cc: samba-technical@lists.samba.org
+Signed-off-by: Stefan Metzmacher <metze@samba.org>
+Acked-by: Namjae Jeon <linkinjeon@kernel.org>
+Signed-off-by: Steve French <stfrench@microsoft.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/smb/common/smbdirect/smbdirect_socket.h | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+--- a/fs/smb/common/smbdirect/smbdirect_socket.h
++++ b/fs/smb/common/smbdirect/smbdirect_socket.h
+@@ -163,6 +163,17 @@ struct smbdirect_socket {
+ } mem;
+
+ /*
++ * This is a coordination for smbdirect_send_batch.
++ *
++ * There's only one possible credit, which means
++ * only one instance is running at a time.
++ */
++ struct {
++ atomic_t count;
++ wait_queue_head_t wait_queue;
++ } bcredits;
++
++ /*
+ * The local credit state for ib_post_send()
+ */
+ struct {
+@@ -371,6 +382,9 @@ static __always_inline void smbdirect_so
+ INIT_DELAYED_WORK(&sc->idle.timer_work, __smbdirect_socket_disabled_work);
+ disable_delayed_work_sync(&sc->idle.timer_work);
+
++ atomic_set(&sc->send_io.bcredits.count, 0);
++ init_waitqueue_head(&sc->send_io.bcredits.wait_queue);
++
+ atomic_set(&sc->send_io.lcredits.count, 0);
+ init_waitqueue_head(&sc->send_io.lcredits.wait_queue);
+
+@@ -485,6 +499,8 @@ struct smbdirect_send_batch {
+ */
+ bool need_invalidate_rkey;
+ u32 remote_key;
++
++ int credit;
+ };
+
+ struct smbdirect_recv_io {