| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2022, Microsoft Corporation. All rights reserved. |
| */ |
| |
| #include "mana_ib.h" |
| |
| int mana_ib_create_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *attr, |
| struct uverbs_attr_bundle *attrs) |
| { |
| struct ib_udata *udata = &attrs->driver_udata; |
| struct mana_ib_cq *cq = container_of(ibcq, struct mana_ib_cq, ibcq); |
| struct mana_ib_create_cq_resp resp = {}; |
| struct mana_ib_ucontext *mana_ucontext; |
| struct ib_device *ibdev = ibcq->device; |
| struct mana_ib_create_cq ucmd = {}; |
| struct mana_ib_dev *mdev; |
| struct gdma_context *gc; |
| bool is_rnic_cq; |
| u32 doorbell; |
| u32 buf_size; |
| int err; |
| |
| mdev = container_of(ibdev, struct mana_ib_dev, ib_dev); |
| gc = mdev_to_gc(mdev); |
| |
| cq->comp_vector = attr->comp_vector % ibdev->num_comp_vectors; |
| cq->cq_handle = INVALID_MANA_HANDLE; |
| |
| if (udata) { |
| if (udata->inlen < offsetof(struct mana_ib_create_cq, flags)) |
| return -EINVAL; |
| |
| err = ib_copy_from_udata(&ucmd, udata, min(sizeof(ucmd), udata->inlen)); |
| if (err) { |
| ibdev_dbg(ibdev, "Failed to copy from udata for create cq, %d\n", err); |
| return err; |
| } |
| |
| is_rnic_cq = !!(ucmd.flags & MANA_IB_CREATE_RNIC_CQ); |
| |
| if ((!is_rnic_cq && attr->cqe > mdev->adapter_caps.max_qp_wr) || |
| attr->cqe > U32_MAX / COMP_ENTRY_SIZE) { |
| ibdev_dbg(ibdev, "CQE %d exceeding limit\n", attr->cqe); |
| return -EINVAL; |
| } |
| |
| cq->cqe = attr->cqe; |
| err = mana_ib_create_queue(mdev, ucmd.buf_addr, cq->cqe * COMP_ENTRY_SIZE, |
| &cq->queue); |
| if (err) { |
| ibdev_dbg(ibdev, "Failed to create queue for create cq, %d\n", err); |
| return err; |
| } |
| |
| mana_ucontext = rdma_udata_to_drv_context(udata, struct mana_ib_ucontext, |
| ibucontext); |
| doorbell = mana_ucontext->doorbell; |
| } else { |
| is_rnic_cq = true; |
| buf_size = MANA_PAGE_ALIGN(roundup_pow_of_two(attr->cqe * COMP_ENTRY_SIZE)); |
| cq->cqe = buf_size / COMP_ENTRY_SIZE; |
| err = mana_ib_create_kernel_queue(mdev, buf_size, GDMA_CQ, &cq->queue); |
| if (err) { |
| ibdev_dbg(ibdev, "Failed to create kernel queue for create cq, %d\n", err); |
| return err; |
| } |
| doorbell = gc->mana_ib.doorbell; |
| } |
| |
| if (is_rnic_cq) { |
| err = mana_ib_gd_create_cq(mdev, cq, doorbell); |
| if (err) { |
| ibdev_dbg(ibdev, "Failed to create RNIC cq, %d\n", err); |
| goto err_destroy_queue; |
| } |
| |
| err = mana_ib_install_cq_cb(mdev, cq); |
| if (err) { |
| ibdev_dbg(ibdev, "Failed to install cq callback, %d\n", err); |
| goto err_destroy_rnic_cq; |
| } |
| } |
| |
| if (udata) { |
| resp.cqid = cq->queue.id; |
| err = ib_copy_to_udata(udata, &resp, min(sizeof(resp), udata->outlen)); |
| if (err) { |
| ibdev_dbg(&mdev->ib_dev, "Failed to copy to udata, %d\n", err); |
| goto err_remove_cq_cb; |
| } |
| } |
| |
| spin_lock_init(&cq->cq_lock); |
| INIT_LIST_HEAD(&cq->list_send_qp); |
| INIT_LIST_HEAD(&cq->list_recv_qp); |
| |
| return 0; |
| |
| err_remove_cq_cb: |
| mana_ib_remove_cq_cb(mdev, cq); |
| err_destroy_rnic_cq: |
| mana_ib_gd_destroy_cq(mdev, cq); |
| err_destroy_queue: |
| mana_ib_destroy_queue(mdev, &cq->queue); |
| |
| return err; |
| } |
| |
| int mana_ib_destroy_cq(struct ib_cq *ibcq, struct ib_udata *udata) |
| { |
| struct mana_ib_cq *cq = container_of(ibcq, struct mana_ib_cq, ibcq); |
| struct ib_device *ibdev = ibcq->device; |
| struct mana_ib_dev *mdev; |
| |
| mdev = container_of(ibdev, struct mana_ib_dev, ib_dev); |
| |
| mana_ib_remove_cq_cb(mdev, cq); |
| |
| /* Ignore return code as there is not much we can do about it. |
| * The error message is printed inside. |
| */ |
| mana_ib_gd_destroy_cq(mdev, cq); |
| |
| mana_ib_destroy_queue(mdev, &cq->queue); |
| |
| return 0; |
| } |
| |
| static void mana_ib_cq_handler(void *ctx, struct gdma_queue *gdma_cq) |
| { |
| struct mana_ib_cq *cq = ctx; |
| |
| if (cq->ibcq.comp_handler) |
| cq->ibcq.comp_handler(&cq->ibcq, cq->ibcq.cq_context); |
| } |
| |
| int mana_ib_install_cq_cb(struct mana_ib_dev *mdev, struct mana_ib_cq *cq) |
| { |
| struct gdma_context *gc = mdev_to_gc(mdev); |
| struct gdma_queue *gdma_cq; |
| |
| if (cq->queue.id >= gc->max_num_cqs) |
| return -EINVAL; |
| /* Create CQ table entry */ |
| WARN_ON(gc->cq_table[cq->queue.id]); |
| if (cq->queue.kmem) |
| gdma_cq = cq->queue.kmem; |
| else |
| gdma_cq = kzalloc(sizeof(*gdma_cq), GFP_KERNEL); |
| if (!gdma_cq) |
| return -ENOMEM; |
| |
| gdma_cq->cq.context = cq; |
| gdma_cq->type = GDMA_CQ; |
| gdma_cq->cq.callback = mana_ib_cq_handler; |
| gdma_cq->id = cq->queue.id; |
| gc->cq_table[cq->queue.id] = gdma_cq; |
| return 0; |
| } |
| |
| void mana_ib_remove_cq_cb(struct mana_ib_dev *mdev, struct mana_ib_cq *cq) |
| { |
| struct gdma_context *gc = mdev_to_gc(mdev); |
| |
| if (cq->queue.id >= gc->max_num_cqs || cq->queue.id == INVALID_QUEUE_ID) |
| return; |
| |
| if (cq->queue.kmem) |
| /* Then it will be cleaned and removed by the mana */ |
| return; |
| |
| kfree(gc->cq_table[cq->queue.id]); |
| gc->cq_table[cq->queue.id] = NULL; |
| } |
| |
| int mana_ib_arm_cq(struct ib_cq *ibcq, enum ib_cq_notify_flags flags) |
| { |
| struct mana_ib_cq *cq = container_of(ibcq, struct mana_ib_cq, ibcq); |
| struct gdma_queue *gdma_cq = cq->queue.kmem; |
| |
| if (!gdma_cq) |
| return -EINVAL; |
| |
| mana_gd_ring_cq(gdma_cq, SET_ARM_BIT); |
| return 0; |
| } |
| |
| static inline void handle_ud_sq_cqe(struct mana_ib_qp *qp, struct gdma_comp *cqe) |
| { |
| struct mana_rdma_cqe *rdma_cqe = (struct mana_rdma_cqe *)cqe->cqe_data; |
| struct gdma_queue *wq = qp->ud_qp.queues[MANA_UD_SEND_QUEUE].kmem; |
| struct ud_sq_shadow_wqe *shadow_wqe; |
| |
| shadow_wqe = shadow_queue_get_next_to_complete(&qp->shadow_sq); |
| if (!shadow_wqe) |
| return; |
| |
| shadow_wqe->header.error_code = rdma_cqe->ud_send.vendor_error; |
| |
| wq->tail += shadow_wqe->header.posted_wqe_size; |
| shadow_queue_advance_next_to_complete(&qp->shadow_sq); |
| } |
| |
| static inline void handle_ud_rq_cqe(struct mana_ib_qp *qp, struct gdma_comp *cqe) |
| { |
| struct mana_rdma_cqe *rdma_cqe = (struct mana_rdma_cqe *)cqe->cqe_data; |
| struct gdma_queue *wq = qp->ud_qp.queues[MANA_UD_RECV_QUEUE].kmem; |
| struct ud_rq_shadow_wqe *shadow_wqe; |
| |
| shadow_wqe = shadow_queue_get_next_to_complete(&qp->shadow_rq); |
| if (!shadow_wqe) |
| return; |
| |
| shadow_wqe->byte_len = rdma_cqe->ud_recv.msg_len; |
| shadow_wqe->src_qpn = rdma_cqe->ud_recv.src_qpn; |
| shadow_wqe->header.error_code = IB_WC_SUCCESS; |
| |
| wq->tail += shadow_wqe->header.posted_wqe_size; |
| shadow_queue_advance_next_to_complete(&qp->shadow_rq); |
| } |
| |
| static void mana_handle_cqe(struct mana_ib_dev *mdev, struct gdma_comp *cqe) |
| { |
| struct mana_ib_qp *qp = mana_get_qp_ref(mdev, cqe->wq_num, cqe->is_sq); |
| |
| if (!qp) |
| return; |
| |
| if (qp->ibqp.qp_type == IB_QPT_GSI || qp->ibqp.qp_type == IB_QPT_UD) { |
| if (cqe->is_sq) |
| handle_ud_sq_cqe(qp, cqe); |
| else |
| handle_ud_rq_cqe(qp, cqe); |
| } |
| |
| mana_put_qp_ref(qp); |
| } |
| |
| static void fill_verbs_from_shadow_wqe(struct mana_ib_qp *qp, struct ib_wc *wc, |
| const struct shadow_wqe_header *shadow_wqe) |
| { |
| const struct ud_rq_shadow_wqe *ud_wqe = (const struct ud_rq_shadow_wqe *)shadow_wqe; |
| |
| wc->wr_id = shadow_wqe->wr_id; |
| wc->status = shadow_wqe->error_code; |
| wc->opcode = shadow_wqe->opcode; |
| wc->vendor_err = shadow_wqe->error_code; |
| wc->wc_flags = 0; |
| wc->qp = &qp->ibqp; |
| wc->pkey_index = 0; |
| |
| if (shadow_wqe->opcode == IB_WC_RECV) { |
| wc->byte_len = ud_wqe->byte_len; |
| wc->src_qp = ud_wqe->src_qpn; |
| wc->wc_flags |= IB_WC_GRH; |
| } |
| } |
| |
| static int mana_process_completions(struct mana_ib_cq *cq, int nwc, struct ib_wc *wc) |
| { |
| struct shadow_wqe_header *shadow_wqe; |
| struct mana_ib_qp *qp; |
| int wc_index = 0; |
| |
| /* process send shadow queue completions */ |
| list_for_each_entry(qp, &cq->list_send_qp, cq_send_list) { |
| while ((shadow_wqe = shadow_queue_get_next_to_consume(&qp->shadow_sq)) |
| != NULL) { |
| if (wc_index >= nwc) |
| goto out; |
| |
| fill_verbs_from_shadow_wqe(qp, &wc[wc_index], shadow_wqe); |
| shadow_queue_advance_consumer(&qp->shadow_sq); |
| wc_index++; |
| } |
| } |
| |
| /* process recv shadow queue completions */ |
| list_for_each_entry(qp, &cq->list_recv_qp, cq_recv_list) { |
| while ((shadow_wqe = shadow_queue_get_next_to_consume(&qp->shadow_rq)) |
| != NULL) { |
| if (wc_index >= nwc) |
| goto out; |
| |
| fill_verbs_from_shadow_wqe(qp, &wc[wc_index], shadow_wqe); |
| shadow_queue_advance_consumer(&qp->shadow_rq); |
| wc_index++; |
| } |
| } |
| |
| out: |
| return wc_index; |
| } |
| |
| int mana_ib_poll_cq(struct ib_cq *ibcq, int num_entries, struct ib_wc *wc) |
| { |
| struct mana_ib_cq *cq = container_of(ibcq, struct mana_ib_cq, ibcq); |
| struct mana_ib_dev *mdev = container_of(ibcq->device, struct mana_ib_dev, ib_dev); |
| struct gdma_queue *queue = cq->queue.kmem; |
| struct gdma_comp gdma_cqe; |
| unsigned long flags; |
| int num_polled = 0; |
| int comp_read, i; |
| |
| spin_lock_irqsave(&cq->cq_lock, flags); |
| for (i = 0; i < num_entries; i++) { |
| comp_read = mana_gd_poll_cq(queue, &gdma_cqe, 1); |
| if (comp_read < 1) |
| break; |
| mana_handle_cqe(mdev, &gdma_cqe); |
| } |
| |
| num_polled = mana_process_completions(cq, num_entries, wc); |
| spin_unlock_irqrestore(&cq->cq_lock, flags); |
| |
| return num_polled; |
| } |