From: Stefan Metzmacher Date: Wed, 19 Nov 2025 10:46:01 +0000 (+0100) Subject: smb: smbdirect: introduce smbdirect_socket_{listen,accept}() X-Git-Tag: v7.1-rc1~128^2~85 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=dc691b91ad1677def14582a279e56fd943b52f94;p=thirdparty%2Flinux.git smb: smbdirect: introduce smbdirect_socket_{listen,accept}() These will be used by the server soon instead of using smbdirect_accept_connect_request() together with rdma_listen(). Cc: Steve French Cc: Tom Talpey Cc: Long Li Cc: Namjae Jeon Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher Acked-by: Namjae Jeon Signed-off-by: Steve French --- diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c index 443ff427e28f2..f5ba334607232 100644 --- a/fs/smb/client/smbdirect.c +++ b/fs/smb/client/smbdirect.c @@ -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; diff --git a/fs/smb/common/smbdirect/smbdirect_accept.c b/fs/smb/common/smbdirect/smbdirect_accept.c index 72ee80a185f60..4fc5983e99b81 100644 --- a/fs/smb/common/smbdirect/smbdirect_accept.c +++ b/fs/smb/common/smbdirect/smbdirect_accept.c @@ -6,6 +6,7 @@ */ #include "smbdirect_internal.h" +#include #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); diff --git a/fs/smb/common/smbdirect/smbdirect_all_c_files.c b/fs/smb/common/smbdirect/smbdirect_all_c_files.c index 40e2ceb9a4a46..03e5852cdf86e 100644 --- a/fs/smb/common/smbdirect/smbdirect_all_c_files.c +++ b/fs/smb/common/smbdirect/smbdirect_all_c_files.c @@ -22,3 +22,4 @@ #include "smbdirect_debug.c" #include "smbdirect_connect.c" #include "smbdirect_accept.c" +#include "smbdirect_listen.c" diff --git a/fs/smb/common/smbdirect/smbdirect_connection.c b/fs/smb/common/smbdirect/smbdirect_connection.c index b7adbd04eb695..4afeb4ddadd0c 100644 --- a/fs/smb/common/smbdirect/smbdirect_connection.c +++ b/fs/smb/common/smbdirect/smbdirect_connection.c @@ -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 index 0000000000000..ad1ecf64762bc --- /dev/null +++ b/fs/smb/common/smbdirect/smbdirect_listen.c @@ -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; +} diff --git a/fs/smb/common/smbdirect/smbdirect_public.h b/fs/smb/common/smbdirect/smbdirect_public.h index 95837beeece8f..c0144c5a808c8 100644 --- a/fs/smb/common/smbdirect/smbdirect_public.h +++ b/fs/smb/common/smbdirect/smbdirect_public.h @@ -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, diff --git a/fs/smb/common/smbdirect/smbdirect_socket.c b/fs/smb/common/smbdirect/smbdirect_socket.c index def67fdac066e..63cdfccedd55c 100644 --- a/fs/smb/common/smbdirect/smbdirect_socket.c +++ b/fs/smb/common/smbdirect/smbdirect_socket.c @@ -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; diff --git a/fs/smb/common/smbdirect/smbdirect_socket.h b/fs/smb/common/smbdirect/smbdirect_socket.h index 44d04cc63d04a..127197c3e1648 100644 --- a/fs/smb/common/smbdirect/smbdirect_socket.h +++ b/fs/smb/common/smbdirect/smbdirect_socket.h @@ -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); diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c index 12a8def52ff8c..2978b88500827 100644 --- a/fs/smb/server/transport_rdma.c +++ b/fs/smb/server/transport_rdma.c @@ -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;