Author SHA1 Message Date
lukaszraczylo b26afa2a5d patches/linux: add 0004 - track TX-tail movement via boolean
Replaces snapshot-of-tx_tail comparison in the watchdog with a
tx_stall_tail_moved flag set/cleared under tx_ptr_lock.  Avoids
the index-aliasing false-positive Phil Elwell flagged on the
raspberrypi/linux PR review.

Anchored against the rpi-6.18.y vendor fork so it applies cleanly
on top of patches 0001..0003.  See talos-kernel-patches/0004-*.patch
for the mainline-anchored variant intended for netdev v2.
2026-05-02 15:00:28 +01:00
lukaszraczylo 77426168b0 patches/linux: add README explaining the macb series + provenance
For readers landing on the public repo, document:

  * what the three patches do and what bug they target
  * link to the upstream netdev submission (lore archive)
  * link to the Cilium / Ubuntu launchpad issue trackers
  * how the rpi-fork-targeted version here differs from the
    mainline-targeted version on netdev (5-line macb.h hunk variation
    for the vendor fork's extra bool tx_pending field)
  * license: GPL-2.0-only (kernel-derived) overriding the harness's MIT
  * how to add additional patches (drop a *.patch file; sorted apply
    order)

No code changes.
2026-04-25 23:20:44 +01:00
2 changed files with 149 additions and 0 deletions
@@ -0,0 +1,115 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Lukasz Raczylo <[email protected]>
Date: Sat, 2 May 2026 14:42:29 +0100
Subject: [PATCH] net: macb: track TX-tail movement via boolean to avoid index
aliasing
Address review feedback from pelwell on raspberrypi/linux#7340.
Per-queue ring indices are bounded and reused, so the previous
snapshot-of-tx_tail comparison in macb_tx_stall_watchdog() can
false-positive when the index lands on the same value between
two ticks under sustained load.
Replace the snapshot with a tx_stall_tail_moved boolean that
macb_tx_complete() sets under tx_ptr_lock whenever tx_tail
advances and the watchdog clears under the same lock at each
tick. No atomic is required (single producer / single consumer
already serialised by tx_ptr_lock).
Initialise the flag to true in macb_open() so the first tick
after open is a grace period rather than a synthetic stall.
Anchored against the rpi-6.18.y vendor fork (which carries the
extra `bool tx_pending` field on struct macb_queue) — the
mainline-anchored variant lives at
home-cluster/talos-kernel-patches/0004-*.patch.
Link: https://github.com/raspberrypi/linux/pull/7340#pullrequestreview-4198291045
Signed-off-by: Lukasz Raczylo <[email protected]>
---
drivers/net/ethernet/cadence/macb.h | 10 ++++++++--
drivers/net/ethernet/cadence/macb_main.c | 23 +++++++++++++++--------
2 files changed, 23 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h
index 827075e4b..bdbafe097 100644
--- a/drivers/net/ethernet/cadence/macb.h
+++ b/drivers/net/ethernet/cadence/macb.h
@@ -1295,9 +1295,15 @@ struct macb_queue {
bool txubr_pending;
bool tx_pending;
- /* TX stall watchdog -- see macb_tx_stall_watchdog() in macb_main.c */
+ /* TX stall watchdog -- see macb_tx_stall_watchdog() in macb_main.c.
+ * tx_stall_tail_moved is set by macb_tx_complete() under tx_ptr_lock
+ * whenever tx_tail advances, and cleared by the watchdog tick on the
+ * same lock. A bool avoids the index-aliasing false-positive that a
+ * snapshot-of-tx_tail comparison would have when the ring index space
+ * happens to wrap to the same value between two ticks.
+ */
struct delayed_work tx_stall_watchdog_work;
- unsigned int tx_stall_last_tail;
+ bool tx_stall_tail_moved;
struct napi_struct napi_tx;
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index 77fbcbab8..423aac02a 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -1505,6 +1505,8 @@ static int macb_tx_complete(struct macb_queue *queue, int budget)
packets, bytes);
queue->tx_tail = tail;
+ if (packets)
+ queue->tx_stall_tail_moved = true;
if (__netif_subqueue_stopped(bp->dev, queue_index) &&
CIRC_CNT(queue->tx_head, queue->tx_tail,
bp->tx_ring_size) <= MACB_TX_WAKEUP_THRESH(bp))
@@ -2036,9 +2038,16 @@ static int macb_tx_poll(struct napi_struct *napi, int budget)
* recovery chain (tx_pending -> txubr_pending -> macb_tx_restart())
* that fires on TCOMP; if TCOMP itself is lost the TX ring stalls
* silently until something else kicks TSTART. This watchdog runs
- * once per second per queue, snapshots tx_tail, and calls
- * macb_tx_restart() if the ring is non-empty and tx_tail has not
- * advanced since the previous tick.
+ * once per second per queue and calls macb_tx_restart() if the ring
+ * is non-empty and tx_tail has not advanced since the previous tick.
+ *
+ * Movement is tracked via the tx_stall_tail_moved boolean rather
+ * than by snapshotting tx_tail. Per-queue ring indices are bounded
+ * (and reused), so a snapshot comparison can false-positive when the
+ * index happens to land on the same value between two ticks under
+ * sustained load. The boolean is set by macb_tx_complete() whenever
+ * tx_tail advances and cleared by this watchdog after each tick;
+ * both writes are under tx_ptr_lock, so no atomic is required.
*
* macb_tx_restart() already checks the hardware's TBQP against the
* driver's head index before re-asserting TSTART, so on a healthy
@@ -2061,11 +2070,9 @@ static void macb_tx_stall_watchdog(struct work_struct *work)
spin_lock_irqsave(&queue->tx_ptr_lock, flags);
cur_tail = queue->tx_tail;
cur_head = queue->tx_head;
- if (cur_head != cur_tail &&
- cur_tail == queue->tx_stall_last_tail)
+ if (cur_head != cur_tail && !queue->tx_stall_tail_moved)
stalled = true;
- else
- queue->tx_stall_last_tail = cur_tail;
+ queue->tx_stall_tail_moved = false;
spin_unlock_irqrestore(&queue->tx_ptr_lock, flags);
if (stalled) {
@@ -3346,7 +3353,7 @@ static int macb_open(struct net_device *dev)
for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
napi_enable(&queue->napi_rx);
napi_enable(&queue->napi_tx);
- queue->tx_stall_last_tail = queue->tx_tail;
+ queue->tx_stall_tail_moved = true;
schedule_delayed_work(&queue->tx_stall_watchdog_work,
msecs_to_jiffies(MACB_TX_STALL_INTERVAL_MS));
}
--
2.54.0
+34
View File
@@ -0,0 +1,34 @@
# Local kernel patches
Drop-in `.patch` files copied into `pkgs/kernel/build/patches/` at build time by the `patches-linux` Makefile target. Filenames sort lexicographically; the kernel build script in `pkgs` applies them in that order with `patch -p1`.
## Current series — `0001`..`0003`-net-macb
Three patches addressing the silent TX stall on Raspberry Pi 5 (BCM2712 + RP1 PCIe south bridge, Cadence GEM via macb). Same series posted to the Linux netdev mailing list as `[RFC PATCH net-next 0/3] net: macb: candidate fixes for silent TX stall on BCM2712/RP1`:
- lore thread: <https://lore.kernel.org/netdev/cover.1777064117.git.lukasz@raczylo.com/T/>
- related issues: [cilium/cilium#43198](https://github.com/cilium/cilium/issues/43198), [Ubuntu launchpad #2133877](https://bugs.launchpad.net/ubuntu/+source/linux-raspi/+bug/2133877)
| Patch | What it does |
|---|---|
| `0001-net-macb-flush-PCIe-posted-write-after-TSTART-doorbell.patch` | Read-back of `NCR` after each `TSTART` write so the doorbell reaches the MAC before the function returns. |
| `0002-net-macb-re-check-ISR-after-IER-re-enable-in-macb_tx_poll.patch` | Reads ISR after IER re-enable in `macb_tx_poll()` — both a direct pending-TCOMP check and a PCIe read barrier for in-flight descriptor TX_USED DMA writes. |
| `0003-net-macb-add-TX-stall-watchdog-to-recover-from-lost-TCOMP.patch` | Per-queue `delayed_work` safety net. Calls the existing `macb_tx_restart()` if `tx_tail` hasn't advanced for ≥ 1 s while the ring is non-empty. |
### vs. the version posted to netdev
The bodies of patches 1 and 2 are byte-identical between this directory and the netdev submission. Patch 3 has a 5-line variation in its `macb.h` hunk: this version is anchored against the `raspberrypi/linux` rpi-6.18.y vendor fork (which carries an extra `bool tx_pending` field on `struct macb_queue`); the netdev-submitted version is anchored against mainline (which doesn't). The semantic change — adding `delayed_work tx_stall_watchdog_work` and `unsigned int tx_stall_last_tail` to `struct macb_queue` — is the same in both.
### License
The patches are derived works of the Linux kernel and inherit its license: **GPL-2.0-only**. This is consistent with the upstream Linux kernel and supersedes the MIT license that covers the rest of this build harness.
### Author
Lukasz Raczylo <lukasz@raczylo.com>.
## Adding more patches
Just drop a `*.patch` file in this directory. `patches-linux` copies anything matching `*.patch` into the kernel build's patch dir, and the build script applies them in sorted order. Use `git format-patch` style or plain unified diffs — both work.
For patches that need to apply against the vendor fork specifically, anchor them on what's there (e.g. the extra `bool tx_pending` field). For patches that should also be acceptable upstream, keep the diff minimal and avoid vendor-only context.