root/fs/smb/smbdirect/listen.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *   Copyright (C) 2017, Microsoft Corporation.
 *   Copyright (C) 2018, LG Electronics.
 *   Copyright (c) 2025, Stefan Metzmacher
 */

#include "internal.h"

static int smbdirect_listen_rdma_event_handler(struct rdma_cm_id *id,
                                               struct rdma_cm_event *event);

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;
}
EXPORT_SYMBOL_GPL(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;
}