From: Kordian Chmielewski <kudzo51@gmail.com>
To: netdev@vger.kernel.org
Cc: "Florian Fainelli" <f.fainelli@gmail.com>,
"Florian Fainelli" <florian.fainelli@broadcom.com>,
"Rafał Miłecki" <rafal@milecki.pl>,
bcm-kernel-feedback-list@broadcom.com,
"Andrew Lunn" <andrew+netdev@lunn.ch>,
"David S . Miller" <davem@davemloft.net>,
"Eric Dumazet" <edumazet@kernel.org>,
"Jakub Kicinski" <kuba@kernel.org>,
"Paolo Abeni" <pabeni@redhat.com>,
linux-kernel@vger.kernel.org
Subject: [PATCH net v2 1/6] net: bcm4908_enet: fix the TX ring wrap
Date: Thu, 12 Nov 2026 20:50:46 +0100 [thread overview]
Message-ID: <20261112195051.597102-2-kudzo51@gmail.com> (raw)
In-Reply-To: <20261112195051.597102-1-kudzo51@gmail.com>
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
next prev parent reply other threads:[~2026-11-12 19:51 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
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 ` [PATCH net v2 3/6] net: bcm4908_enet: release the TX ring on stop Kordian Chmielewski
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 ` [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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261112195051.597102-2-kudzo51@gmail.com \
--to=kudzo51@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=bcm-kernel-feedback-list@broadcom.com \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=f.fainelli@gmail.com \
--cc=florian.fainelli@broadcom.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=rafal@milecki.pl \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox