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