root/fs/smb/server/transport_rdma.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *   Copyright (C) 2017, Microsoft Corporation.
 *   Copyright (C) 2018, LG Electronics.
 *
 *   Author(s): Long Li <longli@microsoft.com>,
 *              Hyunchul Lee <hyc.lee@gmail.com>
 */

#define SUBMOD_NAME     "smb_direct"

#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/string_choices.h>

#include "glob.h"
#include "connection.h"
#include "smb_common.h"
#include "../common/smb2status.h"
#include "transport_rdma.h"


#define SMB_DIRECT_PORT_IWARP           5445
#define SMB_DIRECT_PORT_INFINIBAND      445

/* SMB_DIRECT negotiation timeout (for the server) in seconds */
#define SMB_DIRECT_NEGOTIATE_TIMEOUT            5

/* The timeout to wait for a keepalive message from peer in seconds */
#define SMB_DIRECT_KEEPALIVE_SEND_INTERVAL      120

/* The timeout to wait for a keepalive message from peer in seconds */
#define SMB_DIRECT_KEEPALIVE_RECV_TIMEOUT       5

/*
 * Default maximum number of RDMA read/write outstanding on this connection
 * This value is possibly decreased during QP creation on hardware limit
 */
#define SMB_DIRECT_CM_INITIATOR_DEPTH           8

/*
 * User configurable initial values per SMB_DIRECT transport connection
 * as defined in [MS-SMBD] 3.1.1.1
 * Those may change after a SMB_DIRECT negotiation
 */

/* The local peer's maximum number of credits to grant to the peer */
static int smb_direct_receive_credit_max = 255;

/* The remote peer's credit request of local peer */
static int smb_direct_send_credit_target = 255;

/* The maximum single message size can be sent to remote peer */
static int smb_direct_max_send_size = 1364;

/*
 * The maximum fragmented upper-layer payload receive size supported
 *
 * Assume max_payload_per_credit is
 * smb_direct_receive_credit_max - 24 = 1340
 *
 * The maximum number would be
 * smb_direct_receive_credit_max * max_payload_per_credit
 *
 *                       1340 * 255 = 341700 (0x536C4)
 *
 * The minimum value from the spec is 131072 (0x20000)
 *
 * For now we use the logic we used before:
 *                 (1364 * 255) / 2 = 173910 (0x2A756)
 */
static int smb_direct_max_fragmented_recv_size = (1364 * 255) / 2;

/*  The maximum single-message size which can be received */
static int smb_direct_max_receive_size = 1364;

static int smb_direct_max_read_write_size = SMBD_DEFAULT_IOSIZE;

static struct smb_direct_listener {
        int                     port;

        struct task_struct      *thread;

        struct smbdirect_socket *socket;
} smb_direct_ib_listener, smb_direct_iw_listener;

struct smb_direct_transport {
        struct ksmbd_transport  transport;

        struct smbdirect_socket *socket;
};

static bool smb_direct_logging_needed(struct smbdirect_socket *sc,
                                      void *private_ptr,
                                      unsigned int lvl,
                                      unsigned int cls)
{
        if (lvl <= SMBDIRECT_LOG_ERR)
                return true;

        if (lvl > SMBDIRECT_LOG_INFO)
                return false;

        switch (cls) {
        /*
         * These were more or less also logged before
         * the move to common code.
         *
         * SMBDIRECT_LOG_RDMA_MR was not used, but
         * that's client only code and we should
         * notice if it's used on the server...
         */
        case SMBDIRECT_LOG_RDMA_EVENT:
        case SMBDIRECT_LOG_RDMA_SEND:
        case SMBDIRECT_LOG_RDMA_RECV:
        case SMBDIRECT_LOG_WRITE:
        case SMBDIRECT_LOG_READ:
        case SMBDIRECT_LOG_NEGOTIATE:
        case SMBDIRECT_LOG_OUTGOING:
        case SMBDIRECT_LOG_RDMA_RW:
        case SMBDIRECT_LOG_RDMA_MR:
                return true;
        /*
         * These were not logged before the move
         * to common code.
         */
        case SMBDIRECT_LOG_KEEP_ALIVE:
        case SMBDIRECT_LOG_INCOMING:
                return false;
        }

        /*
         * Log all unknown messages
         */
        return true;
}

static void smb_direct_logging_vaprintf(struct smbdirect_socket *sc,
                                        const char *func,
                                        unsigned int line,
                                        void *private_ptr,
                                        unsigned int lvl,
                                        unsigned int cls,
                                        struct va_format *vaf)
{
        if (lvl <= SMBDIRECT_LOG_ERR)
                pr_err("%pV", vaf);
        else
                ksmbd_debug(RDMA, "%pV", vaf);
}

#define KSMBD_TRANS(t) (&(t)->transport)
#define SMBD_TRANS(t)   (container_of(t, \
                                struct smb_direct_transport, transport))

static const struct ksmbd_transport_ops ksmbd_smb_direct_transport_ops;

void init_smbd_max_io_size(unsigned int sz)
{
        sz = clamp_val(sz, SMBD_MIN_IOSIZE, SMBD_MAX_IOSIZE);
        smb_direct_max_read_write_size = sz;
}

unsigned int get_smbd_max_read_write_size(struct ksmbd_transport *kt)
{
        struct smb_direct_transport *t;
        const struct smbdirect_socket_parameters *sp;

        if (kt->ops != &ksmbd_smb_direct_transport_ops)
                return 0;

        t = SMBD_TRANS(kt);
        sp = smbdirect_socket_get_current_parameters(t->socket);

        return sp->max_read_write_size;
}

static struct smb_direct_transport *alloc_transport(struct smbdirect_socket *sc)
{
        struct smb_direct_transport *t;
        struct ksmbd_conn *conn;

        t = kzalloc_obj(*t, KSMBD_DEFAULT_GFP);
        if (!t)
                return NULL;
        t->socket = sc;

        conn = ksmbd_conn_alloc();
        if (!conn)
                goto conn_alloc_failed;

        down_write(&conn_list_lock);
        hash_add(conn_list, &conn->hlist, 0);
        up_write(&conn_list_lock);

        conn->transport = KSMBD_TRANS(t);
        KSMBD_TRANS(t)->conn = conn;
        KSMBD_TRANS(t)->ops = &ksmbd_smb_direct_transport_ops;

        return t;

conn_alloc_failed:
        kfree(t);
        return NULL;
}

static void smb_direct_free_transport(struct ksmbd_transport *kt)
{
        struct smb_direct_transport *t = SMBD_TRANS(kt);

        smbdirect_socket_release(t->socket);
        kfree(t);
}

static void free_transport(struct smb_direct_transport *t)
{
        smbdirect_socket_shutdown(t->socket);
        ksmbd_conn_free(KSMBD_TRANS(t)->conn);
}

static int smb_direct_read(struct ksmbd_transport *t, char *buf,
                           unsigned int size, int unused)
{
        struct smb_direct_transport *st = SMBD_TRANS(t);
        struct smbdirect_socket *sc = st->socket;
        struct msghdr msg = { .msg_flags = 0, };
        struct kvec iov = {
                .iov_base = buf,
                .iov_len = size,
        };
        int ret;

        iov_iter_kvec(&msg.msg_iter, ITER_DEST, &iov, 1, size);

        ret = smbdirect_connection_recvmsg(sc, &msg, 0);
        if (ret == -ERESTARTSYS)
                ret = -EINTR;
        return ret;
}

static int smb_direct_writev(struct ksmbd_transport *t,
                             struct kvec *iov, int niovs, int buflen,
                             bool need_invalidate, unsigned int remote_key)
{
        struct smb_direct_transport *st = SMBD_TRANS(t);
        struct smbdirect_socket *sc = st->socket;
        struct iov_iter iter;

        iov_iter_kvec(&iter, ITER_SOURCE, iov, niovs, buflen);

        return smbdirect_connection_send_iter(sc, &iter, 0,
                                              need_invalidate, remote_key);
}

static int smb_direct_rdma_write(struct ksmbd_transport *t,
                                 void *buf, unsigned int buflen,
                                 struct smbdirect_buffer_descriptor_v1 *desc,
                                 unsigned int desc_len)
{
        struct smb_direct_transport *st = SMBD_TRANS(t);
        struct smbdirect_socket *sc = st->socket;

        return smbdirect_connection_rdma_xmit(sc, buf, buflen,
                                              desc, desc_len, false);
}

static int smb_direct_rdma_read(struct ksmbd_transport *t,
                                void *buf, unsigned int buflen,
                                struct smbdirect_buffer_descriptor_v1 *desc,
                                unsigned int desc_len)
{
        struct smb_direct_transport *st = SMBD_TRANS(t);
        struct smbdirect_socket *sc = st->socket;

        return smbdirect_connection_rdma_xmit(sc, buf, buflen,
                                              desc, desc_len, true);
}

static void smb_direct_disconnect(struct ksmbd_transport *t)
{
        struct smb_direct_transport *st = SMBD_TRANS(t);
        struct smbdirect_socket *sc = st->socket;

        ksmbd_debug(RDMA, "Disconnecting sc=%p\n", sc);

        free_transport(st);
}

static void smb_direct_shutdown(struct ksmbd_transport *t)
{
        struct smb_direct_transport *st = SMBD_TRANS(t);
        struct smbdirect_socket *sc = st->socket;

        ksmbd_debug(RDMA, "smb-direct shutdown sc=%p\n", sc);

        smbdirect_socket_shutdown(sc);
}

static int smb_direct_new_connection(struct smb_direct_listener *listener,
                                     struct smbdirect_socket *client_sc)
{
        struct smb_direct_transport *t;
        struct task_struct *handler;
        int ret;

        t = alloc_transport(client_sc);
        if (!t) {
                smbdirect_socket_release(client_sc);
                return -ENOMEM;
        }

        handler = kthread_run(ksmbd_conn_handler_loop,
                              KSMBD_TRANS(t)->conn, "ksmbd:r%u",
                              listener->port);
        if (IS_ERR(handler)) {
                ret = PTR_ERR(handler);
                pr_err("Can't start thread\n");
                goto out_err;
        }

        return 0;
out_err:
        free_transport(t);
        return ret;
}

static int smb_direct_listener_kthread_fn(void *p)
{
        struct smb_direct_listener *listener = (struct smb_direct_listener *)p;
        struct smbdirect_socket *client_sc = NULL;

        while (!kthread_should_stop()) {
                struct proto_accept_arg arg = { .err = -EINVAL, };
                long timeo = MAX_SCHEDULE_TIMEOUT;

                if (!listener->socket)
                        break;
                client_sc = smbdirect_socket_accept(listener->socket, timeo, &arg);
                if (!client_sc && arg.err == -EINVAL)
                        break;
                if (!client_sc)
                        continue;

                ksmbd_debug(CONN, "connect success: accepted new connection\n");
                smb_direct_new_connection(listener, client_sc);
        }

        ksmbd_debug(CONN, "releasing socket\n");
        return 0;
}

static void smb_direct_listener_destroy(struct smb_direct_listener *listener)
{
        int ret;

        if (listener->socket)
                smbdirect_socket_shutdown(listener->socket);

        if (listener->thread) {
                ret = kthread_stop(listener->thread);
                if (ret)
                        pr_err("failed to stop forker thread\n");
                listener->thread = NULL;
        }

        if (listener->socket) {
                smbdirect_socket_release(listener->socket);
                listener->socket = NULL;
        }

        listener->port = 0;
}

static int smb_direct_listen(struct smb_direct_listener *listener,
                             int port)
{
        struct net *net = current->nsproxy->net_ns;
        struct task_struct *kthread;
        struct sockaddr_in sin = {
                .sin_family             = AF_INET,
                .sin_addr.s_addr        = htonl(INADDR_ANY),
                .sin_port               = htons(port),
        };
        struct smbdirect_socket_parameters init_params = {};
        struct smbdirect_socket_parameters *sp;
        struct smbdirect_socket *sc;
        u64 port_flags = 0;
        int ret;

        switch (port) {
        case SMB_DIRECT_PORT_IWARP:
                /*
                 * only allow iWarp devices
                 * for port 5445.
                 */
                port_flags |= SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW;
                break;
        case SMB_DIRECT_PORT_INFINIBAND:
                /*
                 * only allow InfiniBand, RoCEv1 or RoCEv2
                 * devices for port 445.
                 *
                 * (Basically don't allow iWarp devices)
                 */
                port_flags |= SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB;
                break;
        default:
                pr_err("unsupported smbdirect port=%d!\n", port);
                return -ENODEV;
        }

        ret = smbdirect_socket_create_kern(net, &sc);
        if (ret) {
                pr_err("smbdirect_socket_create_kern() failed: %d %1pe\n",
                       ret, ERR_PTR(ret));
                return ret;
        }

        /*
         * Create the initial parameters
         */
        sp = &init_params;
        sp->flags |= port_flags;
        sp->negotiate_timeout_msec = SMB_DIRECT_NEGOTIATE_TIMEOUT * 1000;
        sp->initiator_depth = SMB_DIRECT_CM_INITIATOR_DEPTH;
        sp->responder_resources = 1;
        sp->recv_credit_max = smb_direct_receive_credit_max;
        sp->send_credit_target = smb_direct_send_credit_target;
        sp->max_send_size = smb_direct_max_send_size;
        sp->max_fragmented_recv_size = smb_direct_max_fragmented_recv_size;
        sp->max_recv_size = smb_direct_max_receive_size;
        sp->max_read_write_size = smb_direct_max_read_write_size;
        sp->keepalive_interval_msec = SMB_DIRECT_KEEPALIVE_SEND_INTERVAL * 1000;
        sp->keepalive_timeout_msec = SMB_DIRECT_KEEPALIVE_RECV_TIMEOUT * 1000;

        smbdirect_socket_set_logging(sc, NULL,
                                     smb_direct_logging_needed,
                                     smb_direct_logging_vaprintf);
        ret = smbdirect_socket_set_initial_parameters(sc, sp);
        if (ret) {
                pr_err("Failed smbdirect_socket_set_initial_parameters(): %d %1pe\n",
                       ret, ERR_PTR(ret));
                goto err;
        }
        ret = smbdirect_socket_set_kernel_settings(sc, IB_POLL_WORKQUEUE, KSMBD_DEFAULT_GFP);
        if (ret) {
                pr_err("Failed smbdirect_socket_set_kernel_settings(): %d %1pe\n",
                       ret, ERR_PTR(ret));
                goto err;
        }

        ret = smbdirect_socket_bind(sc, (struct sockaddr *)&sin);
        if (ret) {
                pr_err("smbdirect_socket_bind() failed: %d %1pe\n",
                       ret, ERR_PTR(ret));
                goto err;
        }

        ret = smbdirect_socket_listen(sc, 10);
        if (ret) {
                pr_err("Port[%d] smbdirect_socket_listen() failed: %d %1pe\n",
                       port, ret, ERR_PTR(ret));
                goto err;
        }

        listener->port = port;
        listener->socket = sc;

        kthread = kthread_run(smb_direct_listener_kthread_fn,
                              listener,
                              "ksmbd-smbdirect-listener-%u", port);
        if (IS_ERR(kthread)) {
                ret = PTR_ERR(kthread);
                pr_err("Can't start ksmbd listen kthread: %d %1pe\n",
                       ret, ERR_PTR(ret));
                goto err;
        }

        listener->thread = kthread;
        return 0;
err:
        smb_direct_listener_destroy(listener);
        return ret;
}

int ksmbd_rdma_init(void)
{
        int ret;

        smb_direct_ib_listener = smb_direct_iw_listener = (struct smb_direct_listener) {
                .socket = NULL,
        };

        ret = smb_direct_listen(&smb_direct_ib_listener,
                                SMB_DIRECT_PORT_INFINIBAND);
        if (ret) {
                pr_err("Can't listen on InfiniBand/RoCEv1/RoCEv2: %d\n", ret);
                goto err;
        }

        ksmbd_debug(RDMA, "InfiniBand/RoCEv1/RoCEv2 RDMA listener. socket=%p\n",
                    smb_direct_ib_listener.socket);

        ret = smb_direct_listen(&smb_direct_iw_listener,
                                SMB_DIRECT_PORT_IWARP);
        if (ret) {
                pr_err("Can't listen on iWarp: %d\n", ret);
                goto err;
        }

        ksmbd_debug(RDMA, "iWarp RDMA listener. socket=%p\n",
                    smb_direct_iw_listener.socket);

        return 0;
err:
        ksmbd_rdma_stop_listening();
        return ret;
}

void ksmbd_rdma_stop_listening(void)
{
        smb_direct_listener_destroy(&smb_direct_ib_listener);
        smb_direct_listener_destroy(&smb_direct_iw_listener);
}

bool ksmbd_rdma_capable_netdev(struct net_device *netdev)
{
        u8 node_type = smbdirect_netdev_rdma_capable_node_type(netdev);

        return node_type != RDMA_NODE_UNSPECIFIED;
}

static const struct ksmbd_transport_ops ksmbd_smb_direct_transport_ops = {
        .disconnect     = smb_direct_disconnect,
        .shutdown       = smb_direct_shutdown,
        .writev         = smb_direct_writev,
        .read           = smb_direct_read,
        .rdma_read      = smb_direct_rdma_read,
        .rdma_write     = smb_direct_rdma_write,
        .free_transport = smb_direct_free_transport,
};

MODULE_IMPORT_NS("SMBDIRECT");