blob: 589024c7190f066c9d8e4760cebbb75b794ad717 [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/*
* Copyright (c) 2018-2025 Advanced Micro Devices, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <errno.h>
#include <sys/mman.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <infiniband/verbs.h>
#include <linux/types.h>
#include <util/util.h>
#include "ionic_fw.h"
#include "ionic.h"
static void ionic_reserve_sync_cq(struct ionic_ctx *ctx, struct ionic_cq *cq);
static int ionic_poll_cq(struct ibv_cq *ibcq, int nwc, struct ibv_wc *wc);
#ifdef __x86_64__
static bool ionic_have_movdir64b;
#ifndef bit_MOVDIR64B
#define bit_MOVDIR64B BIT(28)
#endif
static inline bool have_movdir64b(void)
{
#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4))
unsigned int ax, bx, cx, dx, count = 0, leaf = 7;
/* Return highest supported cpuid input value. */
__asm__ volatile ("cpuid\n\t"
: "=a" (ax), "=b" (bx), "=c" (cx), "=d" (dx)
: "0" (leaf));
if (ax == 0 || ax < leaf)
return 0;
__asm__ volatile ("cpuid\n\t"
: "=a" (ax), "=b" (bx), "=c" (cx), "=d" (dx)
: "0" (leaf), "2" (count));
return cx & bit_MOVDIR64B ? 1 : 0;
#else
return 0;
#endif
}
static __attribute__((constructor))
void init_ionic_mmio_memcpy(void)
{
ionic_have_movdir64b = have_movdir64b();
}
static inline void ionic_movdir64b_mmio_memcpy_x64_64(void *dst, const void *src)
{
const struct { char _[64]; } *__src = src;
struct { char _[64]; } *__dst = dst;
/*
* Caller must guarantee:
* assert(((uintptr_t)dst) % 64 == 0);
* assert(((uintptr_t)src) % 64 == 0);
*/
/*
* MOVDIR64B %(rdx), rax.
*
* Both __src and __dst must be memory constraints in order to tell the
* compiler that no other memory accesses should be reordered around
* this one.
*
* Also, both must be supplied as lvalues because this tells
* the compiler what the object is (its size) the instruction accesses.
* I.e., not the pointers but what they point to, thus the deref'ing '*'.
*/
asm volatile(".byte 0x66, 0x0f, 0x38, 0xf8, 0x02"
: "+m" (*__dst)
: "m" (*__src), "a" (__dst), "d" (__src));
}
static inline void ionic_movdir64b_mmio_memcpy_x64(void *dst, const void *src,
size_t bytecnt)
{
/*
* Caller must guarantee:
* assert(bytecnt != 0);
* assert((bytecnt % 64) == 0);
* assert(((uintptr_t)dst) % 64 == 0);
* assert(((uintptr_t)src) % 64 == 0);
*/
do {
ionic_movdir64b_mmio_memcpy_x64_64(dst, src);
bytecnt -= 64;
src += 64;
dst += 64;
} while (bytecnt > 0);
}
#endif /* #if defined(__x86_64__) */
static inline void ionic_mmio_memcpy_x64_64(void *dst, const void *src)
{
#ifdef __x86_64__
if (likely(ionic_have_movdir64b))
ionic_movdir64b_mmio_memcpy_x64_64(dst, src);
else
#endif
mmio_memcpy_x64(dst, src, 64);
}
static inline void ionic_mmio_memcpy_x64(void *dst, const void *src, size_t bytecnt)
{
#ifdef __x86_64__
if (likely(ionic_have_movdir64b))
ionic_movdir64b_mmio_memcpy_x64(dst, src, bytecnt);
else
#endif
mmio_memcpy_x64(dst, src, bytecnt);
}
#define ionic_cq_spin_lock(cq) do { \
if (unlikely(!(cq)->lockfree)) \
pthread_spin_lock(&(cq)->lock); \
} while (0)
#define ionic_cq_spin_trylock(cq) \
(likely((cq)->lockfree) ? \
0 : pthread_spin_trylock(&(cq)->lock))
#define ionic_cq_spin_unlock(cq) do { \
if (unlikely(!(cq)->lockfree)) \
pthread_spin_unlock(&(cq)->lock); \
} while (0)
#define ionic_rq_spin_lock(qp) do { \
if (unlikely(!(qp)->lockfree)) \
pthread_spin_lock(&(qp)->rq.lock); \
} while (0)
#define ionic_rq_spin_unlock(qp) do { \
if (unlikely(!(qp)->lockfree)) \
pthread_spin_unlock(&(qp)->rq.lock); \
} while (0)
#define ionic_sq_spin_lock(qp) do { \
if (unlikely(!(qp)->lockfree)) \
pthread_spin_lock(&(qp)->sq.lock); \
} while (0)
#define ionic_sq_spin_unlock(qp) do { \
if (unlikely(!(qp)->lockfree)) \
pthread_spin_unlock(&(qp)->sq.lock); \
} while (0)
#define IONIC_OP(version, opname) \
((version) < 2 ? IONIC_V1_OP_##opname : IONIC_V2_OP_##opname)
static int ionic_query_device_ex(struct ibv_context *ibctx,
const struct ibv_query_device_ex_input *input,
struct ibv_device_attr_ex *ex,
size_t ex_size)
{
struct ibv_device_attr *dev_attr = &ex->orig_attr;
struct ib_uverbs_ex_query_device_resp resp = {};
size_t resp_size = sizeof(resp);
int rc;
rc = ibv_cmd_query_device_any(ibctx, input, ex, ex_size, &resp, &resp_size);
if (rc)
return rc;
rc = ibv_read_sysfs_file(ibctx->device->ibdev_path, "fw_ver",
dev_attr->fw_ver, sizeof(dev_attr->fw_ver));
if (rc < 0)
dev_attr->fw_ver[0] = 0;
return 0;
}
static int ionic_query_port(struct ibv_context *ibctx, uint8_t port,
struct ibv_port_attr *port_attr)
{
struct ibv_query_port req = {};
return ibv_cmd_query_port(ibctx, port, port_attr,
&req, sizeof(req));
}
static struct ibv_pd *ionic_alloc_parent_domain(struct ibv_context *context,
struct ibv_parent_domain_init_attr *attr)
{
struct ionic_pd *pd, *init_pd;
struct ibv_pd *root_ibpd;
int rc;
if (ibv_check_alloc_parent_domain(attr)) {
rc = errno;
goto err_pd;
}
if (!check_comp_mask(attr->comp_mask,
IBV_PARENT_DOMAIN_INIT_ATTR_ALLOCATORS |
IBV_PARENT_DOMAIN_INIT_ATTR_PD_CONTEXT)) {
rc = EINVAL;
goto err_pd;
}
pd = calloc(1, sizeof(*pd));
if (!pd) {
rc = errno;
goto err_pd;
}
init_pd = to_ionic_pd(attr->pd);
root_ibpd = ionic_root_ibpd(init_pd);
ibv_initialize_parent_domain(&pd->ibpd, root_ibpd);
pd->root_ibpd = root_ibpd;
pd->udma_mask = init_pd->udma_mask;
pd->sq_cmb = init_pd->sq_cmb;
pd->rq_cmb = init_pd->rq_cmb;
if (attr->comp_mask & IBV_PARENT_DOMAIN_INIT_ATTR_ALLOCATORS) {
pd->alloc = attr->alloc;
pd->free = attr->free;
}
if (attr->comp_mask & IBV_PARENT_DOMAIN_INIT_ATTR_PD_CONTEXT)
pd->pd_context = attr->pd_context;
return &pd->ibpd;
err_pd:
errno = rc;
return NULL;
}
static struct ibv_pd *ionic_alloc_pd(struct ibv_context *ibctx)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibctx);
struct ionic_pd *pd;
struct ibv_alloc_pd req = {};
struct ib_uverbs_alloc_pd_resp resp = {};
int rc;
pd = calloc(1, sizeof(*pd));
if (!pd) {
rc = errno;
goto err_pd;
}
pd->root_ibpd = &pd->ibpd;
rc = ibv_cmd_alloc_pd(ibctx, &pd->ibpd,
&req, sizeof(req),
&resp, sizeof(resp));
if (rc)
goto err_cmd;
pd->udma_mask = ionic_ctx_udma_mask(ctx);
pd->sq_cmb = IONIC_CMB_ENABLE;
if (ctx->sq_expdb)
pd->sq_cmb |= IONIC_CMB_EXPDB;
pd->rq_cmb = IONIC_CMB_ENABLE;
if (ctx->rq_expdb)
pd->rq_cmb |= IONIC_CMB_EXPDB;
return &pd->ibpd;
err_cmd:
free(pd);
err_pd:
errno = rc;
return NULL;
}
static int ionic_dealloc_pd(struct ibv_pd *ibpd)
{
struct ionic_pd *pd = to_ionic_pd(ibpd);
int rc;
if (&pd->ibpd == pd->root_ibpd) {
rc = ibv_cmd_dealloc_pd(&pd->ibpd);
if (rc)
return rc;
}
free(pd);
return 0;
}
static struct ibv_mr *ionic_reg_dmabuf_mr(struct ibv_pd *ibpd, uint64_t offset,
size_t length, uint64_t iova,
int fd, int access)
{
struct ibv_pd *root_ibpd = to_ionic_root_ibpd(ibpd);
struct verbs_mr *vmr;
int rc;
vmr = calloc(1, sizeof(*vmr));
if (!vmr) {
rc = errno;
goto err_mr;
}
rc = ibv_cmd_reg_dmabuf_mr(root_ibpd, offset, length, iova, fd, access, vmr, NULL);
if (rc)
goto err_cmd;
return &vmr->ibv_mr;
err_cmd:
free(vmr);
err_mr:
errno = rc;
return NULL;
}
static struct ibv_mr *ionic_reg_mr(struct ibv_pd *ibpd,
void *addr,
size_t len,
uint64_t hca_va,
int access)
{
struct ibv_pd *root_ibpd = to_ionic_root_ibpd(ibpd);
struct verbs_mr *vmr;
struct ib_uverbs_reg_mr_resp resp = {};
struct ibv_reg_mr req = {};
int rc;
vmr = calloc(1, sizeof(*vmr));
if (!vmr) {
rc = errno;
goto err_mr;
}
rc = ibv_cmd_reg_mr(root_ibpd, addr, len,
hca_va,
access, vmr,
&req, sizeof(req),
&resp, sizeof(resp));
if (rc)
goto err_cmd;
return &vmr->ibv_mr;
err_cmd:
free(vmr);
err_mr:
errno = rc;
return NULL;
}
static int ionic_dereg_mr(struct verbs_mr *vmr)
{
int rc;
rc = ibv_cmd_dereg_mr(vmr);
if (rc)
return rc;
free(vmr);
return 0;
}
static void ionic_vcq_cq_deinit(struct ionic_cq *cq)
{
if (!cq->vcq)
return;
cq->vcq = NULL;
ionic_queue_destroy(&cq->q);
pthread_spin_destroy(&cq->lock);
}
static int ionic_vcq_cq_init1(struct ionic_ctx *ctx,
struct ionic_vcq *vcq,
struct ionic_cq *cq,
struct ibv_cq_init_attr_ex *ex,
struct ionic_pd *pd,
struct ionic_qdesc *req_cq)
{
int rc;
cq->vcq = vcq;
cq->lockfree = false;
pthread_spin_init(&cq->lock, PTHREAD_PROCESS_PRIVATE);
list_head_init(&cq->poll_sq);
list_head_init(&cq->poll_rq);
list_head_init(&cq->flush_sq);
list_head_init(&cq->flush_rq);
rc = ionic_queue_init(&cq->q, pd, IONIC_PD_TAG_CQ,
ctx->pg_shift, ex->cqe + IONIC_CQ_GRACE,
sizeof(struct ionic_v1_cqe));
if (rc)
goto err_queue;
cq->color = true;
cq->reserve = cq->q.mask;
cq->reserve_pending = 0;
req_cq->addr = (uintptr_t)cq->q.ptr;
req_cq->size = cq->q.size;
req_cq->mask = cq->q.mask;
req_cq->depth_log2 = cq->q.depth_log2;
req_cq->stride_log2 = cq->q.stride_log2;
return 0;
err_queue:
pthread_spin_destroy(&cq->lock);
return rc;
}
static void ionic_vcq_cq_init2(struct ionic_cq *cq, uint32_t resp_cqid)
{
cq->cqid = resp_cqid;
ionic_queue_dbell_init(&cq->q, cq->cqid);
}
/*
* NOTE: ionic_start_poll, ionic_next_poll and ionic_end_poll provide a
* minimal implementations of the ibv_cq_ex polling mechanism, sufficient to
* make functional tests pass but not performant. The intention of the API is
* that start_poll should take any required locks, next_poll should assume the
* locks are held and end_poll should release them.
*/
static int ionic_start_poll(struct ibv_cq_ex *ibcq_ex, struct ibv_poll_cq_attr *attr)
{
struct ibv_cq *ibcq = ibv_cq_ex_to_cq(ibcq_ex);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
int rc = ionic_poll_cq(ibcq, 1, &vcq->cur_wc);
if (rc != 1) /* no completions ready or poll failed */
return (rc == 0) ? ENOENT : rc;
ibcq_ex->wr_id = vcq->cur_wc.wr_id;
ibcq_ex->status = vcq->cur_wc.status;
return 0;
}
static int ionic_next_poll(struct ibv_cq_ex *ibcq_ex)
{
return ionic_start_poll(ibcq_ex, NULL);
}
static void ionic_end_poll(struct ibv_cq_ex *ibcq_ex)
{
/* nothing to do here */
}
static enum ibv_wc_opcode ionic_wc_read_opcode(struct ibv_cq_ex *ibcq_ex)
{
struct ibv_cq *ibcq = ibv_cq_ex_to_cq(ibcq_ex);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
return vcq->cur_wc.opcode;
}
static uint32_t ionic_wc_read_vendor_err(struct ibv_cq_ex *ibcq_ex)
{
struct ibv_cq *ibcq = ibv_cq_ex_to_cq(ibcq_ex);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
return vcq->cur_wc.vendor_err;
}
static uint32_t ionic_wc_read_byte_len(struct ibv_cq_ex *ibcq_ex)
{
struct ibv_cq *ibcq = ibv_cq_ex_to_cq(ibcq_ex);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
return vcq->cur_wc.byte_len;
}
static __be32 ionic_wc_read_imm_data(struct ibv_cq_ex *ibcq_ex)
{
struct ibv_cq *ibcq = ibv_cq_ex_to_cq(ibcq_ex);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
return vcq->cur_wc.imm_data;
}
static uint32_t ionic_wc_read_qp_num(struct ibv_cq_ex *ibcq_ex)
{
struct ibv_cq *ibcq = ibv_cq_ex_to_cq(ibcq_ex);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
return vcq->cur_wc.qp_num;
}
static uint32_t ionic_wc_read_src_qp(struct ibv_cq_ex *ibcq_ex)
{
struct ibv_cq *ibcq = ibv_cq_ex_to_cq(ibcq_ex);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
return vcq->cur_wc.src_qp;
}
static unsigned int ionic_wc_read_wc_flags(struct ibv_cq_ex *ibcq_ex)
{
struct ibv_cq *ibcq = ibv_cq_ex_to_cq(ibcq_ex);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
return vcq->cur_wc.wc_flags;
}
static uint8_t ionic_wc_read_sl(struct ibv_cq_ex *ibcq_ex)
{
struct ibv_cq *ibcq = ibv_cq_ex_to_cq(ibcq_ex);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
return vcq->cur_wc.sl;
}
static uint32_t ionic_wc_read_slid(struct ibv_cq_ex *ibcq_ex)
{
struct ibv_cq *ibcq = ibv_cq_ex_to_cq(ibcq_ex);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
return vcq->cur_wc.slid;
}
static uint8_t ionic_wc_read_dlid_path_bits(struct ibv_cq_ex *ibcq_ex)
{
struct ibv_cq *ibcq = ibv_cq_ex_to_cq(ibcq_ex);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
return vcq->cur_wc.dlid_path_bits;
}
static struct ibv_cq_ex *ionic_create_cq_ex(struct ibv_context *ibctx,
struct ibv_cq_init_attr_ex *ex)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibctx);
struct ionic_pd *pd = NULL;
struct ionic_vcq *vcq;
struct uionic_cq req = {};
struct uionic_cq_resp resp = {};
int cq_i, rc;
if (ex->wc_flags & ~IONIC_CQ_SUPPORTED_WC_FLAGS) {
rc = ENOTSUP;
goto err;
}
if (ex->cqe < 1 || ex->cqe + IONIC_CQ_GRACE > 0xffff) {
rc = EINVAL;
goto err;
}
vcq = calloc(1, sizeof(*vcq));
if (!vcq) {
rc = ENOMEM;
goto err;
}
if (ex->comp_mask & IBV_CQ_INIT_ATTR_MASK_PD) {
pd = to_ionic_pd(ex->parent_domain);
vcq->udma_mask = pd->udma_mask;
} else {
vcq->udma_mask = ionic_ctx_udma_mask(ctx);
}
for (cq_i = 0; cq_i < ctx->udma_count; ++cq_i) {
if (!(vcq->udma_mask & BIT(cq_i)))
continue;
rc = ionic_vcq_cq_init1(ctx, vcq, &vcq->cq[cq_i], ex, pd, &req.cq[cq_i]);
if (rc)
goto err_cq;
}
req.udma_mask = vcq->udma_mask;
rc = ibv_cmd_create_cq(ibctx, ex->cqe, ex->channel,
ex->comp_vector, &vcq->vcq.cq,
&req.ibv_cmd, sizeof(req),
&resp.ibv_resp, sizeof(resp));
if (rc)
goto err_cmd;
if (resp.udma_mask != vcq->udma_mask) {
rc = EINVAL;
goto err_udma;
}
for (cq_i = 0; cq_i < ctx->udma_count; ++cq_i) {
if (!(vcq->udma_mask & BIT(cq_i)))
continue;
ionic_vcq_cq_init2(&vcq->cq[cq_i], resp.cqid[cq_i]);
}
vcq->vcq.cq_ex.start_poll = ionic_start_poll;
vcq->vcq.cq_ex.next_poll = ionic_next_poll;
vcq->vcq.cq_ex.end_poll = ionic_end_poll;
vcq->vcq.cq_ex.read_opcode = ionic_wc_read_opcode;
vcq->vcq.cq_ex.read_vendor_err = ionic_wc_read_vendor_err;
vcq->vcq.cq_ex.read_wc_flags = ionic_wc_read_wc_flags;
if (ex->wc_flags & IBV_WC_EX_WITH_BYTE_LEN)
vcq->vcq.cq_ex.read_byte_len = ionic_wc_read_byte_len;
if (ex->wc_flags & IBV_WC_EX_WITH_IMM)
vcq->vcq.cq_ex.read_imm_data = ionic_wc_read_imm_data;
if (ex->wc_flags & IBV_WC_EX_WITH_QP_NUM)
vcq->vcq.cq_ex.read_qp_num = ionic_wc_read_qp_num;
if (ex->wc_flags & IBV_WC_EX_WITH_SRC_QP)
vcq->vcq.cq_ex.read_src_qp = ionic_wc_read_src_qp;
if (ex->wc_flags & IBV_WC_EX_WITH_SL)
vcq->vcq.cq_ex.read_sl = ionic_wc_read_sl;
if (ex->wc_flags & IBV_WC_EX_WITH_SLID)
vcq->vcq.cq_ex.read_slid = ionic_wc_read_slid;
if (ex->wc_flags & IBV_WC_EX_WITH_DLID_PATH_BITS)
vcq->vcq.cq_ex.read_dlid_path_bits = ionic_wc_read_dlid_path_bits;
return &vcq->vcq.cq_ex;
err_udma:
(void)ibv_cmd_destroy_cq(&vcq->vcq.cq);
err_cmd:
while (cq_i) {
--cq_i;
if (!(vcq->udma_mask & BIT(cq_i)))
continue;
ionic_vcq_cq_deinit(&vcq->cq[cq_i]);
err_cq:
;
}
free(vcq);
err:
errno = rc;
return NULL;
}
static struct ibv_cq *ionic_create_cq(struct ibv_context *ibctx, int ncqe,
struct ibv_comp_channel *channel,
int vec)
{
struct ibv_cq_init_attr_ex ex = {
.cqe = ncqe,
.channel = channel,
.comp_vector = vec,
};
struct ibv_cq_ex *ibcq;
if (ncqe < 0) {
errno = EINVAL;
return NULL;
}
ibcq = ionic_create_cq_ex(ibctx, &ex);
return ibv_cq_ex_to_cq(ibcq);
}
static int ionic_destroy_cq(struct ibv_cq *ibcq)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibcq->context);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
int cq_i, rc;
rc = ibv_cmd_destroy_cq(ibcq);
if (rc)
return rc;
for (cq_i = ctx->udma_count; cq_i;) {
--cq_i;
if (!(vcq->udma_mask & BIT(cq_i)))
continue;
ionic_vcq_cq_deinit(&vcq->cq[cq_i]);
}
free(vcq);
return 0;
}
static int ionic_flush_recv(struct ionic_qp *qp, struct ibv_wc *wc)
{
struct ionic_rq_meta *meta;
struct ionic_v1_wqe *wqe;
struct ionic_ctx *ctx;
if (!qp->rq.flush)
return 0;
if (ionic_queue_empty(&qp->rq.queue))
return 0;
wqe = ionic_queue_at_cons(&qp->rq.queue);
ctx = to_ionic_ctx(qp->vqp.qp.context);
/* wqe_id must be a valid queue index */
if (unlikely(wqe->base.wqe_id >> qp->rq.queue.depth_log2)) {
verbs_err(&ctx->vctx, "invalid id %#lx",
(unsigned long)wqe->base.wqe_id);
return -EIO;
}
/* wqe_id must indicate a request that is outstanding */
meta = &qp->rq.meta[wqe->base.wqe_id];
if (unlikely(meta->next != IONIC_META_POSTED)) {
verbs_err(&ctx->vctx, "wqe not posted %#lx",
(unsigned long)wqe->base.wqe_id);
return -EIO;
}
ionic_queue_consume(&qp->rq.queue);
memset(wc, 0, sizeof(*wc));
wc->status = IBV_WC_WR_FLUSH_ERR;
wc->wr_id = meta->wrid;
wc->qp_num = qp->qpid;
meta->next = qp->rq.meta_head;
qp->rq.meta_head = meta;
return 1;
}
static int ionic_flush_recv_many(struct ionic_qp *qp,
struct ibv_wc *wc, int nwc)
{
int rc = 0, npolled = 0;
while (npolled < nwc) {
rc = ionic_flush_recv(qp, wc + npolled);
if (rc <= 0)
break;
npolled += rc;
}
return npolled ?: rc;
}
static int ionic_flush_send(struct ionic_qp *qp, struct ibv_wc *wc)
{
struct ionic_sq_meta *meta;
if (!qp->sq.flush)
return 0;
if (ionic_queue_empty(&qp->sq.queue))
return 0;
meta = &qp->sq.meta[qp->sq.queue.cons];
ionic_queue_consume(&qp->sq.queue);
memset(wc, 0, sizeof(*wc));
wc->status = IBV_WC_WR_FLUSH_ERR;
wc->wr_id = meta->wrid;
wc->qp_num = qp->qpid;
return 1;
}
static int ionic_flush_send_many(struct ionic_qp *qp,
struct ibv_wc *wc, int nwc)
{
int rc = 0, npolled = 0;
while (npolled < nwc) {
rc = ionic_flush_send(qp, wc + npolled);
if (rc <= 0)
break;
npolled += rc;
}
return npolled ?: rc;
}
static int ionic_poll_recv(struct ionic_ctx *ctx, struct ionic_cq *cq,
struct ionic_qp *cqe_qp, struct ionic_v1_cqe *cqe,
struct ibv_wc *wc)
{
struct ionic_qp *qp = NULL;
struct ionic_rq_meta *meta;
uint32_t src_qpn, st_len;
uint16_t vlan_tag;
uint8_t op;
if (cqe_qp->rq.flush)
return 0;
qp = cqe_qp;
st_len = be32toh(cqe->status_length);
/* ignore wqe_id in case of flush error */
if (ionic_v1_cqe_error(cqe) && st_len == IONIC_STS_WQE_FLUSHED_ERR) {
cqe_qp->rq.flush = true;
cq->flush = true;
list_del(&cqe_qp->cq_flush_rq);
list_add_tail(&cq->flush_rq, &cqe_qp->cq_flush_rq);
/* posted recvs (if any) flushed by ionic_flush_recv */
return 0;
}
/* there had better be something in the recv queue to complete */
if (ionic_queue_empty(&qp->rq.queue)) {
verbs_err(&ctx->vctx, "rq is empty %u", qp->qpid);
return -EIO;
}
/* wqe_id must be a valid queue index */
if (unlikely(cqe->recv.wqe_id >> qp->rq.queue.depth_log2)) {
verbs_err(&ctx->vctx, "invalid id %#lx",
(unsigned long)cqe->recv.wqe_id);
return -EIO;
}
/* wqe_id must indicate a request that is outstanding */
meta = &qp->rq.meta[qp->rq.meta_idx[cqe->recv.wqe_id]];
if (unlikely(meta->next != IONIC_META_POSTED)) {
verbs_err(&ctx->vctx, "wqe is not posted for idx %lu meta_idx %u qpid %u rq.prod %u rq.cons %u cqid %u",
(unsigned long)cqe->recv.wqe_id,
qp->rq.meta_idx[cqe->recv.wqe_id],
qp->qpid, qp->rq.queue.prod,
qp->rq.queue.cons, cq->cqid);
return -EIO;
}
meta->next = qp->rq.meta_head;
qp->rq.meta_head = meta;
memset(wc, 0, sizeof(*wc));
wc->wr_id = meta->wrid;
wc->qp_num = cqe_qp->qpid;
if (ionic_v1_cqe_error(cqe)) {
wc->vendor_err = st_len;
wc->status = ionic_to_ibv_status(st_len);
cqe_qp->rq.flush = true;
cq->flush = true;
list_del(&cqe_qp->cq_flush_rq);
list_add_tail(&cq->flush_rq, &cqe_qp->cq_flush_rq);
verbs_err(&ctx->vctx, "cqe with error %u for %#x (recv), qpid %u cqid %u",
wc->status, be32toh(cqe->send.msg_msn),
qp->qpid, cq->cqid);
goto out;
}
wc->vendor_err = 0;
wc->status = IBV_WC_SUCCESS;
src_qpn = be32toh(cqe->recv.src_qpn_op);
op = src_qpn >> IONIC_V1_CQE_RECV_OP_SHIFT;
src_qpn &= IONIC_V1_CQE_RECV_QPN_MASK;
op &= IONIC_V1_CQE_RECV_OP_MASK;
wc->opcode = IBV_WC_RECV;
switch (op) {
case IONIC_V1_CQE_RECV_OP_RDMA_IMM:
wc->opcode = IBV_WC_RECV_RDMA_WITH_IMM;
SWITCH_FALLTHROUGH;
case IONIC_V1_CQE_RECV_OP_SEND_IMM:
wc->wc_flags |= IBV_WC_WITH_IMM;
wc->imm_data = cqe->recv.imm_data_rkey; /* be32 in wc */
break;
case IONIC_V1_CQE_RECV_OP_SEND_INV:
wc->wc_flags |= IBV_WC_WITH_INV;
wc->invalidated_rkey = be32toh(cqe->recv.imm_data_rkey);
}
wc->byte_len = st_len;
wc->src_qp = src_qpn;
if (qp->vqp.qp.qp_type == IBV_QPT_UD) {
wc->wc_flags |= IBV_WC_GRH;
/* vlan_tag in cqe will be valid from dpath even if no vlan */
vlan_tag = be16toh(cqe->recv.vlan_tag);
wc->sl = vlan_tag >> 13; /* 802.1q PCP */
}
wc->pkey_index = 0;
out:
verbs_debug(&ctx->vctx, "poll cq %u qp %u cons %u st %u wrid %" PRIx64 " op %u len %u",
cq->cqid, qp->qpid, qp->rq.queue.cons, wc->status,
meta->wrid, wc->opcode, st_len);
cq->reserve_pending++;
ionic_queue_consume(&qp->rq.queue);
return 1;
}
static bool ionic_peek_send(struct ionic_qp *qp)
{
struct ionic_sq_meta *meta;
if (qp->sq.flush)
return 0;
/* completed all send queue requests? */
if (ionic_queue_empty(&qp->sq.queue))
return false;
meta = &qp->sq.meta[qp->sq.queue.cons];
/* waiting for remote completion? */
if (meta->remote && meta->seq == qp->sq.msn_cons)
return false;
/* waiting for local completion? */
if (!meta->remote && !meta->local_comp)
return false;
return true;
}
static int ionic_poll_send(struct ionic_ctx *ctx,
struct ionic_cq *cq,
struct ionic_qp *qp,
struct ibv_wc *wc)
{
struct ionic_sq_meta *meta;
if (qp->sq.flush)
return 0;
do {
/* completed all send queue requests? */
if (ionic_queue_empty(&qp->sq.queue))
goto out_empty;
meta = &qp->sq.meta[qp->sq.queue.cons];
/* waiting for remote completion? */
if (meta->remote && meta->seq == qp->sq.msn_cons)
goto out_empty;
/* waiting for local completion? */
if (!meta->remote && !meta->local_comp)
goto out_empty;
verbs_debug(&ctx->vctx, "poll cq %u qp %u cons %u st %u wr %" PRIx64 " op %u l %u",
cq->cqid, qp->qpid, qp->sq.queue.cons, meta->ibsts,
meta->wrid, meta->ibop, meta->len);
cq->reserve_pending++;
ionic_queue_consume(&qp->sq.queue);
/* produce wc only if signaled or error status */
} while (!meta->signal && meta->ibsts == IBV_WC_SUCCESS);
memset(wc, 0, sizeof(*wc));
wc->status = meta->ibsts;
wc->wr_id = meta->wrid;
wc->qp_num = qp->qpid;
if (meta->ibsts == IBV_WC_SUCCESS) {
wc->byte_len = meta->len;
wc->opcode = meta->ibop;
} else {
wc->vendor_err = meta->len;
qp->sq.flush = true;
cq->flush = true;
list_del(&qp->cq_flush_sq);
list_add_tail(&cq->flush_sq, &qp->cq_flush_sq);
}
return 1;
out_empty:
if (qp->sq.flush_rcvd) {
qp->sq.flush = true;
cq->flush = true;
list_del(&qp->cq_flush_sq);
list_add_tail(&cq->flush_sq, &qp->cq_flush_sq);
}
return 0;
}
static int ionic_poll_send_many(struct ionic_ctx *ctx,
struct ionic_cq *cq,
struct ionic_qp *qp,
struct ibv_wc *wc, int nwc)
{
int rc = 0, npolled = 0;
while (npolled < nwc) {
rc = ionic_poll_send(ctx, cq, qp, wc + npolled);
if (rc <= 0)
break;
npolled += rc;
}
return npolled ?: rc;
}
static int ionic_validate_cons(uint16_t prod, uint16_t cons,
uint16_t comp, uint16_t mask)
{
if (((prod - cons) & mask) <= ((comp - cons) & mask))
return -EIO;
return 0;
}
static int ionic_comp_msn(struct ionic_ctx *ctx,
struct ionic_qp *qp,
struct ionic_v1_cqe *cqe)
{
struct ionic_sq_meta *meta;
uint16_t cqe_seq, cqe_idx;
int rc;
if (qp->sq.flush)
return 0;
cqe_seq = be32toh(cqe->send.msg_msn) & qp->sq.queue.mask;
rc = ionic_validate_cons(qp->sq.msn_prod,
qp->sq.msn_cons,
cqe_seq - 1,
qp->sq.queue.mask);
if (rc) {
struct ionic_cq *cq =
to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx);
verbs_err(&ctx->vctx, "wqe is not posted for %#x (msn), qpid %u cqid %u seq %u for prod %u cons %u\n",
be32toh(cqe->send.msg_msn), qp->qpid, cq->cqid,
cqe_seq, qp->sq.msn_prod, qp->sq.msn_cons);
return rc;
}
qp->sq.msn_cons = cqe_seq;
if (ionic_v1_cqe_error(cqe)) {
struct ionic_cq *cq =
to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx);
cqe_idx = qp->sq.msn_idx[(cqe_seq - 1) & qp->sq.queue.mask];
meta = &qp->sq.meta[cqe_idx];
meta->len = be32toh(cqe->status_length);
meta->ibsts = ionic_to_ibv_status(meta->len);
verbs_err(&ctx->vctx, "cqe with error %u for %#x (msn), qpid %u cqid %u",
meta->ibsts, be32toh(cqe->send.msg_msn),
qp->qpid, cq->cqid);
}
return 0;
}
static int ionic_comp_npg(struct ionic_ctx *ctx,
struct ionic_qp *qp,
struct ionic_v1_cqe *cqe)
{
struct ionic_sq_meta *meta;
uint16_t cqe_idx;
uint32_t st_len;
if (qp->sq.flush)
return 0;
st_len = be32toh(cqe->status_length);
if (ionic_v1_cqe_error(cqe) && st_len == IONIC_STS_WQE_FLUSHED_ERR) {
/* Flush cqe does not consume a wqe on the device, and maybe
* no such work request is posted.
*
* The driver should begin flushing after the last indicated
* normal or error completion. Here, only set a hint that the
* flush request was indicated. In poll_send, if nothing more
* can be polled normally, then begin flushing.
*/
qp->sq.flush_rcvd = true;
return 0;
}
cqe_idx = cqe->send.npg_wqe_id & qp->sq.queue.mask;
meta = &qp->sq.meta[cqe_idx];
meta->local_comp = true;
if (ionic_v1_cqe_error(cqe)) {
struct ionic_cq *cq =
to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx);
meta->len = st_len;
meta->ibsts = ionic_to_ibv_status(st_len);
meta->remote = false;
verbs_err(&ctx->vctx, "cqe with error %s for %#x (npg), qpid %u cqid %u",
ibv_wc_status_str(meta->ibsts),
be32toh(cqe->send.msg_msn),
qp->qpid, cq->cqid);
}
return 0;
}
static bool ionic_next_cqe(struct ionic_ctx *ctx,
struct ionic_cq *cq,
struct ionic_v1_cqe **cqe)
{
struct ionic_v1_cqe *qcqe = ionic_queue_at_prod(&cq->q);
if (unlikely(cq->color != ionic_v1_cqe_color(qcqe)))
return false;
udma_from_device_barrier();
ionic_dbg_xdump(ctx, "cqe", qcqe, 1u << cq->q.stride_log2);
*cqe = qcqe;
return true;
}
static void ionic_clean_cq(struct ionic_cq *cq, uint32_t qpid)
{
struct ionic_v1_cqe *qcqe;
int prod, qtf, qid;
bool color;
color = cq->color;
prod = cq->q.prod;
qcqe = ionic_queue_at(&cq->q, prod);
while (color == ionic_v1_cqe_color(qcqe)) {
qtf = ionic_v1_cqe_qtf(qcqe);
qid = ionic_v1_cqe_qtf_qid(qtf);
if (qid == qpid)
ionic_v1_cqe_clean(qcqe);
prod = ionic_queue_next(&cq->q, prod);
qcqe = ionic_queue_at(&cq->q, prod);
color = ionic_color_wrap(prod, color);
}
}
static void ionic_reserve_sync_cq(struct ionic_ctx *ctx, struct ionic_cq *cq)
{
if (!ionic_queue_empty(&cq->q)) {
cq->reserve += cq->reserve_pending;
cq->reserve_pending = 0;
cq->q.cons = cq->q.prod;
verbs_debug(&ctx->vctx, "dbell cq %u val %" PRIx64 " rsv %d",
cq->cqid, ionic_queue_dbell_val(&cq->q),
cq->reserve);
ionic_dbell_ring(&ctx->dbpage[ctx->cq_qtype],
ionic_queue_dbell_val(&cq->q));
}
}
static void ionic_arm_cq(struct ionic_ctx *ctx, struct ionic_cq *cq)
{
uint64_t dbell_val = cq->q.dbell;
if (cq->deferred_arm_sol_only) {
cq->arm_sol_prod = ionic_queue_next(&cq->q, cq->arm_sol_prod);
dbell_val |= cq->arm_sol_prod | IONIC_DBELL_RING_SONLY;
} else {
cq->arm_any_prod = ionic_queue_next(&cq->q, cq->arm_any_prod);
dbell_val |= cq->arm_any_prod | IONIC_DBELL_RING_ARM;
}
verbs_debug(&ctx->vctx, "dbell cq %u val %" PRIx64 " (%s)",
cq->cqid, dbell_val,
cq->deferred_arm_sol_only ? "sonly" : "arm");
ionic_dbell_ring(&ctx->dbpage[ctx->cq_qtype], dbell_val);
}
static void ionic_reserve_cq(struct ionic_ctx *ctx, struct ionic_cq *cq,
int spend)
{
cq->reserve -= spend;
if (cq->reserve <= 0 || cq->deferred_arm)
ionic_reserve_sync_cq(ctx, cq);
if (cq->deferred_arm) {
ionic_arm_cq(ctx, cq);
cq->deferred_arm = false;
}
}
static int ionic_poll_vcq_cq(struct ionic_ctx *ctx, struct ionic_cq *cq,
int nwc, struct ibv_wc *wc)
{
struct ionic_qp *qp, *qp_next;
struct ionic_v1_cqe *cqe;
uint32_t qtf, qid;
uint8_t type;
bool peek;
int rc = 0, npolled = 0;
uint16_t old_prod;
if (nwc < 1)
return 0;
ionic_cq_spin_lock(cq);
++cq->cqseq;
old_prod = cq->q.prod;
/* poll already indicated work completions for send queue */
list_for_each_safe(&cq->poll_sq, qp, qp_next, cq_poll_sq) {
if (npolled == nwc)
goto out;
ionic_sq_spin_lock(qp);
rc = ionic_poll_send_many(ctx, cq, qp, wc + npolled, nwc - npolled);
peek = ionic_peek_send(qp);
ionic_sq_spin_unlock(qp);
if (rc < 0)
goto out;
npolled += rc;
if (!peek)
list_del_init(&qp->cq_poll_sq);
}
/* poll for more work completions */
while (likely(ionic_next_cqe(ctx, cq, &cqe))) {
if (npolled == nwc)
goto out;
qtf = ionic_v1_cqe_qtf(cqe);
qid = ionic_v1_cqe_qtf_qid(qtf);
type = ionic_v1_cqe_qtf_type(qtf);
qp = ionic_tbl_lookup(&ctx->qp_tbl, qid);
if (unlikely(!qp)) {
verbs_err(&ctx->vctx, "cq %d pos %d missing qp for qid %#x",
cq->cqid, cq->q.prod, qid);
goto cq_next;
}
switch (type) {
case IONIC_V1_CQE_TYPE_RECV:
ionic_rq_spin_lock(qp);
rc = ionic_poll_recv(ctx, cq, qp, cqe, wc + npolled);
ionic_rq_spin_unlock(qp);
if (rc < 0)
goto out;
npolled += rc;
break;
case IONIC_V1_CQE_TYPE_SEND_MSN:
ionic_sq_spin_lock(qp);
rc = ionic_comp_msn(ctx, qp, cqe);
if (!rc) {
rc = ionic_poll_send_many(ctx, cq, qp,
wc + npolled,
nwc - npolled);
peek = ionic_peek_send(qp);
}
ionic_sq_spin_unlock(qp);
if (rc < 0)
goto out;
npolled += rc;
if (peek) {
list_del(&qp->cq_poll_sq);
list_add_tail(&cq->poll_sq, &qp->cq_poll_sq);
}
break;
case IONIC_V1_CQE_TYPE_SEND_NPG:
ionic_sq_spin_lock(qp);
rc = ionic_comp_npg(ctx, qp, cqe);
if (!rc) {
rc = ionic_poll_send_many(ctx, cq, qp,
wc + npolled,
nwc - npolled);
peek = ionic_peek_send(qp);
}
ionic_sq_spin_unlock(qp);
if (rc < 0)
goto out;
npolled += rc;
if (peek) {
list_del(&qp->cq_poll_sq);
list_add_tail(&cq->poll_sq, &qp->cq_poll_sq);
}
break;
case IONIC_V1_CQE_TYPE_RECV_INDIR:
list_del(&qp->cq_poll_rq);
list_add_tail(&cq->poll_rq, &qp->cq_poll_rq);
break;
default:
verbs_err(&ctx->vctx, "unexpected cqe type %u", type);
rc = -EIO;
goto out;
}
cq_next:
ionic_queue_produce(&cq->q);
cq->color = ionic_color_wrap(cq->q.prod, cq->color);
}
/* lastly, flush send and recv queues */
if (likely(!cq->flush))
goto out;
cq->flush = false;
list_for_each_safe(&cq->flush_sq, qp, qp_next, cq_flush_sq) {
if (npolled == nwc)
goto out;
ionic_sq_spin_lock(qp);
rc = ionic_flush_send_many(qp, wc + npolled, nwc - npolled);
ionic_sq_spin_unlock(qp);
if (rc > 0)
npolled += rc;
if (npolled < nwc)
list_del_init(&qp->cq_flush_sq);
else
cq->flush = true;
}
list_for_each_safe(&cq->flush_rq, qp, qp_next, cq_flush_rq) {
if (npolled == nwc)
goto out;
ionic_rq_spin_lock(qp);
rc = ionic_flush_recv_many(qp, wc + npolled, nwc - npolled);
ionic_rq_spin_unlock(qp);
if (rc > 0)
npolled += rc;
if (npolled < nwc)
list_del_init(&qp->cq_flush_rq);
else
cq->flush = true;
}
out:
ionic_reserve_cq(ctx, cq, 0);
old_prod = (cq->q.prod - old_prod) & cq->q.mask;
ionic_cq_spin_unlock(cq);
return npolled ?: rc;
}
/*
* Note about rc: (noted here because poll is different)
*
* Functions without "poll" in the name, if they return an integer, return
* zero on success, or a positive error number. Functions returning a
* pointer return NULL on error and set errno to a positive error number.
*
* Functions with "poll" in the name return negative error numbers, or
* greater or equal to zero number of completions on success.
*/
static int ionic_poll_cq(struct ibv_cq *ibcq, int nwc, struct ibv_wc *wc)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibcq->context);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
int rc_tmp, rc = 0, npolled = 0;
int cq_i, cq_x, cq_ix;
cq_x = vcq->poll_idx;
vcq->poll_idx ^= ctx->udma_count - 1;
for (cq_i = 0; npolled < nwc && cq_i < ctx->udma_count; ++cq_i) {
cq_ix = cq_i ^ cq_x;
if (!(vcq->udma_mask & BIT(cq_ix)))
continue;
rc_tmp = ionic_poll_vcq_cq(ctx, &vcq->cq[cq_ix],
nwc - npolled, wc + npolled);
if (rc_tmp >= 0)
npolled += rc_tmp;
else if (!rc)
rc = rc_tmp;
}
return npolled ?: rc;
}
static void ionic_req_notify_vcq_cq(struct ionic_cq *cq, int solicited_only)
{
ionic_cq_spin_lock(cq);
if (cq->deferred_arm && !cq->deferred_arm_sol_only)
solicited_only = 0; /* do not downgrade scheduled ARM to SARM */
cq->deferred_arm = true;
cq->deferred_arm_sol_only = (bool)solicited_only;
ionic_cq_spin_unlock(cq);
}
static int ionic_req_notify_cq(struct ibv_cq *ibcq, int solicited_only)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibcq->context);
struct ionic_vcq *vcq = to_ionic_vcq(ibcq);
int cq_i;
if (!vcq->vcq.cq.channel)
return -ENOENT;
for (cq_i = 0; cq_i < ctx->udma_count; ++cq_i) {
if (!(vcq->udma_mask & BIT(cq_i)))
continue;
ionic_req_notify_vcq_cq(&vcq->cq[cq_i], solicited_only);
}
return 0;
}
static bool ionic_expdb_wqe_size_supported(struct ionic_ctx *ctx,
uint32_t wqe_size)
{
switch (wqe_size) {
case 64: return ctx->expdb_mask & IONIC_EXPDB_64;
case 128: return ctx->expdb_mask & IONIC_EXPDB_128;
case 256: return ctx->expdb_mask & IONIC_EXPDB_256;
case 512: return ctx->expdb_mask & IONIC_EXPDB_512;
}
return false;
}
static int ionic_set_cmb(struct ionic_ctx *ctx, struct ionic_queue *queue,
uint8_t cmb, uint64_t cmb_offset, uint8_t qtype,
void **cmb_ptr)
{
uint64_t db_base, db_data;
uint16_t wqe_sz, pos;
__le64 *db_addr;
void *db_ptr;
if (!(cmb & IONIC_CMB_ENABLE)) {
*cmb_ptr = NULL;
return 0;
}
*cmb_ptr = ionic_map_device(queue->size, ctx->vctx.context.cmd_fd,
cmb_offset);
if (!*cmb_ptr)
return errno;
if (cmb & IONIC_CMB_EXPDB) {
/* Pre-fill express doorbells into our buffer */
wqe_sz = 1 << queue->stride_log2;
db_ptr = queue->ptr + wqe_sz - IONIC_EXP_DBELL_SZ;
/* Assume ring 0 */
db_base = ((uint64_t)qtype << 48) | queue->dbell |
((0x03 | 0x8) << 19); /* SCHED_SET | PICI_PISET */
for (pos = 0; pos <= queue->mask; pos++, db_ptr += wqe_sz) {
db_addr = (__le64 *)db_ptr;
db_data = db_base | ((pos + 1) & queue->mask);
*db_addr = htole64(db_data);
}
}
return 0;
}
static int ionic_qp_sq_init(struct ionic_ctx *ctx, struct ionic_qp *qp, struct ionic_pd *pd,
int max_wr, int max_sge, int max_data)
{
uint32_t wqe_size;
int rc;
if (!qp->has_sq)
return 0;
if (max_wr < 0 || max_wr > 0xffff)
return EINVAL;
if (max_sge < 0)
return EINVAL;
if (max_sge > ionic_v1_send_wqe_max_sge(ctx->max_stride, 0, false))
return EINVAL;
if (max_data < 0)
return EINVAL;
if (max_data > ionic_v1_send_wqe_max_data(ctx->max_stride, false))
return EINVAL;
qp->sq.spec = ionic_v1_use_spec_sge(max_sge, ctx->spec);
if (qp->sq.cmb & IONIC_CMB_EXPDB) {
wqe_size = ionic_v1_send_wqe_min_size(max_sge, max_data,
qp->sq.spec, true);
if (!ionic_expdb_wqe_size_supported(ctx, wqe_size))
qp->sq.cmb &= ~IONIC_CMB_EXPDB;
}
if (!(qp->sq.cmb & IONIC_CMB_EXPDB))
wqe_size = ionic_v1_send_wqe_min_size(max_sge, max_data,
qp->sq.spec, false);
rc = ionic_queue_init(&qp->sq.queue, pd, IONIC_PD_TAG_SQ,
ctx->pg_shift, max_wr, wqe_size);
if (rc)
goto err_sq;
qp->sq.cmb_ptr = NULL;
qp->sq.cmb_prod = 0;
qp->sq.color = true;
qp->sq.meta = calloc((uint32_t)qp->sq.queue.mask + 1,
sizeof(*qp->sq.meta));
if (!qp->sq.meta) {
rc = ENOMEM;
goto err_sq_meta;
}
qp->sq.msn_idx = calloc((uint32_t)qp->sq.queue.mask + 1,
sizeof(*qp->sq.msn_idx));
if (!qp->sq.msn_idx) {
rc = ENOMEM;
goto err_sq_msn;
}
return 0;
err_sq_msn:
free(qp->sq.meta);
err_sq_meta:
ionic_queue_destroy(&qp->sq.queue);
err_sq:
return rc;
}
static void ionic_qp_sq_destroy(struct ionic_qp *qp)
{
if (!qp->has_sq)
return;
free(qp->sq.msn_idx);
free(qp->sq.meta);
ionic_queue_destroy(&qp->sq.queue);
}
static int ionic_qp_rq_init(struct ionic_ctx *ctx, struct ionic_qp *qp, struct ionic_pd *pd,
int max_wr, int max_sge)
{
uint32_t wqe_size;
uint64_t pd_tag;
int rc, i;
if (!qp->has_rq)
return 0;
if (max_wr < 0 || max_wr > 0xffff)
return EINVAL;
if (max_sge < 0)
return EINVAL;
if (max_sge > ionic_v1_recv_wqe_max_sge(ctx->max_stride, 0, false))
return EINVAL;
pd_tag = IONIC_PD_TAG_RQ;
qp->rq.spec = ionic_v1_use_spec_sge(max_sge, ctx->spec);
if (qp->rq.cmb & IONIC_CMB_EXPDB) {
wqe_size = ionic_v1_recv_wqe_min_size(max_sge, qp->rq.spec, true);
if (!ionic_expdb_wqe_size_supported(ctx, wqe_size))
qp->rq.cmb &= ~IONIC_CMB_EXPDB;
}
if (!(qp->rq.cmb & IONIC_CMB_EXPDB))
wqe_size = ionic_v1_recv_wqe_min_size(max_sge, qp->rq.spec, false);
rc = ionic_queue_init(&qp->rq.queue, pd, pd_tag, ctx->pg_shift, max_wr, wqe_size);
if (rc)
goto err_rq;
qp->rq.cmb_ptr = NULL;
qp->rq.cmb_prod = 0;
qp->rq.meta = calloc((uint32_t)qp->rq.queue.mask + 1,
sizeof(*qp->rq.meta));
if (!qp->rq.meta) {
rc = ENOMEM;
goto err_rq_meta;
}
for (i = 0; i < qp->rq.queue.mask; ++i)
qp->rq.meta[i].next = &qp->rq.meta[i + 1];
qp->rq.meta[i].next = IONIC_META_LAST;
qp->rq.meta_head = &qp->rq.meta[0];
qp->rq.meta_idx = calloc((uint32_t)qp->rq.queue.mask + 1,
sizeof(*qp->rq.meta_idx));
if (!qp->rq.meta_idx) {
rc = ENOMEM;
goto err_rq_meta_idx;
}
return 0;
err_rq_meta_idx:
free(qp->rq.meta);
err_rq_meta:
ionic_queue_destroy(&qp->rq.queue);
err_rq:
return rc;
}
static void ionic_qp_rq_destroy(struct ionic_qp *qp)
{
if (!qp->has_rq)
return;
free(qp->rq.meta_idx);
free(qp->rq.meta);
ionic_queue_destroy(&qp->rq.queue);
}
static struct ibv_qp *ionic_create_qp_ex(struct ibv_context *ibctx,
struct ibv_qp_init_attr_ex *ex)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibctx);
struct ionic_pd *pd = to_ionic_pd(ex->pd);
struct uionic_qp_resp resp;
struct uionic_qp req;
struct ionic_qp *qp;
struct ionic_cq *cq;
int rc;
qp = calloc(1, sizeof(*qp));
if (!qp) {
rc = ENOMEM;
goto err_qp;
}
qp->vqp.qp.qp_type = ex->qp_type;
qp->has_sq = true;
qp->has_rq = true;
qp->lockfree = false;
list_node_init(&qp->cq_poll_sq);
list_node_init(&qp->cq_poll_rq);
list_node_init(&qp->cq_flush_sq);
list_node_init(&qp->cq_flush_rq);
pthread_spin_init(&qp->sq.lock, PTHREAD_PROCESS_PRIVATE);
pthread_spin_init(&qp->rq.lock, PTHREAD_PROCESS_PRIVATE);
qp->sq.cmb = pd->sq_cmb;
qp->rq.cmb = pd->rq_cmb;
rc = ionic_qp_sq_init(ctx, qp, pd, ex->cap.max_send_wr,
ex->cap.max_send_sge, ex->cap.max_inline_data);
if (rc)
goto err_sq;
rc = ionic_qp_rq_init(ctx, qp, pd, ex->cap.max_recv_wr,
ex->cap.max_recv_sge);
if (rc)
goto err_rq;
req.sq.addr = (uintptr_t)qp->sq.queue.ptr;
req.sq.size = qp->sq.queue.size;
req.sq.mask = qp->sq.queue.mask;
req.sq.depth_log2 = qp->sq.queue.depth_log2;
req.sq.stride_log2 = qp->sq.queue.stride_log2;
req.sq_cmb = qp->sq.cmb;
req.rq.addr = (uintptr_t)qp->rq.queue.ptr;
req.rq.size = qp->rq.queue.size;
req.rq.mask = qp->rq.queue.mask;
req.rq.depth_log2 = qp->rq.queue.depth_log2;
req.rq.stride_log2 = qp->rq.queue.stride_log2;
req.rq_cmb = qp->rq.cmb;
req.sq_spec = qp->sq.spec;
req.rq_spec = qp->rq.spec;
req.udma_mask = pd->udma_mask;
rc = ibv_cmd_create_qp_ex2(ibctx, &qp->vqp, ex,
&req.ibv_cmd,
sizeof(req),
&resp.ibv_resp,
sizeof(resp));
if (rc)
goto err_cmd;
qp->qpid = resp.qpid;
qp->udma_idx = resp.udma_idx;
ionic_queue_dbell_init(&qp->sq.queue, qp->qpid);
ionic_queue_dbell_init(&qp->rq.queue, qp->qpid);
qp->sq.cmb = resp.sq_cmb;
qp->rq.cmb = resp.rq_cmb;
rc = ionic_set_cmb(ctx, &qp->sq.queue, qp->sq.cmb, resp.sq_cmb_offset,
ctx->sq_qtype, &qp->sq.cmb_ptr);
if (rc)
goto err_cmb;
rc = ionic_set_cmb(ctx, &qp->rq.queue, qp->rq.cmb, resp.rq_cmb_offset,
ctx->rq_qtype, &qp->rq.cmb_ptr);
if (rc)
goto err_cmb;
pthread_mutex_lock(&ctx->mut);
ionic_tbl_alloc_node(&ctx->qp_tbl);
ionic_tbl_insert(&ctx->qp_tbl, qp, qp->qpid);
pthread_mutex_unlock(&ctx->mut);
if (qp->has_sq) {
cq = to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx);
ionic_cq_spin_lock(cq);
ionic_cq_spin_unlock(cq);
ex->cap.max_send_wr = qp->sq.queue.mask;
ex->cap.max_send_sge =
ionic_v1_send_wqe_max_sge(qp->sq.queue.stride_log2, qp->sq.spec,
qp->sq.cmb & IONIC_CMB_EXPDB);
ex->cap.max_inline_data =
ionic_v1_send_wqe_max_data(qp->sq.queue.stride_log2,
qp->sq.cmb & IONIC_CMB_EXPDB);
}
if (qp->has_rq) {
cq = to_ionic_vcq_cq(qp->vqp.qp.recv_cq, qp->udma_idx);
ionic_cq_spin_lock(cq);
ionic_cq_spin_unlock(cq);
ex->cap.max_recv_wr = qp->rq.queue.mask;
ex->cap.max_recv_sge =
ionic_v1_recv_wqe_max_sge(qp->rq.queue.stride_log2, qp->rq.spec,
qp->rq.cmb & IONIC_CMB_EXPDB);
}
return &qp->vqp.qp;
err_cmb:
ibv_cmd_destroy_qp(&qp->vqp.qp);
ionic_unmap(qp->sq.cmb_ptr, qp->sq.queue.size);
ionic_unmap(qp->rq.cmb_ptr, qp->rq.queue.size);
err_cmd:
ionic_qp_rq_destroy(qp);
err_rq:
ionic_qp_sq_destroy(qp);
err_sq:
pthread_spin_destroy(&qp->rq.lock);
pthread_spin_destroy(&qp->sq.lock);
free(qp);
err_qp:
errno = rc;
return NULL;
}
static void ionic_flush_qp(struct ionic_ctx *ctx, struct ionic_qp *qp)
{
struct ionic_cq *cq;
if (qp->vqp.qp.send_cq) {
cq = to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx);
/* Hold the CQ lock and QP sq_lock while setting up flush */
ionic_cq_spin_lock(cq);
ionic_sq_spin_lock(qp);
qp->sq.flush = true;
if (!ionic_queue_empty(&qp->sq.queue)) {
cq->flush = true;
list_del(&qp->cq_flush_sq);
list_add_tail(&cq->flush_sq, &qp->cq_flush_sq);
}
ionic_sq_spin_unlock(qp);
ionic_cq_spin_unlock(cq);
}
if (qp->vqp.qp.recv_cq) {
cq = to_ionic_vcq_cq(qp->vqp.qp.recv_cq, qp->udma_idx);
/* Hold the CQ lock and QP rq_lock while setting up flush */
ionic_cq_spin_lock(cq);
ionic_rq_spin_lock(qp);
qp->rq.flush = true;
if (!ionic_queue_empty(&qp->rq.queue)) {
cq->flush = true;
list_del(&qp->cq_flush_rq);
list_add_tail(&cq->flush_rq, &qp->cq_flush_rq);
}
ionic_rq_spin_unlock(qp);
ionic_cq_spin_unlock(cq);
}
}
static void ionic_reset_qp(struct ionic_ctx *ctx, struct ionic_qp *qp)
{
struct ionic_cq *cq;
int i;
if (qp->vqp.qp.send_cq) {
cq = to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx);
ionic_cq_spin_lock(cq);
ionic_clean_cq(cq, qp->qpid);
ionic_cq_spin_unlock(cq);
}
if (qp->vqp.qp.recv_cq) {
cq = to_ionic_vcq_cq(qp->vqp.qp.recv_cq, qp->udma_idx);
ionic_cq_spin_lock(cq);
ionic_clean_cq(cq, qp->qpid);
ionic_cq_spin_unlock(cq);
}
if (qp->has_sq) {
ionic_sq_spin_lock(qp);
qp->sq.flush = false;
qp->sq.flush_rcvd = false;
qp->sq.msn_prod = 0;
qp->sq.msn_cons = 0;
qp->sq.cmb_prod = 0;
qp->sq.old_prod = 0;
qp->sq.queue.prod = 0;
qp->sq.queue.cons = 0;
ionic_sq_spin_unlock(qp);
}
if (qp->has_rq) {
ionic_rq_spin_lock(qp);
qp->rq.flush = false;
qp->rq.queue.prod = 0;
qp->rq.queue.cons = 0;
qp->rq.cmb_prod = 0;
qp->rq.old_prod = 0;
for (i = 0; i < qp->rq.queue.mask; ++i)
qp->rq.meta[i].next = &qp->rq.meta[i + 1];
qp->rq.meta[i].next = IONIC_META_LAST;
qp->rq.meta_head = &qp->rq.meta[0];
ionic_rq_spin_unlock(qp);
}
}
static int ionic_modify_qp(struct ibv_qp *ibqp,
struct ibv_qp_attr *attr,
int attr_mask)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibqp->context);
struct ionic_qp *qp = to_ionic_qp(ibqp);
struct ibv_modify_qp cmd = {};
int rc;
if (!attr_mask)
return 0;
rc = ibv_cmd_modify_qp(ibqp, attr, attr_mask, &cmd, sizeof(cmd));
if (rc)
goto err_cmd;
if (attr_mask & IBV_QP_STATE) {
if (attr->qp_state == IBV_QPS_ERR)
ionic_flush_qp(ctx, qp);
else if (attr->qp_state == IBV_QPS_RESET)
ionic_reset_qp(ctx, qp);
}
err_cmd:
if (attr_mask & IBV_QP_STATE)
verbs_debug(&ctx->vctx, "modify qp %u state %u -> %u rc %d",
qp->qpid, qp->vqp.qp.state, attr->qp_state, rc);
return rc;
}
static int ionic_query_qp(struct ibv_qp *ibqp,
struct ibv_qp_attr *attr,
int attr_mask,
struct ibv_qp_init_attr *init_attr)
{
struct ionic_qp *qp = to_ionic_qp(ibqp);
struct ionic_ctx *ctx = to_ionic_ctx(ibqp->context);
struct ibv_query_qp cmd;
int rc;
rc = ibv_cmd_query_qp(ibqp, attr, attr_mask, init_attr,
&cmd, sizeof(cmd));
if (qp->has_sq) {
init_attr->cap.max_send_wr = qp->sq.queue.mask;
init_attr->cap.max_send_sge =
ionic_v1_send_wqe_max_sge(qp->sq.queue.stride_log2, qp->sq.spec,
qp->sq.cmb & IONIC_CMB_EXPDB);
init_attr->cap.max_inline_data =
ionic_v1_send_wqe_max_data(qp->sq.queue.stride_log2,
qp->sq.cmb & IONIC_CMB_EXPDB);
}
if (qp->has_rq) {
init_attr->cap.max_recv_wr = qp->rq.queue.mask;
init_attr->cap.max_recv_sge =
ionic_v1_send_wqe_max_sge(qp->rq.queue.stride_log2, qp->rq.spec,
qp->rq.cmb & IONIC_CMB_EXPDB);
}
attr->cap = init_attr->cap;
verbs_debug(&ctx->vctx, "query qp %u attr_state %u rc %d",
qp->qpid, attr->qp_state, rc);
return rc;
}
static int ionic_destroy_qp(struct ibv_qp *ibqp)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibqp->context);
struct ionic_qp *qp = to_ionic_qp(ibqp);
struct ionic_cq *cq;
int rc;
rc = ibv_cmd_destroy_qp(ibqp);
if (rc)
return rc;
pthread_mutex_lock(&ctx->mut);
ionic_tbl_free_node(&ctx->qp_tbl);
ionic_tbl_delete(&ctx->qp_tbl, qp->qpid);
pthread_mutex_unlock(&ctx->mut);
if (qp->vqp.qp.send_cq) {
cq = to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx);
ionic_cq_spin_lock(cq);
ionic_clean_cq(cq, qp->qpid);
list_del(&qp->cq_poll_sq);
list_del(&qp->cq_flush_sq);
ionic_cq_spin_unlock(cq);
}
if (qp->vqp.qp.recv_cq) {
cq = to_ionic_vcq_cq(qp->vqp.qp.recv_cq, qp->udma_idx);
ionic_cq_spin_lock(cq);
ionic_clean_cq(cq, qp->qpid);
list_del(&qp->cq_poll_rq);
list_del(&qp->cq_flush_rq);
ionic_cq_spin_unlock(cq);
}
ionic_unmap(qp->sq.cmb_ptr, qp->sq.queue.size);
ionic_unmap(qp->rq.cmb_ptr, qp->rq.queue.size);
pthread_spin_destroy(&qp->rq.lock);
pthread_spin_destroy(&qp->sq.lock);
ionic_qp_rq_destroy(qp);
ionic_qp_sq_destroy(qp);
free(qp);
return 0;
}
static int64_t ionic_prep_inline(void *data, uint32_t max_data,
struct ibv_sge *ibv_sgl, int num_sge)
{
int64_t len = 0, sg_len;
int sg_i;
for (sg_i = 0; sg_i < num_sge; ++sg_i) {
sg_len = ibv_sgl[sg_i].length;
/* greater than max inline data is invalid */
if (unlikely(len + sg_len > max_data))
return -EINVAL;
memcpy(data + len, (void *)(uintptr_t)ibv_sgl[sg_i].addr, sg_len);
len += sg_len;
}
return len;
}
static int64_t ionic_v1_prep_pld(struct ionic_v1_wqe *wqe,
union ionic_v1_pld *pld,
int spec, uint32_t max_sge,
struct ibv_sge *ibv_sgl,
int num_sge)
{
static const int64_t bit_31 = 1l << 31;
int64_t len = 0, sg_len;
struct ionic_sge *sgl;
__be32 *spec32 = NULL;
__be16 *spec16 = NULL;
int sg_i = 0;
if (unlikely(num_sge < 0 || (uint32_t)num_sge > max_sge))
return -EINVAL;
if (spec && num_sge > IONIC_V1_SPEC_FIRST_SGE) {
sg_i = IONIC_V1_SPEC_FIRST_SGE;
if (num_sge > 8) {
wqe->base.flags |= htobe16(IONIC_V1_FLAG_SPEC16);
spec16 = pld->spec16;
} else {
wqe->base.flags |= htobe16(IONIC_V1_FLAG_SPEC32);
spec32 = pld->spec32;
}
}
sgl = &pld->sgl[sg_i];
for (sg_i = 0; sg_i < num_sge; ++sg_i) {
sg_len = ibv_sgl[sg_i].length;
/* greater than 2GB data is invalid */
if (unlikely(len + sg_len > bit_31))
return -EINVAL;
sgl[sg_i].va = htobe64(ibv_sgl[sg_i].addr);
sgl[sg_i].len = htobe32(sg_len);
sgl[sg_i].lkey = htobe32(ibv_sgl[sg_i].lkey);
if (spec32) {
spec32[sg_i] = sgl[sg_i].len;
} else if (spec16) {
if (unlikely(sg_len > UINT16_MAX))
return -EINVAL;
spec16[sg_i] = htobe16(sg_len);
}
len += sg_len;
}
return len;
}
static void ionic_v1_prep_base(struct ionic_qp *qp,
struct ibv_send_wr *wr,
struct ionic_sq_meta *meta,
struct ionic_v1_wqe *wqe)
{
struct ionic_ctx *ctx = to_ionic_ctx(qp->vqp.qp.context);
meta->wrid = wr->wr_id;
meta->ibsts = IBV_WC_SUCCESS;
meta->signal = false;
meta->local_comp = false;
wqe->base.wqe_id = qp->sq.queue.prod;
if (qp->sq.color)
wqe->base.flags |= htobe16(IONIC_V1_FLAG_COLOR);
if (wr->send_flags & IBV_SEND_FENCE)
wqe->base.flags |= htobe16(IONIC_V1_FLAG_FENCE);
if (wr->send_flags & IBV_SEND_SOLICITED)
wqe->base.flags |= htobe16(IONIC_V1_FLAG_SOL);
if (wr->send_flags & IBV_SEND_SIGNALED) {
wqe->base.flags |= htobe16(IONIC_V1_FLAG_SIG);
meta->signal = true;
}
meta->seq = qp->sq.msn_prod;
meta->remote = qp->vqp.qp.qp_type != IBV_QPT_UD &&
!ionic_ibop_is_local(wr->opcode);
if (meta->remote) {
qp->sq.msn_idx[meta->seq] = qp->sq.queue.prod;
qp->sq.msn_prod = ionic_queue_next(&qp->sq.queue, qp->sq.msn_prod);
}
verbs_debug(&ctx->vctx, "post send %u prod %u",
qp->qpid, qp->sq.queue.prod);
ionic_dbg_xdump(ctx, "wqe", wqe, 1u << qp->sq.queue.stride_log2);
ionic_queue_produce(&qp->sq.queue);
qp->sq.color = ionic_color_wrap(qp->sq.queue.prod, qp->sq.color);
}
static int ionic_v1_prep_common(struct ionic_qp *qp,
struct ibv_send_wr *wr,
struct ionic_sq_meta *meta,
struct ionic_v1_wqe *wqe)
{
int64_t signed_len;
uint32_t mval;
if (wr->send_flags & IBV_SEND_INLINE) {
wqe->base.num_sge_key = 0;
wqe->base.flags |= htobe16(IONIC_V1_FLAG_INL);
mval = ionic_v1_send_wqe_max_data(qp->sq.queue.stride_log2,
qp->sq.cmb & IONIC_CMB_EXPDB);
signed_len = ionic_prep_inline(wqe->common.pld.data, mval,
wr->sg_list, wr->num_sge);
} else {
wqe->base.num_sge_key = wr->num_sge;
mval = ionic_v1_send_wqe_max_sge(qp->sq.queue.stride_log2, qp->sq.spec,
qp->sq.cmb & IONIC_CMB_EXPDB);
signed_len = ionic_v1_prep_pld(wqe, &wqe->common.pld,
qp->sq.spec, mval,
wr->sg_list, wr->num_sge);
}
if (unlikely(signed_len < 0))
return -signed_len;
meta->len = signed_len;
wqe->common.length = htobe32(signed_len);
ionic_v1_prep_base(qp, wr, meta, wqe);
return 0;
}
static void ionic_prep_sq_wqe(struct ionic_qp *qp, void *wqe)
{
uint32_t wqe_sz = 1u << qp->sq.queue.stride_log2;
if (qp->sq.cmb & IONIC_CMB_EXPDB)
memset(wqe, 0, wqe_sz - IONIC_EXP_DBELL_SZ);
else
memset(wqe, 0, wqe_sz);
}
static void ionic_prep_rq_wqe(struct ionic_qp *qp, void *wqe)
{
uint32_t wqe_sz = 1u << qp->rq.queue.stride_log2;
if (qp->rq.cmb & IONIC_CMB_EXPDB)
memset(wqe, 0, wqe_sz - IONIC_EXP_DBELL_SZ);
else
memset(wqe, 0, wqe_sz);
}
static int ionic_v1_prep_send(struct ionic_qp *qp,
struct ibv_send_wr *wr)
{
struct ionic_ctx *ctx = to_ionic_ctx(qp->vqp.qp.context);
struct ionic_sq_meta *meta;
struct ionic_v1_wqe *wqe;
meta = &qp->sq.meta[qp->sq.queue.prod];
wqe = ionic_queue_at_prod(&qp->sq.queue);
ionic_prep_sq_wqe(qp, wqe);
meta->ibop = IBV_WC_SEND;
switch (wr->opcode) {
case IBV_WR_SEND:
wqe->base.op = IONIC_OP(ctx->version, SEND);
break;
case IBV_WR_SEND_WITH_IMM:
wqe->base.op = IONIC_OP(ctx->version, SEND_IMM);
wqe->base.imm_data_key = wr->imm_data;
break;
case IBV_WR_SEND_WITH_INV:
wqe->base.op = IONIC_OP(ctx->version, SEND_INV);
wqe->base.imm_data_key = htobe32(wr->invalidate_rkey);
break;
default:
return EINVAL;
}
return ionic_v1_prep_common(qp, wr, meta, wqe);
}
static int ionic_v1_prep_send_ud(struct ionic_qp *qp, struct ibv_send_wr *wr)
{
struct ionic_ctx *ctx = to_ionic_ctx(qp->vqp.qp.context);
struct ionic_sq_meta *meta;
struct ionic_v1_wqe *wqe;
struct ionic_ah *ah;
if (unlikely(!wr->wr.ud.ah))
return EINVAL;
ah = to_ionic_ah(wr->wr.ud.ah);
meta = &qp->sq.meta[qp->sq.queue.prod];
wqe = ionic_queue_at_prod(&qp->sq.queue);
ionic_prep_sq_wqe(qp, wqe);
wqe->common.send.ah_id = htobe32(ah->ahid);
wqe->common.send.dest_qpn = htobe32(wr->wr.ud.remote_qpn);
wqe->common.send.dest_qkey = htobe32(wr->wr.ud.remote_qkey);
meta->ibop = IBV_WC_SEND;
switch (wr->opcode) {
case IBV_WR_SEND:
wqe->base.op = IONIC_OP(ctx->version, SEND);
break;
case IBV_WR_SEND_WITH_IMM:
wqe->base.op = IONIC_OP(ctx->version, SEND_IMM);
wqe->base.imm_data_key = wr->imm_data;
break;
default:
return EINVAL;
}
return ionic_v1_prep_common(qp, wr, meta, wqe);
}
static int ionic_v1_prep_rdma(struct ionic_qp *qp,
struct ibv_send_wr *wr)
{
struct ionic_ctx *ctx = to_ionic_ctx(qp->vqp.qp.context);
struct ionic_sq_meta *meta;
struct ionic_v1_wqe *wqe;
meta = &qp->sq.meta[qp->sq.queue.prod];
wqe = ionic_queue_at_prod(&qp->sq.queue);
ionic_prep_sq_wqe(qp, wqe);
meta->ibop = IBV_WC_RDMA_WRITE;
switch (wr->opcode) {
case IBV_WR_RDMA_READ:
if (wr->send_flags & (IBV_SEND_SOLICITED | IBV_SEND_INLINE))
return EINVAL;
meta->ibop = IBV_WC_RDMA_READ;
wqe->base.op = IONIC_OP(ctx->version, RDMA_READ);
break;
case IBV_WR_RDMA_WRITE:
if (wr->send_flags & IBV_SEND_SOLICITED)
return EINVAL;
wqe->base.op = IONIC_OP(ctx->version, RDMA_WRITE);
break;
case IBV_WR_RDMA_WRITE_WITH_IMM:
wqe->base.op = IONIC_OP(ctx->version, RDMA_WRITE_IMM);
wqe->base.imm_data_key = wr->imm_data;
break;
default:
return EINVAL;
}
wqe->common.rdma.remote_va_high =
htobe32(wr->wr.rdma.remote_addr >> 32);
wqe->common.rdma.remote_va_low = htobe32(wr->wr.rdma.remote_addr);
wqe->common.rdma.remote_rkey = htobe32(wr->wr.rdma.rkey);
return ionic_v1_prep_common(qp, wr, meta, wqe);
}
static int ionic_v1_prep_atomic(struct ionic_qp *qp,
struct ibv_send_wr *wr)
{
struct ionic_ctx *ctx = to_ionic_ctx(qp->vqp.qp.context);
struct ionic_sq_meta *meta;
struct ionic_v1_wqe *wqe;
if (wr->num_sge != 1 || wr->sg_list[0].length != 8)
return EINVAL;
if (wr->send_flags & (IBV_SEND_SOLICITED | IBV_SEND_INLINE))
return EINVAL;
meta = &qp->sq.meta[qp->sq.queue.prod];
wqe = ionic_queue_at_prod(&qp->sq.queue);
ionic_prep_sq_wqe(qp, wqe);
switch (wr->opcode) {
case IBV_WR_ATOMIC_CMP_AND_SWP:
meta->ibop = IBV_WC_COMP_SWAP;
wqe->base.op = IONIC_OP(ctx->version, ATOMIC_CS);
wqe->atomic.swap_add_high = htobe32(wr->wr.atomic.swap >> 32);
wqe->atomic.swap_add_low = htobe32(wr->wr.atomic.swap);
wqe->atomic.compare_high =
htobe32(wr->wr.atomic.compare_add >> 32);
wqe->atomic.compare_low = htobe32(wr->wr.atomic.compare_add);
break;
case IBV_WR_ATOMIC_FETCH_AND_ADD:
meta->ibop = IBV_WC_FETCH_ADD;
wqe->base.op = IONIC_OP(ctx->version, ATOMIC_FA);
wqe->atomic.swap_add_high =
htobe32(wr->wr.atomic.compare_add >> 32);
wqe->atomic.swap_add_low = htobe32(wr->wr.atomic.compare_add);
break;
default:
return EINVAL;
}
wqe->atomic.remote_va_high = htobe32(wr->wr.atomic.remote_addr >> 32);
wqe->atomic.remote_va_low = htobe32(wr->wr.atomic.remote_addr);
wqe->atomic.remote_rkey = htobe32(wr->wr.atomic.rkey);
wqe->base.num_sge_key = 1;
/*
* The fields above are common to atomic and atomic_v2. Deal now with
* the fields that differ.
*/
if (likely(ctx->version >= 2)) {
wqe->atomic_v2.local_va = htobe64(wr->sg_list[0].addr);
wqe->atomic_v2.lkey = htobe32(wr->sg_list[0].lkey);
} else {
wqe->atomic.sge.va = htobe64(wr->sg_list[0].addr);
wqe->atomic.sge.len = htobe32(8);
wqe->atomic.sge.lkey = htobe32(wr->sg_list[0].lkey);
}
ionic_v1_prep_base(qp, wr, meta, wqe);
return 0;
}
static int ionic_v1_prep_inv(struct ionic_qp *qp, struct ibv_send_wr *wr)
{
struct ionic_ctx *ctx = to_ionic_ctx(qp->vqp.qp.context);
struct ionic_sq_meta *meta;
struct ionic_v1_wqe *wqe;
if (wr->send_flags & (IBV_SEND_SOLICITED | IBV_SEND_INLINE))
return EINVAL;
meta = &qp->sq.meta[qp->sq.queue.prod];
meta->ibop = IBV_WC_LOCAL_INV;
wqe = ionic_queue_at_prod(&qp->sq.queue);
ionic_prep_sq_wqe(qp, wqe);
wqe->base.op = IONIC_OP(ctx->version, LOCAL_INV);
wqe->base.imm_data_key = htobe32(wr->invalidate_rkey);
ionic_v1_prep_base(qp, wr, meta, wqe);
return 0;
}
static int ionic_v1_prep_bind(struct ionic_qp *qp,
struct ibv_send_wr *wr,
bool send_path)
{
struct ionic_ctx *ctx = to_ionic_ctx(qp->vqp.qp.context);
struct ionic_sq_meta *meta;
struct ionic_v1_wqe *wqe;
int flags;
if (wr->send_flags & (IBV_SEND_SOLICITED | IBV_SEND_INLINE))
return EINVAL;
/* type 1 must use bind_mw; type 2 must use post_send */
if (send_path == (wr->bind_mw.mw->type == IBV_MW_TYPE_1))
return EINVAL;
/* only type 1 can unbind with zero length */
if (!wr->bind_mw.bind_info.length &&
wr->bind_mw.mw->type != IBV_MW_TYPE_1)
return EINVAL;
meta = &qp->sq.meta[qp->sq.queue.prod];
meta->ibop = IBV_WC_BIND_MW;
wqe = ionic_queue_at_prod(&qp->sq.queue);
ionic_prep_sq_wqe(qp, wqe);
flags = to_ionic_mr_flags(wr->bind_mw.bind_info.mw_access_flags);
if (wr->bind_mw.mw->type == IBV_MW_TYPE_1)
flags |= IONIC_MRF_MW_1;
else
flags |= IONIC_MRF_MW_2;
wqe->base.op = IONIC_OP(ctx->version, BIND_MW);
wqe->base.num_sge_key = wr->bind_mw.rkey;
wqe->base.imm_data_key = htobe32(wr->bind_mw.mw->rkey);
wqe->bind_mw.va = htobe64(wr->bind_mw.bind_info.addr);
wqe->bind_mw.length = htobe64(wr->bind_mw.bind_info.length);
wqe->bind_mw.lkey = htobe32(wr->bind_mw.bind_info.mr->lkey);
wqe->bind_mw.flags = htobe16(flags);
ionic_v1_prep_base(qp, wr, meta, wqe);
return 0;
}
static int ionic_v1_prep_one_rc(struct ionic_qp *qp,
struct ibv_send_wr *wr,
bool send_path)
{
struct ionic_ctx *ctx = to_ionic_ctx(qp->vqp.qp.context);
int rc = 0;
switch (wr->opcode) {
case IBV_WR_SEND:
case IBV_WR_SEND_WITH_IMM:
case IBV_WR_SEND_WITH_INV:
rc = ionic_v1_prep_send(qp, wr);
break;
case IBV_WR_RDMA_READ:
case IBV_WR_RDMA_WRITE:
case IBV_WR_RDMA_WRITE_WITH_IMM:
rc = ionic_v1_prep_rdma(qp, wr);
break;
case IBV_WR_ATOMIC_CMP_AND_SWP:
case IBV_WR_ATOMIC_FETCH_AND_ADD:
rc = ionic_v1_prep_atomic(qp, wr);
break;
case IBV_WR_LOCAL_INV:
rc = ionic_v1_prep_inv(qp, wr);
break;
case IBV_WR_BIND_MW:
rc = ionic_v1_prep_bind(qp, wr, send_path);
break;
default:
verbs_warn(&ctx->vctx, "invalid opcode %d", wr->opcode);
rc = EINVAL;
}
return rc;
}
static int ionic_v1_prep_one_ud(struct ionic_qp *qp,
struct ibv_send_wr *wr)
{
struct ionic_ctx *ctx = to_ionic_ctx(qp->vqp.qp.context);
int rc = 0;
switch (wr->opcode) {
case IBV_WR_SEND:
case IBV_WR_SEND_WITH_IMM:
rc = ionic_v1_prep_send_ud(qp, wr);
break;
default:
verbs_warn(&ctx->vctx, "invalid opcode %d", wr->opcode);
rc = EINVAL;
}
return rc;
}
static void ionic_post_cmb_common(struct ionic_ctx *ctx,
struct ionic_queue *queue,
uint8_t cmb,
void *cmb_ptr,
uint16_t *cmb_prod,
uint8_t qtype,
uint32_t qpid)
{
void *cmb_dst_ptr;
void *wqe_src_ptr;
uint32_t stride;
uint16_t pos, end;
uint8_t stride_log2, cmb_wc;
stride_log2 = queue->stride_log2;
pos = *cmb_prod;
end = queue->prod;
cmb_wc = !(cmb & IONIC_CMB_UC);
if (cmb & IONIC_CMB_EXPDB) {
/* Express doorbell mode: copy each WQE individually with barriers */
while (pos != end) {
cmb_dst_ptr = cmb_ptr + ((size_t)pos << stride_log2);
wqe_src_ptr = ionic_queue_at(queue, pos);
stride = 1u << stride_log2;
do {
udma_to_device_barrier();
/* only fence before last 64B of each
* WC wqe, no need to mmio_wc_start()
*/
if (cmb_wc && stride <= 64)
mmio_flush_writes();
ionic_mmio_memcpy_x64_64(cmb_dst_ptr, wqe_src_ptr);
stride -= 64;
cmb_dst_ptr += 64;
wqe_src_ptr += 64;
} while (stride);
pos = ionic_queue_next(queue, pos);
}
} else {
/* Regular doorbell mode: bulk copy with wrap-around handling */
if (pos > end) {
/* Handle wrap-around case: copy from pos to end of ring */
cmb_dst_ptr = cmb_ptr + ((size_t)pos << stride_log2);
wqe_src_ptr = ionic_queue_at(queue, pos);
stride = (uint32_t)(queue->mask - pos + 1) << stride_log2;
ionic_mmio_memcpy_x64(cmb_dst_ptr, wqe_src_ptr, stride);
pos = 0;
}
if (pos < end) {
/* Copy from pos to end */
cmb_dst_ptr = cmb_ptr + ((size_t)pos << stride_log2);
wqe_src_ptr = ionic_queue_at(queue, pos);
stride = (uint32_t)(end - pos) << stride_log2;
ionic_mmio_memcpy_x64(cmb_dst_ptr, wqe_src_ptr, stride);
pos = end;
}
if (cmb_wc)
mmio_flush_writes();
/* Ring doorbell */
verbs_debug(&ctx->vctx, "dbell qp %u qtype %d val %" PRIx64,
qpid, qtype, queue->dbell | pos);
ionic_dbell_ring(&ctx->dbpage[qtype], queue->dbell | pos);
}
*cmb_prod = end;
}
static void ionic_post_send_cmb(struct ionic_ctx *ctx, struct ionic_qp *qp)
{
ionic_post_cmb_common(ctx,
&qp->sq.queue,
qp->sq.cmb,
qp->sq.cmb_ptr,
&qp->sq.cmb_prod,
ctx->sq_qtype,
qp->qpid);
}
static void ionic_post_recv_cmb(struct ionic_ctx *ctx, struct ionic_qp *qp)
{
ionic_post_cmb_common(ctx,
&qp->rq.queue,
qp->rq.cmb,
qp->rq.cmb_ptr,
&qp->rq.cmb_prod,
ctx->rq_qtype,
qp->qpid);
}
static int ionic_post_send_common(struct ionic_ctx *ctx,
struct ionic_cq *cq,
struct ionic_qp *qp,
struct ibv_send_wr *wr,
struct ibv_send_wr **bad,
bool send_path)
{
int spend, rc = 0;
uint16_t old_prod;
if (unlikely(!bad))
return EINVAL;
if (unlikely(!qp->has_sq)) {
*bad = wr;
return EINVAL;
}
if (unlikely(qp->vqp.qp.state < IBV_QPS_RTS)) {
*bad = wr;
return EINVAL;
}
ionic_sq_spin_lock(qp);
old_prod = qp->sq.queue.prod;
while (wr) {
if (ionic_queue_full(&qp->sq.queue)) {
verbs_info(&ctx->vctx,
"send queue full cons %u prod %u",
qp->sq.queue.cons, qp->sq.queue.prod);
rc = ENOMEM;
goto out;
}
if (qp->vqp.qp.qp_type == IBV_QPT_UD)
rc = ionic_v1_prep_one_ud(qp, wr);
else
rc = ionic_v1_prep_one_rc(qp, wr, send_path);
if (rc)
goto out;
wr = wr->next;
}
out:
old_prod = (qp->sq.queue.prod - old_prod) & qp->sq.queue.mask;
if (ionic_cq_spin_trylock(cq)) {
ionic_sq_spin_unlock(qp);
ionic_cq_spin_lock(cq);
ionic_sq_spin_lock(qp);
}
if (likely(qp->sq.queue.prod != qp->sq.old_prod)) {
/* ring cq doorbell just in time */
spend = (qp->sq.queue.prod - qp->sq.old_prod) & qp->sq.queue.mask;
ionic_reserve_cq(ctx, cq, spend);
qp->sq.old_prod = qp->sq.queue.prod;
if (qp->sq.cmb_ptr) {
ionic_post_send_cmb(ctx, qp);
} else {
udma_to_device_barrier();
verbs_debug(&ctx->vctx, "dbell qp %u sq val %" PRIx64,
qp->qpid, ionic_queue_dbell_val(&qp->sq.queue));
ionic_dbell_ring(&ctx->dbpage[ctx->sq_qtype],
ionic_queue_dbell_val(&qp->sq.queue));
}
}
if (qp->sq.flush) {
cq->flush = true;
list_del(&qp->cq_flush_sq);
list_add_tail(&cq->flush_sq, &qp->cq_flush_sq);
}
ionic_sq_spin_unlock(qp);
ionic_cq_spin_unlock(cq);
*bad = wr;
return rc;
}
static int ionic_v1_prep_recv(struct ionic_qp *qp,
struct ibv_recv_wr *wr)
{
struct ionic_ctx *ctx = to_ionic_ctx(qp->vqp.qp.context);
struct ionic_rq_meta *meta;
struct ionic_v1_wqe *wqe;
int64_t signed_len;
uint32_t mval;
wqe = ionic_queue_at_prod(&qp->rq.queue);
/* if wqe is owned by device, caller can try posting again soon */
if (wqe->base.flags & htobe16(IONIC_V1_FLAG_FENCE))
return -EAGAIN;
meta = qp->rq.meta_head;
if (unlikely(meta == IONIC_META_LAST) ||
unlikely(meta == IONIC_META_POSTED))
return -EIO;
ionic_prep_rq_wqe(qp, wqe);
mval = ionic_v1_recv_wqe_max_sge(qp->rq.queue.stride_log2,
qp->rq.spec, qp->rq.cmb & IONIC_CMB_EXPDB);
signed_len = ionic_v1_prep_pld(wqe, &wqe->recv.pld,
qp->rq.spec, mval,
wr->sg_list, wr->num_sge);
if (signed_len < 0)
return -signed_len;
meta->wrid = wr->wr_id;
wqe->base.wqe_id = qp->rq.queue.prod;
wqe->base.num_sge_key = wr->num_sge;
qp->rq.meta_idx[qp->rq.queue.prod] = meta - qp->rq.meta;
/* total length for recv goes in base imm_data_key */
wqe->base.imm_data_key = htobe32(signed_len);
verbs_debug(&ctx->vctx, "post recv %u prod %u",
qp->qpid, qp->rq.queue.prod);
ionic_dbg_xdump(ctx, "wqe", wqe, 1u << qp->rq.queue.stride_log2);
ionic_queue_produce(&qp->rq.queue);
qp->rq.meta_head = meta->next;
meta->next = IONIC_META_POSTED;
return 0;
}
static int ionic_post_recv_common(struct ionic_ctx *ctx,
struct ionic_cq *cq,
struct ionic_qp *qp,
struct ibv_recv_wr *wr,
struct ibv_recv_wr **bad)
{
int spend, rc = 0;
uint16_t old_prod;
if (unlikely(!bad))
return EINVAL;
if (unlikely(!qp->has_rq)) {
*bad = wr;
return EINVAL;
}
if (unlikely(qp->vqp.qp.state < IBV_QPS_INIT)) {
*bad = wr;
return EINVAL;
}
ionic_rq_spin_lock(qp);
old_prod = qp->rq.queue.prod;
while (wr) {
if (ionic_queue_full(&qp->rq.queue)) {
verbs_info(&ctx->vctx, "recv queue full cons %u prod %u",
qp->rq.queue.cons, qp->rq.queue.prod);
rc = ENOMEM;
goto out;
}
rc = ionic_v1_prep_recv(qp, wr);
if (rc)
goto out;
wr = wr->next;
}
out:
old_prod = (qp->rq.queue.prod - old_prod) & qp->rq.queue.mask;
if (!cq) {
ionic_rq_spin_unlock(qp);
goto out_unlocked;
}
if (ionic_cq_spin_trylock(cq)) {
ionic_rq_spin_unlock(qp);
ionic_cq_spin_lock(cq);
ionic_rq_spin_lock(qp);
}
if (likely(qp->rq.queue.prod != qp->rq.old_prod)) {
/* ring cq doorbell just in time */
spend = (qp->rq.queue.prod - qp->rq.old_prod) & qp->rq.queue.mask;
ionic_reserve_cq(ctx, cq, spend);
qp->rq.old_prod = qp->rq.queue.prod;
if (qp->rq.cmb_ptr) {
ionic_post_recv_cmb(ctx, qp);
} else {
udma_to_device_barrier();
verbs_debug(&ctx->vctx, "dbell qp %u rq val %" PRIx64,
qp->qpid,
ionic_queue_dbell_val(&qp->rq.queue));
ionic_dbell_ring(&ctx->dbpage[ctx->rq_qtype],
ionic_queue_dbell_val(&qp->rq.queue));
}
}
if (qp->rq.flush) {
cq->flush = true;
list_del(&qp->cq_flush_rq);
list_add_tail(&cq->flush_rq, &qp->cq_flush_rq);
}
ionic_rq_spin_unlock(qp);
ionic_cq_spin_unlock(cq);
out_unlocked:
*bad = wr;
return rc;
}
static int ionic_post_send(struct ibv_qp *ibqp,
struct ibv_send_wr *wr,
struct ibv_send_wr **bad)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibqp->context);
struct ionic_qp *qp = to_ionic_qp(ibqp);
struct ionic_cq *cq = to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx);
return ionic_post_send_common(ctx, cq, qp, wr, bad, true);
}
static int ionic_post_recv(struct ibv_qp *ibqp,
struct ibv_recv_wr *wr,
struct ibv_recv_wr **bad)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibqp->context);
struct ionic_qp *qp = to_ionic_qp(ibqp);
struct ionic_cq *cq = to_ionic_vcq_cq(qp->vqp.qp.recv_cq, qp->udma_idx);
return ionic_post_recv_common(ctx, cq, qp, wr, bad);
}
static struct ibv_qp *ionic_create_qp(struct ibv_pd *ibpd,
struct ibv_qp_init_attr *attr)
{
struct ibv_qp_init_attr_ex ex = {
.qp_context = attr->qp_context,
.send_cq = attr->send_cq,
.recv_cq = attr->recv_cq,
.srq = attr->srq,
.cap = attr->cap,
.qp_type = attr->qp_type,
.sq_sig_all = attr->sq_sig_all,
.comp_mask = IBV_QP_INIT_ATTR_PD,
.pd = ibpd,
};
struct verbs_context *vctx;
struct ibv_qp *ibqp;
vctx = container_of(ibpd->context, struct verbs_context, context);
ibqp = vctx->create_qp_ex(&vctx->context, &ex);
attr->cap = ex.cap;
return ibqp;
}
static struct ibv_ah *ionic_create_ah(struct ibv_pd *ibpd,
struct ibv_ah_attr *attr)
{
struct ibv_pd *root_ibpd = to_ionic_root_ibpd(ibpd);
struct uionic_ah_resp resp;
struct ionic_ah *ah;
int rc;
ah = calloc(1, sizeof(*ah));
if (!ah) {
rc = errno;
goto err_ah;
}
rc = ibv_cmd_create_ah(root_ibpd, &ah->ibah, attr,
&resp.ibv_resp, sizeof(resp));
if (rc)
goto err_cmd;
ah->ahid = resp.ahid;
return &ah->ibah;
err_cmd:
free(ah);
err_ah:
errno = rc;
return NULL;
}
static int ionic_destroy_ah(struct ibv_ah *ibah)
{
struct ionic_ah *ah = to_ionic_ah(ibah);
int rc;
rc = ibv_cmd_destroy_ah(ibah);
if (rc)
return rc;
free(ah);
return 0;
}
static struct ibv_mw *ionic_alloc_mw(struct ibv_pd *ibpd,
enum ibv_mw_type type)
{
struct ibv_pd *root_ibpd = to_ionic_root_ibpd(ibpd);
struct ib_uverbs_alloc_mw_resp resp;
struct ibv_alloc_mw cmd;
struct ibv_mw *ibmw;
int rc;
ibmw = calloc(1, sizeof(*ibmw));
if (!ibmw) {
rc = errno;
goto err_mw;
}
rc = ibv_cmd_alloc_mw(root_ibpd, type, ibmw,
&cmd, sizeof(cmd),
&resp, sizeof(resp));
if (rc)
goto err_cmd;
return ibmw;
err_cmd:
free(ibmw);
err_mw:
errno = rc;
return NULL;
}
static int ionic_bind_mw(struct ibv_qp *ibqp, struct ibv_mw *ibmw,
struct ibv_mw_bind *bind)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibqp->context);
struct ionic_qp *qp = to_ionic_qp(ibqp);
struct ionic_cq *cq = to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx);
struct ibv_send_wr *bad;
struct ibv_send_wr wr = {
.opcode = IBV_WR_BIND_MW,
.wr_id = bind->wr_id,
.send_flags = bind->send_flags,
.bind_mw = {
.mw = ibmw,
.rkey = ibmw->rkey,
.bind_info = bind->bind_info,
}
};
int rc;
if (bind->bind_info.length)
wr.bind_mw.rkey = ibv_inc_rkey(ibmw->rkey);
rc = ionic_post_send_common(ctx, cq, qp, &wr, &bad, false);
if (!rc)
ibmw->rkey = wr.bind_mw.rkey;
return rc;
}
static int ionic_dealloc_mw(struct ibv_mw *ibmw)
{
int rc;
rc = ibv_cmd_dealloc_mw(ibmw);
if (rc)
return rc;
free(ibmw);
return 0;
}
static void ionic_free_context(struct ibv_context *ibctx)
{
struct ionic_ctx *ctx = to_ionic_ctx(ibctx);
ionic_tbl_destroy(&ctx->qp_tbl);
pthread_mutex_destroy(&ctx->mut);
ionic_unmap(ctx->dbpage_page, 1u << ctx->pg_shift);
verbs_uninit_context(&ctx->vctx);
free(ctx);
}
bool is_ionic_ctx(struct ibv_context *ibctx)
{
/* whatever we do here must be safe with non-ionic ibctx. */
struct verbs_context *vctx = verbs_get_ctx_op(ibctx, alloc_parent_domain);
return vctx && vctx->alloc_parent_domain == ionic_alloc_parent_domain;
}
static const struct verbs_context_ops ionic_ctx_ops = {
.query_device_ex = ionic_query_device_ex,
.query_port = ionic_query_port,
.alloc_parent_domain = ionic_alloc_parent_domain,
.alloc_pd = ionic_alloc_pd,
.dealloc_pd = ionic_dealloc_pd,
.reg_dmabuf_mr = ionic_reg_dmabuf_mr,
.reg_mr = ionic_reg_mr,
.dereg_mr = ionic_dereg_mr,
.create_cq = ionic_create_cq,
.create_cq_ex = ionic_create_cq_ex,
.poll_cq = ionic_poll_cq,
.req_notify_cq = ionic_req_notify_cq,
.destroy_cq = ionic_destroy_cq,
.create_qp = ionic_create_qp,
.create_qp_ex = ionic_create_qp_ex,
.query_qp = ionic_query_qp,
.modify_qp = ionic_modify_qp,
.destroy_qp = ionic_destroy_qp,
.post_send = ionic_post_send,
.post_recv = ionic_post_recv,
.create_ah = ionic_create_ah,
.destroy_ah = ionic_destroy_ah,
.alloc_mw = ionic_alloc_mw,
.bind_mw = ionic_bind_mw,
.dealloc_mw = ionic_dealloc_mw,
.free_context = ionic_free_context,
};
void ionic_verbs_set_ops(struct ionic_ctx *ctx)
{
verbs_set_ops(&ctx->vctx, &ionic_ctx_ops);
}