[PATCH net-next 2/9] net: sparx5/lan969x: free the FDMA buffers on init errors and deinit

From: Daniel Machon

Date: Thu Nov 05 2026 - 08:59:56 EST


The FDMA RX buffers are skbs on Sparx5, and pages from a page_pool on
lan969x, which are handed to the FDMA through the RX DCBs. On lan969x,
the TX DCBs also have an array holding the state of the TX buffers.

These are not all freed. On Sparx5, the RX skbs are never freed, and
if the TX buffers cannot be allocated, the RX DCBs are not freed. On
lan969x, if the RX DCBs cannot be allocated, the page_pool is not
destroyed, if the TX buffers cannot be allocated, only the RX DCBs are
freed, and lan969x_fdma_deinit() never frees the TX buffer state. On
both, the return value of fdma_dcbs_init() is also ignored for RX, so
if a buffer cannot be allocated while filling the RX ring, the FDMA is
started with a partly filled ring, and on lan969x,
lan969x_fdma_free_pages() later puts the pages that were never
allocated.

In preparation for MTU support, add sparx5_fdma_rx_free(), which frees
the RX skbs and DCBs, lan969x_fdma_rx_free(), which returns the pages to
the page_pool and frees the page_pool and the RX DCBs, and
lan969x_fdma_tx_free(), which frees the TX buffer state and the TX
DCBs, and use them on the error paths and on deinit. Check the return
value of fdma_dcbs_init() for RX. To know which RX buffers are still
owned by the driver, clear the RX buffer slot once the buffer has been
passed to the stack on Sparx5, and make lan969x_fdma_free_pages() skip
and clear the empty slots of the ring.

Signed-off-by: Daniel Machon <daniel.machon@xxxxxxxxxxxxx>
---
.../microchip/sparx5/lan969x/lan969x_fdma.c | 54 ++++++++++++++++------
.../net/ethernet/microchip/sparx5/sparx5_fdma.c | 30 ++++++++++--
2 files changed, 65 insertions(+), 19 deletions(-)

diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c
index 053d125eda658..d162bea02cbfb 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c
@@ -90,12 +90,29 @@ 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)
+ for (int j = 0; j < fdma->n_dbs; ++j) {
+ if (!rx->page[i][j])
+ continue;
+
page_pool_put_full_page(rx->page_pool,
rx->page[i][j], false);
+ rx->page[i][j] = NULL;
+ }
}
}

+/* Return the pages in the RX ring to the page_pool, and free the page_pool
+ * and the RX DCBs.
+ */
+static void lan969x_fdma_rx_free(struct sparx5 *sparx5)
+{
+ struct sparx5_rx *rx = &sparx5->rx;
+
+ lan969x_fdma_free_pages(rx);
+ fdma_free_coherent(sparx5->dev, &rx->fdma);
+ page_pool_destroy(rx->page_pool);
+}
+
static struct sk_buff *lan969x_fdma_rx_get_frame(struct sparx5 *sparx5,
struct sparx5_rx *rx)
{
@@ -169,14 +186,18 @@ static int lan969x_fdma_rx_alloc(struct sparx5 *sparx5)
return PTR_ERR(rx->page_pool);

err = fdma_alloc_coherent(sparx5->dev, fdma);
- if (err)
+ if (err) {
+ page_pool_destroy(rx->page_pool);
return err;
+ }

- fdma_dcbs_init(fdma,
- FDMA_DCB_INFO_DATAL(fdma->db_size),
- FDMA_DCB_STATUS_INTR);
+ err = fdma_dcbs_init(fdma,
+ FDMA_DCB_INFO_DATAL(fdma->db_size),
+ FDMA_DCB_STATUS_INTR);
+ if (err)
+ lan969x_fdma_rx_free(sparx5);

- return 0;
+ return err;
}

static int lan969x_fdma_tx_alloc(struct sparx5 *sparx5)
@@ -202,6 +223,15 @@ static int lan969x_fdma_tx_alloc(struct sparx5 *sparx5)
return 0;
}

+/* Free the TX buffer state and the TX DCBs */
+static void lan969x_fdma_tx_free(struct sparx5 *sparx5)
+{
+ struct sparx5_tx *tx = &sparx5->tx;
+
+ fdma_free_coherent(sparx5->dev, &tx->fdma);
+ kfree(tx->dbs);
+}
+
static void lan969x_fdma_rx_init(struct sparx5 *sparx5)
{
struct fdma *fdma = &sparx5->rx.fdma;
@@ -353,7 +383,6 @@ int lan969x_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,

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

lan969x_fdma_rx_init(sparx5);
@@ -375,7 +404,7 @@ int lan969x_fdma_init(struct sparx5 *sparx5)

err = lan969x_fdma_tx_alloc(sparx5);
if (err) {
- fdma_free_coherent(sparx5->dev, &rx->fdma);
+ lan969x_fdma_rx_free(sparx5);
dev_err(sparx5->dev, "Failed to allocate TX buffers: %d\n",
err);
return err;
@@ -390,14 +419,9 @@ int lan969x_fdma_init(struct sparx5 *sparx5)

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);
+ lan969x_fdma_tx_free(sparx5);
+ lan969x_fdma_rx_free(sparx5);

return 0;
}
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_fdma.c b/drivers/net/ethernet/microchip/sparx5/sparx5_fdma.c
index bcae0bbd5e44e..f4e8ac00dbe2f 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_fdma.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_fdma.c
@@ -174,6 +174,10 @@ static bool sparx5_fdma_rx_get_frame(struct sparx5 *sparx5, struct sparx5_rx *rx
skb->dev->stats.rx_bytes += skb->len;
skb->dev->stats.rx_packets++;
rx->packets++;
+ /* The skb is passed to the stack, so clear its slot in the ring, so
+ * that sparx5_fdma_rx_free() does not free it.
+ */
+ rx->skb[fdma->dcb_index][fdma->db_index] = NULL;
netif_receive_skb(skb);
return true;
}
@@ -239,6 +243,21 @@ int sparx5_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,
return NETDEV_TX_OK;
}

+/* Free the RX skbs owned by the driver, and the RX DCBs */
+static void sparx5_fdma_rx_free(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++) {
+ consume_skb(rx->skb[i][j]);
+ rx->skb[i][j] = NULL;
+ }
+ }
+
+ fdma_free_phys(fdma);
+}
+
static int sparx5_fdma_rx_alloc(struct sparx5 *sparx5)
{
struct sparx5_rx *rx = &sparx5->rx;
@@ -249,10 +268,12 @@ static int sparx5_fdma_rx_alloc(struct sparx5 *sparx5)
if (err)
return err;

- fdma_dcbs_init(fdma, FDMA_DCB_INFO_DATAL(fdma->db_size),
- FDMA_DCB_STATUS_INTR);
+ err = fdma_dcbs_init(fdma, FDMA_DCB_INFO_DATAL(fdma->db_size),
+ FDMA_DCB_STATUS_INTR);
+ if (err)
+ sparx5_fdma_rx_free(rx);

- return 0;
+ return err;
}

static int sparx5_fdma_tx_alloc(struct sparx5 *sparx5)
@@ -425,6 +446,7 @@ int sparx5_fdma_init(struct sparx5 *sparx5)
}
err = sparx5_fdma_tx_alloc(sparx5);
if (err) {
+ sparx5_fdma_rx_free(&sparx5->rx);
dev_err(sparx5->dev, "Could not allocate TX buffers: %d\n", err);
return err;
}
@@ -434,7 +456,7 @@ int sparx5_fdma_init(struct sparx5 *sparx5)
int sparx5_fdma_deinit(struct sparx5 *sparx5)
{
sparx5_fdma_stop(sparx5);
- fdma_free_phys(&sparx5->rx.fdma);
+ sparx5_fdma_rx_free(&sparx5->rx);
fdma_free_phys(&sparx5->tx.fdma);

return 0;

--
2.34.1