/
githubmirror
/
rdma-core
Обзор
Документация
Войти
/
githubmirror
/
rdma-core
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
providers/ionic/ionic_verbs.c
3 561 строка
82 KB
Abhijit Gangurde
ionic: Add support for PHC timestamping
04 авг 2026, 15:31
04 авг 2026, 15:31
8a6fdc2
Код
Авторство
О чём код?
// 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); static void ionic_post_send_cmb(struct ionic_ctx *ctx, struct ionic_qp *qp); static int ionic_v1_prep_one_rc(struct ionic_qp *qp, struct ibv_send_wr *wr, bool send_path); #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" (0)); 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_phc_update(struct ionic_phc *phc, struct ib_uverbs_clock_info *state) { uint32_t sign, *state_sign; int retry; state_sign = &state->sign; for (retry = 10; true; --retry) { if (!retry) return EBUSY; sign = atomic_load((_Atomic(uint32_t) *)state_sign); /* odd sign means state is updating */ if (sign & 1) continue; phc->mask = state->mask; phc->cycles = state->cycles; phc->nsec = state->nsec; phc->frac = state->frac; phc->mult = state->mult; phc->shift = state->shift; /* same sign means state did not update */ if (sign == atomic_load((_Atomic(uint32_t) *)state_sign)) break; } return 0; } 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 = {}; struct ionic_ctx *ctx = to_ionic_ctx(ibctx); 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; if (ctx->phc_state) { struct ionic_phc phc = {}; rc = ionic_phc_update(&phc, ctx->phc_state); if (!rc) { const uint64_t ns_per_ms = 1000000; const uint32_t shift = phc.shift; const uint32_t mult = phc.mult; if (ex_size > offsetof(typeof(*ex), completion_timestamp_mask)) ex->completion_timestamp_mask = phc.mask; if (ex_size > offsetof(typeof(*ex), hca_core_clock)) ex->hca_core_clock = ((ns_per_ms << shift) + (mult >> 1)) / mult; } } 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_td *ionic_alloc_td(struct ibv_context *ibctx, struct ibv_td_init_attr *init_attr) { struct ionic_td *td; if (init_attr->comp_mask) { errno = EOPNOTSUPP; return NULL; } td = calloc(1, sizeof(*td)); if (!td) { errno = ENOMEM; return NULL; } td->ibtd.context = ibctx; atomic_init(&td->refcount, 1); return &td->ibtd; } static int ionic_dealloc_td(struct ibv_td *ibtd) { struct ionic_td *td = to_ionic_td(ibtd); if (atomic_load(&td->refcount) > 1) return EBUSY; free(td); return 0; } 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->td) { pd->td = to_ionic_td(attr->td); atomic_fetch_add(&pd->td->refcount, 1); } 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; } else if (pd->td) { atomic_fetch_sub(&pd->td->refcount, 1); } 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 = pd ? ionic_pd_lockfree(&pd->ibpd) : 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); } static uint64_t ionic_phc_ts_to_ns(struct ionic_phc *phc, uint64_t ts) { uint64_t dt, dt_sign, ns; dt = (ts - phc->cycles) & phc->mask; dt_sign = phc->mask ^ (phc->mask >> 1); ns = phc->nsec; if (dt & dt_sign) { dt = -dt & phc->mask; ns -= ((dt * phc->mult) - phc->frac) >> phc->shift; } else { ns += ((dt * phc->mult) + phc->frac) >> phc->shift; } return ns; } /* * 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; if (!vcq->cur_wc_pending) { 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; /* if there is an error after polling the cur_wc, * don't replace it in the next call to start. */ vcq->cur_wc_pending = true; } if (vcq->phc_update) { struct ionic_ctx *ctx = to_ionic_ctx(ibcq->context); rc = ionic_phc_update(&vcq->phc, ctx->phc_state); if (rc) return rc; } /* success, so the next call to start should replace cur_wc */ vcq->cur_wc_pending = false; 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 uint64_t ionic_wc_read_completion_ts(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_timestamp; } static uint64_t ionic_wc_read_completion_wallclock_ns(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 ionic_phc_ts_to_ns(&vcq->phc, vcq->cur_wc_timestamp); } 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; if (ex->wc_flags & IBV_WC_EX_WITH_COMPLETION_TIMESTAMP) { if (!ctx->phc_state) { rc = EOPNOTSUPP; goto err_udma; } vcq->vcq.cq_ex.read_completion_ts = ionic_wc_read_completion_ts; } if (ex->wc_flags & IBV_WC_EX_WITH_COMPLETION_TIMESTAMP_WALLCLOCK) { if (!ctx->phc_state) { rc = EOPNOTSUPP; goto err_udma; } vcq->phc_update = true; vcq->vcq.cq_ex.read_completion_wallclock_ns = ionic_wc_read_completion_wallclock_ns; } 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; uint64_t wqe_idx; if (!qp->rq.flush) return 0; if (ionic_queue_empty(&qp->rq.queue)) return 0; wqe = ionic_queue_at_cons(&qp->rq.queue); wqe_idx = le64toh(wqe->base.wqe_idx); ctx = to_ionic_ctx(qp->vqp.qp.context); /* wqe_idx must be a valid queue index */ if (unlikely(wqe_idx >> qp->rq.queue.depth_log2)) { verbs_err(&ctx->vctx, "invalid id %#lx", (unsigned long)wqe_idx); return -EIO; } /* wqe_idx must indicate a request that is outstanding */ meta = &qp->rq.meta[wqe_idx]; if (unlikely(meta->next != IONIC_META_POSTED)) { verbs_err(&ctx->vctx, "wqe not posted %#lx", (unsigned long)wqe_idx); 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; uint64_t wqe_idx_timestamp; uint16_t vlan_tag, wqe_idx; uint32_t src_qpn, st_len; uint8_t op; if (cqe_qp->rq.flush) return 0; qp = cqe_qp; st_len = be32toh(cqe->status_length); /* ignore wqe_idx 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_idx_timestamp = le64toh(cqe->recv.wqe_idx_timestamp); wqe_idx = wqe_idx_timestamp & IONIC_V1_CQE_WQE_IDX_MASK; /* wqe_idx must be a valid queue index */ if (unlikely(wqe_idx >> qp->rq.queue.depth_log2)) { verbs_err(&ctx->vctx, "invalid id %#lx", (unsigned long)wqe_idx); return -EIO; } /* wqe_idx must indicate a request that is outstanding */ meta = &qp->rq.meta[qp->rq.meta_idx[wqe_idx]]; 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)wqe_idx, qp->rq.meta_idx[wqe_idx], 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; cq->vcq->cur_wc_timestamp = wqe_idx_timestamp >> IONIC_V1_CQE_TIMESTAMP_SHIFT; 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); cq->vcq->cur_wc_timestamp = meta->cqe_timestamp; 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; uint64_t timestamp; 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; } timestamp = le64toh(cqe->send.npg_wqe_idx_timestamp) >> IONIC_V1_CQE_TIMESTAMP_SHIFT; for (cqe_idx = qp->sq.msn_cons; cqe_idx != cqe_seq; cqe_idx = ionic_queue_next(&qp->sq.queue, cqe_idx)) { meta = &qp->sq.meta[cqe_idx]; if (meta->remote) meta->cqe_timestamp = timestamp; } 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; uint64_t wqe_idx_timestamp; uint16_t wqe_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; } wqe_idx_timestamp = le64toh(cqe->send.npg_wqe_idx_timestamp); wqe_idx = wqe_idx_timestamp & qp->sq.queue.mask; meta = &qp->sq.meta[wqe_idx]; meta->local_comp = true; meta->cqe_timestamp = wqe_idx_timestamp >> IONIC_V1_CQE_TIMESTAMP_SHIFT; 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 void ionic_wr_start(struct ibv_qp_ex *ibqp_ex) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ionic_cq *cq = to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx); ionic_cq_spin_lock(cq); ionic_sq_spin_lock(qp); qp->ex_rc = 0; if (unlikely(!qp->has_sq)) qp->ex_rc = EINVAL; if (unlikely(qp->vqp.qp.state < IBV_QPS_RTS)) qp->ex_rc = EINVAL; } static void ionic_wr_abort(struct ibv_qp_ex *ibqp_ex) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ionic_cq *cq = to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx); qp->sq.queue.prod = qp->sq.old_prod; ionic_sq_spin_unlock(qp); ionic_cq_spin_unlock(cq); } static int ionic_wr_complete(struct ibv_qp_ex *ibqp_ex) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ionic_cq *cq = to_ionic_vcq_cq(qp->vqp.qp.send_cq, qp->udma_idx); struct ionic_ctx *ctx = to_ionic_ctx(qp->vqp.qp.context); int spend, rc = qp->ex_rc; if (rc) { ionic_wr_abort(ibqp_ex); return rc; } 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); return 0; } static void ionic_wr_set(struct ionic_qp *qp, int wr_flags, size_t num_sge, const struct ibv_sge *sg_list) { if (qp->ex_rc) return; qp->ex_wr.send_flags = qp->vqp.qp_ex.wr_flags | wr_flags, qp->ex_wr.sg_list = (struct ibv_sge *)sg_list; qp->ex_wr.num_sge = num_sge; qp->ex_rc = ionic_v1_prep_one_rc(qp, &qp->ex_wr, true); } static void ionic_wr_set_sge(struct ibv_qp_ex *ibqp_ex, uint32_t lkey, uint64_t addr, uint32_t length) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_sge sge = { .addr = addr, .length = length, .lkey = lkey, }; ionic_wr_set(qp, 0, 1, &sge); } static void ionic_wr_set_sge_list(struct ibv_qp_ex *ibqp_ex, size_t num_sge, const struct ibv_sge *sg_list) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); ionic_wr_set(qp, 0, num_sge, sg_list); } static void ionic_wr_set_inline_data(struct ibv_qp_ex *ibqp_ex, void *addr, size_t length) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_sge sge = { .addr = (uintptr_t)addr, .length = length, }; ionic_wr_set(qp, IBV_SEND_INLINE, 1, &sge); } static void ionic_wr_set_inline_data_list(struct ibv_qp_ex *ibqp_ex, size_t num_buf, const struct ibv_data_buf *buf_list) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_sge *sg_list; int sge_i; sg_list = calloc(num_buf, sizeof(*sg_list)); if (!sg_list) { qp->ex_rc = ENOMEM; return; } for (sge_i = 0; sge_i < num_buf; ++sge_i) { sg_list[sge_i].addr = (uintptr_t)buf_list[sge_i].addr; sg_list[sge_i].length = buf_list[sge_i].length; } ionic_wr_set(qp, IBV_SEND_INLINE, num_buf, sg_list); free(sg_list); } static void ionic_wr_rdma_write(struct ibv_qp_ex *ibqp_ex, uint32_t rkey, uint64_t remote_addr) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_send_wr wr = { .wr_id = qp->vqp.qp_ex.wr_id, .opcode = IBV_WR_RDMA_WRITE, .wr.rdma.remote_addr = remote_addr, .wr.rdma.rkey = rkey, }; qp->ex_wr = wr; } static void ionic_wr_rdma_write_imm(struct ibv_qp_ex *ibqp_ex, uint32_t rkey, uint64_t remote_addr, __be32 imm_data) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_send_wr wr = { .wr_id = qp->vqp.qp_ex.wr_id, .opcode = IBV_WR_RDMA_WRITE_WITH_IMM, .imm_data = imm_data, .wr.rdma.remote_addr = remote_addr, .wr.rdma.rkey = rkey, }; qp->ex_wr = wr; } static void ionic_wr_rdma_read(struct ibv_qp_ex *ibqp_ex, uint32_t rkey, uint64_t remote_addr) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_send_wr wr = { .wr_id = qp->vqp.qp_ex.wr_id, .opcode = IBV_WR_RDMA_READ, .wr.rdma.remote_addr = remote_addr, .wr.rdma.rkey = rkey, }; qp->ex_wr = wr; } static void ionic_wr_send(struct ibv_qp_ex *ibqp_ex) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_send_wr wr = { .wr_id = qp->vqp.qp_ex.wr_id, .opcode = IBV_WR_SEND, }; qp->ex_wr = wr; } static void ionic_wr_send_imm(struct ibv_qp_ex *ibqp_ex, __be32 imm_data) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_send_wr wr = { .wr_id = qp->vqp.qp_ex.wr_id, .opcode = IBV_WR_SEND_WITH_IMM, .imm_data = imm_data, }; qp->ex_wr = wr; } static void ionic_wr_send_inv(struct ibv_qp_ex *ibqp_ex, uint32_t invalidate_rkey) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_send_wr wr = { .wr_id = qp->vqp.qp_ex.wr_id, .opcode = IBV_WR_SEND_WITH_INV, .invalidate_rkey = invalidate_rkey, }; qp->ex_wr = wr; } static void ionic_wr_atomic_cmp_swp(struct ibv_qp_ex *ibqp_ex, uint32_t rkey, uint64_t remote_addr, uint64_t compare, uint64_t swap) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_send_wr wr = { .wr_id = qp->vqp.qp_ex.wr_id, .opcode = IBV_WR_ATOMIC_CMP_AND_SWP, .wr.atomic.remote_addr = remote_addr, .wr.atomic.compare_add = compare, .wr.atomic.swap = swap, .wr.atomic.rkey = rkey, }; qp->ex_wr = wr; } static void ionic_wr_atomic_fetch_add(struct ibv_qp_ex *ibqp_ex, uint32_t rkey, uint64_t remote_addr, uint64_t add) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_send_wr wr = { .wr_id = qp->vqp.qp_ex.wr_id, .opcode = IBV_WR_ATOMIC_FETCH_AND_ADD, .wr.atomic.remote_addr = remote_addr, .wr.atomic.compare_add = add, .wr.atomic.rkey = rkey, }; qp->ex_wr = wr; } static void ionic_wr_bind_mw(struct ibv_qp_ex *ibqp_ex, struct ibv_mw *mw, uint32_t rkey, const struct ibv_mw_bind_info *bind_info) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_send_wr wr = { .wr_id = qp->vqp.qp_ex.wr_id, .opcode = IBV_WR_BIND_MW, .bind_mw.mw = mw, .bind_mw.rkey = rkey, .bind_mw.bind_info = *bind_info, }; qp->ex_wr = wr; ionic_wr_set(qp, 0, 0, NULL); } static void ionic_wr_local_inv(struct ibv_qp_ex *ibqp_ex, uint32_t invalidate_rkey) { struct ionic_qp *qp = to_ionic_qp_ex(ibqp_ex); struct ibv_send_wr wr = { .wr_id = qp->vqp.qp_ex.wr_id, .opcode = IBV_WR_LOCAL_INV, .invalidate_rkey = invalidate_rkey, }; qp->ex_wr = wr; ionic_wr_set(qp, 0, 0, NULL); } 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; if ((ex->comp_mask & IONIC_QP_REQUIRED_COMP_MASK) != IONIC_QP_REQUIRED_COMP_MASK) { rc = ENOTSUP; goto err_qp; } if (ex->qp_type == IBV_QPT_RC) { if (!check_comp_mask(ex->comp_mask, IONIC_QP_SUPPORTED_COMP_MASK_RC)) { rc = ENOTSUP; goto err_qp; } } else if (ex->qp_type == IBV_QPT_UD) { if (!check_comp_mask(ex->comp_mask, IONIC_QP_SUPPORTED_COMP_MASK_UD)) { rc = ENOTSUP; goto err_qp; } } else { rc = ENOTSUP; goto err_qp; } if (!check_comp_mask(ex->send_ops_flags, IONIC_QP_SUPPORTED_SEND_OPS_FLAGS)) { rc = ENOTSUP; goto err_qp; } 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 = ex->pd ? ionic_pd_lockfree(ex->pd) : false; qp->sig_all = ex->sq_sig_all; 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), NULL); 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); } /* management of the critical section */ qp->vqp.qp_ex.wr_start = ionic_wr_start; qp->vqp.qp_ex.wr_abort = ionic_wr_abort; qp->vqp.qp_ex.wr_complete = ionic_wr_complete; /* setters cause wqe to be posted without doorbell */ qp->vqp.qp_ex.wr_set_sge_list = ionic_wr_set_sge_list; qp->vqp.qp_ex.wr_set_sge = ionic_wr_set_sge; qp->vqp.qp_ex.wr_set_inline_data_list = ionic_wr_set_inline_data_list; qp->vqp.qp_ex.wr_set_inline_data = ionic_wr_set_inline_data; /* builders supply opcode for the wqe to be posted by setters */ qp->vqp.qp_ex.wr_send = ionic_wr_send; qp->vqp.qp_ex.wr_send_imm = ionic_wr_send_imm; qp->vqp.qp_ex.wr_send_inv = ionic_wr_send_inv; qp->vqp.qp_ex.wr_rdma_write = ionic_wr_rdma_write; qp->vqp.qp_ex.wr_rdma_write_imm = ionic_wr_rdma_write_imm; qp->vqp.qp_ex.wr_rdma_read = ionic_wr_rdma_read; qp->vqp.qp_ex.wr_atomic_cmp_swp = ionic_wr_atomic_cmp_swp; qp->vqp.qp_ex.wr_atomic_fetch_add = ionic_wr_atomic_fetch_add; qp->vqp.qp_ex.wr_bind_mw = ionic_wr_bind_mw; qp->vqp.qp_ex.wr_local_inv = ionic_wr_local_inv; qp->vqp.comp_mask = VERBS_QP_EX; 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_idx = htole64(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 (qp->sig_all || (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_idx = htole64(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); if (ctx->phc_state) munmap(ctx->phc_state, IONIC_PAGE_SIZE); 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_td = ionic_alloc_td, .dealloc_td = ionic_dealloc_td, .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); }