summaryrefslogtreecommitdiffstats
path: root/fs/smb/smbdirect/socket.c
diff options
context:
space:
mode:
authorJinke Han <jinkehan@didiglobal.com>2026-09-08 15:37:42 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-17 09:48:42 +0200
commita5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch)
tree73fef824f9a846cf935869d0dbc5070ef6e2f741 /fs/smb/smbdirect/socket.c
downloadlinux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz
linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Jinke Han <jinkehan@didiglobal.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Yafang Shao <laoar.shao@gmail.com> Acked-by: Borislav Petkov <bp@alien8.de> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'fs/smb/smbdirect/socket.c')
-rw-r--r--fs/smb/smbdirect/socket.c791
1 files changed, 791 insertions, 0 deletions
diff --git a/fs/smb/smbdirect/socket.c b/fs/smb/smbdirect/socket.c
new file mode 100644
index 000000000..bb02df615
--- /dev/null
+++ b/fs/smb/smbdirect/socket.c
@@ -0,0 +1,791 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2017, Microsoft Corporation.
+ * Copyright (c) 2025, Stefan Metzmacher
+ */
+
+#include "internal.h"
+
+bool smbdirect_frwr_is_supported(const struct ib_device_attr *attrs)
+{
+ /*
+ * Test if FRWR (Fast Registration Work Requests) is supported on the
+ * device This implementation requires FRWR on RDMA read/write return
+ * value: true if it is supported
+ */
+
+ if (!(attrs->device_cap_flags & IB_DEVICE_MEM_MGT_EXTENSIONS))
+ return false;
+ if (attrs->max_fast_reg_page_list_len == 0)
+ return false;
+ return true;
+}
+EXPORT_SYMBOL_GPL(smbdirect_frwr_is_supported);
+
+static void smbdirect_socket_cleanup_work(struct work_struct *work);
+
+static int smbdirect_socket_rdma_event_handler(struct rdma_cm_id *id,
+ struct rdma_cm_event *event)
+{
+ struct smbdirect_socket *sc = id->context;
+ int ret = -ESTALE;
+
+ /*
+ * This should be replaced before any real work
+ * starts! So it should never be called!
+ */
+
+ if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL)
+ ret = -ENETDOWN;
+ if (IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(event->status)))
+ ret = event->status;
+ pr_err("%s (first_error=%1pe, expected=%s) => event=%s status=%d => ret=%1pe\n",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
+ rdma_event_msg(sc->rdma.expected_event),
+ rdma_event_msg(event->event),
+ event->status,
+ SMBDIRECT_DEBUG_ERR_PTR(ret));
+ WARN_ONCE(1, "%s should not be called!\n", __func__);
+ sc->rdma.cm_id = NULL;
+ return -ESTALE;
+}
+
+int smbdirect_socket_init_new(struct net *net, struct smbdirect_socket *sc)
+{
+ struct rdma_cm_id *id;
+ int ret;
+
+ smbdirect_socket_init(sc);
+
+ id = rdma_create_id(net,
+ smbdirect_socket_rdma_event_handler,
+ sc,
+ RDMA_PS_TCP,
+ IB_QPT_RC);
+ if (IS_ERR(id)) {
+ pr_err("%s: rdma_create_id() failed %1pe\n", __func__, id);
+ return PTR_ERR(id);
+ }
+
+ ret = rdma_set_afonly(id, 1);
+ if (ret) {
+ rdma_destroy_id(id);
+ pr_err("%s: rdma_set_afonly() failed %1pe\n",
+ __func__, SMBDIRECT_DEBUG_ERR_PTR(ret));
+ return ret;
+ }
+
+ sc->rdma.cm_id = id;
+
+ INIT_WORK(&sc->disconnect_work, smbdirect_socket_cleanup_work);
+
+ return 0;
+}
+
+int smbdirect_socket_create_kern(struct net *net, struct smbdirect_socket **_sc)
+{
+ struct smbdirect_socket *sc;
+ int ret;
+
+ ret = -ENOMEM;
+ sc = kzalloc_obj(*sc);
+ if (!sc)
+ goto alloc_failed;
+
+ ret = smbdirect_socket_init_new(net, sc);
+ if (ret)
+ goto init_failed;
+
+ kref_init(&sc->refs.destroy);
+
+ *_sc = sc;
+ return 0;
+
+init_failed:
+ kfree(sc);
+alloc_failed:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_create_kern);
+
+int smbdirect_socket_init_accepting(struct rdma_cm_id *id, struct smbdirect_socket *sc)
+{
+ smbdirect_socket_init(sc);
+
+ sc->rdma.cm_id = id;
+ sc->rdma.cm_id->context = sc;
+ sc->rdma.cm_id->event_handler = smbdirect_socket_rdma_event_handler;
+
+ sc->ib.dev = sc->rdma.cm_id->device;
+
+ INIT_WORK(&sc->disconnect_work, smbdirect_socket_cleanup_work);
+
+ return 0;
+}
+
+int smbdirect_socket_create_accepting(struct rdma_cm_id *id, struct smbdirect_socket **_sc)
+{
+ struct smbdirect_socket *sc;
+ int ret;
+
+ ret = -ENOMEM;
+ sc = kzalloc_obj(*sc);
+ if (!sc)
+ goto alloc_failed;
+
+ ret = smbdirect_socket_init_accepting(id, sc);
+ if (ret)
+ goto init_failed;
+
+ kref_init(&sc->refs.destroy);
+
+ *_sc = sc;
+ return 0;
+
+init_failed:
+ kfree(sc);
+alloc_failed:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_create_accepting);
+
+int smbdirect_socket_set_initial_parameters(struct smbdirect_socket *sc,
+ const struct smbdirect_socket_parameters *sp)
+{
+ /*
+ * This is only allowed before connect or accept
+ */
+ WARN_ONCE(sc->status != SMBDIRECT_SOCKET_CREATED,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+ if (sc->status != SMBDIRECT_SOCKET_CREATED)
+ return -EINVAL;
+
+ if (sp->flags & ~SMBDIRECT_FLAG_PORT_RANGE_MASK)
+ return -EINVAL;
+
+ if (sp->initiator_depth > U8_MAX)
+ return -EINVAL;
+ if (sp->responder_resources > U8_MAX)
+ return -EINVAL;
+
+ if (sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB &&
+ sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW)
+ return -EINVAL;
+ else if (sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB)
+ rdma_restrict_node_type(sc->rdma.cm_id, RDMA_NODE_IB_CA);
+ else if (sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW)
+ rdma_restrict_node_type(sc->rdma.cm_id, RDMA_NODE_RNIC);
+
+ /*
+ * Make a copy of the callers parameters
+ * from here we only work on the copy
+ *
+ * TODO: do we want consistency checking?
+ */
+ sc->parameters = *sp;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_set_initial_parameters);
+
+const struct smbdirect_socket_parameters *
+smbdirect_socket_get_current_parameters(struct smbdirect_socket *sc)
+{
+ return &sc->parameters;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_get_current_parameters);
+
+int smbdirect_socket_set_kernel_settings(struct smbdirect_socket *sc,
+ enum ib_poll_context poll_ctx,
+ gfp_t gfp_mask)
+{
+ /*
+ * This is only allowed before connect or accept
+ */
+ WARN_ONCE(sc->status != SMBDIRECT_SOCKET_CREATED,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+ if (sc->status != SMBDIRECT_SOCKET_CREATED)
+ return -EINVAL;
+
+ sc->ib.poll_ctx = poll_ctx;
+
+ sc->send_io.mem.gfp_mask = gfp_mask;
+ sc->recv_io.mem.gfp_mask = gfp_mask;
+ sc->rw_io.mem.gfp_mask = gfp_mask;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_set_kernel_settings);
+
+void smbdirect_socket_set_logging(struct smbdirect_socket *sc,
+ void *private_ptr,
+ bool (*needed)(struct smbdirect_socket *sc,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls),
+ void (*vaprintf)(struct smbdirect_socket *sc,
+ const char *func,
+ unsigned int line,
+ void *private_ptr,
+ unsigned int lvl,
+ unsigned int cls,
+ struct va_format *vaf))
+{
+ sc->logging.private_ptr = private_ptr;
+ sc->logging.needed = needed;
+ sc->logging.vaprintf = vaprintf;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_set_logging);
+
+static void smbdirect_socket_wake_up_all(struct smbdirect_socket *sc)
+{
+ /*
+ * Wake up all waiters in all wait queues
+ * in order to notice the broken connection.
+ */
+ wake_up_all(&sc->status_wait);
+ wake_up_all(&sc->listen.wait_queue);
+ 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);
+ 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);
+}
+
+void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
+ const char *macro_name,
+ unsigned int lvl,
+ const char *func,
+ unsigned int line,
+ int error,
+ enum smbdirect_socket_status *force_status)
+{
+ struct smbdirect_socket *psc, *tsc;
+ unsigned long flags;
+ bool was_first = false;
+
+ if (!sc->first_error) {
+ ___smbdirect_log_generic(sc, func, line,
+ lvl,
+ SMBDIRECT_LOG_RDMA_EVENT,
+ "%s(%1pe%s%s) called from %s in line=%u status=%s\n",
+ macro_name,
+ SMBDIRECT_DEBUG_ERR_PTR(error),
+ force_status ? ", " : "",
+ force_status ? smbdirect_socket_status_string(*force_status) : "",
+ func, line,
+ smbdirect_socket_status_string(sc->status));
+ if (error)
+ sc->first_error = error;
+ else
+ sc->first_error = -ECONNABORTED;
+ was_first = true;
+ }
+
+ /*
+ * make sure other work (than disconnect_work)
+ * is not queued again but here we don't block and avoid
+ * disable[_delayed]_work_sync()
+ */
+ disable_work(&sc->connect.work);
+ disable_work(&sc->recv_io.posted.refill_work);
+ disable_work(&sc->idle.immediate_work);
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
+ disable_delayed_work(&sc->idle.timer_work);
+
+ /*
+ * In case we were a listener we need to
+ * disconnect all pending and ready sockets
+ *
+ * First we move ready sockets to pending again.
+ *
+ * Only a socket that was a listener (listen.backlog != -1) owns a
+ * populated listen.ready/pending list. Guarding on that also keeps
+ * lockdep quiet: without it, the listener holds sc->listen.lock while
+ * the loop recurses into each child psc, which takes psc->listen.lock.
+ * Those are always different instances of the same lock class -- a
+ * child never listens, so the nesting is strictly listener -> child
+ * and cannot really deadlock -- but lockdep only sees the class and
+ * reports "possible recursive locking". A child has empty listen
+ * lists and nothing to do here, so skipping it loses nothing, and a
+ * pending child stays on its listener's list for the free path
+ * (smbdirect_socket_destroy) to reap.
+ */
+ if (sc->listen.backlog != -1) { /* was a listener */
+ spin_lock_irqsave(&sc->listen.lock, flags);
+ list_splice_init(&sc->listen.ready, &sc->listen.pending);
+ list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list)
+ smbdirect_socket_schedule_cleanup(psc, sc->first_error);
+ spin_unlock_irqrestore(&sc->listen.lock, flags);
+ }
+
+ switch (sc->status) {
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED:
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED:
+ case SMBDIRECT_SOCKET_NEGOTIATE_FAILED:
+ case SMBDIRECT_SOCKET_ERROR:
+ case SMBDIRECT_SOCKET_DISCONNECTING:
+ case SMBDIRECT_SOCKET_DISCONNECTED:
+ case SMBDIRECT_SOCKET_DESTROYED:
+ /*
+ * Keep the current error status
+ */
+ break;
+
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
+ sc->status = SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED;
+ break;
+
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED:
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING:
+ sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED;
+ break;
+
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED:
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING:
+ sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED;
+ break;
+
+ case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
+ case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING:
+ sc->status = SMBDIRECT_SOCKET_NEGOTIATE_FAILED;
+ break;
+
+ case SMBDIRECT_SOCKET_CREATED:
+ case SMBDIRECT_SOCKET_LISTENING:
+ sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
+ break;
+
+ case SMBDIRECT_SOCKET_CONNECTED:
+ sc->status = SMBDIRECT_SOCKET_ERROR;
+ break;
+ }
+
+ if (force_status && (was_first || *force_status > sc->status))
+ sc->status = *force_status;
+
+ /*
+ * Wake up all waiters in all wait queues
+ * in order to notice the broken connection.
+ */
+ smbdirect_socket_wake_up_all(sc);
+
+ queue_work(sc->workqueues.cleanup, &sc->disconnect_work);
+}
+
+static void smbdirect_socket_cleanup_work(struct work_struct *work)
+{
+ struct smbdirect_socket *sc =
+ container_of(work, struct smbdirect_socket, disconnect_work);
+ struct smbdirect_socket *psc, *tsc;
+ unsigned long flags;
+
+ /*
+ * This should not never be called in an interrupt!
+ */
+ WARN_ON_ONCE(in_interrupt());
+
+ if (!sc->first_error) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
+ "%s called with first_error==0\n",
+ smbdirect_socket_status_string(sc->status));
+
+ sc->first_error = -ECONNABORTED;
+ }
+
+ /*
+ * make sure this and other work is not queued again
+ * but here we don't block and avoid
+ * disable[_delayed]_work_sync()
+ */
+ disable_work(&sc->disconnect_work);
+ disable_work(&sc->connect.work);
+ disable_work(&sc->recv_io.posted.refill_work);
+ disable_work(&sc->idle.immediate_work);
+ sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
+ disable_delayed_work(&sc->idle.timer_work);
+
+ /*
+ * In case we were a listener we need to
+ * disconnect all pending and ready sockets
+ *
+ * First we move ready sockets to pending again.
+ *
+ * Guarded on listen.backlog != -1 for the same reason as in
+ * __smbdirect_socket_schedule_cleanup(): only a listener owns a
+ * populated listen list, and skipping the block for a child avoids
+ * nesting psc->listen.lock under a listener's listen.lock (different
+ * instances of one class -- harmless, but lockdep cannot tell).
+ */
+ if (sc->listen.backlog != -1) { /* was a listener */
+ spin_lock_irqsave(&sc->listen.lock, flags);
+ list_splice_init(&sc->listen.ready, &sc->listen.pending);
+ list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list)
+ smbdirect_socket_schedule_cleanup(psc, sc->first_error);
+ spin_unlock_irqrestore(&sc->listen.lock, flags);
+ }
+
+ switch (sc->status) {
+ case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
+ case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING:
+ case SMBDIRECT_SOCKET_NEGOTIATE_FAILED:
+ case SMBDIRECT_SOCKET_CONNECTED:
+ case SMBDIRECT_SOCKET_ERROR:
+ sc->status = SMBDIRECT_SOCKET_DISCONNECTING;
+ /*
+ * Make sure we hold the callback lock
+ * im order to coordinate with the
+ * rdma_event handlers, typically
+ * smbdirect_connection_rdma_event_handler(),
+ * and smbdirect_socket_destroy().
+ *
+ * So that the order of ib_drain_qp()
+ * and rdma_disconnect() is controlled
+ * by the mutex.
+ */
+ rdma_lock_handler(sc->rdma.cm_id);
+ rdma_disconnect(sc->rdma.cm_id);
+ rdma_unlock_handler(sc->rdma.cm_id);
+ break;
+
+ case SMBDIRECT_SOCKET_CREATED:
+ case SMBDIRECT_SOCKET_LISTENING:
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
+ case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED:
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING:
+ case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED:
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED:
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING:
+ case SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED:
+ /*
+ * rdma_{accept,connect}() never reached
+ * RDMA_CM_EVENT_ESTABLISHED
+ */
+ sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
+ break;
+
+ case SMBDIRECT_SOCKET_DISCONNECTING:
+ case SMBDIRECT_SOCKET_DISCONNECTED:
+ case SMBDIRECT_SOCKET_DESTROYED:
+ break;
+ }
+
+ /*
+ * Wake up all waiters in all wait queues
+ * in order to notice the broken connection.
+ */
+ smbdirect_socket_wake_up_all(sc);
+}
+
+static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
+{
+ struct smbdirect_socket *psc, *tsc;
+ size_t psockets;
+ struct smbdirect_recv_io *recv_io;
+ struct smbdirect_recv_io *recv_tmp;
+ LIST_HEAD(all_list);
+ LIST_HEAD(pending_list);
+ unsigned long flags;
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+
+ /*
+ * This should not never be called in an interrupt!
+ */
+ WARN_ON_ONCE(in_interrupt());
+
+ if (sc->status == SMBDIRECT_SOCKET_DESTROYED)
+ return;
+
+ WARN_ONCE(sc->status != SMBDIRECT_SOCKET_DISCONNECTED,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+
+ /*
+ * The listener should clear this before we reach this
+ */
+ WARN_ONCE(sc->accept.listener,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+
+ /*
+ * Wake up all waiters in all wait queues
+ * in order to notice the broken connection.
+ *
+ * Most likely this was already called via
+ * smbdirect_socket_cleanup_work(), but call it again...
+ */
+ smbdirect_socket_wake_up_all(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);
+
+ if (sc->rdma.cm_id)
+ rdma_lock_handler(sc->rdma.cm_id);
+
+ if (sc->ib.qp) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "drain qp\n");
+ ib_drain_qp(sc->ib.qp);
+ }
+
+ /*
+ * In case we were a listener we need to
+ * disconnect all pending and ready sockets
+ *
+ * We move ready sockets to pending again.
+ *
+ * Capture them here -- rdma_lock_handler(sc->rdma.cm_id) is held above,
+ * so a concurrent CM CONNECT_REQUEST cannot add more; sc->listen.lock
+ * below only protects the list splice itself -- but DEFER releasing
+ * them until the listener's cm_id is destroyed:
+ *
+ * - smbdirect_socket_release() -> smbdirect_socket_destroy() takes the
+ * child's own rdma_lock_handler() lock (&id_priv->handler_mutex).
+ * The listener's and the child's cm_id are always different
+ * instances, so the nesting cannot really deadlock, but lockdep only
+ * sees one lock class and reports "possible recursive locking".
+ *
+ * - rdma_destroy_id() of a child before the listener's own
+ * rdma_destroy_id() below lets _cma_cancel_listens() walk the freed
+ * child id_priv (KASAN slab-use-after-free in __mutex_lock()).
+ *
+ * The children are independent sockets whose teardown does not need
+ * the listener's handler lock.
+ */
+ spin_lock_irqsave(&sc->listen.lock, flags);
+ list_splice_tail_init(&sc->listen.ready, &pending_list);
+ list_splice_tail_init(&sc->listen.pending, &pending_list);
+ spin_unlock_irqrestore(&sc->listen.lock, flags);
+
+ /* It's not possible for upper layer to get to reassembly */
+ if (sc->listen.backlog == -1) /* was not a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "drain the reassembly queue\n");
+ spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
+ list_splice_tail_init(&sc->recv_io.reassembly.list, &all_list);
+ spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
+ list_for_each_entry_safe(recv_io, recv_tmp, &all_list, list)
+ smbdirect_connection_put_recv_io(recv_io);
+ sc->recv_io.reassembly.data_length = 0;
+
+ if (sc->listen.backlog == -1) /* was not a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "freeing mr list\n");
+ smbdirect_connection_destroy_mr_list(sc);
+
+ if (sc->listen.backlog == -1) /* was not a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "destroying qp\n");
+ smbdirect_connection_destroy_qp(sc);
+ if (sc->rdma.cm_id) {
+ rdma_unlock_handler(sc->rdma.cm_id);
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "destroying cm_id\n");
+ rdma_destroy_id(sc->rdma.cm_id);
+ sc->rdma.cm_id = NULL;
+ }
+
+ /*
+ * The listener's rdma_lock_handler() lock is dropped and its cm_id is
+ * destroyed, so it is safe to release the child sockets captured
+ * above: each release recurses into smbdirect_socket_destroy() and
+ * takes that child's own handler_mutex without nesting it under the
+ * listener's, and _cma_cancel_listens() can no longer reach them.
+ */
+ psockets = list_count_nodes(&pending_list);
+ if (sc->listen.backlog != -1) /* was a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "release %zu pending sockets\n", psockets);
+ list_for_each_entry_safe(psc, tsc, &pending_list, accept.list) {
+ list_del_init(&psc->accept.list);
+ psc->accept.listener = NULL;
+ smbdirect_socket_release(psc);
+ }
+ if (sc->listen.backlog != -1) /* was a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "released %zu pending sockets\n", psockets);
+
+ if (sc->listen.backlog == -1) /* was not a listener */
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "destroying mem pools\n");
+ smbdirect_connection_destroy_mem_pools(sc);
+
+ sc->status = SMBDIRECT_SOCKET_DESTROYED;
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "rdma session destroyed\n");
+}
+
+void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
+{
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+
+ /*
+ * This should not never be called in an interrupt!
+ */
+ WARN_ON_ONCE(in_interrupt());
+
+ /*
+ * First we try to disable the work
+ * without disable_work_sync() in a
+ * non blocking way, if it's already
+ * running it will be handles by
+ * disable_work_sync() below.
+ *
+ * Here we just want to make sure queue_work() in
+ * smbdirect_socket_schedule_cleanup_lvl()
+ * is a no-op.
+ */
+ disable_work(&sc->disconnect_work);
+
+ if (!sc->first_error)
+ /*
+ * 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_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "cancelling and disable disconnect_work\n");
+ disable_work_sync(&sc->disconnect_work);
+
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "destroying rdma session\n");
+ if (sc->status < SMBDIRECT_SOCKET_DISCONNECTING)
+ smbdirect_socket_cleanup_work(&sc->disconnect_work);
+ if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) {
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "wait for transport being disconnected\n");
+ wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED);
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "waited for transport being disconnected\n");
+ }
+
+ /*
+ * Once we reached SMBDIRECT_SOCKET_DISCONNECTED,
+ * we should call smbdirect_socket_destroy()
+ */
+ smbdirect_socket_destroy(sc);
+ smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+ "status=%s first_error=%1pe",
+ smbdirect_socket_status_string(sc->status),
+ SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
+}
+
+int smbdirect_socket_bind(struct smbdirect_socket *sc, struct sockaddr *addr)
+{
+ int ret;
+
+ if (sc->status != SMBDIRECT_SOCKET_CREATED)
+ return -EINVAL;
+
+ ret = rdma_bind_addr(sc->rdma.cm_id, addr);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_bind);
+
+void smbdirect_socket_shutdown(struct smbdirect_socket *sc)
+{
+ smbdirect_socket_schedule_cleanup_lvl(sc, SMBDIRECT_LOG_INFO, -ESHUTDOWN);
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_shutdown);
+
+static void smbdirect_socket_release_disconnect(struct kref *kref)
+{
+ struct smbdirect_socket *sc =
+ container_of(kref, struct smbdirect_socket, refs.disconnect);
+
+ /*
+ * For now do a sync disconnect/destroy
+ */
+ smbdirect_socket_destroy_sync(sc);
+}
+
+static void smbdirect_socket_release_destroy(struct kref *kref)
+{
+ struct smbdirect_socket *sc =
+ container_of(kref, struct smbdirect_socket, refs.destroy);
+
+ /*
+ * Do a sync disconnect/destroy...
+ * hopefully a no-op, as it should be already
+ * in DESTROYED state, before we free the memory.
+ */
+ smbdirect_socket_destroy_sync(sc);
+ kfree(sc);
+}
+
+void smbdirect_socket_release(struct smbdirect_socket *sc)
+{
+ /*
+ * We expect only 1 disconnect reference
+ * and if it is already 0, it's a use after free!
+ */
+ WARN_ON_ONCE(kref_read(&sc->refs.disconnect) != 1);
+ WARN_ON(!kref_put(&sc->refs.disconnect, smbdirect_socket_release_disconnect));
+
+ /*
+ * This may not trigger smbdirect_socket_release_destroy(),
+ * if struct smbdirect_socket is embedded in another structure
+ * indicated by REFCOUNT_MAX.
+ */
+ kref_put(&sc->refs.destroy, smbdirect_socket_release_destroy);
+}
+EXPORT_SYMBOL_GPL(smbdirect_socket_release);
+
+int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc,
+ enum smbdirect_socket_status expected_status,
+ int unexpected_errno,
+ wait_queue_head_t *waitq,
+ atomic_t *total_credits,
+ int needed)
+{
+ int ret;
+
+ if (WARN_ON_ONCE(needed < 0))
+ return -EINVAL;
+
+ 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 != expected_status);
+
+ if (sc->status != expected_status)
+ return unexpected_errno;
+ else if (ret < 0)
+ return ret;
+ } while (true);
+}