* [PATCH net 1/7] net: bcm4908_enet: fix the TX ring wrap
2026-11-09 23:56 [PATCH net 0/7] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski (mlody1212)
@ 2026-11-09 23:56 ` Kordian Chmielewski (mlody1212)
2026-11-09 23:56 ` [PATCH net 2/7] net: bcm4908_enet: drop a TX frame whose DMA mapping failed Kordian Chmielewski (mlody1212)
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kordian Chmielewski (mlody1212) @ 2026-11-09 23:56 UTC (permalink / raw)
To: netdev
Cc: Rafał Miłecki, bcm-kernel-feedback-list, Andrew Lunn,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
linux-kernel
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@gmail.com>
---
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
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH net 2/7] net: bcm4908_enet: drop a TX frame whose DMA mapping failed
2026-11-09 23:56 [PATCH net 0/7] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski (mlody1212)
2026-11-09 23:56 ` [PATCH net 1/7] net: bcm4908_enet: fix the TX ring wrap Kordian Chmielewski (mlody1212)
@ 2026-11-09 23:56 ` Kordian Chmielewski (mlody1212)
2026-11-09 23:56 ` [PATCH net 3/7] net: bcm4908_enet: release the TX ring on stop Kordian Chmielewski (mlody1212)
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kordian Chmielewski (mlody1212) @ 2026-11-09 23:56 UTC (permalink / raw)
To: netdev
Cc: Rafał Miłecki, bcm-kernel-feedback-list, Andrew Lunn,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
linux-kernel
When dma_map_single() fails, start_xmit() returns NETDEV_TX_BUSY after
it has already stored the skb in the slot. The stack requeues the same
frame and retries it at once, and the skb is left in a slot that never
gets a descriptor. A failed mapping is not something a retry fixes:
free the skb, count it as dropped and return NETDEV_TX_OK. Map the
buffer before the slot is touched.
Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
Signed-off-by: Kordian Chmielewski (mlody1212) <kudzo51@gmail.com>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index 01a3e1d01..727ad1e96 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -535,6 +535,7 @@ 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;
+ dma_addr_t dma_addr;
u32 tmp;
/* Free transmitted skbs */
@@ -554,12 +555,17 @@ static netdev_tx_t bcm4908_enet_start_xmit(struct sk_buff *skb, struct net_devic
return NETDEV_TX_BUSY;
}
+ dma_addr = dma_map_single(dev, skb->data, skb->len, DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(dev, dma_addr))) {
+ dev_kfree_skb_any(skb);
+ netdev->stats.tx_dropped++;
+ return NETDEV_TX_OK;
+ }
+
slot = &ring->slots[ring->write_idx];
slot->skb = skb;
slot->len = skb->len;
- slot->dma_addr = dma_map_single(dev, skb->data, skb->len, DMA_TO_DEVICE);
- if (unlikely(dma_mapping_error(dev, slot->dma_addr)))
- return NETDEV_TX_BUSY;
+ slot->dma_addr = dma_addr;
tmp = skb->len << DMA_CTL_LEN_DESC_BUFLENGTH_SHIFT;
tmp |= DMA_CTL_STATUS_OWN;
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH net 3/7] net: bcm4908_enet: release the TX ring on stop
2026-11-09 23:56 [PATCH net 0/7] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski (mlody1212)
2026-11-09 23:56 ` [PATCH net 1/7] net: bcm4908_enet: fix the TX ring wrap Kordian Chmielewski (mlody1212)
2026-11-09 23:56 ` [PATCH net 2/7] net: bcm4908_enet: drop a TX frame whose DMA mapping failed Kordian Chmielewski (mlody1212)
@ 2026-11-09 23:56 ` Kordian Chmielewski (mlody1212)
2026-11-09 23:56 ` [PATCH net 4/7] net: bcm4908_enet: unmap the buffer of a dropped RX frame Kordian Chmielewski (mlody1212)
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kordian Chmielewski (mlody1212) @ 2026-11-09 23:56 UTC (permalink / raw)
To: netdev
Cc: Rafał Miłecki, bcm-kernel-feedback-list, Andrew Lunn,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
linux-kernel
bcm4908_enet_stop() disables the TX DMA but leaves the ring as it is:
the skbs of frames not completed yet stay mapped and are never freed,
and their descriptors keep DMA_CTL_STATUS_OWN. The next open starts
the indexes from 0 again over those descriptors, so the DMA can send
from a buffer that has been freed, and start_xmit() finds OWN set on a
slot it is about to use.
Unmap and free the skbs left in the TX ring on stop, clear slot->skb
once poll_tx() has freed it, and clear the TX descriptors when the ring
is initialised.
Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
Signed-off-by: Kordian Chmielewski (mlody1212) <kudzo51@gmail.com>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 23 ++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index 727ad1e96..8008b44a4 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -312,6 +312,10 @@ static void bcm4908_enet_dma_ring_init(struct bcm4908_enet *enet,
enet_write(enet, ring->st_ram_block + ENET_DMA_CH_STATE_RAM_BASE_DESC_PTR,
(uint32_t)ring->dma_addr);
+ /* A descriptor the stopped DMA never completed still carries OWN */
+ if (ring->is_tx)
+ memset(ring->cpu_addr, 0, ring->length * sizeof(*ring->buf_desc));
+
ring->read_idx = 0;
ring->write_idx = 0;
}
@@ -495,6 +499,23 @@ static int bcm4908_enet_open(struct net_device *netdev)
return 0;
}
+static void bcm4908_enet_tx_ring_clean(struct bcm4908_enet *enet)
+{
+ struct bcm4908_enet_dma_ring *tx_ring = &enet->tx_ring;
+ struct device *dev = enet->dev;
+ int i;
+
+ for (i = 0; i < tx_ring->length; i++) {
+ struct bcm4908_enet_dma_ring_slot *slot = &tx_ring->slots[i];
+
+ if (!slot->skb)
+ continue;
+ dma_unmap_single(dev, slot->dma_addr, slot->len, DMA_TO_DEVICE);
+ dev_kfree_skb_any(slot->skb);
+ slot->skb = NULL;
+ }
+}
+
static int bcm4908_enet_stop(struct net_device *netdev)
{
struct bcm4908_enet *enet = netdev_priv(netdev);
@@ -510,6 +531,7 @@ static int bcm4908_enet_stop(struct net_device *netdev)
bcm4908_enet_dma_rx_ring_disable(enet, &enet->rx_ring);
bcm4908_enet_dma_tx_ring_disable(enet, &enet->tx_ring);
+ bcm4908_enet_tx_ring_clean(enet);
bcm4908_enet_dma_uninit(enet);
free_irq(enet->irq_tx, enet);
@@ -686,6 +708,7 @@ static int bcm4908_enet_poll_tx(struct napi_struct *napi, int weight)
dma_unmap_single(dev, slot->dma_addr, slot->len, DMA_TO_DEVICE);
dev_kfree_skb(slot->skb);
+ slot->skb = NULL;
handled++;
bytes += slot->len;
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH net 4/7] net: bcm4908_enet: unmap the buffer of a dropped RX frame
2026-11-09 23:56 [PATCH net 0/7] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski (mlody1212)
` (2 preceding siblings ...)
2026-11-09 23:56 ` [PATCH net 3/7] net: bcm4908_enet: release the TX ring on stop Kordian Chmielewski (mlody1212)
@ 2026-11-09 23:56 ` Kordian Chmielewski (mlody1212)
2026-11-09 23:56 ` [PATCH net 5/7] net: bcm4908_enet: unmap RX buffers on stop with their mapped size Kordian Chmielewski (mlody1212)
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kordian Chmielewski (mlody1212) @ 2026-11-09 23:56 UTC (permalink / raw)
To: netdev
Cc: Rafał Miłecki, bcm-kernel-feedback-list, Andrew Lunn,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
linux-kernel
When poll_rx() drops a frame (too short, or not in a single buffer),
it frees the buffer without unmapping it first, so the memory goes
back to the page allocator while still mapped for the device. Unmap it
as the path for good frames does.
Fixes: 4dc7f09b8bec ("net: broadcom: bcm4908_enet: fix RX path possible mem leak")
Signed-off-by: Kordian Chmielewski (mlody1212) <kudzo51@gmail.com>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index 8008b44a4..778920e37 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -653,6 +653,7 @@ static int bcm4908_enet_poll_rx(struct napi_struct *napi, int weight)
if (len < ETH_ZLEN ||
(ctl & (DMA_CTL_STATUS_SOP | DMA_CTL_STATUS_EOP)) != (DMA_CTL_STATUS_SOP | DMA_CTL_STATUS_EOP)) {
+ dma_unmap_single(dev, slot.dma_addr, ENET_RX_BUF_DMA_SIZE, DMA_FROM_DEVICE);
skb_free_frag(slot.buf);
enet->netdev->stats.rx_dropped++;
break;
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH net 5/7] net: bcm4908_enet: unmap RX buffers on stop with their mapped size
2026-11-09 23:56 [PATCH net 0/7] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski (mlody1212)
` (3 preceding siblings ...)
2026-11-09 23:56 ` [PATCH net 4/7] net: bcm4908_enet: unmap the buffer of a dropped RX frame Kordian Chmielewski (mlody1212)
@ 2026-11-09 23:56 ` Kordian Chmielewski (mlody1212)
2026-11-09 23:56 ` [PATCH net 6/7] net: bcm4908_enet: publish the buffer address before OWN Kordian Chmielewski (mlody1212)
2026-11-09 23:57 ` [PATCH net 7/7] net: bcm4908_enet: drop RX frames longer than the buffer Kordian Chmielewski (mlody1212)
6 siblings, 0 replies; 8+ messages in thread
From: Kordian Chmielewski (mlody1212) @ 2026-11-09 23:56 UTC (permalink / raw)
To: netdev
Cc: Rafał Miłecki, bcm-kernel-feedback-list, Andrew Lunn,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
linux-kernel
Since the switch to build_skb() the RX buffers are mapped with
ENET_RX_BUF_DMA_SIZE and slot->len is no longer set for them, but
bcm4908_enet_dma_uninit() still unmaps them with slot->len. Use the
size they were mapped with.
Fixes: 3a1cc23a75ab ("net: broadcom: bcm4908_enet: use build_skb()")
Signed-off-by: Kordian Chmielewski (mlody1212) <kudzo51@gmail.com>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index 778920e37..c0aca6801 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -331,7 +331,7 @@ static void bcm4908_enet_dma_uninit(struct bcm4908_enet *enet)
slot = &rx_ring->slots[i];
if (!slot->buf)
continue;
- dma_unmap_single(dev, slot->dma_addr, slot->len, DMA_FROM_DEVICE);
+ dma_unmap_single(dev, slot->dma_addr, ENET_RX_BUF_DMA_SIZE, DMA_FROM_DEVICE);
skb_free_frag(slot->buf);
slot->buf = NULL;
}
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH net 6/7] net: bcm4908_enet: publish the buffer address before OWN
2026-11-09 23:56 [PATCH net 0/7] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski (mlody1212)
` (4 preceding siblings ...)
2026-11-09 23:56 ` [PATCH net 5/7] net: bcm4908_enet: unmap RX buffers on stop with their mapped size Kordian Chmielewski (mlody1212)
@ 2026-11-09 23:56 ` Kordian Chmielewski (mlody1212)
2026-11-09 23:57 ` [PATCH net 7/7] net: bcm4908_enet: drop RX frames longer than the buffer Kordian Chmielewski (mlody1212)
6 siblings, 0 replies; 8+ messages in thread
From: Kordian Chmielewski (mlody1212) @ 2026-11-09 23:56 UTC (permalink / raw)
To: netdev
Cc: Rafał Miłecki, bcm-kernel-feedback-list, Andrew Lunn,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
linux-kernel
bcm4908_enet_dma_alloc_rx_buf() hands a descriptor to the DMA by
writing its control word, with DMA_CTL_STATUS_OWN, first and the new
buffer address second. The refill runs on the descriptor that has just
been completed, which is where the DMA waits once the ring is full: if
it picks the descriptor up between the two stores, it writes the next
frame into the old buffer, which poll_rx() is turning into an skb.
start_xmit() writes the address first, but nothing orders the two
stores, and the descriptors live in coherent memory, where arm64 may
make them visible in either order.
Write the address first on both paths and order it before the control
word with dma_wmb(). In poll_rx(), read what the DMA wrote only after
seeing OWN clear (dma_rmb()).
Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
Signed-off-by: Kordian Chmielewski (mlody1212) <kudzo51@gmail.com>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index c0aca6801..ea4a9eb98 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -289,8 +289,13 @@ static int bcm4908_enet_dma_alloc_rx_buf(struct bcm4908_enet *enet, unsigned int
tmp |= DMA_CTL_STATUS_OWN;
if (idx == enet->rx_ring.length - 1)
tmp |= DMA_CTL_STATUS_WRAP;
- buf_desc->ctl = cpu_to_le32(tmp);
+ /* The DMA must not see OWN before the new buffer address: with a full
+ * ring it waits on this very descriptor and would fill the old buffer,
+ * which is on its way up the stack as an skb.
+ */
buf_desc->addr = cpu_to_le32(slot->dma_addr);
+ dma_wmb();
+ buf_desc->ctl = cpu_to_le32(tmp);
return 0;
}
@@ -600,6 +605,8 @@ static netdev_tx_t bcm4908_enet_start_xmit(struct sk_buff *skb, struct net_devic
netdev_sent_queue(enet->netdev, skb->len);
buf_desc->addr = cpu_to_le32((uint32_t)slot->dma_addr);
+ /* The DMA must not see OWN before the buffer address */
+ dma_wmb();
buf_desc->ctl = cpu_to_le32(tmp);
bcm4908_enet_dma_tx_ring_enable(enet, &enet->tx_ring);
@@ -638,6 +645,8 @@ static int bcm4908_enet_poll_rx(struct napi_struct *napi, int weight)
ctl = le32_to_cpu(buf_desc->ctl);
if (ctl & DMA_CTL_STATUS_OWN)
break;
+ /* Nothing the DMA wrote may be read before it gave the descriptor back */
+ dma_rmb();
slot = enet->rx_ring.slots[enet->rx_ring.read_idx];
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH net 7/7] net: bcm4908_enet: drop RX frames longer than the buffer
2026-11-09 23:56 [PATCH net 0/7] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski (mlody1212)
` (5 preceding siblings ...)
2026-11-09 23:56 ` [PATCH net 6/7] net: bcm4908_enet: publish the buffer address before OWN Kordian Chmielewski (mlody1212)
@ 2026-11-09 23:57 ` Kordian Chmielewski (mlody1212)
6 siblings, 0 replies; 8+ messages in thread
From: Kordian Chmielewski (mlody1212) @ 2026-11-09 23:57 UTC (permalink / raw)
To: netdev
Cc: Rafał Miłecki, bcm-kernel-feedback-list, Andrew Lunn,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
linux-kernel
poll_rx() takes the frame length from the DMA descriptor and passes it
to skb_put() unchecked, so a length larger than the buffer ends in
skb_over_panic() from the NAPI poll. It was seen on a BCM4908 with the
RX channel's MAX_BURST raised to 32, where the DMA reported a length of
3388 bytes, more than twice the size of the buffer; with the default
burst length it has not been seen. Drop such a frame like any other bad
one and count it as a length error.
Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
Signed-off-by: Kordian Chmielewski (mlody1212) <kudzo51@gmail.com>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index ea4a9eb98..b2debdfa0 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -660,10 +660,13 @@ static int bcm4908_enet_poll_rx(struct napi_struct *napi, int weight)
len = (ctl & DMA_CTL_LEN_DESC_BUFLENGTH) >> DMA_CTL_LEN_DESC_BUFLENGTH_SHIFT;
- if (len < ETH_ZLEN ||
+ /* A length past the buffer would make skb_put() panic the kernel */
+ if (len < ETH_ZLEN || len > ENET_RX_BUF_DMA_SIZE ||
(ctl & (DMA_CTL_STATUS_SOP | DMA_CTL_STATUS_EOP)) != (DMA_CTL_STATUS_SOP | DMA_CTL_STATUS_EOP)) {
dma_unmap_single(dev, slot.dma_addr, ENET_RX_BUF_DMA_SIZE, DMA_FROM_DEVICE);
skb_free_frag(slot.buf);
+ if (len > ENET_RX_BUF_DMA_SIZE)
+ enet->netdev->stats.rx_length_errors++;
enet->netdev->stats.rx_dropped++;
break;
}
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread