| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (C) 2025 Intel Corporation */ |
| |
| #include <linux/bitfield.h> |
| #include <net/libeth/rx.h> |
| |
| #include <linux/net/intel/libie/controlq.h> |
| |
| #define LIBIE_CTLQ_DESC_QWORD0(sz) \ |
| (LIBIE_CTLQ_DESC_FLAG_BUF | \ |
| LIBIE_CTLQ_DESC_FLAG_RD | \ |
| FIELD_PREP(LIBIE_CTLQ_DESC_DATA_LEN, sz)) |
| |
| /** |
| * libie_ctlq_free_fq - free fill queue resources, including buffers |
| * @ctlq: Rx control queue whose resources need to be freed |
| */ |
| static void libie_ctlq_free_fq(struct libie_ctlq_info *ctlq) |
| { |
| struct libeth_fq fq = { |
| .fqes = ctlq->rx_fqes, |
| .pp = ctlq->pp, |
| }; |
| |
| for (u32 ntc = ctlq->next_to_clean; ntc != ctlq->next_to_post; ) { |
| page_pool_put_full_netmem(fq.pp, fq.fqes[ntc].netmem, false); |
| |
| if (++ntc >= ctlq->ring_len) |
| ntc = 0; |
| } |
| |
| libeth_rx_fq_destroy(&fq); |
| } |
| |
| /** |
| * libie_ctlq_init_fq - initialize fill queue for an Rx controlq |
| * @ctlq: control queue that needs Rx buffer allocation |
| * |
| * Return: %0 on success, -%errno on failure |
| */ |
| static int libie_ctlq_init_fq(struct libie_ctlq_info *ctlq) |
| { |
| struct libeth_fq fq = { |
| .count = ctlq->ring_len, |
| .truesize = LIBIE_CTLQ_MAX_BUF_LEN, |
| .nid = NUMA_NO_NODE, |
| .type = LIBETH_FQE_SHORT, |
| .hsplit = true, |
| .no_napi = true, |
| }; |
| int err; |
| |
| err = libeth_rx_fq_create(&fq, ctlq->dev); |
| if (err) |
| return err; |
| |
| ctlq->pp = fq.pp; |
| ctlq->rx_fqes = fq.fqes; |
| ctlq->truesize = fq.truesize; |
| |
| return 0; |
| } |
| |
| /** |
| * libie_ctlq_prep_rx_desc - prepare the descriptor with a new address |
| * @desc: descriptor to (re)initialize |
| * @addr: physical address to put into descriptor |
| * @mem_truesize: size of the accessible memory |
| */ |
| static void libie_ctlq_prep_rx_desc(struct libie_ctlq_desc *desc, |
| dma_addr_t addr, u32 mem_truesize) |
| { |
| u64 qword; |
| |
| qword = LIBIE_CTLQ_DESC_QWORD0(mem_truesize); |
| desc->qword0 = cpu_to_le64(qword); |
| |
| qword = FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_HIGH, |
| upper_32_bits(addr)) | |
| FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_LOW, |
| lower_32_bits(addr)); |
| desc->qword3 = cpu_to_le64(qword); |
| } |
| |
| /** |
| * libie_ctlq_post_rx_buffs - post buffers to descriptor ring |
| * @ctlq: control queue that requires Rx descriptor ring to be initialized with |
| * new Rx buffers |
| * |
| * The caller must make sure that calls to libie_ctlq_post_rx_buffs() |
| * and libie_ctlq_recv() for each queue are either serialized |
| * or used under ctlq->lock. |
| * |
| * Return: %0 on success, -%ENOMEM if any buffer could not be allocated |
| */ |
| int libie_ctlq_post_rx_buffs(struct libie_ctlq_info *ctlq) |
| { |
| u32 ntp = ctlq->next_to_post, ntc = ctlq->next_to_clean, num_to_post; |
| const struct libeth_fq_fp fq = { |
| .pp = ctlq->pp, |
| .fqes = ctlq->rx_fqes, |
| .truesize = ctlq->truesize, |
| .count = ctlq->ring_len, |
| }; |
| int ret = 0; |
| |
| num_to_post = (ntc > ntp ? 0 : ctlq->ring_len) + ntc - ntp - 1; |
| |
| while (num_to_post--) { |
| dma_addr_t addr; |
| |
| ctlq->descs[ntp] = (struct libie_ctlq_desc) {}; |
| |
| addr = libeth_rx_alloc(&fq, ntp); |
| if (unlikely(addr == DMA_MAPPING_ERROR)) { |
| ret = -ENOMEM; |
| goto post_bufs; |
| } |
| |
| libie_ctlq_prep_rx_desc(&ctlq->descs[ntp], addr, fq.truesize); |
| |
| if (unlikely(++ntp == ctlq->ring_len)) |
| ntp = 0; |
| } |
| |
| post_bufs: |
| if (likely(ctlq->next_to_post != ntp)) { |
| ctlq->next_to_post = ntp; |
| |
| dma_wmb(); |
| writel(ntp, ctlq->reg.tail); |
| } |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_post_rx_buffs, "LIBIE_CP"); |
| |
| /** |
| * libie_ctlq_free_tx_msgs - Free Tx control queue messages |
| * @ctlq: Tx control queue being destroyed |
| * @num_msgs: number of messages allocated so far |
| */ |
| static void libie_ctlq_free_tx_msgs(struct libie_ctlq_info *ctlq, |
| u32 num_msgs) |
| { |
| for (u32 i = 0; i < num_msgs; i++) |
| kfree(ctlq->tx_msg[i]); |
| |
| kvfree(ctlq->tx_msg); |
| } |
| |
| /** |
| * libie_ctlq_alloc_tx_msgs - Allocate Tx control queue messages |
| * @ctlq: Tx control queue being created |
| * |
| * Return: %0 on success, -%ENOMEM on allocation error |
| */ |
| static int libie_ctlq_alloc_tx_msgs(struct libie_ctlq_info *ctlq) |
| { |
| ctlq->tx_msg = kvzalloc_objs(*ctlq->tx_msg, ctlq->ring_len); |
| if (!ctlq->tx_msg) |
| return -ENOMEM; |
| |
| for (u32 i = 0; i < ctlq->ring_len; i++) { |
| ctlq->tx_msg[i] = kzalloc_obj(*ctlq->tx_msg[i]); |
| if (!ctlq->tx_msg[i]) { |
| libie_ctlq_free_tx_msgs(ctlq, i); |
| return -ENOMEM; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * libie_cp_free_desc_mem - free the previously allocated descriptor DMA memory |
| * @dev: device information |
| * @mem: DMA memory information |
| */ |
| static void libie_cp_free_desc_mem(struct device *dev, |
| struct libie_cp_dma_mem *mem) |
| { |
| dma_free_coherent(dev, mem->size, mem->va, mem->pa); |
| mem->va = NULL; |
| } |
| |
| /** |
| * libie_ctlq_dealloc_ring_res - free memory allocated for control queue |
| * @ctlq: control queue that requires its ring memory to be freed |
| * |
| * Free the memory used by the ring, buffers and other related structures. |
| */ |
| static void libie_ctlq_dealloc_ring_res(struct libie_ctlq_info *ctlq) |
| { |
| struct libie_cp_dma_mem *dma = &ctlq->ring_mem; |
| |
| if (ctlq->type == LIBIE_CTLQ_TYPE_TX) |
| libie_ctlq_free_tx_msgs(ctlq, ctlq->ring_len); |
| else |
| libie_ctlq_free_fq(ctlq); |
| |
| libie_cp_free_desc_mem(ctlq->dev, dma); |
| } |
| |
| /** |
| * libie_cp_alloc_desc_mem - allocate DMA memory for descriptor ring |
| * @dev: device information |
| * @mem: memory for DMA information to be stored |
| * @size: size of the memory to allocate |
| * |
| * Return: virtual address of DMA memory or NULL. |
| */ |
| static void *libie_cp_alloc_desc_mem(struct device *dev, |
| struct libie_cp_dma_mem *mem, u32 size) |
| { |
| size = LARGEST_ALIGN(size); |
| |
| mem->va = dma_alloc_coherent(dev, size, &mem->pa, GFP_KERNEL); |
| mem->size = size; |
| mem->direction = DMA_BIDIRECTIONAL; |
| |
| return mem->va; |
| } |
| |
| /** |
| * libie_ctlq_alloc_queue_res - allocate memory for descriptor ring and bufs |
| * @ctlq: control queue that requires its ring resources to be allocated |
| * |
| * Return: %0 on success, -%errno on failure |
| */ |
| static int libie_ctlq_alloc_queue_res(struct libie_ctlq_info *ctlq) |
| { |
| size_t size = array_size(ctlq->ring_len, sizeof(*ctlq->descs)); |
| struct libie_cp_dma_mem *dma = &ctlq->ring_mem; |
| int err = -ENOMEM; |
| |
| if (!libie_cp_alloc_desc_mem(ctlq->dev, dma, size)) |
| return -ENOMEM; |
| |
| ctlq->descs = dma->va; |
| |
| if (ctlq->type == LIBIE_CTLQ_TYPE_TX) { |
| if (libie_ctlq_alloc_tx_msgs(ctlq)) |
| goto free_dma_mem; |
| } else { |
| err = libie_ctlq_init_fq(ctlq); |
| if (err) |
| goto free_dma_mem; |
| |
| err = libie_ctlq_post_rx_buffs(ctlq); |
| if (err) { |
| libie_ctlq_free_fq(ctlq); |
| goto free_dma_mem; |
| } |
| } |
| |
| return 0; |
| |
| free_dma_mem: |
| libie_cp_free_desc_mem(ctlq->dev, dma); |
| |
| return err; |
| } |
| |
| /** |
| * libie_ctlq_init_regs - Initialize control queue registers |
| * @ctlq: control queue that needs to be initialized |
| * |
| * Initialize registers. The caller is expected to have already initialized the |
| * descriptor ring memory and buffer memory. |
| */ |
| static void libie_ctlq_init_regs(struct libie_ctlq_info *ctlq) |
| { |
| u32 dword; |
| |
| if (ctlq->type == LIBIE_CTLQ_TYPE_RX) |
| writel(ctlq->ring_len - 1, ctlq->reg.tail); |
| else |
| writel(0, ctlq->reg.tail); |
| |
| writel(0, ctlq->reg.head); |
| writel(lower_32_bits(ctlq->ring_mem.pa), ctlq->reg.addr_low); |
| writel(upper_32_bits(ctlq->ring_mem.pa), ctlq->reg.addr_high); |
| |
| dword = FIELD_PREP(LIBIE_CTLQ_MBX_ATQ_LEN, ctlq->ring_len) | |
| ctlq->reg.len_ena_mask; |
| writel(dword, ctlq->reg.len); |
| } |
| |
| /** |
| * libie_find_ctlq - find the controlq for the given id and type |
| * @ctx: libie CP context information |
| * @type: type of controlq to find |
| * @id: controlq id to find |
| * |
| * Return: control queue info pointer on success, NULL on failure |
| */ |
| struct libie_ctlq_info *libie_find_ctlq(struct libie_ctlq_ctx *ctx, |
| enum libie_ctlq_type type, |
| int id) |
| { |
| struct libie_ctlq_info *cq; |
| |
| guard(spinlock)(&ctx->ctlqs_lock); |
| |
| list_for_each_entry(cq, &ctx->ctlqs, list) |
| if (cq->qid == id && cq->type == type) |
| return cq; |
| |
| return NULL; |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_find_ctlq, "LIBIE_CP"); |
| |
| /** |
| * libie_ctlq_add - add one control queue |
| * @ctx: libie CP context information |
| * @qinfo: information required for queue creation |
| * |
| * Allocate and initialize a control queue and add it to the control queue list. |
| * libie_ctlq_init() must be called prior to any calls to libie_ctlq_add. |
| * |
| * Return: added control queue info pointer on success, error pointer on failure |
| */ |
| static struct libie_ctlq_info * |
| libie_ctlq_add(struct libie_ctlq_ctx *ctx, |
| const struct libie_ctlq_create_info *qinfo) |
| { |
| struct libie_ctlq_info *ctlq; |
| int err; |
| |
| if (qinfo->id != LIBIE_CTLQ_MBX_ID) |
| return ERR_PTR(-EOPNOTSUPP); |
| |
| if (qinfo->len > FIELD_MAX(LIBIE_CTLQ_MBX_ATQ_LEN) || !qinfo->len) |
| return ERR_PTR(-EINVAL); |
| |
| ctlq = kvzalloc_obj(*ctlq); |
| if (!ctlq) |
| return ERR_PTR(-ENOMEM); |
| |
| ctlq->type = qinfo->type; |
| ctlq->qid = qinfo->id; |
| ctlq->ring_len = qinfo->len; |
| ctlq->dev = &ctx->mmio_info.pdev->dev; |
| ctlq->reg = qinfo->reg; |
| |
| err = libie_ctlq_alloc_queue_res(ctlq); |
| if (err) { |
| kvfree(ctlq); |
| return ERR_PTR(err); |
| } |
| |
| libie_ctlq_init_regs(ctlq); |
| |
| spin_lock_init(&ctlq->lock); |
| |
| scoped_guard(spinlock, &ctx->ctlqs_lock) |
| list_add(&ctlq->list, &ctx->ctlqs); |
| |
| return ctlq; |
| } |
| |
| /** |
| * libie_ctlq_remove - deallocate and remove specified control queue |
| * @ctx: libie CP context information |
| * @ctlq: specific control queue that needs to be removed |
| */ |
| static void libie_ctlq_remove(struct libie_ctlq_ctx *ctx, |
| struct libie_ctlq_info *ctlq) |
| { |
| scoped_guard(spinlock, &ctx->ctlqs_lock) |
| list_del(&ctlq->list); |
| |
| libie_ctlq_dealloc_ring_res(ctlq); |
| kvfree(ctlq); |
| } |
| |
| /** |
| * libie_ctlq_init - main initialization routine for all control queues |
| * @ctx: libie CP context information |
| * @qinfo: array of structs containing info for each queue to be initialized |
| * @numq: number of queues to initialize |
| * |
| * This initializes queue list and adds any number and any type of control |
| * queues. This is an all or nothing routine; if one fails, all previously |
| * allocated queues will be destroyed. |
| * |
| * Please note that any control queue send/receive functions are not |
| * softirq/NAPI safe, and therefore API can be used in process context only. |
| * |
| * Return: %0 on success, -%errno on failure |
| */ |
| int libie_ctlq_init(struct libie_ctlq_ctx *ctx, |
| const struct libie_ctlq_create_info *qinfo, |
| u32 numq) |
| { |
| INIT_LIST_HEAD(&ctx->ctlqs); |
| spin_lock_init(&ctx->ctlqs_lock); |
| |
| for (u32 i = 0; i < numq; i++) { |
| struct libie_ctlq_info *ctlq; |
| |
| ctlq = libie_ctlq_add(ctx, &qinfo[i]); |
| if (IS_ERR(ctlq)) { |
| libie_ctlq_deinit(ctx); |
| return PTR_ERR(ctlq); |
| } |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_init, "LIBIE_CP"); |
| |
| /** |
| * libie_ctlq_deinit - destroy all control queues |
| * @ctx: libie CP context information |
| */ |
| void libie_ctlq_deinit(struct libie_ctlq_ctx *ctx) |
| { |
| struct libie_ctlq_info *ctlq, *tmp; |
| |
| list_for_each_entry_safe(ctlq, tmp, &ctx->ctlqs, list) |
| libie_ctlq_remove(ctx, ctlq); |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_deinit, "LIBIE_CP"); |
| |
| /** |
| * libie_ctlq_tx_desc_from_msg - initialize a Tx descriptor from a message |
| * @desc: descriptor to be initialized |
| * @msg: filled control queue message |
| */ |
| static void libie_ctlq_tx_desc_from_msg(struct libie_ctlq_desc *desc, |
| const struct libie_ctlq_msg *msg) |
| { |
| const struct libie_cp_dma_mem *dma = &msg->send_mem; |
| u64 qword; |
| |
| qword = FIELD_PREP(LIBIE_CTLQ_DESC_FLAGS, msg->flags) | |
| FIELD_PREP(LIBIE_CTLQ_DESC_INFRA_OPCODE, msg->opcode) | |
| FIELD_PREP(LIBIE_CTLQ_DESC_PFID_VFID, msg->func_id); |
| desc->qword0 = cpu_to_le64(qword); |
| |
| qword = FIELD_PREP(LIBIE_CTLQ_DESC_VIRTCHNL_OPCODE, |
| msg->chnl_opcode) | |
| FIELD_PREP(LIBIE_CTLQ_DESC_VIRTCHNL_MSG_RET_VAL, |
| msg->chnl_retval); |
| desc->qword1 = cpu_to_le64(qword); |
| |
| qword = FIELD_PREP(LIBIE_CTLQ_DESC_MSG_PARAM0, msg->param0) | |
| FIELD_PREP(LIBIE_CTLQ_DESC_SW_COOKIE, |
| msg->sw_cookie) | |
| FIELD_PREP(LIBIE_CTLQ_DESC_VIRTCHNL_FLAGS, |
| msg->virt_flags); |
| desc->qword2 = cpu_to_le64(qword); |
| |
| if (likely(msg->data_len)) { |
| desc->qword0 |= |
| cpu_to_le64(LIBIE_CTLQ_DESC_QWORD0(msg->data_len)); |
| qword = FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_HIGH, |
| upper_32_bits(dma->pa)) | |
| FIELD_PREP(LIBIE_CTLQ_DESC_DATA_ADDR_LOW, |
| lower_32_bits(dma->pa)); |
| } else { |
| qword = msg->addr_param; |
| } |
| |
| desc->qword3 = cpu_to_le64(qword); |
| } |
| |
| /** |
| * libie_ctlq_send_desc_avail - get number of free descriptors on a Tx ctlq |
| * @ctlq: specific control queue which is going be used for sending messages |
| * |
| * The caller must hold ctlq->lock. Any dependent sending must be done |
| * in the same critical section. |
| * |
| * Return: number of available descriptors/messages on a given control queue. |
| */ |
| u32 libie_ctlq_send_desc_avail(const struct libie_ctlq_info *ctlq) |
| { |
| u32 ntu = ctlq->next_to_use, ntc = ctlq->next_to_clean; |
| |
| lockdep_assert_held(&ctlq->lock); |
| |
| return (ntc > ntu ? 0 : ctlq->ring_len) + ntc - ntu - 1; |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_send_desc_avail, "LIBIE_CP"); |
| |
| /** |
| * libie_ctlq_send - send a message to Control Plane or Peer |
| * @ctlq: specific control queue which is used for sending a message |
| * @num_q_msg: number of messages present to send on @ctlq, |
| * positive and no greater than the number of available descriptors |
| * |
| * The caller must fill in @num_q_msg Tx messages starting at ntu beforehand. |
| * |
| * The caller must hold ctlq->lock. The intended pattern is to first check |
| * the number of descriptors available, then fill in the messages and perform |
| * send within a single critical section. |
| */ |
| void libie_ctlq_send(struct libie_ctlq_info *ctlq, u32 num_q_msg) |
| { |
| u32 ntu = ctlq->next_to_use; |
| |
| lockdep_assert_held(&ctlq->lock); |
| |
| for (int i = 0; i < num_q_msg; i++) { |
| struct libie_ctlq_msg *msg = ctlq->tx_msg[ntu]; |
| struct libie_ctlq_desc *desc; |
| |
| desc = &ctlq->descs[ntu]; |
| libie_ctlq_tx_desc_from_msg(desc, msg); |
| |
| if (unlikely(++ntu == ctlq->ring_len)) |
| ntu = 0; |
| } |
| dma_wmb(); |
| writel(ntu, ctlq->reg.tail); |
| ctlq->next_to_use = ntu; |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_send, "LIBIE_CP"); |
| |
| /** |
| * libie_ctlq_send_clean - cleanup the send control queue message buffers |
| * @params: information for handling of Tx completions |
| * |
| * Cleanup the send buffers for the given control queue, if force is set, then |
| * clear all the outstanding send messages irrespective of their send status, |
| * until a zero-length message is encountered, which is either a message that |
| * is already cleared, or a VF reset message, which is always last. |
| * Force should be used during deinit or reset. |
| * |
| * Return: number of send buffers cleaned. |
| */ |
| u32 libie_ctlq_send_clean(const struct libie_ctlq_clean_params *params) |
| { |
| struct libie_ctlq_info *ctlq = params->ctlq; |
| u32 ntc, i; |
| |
| spin_lock(&ctlq->lock); |
| ntc = ctlq->next_to_clean; |
| |
| for (i = 0; i < params->num_msgs; i++) { |
| struct libie_ctlq_msg *msg = ctlq->tx_msg[ntc]; |
| struct libie_ctlq_desc *desc; |
| u64 qword; |
| |
| desc = &ctlq->descs[ntc]; |
| qword = le64_to_cpu(desc->qword0); |
| |
| if (!FIELD_GET(LIBIE_CTLQ_DESC_FLAG_DD, qword) && |
| !(unlikely(params->force) && msg->data_len)) |
| break; |
| |
| /* This cannot be reordered and lock is taken, so no barriers */ |
| desc->qword0 = 0; |
| |
| params->rel_dma_mem(params->rel_ctx, &msg->send_mem); |
| memset(msg, 0, sizeof(*msg)); |
| |
| if (unlikely(++ntc == ctlq->ring_len)) |
| ntc = 0; |
| } |
| |
| ctlq->next_to_clean = ntc; |
| spin_unlock(&ctlq->lock); |
| |
| return i; |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_send_clean, "LIBIE_CP"); |
| |
| /** |
| * libie_ctlq_fill_rx_msg - fill in a message from Rx descriptor and buffer |
| * @msg: message to be filled in |
| * @desc: received descriptor |
| * @rx_buf: fill queue buffer associated with the descriptor |
| */ |
| static void libie_ctlq_fill_rx_msg(struct libie_ctlq_msg *msg, |
| const struct libie_ctlq_desc *desc, |
| struct libeth_fqe *rx_buf) |
| { |
| u64 qword = le64_to_cpu(desc->qword0); |
| |
| msg->flags = FIELD_GET(LIBIE_CTLQ_DESC_FLAGS, qword); |
| msg->opcode = FIELD_GET(LIBIE_CTLQ_DESC_INFRA_OPCODE, qword); |
| msg->data_len = FIELD_GET(LIBIE_CTLQ_DESC_DATA_LEN, qword); |
| msg->hw_retval = FIELD_GET(LIBIE_CTLQ_DESC_HW_RETVAL, qword); |
| |
| qword = le64_to_cpu(desc->qword1); |
| msg->chnl_opcode = |
| FIELD_GET(LIBIE_CTLQ_DESC_VIRTCHNL_OPCODE, qword); |
| msg->chnl_retval = |
| FIELD_GET(LIBIE_CTLQ_DESC_VIRTCHNL_MSG_RET_VAL, qword); |
| |
| qword = le64_to_cpu(desc->qword2); |
| msg->param0 = |
| FIELD_GET(LIBIE_CTLQ_DESC_MSG_PARAM0, qword); |
| msg->sw_cookie = |
| FIELD_GET(LIBIE_CTLQ_DESC_SW_COOKIE, qword); |
| msg->virt_flags = |
| FIELD_GET(LIBIE_CTLQ_DESC_VIRTCHNL_FLAGS, qword); |
| |
| if (likely(msg->data_len)) { |
| if (unlikely(msg->data_len > LIBIE_CTLQ_MAX_BUF_LEN)) { |
| msg->data_len = LIBIE_CTLQ_MAX_BUF_LEN; |
| msg->chnl_retval = U32_MAX; |
| } |
| msg->recv_mem = (struct kvec) { |
| .iov_base = netmem_address(rx_buf->netmem) + |
| rx_buf->offset, |
| .iov_len = msg->data_len, |
| }; |
| libeth_rx_sync_for_cpu(rx_buf, msg->data_len); |
| } else { |
| msg->recv_mem = (struct kvec) {}; |
| msg->addr_param = le64_to_cpu(desc->qword3); |
| page_pool_put_full_netmem(netmem_get_pp(rx_buf->netmem), |
| rx_buf->netmem, false); |
| } |
| } |
| |
| /** |
| * libie_ctlq_recv - receive control queue messages |
| * @ctlq: control queue that needs to processed for receive |
| * @msg: array of received control queue messages on this q; |
| * needs to be pre-allocated by caller for as many messages as requested |
| * @num_q_msg: number of messages that can be stored in msg buffer, |
| * no greater than number of posted buffers |
| * |
| * Caller is expected to return buffers via libie_ctlq_release_rx_buf(). |
| * |
| * The caller must make sure that calls to libie_ctlq_post_rx_buffs() |
| * and libie_ctlq_recv() for each queue are either serialized |
| * or used under ctlq->lock. |
| * |
| * Return: number of messages received |
| */ |
| u32 libie_ctlq_recv(struct libie_ctlq_info *ctlq, struct libie_ctlq_msg *msg, |
| u32 num_q_msg) |
| { |
| u32 ntc, i; |
| |
| ntc = ctlq->next_to_clean; |
| |
| for (i = 0; i < num_q_msg; i++) { |
| struct libie_ctlq_desc *desc = &ctlq->descs[ntc]; |
| struct libeth_fqe *rx_buf = &ctlq->rx_fqes[ntc]; |
| u64 qword; |
| |
| qword = le64_to_cpu(desc->qword0); |
| if (!FIELD_GET(LIBIE_CTLQ_DESC_FLAG_DD, qword)) |
| break; |
| |
| dma_rmb(); |
| |
| libie_ctlq_fill_rx_msg(&msg[i], desc, rx_buf); |
| desc->qword0 = 0; |
| |
| if (unlikely(++ntc == ctlq->ring_len)) |
| ntc = 0; |
| } |
| |
| ctlq->next_to_clean = ntc; |
| |
| return i; |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_recv, "LIBIE_CP"); |
| |
| /** |
| * libie_ctlq_xn_pop_free - get a free Xn entry from the free list |
| * @xnm: Xn transaction manager |
| * |
| * Retrieve a free Xn entry from the free list. |
| * |
| * Return: valid Xn entry pointer or NULL if there are no free Xn entries. |
| */ |
| static struct libie_ctlq_xn * |
| libie_ctlq_xn_pop_free(struct libie_ctlq_xn_manager *xnm) |
| { |
| struct libie_ctlq_xn *xn; |
| u32 free_idx; |
| |
| guard(spinlock)(&xnm->free_xns_bm_lock); |
| |
| if (unlikely(xnm->shutdown)) |
| return NULL; |
| |
| for_each_set_bit(free_idx, xnm->free_xns_bm, |
| LIBIE_CTLQ_MAX_XN_ENTRIES) { |
| xn = &xnm->ring[free_idx]; |
| |
| /* Torn read of the physical address is possible, the worst case |
| * scenario is a transient spurious skip. If the physical |
| * address is dirty in any way, reuse is already safe. |
| */ |
| if (xn->tx_msg && |
| data_race(xn->tx_msg->send_mem.pa) == xn->small_dma_mem.pa) |
| continue; |
| |
| clear_bit(free_idx, xnm->free_xns_bm); |
| |
| return xn; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * __libie_ctlq_xn_push_free - unsafely push an xn entry into the free list |
| * @xnm: Xn transaction manager |
| * @xn: xn entry to be added into the free list |
| * |
| * Return: whether xnm destruction can be triggered by the caller |
| */ |
| static bool __libie_ctlq_xn_push_free(struct libie_ctlq_xn_manager *xnm, |
| struct libie_ctlq_xn *xn) |
| { |
| xn->cookie++; |
| set_bit(xn->index, xnm->free_xns_bm); |
| |
| if (unlikely(xnm->shutdown) && |
| bitmap_full(xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES)) |
| return true; |
| |
| return false; |
| } |
| |
| /** |
| * libie_ctlq_xn_push_free - push a Xn entry into the free list |
| * @xnm: Xn transaction manager |
| * @xn: xn entry to be added into the free list, not locked |
| * |
| * Safely add a used Xn entry back to the free list. |
| */ |
| static void libie_ctlq_xn_push_free(struct libie_ctlq_xn_manager *xnm, |
| struct libie_ctlq_xn *xn) |
| { |
| bool can_destroy; |
| |
| scoped_guard(spinlock, &xnm->free_xns_bm_lock) |
| can_destroy = __libie_ctlq_xn_push_free(xnm, xn); |
| |
| if (can_destroy) |
| complete(&xnm->can_destroy); |
| } |
| |
| /** |
| * libie_ctlq_xn_deinit_dma - free the DMA memory allocated for send messages |
| * @xnm: pointer to the transaction manager |
| * @num_entries: number of Xn entries to free the DMA for |
| */ |
| static void libie_ctlq_xn_deinit_dma(struct libie_ctlq_xn_manager *xnm, |
| u32 num_entries) |
| { |
| for (u32 i = 0; i < num_entries; i++) { |
| struct libie_ctlq_xn *xn = &xnm->ring[i]; |
| |
| dma_pool_free(xnm->small_buff_pool, xn->small_dma_mem.va, |
| xn->small_dma_mem.pa); |
| } |
| |
| dma_pool_destroy(xnm->small_buff_pool); |
| } |
| |
| /** |
| * libie_ctlq_xn_init_dma - pre-allocate DMA memory for send messages that use |
| * stack variables |
| * @dev: device pointer |
| * @xnm: pointer to transaction manager |
| * |
| * Return: %0 on success or error if memory allocation fails |
| */ |
| static int libie_ctlq_xn_init_dma(struct device *dev, |
| struct libie_ctlq_xn_manager *xnm) |
| { |
| u32 i; |
| |
| xnm->small_buff_pool = |
| dma_pool_create("libie_ctlq_xn_tx", dev, LIBIE_CP_TX_COPYBREAK, |
| LIBIE_CP_TX_COPYBREAK, 0); |
| if (!xnm->small_buff_pool) |
| return -ENOMEM; |
| |
| for (i = 0; i < LIBIE_CTLQ_MAX_XN_ENTRIES; i++) { |
| struct libie_cp_dma_mem *mem = &xnm->ring[i].small_dma_mem; |
| |
| mem->va = dma_pool_zalloc(xnm->small_buff_pool, GFP_KERNEL, |
| &mem->pa); |
| if (!mem->va) |
| goto dealloc_dma; |
| |
| mem->direction = DMA_BIDIRECTIONAL; |
| mem->size = LIBIE_CP_TX_COPYBREAK; |
| } |
| |
| return 0; |
| |
| dealloc_dma: |
| libie_ctlq_xn_deinit_dma(xnm, i); |
| |
| return -ENOMEM; |
| } |
| |
| /** |
| * libie_ctlq_xn_process_recv - process Xn data in receive message |
| * @params: Xn receive param information to handle a receive message |
| * @ctlq_msg: received control queue message |
| * |
| * Process a control queue receive message and send a complete event |
| * notification. |
| * |
| * Return: true if a message has been processed, false otherwise. |
| */ |
| static bool |
| libie_ctlq_xn_process_recv(struct libie_ctlq_xn_recv_params *params, |
| struct libie_ctlq_msg *ctlq_msg) |
| { |
| struct libie_ctlq_xn_manager *xnm = params->xnm; |
| struct libie_ctlq_xn *xn; |
| u16 msg_cookie, xn_index; |
| struct kvec *response; |
| int status; |
| u16 data; |
| |
| data = ctlq_msg->sw_cookie; |
| xn_index = FIELD_GET(LIBIE_CTLQ_XN_INDEX_M, data); |
| msg_cookie = FIELD_GET(LIBIE_CTLQ_XN_COOKIE_M, data); |
| status = ctlq_msg->chnl_retval ? -EFAULT : 0; |
| |
| xn = &xnm->ring[xn_index]; |
| spin_lock(&xn->xn_lock); |
| if (ctlq_msg->chnl_opcode != xn->virtchnl_opcode || |
| msg_cookie != xn->cookie) { |
| spin_unlock(&xn->xn_lock); |
| return false; |
| } |
| |
| if (xn->state != LIBIE_CTLQ_XN_ASYNC && |
| xn->state != LIBIE_CTLQ_XN_WAITING) { |
| spin_unlock(&xn->xn_lock); |
| return false; |
| } |
| |
| response = &ctlq_msg->recv_mem; |
| if (xn->state == LIBIE_CTLQ_XN_ASYNC) { |
| xn->resp_cb(xn->send_ctx, response, status); |
| libie_ctlq_release_rx_buf(response); |
| xn->state = LIBIE_CTLQ_XN_IDLE; |
| spin_unlock(&xn->xn_lock); |
| libie_ctlq_xn_push_free(xnm, xn); |
| |
| return true; |
| } |
| |
| xn->recv_mem = *response; |
| xn->state = status ? LIBIE_CTLQ_XN_COMPLETED_FAILED : |
| LIBIE_CTLQ_XN_COMPLETED_SUCCESS; |
| |
| complete(&xn->cmd_completion_event); |
| spin_unlock(&xn->xn_lock); |
| |
| return true; |
| } |
| |
| /** |
| * libie_xn_check_async_timeout - Check for asynchronous message timeouts |
| * @xnm: Xn transaction manager |
| * |
| * Call the corresponding callback to notify the caller about the timeout. |
| * Iterates free_xns_bm locklessly, potential races are caught under |
| * xn->xn_lock. |
| */ |
| static void libie_xn_check_async_timeout(struct libie_ctlq_xn_manager *xnm) |
| { |
| u32 idx; |
| |
| for_each_clear_bit(idx, xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES) { |
| struct libie_ctlq_xn *xn = &xnm->ring[idx]; |
| u64 timeout_ms; |
| |
| spin_lock(&xn->xn_lock); |
| |
| timeout_ms = ktime_ms_delta(ktime_get(), xn->timestamp); |
| if (xn->state != LIBIE_CTLQ_XN_ASYNC || |
| timeout_ms < xn->timeout_ms) { |
| spin_unlock(&xn->xn_lock); |
| continue; |
| } |
| |
| xn->resp_cb(xn->send_ctx, NULL, -ETIMEDOUT); |
| xn->state = LIBIE_CTLQ_XN_IDLE; |
| spin_unlock(&xn->xn_lock); |
| libie_ctlq_xn_push_free(xnm, xn); |
| } |
| } |
| |
| /** |
| * libie_ctlq_xn_recv - process control queue receive message |
| * @params: Xn receive param information to handle a receive message |
| * |
| * Process a receive message and update the receive queue buffer. |
| * Also terminates async transactions for which it failed to receive a response |
| * within a given timeframe. |
| * Function is intended to be called periodically from a single task. |
| * |
| * Return: remaining budget. |
| */ |
| u32 libie_ctlq_xn_recv(struct libie_ctlq_xn_recv_params *params) |
| { |
| struct libie_ctlq_msg ctlq_msg; |
| u32 budget = params->budget; |
| |
| while (budget && libie_ctlq_recv(params->ctlq, &ctlq_msg, 1)) { |
| budget--; |
| if (!libie_ctlq_xn_process_recv(params, &ctlq_msg)) |
| params->ctlq_msg_handler(params->xnm->ctx, &ctlq_msg); |
| } |
| |
| libie_ctlq_post_rx_buffs(params->ctlq); |
| libie_xn_check_async_timeout(params->xnm); |
| |
| return budget; |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_recv, "LIBIE_CP"); |
| |
| /** |
| * libie_cp_map_dma_mem - map a given virtual address for DMA |
| * @dev: device information |
| * @va: virtual address to be mapped |
| * @size: size of the memory |
| * @direction: DMA direction either from/to device |
| * @dma_mem: memory for DMA information to be stored |
| * |
| * Return: true on success, false on DMA map failure. |
| */ |
| static bool libie_cp_map_dma_mem(struct device *dev, void *va, size_t size, |
| int direction, |
| struct libie_cp_dma_mem *dma_mem) |
| { |
| dma_mem->pa = dma_map_single(dev, va, size, direction); |
| |
| return dma_mapping_error(dev, dma_mem->pa) ? false : true; |
| } |
| |
| /** |
| * libie_cp_unmap_dma_mem - unmap previously mapped DMA address |
| * @dev: device information |
| * @dma_mem: DMA memory information |
| */ |
| static void libie_cp_unmap_dma_mem(struct device *dev, |
| const struct libie_cp_dma_mem *dma_mem) |
| { |
| dma_unmap_single(dev, dma_mem->pa, dma_mem->size, |
| dma_mem->direction); |
| } |
| |
| /** |
| * libie_ctlq_xn_process_send - process and send a control queue message |
| * @params: Xn send param information for sending a control queue message |
| * @xn: Assigned Xn entry for tracking the control queue message |
| * |
| * Return: %0 on success, -%errno on failure. |
| */ |
| static |
| int libie_ctlq_xn_process_send(struct libie_ctlq_xn_send_params *params, |
| struct libie_ctlq_xn *xn) |
| { |
| size_t buf_len = params->send_buf.iov_len; |
| struct device *dev = params->ctlq->dev; |
| void *buf = params->send_buf.iov_base; |
| struct libie_cp_dma_mem *dma_mem; |
| u16 cookie; |
| |
| if (!buf || !buf_len) |
| return -EOPNOTSUPP; |
| |
| if (libie_cp_can_send_onstack(buf_len)) { |
| dma_mem = &xn->small_dma_mem; |
| memcpy(dma_mem->va, buf, buf_len); |
| } else { |
| dma_mem = &xn->send_dma_mem; |
| dma_mem->va = buf; |
| dma_mem->size = buf_len; |
| dma_mem->direction = DMA_TO_DEVICE; |
| |
| if (!libie_cp_map_dma_mem(dev, buf, buf_len, DMA_TO_DEVICE, |
| dma_mem)) |
| return -ENOMEM; |
| } |
| |
| cookie = FIELD_PREP(LIBIE_CTLQ_XN_COOKIE_M, xn->cookie) | |
| FIELD_PREP(LIBIE_CTLQ_XN_INDEX_M, xn->index); |
| |
| scoped_guard(spinlock, ¶ms->ctlq->lock) { |
| struct libie_ctlq_info *ctlq = params->ctlq; |
| struct libie_ctlq_msg *ctlq_msg; |
| |
| if (!libie_ctlq_send_desc_avail(ctlq)) { |
| if (!libie_cp_can_send_onstack(buf_len)) |
| libie_cp_unmap_dma_mem(dev, dma_mem); |
| |
| return -EBUSY; |
| } |
| |
| ctlq_msg = ctlq->tx_msg[ctlq->next_to_use]; |
| xn->tx_msg = dma_mem == &xn->small_dma_mem ? ctlq_msg : NULL; |
| if (params->ctlq_msg) |
| *ctlq_msg = *params->ctlq_msg; |
| else |
| /* Unused ctlq messages are already zeroed */ |
| ctlq_msg->opcode = LIBIE_CTLQ_SEND_MSG_TO_CP; |
| |
| ctlq_msg->sw_cookie = cookie; |
| ctlq_msg->send_mem = *dma_mem; |
| ctlq_msg->data_len = buf_len; |
| ctlq_msg->chnl_opcode = params->chnl_opcode; |
| libie_ctlq_send(params->ctlq, 1); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * libie_ctlq_xn_send - send a control queue message, initiating a transaction |
| * @params: Xn send param information for sending a control queue message |
| * |
| * Send a control queue (mailbox or config) message. |
| * Based on the params value, the call can be completed synchronously or |
| * asynchronously. |
| * |
| * Return: %0 on success, -%errno on failure. |
| */ |
| int libie_ctlq_xn_send(struct libie_ctlq_xn_send_params *params) |
| { |
| bool free_send = !libie_cp_can_send_onstack(params->send_buf.iov_len); |
| struct libie_ctlq_xn *xn; |
| int ret; |
| |
| if (params->send_buf.iov_len > LIBIE_CTLQ_MAX_BUF_LEN) { |
| ret = -EINVAL; |
| goto free_buf; |
| } |
| |
| xn = libie_ctlq_xn_pop_free(params->xnm); |
| /* no free transactions available */ |
| if (unlikely(!xn)) { |
| ret = -EAGAIN; |
| goto free_buf; |
| } |
| |
| spin_lock(&xn->xn_lock); |
| if (xn->state == LIBIE_CTLQ_XN_SHUTDOWN) { |
| ret = -ENXIO; |
| goto unlock_xn; |
| } |
| |
| xn->state = params->resp_cb ? LIBIE_CTLQ_XN_ASYNC : |
| LIBIE_CTLQ_XN_WAITING; |
| xn->virtchnl_opcode = params->chnl_opcode; |
| |
| if (params->resp_cb) { |
| xn->send_ctx = params->send_ctx; |
| xn->resp_cb = params->resp_cb; |
| xn->timeout_ms = params->timeout_ms; |
| xn->timestamp = ktime_get(); |
| } |
| |
| ret = libie_ctlq_xn_process_send(params, xn); |
| if (ret) |
| goto release_xn; |
| else |
| free_send = false; |
| |
| spin_unlock(&xn->xn_lock); |
| |
| if (params->resp_cb) |
| return 0; |
| |
| wait_for_completion_timeout(&xn->cmd_completion_event, |
| msecs_to_jiffies(params->timeout_ms)); |
| |
| spin_lock(&xn->xn_lock); |
| switch (xn->state) { |
| case LIBIE_CTLQ_XN_WAITING: |
| ret = -ETIMEDOUT; |
| break; |
| case LIBIE_CTLQ_XN_COMPLETED_SUCCESS: |
| params->recv_mem = xn->recv_mem; |
| break; |
| default: |
| ret = -EBADMSG; |
| break; |
| } |
| |
| /* Free the receive buffer in case of failure. On timeout, receive |
| * buffer is not allocated. |
| */ |
| if (ret && ret != -ETIMEDOUT) |
| libie_ctlq_release_rx_buf(&xn->recv_mem); |
| |
| release_xn: |
| xn->state = LIBIE_CTLQ_XN_IDLE; |
| reinit_completion(&xn->cmd_completion_event); |
| unlock_xn: |
| spin_unlock(&xn->xn_lock); |
| libie_ctlq_xn_push_free(params->xnm, xn); |
| free_buf: |
| if (free_send) |
| params->rel_tx_buf(params->send_buf.iov_base); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_send, "LIBIE_CP"); |
| |
| /** |
| * struct libie_ctlq_xn_rel_tx_ctx - context needed to release xn Tx message |
| * @dev: device for which DMA was mapped |
| * @rel_tx_buf: freeing function for non-small buffers |
| */ |
| struct libie_ctlq_xn_rel_tx_ctx { |
| struct device *dev; |
| void (*rel_tx_buf)(const void *buf_va); |
| }; |
| |
| /** |
| * libie_ctlq_xn_rel_tx_buf - release xn-controlled Tx message buffer |
| * @ctx: context, namely DMA device and freeing function |
| * @dma_mem: DMA memory to reclaim/unmap |
| */ |
| static void libie_ctlq_xn_rel_tx_buf(const void *ctx, |
| struct libie_cp_dma_mem *dma_mem) |
| { |
| const struct libie_ctlq_xn_rel_tx_ctx *rel_ctx = ctx; |
| |
| if (!libie_cp_can_send_onstack(dma_mem->size)) { |
| libie_cp_unmap_dma_mem(rel_ctx->dev, dma_mem); |
| rel_ctx->rel_tx_buf(dma_mem->va); |
| } |
| } |
| |
| /** |
| * libie_ctlq_xn_send_clean - clean xn-controlled Tx messages |
| * @ctlq: control queue to clean |
| * @rel_tx_buf: driver callback to free the buffer |
| * @force: clean regardless of DD |
| * |
| * Return: number of completed/released messages. |
| */ |
| u32 libie_ctlq_xn_send_clean(struct libie_ctlq_info *ctlq, |
| void (*rel_tx_buf)(const void *buf_va), |
| bool force) |
| { |
| struct libie_ctlq_xn_rel_tx_ctx rel_ctx = { |
| .dev = ctlq->dev, |
| .rel_tx_buf = rel_tx_buf, |
| }; |
| struct libie_ctlq_clean_params params = { |
| .ctlq = ctlq, |
| .force = force, |
| .num_msgs = ctlq->ring_len, |
| .rel_ctx = &rel_ctx, |
| .rel_dma_mem = libie_ctlq_xn_rel_tx_buf, |
| }; |
| |
| return libie_ctlq_send_clean(¶ms); |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_send_clean, "LIBIE_CP"); |
| |
| /** |
| * libie_ctlq_xn_shutdown - terminate control queue transactions |
| * @xnm: pointer to the transaction manager |
| * |
| * Synchronously terminate existing transactions and stop accepting new ones. |
| * Async transactions are discarded without invoking resp_cb. |
| */ |
| void libie_ctlq_xn_shutdown(struct libie_ctlq_xn_manager *xnm) |
| { |
| bool must_wait = false; |
| u32 i; |
| |
| /* Should be no new clear bits after this */ |
| spin_lock(&xnm->free_xns_bm_lock); |
| xnm->shutdown = true; |
| |
| for_each_clear_bit(i, xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES) { |
| struct libie_ctlq_xn *xn = &xnm->ring[i]; |
| |
| spin_lock(&xn->xn_lock); |
| |
| switch (xn->state) { |
| /* if an idle xn is not free, it is about to be either |
| * freed or initialized, prevent the latter and wait |
| */ |
| case LIBIE_CTLQ_XN_IDLE: |
| xn->state = LIBIE_CTLQ_XN_SHUTDOWN; |
| fallthrough; |
| /* waiting thread possibly needs a push to return the xn, |
| * transaction will be reported as timed out |
| */ |
| case LIBIE_CTLQ_XN_WAITING: |
| complete(&xn->cmd_completion_event); |
| fallthrough; |
| /* these states will return the xn soon */ |
| case LIBIE_CTLQ_XN_COMPLETED_SUCCESS: |
| case LIBIE_CTLQ_XN_COMPLETED_FAILED: |
| case LIBIE_CTLQ_XN_SHUTDOWN: |
| must_wait = true; |
| break; |
| /* no thread should reference async xns at this point */ |
| case LIBIE_CTLQ_XN_ASYNC: |
| xn->state = LIBIE_CTLQ_XN_IDLE; |
| __libie_ctlq_xn_push_free(xnm, xn); |
| break; |
| } |
| |
| spin_unlock(&xn->xn_lock); |
| } |
| |
| spin_unlock(&xnm->free_xns_bm_lock); |
| |
| if (must_wait) |
| wait_for_completion(&xnm->can_destroy); |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_shutdown, "LIBIE_CP"); |
| |
| /** |
| * libie_ctlq_xn_deinit - deallocate and free the transaction manager resources |
| * @xnm: pointer to the transaction manager |
| * @ctx: libie CP context information |
| * |
| * Rx processing must be stopped beforehand via cancelling tasks. |
| * Tx processing must be stopped beforehand via libie_ctlq_xn_shutdown(), |
| * all buffers must be force-cleaned from the send queue. |
| */ |
| void libie_ctlq_xn_deinit(struct libie_ctlq_xn_manager *xnm, |
| struct libie_ctlq_ctx *ctx) |
| { |
| libie_ctlq_xn_deinit_dma(xnm, LIBIE_CTLQ_MAX_XN_ENTRIES); |
| kvfree(xnm); |
| libie_ctlq_deinit(ctx); |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_deinit, "LIBIE_CP"); |
| |
| /** |
| * libie_ctlq_xn_init - initialize the Xn transaction manager |
| * @params: Xn init param information for allocating Xn manager resources |
| * |
| * Return: %0 on success, -%errno on failure. |
| */ |
| int libie_ctlq_xn_init(struct libie_ctlq_xn_init_params *params) |
| { |
| struct libie_ctlq_xn_manager *xnm; |
| int ret; |
| |
| ret = libie_ctlq_init(params->ctx, params->cctlq_info, params->num_qs); |
| if (ret) |
| return ret; |
| |
| xnm = kvzalloc_obj(*xnm); |
| if (!xnm) |
| goto ctlq_deinit; |
| |
| ret = libie_ctlq_xn_init_dma(¶ms->ctx->mmio_info.pdev->dev, xnm); |
| if (ret) |
| goto free_xnm; |
| |
| spin_lock_init(&xnm->free_xns_bm_lock); |
| init_completion(&xnm->can_destroy); |
| bitmap_fill(xnm->free_xns_bm, LIBIE_CTLQ_MAX_XN_ENTRIES); |
| |
| for (u32 i = 0; i < LIBIE_CTLQ_MAX_XN_ENTRIES; i++) { |
| struct libie_ctlq_xn *xn = &xnm->ring[i]; |
| |
| xn->index = i; |
| init_completion(&xn->cmd_completion_event); |
| spin_lock_init(&xn->xn_lock); |
| } |
| xnm->ctx = params->ctx; |
| params->xnm = xnm; |
| |
| return 0; |
| |
| free_xnm: |
| kvfree(xnm); |
| ctlq_deinit: |
| libie_ctlq_deinit(params->ctx); |
| |
| return -ENOMEM; |
| } |
| EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_init, "LIBIE_CP"); |
| |
| MODULE_DESCRIPTION("Control Plane communication API"); |
| MODULE_IMPORT_NS("LIBETH"); |
| MODULE_LICENSE("GPL"); |