bes2600: Patch C — direct-deliver RX SKBs from sdio_rx_work #3
Reference in New Issue
Block a user
Delete Branch "bes2600/sdio-rx-direct-deliver"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Patch C of the Bug-#5 series. Collapses the
sdio_rx_work → rx_queue → bh_workrelay into a direct call frombes2600_sdio_extract_packetsinto a new helperbes2600_bh_handle_rx_skbinbh.cthat does the per-SKB bookkeeping the bh thread used to do.Pre-patch baseline (4 MB/s, ~5 min, srcversion 1B3B3ED0)
sdio_rx_work34,994 dispatches → 86.4/s = 90.3 per 1000 RX pktsbes2600_bh_workredispatches: 0 (single long-lived work item)What this saves per RX frame
spin_lock(&self->rx_queue_lock)acquires (queue-tail + dequeue)self->irq_handlerno longer called from sdio_rx_work)Contract (cited in bh.c block comment)
wsm_handle_rx:wsm.h:2108,wsm.c:2211,EXPORT_SYMBOLatwsm.c:2463. Process context, sleepable. Caller MUST hold no bes2600 spinlock; SDIO mutex is released insdio_rx_workbeforeextract_packetsis called.bes2600_bh_handle_rx_skbfrees on every path (success and error).wsm_release_tx_bufferreturns 1, the new helper callsbes2600_bh_wakeup()to mirror the in-bhtx=1signaling the oldbh_rx_helperdid.Minimum-diff scope
Following items kept in source for clean bisection (each is dead code after this patch but has zero functional impact):
struct sbus_priv->rx_queue+->rx_queue_lock— defined and initialised, never used. Removed in a follow-up hygiene patch.bes2600_sdio_pipe_read— returns NULL (rx_queue always empty). Same.bes2600_bh_rx_helper— still invoked from bh, returns 0 every wake. Wastes 1 spinlock + skb_dequeue per bh wake; quantified in Phase 7 to decide cleanup priority.Phase 7 plan
Per
notes/patch-c-phase4-plan-2026-05-07.md§4.8 in marfrit/besser:rx_queue->lockacquire rate: 1914/s → 0 (high confidence)_raw_spin_unlock_irqrestoreCPU%: 20% → 12-15% (medium)/lib/modules/$(uname -r)/extra/bes2600.kosrcversion84239431…. Awaiting Markus's reboot button perfeedback_user_pushes_reboot_button./var/tmp/bes2600.patchB.rollback.koif Phase 7 reveals a regression.Test plan
lsmod | grep bes2600,dmesgfor any new WARN/BUGPatch C — collapse the sdio_rx_work → rx_queue → bh_work relay into a direct call from bes2600_sdio_extract_packets into a new helper bes2600_bh_handle_rx_skb that performs the per-SKB bookkeeping previously done inside bes2600_bh_rx_helper after pipe_read. What this saves per RX frame: - two spinlock acquires on self->rx_queue->lock (skb_queue_tail in extract_packets + skb_dequeue in pipe_read) - one bh wait-queue wake-up per IRQ batch (sdio_rx_work no longer calls self->irq_handler) Pre-patch baseline on ohm (4 MB/s sender, ~5 min, srcversion 1B3B3ED0): - 387,532 RX packets, 578 MB, 1.36 MB/s observed receive - sdio_rx_work dispatched 34,994 times = 86.4/s = 90.3 per 1000 RX pkts - sdio_tx_work dispatched 111,770 times = 276.1/s - bes2600_bh_work redispatches: 0 (single long-lived work item, refutes earlier review claim of "9 events per frame") API contract for wsm_handle_rx (cited in bh.c block comment): - declared wsm.h:2108, defined wsm.c:2211, EXPORT_SYMBOL wsm.c:2463 - process context, sleepable - caller MUST hold no bes2600 spinlock; SDIO mutex is released at bes2600_sdio.c before extract_packets is called - SKB ownership: zeroes *skb_p if consumed, caller frees otherwise. bes2600_bh_handle_rx_skb frees on every path (success + error) After patch, bh thread retains responsibility for TX work. TX-confirm packets that release a TX buffer wake the bh thread via bes2600_bh_wakeup() inside bes2600_bh_handle_rx_skb (mirrors the in-bh tx=1 signaling the old bh_rx_helper used to do). Early-boot IRQs before fw_started are still served via the GPIO IRQ handler's fallback self->irq_handler path; that branch is unchanged. Minimum-diff scope: - struct sbus_priv->rx_queue + ->rx_queue_lock are still defined and initialised (allocated but never used after this patch). Removed in a follow-up hygiene patch, not bundled here. - bes2600_sdio_pipe_read still exists; returns NULL after this patch (rx_queue always empty). Removed in same follow-up. - bes2600_bh_rx_helper still exists and still calls pipe_read; it now always returns 0. Wasted 1 spinlock + skb_dequeue per bh wake; quantified in Phase 7 to decide whether worth a follow-up. Awaiting Phase 7 verification on ohm. Per Phase 4 plan §4.5 predicted delta: - rx_queue->lock acquire rate: 1914/s -> 0 (high confidence) - _raw_spin_unlock_irqrestore CPU%: 20% -> 12-15% (medium) - observed RX KB/s @ 4 MB/s: floor lifts toward >= 1 MB/s sustained (medium; rep 3's 75 KB/s death has multiple causes) Phase 7 will N=3 ramp 1 -> 2 -> 4 -> 8 MB/s with identical instrumentation pre/post. See notes/patch-c-phase4-plan-2026-05-07.md in marfrit/besser for the plan; this commit is the Phase 6 deliverable.Phase 7 verdict: FAILED — closing as broken-by-design
Loaded module srcversion
84239431…on ohm, kernel 6.19.10. Boot -1 of session, after a 5-min @ 4 MB/s nc stress run started.Storm fired at T+13s into stress and continued; chip wedged shortly after, host fell off network.
Root cause
wsm_release_tx_buffer()(bh.c:222) does unlocked read-modify-write onhw_priv->hw_bufs_used. Pre-Patch-C, the BH thread was the sole writer (TX-sidewsm_alloc_tx_bufferincrement AND RX-confirm-side decrement both ran in BH context — single-threaded by structural invariant, no lock annotation made it visible). Patch C moved the RX-confirm-side decrement intosdio_rx_workworkqueue context. Now BH thread and sdio_rx_work race on the counter; one decrement underflows below zero, the WARN fires, return is -1, bookkeeping is corrupt, TX wedges.Phase 6 contract analysis miss
The block comment in
bh.c::bes2600_bh_handle_rx_skbcorrectly citedwsm_handle_rx's sleepability and held-lock invariants. It did NOT enumeratehw_bufs_usedas shared-state-mutated-by-the-callee. Lesson saved asfeedback_phase6_contract_threadsafetymemory: when moving a function call between threading contexts, audit every field the function (and every transitive callee) mutates, not just the function signature.Resolution
Closing this PR as superseded. Phase 4 v2 will be a two-step:
hw_bufs_usedandhw_bufs_used_vif[]toatomic_t. Useatomic_fetch_sub_release()inwsm_release_tx_buffer(returns prior value for the>= numInpChBufs - 1predicate). Update ~30 read sites toatomic_read(). Lands first, separately verifiable as no-functional-change.sdio_rx_workdirect-deliver on top of C-prep. Identical structural change, but now the racing counter is safe.Rollback is staged on ohm:
/var/tmp/bes2600.patchB.rollback.ko→/lib/modules/$(uname -r)/extra/bes2600.ko, depmod done. Markus's reboot button to land Patch B again. Broken Patch C module preserved at/var/tmp/bes2600.patchC-broken.kofor forensics.Option B (atomic_t) chosen over Option A (spinlock) and Option C (route confirm back through BH) per inline discussion: atomic_t matches mac80211 driver convention (mt76, iwlwifi, ath11k all use atomic_t for hw-buffer accounting) and preserves the structural win Patch C was trying to deliver.
Pull request closed