* [PATCH RFC net 1/6] net: macb: take bp->lock around NCR read-modify-writes
2026-10-22 9:38 [PATCH RFC net 0/6] net: macb: fix TX stalls, IRQ storms and a lost NCR update on link-up Steffen Dirkwinkel
@ 2026-10-22 9:38 ` Steffen Dirkwinkel
2026-10-22 9:38 ` [PATCH RFC net 2/6] net: macb: reprogram TBQP after shuffling the TX ring on link-up Steffen Dirkwinkel
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Steffen Dirkwinkel @ 2026-10-22 9:38 UTC (permalink / raw)
To: Théo Lebrun, Conor Dooley, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Kevin Hao, Sebastian Andrzej Siewior, Russell King
Cc: netdev, linux-kernel, linux-rt-devel, Christian Taedcke,
Nicolai Buchwitz, Andrea della Porta, stable
From: Théo Lebrun <theo.lebrun@bootlin.com>
NCR is read-modify-written from many contexts:
- macb_mac_link_down() clears RE|TE,
- macb_mac_link_up() sets RE|TE|PTPUNI,
- macb_hresp_error_task() clears then re-sets RE|TE,
- macb_start_xmit() / macb_tx_restart() / macb_tx_error_task() set
TSTART (already under bp->lock),
- macb_interrupt() might toggle RE (also under bp->lock).
The first three risk concurrent RMW with anyone from the list as they
don't grab bp->lock.
Theoretical bugfix only, it has never reproduced on hardware. Also note
it does *not* take the lock hoping to protect against full races
inbetween BH scheduling concurrently. We only want to ensure consistent
NCR RMW operations.
Fixes: 7897b071ac3b ("net: macb: convert to phylink")
Cc: stable@vger.kernel.org
Signed-off-by: Théo Lebrun <theo.lebrun@bootlin.com>
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/cadence/macb_main.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index 261a7e87520a..4b5037867537 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -777,6 +777,7 @@ static void macb_mac_link_down(struct phylink_config *config, unsigned int mode,
struct net_device *netdev = to_net_dev(config->dev);
struct macb *bp = netdev_priv(netdev);
struct macb_queue *queue;
+ unsigned long flags;
unsigned int q;
u32 ctrl;
@@ -786,8 +787,10 @@ static void macb_mac_link_down(struct phylink_config *config, unsigned int mode,
bp->rx_intr_mask | MACB_TX_INT_FLAGS | MACB_BIT(HRESP));
/* Disable Rx and Tx */
+ spin_lock_irqsave(&bp->lock, flags);
ctrl = macb_readl(bp, NCR) & ~(MACB_BIT(RE) | MACB_BIT(TE));
macb_writel(bp, NCR, ctrl);
+ spin_unlock_irqrestore(&bp->lock, flags);
netif_tx_stop_all_queues(netdev);
}
@@ -940,11 +943,13 @@ static void macb_mac_link_up(struct phylink_config *config,
}
/* Enable Rx and Tx; Enable PTP unicast */
+ spin_lock_irqsave(&bp->lock, flags);
ctrl = macb_readl(bp, NCR);
if (gem_has_ptp(bp))
ctrl |= MACB_BIT(PTPUNI);
macb_writel(bp, NCR, ctrl | MACB_BIT(RE) | MACB_BIT(TE));
+ spin_unlock_irqrestore(&bp->lock, flags);
netif_tx_wake_all_queues(netdev);
}
@@ -1995,6 +2000,7 @@ static void macb_hresp_error_task(struct work_struct *work)
struct macb *bp = from_work(bp, work, hresp_err_bh_work);
struct net_device *netdev = bp->netdev;
struct macb_queue *queue;
+ unsigned long flags;
unsigned int q;
u32 ctrl;
@@ -2003,9 +2009,11 @@ static void macb_hresp_error_task(struct work_struct *work)
MACB_TX_INT_FLAGS |
MACB_BIT(HRESP));
}
+ spin_lock_irqsave(&bp->lock, flags);
ctrl = macb_readl(bp, NCR);
ctrl &= ~(MACB_BIT(RE) | MACB_BIT(TE));
macb_writel(bp, NCR, ctrl);
+ spin_unlock_irqrestore(&bp->lock, flags);
netif_tx_stop_all_queues(netdev);
netif_carrier_off(netdev);
@@ -2022,8 +2030,10 @@ static void macb_hresp_error_task(struct work_struct *work)
MACB_TX_INT_FLAGS |
MACB_BIT(HRESP));
- ctrl |= MACB_BIT(RE) | MACB_BIT(TE);
- macb_writel(bp, NCR, ctrl);
+ spin_lock_irqsave(&bp->lock, flags);
+ ctrl = MACB_BIT(RE) | MACB_BIT(TE);
+ macb_writel(bp, NCR, macb_readl(bp, NCR) | ctrl);
+ spin_unlock_irqrestore(&bp->lock, flags);
netif_carrier_on(netdev);
netif_tx_start_all_queues(netdev);
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH RFC net 2/6] net: macb: reprogram TBQP after shuffling the TX ring on link-up
2026-10-22 9:38 [PATCH RFC net 0/6] net: macb: fix TX stalls, IRQ storms and a lost NCR update on link-up Steffen Dirkwinkel
2026-10-22 9:38 ` [PATCH RFC net 1/6] net: macb: take bp->lock around NCR read-modify-writes Steffen Dirkwinkel
@ 2026-10-22 9:38 ` Steffen Dirkwinkel
2026-10-22 9:38 ` [PATCH RFC net 3/6] net: macb: mask TXUBR during TX NAPI poll to prevent IRQ storms Steffen Dirkwinkel
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Steffen Dirkwinkel @ 2026-10-22 9:38 UTC (permalink / raw)
To: Théo Lebrun, Conor Dooley, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Kevin Hao, Sebastian Andrzej Siewior, Russell King
Cc: netdev, linux-kernel, linux-rt-devel, Christian Taedcke,
Nicolai Buchwitz, Andrea della Porta, stable
From: Christian Taedcke <christian.taedcke@weidmueller.com>
gem_shuffle_tx_one_ring() rotates the software TX ring so that the
tail sits at index 0 and resets queue->tx_tail to 0, but it never
reprograms the hardware transmit buffer queue pointer (TBQP). Other
paths that reset tx_tail to the ring base (macb_init_buffers() and
macb_tx_error_task()) also reprogram TBQP to queue->tx_ring_dma; this
path does not, leaving TBQP pointing at a stale descriptor.
gem_shuffle_tx_rings() runs on every link-up from
macb_mac_link_up(). After a few link up/down flaps that leave
un-completed descriptors in the ring, the stale TBQP keeps pointing at
a descriptor whose used bit is set. When TX is re-enabled on link-up,
the GEM reads that used descriptor and raises TXUBR. macb_interrupt()
schedules the TX NAPI, macb_tx_poll() makes no progress (work_done ==
0) and macb_tx_restart() re-issues TSTART, which makes the controller
read the same used descriptor again and re-assert TXUBR. As the MAC
interrupt is level-triggered, it never deasserts and one CPU is pegged
at 100% in the threaded handler, eventually triggering "sched: RT
throttling activated" and a dead network interface.
Fix it by reprogramming TBQP to the ring base on every path of
gem_shuffle_tx_one_ring() that resets tx_tail to 0, mirroring
macb_tx_error_task(). The early return for an already-aligned tail is
left untouched as TBQP is already consistent there. This is safe
because the shuffle runs from macb_mac_link_up() while TE is still
disabled, so the transmitter is halted.
Fixes: 881a0263d502 ("net: macb: Shuffle the tx ring before enabling tx")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Christian Taedcke <christian.taedcke@weidmueller.com>
---
drivers/net/ethernet/cadence/macb_main.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index 4b5037867537..97494933eb1e 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -823,7 +823,7 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue)
if (!count) {
queue->tx_head = 0;
queue->tx_tail = 0;
- goto unlock;
+ goto reset_hw_ptr;
}
shift = tail % ring_size;
@@ -872,6 +872,13 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue)
/* Make descriptor updates visible to hardware */
wmb();
+reset_hw_ptr:
+ /* tx_tail was reset to the ring base, so TBQP must be reprogrammed
+ * to match; otherwise it keeps pointing at a stale descriptor. Safe
+ * to write directly here as TX is still disabled (called from
+ * macb_mac_link_up() before TE is set).
+ */
+ queue_writel(queue, TBQP, lower_32_bits(queue->tx_ring_dma));
unlock:
spin_unlock_irqrestore(&queue->tx_ptr_lock, flags);
}
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH RFC net 3/6] net: macb: mask TXUBR during TX NAPI poll to prevent IRQ storms
2026-10-22 9:38 [PATCH RFC net 0/6] net: macb: fix TX stalls, IRQ storms and a lost NCR update on link-up Steffen Dirkwinkel
2026-10-22 9:38 ` [PATCH RFC net 1/6] net: macb: take bp->lock around NCR read-modify-writes Steffen Dirkwinkel
2026-10-22 9:38 ` [PATCH RFC net 2/6] net: macb: reprogram TBQP after shuffling the TX ring on link-up Steffen Dirkwinkel
@ 2026-10-22 9:38 ` Steffen Dirkwinkel
2026-10-22 9:38 ` [PATCH RFC net 4/6] net: macb: rotate unreaped TX frames behind the ring base on link-up Steffen Dirkwinkel
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Steffen Dirkwinkel @ 2026-10-22 9:38 UTC (permalink / raw)
To: Théo Lebrun, Conor Dooley, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Kevin Hao, Sebastian Andrzej Siewior, Russell King
Cc: netdev, linux-kernel, linux-rt-devel, Christian Taedcke,
Nicolai Buchwitz, Andrea della Porta, stable
From: Christian Taedcke <christian.taedcke@weidmueller.com>
macb_interrupt() defers TX completion handling to NAPI, but when it
schedules the poll it only masks TCOMP, even though TXUBR is enabled
alongside it (both are part of MACB_TX_INT_FLAGS). macb_tx_poll() is
asymmetric in the same way and only re-enables TCOMP. TXUBR is thus
left unmasked while responsibility for handling it has been deferred
to NAPI.
Unlike an edge event, TXUBR is a persistent condition: the controller
keeps it asserted for as long as the transmitter reads a buffer
descriptor whose used bit is set. Leaving a level-triggered source
enabled while NAPI owns its processing means the interrupt refires
immediately after the handler returns, before the poll has had a
chance to clear the underlying condition. This turns into a hard
interrupt storm that pegs a CPU in the (threaded) MAC IRQ handler and,
on PREEMPT_RT, triggers RT throttling ("sched: RT throttling
activated"), taking the network interface down.
Several situations can keep the used-bit read asserted across a poll -
for example unreaped completed descriptors still sitting at tx_tail,
or a transmit restart racing with macb_start_xmit(). The specific
trigger does not matter: as long as the source stays unmasked, any
persistent assertion is enough to storm, so the interrupt handling
itself must be made self-limiting.
Mask TXUBR together with TCOMP in the IDR write when scheduling the TX
NAPI, and re-enable both from the napi_complete path in
macb_tx_poll(), making the TX interrupt mask/unmask symmetric and
consistent with how the driver already treats every other
NAPI-serviced source. The pending TXUBR is still recorded in
queue->txubr_pending before masking and acted on by macb_tx_restart(),
so no event is lost. A persistent TXUBR now degrades to NAPI-paced
polling instead of a CPU-pegging hard interrupt storm.
Fixes: 138badbc21a0 ("net: macb: use NAPI for TX completion path")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Christian Taedcke <christian.taedcke@weidmueller.com>
---
drivers/net/ethernet/cadence/macb_main.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index 97494933eb1e..14c8299cb7f3 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -1979,7 +1979,7 @@ static int macb_tx_poll(struct napi_struct *napi, int budget)
(unsigned int)(queue - bp->queues), work_done, budget);
if (work_done < budget && napi_complete_done(napi, work_done)) {
- queue_writel(queue, IER, MACB_BIT(TCOMP));
+ queue_writel(queue, IER, MACB_BIT(TCOMP) | MACB_BIT(TXUBR));
/* Packet completions only seem to propagate to raise
* interrupts when interrupts are enabled at the time, so if
@@ -2174,7 +2174,8 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id)
if (status & (MACB_BIT(TCOMP) |
MACB_BIT(TXUBR))) {
- queue_writel(queue, IDR, MACB_BIT(TCOMP));
+ queue_writel(queue, IDR, MACB_BIT(TCOMP) |
+ MACB_BIT(TXUBR));
macb_queue_isr_clear(bp, queue, MACB_BIT(TCOMP) |
MACB_BIT(TXUBR));
if (status & MACB_BIT(TXUBR)) {
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH RFC net 4/6] net: macb: rotate unreaped TX frames behind the ring base on link-up
2026-10-22 9:38 [PATCH RFC net 0/6] net: macb: fix TX stalls, IRQ storms and a lost NCR update on link-up Steffen Dirkwinkel
` (2 preceding siblings ...)
2026-10-22 9:38 ` [PATCH RFC net 3/6] net: macb: mask TXUBR during TX NAPI poll to prevent IRQ storms Steffen Dirkwinkel
@ 2026-10-22 9:38 ` Steffen Dirkwinkel
2026-10-22 9:38 ` [PATCH RFC net 5/6] net: macb: kick the transmitter " Steffen Dirkwinkel
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Steffen Dirkwinkel @ 2026-10-22 9:38 UTC (permalink / raw)
To: Théo Lebrun, Conor Dooley, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Kevin Hao, Sebastian Andrzej Siewior, Russell King
Cc: netdev, linux-kernel, linux-rt-devel, Christian Taedcke,
Nicolai Buchwitz, Andrea della Porta, Steffen Dirkwinkel, stable
From: Steffen Dirkwinkel <s.dirkwinkel@beckhoff.com>
Start the link-up rotation in gem_shuffle_tx_one_ring() at the first
frame that still has to be sent instead of at tx_tail, and schedule the
TX NAPI poll once transmit is enabled again.
macb_mac_link_down() masks TCOMP before disabling transmit. A frame
completed in that window keeps the used bit on its first descriptor and
is never reaped. The rotation moves it to index 0, where the controller
starts after link-up, reads the used bit and stops; macb_tx_restart()
then keeps writing TSTART onto the same descriptor, an interrupt storm
that on PREEMPT_RT ends in RCU stalls.
Keeping the completed frames behind the ring base leaves tx_tail on
them for NAPI to reap. Reaping in link-down instead would have to wait
for NAPI there, which deadlocks against macb_close() and the PM
callbacks disabling it.
Fixes: 881a0263d502 ("net: macb: Shuffle the tx ring before enabling tx")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Steffen Dirkwinkel <s.dirkwinkel@beckhoff.com>
---
drivers/net/ethernet/cadence/macb_main.c | 43 +++++++++++++++++++++++---------
1 file changed, 31 insertions(+), 12 deletions(-)
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index 14c8299cb7f3..6e7ead23d0b7 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -798,7 +798,7 @@ static void macb_mac_link_down(struct phylink_config *config, unsigned int mode,
/* Use juggling algorithm to left rotate tx ring and tx skb array */
static void gem_shuffle_tx_one_ring(struct macb_queue *queue)
{
- unsigned int head, tail, count, ring_size, desc_size;
+ unsigned int head, tail, count, ring_size, desc_size, start, done;
struct macb_tx_skb tx_skb, *skb_curr, *skb_next;
struct macb_dma_desc *desc_curr, *desc_next;
unsigned int i, cycles, shift, curr, next;
@@ -817,8 +817,19 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue)
ring_size = bp->tx_ring_size;
count = CIRC_CNT(head, tail, ring_size);
- if (!(tail % ring_size))
- goto unlock;
+ /* Frames completed while link-down had TCOMP masked are still
+ * unreaped and their used bit would stop the controller at the ring
+ * base, so rotate the first frame still to be sent there instead.
+ */
+ rmb();
+ for (start = tail; start != head &&
+ (macb_tx_desc(queue, start)->ctrl & MACB_BIT(TX_USED)); start++)
+ while (start + 1 != head && !macb_tx_skb(queue, start)->skb)
+ start++;
+ done = start - tail;
+
+ if (!(start % ring_size))
+ goto reset_hw_ptr;
if (!count) {
queue->tx_head = 0;
@@ -826,7 +837,7 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue)
goto reset_hw_ptr;
}
- shift = tail % ring_size;
+ shift = start % ring_size;
cycles = gcd(ring_size, shift);
for (i = 0; i < cycles; i++) {
@@ -866,24 +877,23 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue)
sizeof(struct macb_tx_skb));
}
- queue->tx_head = count;
- queue->tx_tail = 0;
+ queue->tx_tail = done ? ring_size - done : 0;
+ queue->tx_head = queue->tx_tail + count;
/* Make descriptor updates visible to hardware */
wmb();
reset_hw_ptr:
- /* tx_tail was reset to the ring base, so TBQP must be reprogrammed
- * to match; otherwise it keeps pointing at a stale descriptor. Safe
- * to write directly here as TX is still disabled (called from
- * macb_mac_link_up() before TE is set).
+ /* The first frame to be sent is now at the ring base, so TBQP must
+ * be reprogrammed to match; otherwise it keeps pointing at a stale
+ * descriptor. Safe to write directly here as TX is still disabled
+ * (called from macb_mac_link_up() before TE is set).
*/
queue_writel(queue, TBQP, lower_32_bits(queue->tx_ring_dma));
-unlock:
spin_unlock_irqrestore(&queue->tx_ptr_lock, flags);
}
-/* Rotate the queue so that the tail is at index 0 */
+/* Rotate the queue so that the first frame to be sent is at index 0 */
static void gem_shuffle_tx_rings(struct macb *bp)
{
struct macb_queue *queue;
@@ -958,6 +968,15 @@ static void macb_mac_link_up(struct phylink_config *config,
macb_writel(bp, NCR, ctrl | MACB_BIT(RE) | MACB_BIT(TE));
spin_unlock_irqrestore(&bp->lock, flags);
+ if (!(bp->caps & MACB_CAPS_MACB_IS_EMAC)) {
+ /* Reap the frames completed while link-down had TCOMP masked */
+ local_bh_disable();
+ for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue)
+ if (READ_ONCE(queue->tx_tail) != READ_ONCE(queue->tx_head))
+ napi_schedule(&queue->napi_tx);
+ local_bh_enable();
+ }
+
netif_tx_wake_all_queues(netdev);
}
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH RFC net 5/6] net: macb: kick the transmitter on link-up
2026-10-22 9:38 [PATCH RFC net 0/6] net: macb: fix TX stalls, IRQ storms and a lost NCR update on link-up Steffen Dirkwinkel
` (3 preceding siblings ...)
2026-10-22 9:38 ` [PATCH RFC net 4/6] net: macb: rotate unreaped TX frames behind the ring base on link-up Steffen Dirkwinkel
@ 2026-10-22 9:38 ` Steffen Dirkwinkel
2026-10-22 9:38 ` [PATCH RFC net 6/6] net: macb: keep best-effort traffic on TX queue 0 Steffen Dirkwinkel
2026-10-22 9:55 ` [PATCH RFC net 0/6] net: macb: fix TX stalls, IRQ storms and a lost NCR update on link-up Nicolai Buchwitz
6 siblings, 0 replies; 8+ messages in thread
From: Steffen Dirkwinkel @ 2026-10-22 9:38 UTC (permalink / raw)
To: Théo Lebrun, Conor Dooley, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Kevin Hao, Sebastian Andrzej Siewior, Russell King
Cc: netdev, linux-kernel, linux-rt-devel, Christian Taedcke,
Nicolai Buchwitz, Andrea della Porta, Steffen Dirkwinkel, stable
From: Steffen Dirkwinkel <s.dirkwinkel@beckhoff.com>
Write TSTART in macb_mac_link_up() for every queue that still has
frames outstanding once transmit has been re-enabled.
Frames queued before a link-down stay in the ring, and setting NCR.TE
does not start the transmitter; the driver relies on the next
macb_start_xmit() to write TSTART. If BQL stopped the queue before the
link went down, that transmit never comes and the queue stays idle
until the netdev watchdog fires.
Like the rotation, the kick is skipped on the at91rm9200 EMAC, which
uses its own TX path.
Fixes: 881a0263d502 ("net: macb: Shuffle the tx ring before enabling tx")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Steffen Dirkwinkel <s.dirkwinkel@beckhoff.com>
---
drivers/net/ethernet/cadence/macb_main.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index 6e7ead23d0b7..3dfc7ffdd598 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -903,6 +903,8 @@ static void gem_shuffle_tx_rings(struct macb *bp)
gem_shuffle_tx_one_ring(queue);
}
+static void macb_tx_restart(struct macb_queue *queue);
+
static void macb_mac_link_up(struct phylink_config *config,
struct phy_device *phydev,
unsigned int mode, phy_interface_t interface,
@@ -969,6 +971,10 @@ static void macb_mac_link_up(struct phylink_config *config,
spin_unlock_irqrestore(&bp->lock, flags);
if (!(bp->caps & MACB_CAPS_MACB_IS_EMAC)) {
+ /* With BQL holding a queue stopped no xmit would issue TSTART */
+ for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue)
+ macb_tx_restart(queue);
+
/* Reap the frames completed while link-down had TCOMP masked */
local_bh_disable();
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue)
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH RFC net 6/6] net: macb: keep best-effort traffic on TX queue 0
2026-10-22 9:38 [PATCH RFC net 0/6] net: macb: fix TX stalls, IRQ storms and a lost NCR update on link-up Steffen Dirkwinkel
` (4 preceding siblings ...)
2026-10-22 9:38 ` [PATCH RFC net 5/6] net: macb: kick the transmitter " Steffen Dirkwinkel
@ 2026-10-22 9:38 ` Steffen Dirkwinkel
2026-10-22 9:55 ` [PATCH RFC net 0/6] net: macb: fix TX stalls, IRQ storms and a lost NCR update on link-up Nicolai Buchwitz
6 siblings, 0 replies; 8+ messages in thread
From: Steffen Dirkwinkel @ 2026-10-22 9:38 UTC (permalink / raw)
To: Théo Lebrun, Conor Dooley, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Kevin Hao, Sebastian Andrzej Siewior, Russell King
Cc: netdev, linux-kernel, linux-rt-devel, Christian Taedcke,
Nicolai Buchwitz, Andrea della Porta, Steffen Dirkwinkel
From: Steffen Dirkwinkel <s.dirkwinkel@beckhoff.com>
Add an ndo_select_queue() that puts every frame on queue 0 unless
traffic classes are configured (taprio, or software mqprio).
The GEM serves its transmit queues in strict priority order, the
highest queue first, while the stack hashes flows over all of them. On
a saturated link the flows on the lower queues starve until the netdev
watchdog fires on them, and the timeout path cannot help as nothing is
wrong with the queue.
XPS settings, the socket queue cache and the queue choice of
PACKET_QDISC_BYPASS sockets no longer take effect on multi-queue GEMs
without traffic classes; queues 1 and up stay reachable through tc.
Fixes: 02c958dd3446 ("net/macb: add TX multiqueue support for gem")
Assisted-by: LLM
Signed-off-by: Steffen Dirkwinkel <s.dirkwinkel@beckhoff.com>
---
drivers/net/ethernet/cadence/macb_main.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index 3dfc7ffdd598..eae43490c5f7 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -4596,10 +4596,21 @@ static void macb_tx_timeout(struct net_device *netdev, unsigned int q)
macb_tx_restart(&bp->queues[q]);
}
+/* GEM serves TX queues in strict priority order, the highest queue first */
+static u16 macb_select_queue(struct net_device *netdev, struct sk_buff *skb,
+ struct net_device *sb_dev)
+{
+ if (netdev_get_num_tc(netdev))
+ return netdev_pick_tx(netdev, skb, sb_dev);
+
+ return 0;
+}
+
static const struct net_device_ops macb_netdev_ops = {
.ndo_open = macb_open,
.ndo_stop = macb_close,
.ndo_start_xmit = macb_start_xmit,
+ .ndo_select_queue = macb_select_queue,
.ndo_set_rx_mode = macb_set_rx_mode,
.ndo_get_stats64 = macb_get_stats,
.ndo_eth_ioctl = macb_ioctl,
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH RFC net 0/6] net: macb: fix TX stalls, IRQ storms and a lost NCR update on link-up
2026-10-22 9:38 [PATCH RFC net 0/6] net: macb: fix TX stalls, IRQ storms and a lost NCR update on link-up Steffen Dirkwinkel
` (5 preceding siblings ...)
2026-10-22 9:38 ` [PATCH RFC net 6/6] net: macb: keep best-effort traffic on TX queue 0 Steffen Dirkwinkel
@ 2026-10-22 9:55 ` Nicolai Buchwitz
6 siblings, 0 replies; 8+ messages in thread
From: Nicolai Buchwitz @ 2026-10-22 9:55 UTC (permalink / raw)
To: Steffen Dirkwinkel
Cc: Théo Lebrun, Conor Dooley, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Kevin Hao, Sebastian Andrzej Siewior, Russell King, netdev,
linux-kernel, linux-rt-devel, Christian Taedcke,
Andrea della Porta, stable, Steffen Dirkwinkel
Hi Steffen
On 22.10.2026 11:38, Steffen Dirkwinkel wrote:
> Hello Théo, Christian, all,
>
> I've been looking at RCU and TX stalls on macb devices (Xilinx ZynqMP
> on
> Beckhoff CX9240) for a while and have been carrying downstream patches
> similar to Christian's. A while ago I let Claude try to reproduce this
> on my
> devices, it was able to reproduce and found some fixes but I wasn't
> happy with the quality of the patches and my understanding of them. I
> briefly talked to Théo in Prague and he suggested I send what I have as
> either the patches or the reproducers may be useful for ideas.
> I've finally gotten around to it and also ran some more testing and
> improved
> the patches in the meantime. The series includes Christian's patches in
> the
> original form (should also at least be reworded) and I also have Patch
> 1 in here
> since the bench seems to have reproduced the issue on hardware (see
> below).
>
> If anyone has other ideas / patches for these issues I'm also happy to
> run tests on devices.
Thanks for posting this RFC. I will go later through the details, but
wanted to let you
know that I'm also investigating the macb watchdog issue on RPi RP1 [1].
Yesterday Théo also shared some findings in the thread.
Three things from that work which touch your series:
- Patch 3 changes the IER write in macb_tx_poll() that I need a readl
after. The write
is posted, a TCOMP landing between the ring check and the write
reaching the MAC is
lost. That seems to be the RP1 stall, masking TXUBR does not change
it.
The two patches need ordering.
- The watchdog only recovers a lost TSTART. With a missed completion
TBQP == tx_head and
macb_tx_restart() does nothing, the queue never comes back. I have a
patch that
schedules TX NAPI from macb_tx_timeout() when the tail descriptor has
TX_USED set.
- RP1 has a single TX queue, so the starvation from patch 6 is not what
we see.
I can run the series on an RP1 board with my reproducer if that helps.
[1]
https://github.com/raspberrypi/linux/issues/7661#issuecomment-6322904343
> [...]
Thanks,
Nicolai
^ permalink raw reply [flat|nested] 8+ messages in thread