// 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,
				     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, &params->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(&params);
}
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(&params->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");
