From: Phani R Burra <phani.r.bu...@intel.com>

Libie will now support control queue setup and configuration APIs. These
are mainly used for mailbox communication between drivers and control
plane.

Make use of the libeth_rx page pool support for managing controlq buffers.

Reviewed-by: Maciej Fijalkowski <maciej.fijalkow...@intel.com>
Signed-off-by: Phani R Burra <phani.r.bu...@intel.com>
Co-developed-by: Victor Raj <victor....@intel.com>
Signed-off-by: Victor Raj <victor....@intel.com>
Co-developed-by: Sridhar Samudrala <sridhar.samudr...@intel.com>
Signed-off-by: Sridhar Samudrala <sridhar.samudr...@intel.com>
Co-developed-by: Pavan Kumar Linga <pavan.kumar.li...@intel.com>
Signed-off-by: Pavan Kumar Linga <pavan.kumar.li...@intel.com>
Co-developed-by: Larysa Zaremba <larysa.zare...@intel.com>
Signed-off-by: Larysa Zaremba <larysa.zare...@intel.com>
---
 drivers/net/ethernet/intel/libie/Kconfig    |   8 +
 drivers/net/ethernet/intel/libie/Makefile   |   4 +
 drivers/net/ethernet/intel/libie/controlq.c | 607 ++++++++++++++++++++
 include/linux/intel/libie/controlq.h        | 249 ++++++++
 4 files changed, 868 insertions(+)
 create mode 100644 drivers/net/ethernet/intel/libie/controlq.c
 create mode 100644 include/linux/intel/libie/controlq.h

diff --git a/drivers/net/ethernet/intel/libie/Kconfig 
b/drivers/net/ethernet/intel/libie/Kconfig
index e54a9ed24882..59c9018a0a16 100644
--- a/drivers/net/ethernet/intel/libie/Kconfig
+++ b/drivers/net/ethernet/intel/libie/Kconfig
@@ -15,6 +15,14 @@ config LIBIE_ADMINQ
          Helper functions used by Intel Ethernet drivers for administration
          queue command interface (aka adminq).
 
+config LIBIE_CP
+       tristate
+       select LIBETH
+       select LIBIE_PCI
+       help
+         Common helper routines to communicate with the device Control Plane
+         using virtchnl2 or related mailbox protocols.
+
 config LIBIE_PCI
        tristate
        help
diff --git a/drivers/net/ethernet/intel/libie/Makefile 
b/drivers/net/ethernet/intel/libie/Makefile
index 5f648d312a2a..65912a357886 100644
--- a/drivers/net/ethernet/intel/libie/Makefile
+++ b/drivers/net/ethernet/intel/libie/Makefile
@@ -9,6 +9,10 @@ obj-$(CONFIG_LIBIE_ADMINQ)     += libie_adminq.o
 
 libie_adminq-y                 := adminq.o
 
+obj-$(CONFIG_LIBIE_CP)         += libie_cp.o
+
+libie_cp-y                     := controlq.o
+
 obj-$(CONFIG_LIBIE_PCI)                += libie_pci.o
 
 libie_pci-y                    := pci.o
diff --git a/drivers/net/ethernet/intel/libie/controlq.c 
b/drivers/net/ethernet/intel/libie/controlq.c
new file mode 100644
index 000000000000..80b0f1c2cc0a
--- /dev/null
+++ b/drivers/net/ethernet/intel/libie/controlq.c
@@ -0,0 +1,607 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2025 Intel Corporation */
+
+#include <linux/bitfield.h>
+#include <net/libeth/rx.h>
+
+#include <linux/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 a 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_reset_rx_desc - reset 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_reset_rx_desc(struct libie_ctlq_desc *desc,
+                                    dma_addr_t addr, u32 mem_truesize)
+{
+       u64 qword;
+
+       *desc = (struct libie_ctlq_desc) {};
+       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 separate queues 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;
+
+               addr = libeth_rx_alloc(&fq, ntp);
+               if (unlikely(addr == DMA_MAPPING_ERROR)) {
+                       ret = -ENOMEM;
+                       goto post_bufs;
+               }
+
+               libie_ctlq_reset_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;
+
+               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 = kvcalloc(ctlq->ring_len,
+                               sizeof(*ctlq->tx_msg), GFP_KERNEL);
+       if (!ctlq->tx_msg)
+               return -ENOMEM;
+
+       for (u32 i = 0; i < ctlq->ring_len; i++) {
+               ctlq->tx_msg[i] = kzalloc(sizeof(*ctlq->tx_msg[i]), GFP_KERNEL);
+
+               if (!ctlq->tx_msg[i]) {
+                       libie_ctlq_free_tx_msgs(ctlq, i);
+                       return -ENOMEM;
+               }
+       }
+
+       return 0;
+}
+
+/**
+ * libie_cp_free_dma_mem - Free the previously allocated DMA memory
+ * @dev: device information
+ * @mem: DMA memory information
+ */
+static void libie_cp_free_dma_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_dma_mem(ctlq->dev, dma);
+}
+
+/**
+ * libie_cp_alloc_dma_mem - Allocate a DMA memory
+ * @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_dma_mem(struct device *dev,
+                                   struct libie_cp_dma_mem *mem, u32 size)
+{
+       size = ALIGN(size, SZ_4K);
+
+       mem->va = dma_alloc_coherent(dev, size, &mem->pa, GFP_KERNEL);
+       mem->size = size;
+
+       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_dma_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_dma_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 == VIRTCHNL2_QUEUE_TYPE_RX)
+               writel(ctlq->ring_len - 1, 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: controlq context structure
+ * @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 virtchnl2_queue_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: controlq context information
+ * @qinfo: information that requires for queue creation
+ *
+ * Allocate and initialize a control queue and add it to the control queue 
list.
+ * The ctlq parameter will be allocated/initialized and passed back to the
+ * caller if no errors occur.
+ *
+ * Note: 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;
+
+       if (qinfo->id != LIBIE_CTLQ_MBX_ID)
+               return ERR_PTR(-EOPNOTSUPP);
+
+       /* libie_ctlq_init was not called */
+       scoped_guard(spinlock, &ctx->ctlqs_lock)
+               if (!ctx->ctlqs.next)
+                       return ERR_PTR(-EINVAL);
+
+       ctlq = kvzalloc(sizeof(*ctlq), GFP_KERNEL);
+       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;
+
+       if (libie_ctlq_alloc_queue_res(ctlq)) {
+               kvfree(ctlq);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       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 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 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. This must be called prior to using
+ * the individual add/remove APIs.
+ *
+ * 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 - send a message to Control Plane or Peer
+ * @ctlq: specific control queue which is used for sending a message
+ * @q_msg: array of queue messages to be sent
+ * @num_q_msg: number of messages to send on control queue
+ *
+ * The control queue will hold a reference to each send message until
+ * the completion for that message has been cleaned.
+ *
+ * The caller must hold ctlq->lock.
+ *
+ * Return: %0 on success, -%errno on failure.
+ */
+int libie_ctlq_send(struct libie_ctlq_info *ctlq,
+                   struct libie_ctlq_msg *q_msg, u32 num_q_msg)
+{
+       u32 num_desc_avail, ntu;
+
+       ntu = ctlq->next_to_use;
+
+       num_desc_avail = (ctlq->next_to_clean > ntu ? 0 : ctlq->ring_len) +
+                         ctlq->next_to_clean - ntu - 1;
+
+       if (num_desc_avail < num_q_msg)
+               return -EBUSY;
+
+       for (int i = 0; i < num_q_msg; i++) {
+               struct libie_ctlq_msg *msg = &q_msg[i];
+               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;
+       }
+       writel(ntu, ctlq->reg.tail);
+       ctlq->next_to_use = ntu;
+
+       return 0;
+}
+EXPORT_SYMBOL_NS_GPL(libie_ctlq_send, "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)) {
+               msg->recv_mem = (struct kvec) {
+                       .iov_base = netmem_address(rx_buf->netmem),
+                       .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 message call back
+ * @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
+ *
+ * Called by interrupt handler or polling mechanism. Caller is expected
+ * to free buffers.
+ *
+ * The caller must make sure that calls to libie_ctlq_post_rx_buffs()
+ * and libie_ctlq_recv() for separate queues 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++) {
+               const 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();
+
+               if (unlikely(FIELD_GET(LIBIE_CTLQ_DESC_FLAG_ERR, qword)))
+                       break;
+
+               libie_ctlq_fill_rx_msg(&msg[i], desc, rx_buf);
+
+               if (unlikely(++ntc == ctlq->ring_len))
+                       ntc = 0;
+       }
+
+       ctlq->next_to_clean = ntc;
+
+       return i;
+}
+EXPORT_SYMBOL_NS_GPL(libie_ctlq_recv, "LIBIE_CP");
+
+MODULE_DESCRIPTION("Control Plane communication API");
+MODULE_IMPORT_NS("LIBETH");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/intel/libie/controlq.h 
b/include/linux/intel/libie/controlq.h
new file mode 100644
index 000000000000..534508fbb405
--- /dev/null
+++ b/include/linux/intel/libie/controlq.h
@@ -0,0 +1,249 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) 2025 Intel Corporation */
+
+#ifndef __LIBIE_CONTROLQ_H
+#define __LIBIE_CONTROLQ_H
+
+#include <net/libeth/rx.h>
+
+#include <linux/intel/libie/pci.h>
+#include <linux/intel/virtchnl2.h>
+
+/* Default mailbox control queue */
+#define LIBIE_CTLQ_MBX_ID                      -1
+#define LIBIE_CTLQ_MAX_BUF_LEN                 SZ_4K
+
+#define LIBIE_CTLQ_TYPE_TX                     0
+#define LIBIE_CTLQ_TYPE_RX                     1
+
+/* Opcode used to send controlq message to the control plane */
+#define LIBIE_CTLQ_SEND_MSG_TO_CP              0x801
+#define LIBIE_CTLQ_SEND_MSG_TO_PEER            0x804
+
+/**
+ * struct libie_ctlq_ctx - contains controlq info and MMIO region info
+ * @mmio_info: MMIO region info structure
+ * @ctlqs: list that stores all the control queues
+ * @ctlqs_lock: lock for control queue list
+ */
+struct libie_ctlq_ctx {
+       struct libie_mmio_info  mmio_info;
+       struct list_head        ctlqs;
+       spinlock_t              ctlqs_lock;     /* protects the ctlqs list */
+};
+
+/**
+ * struct libie_ctlq_reg - structure representing virtual addresses of the
+ *                         controlq registers and masks
+ * @head: controlq head register address
+ * @tail: controlq tail register address
+ * @len: register address to write controlq length and enable bit
+ * @addr_high: register address to write the upper 32b of ring physical address
+ * @addr_low: register address to write the lower 32b of ring physical address
+ * @len_mask: mask to read the controlq length
+ * @len_ena_mask: mask to write the controlq enable bit
+ * @head_mask: mask to read the head value
+ */
+struct libie_ctlq_reg {
+       void __iomem    *head;
+       void __iomem    *tail;
+       void __iomem    *len;
+       void __iomem    *addr_high;
+       void __iomem    *addr_low;
+       u32             len_mask;
+       u32             len_ena_mask;
+       u32             head_mask;
+};
+
+/**
+ * struct libie_cp_dma_mem - structure for DMA memory
+ * @va: virtual address
+ * @pa: physical address
+ * @size: memory size
+ */
+struct libie_cp_dma_mem {
+       void            *va;
+       dma_addr_t      pa;
+       size_t          size;
+};
+
+/**
+ * struct libie_ctlq_msg - control queue message data
+ * @flags: refer to 'Flags sub-structure' definitions
+ * @opcode: infrastructure message opcode
+ * @data_len: size of the payload
+ * @func_id: queue id for the secondary mailbox queue, 0 for default mailbox
+ * @hw_retval: execution status from the HW
+ * @chnl_opcode: virtchnl message opcode
+ * @chnl_retval: virtchnl return value
+ * @param0: indirect message raw parameter0
+ * @sw_cookie: used to verify the response of the sent virtchnl message
+ * @virt_flags: virtchnl capability flags
+ * @addr_param: additional parameters in place of the address, given no buffer
+ * @recv_mem: virtual address and size of the buffer that contains
+ *           the indirect response
+ * @send_mem: physical and virtual address of the DMA buffer,
+ *           used for sending
+ */
+struct libie_ctlq_msg {
+       u16                     flags;
+       u16                     opcode;
+       u16                     data_len;
+       union {
+               u16             func_id;
+               u16             hw_retval;
+       };
+       u32                     chnl_opcode;
+       u32                     chnl_retval;
+       u32                     param0;
+       u16                     sw_cookie;
+       u16                     virt_flags;
+       u64                     addr_param;
+       union {
+               struct kvec     recv_mem;
+               struct  libie_cp_dma_mem send_mem;
+       };
+};
+
+/**
+ * struct libie_ctlq_create_info - control queue create information
+ * @type: control queue type (Rx or Tx)
+ * @id: queue offset passed as input, -1 for default mailbox
+ * @reg: registers accessed by control queue
+ * @len: controlq length
+ */
+struct libie_ctlq_create_info {
+       enum virtchnl2_queue_type       type;
+       int                             id;
+       struct libie_ctlq_reg           reg;
+       u16                             len;
+};
+
+/**
+ * struct libie_ctlq_info - control queue information
+ * @list: used to add a controlq to the list of queues in libie_ctlq_ctx
+ * @type: control queue type
+ * @qid: queue identifier
+ * @lock: control queue lock
+ * @ring_mem: descrtiptor ring DMA memory
+ * @descs: array of descrtiptors
+ * @rx_fqes: array of controlq Rx buffers
+ * @tx_msg: Tx messages sent to hardware
+ * @reg: registers used by control queue
+ * @dev: device that owns this control queue
+ * @pp: page pool for controlq Rx buffers
+ * @truesize: size to allocate per buffer
+ * @next_to_use: next available slot to send buffer
+ * @next_to_clean: next descrtiptor to be cleaned
+ * @next_to_post: next available slot to post buffers to after receive
+ * @ring_len: length of the descriptor ring
+ */
+struct libie_ctlq_info {
+       struct list_head                list;
+       enum virtchnl2_queue_type       type;
+       int                             qid;
+       spinlock_t                      lock;   /* for concurrent processing */
+       struct libie_cp_dma_mem ring_mem;
+       struct libie_ctlq_desc          *descs;
+       union {
+               struct libeth_fqe               *rx_fqes;
+               struct libie_ctlq_msg           **tx_msg;
+       };
+       struct libie_ctlq_reg           reg;
+       struct device                   *dev;
+       struct page_pool                *pp;
+       u32                             truesize;
+       u32                             next_to_clean;
+       union {
+               u32                     next_to_use;
+               u32                     next_to_post;
+       };
+       u32                             ring_len;
+};
+
+#define LIBIE_CTLQ_MBX_ATQ_LEN                 GENMASK(9, 0)
+
+/* Flags sub-structure
+ * |0  |1  |2  |3  |4  |5  |6  |7  |8  |9  |10 |11 |12 |13 |14 |15 |
+ * |DD |CMP|ERR|  * RSV *  |FTYPE  | *RSV* |RD |VFC|BUF|  HOST_ID  |
+ */
+ /* libie controlq descriptor qword0 details */
+#define LIBIE_CTLQ_DESC_FLAG_DD                BIT(0)
+#define LIBIE_CTLQ_DESC_FLAG_CMP               BIT(1)
+#define LIBIE_CTLQ_DESC_FLAG_ERR               BIT(2)
+#define LIBIE_CTLQ_DESC_FLAG_FTYPE_VM          BIT(6)
+#define LIBIE_CTLQ_DESC_FLAG_FTYPE_PF          BIT(7)
+#define LIBIE_CTLQ_DESC_FLAG_FTYPE             GENMASK(7, 6)
+#define LIBIE_CTLQ_DESC_FLAG_RD                BIT(10)
+#define LIBIE_CTLQ_DESC_FLAG_VFC               BIT(11)
+#define LIBIE_CTLQ_DESC_FLAG_BUF               BIT(12)
+#define LIBIE_CTLQ_DESC_FLAG_HOST_ID           GENMASK(15, 13)
+
+#define LIBIE_CTLQ_DESC_FLAGS                  GENMASK(15, 0)
+#define LIBIE_CTLQ_DESC_INFRA_OPCODE           GENMASK_ULL(31, 16)
+#define LIBIE_CTLQ_DESC_DATA_LEN               GENMASK_ULL(47, 32)
+#define LIBIE_CTLQ_DESC_HW_RETVAL              GENMASK_ULL(63, 48)
+
+#define LIBIE_CTLQ_DESC_PFID_VFID              GENMASK_ULL(63, 48)
+
+/* libie controlq descriptor qword1 details */
+#define LIBIE_CTLQ_DESC_VIRTCHNL_OPCODE        GENMASK(27, 0)
+#define LIBIE_CTLQ_DESC_VIRTCHNL_DESC_TYPE     GENMASK_ULL(31, 28)
+#define LIBIE_CTLQ_DESC_VIRTCHNL_MSG_RET_VAL   GENMASK_ULL(63, 32)
+
+/* libie controlq descriptor qword2 details */
+#define LIBIE_CTLQ_DESC_MSG_PARAM0             GENMASK_ULL(31, 0)
+#define LIBIE_CTLQ_DESC_SW_COOKIE              GENMASK_ULL(47, 32)
+#define LIBIE_CTLQ_DESC_VIRTCHNL_FLAGS         GENMASK_ULL(63, 48)
+
+/* libie controlq descriptor qword3 details */
+#define LIBIE_CTLQ_DESC_DATA_ADDR_HIGH         GENMASK_ULL(31, 0)
+#define LIBIE_CTLQ_DESC_DATA_ADDR_LOW          GENMASK_ULL(63, 32)
+
+/**
+ * struct libie_ctlq_desc - control queue descriptor format
+ * @qword0: flags, message opcode, data length etc
+ * @qword1: virtchnl opcode, descriptor type and return value
+ * @qword2: indirect message parameters
+ * @qword3: indirect message buffer address
+ */
+struct libie_ctlq_desc {
+       __le64                  qword0;
+       __le64                  qword1;
+       __le64                  qword2;
+       __le64                  qword3;
+};
+
+/**
+ * libie_ctlq_release_rx_buf - Release Rx buffer for a specific control queue
+ * @rx_buf: Rx buffer to be freed
+ *
+ * Driver uses this function to post back the Rx buffer after the usage.
+ */
+static inline void libie_ctlq_release_rx_buf(struct kvec *rx_buf)
+{
+       netmem_ref netmem;
+
+       if (!rx_buf->iov_base)
+               return;
+
+       netmem = virt_to_netmem(rx_buf->iov_base);
+       page_pool_put_full_netmem(netmem_get_pp(netmem), netmem, false);
+}
+
+int libie_ctlq_init(struct libie_ctlq_ctx *ctx,
+                   const struct libie_ctlq_create_info *qinfo,  u32 numq);
+void libie_ctlq_deinit(struct libie_ctlq_ctx *ctx);
+
+struct libie_ctlq_info *libie_find_ctlq(struct libie_ctlq_ctx *ctx,
+                                       enum virtchnl2_queue_type type,
+                                         int id);
+
+int libie_ctlq_send(struct libie_ctlq_info *ctlq,
+                   struct libie_ctlq_msg *q_msg, u32 num_q_msg);
+u32 libie_ctlq_recv(struct libie_ctlq_info *ctlq, struct libie_ctlq_msg *msg,
+                   u32 num_q_msg);
+
+int libie_ctlq_post_rx_buffs(struct libie_ctlq_info *ctlq);
+
+#endif /* __LIBIE_CONTROLQ_H */
-- 
2.47.0

Reply via email to