]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
smb: smbdirect: introduce smbdirect_socket_{listen,accept}()
authorStefan Metzmacher <metze@samba.org>
Wed, 19 Nov 2025 10:46:01 +0000 (11:46 +0100)
committerSteve French <stfrench@microsoft.com>
Thu, 16 Apr 2026 02:58:20 +0000 (21:58 -0500)
These will be used by the server soon instead of using
smbdirect_accept_connect_request() together with rdma_listen().

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>
fs/smb/client/smbdirect.c
fs/smb/common/smbdirect/smbdirect_accept.c
fs/smb/common/smbdirect/smbdirect_all_c_files.c
fs/smb/common/smbdirect/smbdirect_connection.c
fs/smb/common/smbdirect/smbdirect_listen.c [new file with mode: 0644]
fs/smb/common/smbdirect/smbdirect_public.h
fs/smb/common/smbdirect/smbdirect_socket.c
fs/smb/common/smbdirect/smbdirect_socket.h
fs/smb/server/transport_rdma.c

index 443ff427e28f2d12778553e37ddb416345037f22..f5ba33460723242a8228779925a0705bca1bd4f8 100644 (file)
@@ -244,6 +244,7 @@ static void smbd_disconnect_rdma_work(struct work_struct *work)
                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:
@@ -323,6 +324,7 @@ static void smbd_disconnect_rdma_connection(struct smbdirect_socket *sc)
                break;
 
        case SMBDIRECT_SOCKET_CREATED:
+       case SMBDIRECT_SOCKET_LISTENING:
                sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
                break;
 
index 72ee80a185f6065d3e0b2e6926b87b8d87794978..4fc5983e99b8151ac773dfdef0975c24bc762884 100644 (file)
@@ -6,6 +6,7 @@
  */
 
 #include "smbdirect_internal.h"
+#include <net/sock.h>
 #include "../../common/smb2status.h"
 
 static int smbdirect_accept_rdma_event_handler(struct rdma_cm_id *id,
@@ -454,6 +455,28 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work)
         */
        sp->max_fragmented_send_size = max_fragmented_size;
 
+       if (sc->accept.listener) {
+               struct smbdirect_socket *lsc = sc->accept.listener;
+               unsigned long flags;
+
+               spin_lock_irqsave(&lsc->listen.lock, flags);
+               list_del(&sc->accept.list);
+               list_add_tail(&sc->accept.list, &lsc->listen.ready);
+               wake_up(&lsc->listen.wait_queue);
+               spin_unlock_irqrestore(&lsc->listen.lock, flags);
+
+               /*
+                * smbdirect_socket_accept() will call
+                * smbdirect_accept_negotiate_finish(nsc, 0);
+                *
+                * So that we don't send the negotiation
+                * response that grants credits to the peer
+                * before the socket is accepted by the
+                * application.
+                */
+               return;
+       }
+
        ntstatus = le32_to_cpu(STATUS_SUCCESS);
 
 not_supported:
@@ -748,3 +771,90 @@ static int smbdirect_accept_rdma_event_handler(struct rdma_cm_id *id,
        smbdirect_socket_schedule_cleanup(sc, -ECONNABORTED);
        return 0;
 }
+
+static long smbdirect_socket_wait_for_accept(struct smbdirect_socket *lsc, long timeo)
+{
+       long ret;
+
+       ret = wait_event_interruptible_timeout(lsc->listen.wait_queue,
+                                              !list_empty_careful(&lsc->listen.ready) ||
+                                              lsc->status != SMBDIRECT_SOCKET_LISTENING ||
+                                              lsc->first_error,
+                                              timeo);
+       if (lsc->status != SMBDIRECT_SOCKET_LISTENING)
+               return -EINVAL;
+       if (lsc->first_error)
+               return lsc->first_error;
+       if (!ret)
+               ret = -ETIMEDOUT;
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+__SMBDIRECT_PUBLIC__
+struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc,
+                                                long timeo,
+                                                struct proto_accept_arg *arg)
+{
+       struct smbdirect_socket *nsc;
+       unsigned long flags;
+
+       if (lsc->status != SMBDIRECT_SOCKET_LISTENING) {
+               arg->err = -EINVAL;
+               return NULL;
+       }
+
+       if (lsc->first_error) {
+               arg->err = lsc->first_error;
+               return NULL;
+       }
+
+       if (list_empty_careful(&lsc->listen.ready)) {
+               int ret;
+
+               if (timeo == 0) {
+                       arg->err = -EAGAIN;
+                       return NULL;
+               }
+
+               ret = smbdirect_socket_wait_for_accept(lsc, timeo);
+               if (ret) {
+                       arg->err = ret;
+                       return NULL;
+               }
+       }
+
+       spin_lock_irqsave(&lsc->listen.lock, flags);
+       nsc = list_first_entry_or_null(&lsc->listen.ready,
+                                      struct smbdirect_socket,
+                                      accept.list);
+       if (nsc) {
+               nsc->accept.listener = NULL;
+               list_del_init_careful(&nsc->accept.list);
+               arg->is_empty = list_empty_careful(&lsc->listen.ready);
+       }
+       spin_unlock_irqrestore(&lsc->listen.lock, flags);
+       if (!nsc) {
+               arg->err = -EAGAIN;
+               return NULL;
+       }
+
+       /*
+        * We did not send the negotiation response
+        * yet, so we did not grant any credits to the client,
+        * so it didn't grant any credits to us.
+        *
+        * The caller expects a connected socket
+        * now as there are no credits anyway.
+        *
+        * Then we send the negotiation response in
+        * order to grant credits to the peer.
+        */
+       nsc->status = SMBDIRECT_SOCKET_CONNECTED;
+       smbdirect_accept_negotiate_finish(nsc, 0);
+
+       return nsc;
+}
+__SMBDIRECT_EXPORT_SYMBOL__(smbdirect_socket_accept);
index 40e2ceb9a4a4643f104b9bc6dc5cf4f097eb3eb3..03e5852cdf86e216ce7b5106e18851556e59b464 100644 (file)
@@ -22,3 +22,4 @@
 #include "smbdirect_debug.c"
 #include "smbdirect_connect.c"
 #include "smbdirect_accept.c"
+#include "smbdirect_listen.c"
index b7adbd04eb695b1b1ad2d2dff5fc85e95643f51c..4afeb4ddadd0cfb73ed3dc7ea721cf65fdfc6c9d 100644 (file)
@@ -163,6 +163,14 @@ void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
        if (unlikely(sc->first_error))
                return;
 
+       if (sc->status == SMBDIRECT_SOCKET_CONNECTED)
+               /*
+                * This is the accept case where
+                * smbdirect_socket_accept() already sets
+                * SMBDIRECT_SOCKET_CONNECTED
+                */
+               goto done;
+
        if (sc->status != SMBDIRECT_SOCKET_NEGOTIATE_RUNNING) {
                /*
                 * Something went wrong...
@@ -189,6 +197,7 @@ void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
         * We need to setup the refill and send immediate work
         * in order to get a working connection.
         */
+done:
        INIT_WORK(&sc->recv_io.posted.refill_work, smbdirect_connection_recv_io_refill_work);
        INIT_WORK(&sc->idle.immediate_work, smbdirect_connection_send_immediate_work);
 
diff --git a/fs/smb/common/smbdirect/smbdirect_listen.c b/fs/smb/common/smbdirect/smbdirect_listen.c
new file mode 100644 (file)
index 0000000..ad1ecf6
--- /dev/null
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *   Copyright (C) 2017, Microsoft Corporation.
+ *   Copyright (C) 2018, LG Electronics.
+ *   Copyright (c) 2025, Stefan Metzmacher
+ */
+
+#include "smbdirect_internal.h"
+
+static int smbdirect_listen_rdma_event_handler(struct rdma_cm_id *id,
+                                              struct rdma_cm_event *event);
+
+__SMBDIRECT_PUBLIC__
+int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog)
+{
+       int ret;
+
+       if (backlog < 0)
+               return -EINVAL;
+       if (!backlog)
+               backlog = 1; /* use 1 as default for now */
+
+       if (sc->first_error)
+               return -EINVAL;
+
+       if (sc->status != SMBDIRECT_SOCKET_CREATED)
+               return -EINVAL;
+
+       if (WARN_ON_ONCE(!sc->rdma.cm_id))
+               return -EINVAL;
+
+       if (sc->rdma.cm_id->device)
+               smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+                       "try to listen on addr: %pISpsfc dev: %.*s\n",
+                       &sc->rdma.cm_id->route.addr.src_addr,
+                       IB_DEVICE_NAME_MAX,
+                       sc->rdma.cm_id->device->name);
+       else
+               smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+                       "try to listen on addr: %pISpsfc\n",
+                       &sc->rdma.cm_id->route.addr.src_addr);
+
+       /* already checked above */
+       WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_CREATED);
+       sc->status = SMBDIRECT_SOCKET_LISTENING;
+       sc->rdma.expected_event = RDMA_CM_EVENT_CONNECT_REQUEST;
+       rdma_lock_handler(sc->rdma.cm_id);
+       sc->rdma.cm_id->event_handler = smbdirect_listen_rdma_event_handler;
+       rdma_unlock_handler(sc->rdma.cm_id);
+
+       ret = rdma_listen(sc->rdma.cm_id, backlog);
+       if (ret) {
+               sc->first_error = ret;
+               sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
+               if (sc->rdma.cm_id->device)
+                       smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+                               "listening failed %1pe on addr: %pISpsfc dev: %.*s\n",
+                               SMBDIRECT_DEBUG_ERR_PTR(ret),
+                               &sc->rdma.cm_id->route.addr.src_addr,
+                               IB_DEVICE_NAME_MAX,
+                               sc->rdma.cm_id->device->name);
+               else
+                       smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+                               "listening failed %1pe on addr: %pISpsfc\n",
+                               SMBDIRECT_DEBUG_ERR_PTR(ret),
+                               &sc->rdma.cm_id->route.addr.src_addr);
+               return ret;
+       }
+
+       /*
+        * This is a value > 0, checked above,
+        * so we are able to use sc->listen.backlog == -1,
+        * as indication that the socket was never
+        * a listener.
+        */
+       sc->listen.backlog = backlog;
+
+       if (sc->rdma.cm_id->device)
+               smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+                       "listening on addr: %pISpsfc dev: %.*s\n",
+                       &sc->rdma.cm_id->route.addr.src_addr,
+                       IB_DEVICE_NAME_MAX,
+                       sc->rdma.cm_id->device->name);
+       else
+               smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
+                       "listening on addr: %pISpsfc\n",
+                       &sc->rdma.cm_id->route.addr.src_addr);
+
+       /*
+        * The rest happens async via smbdirect_listen_rdma_event_handler()
+        */
+       return 0;
+}
+__SMBDIRECT_EXPORT_SYMBOL__(smbdirect_socket_listen);
+
+static int smbdirect_new_rdma_event_handler(struct rdma_cm_id *new_id,
+                                           struct rdma_cm_event *event)
+{
+       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;
+       WARN_ONCE(1,
+                 "%s should not be called! event=%s status=%d => ret=%1pe\n",
+                 __func__,
+                 rdma_event_msg(event->event),
+                 event->status,
+                 SMBDIRECT_DEBUG_ERR_PTR(ret));
+       return -ESTALE;
+}
+
+static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
+                                           struct rdma_cm_id *new_id,
+                                           const struct rdma_cm_event *event);
+
+static int smbdirect_listen_rdma_event_handler(struct rdma_cm_id *new_id,
+                                              struct rdma_cm_event *event)
+{
+       struct smbdirect_socket *lsc = new_id->context;
+       int ret;
+
+       if (event->event == RDMA_CM_EVENT_CONNECT_REQUEST) {
+               new_id->context = NULL;
+               new_id->event_handler = smbdirect_new_rdma_event_handler;
+       } else
+               new_id = NULL;
+
+       /*
+        * cma_cm_event_handler() has
+        * lockdep_assert_held(&id_priv->handler_mutex);
+        *
+        * Mutexes are not allowed in interrupts,
+        * and we rely on not being in an interrupt here,
+        * as we might sleep.
+        */
+       WARN_ON_ONCE(in_interrupt());
+
+       if (event->status || event->event != lsc->rdma.expected_event) {
+               ret = -ECONNABORTED;
+
+               if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL)
+                       ret = -ENETDOWN;
+               if (IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(event->status)))
+                       ret = event->status;
+
+               smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+                       "%s (first_error=%1pe, expected=%s) => event=%s status=%d => ret=%1pe\n",
+                       smbdirect_socket_status_string(lsc->status),
+                       SMBDIRECT_DEBUG_ERR_PTR(lsc->first_error),
+                       rdma_event_msg(lsc->rdma.expected_event),
+                       rdma_event_msg(event->event),
+                       event->status,
+                       SMBDIRECT_DEBUG_ERR_PTR(ret));
+
+               /*
+                * In case of error return it and let the caller
+                * destroy new_id
+                */
+               smbdirect_socket_schedule_cleanup(lsc, ret);
+               return new_id ? ret : 0;
+       }
+
+       smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_INFO,
+               "%s (first_error=%1pe) event=%s\n",
+               smbdirect_socket_status_string(lsc->status),
+               SMBDIRECT_DEBUG_ERR_PTR(lsc->first_error),
+               rdma_event_msg(event->event));
+
+       /*
+        * In case of error return it and let the caller
+        * destroy new_id
+        */
+       if (lsc->first_error)
+               return new_id ? lsc->first_error : 0;
+
+       switch (event->event) {
+       case RDMA_CM_EVENT_CONNECT_REQUEST:
+               WARN_ON_ONCE(lsc->status != SMBDIRECT_SOCKET_LISTENING);
+
+               /*
+                * In case of error return it and let the caller
+                * destroy new_id
+                */
+               ret = smbdirect_listen_connect_request(lsc, new_id, event);
+               if (ret)
+                       return ret;
+               return 0;
+
+       default:
+               break;
+       }
+
+       /*
+        * This is an internal error
+        */
+       WARN_ON_ONCE(lsc->rdma.expected_event != RDMA_CM_EVENT_CONNECT_REQUEST);
+       smbdirect_socket_schedule_cleanup(lsc, -EINVAL);
+       return 0;
+}
+
+static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
+                                           struct rdma_cm_id *new_id,
+                                           const struct rdma_cm_event *event)
+{
+       const struct smbdirect_socket_parameters *lsp = &lsc->parameters;
+       struct smbdirect_socket *nsc;
+       unsigned long flags;
+       size_t backlog = max_t(size_t, 1, lsc->listen.backlog);
+       size_t psockets;
+       size_t rsockets;
+       int ret;
+
+       if (!smbdirect_frwr_is_supported(&new_id->device->attrs)) {
+               smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+                       "Fast Registration Work Requests (FRWR) is not supported device %.*s\n",
+                       IB_DEVICE_NAME_MAX,
+                       new_id->device->name);
+               smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+                       "Device capability flags = %llx max_fast_reg_page_list_len = %u\n",
+                       new_id->device->attrs.device_cap_flags,
+                       new_id->device->attrs.max_fast_reg_page_list_len);
+               return -EPROTONOSUPPORT;
+       }
+
+       if (lsp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB &&
+           !rdma_ib_or_roce(new_id->device, new_id->port_num)) {
+               smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+                       "Not IB: device: %.*s IW:%u local: %pISpsfc remote: %pISpsfc\n",
+                       IB_DEVICE_NAME_MAX,
+                       new_id->device->name,
+                       rdma_protocol_iwarp(new_id->device, new_id->port_num),
+                       &new_id->route.addr.src_addr,
+                       &new_id->route.addr.dst_addr);
+               return -EPROTONOSUPPORT;
+       }
+       if (lsp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW &&
+           !rdma_protocol_iwarp(new_id->device, new_id->port_num)) {
+               smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+                       "Not IW: device: %.*s IB:%u local: %pISpsfc remote: %pISpsfc\n",
+                       IB_DEVICE_NAME_MAX,
+                       new_id->device->name,
+                       rdma_ib_or_roce(new_id->device, new_id->port_num),
+                       &new_id->route.addr.src_addr,
+                       &new_id->route.addr.dst_addr);
+               return -EPROTONOSUPPORT;
+       }
+
+       spin_lock_irqsave(&lsc->listen.lock, flags);
+       psockets = list_count_nodes(&lsc->listen.pending);
+       rsockets = list_count_nodes(&lsc->listen.ready);
+       spin_unlock_irqrestore(&lsc->listen.lock, flags);
+
+       if (psockets > backlog ||
+           rsockets > backlog ||
+           (psockets + rsockets) > backlog) {
+               smbdirect_log_rdma_event(lsc, SMBDIRECT_LOG_ERR,
+                       "Backlog[%d][%zu] full pending[%zu] ready[%zu]\n",
+                       lsc->listen.backlog, backlog, psockets, rsockets);
+               return -EBUSY;
+       }
+
+       ret = smbdirect_socket_create_accepting(new_id, &nsc);
+       if (ret)
+               goto socket_init_failed;
+
+       nsc->logging = lsc->logging;
+       ret = smbdirect_socket_set_initial_parameters(nsc, &lsc->parameters);
+       if (ret)
+               goto set_params_failed;
+       ret = smbdirect_socket_set_kernel_settings(nsc,
+                                                  lsc->ib.poll_ctx,
+                                                  lsc->send_io.mem.gfp_mask);
+       if (ret)
+               goto set_settings_failed;
+
+       spin_lock_irqsave(&lsc->listen.lock, flags);
+       list_add_tail(&nsc->accept.list, &lsc->listen.pending);
+       nsc->accept.listener = lsc;
+       spin_unlock_irqrestore(&lsc->listen.lock, flags);
+
+       ret = smbdirect_accept_connect_request(nsc, &event->param.conn);
+       if (ret)
+               goto accept_connect_failed;
+
+       return 0;
+
+accept_connect_failed:
+       spin_lock_irqsave(&lsc->listen.lock, flags);
+       list_del_init(&nsc->accept.list);
+       nsc->accept.listener = NULL;
+       spin_unlock_irqrestore(&lsc->listen.lock, flags);
+set_settings_failed:
+set_params_failed:
+       /*
+        * The caller will destroy new_id
+        */
+       nsc->ib.dev = NULL;
+       nsc->rdma.cm_id = NULL;
+       smbdirect_socket_release(nsc);
+socket_init_failed:
+       return ret;
+}
index 95837beeece8faf7ff5b4e55a19b6ce7492f442b..c0144c5a808c8d6123376b15480147b8d7a7046d 100644 (file)
@@ -147,10 +147,18 @@ __SMBDIRECT_PUBLIC__
 int smbdirect_connect_sync(struct smbdirect_socket *sc,
                           const struct sockaddr *dst);
 
+__SMBDIRECT_PUBLIC__
+int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog);
+
 __SMBDIRECT_PUBLIC__
 int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
                                     const struct rdma_conn_param *param);
 
+__SMBDIRECT_PUBLIC__
+struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc,
+                                                long timeo,
+                                                struct proto_accept_arg *arg);
+
 __SMBDIRECT_PUBLIC__
 int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
                                   void *buf, size_t buf_len,
index def67fdac066e20bc853fcead5cfa2eae7e00f6a..63cdfccedd55c8c3efdb4bd222d3adf6abf9e5e2 100644 (file)
@@ -299,6 +299,7 @@ static void smbdirect_socket_wake_up_all(struct smbdirect_socket *sc)
         * 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);
@@ -319,6 +320,8 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
                                         int error,
                                         enum smbdirect_socket_status *force_status)
 {
+       struct smbdirect_socket *psc, *tsc;
+       unsigned long flags;
        bool was_first = false;
 
        if (!sc->first_error) {
@@ -351,6 +354,18 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
        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.
+        */
+       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:
@@ -386,6 +401,7 @@ void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
                break;
 
        case SMBDIRECT_SOCKET_CREATED:
+       case SMBDIRECT_SOCKET_LISTENING:
                sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
                break;
 
@@ -410,6 +426,8 @@ 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!
@@ -437,6 +455,18 @@ static void smbdirect_socket_cleanup_work(struct work_struct *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.
+        */
+       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:
@@ -448,6 +478,7 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work)
                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:
@@ -479,6 +510,8 @@ static void smbdirect_socket_cleanup_work(struct work_struct *work)
 
 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);
@@ -502,6 +535,14 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
                  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.
@@ -527,9 +568,34 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
                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.
+        */
+       spin_lock_irqsave(&sc->listen.lock, flags);
+       list_splice_tail_init(&sc->listen.ready, &all_list);
+       list_splice_tail_init(&sc->listen.pending, &all_list);
+       spin_unlock_irqrestore(&sc->listen.lock, flags);
+       psockets = list_count_nodes(&all_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, &all_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);
+       INIT_LIST_HEAD(&all_list);
+
        /* It's not possible for upper layer to get to reassembly */
-       smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
-               "drain the reassembly queue\n");
+       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);
@@ -537,12 +603,14 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
                smbdirect_connection_put_recv_io(recv_io);
        sc->recv_io.reassembly.data_length = 0;
 
-       smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
-               "freeing mr list\n");
+       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);
 
-       smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
-               "destroying qp\n");
+       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);
@@ -552,8 +620,9 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
                sc->rdma.cm_id = NULL;
        }
 
-       smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
-               "destroying mem pools\n");
+       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;
index 44d04cc63d04ae7bd5ff799bc1abc6726b0ad012..127197c3e1648564dff6ce198391b8017b3b3be1 100644 (file)
@@ -17,6 +17,7 @@
 
 enum smbdirect_socket_status {
        SMBDIRECT_SOCKET_CREATED,
+       SMBDIRECT_SOCKET_LISTENING,
        SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED,
        SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING,
        SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED,
@@ -42,6 +43,8 @@ const char *smbdirect_socket_status_string(enum smbdirect_socket_status status)
        switch (status) {
        case SMBDIRECT_SOCKET_CREATED:
                return "CREATED";
+       case SMBDIRECT_SOCKET_LISTENING:
+               return "LISTENING";
        case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
                return "RESOLVE_ADDR_NEEDED";
        case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
@@ -193,6 +196,35 @@ struct smbdirect_socket {
                struct delayed_work timer_work;
        } idle;
 
+       /*
+        * The state for listen sockets
+        */
+       struct {
+               spinlock_t lock;
+               struct list_head pending;
+               struct list_head ready;
+               wait_queue_head_t wait_queue;
+               /*
+                * This starts as -1 and a value != -1
+                * means this socket was in LISTENING state
+                * before. Note the valid backlog can
+                * only be > 0.
+                */
+               int backlog;
+       } listen;
+
+       /*
+        * The state for sockets waiting
+        * for accept, either still waiting
+        * for the negotiation to finish
+        * or already ready with a usable
+        * connection.
+        */
+       struct {
+               struct smbdirect_socket *listener;
+               struct list_head list;
+       } accept;
+
        /*
         * The state for posted send buffers
         */
@@ -552,6 +584,14 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
        INIT_DELAYED_WORK(&sc->idle.timer_work, __smbdirect_socket_disabled_work);
        disable_delayed_work_sync(&sc->idle.timer_work);
 
+       spin_lock_init(&sc->listen.lock);
+       INIT_LIST_HEAD(&sc->listen.pending);
+       INIT_LIST_HEAD(&sc->listen.ready);
+       sc->listen.backlog = -1; /* not a listener */
+       init_waitqueue_head(&sc->listen.wait_queue);
+
+       INIT_LIST_HEAD(&sc->accept.list);
+
        sc->send_io.mem.gfp_mask = GFP_KERNEL;
 
        atomic_set(&sc->send_io.bcredits.count, 0);
index 12a8def52ff8cafe13d1bad54a8d6664426fba86..2978b88500827065e74382cacc5e5d85854bef80 100644 (file)
@@ -284,6 +284,7 @@ static void smb_direct_disconnect_rdma_work(struct work_struct *work)
                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:
@@ -364,6 +365,7 @@ smb_direct_disconnect_rdma_connection(struct smbdirect_socket *sc)
                break;
 
        case SMBDIRECT_SOCKET_CREATED:
+       case SMBDIRECT_SOCKET_LISTENING:
                sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
                break;