2
0
mirror of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git synced 2025-09-04 20:19:47 +08:00
linux/drivers/net/ethernet/microchip/sparx5/sparx5_fdma.c
Daniel Machon d84ad2c0d8 net: lan969x: add FDMA implementation
The lan969x switch device supports manual frame injection and extraction
to and from the switch core, using a number of injection and extraction
queues.  This technique is currently supported, but delivers poor
performance compared to Frame DMA (FDMA).

This lan969x implementation of FDMA, hooks into the existing FDMA for
Sparx5, but requires its own RX and TX handling, as lan969x does not
support the same native cache coherency that Sparx5 does. Effectively,
this means that we are going to use the DMA mapping API for mapping and
unmapping TX buffers. The RX loop will utilize the page pool API for
efficient RX handling. Other than that, the implementation is largely
the same, and utilizes the FDMA library for DCB and DB handling.

Some numbers:

Manual injection/extraction (before this series):

// iperf3 -c 1.0.1.1

[ ID] Interval           Transfer     Bitrate
[  5]   0.00-10.02  sec   345 MBytes   289 Mbits/sec  sender
[  5]   0.00-10.06  sec   345 MBytes   288 Mbits/sec  receiver

FDMA (after this series):

// iperf3 -c 1.0.1.1

[ ID] Interval           Transfer     Bitrate
[  5]   0.00-10.03  sec  1.10 GBytes   940 Mbits/sec  sender
[  5]   0.00-10.07  sec  1.10 GBytes   936 Mbits/sec  receiver

Reviewed-by: Steen Hegelund <Steen.Hegelund@microchip.com>
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
Link: https://patch.msgid.link/20250113-sparx5-lan969x-switch-driver-5-v2-5-c468f02fd623@microchip.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2025-01-15 14:13:33 -08:00

487 lines
13 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
*
* The Sparx5 Chip Register Model can be browsed at this location:
* https://github.com/microchip-ung/sparx-5_reginfo
*/
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/interrupt.h>
#include <linux/ip.h>
#include <linux/dma-mapping.h>
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
#include "sparx5_port.h"
#define FDMA_XTR_BUFFER_SIZE 2048
#define FDMA_WEIGHT 4
static int sparx5_fdma_tx_dataptr_cb(struct fdma *fdma, int dcb, int db,
u64 *dataptr)
{
*dataptr = fdma->dma + (sizeof(struct fdma_dcb) * fdma->n_dcbs) +
((dcb * fdma->n_dbs + db) * fdma->db_size);
return 0;
}
static int sparx5_fdma_rx_dataptr_cb(struct fdma *fdma, int dcb, int db,
u64 *dataptr)
{
struct sparx5 *sparx5 = fdma->priv;
struct sparx5_rx *rx = &sparx5->rx;
struct sk_buff *skb;
skb = __netdev_alloc_skb(rx->ndev, fdma->db_size, GFP_ATOMIC);
if (unlikely(!skb))
return -ENOMEM;
*dataptr = virt_to_phys(skb->data);
rx->skb[dcb][db] = skb;
return 0;
}
static void sparx5_fdma_rx_activate(struct sparx5 *sparx5, struct sparx5_rx *rx)
{
struct fdma *fdma = &rx->fdma;
/* Write the buffer address in the LLP and LLP1 regs */
spx5_wr(((u64)fdma->dma) & GENMASK(31, 0), sparx5,
FDMA_DCB_LLP(fdma->channel_id));
spx5_wr(((u64)fdma->dma) >> 32, sparx5,
FDMA_DCB_LLP1(fdma->channel_id));
/* Set the number of RX DBs to be used, and DB end-of-frame interrupt */
spx5_wr(FDMA_CH_CFG_CH_DCB_DB_CNT_SET(fdma->n_dbs) |
FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY_SET(1) |
FDMA_CH_CFG_CH_INJ_PORT_SET(XTR_QUEUE),
sparx5, FDMA_CH_CFG(fdma->channel_id));
/* Set the RX Watermark to max */
spx5_rmw(FDMA_XTR_CFG_XTR_FIFO_WM_SET(31), FDMA_XTR_CFG_XTR_FIFO_WM,
sparx5,
FDMA_XTR_CFG);
/* Start RX fdma */
spx5_rmw(FDMA_PORT_CTRL_XTR_STOP_SET(0), FDMA_PORT_CTRL_XTR_STOP,
sparx5, FDMA_PORT_CTRL(0));
/* Enable RX channel DB interrupt */
spx5_rmw(BIT(fdma->channel_id),
BIT(fdma->channel_id) & FDMA_INTR_DB_ENA_INTR_DB_ENA,
sparx5, FDMA_INTR_DB_ENA);
/* Activate the RX channel */
spx5_wr(BIT(fdma->channel_id), sparx5, FDMA_CH_ACTIVATE);
}
static void sparx5_fdma_rx_deactivate(struct sparx5 *sparx5, struct sparx5_rx *rx)
{
struct fdma *fdma = &rx->fdma;
/* Deactivate the RX channel */
spx5_rmw(0, BIT(fdma->channel_id) & FDMA_CH_ACTIVATE_CH_ACTIVATE,
sparx5, FDMA_CH_ACTIVATE);
/* Disable RX channel DB interrupt */
spx5_rmw(0, BIT(fdma->channel_id) & FDMA_INTR_DB_ENA_INTR_DB_ENA,
sparx5, FDMA_INTR_DB_ENA);
/* Stop RX fdma */
spx5_rmw(FDMA_PORT_CTRL_XTR_STOP_SET(1), FDMA_PORT_CTRL_XTR_STOP,
sparx5, FDMA_PORT_CTRL(0));
}
static void sparx5_fdma_tx_activate(struct sparx5 *sparx5, struct sparx5_tx *tx)
{
struct fdma *fdma = &tx->fdma;
/* Write the buffer address in the LLP and LLP1 regs */
spx5_wr(((u64)fdma->dma) & GENMASK(31, 0), sparx5,
FDMA_DCB_LLP(fdma->channel_id));
spx5_wr(((u64)fdma->dma) >> 32, sparx5,
FDMA_DCB_LLP1(fdma->channel_id));
/* Set the number of TX DBs to be used, and DB end-of-frame interrupt */
spx5_wr(FDMA_CH_CFG_CH_DCB_DB_CNT_SET(fdma->n_dbs) |
FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY_SET(1) |
FDMA_CH_CFG_CH_INJ_PORT_SET(INJ_QUEUE),
sparx5, FDMA_CH_CFG(fdma->channel_id));
/* Start TX fdma */
spx5_rmw(FDMA_PORT_CTRL_INJ_STOP_SET(0), FDMA_PORT_CTRL_INJ_STOP,
sparx5, FDMA_PORT_CTRL(0));
/* Activate the channel */
spx5_wr(BIT(fdma->channel_id), sparx5, FDMA_CH_ACTIVATE);
}
static void sparx5_fdma_tx_deactivate(struct sparx5 *sparx5, struct sparx5_tx *tx)
{
/* Disable the channel */
spx5_rmw(0, BIT(tx->fdma.channel_id) & FDMA_CH_ACTIVATE_CH_ACTIVATE,
sparx5, FDMA_CH_ACTIVATE);
}
void sparx5_fdma_reload(struct sparx5 *sparx5, struct fdma *fdma)
{
/* Reload the RX channel */
spx5_wr(BIT(fdma->channel_id), sparx5, FDMA_CH_RELOAD);
}
static bool sparx5_fdma_rx_get_frame(struct sparx5 *sparx5, struct sparx5_rx *rx)
{
struct fdma *fdma = &rx->fdma;
struct sparx5_port *port;
struct fdma_db *db_hw;
struct frame_info fi;
struct sk_buff *skb;
/* Check if the DCB is done */
db_hw = fdma_db_next_get(fdma);
if (unlikely(!fdma_db_is_done(db_hw)))
return false;
skb = rx->skb[fdma->dcb_index][fdma->db_index];
skb_put(skb, fdma_db_len_get(db_hw));
/* Now do the normal processing of the skb */
sparx5_ifh_parse(sparx5, (u32 *)skb->data, &fi);
/* Map to port netdev */
port = fi.src_port < sparx5->data->consts->n_ports ?
sparx5->ports[fi.src_port] :
NULL;
if (!port || !port->ndev) {
dev_err(sparx5->dev, "Data on inactive port %d\n", fi.src_port);
sparx5_xtr_flush(sparx5, XTR_QUEUE);
return false;
}
skb->dev = port->ndev;
skb_pull(skb, IFH_LEN * sizeof(u32));
if (likely(!(skb->dev->features & NETIF_F_RXFCS)))
skb_trim(skb, skb->len - ETH_FCS_LEN);
sparx5_ptp_rxtstamp(sparx5, skb, fi.timestamp);
skb->protocol = eth_type_trans(skb, skb->dev);
/* Everything we see on an interface that is in the HW bridge
* has already been forwarded
*/
if (test_bit(port->portno, sparx5->bridge_mask))
skb->offload_fwd_mark = 1;
skb->dev->stats.rx_bytes += skb->len;
skb->dev->stats.rx_packets++;
rx->packets++;
netif_receive_skb(skb);
return true;
}
int sparx5_fdma_napi_callback(struct napi_struct *napi, int weight)
{
struct sparx5_rx *rx = container_of(napi, struct sparx5_rx, napi);
struct sparx5 *sparx5 = container_of(rx, struct sparx5, rx);
struct fdma *fdma = &rx->fdma;
int counter = 0;
while (counter < weight && sparx5_fdma_rx_get_frame(sparx5, rx)) {
fdma_db_advance(fdma);
counter++;
/* Check if the DCB can be reused */
if (fdma_dcb_is_reusable(fdma))
continue;
fdma_dcb_add(fdma, fdma->dcb_index,
FDMA_DCB_INFO_DATAL(fdma->db_size),
FDMA_DCB_STATUS_INTR);
fdma_db_reset(fdma);
fdma_dcb_advance(fdma);
}
if (counter < weight) {
napi_complete_done(&rx->napi, counter);
spx5_rmw(BIT(fdma->channel_id),
BIT(fdma->channel_id) & FDMA_INTR_DB_ENA_INTR_DB_ENA,
sparx5, FDMA_INTR_DB_ENA);
}
if (counter)
sparx5_fdma_reload(sparx5, fdma);
return counter;
}
int sparx5_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,
struct net_device *dev)
{
struct sparx5_tx *tx = &sparx5->tx;
struct fdma *fdma = &tx->fdma;
void *virt_addr;
fdma_dcb_advance(fdma);
if (!fdma_db_is_done(fdma_db_get(fdma, fdma->dcb_index, 0)))
return -EINVAL;
/* Get the virtual address of the dataptr for the next DB */
virt_addr = ((u8 *)fdma->dcbs +
(sizeof(struct fdma_dcb) * fdma->n_dcbs) +
((fdma->dcb_index * fdma->n_dbs) * fdma->db_size));
memcpy(virt_addr, ifh, IFH_LEN * 4);
memcpy(virt_addr + IFH_LEN * 4, skb->data, skb->len);
fdma_dcb_add(fdma, fdma->dcb_index, 0,
FDMA_DCB_STATUS_SOF |
FDMA_DCB_STATUS_EOF |
FDMA_DCB_STATUS_BLOCKO(0) |
FDMA_DCB_STATUS_BLOCKL(skb->len + IFH_LEN * 4 + 4));
sparx5_fdma_reload(sparx5, fdma);
return NETDEV_TX_OK;
}
static int sparx5_fdma_rx_alloc(struct sparx5 *sparx5)
{
struct sparx5_rx *rx = &sparx5->rx;
struct fdma *fdma = &rx->fdma;
int err;
err = fdma_alloc_phys(fdma);
if (err)
return err;
fdma_dcbs_init(fdma, FDMA_DCB_INFO_DATAL(fdma->db_size),
FDMA_DCB_STATUS_INTR);
return 0;
}
static int sparx5_fdma_tx_alloc(struct sparx5 *sparx5)
{
struct sparx5_tx *tx = &sparx5->tx;
struct fdma *fdma = &tx->fdma;
int err;
err = fdma_alloc_phys(fdma);
if (err)
return err;
fdma_dcbs_init(fdma, FDMA_DCB_INFO_DATAL(fdma->db_size),
FDMA_DCB_STATUS_DONE);
return 0;
}
static void sparx5_fdma_rx_init(struct sparx5 *sparx5,
struct sparx5_rx *rx, int channel)
{
struct fdma *fdma = &rx->fdma;
int idx;
fdma->channel_id = channel;
fdma->n_dcbs = FDMA_DCB_MAX;
fdma->n_dbs = FDMA_RX_DCB_MAX_DBS;
fdma->priv = sparx5;
fdma->db_size = ALIGN(FDMA_XTR_BUFFER_SIZE, PAGE_SIZE);
fdma->size = fdma_get_size(&sparx5->rx.fdma);
fdma->ops.dataptr_cb = &sparx5_fdma_rx_dataptr_cb;
fdma->ops.nextptr_cb = &fdma_nextptr_cb;
/* Fetch a netdev for SKB and NAPI use, any will do */
for (idx = 0; idx < sparx5->data->consts->n_ports; ++idx) {
struct sparx5_port *port = sparx5->ports[idx];
if (port && port->ndev) {
rx->ndev = port->ndev;
break;
}
}
}
static void sparx5_fdma_tx_init(struct sparx5 *sparx5,
struct sparx5_tx *tx, int channel)
{
struct fdma *fdma = &tx->fdma;
fdma->channel_id = channel;
fdma->n_dcbs = FDMA_DCB_MAX;
fdma->n_dbs = FDMA_TX_DCB_MAX_DBS;
fdma->priv = sparx5;
fdma->db_size = ALIGN(FDMA_XTR_BUFFER_SIZE, PAGE_SIZE);
fdma->size = fdma_get_size_contiguous(&sparx5->tx.fdma);
fdma->ops.dataptr_cb = &sparx5_fdma_tx_dataptr_cb;
fdma->ops.nextptr_cb = &fdma_nextptr_cb;
}
irqreturn_t sparx5_fdma_handler(int irq, void *args)
{
struct sparx5 *sparx5 = args;
u32 db = 0, err = 0;
db = spx5_rd(sparx5, FDMA_INTR_DB);
err = spx5_rd(sparx5, FDMA_INTR_ERR);
/* Clear interrupt */
if (db) {
spx5_wr(0, sparx5, FDMA_INTR_DB_ENA);
spx5_wr(db, sparx5, FDMA_INTR_DB);
napi_schedule(&sparx5->rx.napi);
}
if (err) {
u32 err_type = spx5_rd(sparx5, FDMA_ERRORS);
dev_err_ratelimited(sparx5->dev,
"ERR: int: %#x, type: %#x\n",
err, err_type);
spx5_wr(err, sparx5, FDMA_INTR_ERR);
spx5_wr(err_type, sparx5, FDMA_ERRORS);
}
return IRQ_HANDLED;
}
void sparx5_fdma_injection_mode(struct sparx5 *sparx5)
{
const int byte_swap = 1;
int portno;
int urgency;
/* Change mode to fdma extraction and injection */
spx5_wr(QS_XTR_GRP_CFG_MODE_SET(2) |
QS_XTR_GRP_CFG_STATUS_WORD_POS_SET(1) |
QS_XTR_GRP_CFG_BYTE_SWAP_SET(byte_swap),
sparx5, QS_XTR_GRP_CFG(XTR_QUEUE));
spx5_wr(QS_INJ_GRP_CFG_MODE_SET(2) |
QS_INJ_GRP_CFG_BYTE_SWAP_SET(byte_swap),
sparx5, QS_INJ_GRP_CFG(INJ_QUEUE));
/* CPU ports capture setup */
for (portno = sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_0);
portno <= sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_1);
portno++) {
/* ASM CPU port: No preamble, IFH, enable padding */
spx5_wr(ASM_PORT_CFG_PAD_ENA_SET(1) |
ASM_PORT_CFG_NO_PREAMBLE_ENA_SET(1) |
ASM_PORT_CFG_INJ_FORMAT_CFG_SET(1), /* 1 = IFH */
sparx5, ASM_PORT_CFG(portno));
/* Reset WM cnt to unclog queued frames */
spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR_SET(1),
DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR,
sparx5,
DSM_DEV_TX_STOP_WM_CFG(portno));
/* Set Disassembler Stop Watermark level */
spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM_SET(100),
DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM,
sparx5,
DSM_DEV_TX_STOP_WM_CFG(portno));
/* Enable port in queue system */
urgency = sparx5_port_fwd_urg(sparx5, SPEED_2500);
spx5_rmw(QFWD_SWITCH_PORT_MODE_PORT_ENA_SET(1) |
QFWD_SWITCH_PORT_MODE_FWD_URGENCY_SET(urgency),
QFWD_SWITCH_PORT_MODE_PORT_ENA |
QFWD_SWITCH_PORT_MODE_FWD_URGENCY,
sparx5,
QFWD_SWITCH_PORT_MODE(portno));
/* Disable Disassembler buffer underrun watchdog
* to avoid truncated packets in XTR
*/
spx5_rmw(DSM_BUF_CFG_UNDERFLOW_WATCHDOG_DIS_SET(1),
DSM_BUF_CFG_UNDERFLOW_WATCHDOG_DIS,
sparx5,
DSM_BUF_CFG(portno));
/* Disabling frame aging */
spx5_rmw(HSCH_PORT_MODE_AGE_DIS_SET(1),
HSCH_PORT_MODE_AGE_DIS,
sparx5,
HSCH_PORT_MODE(portno));
}
}
int sparx5_fdma_init(struct sparx5 *sparx5)
{
int err;
/* Reset FDMA state */
spx5_wr(FDMA_CTRL_NRESET_SET(0), sparx5, FDMA_CTRL);
spx5_wr(FDMA_CTRL_NRESET_SET(1), sparx5, FDMA_CTRL);
/* Force ACP caching but disable read/write allocation */
spx5_rmw(CPU_PROC_CTRL_ACP_CACHE_FORCE_ENA_SET(1) |
CPU_PROC_CTRL_ACP_AWCACHE_SET(0) |
CPU_PROC_CTRL_ACP_ARCACHE_SET(0),
CPU_PROC_CTRL_ACP_CACHE_FORCE_ENA |
CPU_PROC_CTRL_ACP_AWCACHE |
CPU_PROC_CTRL_ACP_ARCACHE,
sparx5, CPU_PROC_CTRL);
sparx5_fdma_injection_mode(sparx5);
sparx5_fdma_rx_init(sparx5, &sparx5->rx, FDMA_XTR_CHANNEL);
sparx5_fdma_tx_init(sparx5, &sparx5->tx, FDMA_INJ_CHANNEL);
err = sparx5_fdma_rx_alloc(sparx5);
if (err) {
dev_err(sparx5->dev, "Could not allocate RX buffers: %d\n", err);
return err;
}
err = sparx5_fdma_tx_alloc(sparx5);
if (err) {
dev_err(sparx5->dev, "Could not allocate TX buffers: %d\n", err);
return err;
}
return err;
}
int sparx5_fdma_deinit(struct sparx5 *sparx5)
{
sparx5_fdma_stop(sparx5);
fdma_free_phys(&sparx5->rx.fdma);
fdma_free_phys(&sparx5->tx.fdma);
return 0;
}
static u32 sparx5_fdma_port_ctrl(struct sparx5 *sparx5)
{
return spx5_rd(sparx5, FDMA_PORT_CTRL(0));
}
int sparx5_fdma_start(struct sparx5 *sparx5)
{
const struct sparx5_ops *ops = sparx5->data->ops;
struct sparx5_rx *rx = &sparx5->rx;
struct sparx5_tx *tx = &sparx5->tx;
netif_napi_add_weight(rx->ndev,
&rx->napi,
ops->fdma_poll,
FDMA_WEIGHT);
napi_enable(&rx->napi);
sparx5_fdma_rx_activate(sparx5, rx);
sparx5_fdma_tx_activate(sparx5, tx);
return 0;
}
int sparx5_fdma_stop(struct sparx5 *sparx5)
{
struct sparx5_rx *rx = &sparx5->rx;
struct sparx5_tx *tx = &sparx5->tx;
u32 val;
napi_disable(&rx->napi);
/* Stop the fdma and channel interrupts */
sparx5_fdma_rx_deactivate(sparx5, rx);
sparx5_fdma_tx_deactivate(sparx5, tx);
/* Wait for the RX channel to stop */
read_poll_timeout(sparx5_fdma_port_ctrl, val,
FDMA_PORT_CTRL_XTR_BUF_IS_EMPTY_GET(val) == 0,
500, 10000, 0, sparx5);
return 0;
}