// SPDX-License-Identifier: GPL-2.0-only /* Copyright (C) 2025 Intel Corporation */ #include #include #include #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, GFP_KERNEL); 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");