| // 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"); |