root/fs/smb/smbdirect/socket.h
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 *   Copyright (c) 2025 Stefan Metzmacher
 */

#ifndef __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_SOCKET_H__
#define __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_SOCKET_H__

#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/kref.h>
#include <linux/mempool.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/completion.h>
#include <rdma/rw.h>

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,
        SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED,
        SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING,
        SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED,
        SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED,
        SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING,
        SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED,
        SMBDIRECT_SOCKET_NEGOTIATE_NEEDED,
        SMBDIRECT_SOCKET_NEGOTIATE_RUNNING,
        SMBDIRECT_SOCKET_NEGOTIATE_FAILED,
        SMBDIRECT_SOCKET_CONNECTED,
        SMBDIRECT_SOCKET_ERROR,
        SMBDIRECT_SOCKET_DISCONNECTING,
        SMBDIRECT_SOCKET_DISCONNECTED,
        SMBDIRECT_SOCKET_DESTROYED
};

static __always_inline
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:
                return "RESOLVE_ADDR_RUNNING";
        case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
                return "RESOLVE_ADDR_FAILED";
        case SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED:
                return "RESOLVE_ROUTE_NEEDED";
        case SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING:
                return "RESOLVE_ROUTE_RUNNING";
        case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED:
                return "RESOLVE_ROUTE_FAILED";
        case SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED:
                return "RDMA_CONNECT_NEEDED";
        case SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING:
                return "RDMA_CONNECT_RUNNING";
        case SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED:
                return "RDMA_CONNECT_FAILED";
        case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
                return "NEGOTIATE_NEEDED";
        case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING:
                return "NEGOTIATE_RUNNING";
        case SMBDIRECT_SOCKET_NEGOTIATE_FAILED:
                return "NEGOTIATE_FAILED";
        case SMBDIRECT_SOCKET_CONNECTED:
                return "CONNECTED";
        case SMBDIRECT_SOCKET_ERROR:
                return "ERROR";
        case SMBDIRECT_SOCKET_DISCONNECTING:
                return "DISCONNECTING";
        case SMBDIRECT_SOCKET_DISCONNECTED:
                return "DISCONNECTED";
        case SMBDIRECT_SOCKET_DESTROYED:
                return "DESTROYED";
        }

        return "<unknown>";
}

/*
 * This can be used with %1pe to print errors as strings or '0'
 * And it avoids warnings like: warn: passing zero to 'ERR_PTR'
 * from smatch -p=kernel --pedantic
 */
static __always_inline
const void * __must_check SMBDIRECT_DEBUG_ERR_PTR(long error)
{
        if (error == 0)
                return NULL;
        return ERR_PTR(error);
}

enum smbdirect_keepalive_status {
        SMBDIRECT_KEEPALIVE_NONE,
        SMBDIRECT_KEEPALIVE_PENDING,
        SMBDIRECT_KEEPALIVE_SENT
};

struct smbdirect_socket {
        enum smbdirect_socket_status status;
        wait_queue_head_t status_wait;
        int first_error;

        /*
         * This points to the workqueues to
         * be used for this socket.
         */
        struct {
                struct workqueue_struct *accept;
                struct workqueue_struct *connect;
                struct workqueue_struct *idle;
                struct workqueue_struct *refill;
                struct workqueue_struct *immediate;
                struct workqueue_struct *cleanup;
        } workqueues;

        struct work_struct disconnect_work;

        /*
         * The reference counts.
         */
        struct {
                /*
                 * This holds the references by the
                 * frontend, typically the smb layer.
                 *
                 * It is typically 1 and a disconnect
                 * will happen if it reaches 0.
                 */
                struct kref disconnect;

                /*
                 * This holds the reference by the
                 * backend, the code that manages
                 * the lifetime of the whole
                 * struct smbdirect_socket,
                 * if this reaches 0 it can will
                 * be freed.
                 *
                 * Can be REFCOUNT_MAX is part
                 * of another structure.
                 *
                 * This is equal or higher than
                 * the disconnect refcount.
                 */
                struct kref destroy;
        } refs;

        /* RDMA related */
        struct {
                struct rdma_cm_id *cm_id;
                /*
                 * The expected event in our current
                 * cm_id->event_handler, all other events
                 * are treated as an error.
                 */
                enum rdma_cm_event_type expected_event;
                /*
                 * This is for iWarp MPA v1
                 */
                bool legacy_iwarp;
        } rdma;

        /* IB verbs related */
        struct {
                struct ib_pd *pd;
                enum ib_poll_context poll_ctx;
                struct ib_cq *send_cq;
                struct ib_cq *recv_cq;

                /*
                 * shortcuts for rdma.cm_id->{qp,device};
                 */
                struct ib_qp *qp;
                struct ib_device *dev;
        } ib;

        struct smbdirect_socket_parameters parameters;

        /*
         * The state for connect/negotiation
         */
        struct {
                spinlock_t lock;
                struct work_struct work;
        } connect;

        /*
         * The state for keepalive and timeout handling
         */
        struct {
                enum smbdirect_keepalive_status keepalive;
                struct work_struct immediate_work;
                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
         */
        struct {
                /*
                 * Memory pools for preallocating
                 * smbdirect_send_io buffers
                 */
                struct {
                        struct kmem_cache *cache;
                        mempool_t *pool;
                        gfp_t gfp_mask;
                } mem;

                /*
                 * This is a coordination for smbdirect_send_batch.
                 *
                 * There's only one possible credit, which means
                 * only one instance is running at a time.
                 */
                struct {
                        atomic_t count;
                        wait_queue_head_t wait_queue;
                } bcredits;

                /*
                 * The local credit state for ib_post_send()
                 */
                struct {
                        atomic_t count;
                        wait_queue_head_t wait_queue;
                } lcredits;

                /*
                 * The remote credit state for the send side
                 */
                struct {
                        atomic_t count;
                        wait_queue_head_t wait_queue;
                } credits;

                /*
                 * The state about posted/pending sends
                 */
                struct {
                        atomic_t count;
                        /*
                         * woken when count reached zero
                         */
                        wait_queue_head_t zero_wait_queue;
                } pending;
        } send_io;

        /*
         * The state for posted receive buffers
         */
        struct {
                /*
                 * The type of PDU we are expecting
                 */
                enum {
                        SMBDIRECT_EXPECT_NEGOTIATE_REQ = 1,
                        SMBDIRECT_EXPECT_NEGOTIATE_REP = 2,
                        SMBDIRECT_EXPECT_DATA_TRANSFER = 3,
                } expected;

                /*
                 * Memory pools for preallocating
                 * smbdirect_recv_io buffers
                 */
                struct {
                        struct kmem_cache *cache;
                        mempool_t *pool;
                        gfp_t gfp_mask;
                } mem;

                /*
                 * The list of free smbdirect_recv_io
                 * structures
                 */
                struct {
                        struct list_head list;
                        spinlock_t lock;
                } free;

                /*
                 * The state for posted recv_io messages
                 * and the refill work struct.
                 */
                struct {
                        atomic_t count;
                        struct work_struct refill_work;
                } posted;

                /*
                 * The credit state for the recv side
                 */
                struct {
                        u16 target;
                        atomic_t available;
                        atomic_t count;
                } credits;

                /*
                 * The list of arrived non-empty smbdirect_recv_io
                 * structures
                 *
                 * This represents the reassembly queue.
                 */
                struct {
                        struct list_head list;
                        spinlock_t lock;
                        wait_queue_head_t wait_queue;
                        /* total data length of reassembly queue */
                        int data_length;
                        int queue_length;
                        /* the offset to first buffer in reassembly queue */
                        int first_entry_offset;
                        /*
                         * Indicate if we have received a full packet on the
                         * connection This is used to identify the first SMBD
                         * packet of a assembled payload (SMB packet) in
                         * reassembly queue so we can return a RFC1002 length to
                         * upper layer to indicate the length of the SMB packet
                         * received
                         */
                        bool full_packet_received;
                } reassembly;
        } recv_io;

        /*
         * The state for Memory registrations on the client
         */
        struct {
                enum ib_mr_type type;

                /*
                 * The list of free smbdirect_mr_io
                 * structures
                 */
                struct {
                        struct list_head list;
                        spinlock_t lock;
                } all;

                /*
                 * The number of available MRs ready for memory registration
                 */
                struct {
                        atomic_t count;
                        wait_queue_head_t wait_queue;
                } ready;

                /*
                 * The number of used MRs
                 */
                struct {
                        atomic_t count;
                } used;
        } mr_io;

        /*
         * The state for RDMA read/write requests on the server
         */
        struct {
                /*
                 * Memory hints for
                 * smbdirect_rw_io structs
                 */
                struct {
                        gfp_t gfp_mask;
                } mem;

                /*
                 * The credit state for the send side
                 */
                struct {
                        /*
                         * The maximum number of rw credits
                         */
                        size_t max;
                        /*
                         * The number of pages per credit
                         */
                        size_t num_pages;
                        atomic_t count;
                        wait_queue_head_t wait_queue;
                } credits;
        } rw_io;

        /*
         * For debug purposes
         */
        struct {
                u64 get_receive_buffer;
                u64 put_receive_buffer;
                u64 enqueue_reassembly_queue;
                u64 dequeue_reassembly_queue;
                u64 send_empty;
        } statistics;

        struct {
                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);
        } logging;
};

static void __smbdirect_socket_disabled_work(struct work_struct *work)
{
        /*
         * Should never be called as disable_[delayed_]work_sync() was used.
         */
        WARN_ON_ONCE(1);
}

static bool __smbdirect_log_needed(struct smbdirect_socket *sc,
                                   void *private_ptr,
                                   unsigned int lvl,
                                   unsigned int cls)
{
        /*
         * Should never be called, the caller should
         * set it's own functions.
         */
        WARN_ON_ONCE(1);
        return false;
}

static void __smbdirect_log_vaprintf(struct smbdirect_socket *sc,
                                     const char *func,
                                     unsigned int line,
                                     void *private_ptr,
                                     unsigned int lvl,
                                     unsigned int cls,
                                     struct va_format *vaf)
{
        /*
         * Should never be called, the caller should
         * set it's own functions.
         */
        WARN_ON_ONCE(1);
}

__printf(6, 7)
static void __smbdirect_log_printf(struct smbdirect_socket *sc,
                                   const char *func,
                                   unsigned int line,
                                   unsigned int lvl,
                                   unsigned int cls,
                                   const char *fmt,
                                   ...);
__maybe_unused
static void __smbdirect_log_printf(struct smbdirect_socket *sc,
                                   const char *func,
                                   unsigned int line,
                                   unsigned int lvl,
                                   unsigned int cls,
                                   const char *fmt,
                                   ...)
{
        struct va_format vaf;
        va_list args;

        va_start(args, fmt);

        vaf.fmt = fmt;
        vaf.va = &args;

        sc->logging.vaprintf(sc,
                             func,
                             line,
                             sc->logging.private_ptr,
                             lvl,
                             cls,
                             &vaf);
        va_end(args);
}

#define ___smbdirect_log_generic(sc, func, line, lvl, cls, fmt, args...) do {   \
        if (sc->logging.needed(sc, sc->logging.private_ptr, lvl, cls)) {        \
                __smbdirect_log_printf(sc, func, line, lvl, cls, fmt, ##args);  \
        }                                                                       \
} while (0)
#define __smbdirect_log_generic(sc, lvl, cls, fmt, args...) \
        ___smbdirect_log_generic(sc, __func__, __LINE__, lvl, cls, fmt, ##args)

#define smbdirect_log_outgoing(sc, lvl, fmt, args...) \
                __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_OUTGOING, fmt, ##args)
#define smbdirect_log_incoming(sc, lvl, fmt, args...) \
                __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_INCOMING, fmt, ##args)
#define smbdirect_log_read(sc, lvl, fmt, args...) \
                __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_READ, fmt, ##args)
#define smbdirect_log_write(sc, lvl, fmt, args...) \
                __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_WRITE, fmt, ##args)
#define smbdirect_log_rdma_send(sc, lvl, fmt, args...) \
                __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_SEND, fmt, ##args)
#define smbdirect_log_rdma_recv(sc, lvl, fmt, args...) \
                __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_RECV, fmt, ##args)
#define smbdirect_log_keep_alive(sc, lvl, fmt, args...) \
                __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_KEEP_ALIVE, fmt, ##args)
#define smbdirect_log_rdma_event(sc, lvl, fmt, args...) \
                __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_EVENT, fmt, ##args)
#define smbdirect_log_rdma_mr(sc, lvl, fmt, args...) \
                __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_MR, fmt, ##args)
#define smbdirect_log_rdma_rw(sc, lvl, fmt, args...) \
                __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_RW, fmt, ##args)
#define smbdirect_log_negotiate(sc, lvl, fmt, args...) \
                __smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_NEGOTIATE, fmt, ##args)

static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
{
        /*
         * This also sets status = SMBDIRECT_SOCKET_CREATED
         */
        BUILD_BUG_ON(SMBDIRECT_SOCKET_CREATED != 0);
        memset(sc, 0, sizeof(*sc));

        init_waitqueue_head(&sc->status_wait);

        sc->workqueues.accept = smbdirect_globals.workqueues.accept;
        sc->workqueues.connect = smbdirect_globals.workqueues.connect;
        sc->workqueues.idle = smbdirect_globals.workqueues.idle;
        sc->workqueues.refill = smbdirect_globals.workqueues.refill;
        sc->workqueues.immediate = smbdirect_globals.workqueues.immediate;
        sc->workqueues.cleanup = smbdirect_globals.workqueues.cleanup;

        INIT_WORK(&sc->disconnect_work, __smbdirect_socket_disabled_work);
        disable_work_sync(&sc->disconnect_work);

        kref_init(&sc->refs.disconnect);
        sc->refs.destroy = (struct kref) KREF_INIT(REFCOUNT_MAX);

        sc->rdma.expected_event = RDMA_CM_EVENT_INTERNAL;

        sc->ib.poll_ctx = IB_POLL_UNBOUND_WORKQUEUE;

        spin_lock_init(&sc->connect.lock);
        INIT_WORK(&sc->connect.work, __smbdirect_socket_disabled_work);
        disable_work_sync(&sc->connect.work);

        INIT_WORK(&sc->idle.immediate_work, __smbdirect_socket_disabled_work);
        disable_work_sync(&sc->idle.immediate_work);
        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);
        init_waitqueue_head(&sc->send_io.bcredits.wait_queue);

        atomic_set(&sc->send_io.lcredits.count, 0);
        init_waitqueue_head(&sc->send_io.lcredits.wait_queue);

        atomic_set(&sc->send_io.credits.count, 0);
        init_waitqueue_head(&sc->send_io.credits.wait_queue);

        atomic_set(&sc->send_io.pending.count, 0);
        init_waitqueue_head(&sc->send_io.pending.zero_wait_queue);

        sc->recv_io.mem.gfp_mask = GFP_KERNEL;

        INIT_LIST_HEAD(&sc->recv_io.free.list);
        spin_lock_init(&sc->recv_io.free.lock);

        atomic_set(&sc->recv_io.posted.count, 0);
        INIT_WORK(&sc->recv_io.posted.refill_work, __smbdirect_socket_disabled_work);
        disable_work_sync(&sc->recv_io.posted.refill_work);

        atomic_set(&sc->recv_io.credits.available, 0);
        atomic_set(&sc->recv_io.credits.count, 0);

        INIT_LIST_HEAD(&sc->recv_io.reassembly.list);
        spin_lock_init(&sc->recv_io.reassembly.lock);
        init_waitqueue_head(&sc->recv_io.reassembly.wait_queue);

        sc->rw_io.mem.gfp_mask = GFP_KERNEL;
        atomic_set(&sc->rw_io.credits.count, 0);
        init_waitqueue_head(&sc->rw_io.credits.wait_queue);

        spin_lock_init(&sc->mr_io.all.lock);
        INIT_LIST_HEAD(&sc->mr_io.all.list);
        atomic_set(&sc->mr_io.ready.count, 0);
        init_waitqueue_head(&sc->mr_io.ready.wait_queue);
        atomic_set(&sc->mr_io.used.count, 0);

        sc->logging.private_ptr = NULL;
        sc->logging.needed = __smbdirect_log_needed;
        sc->logging.vaprintf = __smbdirect_log_vaprintf;
}

#define __SMBDIRECT_CHECK_STATUS_FAILED(__sc, __expected_status, __error_cmd, __unexpected_cmd) ({ \
        bool __failed = false; \
        if (unlikely((__sc)->first_error)) { \
                __failed = true; \
                __error_cmd \
        } else if (unlikely((__sc)->status != (__expected_status))) { \
                __failed = true; \
                __unexpected_cmd \
        } \
        __failed; \
})

#define __SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, __unexpected_cmd) \
        __SMBDIRECT_CHECK_STATUS_FAILED(__sc, __expected_status, \
        { \
                const struct sockaddr_storage *__src = NULL; \
                const struct sockaddr_storage *__dst = NULL; \
                if ((__sc)->rdma.cm_id) { \
                        __src = &(__sc)->rdma.cm_id->route.addr.src_addr; \
                        __dst = &(__sc)->rdma.cm_id->route.addr.dst_addr; \
                } \
                smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO, \
                        "expected[%s] != %s first_error=%1pe local=%pISpsfc remote=%pISpsfc\n", \
                        smbdirect_socket_status_string(__expected_status), \
                        smbdirect_socket_status_string((__sc)->status), \
                        SMBDIRECT_DEBUG_ERR_PTR((__sc)->first_error), \
                        __src, __dst); \
        }, \
        { \
                const struct sockaddr_storage *__src = NULL; \
                const struct sockaddr_storage *__dst = NULL; \
                if ((__sc)->rdma.cm_id) { \
                        __src = &(__sc)->rdma.cm_id->route.addr.src_addr; \
                        __dst = &(__sc)->rdma.cm_id->route.addr.dst_addr; \
                } \
                smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR, \
                        "expected[%s] != %s first_error=%1pe local=%pISpsfc remote=%pISpsfc\n", \
                        smbdirect_socket_status_string(__expected_status), \
                        smbdirect_socket_status_string((__sc)->status), \
                        SMBDIRECT_DEBUG_ERR_PTR((__sc)->first_error), \
                        __src, __dst); \
                WARN_ONCE(1, \
                        "expected[%s] != %s first_error=%1pe local=%pISpsfc remote=%pISpsfc\n", \
                        smbdirect_socket_status_string(__expected_status), \
                        smbdirect_socket_status_string((__sc)->status), \
                        SMBDIRECT_DEBUG_ERR_PTR((__sc)->first_error), \
                        __src, __dst); \
                __unexpected_cmd \
        })

#define SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status) \
        __SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, /* nothing */)

#ifndef __SMBDIRECT_SOCKET_DISCONNECT
#define __SMBDIRECT_SOCKET_DISCONNECT(__sc) \
                smbdirect_socket_schedule_cleanup(__sc, -ECONNABORTED)
#endif /* ! __SMBDIRECT_SOCKET_DISCONNECT */

#define SMBDIRECT_CHECK_STATUS_DISCONNECT(__sc, __expected_status) \
        __SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, \
                __SMBDIRECT_SOCKET_DISCONNECT(__sc);)

struct smbdirect_send_io {
        struct smbdirect_socket *socket;
        struct ib_cqe cqe;

        /*
         * The SGE entries for this work request
         *
         * The first points to the packet header
         */
#define SMBDIRECT_SEND_IO_MAX_SGE 6
        size_t num_sge;
        struct ib_sge sge[SMBDIRECT_SEND_IO_MAX_SGE];

        /*
         * Link to the list of sibling smbdirect_send_io
         * messages.
         */
        struct list_head sibling_list;
        struct ib_send_wr wr;

        /* SMBD packet header follows this structure */
        u8 packet[];
};

struct smbdirect_send_batch {
        /*
         * List of smbdirect_send_io messages
         */
        struct list_head msg_list;
        /*
         * Number of list entries
         */
        size_t wr_cnt;

        /*
         * Possible remote key invalidation state
         */
        bool need_invalidate_rkey;
        u32 remote_key;

        int credit;
};

struct smbdirect_recv_io {
        struct smbdirect_socket *socket;
        struct ib_cqe cqe;

        /*
         * For now we only use a single SGE
         * as we have just one large buffer
         * per posted recv.
         */
#define SMBDIRECT_RECV_IO_MAX_SGE 1
        struct ib_sge sge;

        /* Link to free or reassembly list */
        struct list_head list;

        /* Indicate if this is the 1st packet of a payload */
        bool first_segment;

        /* SMBD packet header and payload follows this structure */
        u8 packet[];
};

enum smbdirect_mr_state {
        SMBDIRECT_MR_READY,
        SMBDIRECT_MR_REGISTERED,
        SMBDIRECT_MR_INVALIDATED,
        SMBDIRECT_MR_ERROR,
        SMBDIRECT_MR_DISABLED
};

struct smbdirect_mr_io {
        struct smbdirect_socket *socket;
        struct ib_cqe cqe;

        /*
         * We can have up to two references:
         * 1. by the connection
         * 2. by the registration
         */
        struct kref kref;
        struct mutex mutex;

        struct list_head list;

        enum smbdirect_mr_state state;
        struct ib_mr *mr;
        struct sg_table sgt;
        enum dma_data_direction dir;
        union {
                struct ib_reg_wr wr;
                struct ib_send_wr inv_wr;
        };

        bool need_invalidate;
        struct completion invalidate_done;
};

struct smbdirect_rw_io {
        struct smbdirect_socket *socket;
        struct ib_cqe cqe;

        struct list_head list;

        int error;
        struct completion *completion;

        struct rdma_rw_ctx rdma_ctx;
        struct sg_table sgt;
        struct scatterlist sg_list[];
};

static inline size_t smbdirect_get_buf_page_count(const void *buf, size_t size)
{
        return DIV_ROUND_UP((uintptr_t)buf + size, PAGE_SIZE) -
                (uintptr_t)buf / PAGE_SIZE;
}

/*
 * Maximum number of retries on data transfer operations
 */
#define SMBDIRECT_RDMA_CM_RETRY 6
/*
 * No need to retry on Receiver Not Ready since SMB_DIRECT manages credits
 */
#define SMBDIRECT_RDMA_CM_RNR_RETRY 0

#endif /* __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_SOCKET_H__ */