Commit 586b298d authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'net-lan969x-add-fdma-support'

Daniel Machon says:

====================
net: lan969x: add FDMA support

== Description:

This series is the last of a multi-part series, that prepares and adds
support for the new lan969x switch driver.

The upstreaming efforts has been split into multiple series:

        1) Prepare the Sparx5 driver for lan969x (merged)

        2) Add support for lan969x (same basic features as Sparx5
           provides excl. FDMA and VCAP, merged).

        3) Add lan969x VCAP functionality (merged).

        4) Add RGMII support (merged).

    --> 5) Add FDMA support.

== FDMA support:

The lan969x switch device uses the same FDMA engine as the Sparx5 switch
device, with the same number of channels etc. This means we can utilize
the newly added FDMA library, that is already in use by the lan966x and
sparx5 drivers.

As previous lan969x series, the FDMA implementation will hook into the
Sparx5 implementation where possible, however both RX and TX handling
will be done differently on lan969x and therefore requires a separate
implementation of the RX and TX path.

Details are in the commit description of the individual patches

== Patch breakdown:

Patch #1: Enable FDMA support on lan969x
Patch #2: Split start()/stop() functions
Patch #3: Activate TX FDMA in start()
Patch #4: Ops out a few functions that differ on the two platforms
Patch #5: Add FDMA implementation for lan969x

v1: https://lore.kernel.org/20250109-sparx5-lan969x-switch-driver-5-v1-0-13d6d8451e63@microchip.com
====================

Link: https://patch.msgid.link/20250113-sparx5-lan969x-switch-driver-5-v2-0-c468f02fd623@microchip.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents e80ed977 d84ad2c0
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -28,5 +28,6 @@ config SPARX5_DCB
config LAN969X_SWITCH
	bool "Lan969x switch driver"
	depends on SPARX5_SWITCH
	select PAGE_POOL
	help
	  This driver supports the lan969x family of network switch devices.
+2 −1
Original line number Diff line number Diff line
@@ -21,7 +21,8 @@ sparx5-switch-$(CONFIG_LAN969X_SWITCH) += lan969x/lan969x_regs.o \
					  lan969x/lan969x_calendar.o \
					  lan969x/lan969x_vcap_ag_api.o \
					  lan969x/lan969x_vcap_impl.o \
					  lan969x/lan969x_rgmii.o
					  lan969x/lan969x_rgmii.o \
					  lan969x/lan969x_fdma.o

# Provide include files
ccflags-y += -I$(srctree)/drivers/net/ethernet/microchip/vcap
+4 −0
Original line number Diff line number Diff line
@@ -341,6 +341,10 @@ static const struct sparx5_ops lan969x_ops = {
	.ptp_irq_handler         = &lan969x_ptp_irq_handler,
	.dsm_calendar_calc       = &lan969x_dsm_calendar_calc,
	.port_config_rgmii       = &lan969x_port_config_rgmii,
	.fdma_init               = &lan969x_fdma_init,
	.fdma_deinit             = &lan969x_fdma_deinit,
	.fdma_poll               = &lan969x_fdma_napi_poll,
	.fdma_xmit               = &lan969x_fdma_xmit,
};

const struct sparx5_match_data lan969x_desc = {
+7 −0
Original line number Diff line number Diff line
@@ -72,4 +72,11 @@ int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,
int lan969x_port_config_rgmii(struct sparx5_port *port,
			      struct sparx5_port_config *conf);

/* lan969x_fdma.c */
int lan969x_fdma_init(struct sparx5 *sparx5);
int lan969x_fdma_deinit(struct sparx5 *sparx5);
int lan969x_fdma_napi_poll(struct napi_struct *napi, int weight);
int lan969x_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,
		      struct net_device *dev);

#endif
+406 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0+
/* Microchip lan969x Switch driver
 *
 * Copyright (c) 2025 Microchip Technology Inc. and its subsidiaries.
 */
#include <net/page_pool/helpers.h>

#include "../sparx5_main.h"
#include "../sparx5_main_regs.h"
#include "../sparx5_port.h"

#include "fdma_api.h"
#include "lan969x.h"

#define FDMA_PRIV(fdma) ((struct sparx5 *)((fdma)->priv))

static int lan969x_fdma_tx_dataptr_cb(struct fdma *fdma, int dcb, int db,
				      u64 *dataptr)
{
	*dataptr = FDMA_PRIV(fdma)->tx.dbs[dcb].dma_addr;

	return 0;
}

static int lan969x_fdma_rx_dataptr_cb(struct fdma *fdma, int dcb, int db,
				      u64 *dataptr)
{
	struct sparx5_rx *rx = &FDMA_PRIV(fdma)->rx;
	struct page *page;

	page = page_pool_dev_alloc_pages(rx->page_pool);
	if (unlikely(!page))
		return -ENOMEM;

	rx->page[dcb][db] = page;

	*dataptr = page_pool_get_dma_addr(page);

	return 0;
}

static int lan969x_fdma_get_next_dcb(struct sparx5_tx *tx)
{
	struct fdma *fdma = &tx->fdma;

	for (int i = 0; i < fdma->n_dcbs; ++i)
		if (!tx->dbs[i].used && !fdma_is_last(fdma, &fdma->dcbs[i]))
			return i;

	return -ENOSPC;
}

static void lan969x_fdma_tx_clear_buf(struct sparx5 *sparx5, int weight)
{
	struct fdma *fdma = &sparx5->tx.fdma;
	struct sparx5_tx_buf *db;
	unsigned long flags;
	int i;

	spin_lock_irqsave(&sparx5->tx_lock, flags);

	for (i = 0; i < fdma->n_dcbs; ++i) {
		db = &sparx5->tx.dbs[i];

		if (!db->used)
			continue;

		if (!fdma_db_is_done(fdma_db_get(fdma, i, 0)))
			continue;

		db->dev->stats.tx_bytes += db->skb->len;
		db->dev->stats.tx_packets++;
		sparx5->tx.packets++;

		dma_unmap_single(sparx5->dev,
				 db->dma_addr,
				 db->skb->len,
				 DMA_TO_DEVICE);

		if (!db->ptp)
			napi_consume_skb(db->skb, weight);

		db->used = false;
	}

	spin_unlock_irqrestore(&sparx5->tx_lock, flags);
}

static void lan969x_fdma_free_pages(struct sparx5_rx *rx)
{
	struct fdma *fdma = &rx->fdma;

	for (int i = 0; i < fdma->n_dcbs; ++i) {
		for (int j = 0; j < fdma->n_dbs; ++j)
			page_pool_put_full_page(rx->page_pool,
						rx->page[i][j], false);
	}
}

static struct sk_buff *lan969x_fdma_rx_get_frame(struct sparx5 *sparx5,
						 struct sparx5_rx *rx)
{
	const struct sparx5_consts *consts = sparx5->data->consts;
	struct fdma *fdma = &rx->fdma;
	struct sparx5_port *port;
	struct frame_info fi;
	struct sk_buff *skb;
	struct fdma_db *db;
	struct page *page;

	db = &fdma->dcbs[fdma->dcb_index].db[fdma->db_index];
	page = rx->page[fdma->dcb_index][fdma->db_index];

	sparx5_ifh_parse(sparx5, page_address(page), &fi);
	port = fi.src_port < consts->n_ports ? sparx5->ports[fi.src_port] :
					       NULL;
	if (WARN_ON(!port))
		goto free_page;

	skb = build_skb(page_address(page), fdma->db_size);
	if (unlikely(!skb))
		goto free_page;

	skb_mark_for_recycle(skb);
	skb_put(skb, fdma_db_len_get(db));
	skb_pull(skb, IFH_LEN * sizeof(u32));

	skb->dev = port->ndev;

	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);

	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++;

	return skb;

free_page:
	page_pool_recycle_direct(rx->page_pool, page);

	return NULL;
}

static int lan969x_fdma_rx_alloc(struct sparx5 *sparx5)
{
	struct sparx5_rx *rx = &sparx5->rx;
	struct fdma *fdma = &rx->fdma;
	int err;

	struct page_pool_params pp_params = {
		.order = 0,
		.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
		.pool_size = fdma->n_dcbs * fdma->n_dbs,
		.nid = NUMA_NO_NODE,
		.dev = sparx5->dev,
		.dma_dir = DMA_FROM_DEVICE,
		.offset = 0,
		.max_len = fdma->db_size -
			   SKB_DATA_ALIGN(sizeof(struct skb_shared_info)),
	};

	rx->page_pool = page_pool_create(&pp_params);
	if (IS_ERR(rx->page_pool))
		return PTR_ERR(rx->page_pool);

	err = fdma_alloc_coherent(sparx5->dev, fdma);
	if (err)
		return err;

	fdma_dcbs_init(fdma,
		       FDMA_DCB_INFO_DATAL(fdma->db_size),
		       FDMA_DCB_STATUS_INTR);

	return 0;
}

static int lan969x_fdma_tx_alloc(struct sparx5 *sparx5)
{
	struct sparx5_tx *tx = &sparx5->tx;
	struct fdma *fdma = &tx->fdma;
	int err;

	tx->dbs = kcalloc(fdma->n_dcbs,
			  sizeof(struct sparx5_tx_buf),
			  GFP_KERNEL);
	if (!tx->dbs)
		return -ENOMEM;

	err = fdma_alloc_coherent(sparx5->dev, fdma);
	if (err) {
		kfree(tx->dbs);
		return err;
	}

	fdma_dcbs_init(fdma,
		       FDMA_DCB_INFO_DATAL(fdma->db_size),
		       FDMA_DCB_STATUS_DONE);

	return 0;
}

static void lan969x_fdma_rx_init(struct sparx5 *sparx5)
{
	struct fdma *fdma = &sparx5->rx.fdma;

	fdma->channel_id = FDMA_XTR_CHANNEL;
	fdma->n_dcbs = FDMA_DCB_MAX;
	fdma->n_dbs = 1;
	fdma->priv = sparx5;
	fdma->size = fdma_get_size(fdma);
	fdma->db_size = PAGE_SIZE;
	fdma->ops.dataptr_cb = &lan969x_fdma_rx_dataptr_cb;
	fdma->ops.nextptr_cb = &fdma_nextptr_cb;

	/* Fetch a netdev for SKB and NAPI use, any will do */
	for (int idx = 0; idx < sparx5->data->consts->n_ports; ++idx) {
		struct sparx5_port *port = sparx5->ports[idx];

		if (port && port->ndev) {
			sparx5->rx.ndev = port->ndev;
			break;
		}
	}
}

static void lan969x_fdma_tx_init(struct sparx5 *sparx5)
{
	struct fdma *fdma = &sparx5->tx.fdma;

	fdma->channel_id = FDMA_INJ_CHANNEL;
	fdma->n_dcbs = FDMA_DCB_MAX;
	fdma->n_dbs = 1;
	fdma->priv = sparx5;
	fdma->size = fdma_get_size(fdma);
	fdma->db_size = PAGE_SIZE;
	fdma->ops.dataptr_cb = &lan969x_fdma_tx_dataptr_cb;
	fdma->ops.nextptr_cb = &fdma_nextptr_cb;
}

int lan969x_fdma_napi_poll(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);
	int old_dcb, dcb_reload, counter = 0;
	struct fdma *fdma = &rx->fdma;
	struct sk_buff *skb;

	dcb_reload = fdma->dcb_index;

	lan969x_fdma_tx_clear_buf(sparx5, weight);

	/* Process RX data */
	while (counter < weight) {
		if (!fdma_has_frames(fdma))
			break;

		skb = lan969x_fdma_rx_get_frame(sparx5, rx);
		if (!skb)
			break;

		napi_gro_receive(&rx->napi, skb);

		fdma_db_advance(fdma);
		counter++;
		/* Check if the DCB can be reused */
		if (fdma_dcb_is_reusable(fdma))
			continue;

		fdma_db_reset(fdma);
		fdma_dcb_advance(fdma);
	}

	/* Allocate new pages and map them */
	while (dcb_reload != fdma->dcb_index) {
		old_dcb = dcb_reload;
		dcb_reload++;
		 /* n_dcbs must be a power of 2 */
		dcb_reload &= fdma->n_dcbs - 1;

		fdma_dcb_add(fdma,
			     old_dcb,
			     FDMA_DCB_INFO_DATAL(fdma->db_size),
			     FDMA_DCB_STATUS_INTR);

		sparx5_fdma_reload(sparx5, fdma);
	}

	if (counter < weight && napi_complete_done(napi, counter))
		spx5_wr(0xff, sparx5, FDMA_INTR_DB_ENA);

	return counter;
}

int lan969x_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,
		      struct net_device *dev)
{
	int next_dcb, needed_headroom, needed_tailroom, err;
	struct sparx5_tx *tx = &sparx5->tx;
	struct fdma *fdma = &tx->fdma;
	struct sparx5_tx_buf *db_buf;
	u64 status;

	next_dcb = lan969x_fdma_get_next_dcb(tx);
	if (next_dcb < 0)
		return -EBUSY;

	needed_headroom = max_t(int, IFH_LEN * 4 - skb_headroom(skb), 0);
	needed_tailroom = max_t(int, ETH_FCS_LEN - skb_tailroom(skb), 0);
	if (needed_headroom || needed_tailroom || skb_header_cloned(skb)) {
		err = pskb_expand_head(skb, needed_headroom, needed_tailroom,
				       GFP_ATOMIC);
		if (unlikely(err))
			return err;
	}

	skb_push(skb, IFH_LEN * 4);
	memcpy(skb->data, ifh, IFH_LEN * 4);
	skb_put(skb, ETH_FCS_LEN);

	db_buf = &tx->dbs[next_dcb];
	db_buf->dma_addr = dma_map_single(sparx5->dev,
					  skb->data,
					  skb->len,
					  DMA_TO_DEVICE);
	if (dma_mapping_error(sparx5->dev, db_buf->dma_addr))
		return -ENOMEM;

	db_buf->dev = dev;
	db_buf->skb = skb;
	db_buf->ptp = false;
	db_buf->used = true;

	if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP &&
	    SPARX5_SKB_CB(skb)->rew_op == IFH_REW_OP_TWO_STEP_PTP)
		db_buf->ptp = true;

	status = FDMA_DCB_STATUS_SOF |
		 FDMA_DCB_STATUS_EOF |
		 FDMA_DCB_STATUS_BLOCKO(0) |
		 FDMA_DCB_STATUS_BLOCKL(skb->len) |
		 FDMA_DCB_STATUS_INTR;

	fdma_dcb_advance(fdma);
	fdma_dcb_add(fdma, next_dcb, 0, status);

	sparx5_fdma_reload(sparx5, fdma);

	return NETDEV_TX_OK;
}

int lan969x_fdma_init(struct sparx5 *sparx5)
{
	struct sparx5_rx *rx = &sparx5->rx;
	int err;

	lan969x_fdma_rx_init(sparx5);
	lan969x_fdma_tx_init(sparx5);
	sparx5_fdma_injection_mode(sparx5);

	err = dma_set_mask_and_coherent(sparx5->dev, DMA_BIT_MASK(64));
	if (err) {
		dev_err(sparx5->dev, "Failed to set 64-bit FDMA mask");
		return err;
	}

	err = lan969x_fdma_rx_alloc(sparx5);
	if (err) {
		dev_err(sparx5->dev, "Failed to allocate RX buffers: %d\n",
			err);
		return err;
	}

	err = lan969x_fdma_tx_alloc(sparx5);
	if (err) {
		fdma_free_coherent(sparx5->dev, &rx->fdma);
		dev_err(sparx5->dev, "Failed to allocate TX buffers: %d\n",
			err);
		return err;
	}

	/* Reset FDMA state */
	spx5_wr(FDMA_CTRL_NRESET_SET(0), sparx5, FDMA_CTRL);
	spx5_wr(FDMA_CTRL_NRESET_SET(1), sparx5, FDMA_CTRL);

	return err;
}

int lan969x_fdma_deinit(struct sparx5 *sparx5)
{
	struct sparx5_rx *rx = &sparx5->rx;
	struct sparx5_tx *tx = &sparx5->tx;

	sparx5_fdma_stop(sparx5);
	fdma_free_coherent(sparx5->dev, &tx->fdma);
	fdma_free_coherent(sparx5->dev, &rx->fdma);
	lan969x_fdma_free_pages(rx);
	page_pool_destroy(rx->page_pool);

	return 0;
}
Loading