From c9aa1a558e6c3ac336fe22d7ef725aef9ebeb11c Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Wed, 30 Sep 2026 11:52:55 +0200 Subject: [PATCH 1/5] net: macb: schedule TX NAPI on timeout for lost completions When the frames went out but their TCOMP interrupt got lost, TBQP already matches tx_head and macb_tx_restart() does nothing. TX NAPI never runs again and the queue stays stopped for good. Fixes: e438ec3e9e95 ("net: macb: add TX stall timeout callback to recover from lost TSTART write") Link: https://github.com/raspberrypi/linux/issues/7661 [nb: open-code macb_queue_isr_clear(), not available in 6.18] Signed-off-by: Nicolai Buchwitz --- drivers/net/ethernet/cadence/macb_main.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 4a5c3bd74a0a0..0d52b55aab97e 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -4709,8 +4709,21 @@ static int macb_setup_tc(struct net_device *dev, enum tc_setup_type type, static void macb_tx_timeout(struct net_device *dev, unsigned int q) { struct macb *bp = netdev_priv(dev); + struct macb_queue *queue = &bp->queues[q]; - macb_tx_restart(&bp->queues[q]); + macb_tx_restart(queue); + + /* The frames may have gone out with their TCOMP interrupt lost. + * macb_tx_restart() does nothing then as TBQP already matches + * tx_head, so run TX NAPI to complete them or the queue stays + * stopped. + */ + if (macb_tx_complete_pending(queue)) { + queue_writel(queue, IDR, MACB_BIT(TCOMP)); + if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) + queue_writel(queue, ISR, MACB_BIT(TCOMP)); + napi_schedule(&queue->napi_tx); + } } static const struct net_device_ops macb_netdev_ops = { From 4960baf696de51cdb27081af01f078be8add1784 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Wed, 30 Sep 2026 11:52:55 +0200 Subject: [PATCH 2/5] net: macb: credit unsent frames to BQL in macb_tx_error_task() The frames dropped on a TX error are never returned to BQL. Each error shrinks the BQL budget for good until the queue stops with an empty ring. netdev_tx_reset_queue() is not safe here as the task runs without tx_ptr_lock, so macb_start_xmit() may still be queueing. Fixes: e6a532185daa ("net: cadence: macb: Implement BQL") Signed-off-by: Nicolai Buchwitz --- drivers/net/ethernet/cadence/macb_main.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 0d52b55aab97e..1fa265d42a565 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -1357,10 +1357,8 @@ static void macb_tx_error_task(struct work_struct *work) skb->data); bp->dev->stats.tx_packets++; queue->stats.tx_packets++; - packets++; bp->dev->stats.tx_bytes += skb->len; queue->stats.tx_bytes += skb->len; - bytes += skb->len; } } else { /* "Buffers exhausted mid-frame" errors may only happen @@ -1374,6 +1372,12 @@ static void macb_tx_error_task(struct work_struct *work) desc->ctrl = ctrl | MACB_BIT(TX_USED); } + /* BQL has to see every frame leaving the ring, sent or not */ + if (skb) { + packets++; + bytes += skb->len; + } + macb_tx_unmap(bp, tx_skb, 0); } From d9a828b544ca74a7e38a8c6a93244ef6b544997a Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Sun, 18 Oct 2026 11:41:56 +0200 Subject: [PATCH 3/5] net: macb: flush the IER write before re-checking the ring The IER write at the end of macb_tx_poll() is posted, so the ring check right after it can run before TCOMP is unmasked. A frame that completes in that window never raises an interrupt. With TSO this happens when TXUBR triggers a poll while the frame is still on the wire. BQL keeps the queue stopped on that frame and only the watchdog gets it going again. Read IMR back after the write so the check runs with TCOMP unmasked. macb_rx_poll() has the same window. Fixes: 138badbc21a0 ("net: macb: use NAPI for TX completion path") Fixes: 0bf476fc3624 ("net: macb: Fix lost RX packet wakeup race in NAPI receive") Link: https://github.com/raspberrypi/linux/issues/7661 Link: https://lore.kernel.org/netdev/20260514215459.36109-3-lukasz@raczylo.com/ Signed-off-by: Nicolai Buchwitz --- drivers/net/ethernet/cadence/macb_main.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 1fa265d42a565..404be9a9687ef 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -1921,7 +1921,11 @@ static int macb_rx_poll(struct napi_struct *napi, int budget) * potentially race with the interrupt handler doing the same * actions if an interrupt is raised just after enabling them, * but this should be harmless. + * + * The IER write is posted. Flush it before checking the ring, + * a completion in between would raise no interrupt. */ + queue_readl(queue, IMR); if (macb_rx_pending(queue)) { queue_writel(queue, IDR, bp->rx_intr_mask); if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) @@ -2016,7 +2020,11 @@ static int macb_tx_poll(struct napi_struct *napi, int budget) * potentially race with the interrupt handler doing the same * actions if an interrupt is raised just after enabling them, * but this should be harmless. + * + * The IER write is posted. Flush it before checking the ring, + * a completion in between would raise no interrupt. */ + queue_readl(queue, IMR); if (macb_tx_complete_pending(queue)) { queue_writel(queue, IDR, MACB_BIT(TCOMP)); if (bp->caps & MACB_CAPS_ISR_CLEAR_ON_WRITE) From 3f8a93c1f01678ccffc3adf6b16af2984d9307f2 Mon Sep 17 00:00:00 2001 From: Christian Taedcke Date: Thu, 22 Oct 2026 11:38:47 +0200 Subject: [PATCH 4/5] net: macb: reprogram TBQP after shuffling the TX ring on link-up 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 Link: https://lore.kernel.org/netdev/20261022-std-macb-v14-send-v1-2-9187bc545bf6@beckhoff.com/ Signed-off-by: Christian Taedcke Signed-off-by: Nicolai Buchwitz --- 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 404be9a9687ef..f5aeb431a5767 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -854,7 +854,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; @@ -903,6 +903,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); } From 8e98fa6c51a68e2ba00291f86b08cfb141e271e8 Mon Sep 17 00:00:00 2001 From: Steffen Dirkwinkel Date: Fri, 23 Oct 2026 11:59:10 +0200 Subject: [PATCH 5/5] net: macb: kick the transmitter on link-up 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 Link: https://lore.kernel.org/netdev/20261022-std-macb-v14-send-v1-5-9187bc545bf6@beckhoff.com/ Assisted-by: LLM Signed-off-by: Steffen Dirkwinkel [nb: taken without the rotation rework it sits on, the kick goes right after TE is set] Signed-off-by: Nicolai Buchwitz --- drivers/net/ethernet/cadence/macb_main.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index f5aeb431a5767..0ebb88499434a 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -924,6 +924,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 *phy, unsigned int mode, phy_interface_t interface, @@ -987,6 +989,12 @@ static void macb_mac_link_up(struct phylink_config *config, macb_writel(bp, NCR, ctrl | MACB_BIT(RE) | MACB_BIT(TE)); + 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); + } + netif_tx_wake_all_queues(ndev); }