mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
463 lines
11 KiB
C
463 lines
11 KiB
C
![]() |
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
|
||
|
/*
|
||
|
* NETC NTMP (NETC Table Management Protocol) 2.0 Library
|
||
|
* Copyright 2025 NXP
|
||
|
*/
|
||
|
|
||
|
#include <linux/dma-mapping.h>
|
||
|
#include <linux/fsl/netc_global.h>
|
||
|
#include <linux/iopoll.h>
|
||
|
|
||
|
#include "ntmp_private.h"
|
||
|
|
||
|
#define NETC_CBDR_TIMEOUT 1000 /* us */
|
||
|
#define NETC_CBDR_DELAY_US 10
|
||
|
#define NETC_CBDR_MR_EN BIT(31)
|
||
|
|
||
|
#define NTMP_BASE_ADDR_ALIGN 128
|
||
|
#define NTMP_DATA_ADDR_ALIGN 32
|
||
|
|
||
|
/* Define NTMP Table ID */
|
||
|
#define NTMP_MAFT_ID 1
|
||
|
#define NTMP_RSST_ID 3
|
||
|
|
||
|
/* Generic Update Actions for most tables */
|
||
|
#define NTMP_GEN_UA_CFGEU BIT(0)
|
||
|
#define NTMP_GEN_UA_STSEU BIT(1)
|
||
|
|
||
|
#define NTMP_ENTRY_ID_SIZE 4
|
||
|
#define RSST_ENTRY_NUM 64
|
||
|
#define RSST_STSE_DATA_SIZE(n) ((n) * 8)
|
||
|
#define RSST_CFGE_DATA_SIZE(n) (n)
|
||
|
|
||
|
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
|
||
|
const struct netc_cbdr_regs *regs)
|
||
|
{
|
||
|
int cbd_num = NETC_CBDR_BD_NUM;
|
||
|
size_t size;
|
||
|
|
||
|
size = cbd_num * sizeof(union netc_cbd) + NTMP_BASE_ADDR_ALIGN;
|
||
|
cbdr->addr_base = dma_alloc_coherent(dev, size, &cbdr->dma_base,
|
||
|
GFP_KERNEL);
|
||
|
if (!cbdr->addr_base)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
cbdr->dma_size = size;
|
||
|
cbdr->bd_num = cbd_num;
|
||
|
cbdr->regs = *regs;
|
||
|
cbdr->dev = dev;
|
||
|
|
||
|
/* The base address of the Control BD Ring must be 128 bytes aligned */
|
||
|
cbdr->dma_base_align = ALIGN(cbdr->dma_base, NTMP_BASE_ADDR_ALIGN);
|
||
|
cbdr->addr_base_align = PTR_ALIGN(cbdr->addr_base,
|
||
|
NTMP_BASE_ADDR_ALIGN);
|
||
|
|
||
|
cbdr->next_to_clean = 0;
|
||
|
cbdr->next_to_use = 0;
|
||
|
spin_lock_init(&cbdr->ring_lock);
|
||
|
|
||
|
/* Step 1: Configure the base address of the Control BD Ring */
|
||
|
netc_write(cbdr->regs.bar0, lower_32_bits(cbdr->dma_base_align));
|
||
|
netc_write(cbdr->regs.bar1, upper_32_bits(cbdr->dma_base_align));
|
||
|
|
||
|
/* Step 2: Configure the producer index register */
|
||
|
netc_write(cbdr->regs.pir, cbdr->next_to_clean);
|
||
|
|
||
|
/* Step 3: Configure the consumer index register */
|
||
|
netc_write(cbdr->regs.cir, cbdr->next_to_use);
|
||
|
|
||
|
/* Step4: Configure the number of BDs of the Control BD Ring */
|
||
|
netc_write(cbdr->regs.lenr, cbdr->bd_num);
|
||
|
|
||
|
/* Step 5: Enable the Control BD Ring */
|
||
|
netc_write(cbdr->regs.mr, NETC_CBDR_MR_EN);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(ntmp_init_cbdr);
|
||
|
|
||
|
void ntmp_free_cbdr(struct netc_cbdr *cbdr)
|
||
|
{
|
||
|
/* Disable the Control BD Ring */
|
||
|
netc_write(cbdr->regs.mr, 0);
|
||
|
dma_free_coherent(cbdr->dev, cbdr->dma_size, cbdr->addr_base,
|
||
|
cbdr->dma_base);
|
||
|
memset(cbdr, 0, sizeof(*cbdr));
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(ntmp_free_cbdr);
|
||
|
|
||
|
static int ntmp_get_free_cbd_num(struct netc_cbdr *cbdr)
|
||
|
{
|
||
|
return (cbdr->next_to_clean - cbdr->next_to_use - 1 +
|
||
|
cbdr->bd_num) % cbdr->bd_num;
|
||
|
}
|
||
|
|
||
|
static union netc_cbd *ntmp_get_cbd(struct netc_cbdr *cbdr, int index)
|
||
|
{
|
||
|
return &((union netc_cbd *)(cbdr->addr_base_align))[index];
|
||
|
}
|
||
|
|
||
|
static void ntmp_clean_cbdr(struct netc_cbdr *cbdr)
|
||
|
{
|
||
|
union netc_cbd *cbd;
|
||
|
int i;
|
||
|
|
||
|
i = cbdr->next_to_clean;
|
||
|
while (netc_read(cbdr->regs.cir) != i) {
|
||
|
cbd = ntmp_get_cbd(cbdr, i);
|
||
|
memset(cbd, 0, sizeof(*cbd));
|
||
|
i = (i + 1) % cbdr->bd_num;
|
||
|
}
|
||
|
|
||
|
cbdr->next_to_clean = i;
|
||
|
}
|
||
|
|
||
|
static int netc_xmit_ntmp_cmd(struct ntmp_user *user, union netc_cbd *cbd)
|
||
|
{
|
||
|
union netc_cbd *cur_cbd;
|
||
|
struct netc_cbdr *cbdr;
|
||
|
int i, err;
|
||
|
u16 status;
|
||
|
u32 val;
|
||
|
|
||
|
/* Currently only i.MX95 ENETC is supported, and it only has one
|
||
|
* command BD ring
|
||
|
*/
|
||
|
cbdr = &user->ring[0];
|
||
|
|
||
|
spin_lock_bh(&cbdr->ring_lock);
|
||
|
|
||
|
if (unlikely(!ntmp_get_free_cbd_num(cbdr)))
|
||
|
ntmp_clean_cbdr(cbdr);
|
||
|
|
||
|
i = cbdr->next_to_use;
|
||
|
cur_cbd = ntmp_get_cbd(cbdr, i);
|
||
|
*cur_cbd = *cbd;
|
||
|
dma_wmb();
|
||
|
|
||
|
/* Update producer index of both software and hardware */
|
||
|
i = (i + 1) % cbdr->bd_num;
|
||
|
cbdr->next_to_use = i;
|
||
|
netc_write(cbdr->regs.pir, i);
|
||
|
|
||
|
err = read_poll_timeout_atomic(netc_read, val, val == i,
|
||
|
NETC_CBDR_DELAY_US, NETC_CBDR_TIMEOUT,
|
||
|
true, cbdr->regs.cir);
|
||
|
if (unlikely(err))
|
||
|
goto cbdr_unlock;
|
||
|
|
||
|
dma_rmb();
|
||
|
/* Get the writeback command BD, because the caller may need
|
||
|
* to check some other fields of the response header.
|
||
|
*/
|
||
|
*cbd = *cur_cbd;
|
||
|
|
||
|
/* Check the writeback error status */
|
||
|
status = le16_to_cpu(cbd->resp_hdr.error_rr) & NTMP_RESP_ERROR;
|
||
|
if (unlikely(status)) {
|
||
|
err = -EIO;
|
||
|
dev_err(user->dev, "Command BD error: 0x%04x\n", status);
|
||
|
}
|
||
|
|
||
|
ntmp_clean_cbdr(cbdr);
|
||
|
dma_wmb();
|
||
|
|
||
|
cbdr_unlock:
|
||
|
spin_unlock_bh(&cbdr->ring_lock);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static int ntmp_alloc_data_mem(struct ntmp_dma_buf *data, void **buf_align)
|
||
|
{
|
||
|
void *buf;
|
||
|
|
||
|
buf = dma_alloc_coherent(data->dev, data->size + NTMP_DATA_ADDR_ALIGN,
|
||
|
&data->dma, GFP_KERNEL);
|
||
|
if (!buf)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
data->buf = buf;
|
||
|
*buf_align = PTR_ALIGN(buf, NTMP_DATA_ADDR_ALIGN);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void ntmp_free_data_mem(struct ntmp_dma_buf *data)
|
||
|
{
|
||
|
dma_free_coherent(data->dev, data->size + NTMP_DATA_ADDR_ALIGN,
|
||
|
data->buf, data->dma);
|
||
|
}
|
||
|
|
||
|
static void ntmp_fill_request_hdr(union netc_cbd *cbd, dma_addr_t dma,
|
||
|
int len, int table_id, int cmd,
|
||
|
int access_method)
|
||
|
{
|
||
|
dma_addr_t dma_align;
|
||
|
|
||
|
memset(cbd, 0, sizeof(*cbd));
|
||
|
dma_align = ALIGN(dma, NTMP_DATA_ADDR_ALIGN);
|
||
|
cbd->req_hdr.addr = cpu_to_le64(dma_align);
|
||
|
cbd->req_hdr.len = cpu_to_le32(len);
|
||
|
cbd->req_hdr.cmd = cmd;
|
||
|
cbd->req_hdr.access_method = FIELD_PREP(NTMP_ACCESS_METHOD,
|
||
|
access_method);
|
||
|
cbd->req_hdr.table_id = table_id;
|
||
|
cbd->req_hdr.ver_cci_rr = FIELD_PREP(NTMP_HDR_VERSION,
|
||
|
NTMP_HDR_VER2);
|
||
|
/* For NTMP version 2.0 or later version */
|
||
|
cbd->req_hdr.npf = cpu_to_le32(NTMP_NPF);
|
||
|
}
|
||
|
|
||
|
static void ntmp_fill_crd(struct ntmp_cmn_req_data *crd, u8 tblv,
|
||
|
u8 qa, u16 ua)
|
||
|
{
|
||
|
crd->update_act = cpu_to_le16(ua);
|
||
|
crd->tblv_qact = NTMP_TBLV_QACT(tblv, qa);
|
||
|
}
|
||
|
|
||
|
static void ntmp_fill_crd_eid(struct ntmp_req_by_eid *rbe, u8 tblv,
|
||
|
u8 qa, u16 ua, u32 entry_id)
|
||
|
{
|
||
|
ntmp_fill_crd(&rbe->crd, tblv, qa, ua);
|
||
|
rbe->entry_id = cpu_to_le32(entry_id);
|
||
|
}
|
||
|
|
||
|
static const char *ntmp_table_name(int tbl_id)
|
||
|
{
|
||
|
switch (tbl_id) {
|
||
|
case NTMP_MAFT_ID:
|
||
|
return "MAC Address Filter Table";
|
||
|
case NTMP_RSST_ID:
|
||
|
return "RSS Table";
|
||
|
default:
|
||
|
return "Unknown Table";
|
||
|
};
|
||
|
}
|
||
|
|
||
|
static int ntmp_delete_entry_by_id(struct ntmp_user *user, int tbl_id,
|
||
|
u8 tbl_ver, u32 entry_id, u32 req_len,
|
||
|
u32 resp_len)
|
||
|
{
|
||
|
struct ntmp_dma_buf data = {
|
||
|
.dev = user->dev,
|
||
|
.size = max(req_len, resp_len),
|
||
|
};
|
||
|
struct ntmp_req_by_eid *req;
|
||
|
union netc_cbd cbd;
|
||
|
int err;
|
||
|
|
||
|
err = ntmp_alloc_data_mem(&data, (void **)&req);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
ntmp_fill_crd_eid(req, tbl_ver, 0, 0, entry_id);
|
||
|
ntmp_fill_request_hdr(&cbd, data.dma, NTMP_LEN(req_len, resp_len),
|
||
|
tbl_id, NTMP_CMD_DELETE, NTMP_AM_ENTRY_ID);
|
||
|
|
||
|
err = netc_xmit_ntmp_cmd(user, &cbd);
|
||
|
if (err)
|
||
|
dev_err(user->dev,
|
||
|
"Failed to delete entry 0x%x of %s, err: %pe",
|
||
|
entry_id, ntmp_table_name(tbl_id), ERR_PTR(err));
|
||
|
|
||
|
ntmp_free_data_mem(&data);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static int ntmp_query_entry_by_id(struct ntmp_user *user, int tbl_id,
|
||
|
u32 len, struct ntmp_req_by_eid *req,
|
||
|
dma_addr_t dma, bool compare_eid)
|
||
|
{
|
||
|
struct ntmp_cmn_resp_query *resp;
|
||
|
int cmd = NTMP_CMD_QUERY;
|
||
|
union netc_cbd cbd;
|
||
|
u32 entry_id;
|
||
|
int err;
|
||
|
|
||
|
entry_id = le32_to_cpu(req->entry_id);
|
||
|
if (le16_to_cpu(req->crd.update_act))
|
||
|
cmd = NTMP_CMD_QU;
|
||
|
|
||
|
/* Request header */
|
||
|
ntmp_fill_request_hdr(&cbd, dma, len, tbl_id, cmd, NTMP_AM_ENTRY_ID);
|
||
|
err = netc_xmit_ntmp_cmd(user, &cbd);
|
||
|
if (err) {
|
||
|
dev_err(user->dev,
|
||
|
"Failed to query entry 0x%x of %s, err: %pe\n",
|
||
|
entry_id, ntmp_table_name(tbl_id), ERR_PTR(err));
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
/* For a few tables, the first field of their response data is not
|
||
|
* entry_id, so directly return success.
|
||
|
*/
|
||
|
if (!compare_eid)
|
||
|
return 0;
|
||
|
|
||
|
resp = (struct ntmp_cmn_resp_query *)req;
|
||
|
if (unlikely(le32_to_cpu(resp->entry_id) != entry_id)) {
|
||
|
dev_err(user->dev,
|
||
|
"%s: query EID 0x%x doesn't match response EID 0x%x\n",
|
||
|
ntmp_table_name(tbl_id), entry_id, le32_to_cpu(resp->entry_id));
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int ntmp_maft_add_entry(struct ntmp_user *user, u32 entry_id,
|
||
|
struct maft_entry_data *maft)
|
||
|
{
|
||
|
struct ntmp_dma_buf data = {
|
||
|
.dev = user->dev,
|
||
|
.size = sizeof(struct maft_req_add),
|
||
|
};
|
||
|
struct maft_req_add *req;
|
||
|
union netc_cbd cbd;
|
||
|
int err;
|
||
|
|
||
|
err = ntmp_alloc_data_mem(&data, (void **)&req);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
/* Set mac address filter table request data buffer */
|
||
|
ntmp_fill_crd_eid(&req->rbe, user->tbl.maft_ver, 0, 0, entry_id);
|
||
|
req->keye = maft->keye;
|
||
|
req->cfge = maft->cfge;
|
||
|
|
||
|
ntmp_fill_request_hdr(&cbd, data.dma, NTMP_LEN(data.size, 0),
|
||
|
NTMP_MAFT_ID, NTMP_CMD_ADD, NTMP_AM_ENTRY_ID);
|
||
|
err = netc_xmit_ntmp_cmd(user, &cbd);
|
||
|
if (err)
|
||
|
dev_err(user->dev, "Failed to add MAFT entry 0x%x, err: %pe\n",
|
||
|
entry_id, ERR_PTR(err));
|
||
|
|
||
|
ntmp_free_data_mem(&data);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(ntmp_maft_add_entry);
|
||
|
|
||
|
int ntmp_maft_query_entry(struct ntmp_user *user, u32 entry_id,
|
||
|
struct maft_entry_data *maft)
|
||
|
{
|
||
|
struct ntmp_dma_buf data = {
|
||
|
.dev = user->dev,
|
||
|
.size = sizeof(struct maft_resp_query),
|
||
|
};
|
||
|
struct maft_resp_query *resp;
|
||
|
struct ntmp_req_by_eid *req;
|
||
|
int err;
|
||
|
|
||
|
err = ntmp_alloc_data_mem(&data, (void **)&req);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
ntmp_fill_crd_eid(req, user->tbl.maft_ver, 0, 0, entry_id);
|
||
|
err = ntmp_query_entry_by_id(user, NTMP_MAFT_ID,
|
||
|
NTMP_LEN(sizeof(*req), data.size),
|
||
|
req, data.dma, true);
|
||
|
if (err)
|
||
|
goto end;
|
||
|
|
||
|
resp = (struct maft_resp_query *)req;
|
||
|
maft->keye = resp->keye;
|
||
|
maft->cfge = resp->cfge;
|
||
|
|
||
|
end:
|
||
|
ntmp_free_data_mem(&data);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(ntmp_maft_query_entry);
|
||
|
|
||
|
int ntmp_maft_delete_entry(struct ntmp_user *user, u32 entry_id)
|
||
|
{
|
||
|
return ntmp_delete_entry_by_id(user, NTMP_MAFT_ID, user->tbl.maft_ver,
|
||
|
entry_id, NTMP_EID_REQ_LEN, 0);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(ntmp_maft_delete_entry);
|
||
|
|
||
|
int ntmp_rsst_update_entry(struct ntmp_user *user, const u32 *table,
|
||
|
int count)
|
||
|
{
|
||
|
struct ntmp_dma_buf data = {.dev = user->dev};
|
||
|
struct rsst_req_update *req;
|
||
|
union netc_cbd cbd;
|
||
|
int err, i;
|
||
|
|
||
|
if (count != RSST_ENTRY_NUM)
|
||
|
/* HW only takes in a full 64 entry table */
|
||
|
return -EINVAL;
|
||
|
|
||
|
data.size = struct_size(req, groups, count);
|
||
|
err = ntmp_alloc_data_mem(&data, (void **)&req);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
/* Set the request data buffer */
|
||
|
ntmp_fill_crd_eid(&req->rbe, user->tbl.rsst_ver, 0,
|
||
|
NTMP_GEN_UA_CFGEU | NTMP_GEN_UA_STSEU, 0);
|
||
|
for (i = 0; i < count; i++)
|
||
|
req->groups[i] = (u8)(table[i]);
|
||
|
|
||
|
ntmp_fill_request_hdr(&cbd, data.dma, NTMP_LEN(data.size, 0),
|
||
|
NTMP_RSST_ID, NTMP_CMD_UPDATE, NTMP_AM_ENTRY_ID);
|
||
|
|
||
|
err = netc_xmit_ntmp_cmd(user, &cbd);
|
||
|
if (err)
|
||
|
dev_err(user->dev, "Failed to update RSST entry, err: %pe\n",
|
||
|
ERR_PTR(err));
|
||
|
|
||
|
ntmp_free_data_mem(&data);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(ntmp_rsst_update_entry);
|
||
|
|
||
|
int ntmp_rsst_query_entry(struct ntmp_user *user, u32 *table, int count)
|
||
|
{
|
||
|
struct ntmp_dma_buf data = {.dev = user->dev};
|
||
|
struct ntmp_req_by_eid *req;
|
||
|
union netc_cbd cbd;
|
||
|
int err, i;
|
||
|
u8 *group;
|
||
|
|
||
|
if (count != RSST_ENTRY_NUM)
|
||
|
/* HW only takes in a full 64 entry table */
|
||
|
return -EINVAL;
|
||
|
|
||
|
data.size = NTMP_ENTRY_ID_SIZE + RSST_STSE_DATA_SIZE(count) +
|
||
|
RSST_CFGE_DATA_SIZE(count);
|
||
|
err = ntmp_alloc_data_mem(&data, (void **)&req);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
/* Set the request data buffer */
|
||
|
ntmp_fill_crd_eid(req, user->tbl.rsst_ver, 0, 0, 0);
|
||
|
ntmp_fill_request_hdr(&cbd, data.dma, NTMP_LEN(sizeof(*req), data.size),
|
||
|
NTMP_RSST_ID, NTMP_CMD_QUERY, NTMP_AM_ENTRY_ID);
|
||
|
err = netc_xmit_ntmp_cmd(user, &cbd);
|
||
|
if (err) {
|
||
|
dev_err(user->dev, "Failed to query RSST entry, err: %pe\n",
|
||
|
ERR_PTR(err));
|
||
|
goto end;
|
||
|
}
|
||
|
|
||
|
group = (u8 *)req;
|
||
|
group += NTMP_ENTRY_ID_SIZE + RSST_STSE_DATA_SIZE(count);
|
||
|
for (i = 0; i < count; i++)
|
||
|
table[i] = group[i];
|
||
|
|
||
|
end:
|
||
|
ntmp_free_data_mem(&data);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(ntmp_rsst_query_entry);
|
||
|
|
||
|
MODULE_DESCRIPTION("NXP NETC Library");
|
||
|
MODULE_LICENSE("Dual BSD/GPL");
|