summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRosen Penev <rosenp@gmail.com>2026-10-28 09:48:39 -0700
committerJakub Kicinski <kuba@kernel.org>2026-10-29 09:01:39 -0700
commit4f45e97b3aac2282f2b436d97338c9c2c484d093 (patch)
treee6bf315a647c2f4b7f22f41bce15508093c9e814
parentde788d8a7032475f900c16e9cc0c1417f4f719eb (diff)
downloadlinux-4f45e97b3aac2282f2b436d97338c9c2c484d093.tar.gz
linux-4f45e97b3aac2282f2b436d97338c9c2c484d093.zip
net: bgmac: fix TX DMA unmapping and slot cleanup
When mapping a fragment fails, bgmac_dma_tx_add() unwinds with the wrong slots. It passes the failed fragment's slot->dma_addr, which holds the mapping error, to dma_unmap_single() for the head. The fragment loop indexes from ring->end instead of ring->end + 1, so it unmaps the head a second time as a page and leaves the last mapped fragment mapped. None of the affected slots get their dma_addr cleared either, which includes the head slot when mapping the head fails. bgmac_dma_tx_ring_free() unmaps every slot with a non-zero dma_addr, so tearing down the ring unmaps these addresses again. It also frees the skbs of pending TX slots but leaves slot->skb and slot->dma_addr set. The slots survive a down/up cycle, and bgmac_dma_tx_add() only sets slot->skb on the last slot of a packet. If a slot with a stale pointer is later used for the head or a fragment of a scatter/gather packet, bgmac_dma_tx_free() frees the old skb a second time when that slot completes. bgmac_dma_tx_ring_free() also picks dma_unmap_single() or dma_unmap_page() by looking at slot->skb, which is set on the last slot of a packet, while the head was mapped with dma_map_single() and the fragments with skb_frag_dma_map(). In the error path, unmap the head and each mapped fragment from their own slots, and clear dma_addr on every slot the failed packet touched. In bgmac_dma_tx_ring_free(), choose the unmap function by the SOF flag of the descriptor like bgmac_dma_tx_free() does, unmap every slot before freeing any skb, since a packet can wrap around the end of the ring with its skb on the last slot, and clear both fields. This was found through code inspection by an LLM while hitting other bugs on an Asus RT-N16. It did not hit these ones specifically. Fixes: 9cde94506eac ("bgmac: implement scatter/gather support") Signed-off-by: Rosen Penev <rosenp@gmail.com> Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com> Link: https://patch.msgid.link/20261028164839.28605-1-rosenp@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--drivers/net/ethernet/broadcom/bgmac.c47
1 files changed, 30 insertions, 17 deletions
diff --git a/drivers/net/ethernet/broadcom/bgmac.c b/drivers/net/ethernet/broadcom/bgmac.c
index b3a92ae95de3..669eb7ce7d40 100644
--- a/drivers/net/ethernet/broadcom/bgmac.c
+++ b/drivers/net/ethernet/broadcom/bgmac.c
@@ -208,19 +208,24 @@ static netdev_tx_t bgmac_dma_tx_add(struct bgmac *bgmac,
return NETDEV_TX_OK;
err_dma:
- dma_unmap_single(dma_dev, slot->dma_addr, skb_headlen(skb),
- DMA_TO_DEVICE);
+ slot->dma_addr = 0;
while (i-- > 0) {
- int index = (ring->end + i) % BGMAC_TX_RING_SLOTS;
- struct bgmac_slot_info *slot = &ring->slots[index];
- u32 ctl1 = le32_to_cpu(ring->cpu_base[index].ctl1);
- int len = ctl1 & BGMAC_DESC_CTL1_LEN;
-
- dma_unmap_page(dma_dev, slot->dma_addr, len, DMA_TO_DEVICE);
+ index = (ring->end + 1 + i) % BGMAC_TX_RING_SLOTS;
+ slot = &ring->slots[index];
+ dma_unmap_page(dma_dev, slot->dma_addr,
+ skb_frag_size(&skb_shinfo(skb)->frags[i]),
+ DMA_TO_DEVICE);
+ slot->dma_addr = 0;
}
+ slot = &ring->slots[ring->end % BGMAC_TX_RING_SLOTS];
+ dma_unmap_single(dma_dev, slot->dma_addr, skb_headlen(skb),
+ DMA_TO_DEVICE);
+
err_dma_head:
+ slot->dma_addr = 0;
+
netdev_err(bgmac->net_dev, "Mapping error of skb on ring 0x%X\n",
ring->mmio_base);
@@ -537,17 +542,25 @@ static void bgmac_dma_tx_ring_free(struct bgmac *bgmac,
unsigned int len = ctl1 & BGMAC_DESC_CTL1_LEN;
slot = &ring->slots[i];
- dev_kfree_skb(slot->skb);
- if (!slot->dma_addr)
- continue;
+ if (slot->dma_addr) {
+ if (le32_to_cpu(dma_desc[i].ctl0) & BGMAC_DESC_CTL0_SOF)
+ dma_unmap_single(dma_dev, slot->dma_addr,
+ len, DMA_TO_DEVICE);
+ else
+ dma_unmap_page(dma_dev, slot->dma_addr,
+ len, DMA_TO_DEVICE);
+ slot->dma_addr = 0;
+ }
+ }
- if (slot->skb)
- dma_unmap_single(dma_dev, slot->dma_addr,
- len, DMA_TO_DEVICE);
- else
- dma_unmap_page(dma_dev, slot->dma_addr,
- len, DMA_TO_DEVICE);
+ /* A packet can wrap around the end of the ring with its skb on the
+ * last slot, so free the skbs only once every slot is unmapped.
+ */
+ for (i = 0; i < BGMAC_TX_RING_SLOTS; i++) {
+ slot = &ring->slots[i];
+ dev_kfree_skb(slot->skb);
+ slot->skb = NULL;
}
}