From e3c00e95ca170afaa8e95f8a63cd71ca1f65202c Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris Date: Wed, 9 Sep 2026 15:55:15 +0200 Subject: [PATCH] qualcommax: qca_edma: free a completed frame with the budget of its caller edma_clean_tx() passes its loop bound to napi_consume_skb() as the NAPI budget. A non-zero budget tells that helper it is running inside a poll and frees the frame into the per-CPU NAPI cache, which is only safe from softirq context. edma_rings_drain() calls the same routine from process context with INT_MAX - on an MTU change that alters the receive page order, and on the probe error and remove paths - so a transmit completion on the same CPU can interleave with the cache update. The budget the poll was given becomes an argument of its own, zero on the drain, which routes those frees through dev_consume_skb_any() instead. Fixes: bdb0b722b7f0 ("qualcommax: add EDMA driver") Assisted-by: Claude:claude-opus-5 Signed-off-by: Julius Bairaktaris Link: https://github.com/openwrt/openwrt/pull/25084 Signed-off-by: Robert Marko --- .../files/drivers/net/ethernet/qualcomm/qca_edma.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_edma.c b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_edma.c index 21ed1d3b94b..149aaff15a5 100644 --- a/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_edma.c +++ b/target/linux/qualcommax/files/drivers/net/ethernet/qualcomm/qca_edma.c @@ -310,8 +310,12 @@ static bool edma_rx_page_take(struct edma_priv *priv, struct page *page, return false; } +/* @napi_budget is the NAPI budget the poll was given, or zero when the caller + * is not a poll: the skb cache napi_consume_skb() recycles into is per-CPU and + * is only safe to touch from softirq context. + */ static u32 edma_clean_tx(struct edma_priv *priv, struct edma_ring *txcmpl_ring, - int budget) + int budget, int napi_budget) { const struct edma_soc_data *soc = priv->soc; struct platform_device *pdev = priv->pdev; @@ -356,7 +360,7 @@ static u32 edma_clean_tx(struct edma_priv *priv, struct edma_ring *txcmpl_ring, le32_to_cpu(txdesc->buffer_addr), len, DMA_TO_DEVICE); bytes += len - EDMA_TX_PREHDR_SIZE; - napi_consume_skb(skb, budget); + napi_consume_skb(skb, napi_budget); next: if (++cons == txcmpl_ring->count) @@ -477,7 +481,7 @@ next: static int edma_tx_napi(struct napi_struct *napi, int budget) { struct edma_priv *priv = container_of(napi, struct edma_priv, tx_napi); - int work = edma_clean_tx(priv, &priv->txcmpl_ring, budget); + int work = edma_clean_tx(priv, &priv->txcmpl_ring, budget, budget); const struct edma_soc_data *soc = priv->soc; u32 val; @@ -756,7 +760,7 @@ err_txcmpl: static void edma_rings_drain(struct edma_priv *priv) { edma_txdesc_drain(priv, &priv->txdesc_ring); - edma_clean_tx(priv, &priv->txcmpl_ring, INT_MAX); + edma_clean_tx(priv, &priv->txcmpl_ring, INT_MAX, 0); edma_tx_ring_free(priv, &priv->txdesc_ring, sizeof(struct edma_txdesc)); edma_ring_free(priv, &priv->txcmpl_ring, sizeof(struct edma_txcmpl));