* [PATCH net v2 1/6] net: bcm4908_enet: fix the TX ring wrap
2026-11-12 19:50 [PATCH net v2 0/6] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski
@ 2026-11-12 19:50 ` Kordian Chmielewski
2026-11-12 22:24 ` Florian Fainelli
2026-11-12 19:50 ` [PATCH net v2 2/6] net: bcm4908_enet: drop a TX frame whose DMA mapping failed Kordian Chmielewski
` (4 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Kordian Chmielewski @ 2026-11-12 19:50 UTC (permalink / raw)
To: netdev
Cc: Florian Fainelli, Florian Fainelli, 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 everything the CPU sent to the
switch under sustained TX load (about 1.3 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.
The same mismatch also lets poll_tx() step onto slot length - 1, which
start_xmit() never fills: once per lap it completes that slot,
unmapping address 0 with length 0 and counting a packet that was never
sent. That one is from reading the code.
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 and wake the queue with the
netdev_queues.h helpers: start_xmit() stops it when the ring is full,
also when it finds the ring full on entry, and poll_tx() wakes it once
there is room again.
Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
Signed-off-by: Kordian Chmielewski <kudzo51@gmail.com>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 57 ++++++++++++--------
1 file changed, 35 insertions(+), 22 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index fd2715f84..60f711e8c 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -13,6 +13,7 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/string.h>
+#include <net/netdev_queues.h>
#include "bcm4908_enet.h"
#include "unimac.h"
@@ -518,30 +519,34 @@ 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 netdev_queue *txq = netdev_get_tx_queue(netdev, 0);
struct bcm4908_enet *enet = netdev_priv(netdev);
struct bcm4908_enet_dma_ring *ring = &enet->tx_ring;
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) {
- netif_stop_queue(netdev);
+ if (!netif_txq_maybe_stop(txq, bcm4908_enet_tx_free_descs(ring), 1, 1))
return NETDEV_TX_BUSY;
- }
/* Hardware removes OWN bit after sending data */
buf_desc = &ring->buf_desc[ring->write_idx];
@@ -562,7 +567,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 +577,13 @@ 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;
+ if (ring->write_idx == ring->length - 1)
+ WRITE_ONCE(ring->write_idx, 0);
+ else
+ WRITE_ONCE(ring->write_idx, ring->write_idx + 1);
+
+ /* poll_tx() wakes the queue once there is room again */
+ netif_txq_maybe_stop(txq, bcm4908_enet_tx_free_descs(ring), 1, 1);
return NETDEV_TX_OK;
}
@@ -652,17 +662,19 @@ static int bcm4908_enet_poll_tx(struct napi_struct *napi, int weight)
{
struct bcm4908_enet_dma_ring *tx_ring = container_of(napi, struct bcm4908_enet_dma_ring, napi);
struct bcm4908_enet *enet = container_of(tx_ring, struct bcm4908_enet, tx_ring);
+ struct netdev_queue *txq = netdev_get_tx_queue(enet->netdev, 0);
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,22 +682,23 @@ 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;
enet->netdev->stats.tx_bytes += bytes;
+ /* Reports to BQL, and wakes the queue if start_xmit() stopped it */
+ netif_txq_completed_wake(txq, handled, bytes,
+ bcm4908_enet_tx_free_descs(tx_ring), 1);
+
if (handled < weight) {
napi_complete_done(napi, handled);
bcm4908_enet_dma_ring_intrs_on(enet, tx_ring);
}
- if (netif_queue_stopped(enet->netdev))
- netif_wake_queue(enet->netdev);
-
return handled;
}
--
2.56.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH net v2 1/6] net: bcm4908_enet: fix the TX ring wrap
2026-11-12 19:50 ` [PATCH net v2 1/6] net: bcm4908_enet: fix the TX ring wrap Kordian Chmielewski
@ 2026-11-12 22:24 ` Florian Fainelli
0 siblings, 0 replies; 13+ messages in thread
From: Florian Fainelli @ 2026-11-12 22:24 UTC (permalink / raw)
To: Kordian Chmielewski, netdev
Cc: Florian Fainelli, Rafał Miłecki,
bcm-kernel-feedback-list, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
On 11/12/26 11:50, Kordian Chmielewski wrote:
> 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 everything the CPU sent to the
> switch under sustained TX load (about 1.3 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.
>
> The same mismatch also lets poll_tx() step onto slot length - 1, which
> start_xmit() never fills: once per lap it completes that slot,
> unmapping address 0 with length 0 and counting a packet that was never
> sent. That one is from reading the code.
>
> 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 and wake the queue with the
> netdev_queues.h helpers: start_xmit() stops it when the ring is full,
> also when it finds the ring full on entry, and poll_tx() wakes it once
> there is room again.
>
> Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
> Signed-off-by: Kordian Chmielewski <kudzo51@gmail.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH net v2 2/6] net: bcm4908_enet: drop a TX frame whose DMA mapping failed
2026-11-12 19:50 [PATCH net v2 0/6] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski
2026-11-12 19:50 ` [PATCH net v2 1/6] net: bcm4908_enet: fix the TX ring wrap Kordian Chmielewski
@ 2026-11-12 19:50 ` Kordian Chmielewski
2026-11-12 22:21 ` Florian Fainelli
2026-11-12 19:50 ` [PATCH net v2 3/6] net: bcm4908_enet: release the TX ring on stop Kordian Chmielewski
` (3 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Kordian Chmielewski @ 2026-11-12 19:50 UTC (permalink / raw)
To: netdev
Cc: Florian Fainelli, Florian Fainelli, 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 with
the queue still running, after it has already stored the skb in the
slot. Documentation/networking/driver.rst treats NETDEV_TX_BUSY under
normal circumstances as a hard error, and a driver that returns it must
not keep any reference to the skb. Here the stack requeues the frame
and retries it while the slot still points at it. Drop the frame
instead: free the skb, count it as dropped and return NETDEV_TX_OK.
Map the buffer before the slot is touched.
Found by reading the code.
Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
Signed-off-by: Kordian Chmielewski <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 60f711e8c..b1738d954 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -537,6 +537,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 */
@@ -555,12 +556,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 related [flat|nested] 13+ messages in thread* Re: [PATCH net v2 2/6] net: bcm4908_enet: drop a TX frame whose DMA mapping failed
2026-11-12 19:50 ` [PATCH net v2 2/6] net: bcm4908_enet: drop a TX frame whose DMA mapping failed Kordian Chmielewski
@ 2026-11-12 22:21 ` Florian Fainelli
0 siblings, 0 replies; 13+ messages in thread
From: Florian Fainelli @ 2026-11-12 22:21 UTC (permalink / raw)
To: Kordian Chmielewski, netdev
Cc: Florian Fainelli, Rafał Miłecki,
bcm-kernel-feedback-list, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
On 11/12/26 11:50, Kordian Chmielewski wrote:
> When dma_map_single() fails, start_xmit() returns NETDEV_TX_BUSY with
> the queue still running, after it has already stored the skb in the
> slot. Documentation/networking/driver.rst treats NETDEV_TX_BUSY under
> normal circumstances as a hard error, and a driver that returns it must
> not keep any reference to the skb. Here the stack requeues the frame
> and retries it while the slot still points at it. Drop the frame
> instead: free the skb, count it as dropped and return NETDEV_TX_OK.
> Map the buffer before the slot is touched.
>
> Found by reading the code.
>
> Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
> Signed-off-by: Kordian Chmielewski <kudzo51@gmail.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH net v2 3/6] net: bcm4908_enet: release the TX ring on stop
2026-11-12 19:50 [PATCH net v2 0/6] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski
2026-11-12 19:50 ` [PATCH net v2 1/6] net: bcm4908_enet: fix the TX ring wrap Kordian Chmielewski
2026-11-12 19:50 ` [PATCH net v2 2/6] net: bcm4908_enet: drop a TX frame whose DMA mapping failed Kordian Chmielewski
@ 2026-11-12 19:50 ` Kordian Chmielewski
2026-11-12 22:22 ` Florian Fainelli
2026-11-12 19:50 ` [PATCH net v2 4/6] net: bcm4908_enet: unmap the buffer of a dropped RX frame Kordian Chmielewski
` (2 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Kordian Chmielewski @ 2026-11-12 19:50 UTC (permalink / raw)
To: netdev
Cc: Florian Fainelli, Florian Fainelli, 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 that poll_tx() has not freed yet stay mapped and are never
freed, and the descriptors the DMA has not sent yet keep
DMA_CTL_STATUS_OWN. The skbs, the socket memory they hold and their
mappings leak. The next open starts the indexes from 0 again over
those descriptors, so the DMA may send stale frames from before the
stop, and if the first descriptor still has OWN, start_xmit() stops
the queue before it enables the DMA, with nothing left to wake it.
Wait for the TX DMA channel to stop, as is already done for RX, then
unmap and free the skbs between read_idx and write_idx, and clear the
TX descriptors when the ring is initialised. A descriptor that
start_xmit() is about to use then never carries OWN, so drop the check
that stopped the queue on one. The RX helper's timeout warning said
TX; make it say RX.
Found by reading the code; I have not seen these on the hardware.
Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
Signed-off-by: Kordian Chmielewski <kudzo51@gmail.com>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 46 ++++++++++++++++----
1 file changed, 38 insertions(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index b1738d954..a28adef03 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -313,6 +313,11 @@ 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->buf_desc, 0,
+ ring->length * sizeof(*ring->buf_desc));
+
ring->read_idx = 0;
ring->write_idx = 0;
}
@@ -365,7 +370,22 @@ static void bcm4908_enet_dma_tx_ring_enable(struct bcm4908_enet *enet,
static void bcm4908_enet_dma_tx_ring_disable(struct bcm4908_enet *enet,
struct bcm4908_enet_dma_ring *ring)
{
+ unsigned long deadline;
+ u32 tmp;
+
enet_write(enet, ring->cfg_block + ENET_DMA_CH_CFG, 0);
+
+ /* Wait for the channel to stop, as for RX, before freeing buffers */
+ deadline = jiffies + usecs_to_jiffies(2000);
+ do {
+ tmp = enet_read(enet, ring->cfg_block + ENET_DMA_CH_CFG);
+ if (!(tmp & ENET_DMA_CH_CFG_ENABLE))
+ return;
+ enet_write(enet, ring->cfg_block + ENET_DMA_CH_CFG, 0);
+ usleep_range(10, 30);
+ } while (!time_after_eq(jiffies, deadline));
+
+ dev_warn(enet->dev, "Timeout waiting for DMA TX stop\n");
}
static void bcm4908_enet_dma_rx_ring_enable(struct bcm4908_enet *enet,
@@ -391,7 +411,7 @@ static void bcm4908_enet_dma_rx_ring_disable(struct bcm4908_enet *enet,
usleep_range(10, 30);
} while (!time_after_eq(jiffies, deadline));
- dev_warn(enet->dev, "Timeout waiting for DMA TX stop\n");
+ dev_warn(enet->dev, "Timeout waiting for DMA RX stop\n");
}
/***
@@ -496,6 +516,21 @@ 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 = tx_ring->read_idx; i != tx_ring->write_idx;
+ i = i == tx_ring->length - 1 ? 0 : i + 1) {
+ struct bcm4908_enet_dma_ring_slot *slot = &tx_ring->slots[i];
+
+ dma_unmap_single(dev, slot->dma_addr, slot->len, DMA_TO_DEVICE);
+ dev_kfree_skb_any(slot->skb);
+ }
+}
+
static int bcm4908_enet_stop(struct net_device *netdev)
{
struct bcm4908_enet *enet = netdev_priv(netdev);
@@ -511,6 +546,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);
@@ -549,13 +585,6 @@ static netdev_tx_t bcm4908_enet_start_xmit(struct sk_buff *skb, struct net_devic
if (!netif_txq_maybe_stop(txq, bcm4908_enet_tx_free_descs(ring), 1, 1))
return NETDEV_TX_BUSY;
- /* Hardware removes OWN bit after sending data */
- buf_desc = &ring->buf_desc[ring->write_idx];
- if (unlikely(le32_to_cpu(buf_desc->ctl) & DMA_CTL_STATUS_OWN)) {
- netif_stop_queue(netdev);
- 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);
@@ -563,6 +592,7 @@ static netdev_tx_t bcm4908_enet_start_xmit(struct sk_buff *skb, struct net_devic
return NETDEV_TX_OK;
}
+ buf_desc = &ring->buf_desc[ring->write_idx];
slot = &ring->slots[ring->write_idx];
slot->skb = skb;
slot->len = skb->len;
--
2.56.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH net v2 3/6] net: bcm4908_enet: release the TX ring on stop
2026-11-12 19:50 ` [PATCH net v2 3/6] net: bcm4908_enet: release the TX ring on stop Kordian Chmielewski
@ 2026-11-12 22:22 ` Florian Fainelli
0 siblings, 0 replies; 13+ messages in thread
From: Florian Fainelli @ 2026-11-12 22:22 UTC (permalink / raw)
To: Kordian Chmielewski, netdev
Cc: Florian Fainelli, Rafał Miłecki,
bcm-kernel-feedback-list, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
On 11/12/26 11:50, Kordian Chmielewski wrote:
> bcm4908_enet_stop() disables the TX DMA but leaves the ring as it is:
> the skbs that poll_tx() has not freed yet stay mapped and are never
> freed, and the descriptors the DMA has not sent yet keep
> DMA_CTL_STATUS_OWN. The skbs, the socket memory they hold and their
> mappings leak. The next open starts the indexes from 0 again over
> those descriptors, so the DMA may send stale frames from before the
> stop, and if the first descriptor still has OWN, start_xmit() stops
> the queue before it enables the DMA, with nothing left to wake it.
>
> Wait for the TX DMA channel to stop, as is already done for RX, then
> unmap and free the skbs between read_idx and write_idx, and clear the
> TX descriptors when the ring is initialised. A descriptor that
> start_xmit() is about to use then never carries OWN, so drop the check
> that stopped the queue on one. The RX helper's timeout warning said
> TX; make it say RX.
>
> Found by reading the code; I have not seen these on the hardware.
>
> Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
> Signed-off-by: Kordian Chmielewski <kudzo51@gmail.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH net v2 4/6] net: bcm4908_enet: unmap the buffer of a dropped RX frame
2026-11-12 19:50 [PATCH net v2 0/6] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski
` (2 preceding siblings ...)
2026-11-12 19:50 ` [PATCH net v2 3/6] net: bcm4908_enet: release the TX ring on stop Kordian Chmielewski
@ 2026-11-12 19:50 ` Kordian Chmielewski
2026-11-12 22:20 ` Florian Fainelli
2026-11-12 19:50 ` [PATCH net v2 5/6] net: bcm4908_enet: unmap RX buffers on stop with their mapped size Kordian Chmielewski
2026-11-12 19:50 ` [PATCH net v2 6/6] net: bcm4908_enet: publish the buffer address before OWN Kordian Chmielewski
5 siblings, 1 reply; 13+ messages in thread
From: Kordian Chmielewski @ 2026-11-12 19:50 UTC (permalink / raw)
To: netdev
Cc: Florian Fainelli, Florian Fainelli, 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.
Found by reading the code.
Fixes: 4dc7f09b8bec ("net: broadcom: bcm4908_enet: fix RX path possible mem leak")
Signed-off-by: Kordian Chmielewski <kudzo51@gmail.com>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index a28adef03..7f80e4e24 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -658,6 +658,8 @@ 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 related [flat|nested] 13+ messages in thread* Re: [PATCH net v2 4/6] net: bcm4908_enet: unmap the buffer of a dropped RX frame
2026-11-12 19:50 ` [PATCH net v2 4/6] net: bcm4908_enet: unmap the buffer of a dropped RX frame Kordian Chmielewski
@ 2026-11-12 22:20 ` Florian Fainelli
0 siblings, 0 replies; 13+ messages in thread
From: Florian Fainelli @ 2026-11-12 22:20 UTC (permalink / raw)
To: Kordian Chmielewski, netdev
Cc: Florian Fainelli, Rafał Miłecki,
bcm-kernel-feedback-list, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
On 11/12/26 11:50, Kordian Chmielewski wrote:
> 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.
>
> Found by reading the code.
>
> Fixes: 4dc7f09b8bec ("net: broadcom: bcm4908_enet: fix RX path possible mem leak")
> Signed-off-by: Kordian Chmielewski <kudzo51@gmail.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH net v2 5/6] net: bcm4908_enet: unmap RX buffers on stop with their mapped size
2026-11-12 19:50 [PATCH net v2 0/6] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski
` (3 preceding siblings ...)
2026-11-12 19:50 ` [PATCH net v2 4/6] net: bcm4908_enet: unmap the buffer of a dropped RX frame Kordian Chmielewski
@ 2026-11-12 19:50 ` Kordian Chmielewski
2026-11-12 22:20 ` Florian Fainelli
2026-11-12 19:50 ` [PATCH net v2 6/6] net: bcm4908_enet: publish the buffer address before OWN Kordian Chmielewski
5 siblings, 1 reply; 13+ messages in thread
From: Kordian Chmielewski @ 2026-11-12 19:50 UTC (permalink / raw)
To: netdev
Cc: Florian Fainelli, Florian Fainelli, 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.
Found by reading the code.
Fixes: 3a1cc23a75ab ("net: broadcom: bcm4908_enet: use build_skb()")
Signed-off-by: Kordian Chmielewski <kudzo51@gmail.com>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index 7f80e4e24..8ac749c44 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -333,7 +333,8 @@ 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 related [flat|nested] 13+ messages in thread* Re: [PATCH net v2 5/6] net: bcm4908_enet: unmap RX buffers on stop with their mapped size
2026-11-12 19:50 ` [PATCH net v2 5/6] net: bcm4908_enet: unmap RX buffers on stop with their mapped size Kordian Chmielewski
@ 2026-11-12 22:20 ` Florian Fainelli
0 siblings, 0 replies; 13+ messages in thread
From: Florian Fainelli @ 2026-11-12 22:20 UTC (permalink / raw)
To: Kordian Chmielewski, netdev
Cc: Florian Fainelli, Rafał Miłecki,
bcm-kernel-feedback-list, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
On 11/12/26 11:50, Kordian Chmielewski wrote:
> 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.
>
> Found by reading the code.
>
> Fixes: 3a1cc23a75ab ("net: broadcom: bcm4908_enet: use build_skb()")
> Signed-off-by: Kordian Chmielewski <kudzo51@gmail.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH net v2 6/6] net: bcm4908_enet: publish the buffer address before OWN
2026-11-12 19:50 [PATCH net v2 0/6] net: bcm4908_enet: TX ring and DMA fixes Kordian Chmielewski
` (4 preceding siblings ...)
2026-11-12 19:50 ` [PATCH net v2 5/6] net: bcm4908_enet: unmap RX buffers on stop with their mapped size Kordian Chmielewski
@ 2026-11-12 19:50 ` Kordian Chmielewski
2026-11-12 22:20 ` Florian Fainelli
5 siblings, 1 reply; 13+ messages in thread
From: Kordian Chmielewski @ 2026-11-12 19:50 UTC (permalink / raw)
To: netdev
Cc: Florian Fainelli, Florian Fainelli, 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. If the DMA read the descriptor between the two
stores, it would write 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()), as Documentation/memory-barriers.txt
describes for descriptors shared with a device.
Found by reading the code; I have no test that shows the DMA reading a
descriptor while it is being refilled.
Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
Signed-off-by: Kordian Chmielewski <kudzo51@gmail.com>
---
drivers/net/ethernet/broadcom/bcm4908_enet.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/broadcom/bcm4908_enet.c b/drivers/net/ethernet/broadcom/bcm4908_enet.c
index 8ac749c44..3d44b3452 100644
--- a/drivers/net/ethernet/broadcom/bcm4908_enet.c
+++ b/drivers/net/ethernet/broadcom/bcm4908_enet.c
@@ -290,8 +290,10 @@ 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);
buf_desc->addr = cpu_to_le32(slot->dma_addr);
+ /* Hand the descriptor to the DMA only once it holds the new buffer */
+ dma_wmb();
+ buf_desc->ctl = cpu_to_le32(tmp);
return 0;
}
@@ -610,6 +612,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);
@@ -644,6 +648,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;
+ /* Read the rest only after the DMA gave the descriptor back */
+ dma_rmb();
slot = enet->rx_ring.slots[enet->rx_ring.read_idx];
--
2.56.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH net v2 6/6] net: bcm4908_enet: publish the buffer address before OWN
2026-11-12 19:50 ` [PATCH net v2 6/6] net: bcm4908_enet: publish the buffer address before OWN Kordian Chmielewski
@ 2026-11-12 22:20 ` Florian Fainelli
0 siblings, 0 replies; 13+ messages in thread
From: Florian Fainelli @ 2026-11-12 22:20 UTC (permalink / raw)
To: Kordian Chmielewski, netdev
Cc: Florian Fainelli, Rafał Miłecki,
bcm-kernel-feedback-list, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel
On 11/12/26 11:50, Kordian Chmielewski wrote:
> 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. If the DMA read the descriptor between the two
> stores, it would write 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()), as Documentation/memory-barriers.txt
> describes for descriptors shared with a device.
>
> Found by reading the code; I have no test that shows the DMA reading a
> descriptor while it is being refilled.
>
> Fixes: 4feffeadbcb2 ("net: broadcom: bcm4908enet: add BCM4908 controller driver")
> Signed-off-by: Kordian Chmielewski <kudzo51@gmail.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 13+ messages in thread