[PATCH net 1/7] net: bcm4908_enet: fix the TX ring wrap
From: Kordian Chmielewski (mlody1212)
Date: Mon Nov 09 2026 - 18:59:41 EST
start_xmit() wraps write_idx at length - 1 and sets WRAP on the
descriptor at length - 2, while poll_tx() wraps read_idx at length,
and the free descriptor count is taken from length. With read_idx at 0
and write_idx at length - 2 the count comes out as 2, the frame is
queued and write_idx wraps to 0 == read_idx: a full ring looks empty.
The following frames overwrite slots whose skbs were never unmapped
or freed, their bytes stay in BQL, and the queue stops for good with
nothing left to wake it.
On a TP-Link Archer AX6000 this stopped all wired traffic under
sustained TX load (about 1.4 Gbit/s through the switch's CPU port):
tx_packets froze, nothing was logged, and BQL showed about 185 KB in
flight against a limit of about 10 KB.
Use every descriptor, with WRAP on the last one, wrap both indexes at
length and keep one descriptor free, so that read_idx == write_idx can
only mean an empty ring. Stop the queue as soon as the ring is full
and wake it from poll_tx() once there is room again, with the usual
barrier pairing.
Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
Signed-off-by: Kordian Chmielewski (mlody1212) <kudzo51@xxxxxxxxx>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 52 +++++++++++++-------
1 file changed, 35 insertions(+), 17 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index fd2715f84..01a3e1d01 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -518,6 +518,16 @@ static int bcm4908_enet_stop(struct net_device *netdev)
return 0;
}
+/* One descriptor always stays unused, so read_idx == write_idx can only
+ * mean an empty ring
+ */
+static int bcm4908_enet_tx_free_descs(struct bcm4908_enet_dma_ring *ring)
+{
+ int free = READ_ONCE(ring->read_idx) - READ_ONCE(ring->write_idx) - 1;
+
+ return free < 0 ? free + ring->length : free;
+}
+
static netdev_tx_t bcm4908_enet_start_xmit(struct sk_buff *skb, struct net_device *netdev)
{
struct bcm4908_enet *enet = netdev_priv(netdev);
@@ -525,20 +535,14 @@ static netdev_tx_t bcm4908_enet_start_xmit(struct sk_buff *skb, struct net_devic
struct bcm4908_enet_dma_ring_slot *slot;
struct device *dev = enet->dev;
struct bcm4908_enet_dma_ring_bd *buf_desc;
- int free_buf_descs;
u32 tmp;
/* Free transmitted skbs */
if (enet->irq_tx < 0 &&
- !(le32_to_cpu(ring->buf_desc[ring->read_idx].ctl) & DMA_CTL_STATUS_OWN))
+ !(le32_to_cpu(ring->buf_desc[READ_ONCE(ring->read_idx)].ctl) & DMA_CTL_STATUS_OWN))
napi_schedule(&enet->tx_ring.napi);
- /* Don't use the last empty buf descriptor */
- if (ring->read_idx <= ring->write_idx)
- free_buf_descs = ring->read_idx - ring->write_idx + ring->length;
- else
- free_buf_descs = ring->read_idx - ring->write_idx;
- if (free_buf_descs < 2) {
+ if (unlikely(bcm4908_enet_tx_free_descs(ring) < 1)) {
netif_stop_queue(netdev);
return NETDEV_TX_BUSY;
}
@@ -562,7 +566,7 @@ static netdev_tx_t bcm4908_enet_start_xmit(struct sk_buff *skb, struct net_devic
tmp |= DMA_CTL_STATUS_SOP;
tmp |= DMA_CTL_STATUS_EOP;
tmp |= DMA_CTL_STATUS_APPEND_CRC;
- if (ring->write_idx + 1 == ring->length - 1)
+ if (ring->write_idx == ring->length - 1)
tmp |= DMA_CTL_STATUS_WRAP;
netdev_sent_queue(enet->netdev, skb->len);
@@ -572,8 +576,17 @@ static netdev_tx_t bcm4908_enet_start_xmit(struct sk_buff *skb, struct net_devic
bcm4908_enet_dma_tx_ring_enable(enet, &enet->tx_ring);
- if (++ring->write_idx == ring->length - 1)
- ring->write_idx = 0;
+ WRITE_ONCE(ring->write_idx,
+ ring->write_idx == ring->length - 1 ? 0 : ring->write_idx + 1);
+
+ /* Stop while the ring is full; poll_tx() wakes the queue once there is room */
+ if (unlikely(bcm4908_enet_tx_free_descs(ring) < 1)) {
+ netif_stop_queue(netdev);
+ /* Pairs with smp_mb() in poll_tx(), which may have freed descriptors meanwhile */
+ smp_mb();
+ if (bcm4908_enet_tx_free_descs(ring) >= 1)
+ netif_start_queue(netdev);
+ }
return NETDEV_TX_OK;
}
@@ -655,14 +668,15 @@ static int bcm4908_enet_poll_tx(struct napi_struct *napi, int weight)
struct bcm4908_enet_dma_ring_bd *buf_desc;
struct bcm4908_enet_dma_ring_slot *slot;
struct device *dev = enet->dev;
+ int read_idx = tx_ring->read_idx;
unsigned int bytes = 0;
int handled = 0;
- while (handled < weight && tx_ring->read_idx != tx_ring->write_idx) {
- buf_desc = &tx_ring->buf_desc[tx_ring->read_idx];
+ while (handled < weight && read_idx != READ_ONCE(tx_ring->write_idx)) {
+ buf_desc = &tx_ring->buf_desc[read_idx];
if (le32_to_cpu(buf_desc->ctl) & DMA_CTL_STATUS_OWN)
break;
- slot = &tx_ring->slots[tx_ring->read_idx];
+ slot = &tx_ring->slots[read_idx];
dma_unmap_single(dev, slot->dma_addr, slot->len, DMA_TO_DEVICE);
dev_kfree_skb(slot->skb);
@@ -670,9 +684,10 @@ static int bcm4908_enet_poll_tx(struct napi_struct *napi, int weight)
handled++;
bytes += slot->len;
- if (++tx_ring->read_idx == tx_ring->length)
- tx_ring->read_idx = 0;
+ if (++read_idx == tx_ring->length)
+ read_idx = 0;
}
+ WRITE_ONCE(tx_ring->read_idx, read_idx);
netdev_completed_queue(enet->netdev, handled, bytes);
enet->netdev->stats.tx_packets += handled;
@@ -683,7 +698,10 @@ static int bcm4908_enet_poll_tx(struct napi_struct *napi, int weight)
bcm4908_enet_dma_ring_intrs_on(enet, tx_ring);
}
- if (netif_queue_stopped(enet->netdev))
+ /* Pairs with smp_mb() in start_xmit() after it stopped the queue */
+ smp_mb();
+ if (netif_queue_stopped(enet->netdev) &&
+ bcm4908_enet_tx_free_descs(tx_ring) >= 1)
netif_wake_queue(enet->netdev);
return handled;
--
2.56.0