After Patch C v3 closed (PR #5 merged, Phase 7 N=3 verified at +73%
throughput vs Patch B baseline), the post-13-min RX-degradation
pattern remains. Reproduces on Patch B, F, and v3 alike — independent
of the relay/race issues v3 addressed. Side-effect that was masked
by the throughput floor while v2's race was the dominant variable.
Research question (locked):
Why does the bes2600 RX path collapse from ~2 MB/s sustained @
fresh-chip uptime to ~180 B/s @ ~28-min uptime, with periodic
wsm_generic_confirm failed for request 0x0007 + ieee80211 phy0:
[SCAN] Scan failed (-22) every 300 s in the intervening window?
Phase 0 protocol:
- long-capture rig armed on ohm at uptime 0 (fresh boot 23:13 CEST)
- ftrace events: workqueue, mac80211, cfg80211, mmc, sdhci, power
- iw event (cfg80211 reason codes), dmesg follow, per-30s netdev
counter snap, 5 stress probes at T+5/10/15/20/25 min
Phase 0 will:
- re-anchor the predecessor data via the long capture (in-session
N=1; re-run if anomalous)
- characterize state transitions (first scan-fail, first throughput
drop) via cfg80211/mac80211 ftrace + iw event correlation
- feed Phase 1 metric formulation
Mechanism candidates (Phase 4 will discriminate):
1. Firmware-side resource exhaustion (per-scan accumulator)
2. NetworkManager scan-fail recovery loop competing with data
3. AP-side rate limiting / fairness probation
4. PSM state machine deadlock (c7 latch stale)
5. SDIO bus retune interaction
6. Power-management busy-event accumulator leak
Out of scope: Patch C2/D/E, higher-rate ramp, reproducing on different
APs. Independent campaign from Patch C closure.
N=3 stress reps on ohm with v3 module (srcversion 371C6606B73AF19299228CA),
3 min @ 4 MB/s each, all within fresh-chip uptime window (200/391/582 s).
| rep | MB/s | sdio_rx_work | bh_work redispatches |
|----:|----:|-:|-:|
| 1 | 2.363 | 0 | 0 |
| 2 | 2.590 | 0 | 0 |
| 3 | 2.102 | 0 | 0 |
N=3 mean: 2.352 MB/s · median 2.363 MB/s · min 2.102 MB/s.
vs Patch B baseline (1.362 MB/s, run-20260507-patchC-preflight): +73%.
vs original Bug #5 floor (75 KB/s rep 3 death): 28× improvement.
Plan §4.5 prediction verified:
- sdio_rx_work dispatch rate: 86.4/s -> 0/s (function deleted)
- bes2600_bh_work redispatches: 0 (preserved invariant)
- observed receive @ 4 MB/s: floor lifts toward >= 1 MB/s (exceeded —
floor is 2.10 MB/s)
Bonus finding: sdio_tx_work dispatch rate dropped from 276.1/s to
0.8/s. The post-tx queue_work(rx_work) call I rewired to
self->irq_handler() was actually firing more often than predicted;
folding it into bh-wake-up cuts ~99.7% of the workqueue dispatches.
No WARN/BUG/oops on any rep — the v2 race that wedged Patch C v1
within 13 s under stress did NOT reproduce on v3.
Phase 8 lesson distilled as feedback_mine_upstream_ancestor memory:
when patching a fork-from-upstream driver, mine the ancestor's
fix history BEFORE writing fixes from scratch. cw1200 mining
drove the structural pivot from v2's atomic_t wrapper to v3's
no-relay architecture. Without the mine, we'd have shipped v2.
Phase 7 receipts checklist met (N=3, fresh-chip, identical
instrumentation, predicted delta verified, no-WARN under stress).
Supersedes v2 (PR #10). cw1200 mining (~/src/linux-rockchip, 228
cw1200 commits) confirmed: upstream cw1200 has no sdio_rx_work
workqueue at all. IRQ handler bumps bh_rx + wakes bh_wq; bh thread
does the SDIO read inline via cw1200_bh_rx_helper. Single thread =
single writer for hw_bufs_used = no race by construction. Same int
hw_bufs_used as bes2600, never atomic_t'd in 16 years upstream.
v3 brings bes2600 into that shape:
- delete sdio_rx_work, self->rx_work, self->rx_queue,
self->rx_queue_lock, bes2600_sdio_pipe_read
- GPIO IRQ handler calls self->irq_handler directly (matches
cw1200_sdio_irq_handler shape)
- bes2600_bh_rx_helper's BES_SDIO_RX_MULTIPLE_ENABLE branch
replaced with inline SDIO read + extract_packets + per-skb
delivery via new bes2600_bh_handle_rx_skb()
- GPIO wake-flag bracketing moves into bh thread
§5 shared-state delta table (the v2 lesson, applied): zero fields
require new locking. hw_bufs_used / hw_bufs_used_vif / wsm_tx_pending
all stay single-writer-from-bh. v2's atomic_t prep is mooted.
§6 risk #6 is the open question for reviewer: bes2600's
__bes2600_irq_enable(1) call is commented out in the BH-loop done:
label with an asm volatile("nop") in its place. Either SDIO IRQ
is auto-managed (so commenting out is fine) or the current code
relies on sdio_rx_work being queued regardless of driver-side IRQ
flag. Block Phase 6 on this audit.
Patch F (PR #4 merged) is the new baseline. v3 will branch off
F-merged cleanups. Phase 7 N=3 stress ramp uses wired enu1 path
(192.168.88.80) for wedge-resilient telemetry collection.
Question - you said earlier, the driver is a search-and-replace CW12xx driver. Did the CW12xx evolve since this "fork"? If so, are there lessons that can be learned from the CW12xx driver in it's nowadays state?
Reviewed-on: #10
Phase 7 of Patch C (PR #9 → bes2600-dkms PR #3 → boot -1 of ohm
20:18:10) failed with a thread-safety race: wsm_release_tx_buffer's
unlocked R-M-W on hw_bufs_used races against wsm_alloc_tx_buffer in
the bh thread when Patch C moved the RX-confirm decrement into
sdio_rx_work. WARN storm at +13s under stress, chip wedges, host
off-network.
Phase 6 contract analysis cited wsm_handle_rx's sleepability and
held-lock invariants but stopped at the function signature. Did not
enumerate hw_bufs_used as shared state mutated by the callee. Lesson
saved as feedback_phase6_contract_threadsafety memory.
Phase 4 v2 designs around that gap. Two-step:
1. Patch C-prep: NFC refactor — convert hw_bufs_used,
hw_bufs_used_vif[], wsm_tx_pending[] from int / int[] to atomic_t /
atomic_t[]. Use atomic_fetch_sub_release in wsm_release_tx_buffer
(returns prior value for the >= numInpChBufs - 1 predicate).
Mechanical atomic_read swap at ~58 read sites. Lands first;
Phase 7 should show zero delta from baseline.
2. Patch C v2: re-apply the sdio_rx_work direct-deliver on top of
C-prep. Identical structural change to the closed PR #3, but now
the racing counter is safe. Contract block in
bes2600_bh_handle_rx_skb expanded to include the shared-state
delta table.
Plan §2 is the shared-state delta table — every field
bes2600_bh_handle_rx_skb mutates directly or transitively, with
current protection and required action. 3 fields need atomic_t,
the rest are already concurrency-safe or stay single-writer.
Plan §6 lists 6 risks including memory-ordering choices, the
inc/dec_pending_count timer-decision race, and the new wired-rig
fallback (enu1 192.168.88.80) that survives bes2600 wedges so Phase 7
can capture dmesg / ftrace from a wedged ohm without reboot.
PR superseded #3 closed with full verdict comment. Phase B rolled
back on ohm at /lib/modules/.../extra/bes2600.ko. Markus's reboot
button to land Patch B again before C-prep work begins.
Per merged PR #8 inline review: items 1 and 2 split, sequential. Patch C
is item-1-only (collapse the sdio_rx_work → rx_queue → bh_work
indirection). Patch C2 (ieee80211_rx_list batch delivery) is split out
and gated on Task #19 kerneldoc contract verification.
Approach choice: Option A (sdio_rx_work delivers directly into
wsm_handle_rx, removing rx_queue and its two synchronization points per
frame) over Option B (subsume into bh thread). Option A has a smaller
diff and clearer bisection story; the residual per-IRQ workqueue
dispatch is preserved as a measurable Phase 7 data point that motivates
or doesn't motivate a follow-on Option-B patch.
Predicted delta in Phase 3 units, with confidence levels stated
explicitly. §4.6 lists 6 risks, of which 2 require Phase 6 contract
citations (wsm_handle_rx callability from sdio_wq context;
wsm_release_tx_buffer's bh-wake invariant). §4.8 mandates a stress
ramp in Phase 7, not a steady cap, per feedback_phase7_stress_ramp.
Symptom-shaped findings (asm nop, commented-out IRQ re-enable, BUG_ON
in hot path) explicitly deferred to Task #24 per
feedback_dont_patch_downstream_artifacts.
Awaiting Phase 5 second-model review on DokuWiki.
Independent code-review writeup (Opus 4.7) against Sonnet's review of the
same tree. Concurs with Sonnet on items 1+2 (RX relay, batch delivery)
and items 4+5 (ba_lock atomics, ps_state_lock skip-when-pm_unsupported);
pushes back on the "9 workqueue events per frame" quantification and
records BES_SDIO_OPTIMIZED_LEN as hard-baked rather than togglable.
New findings: cw12xx-not-bes2600 genealogy still active in source, ~700
lines of #if 0 fossil in bh.c, Allwinner-specific sw_mci_check_r1_ready
in the SDIO bus path, asm volatile("nop") placeholder where IRQ re-enable
used to live, BUG_ON in steady-state hot path, vendor-SDK Makefile shape
that pollutes every diff, 8 EXPORT_SYMBOLs from a nominally-single-binary
module.
Recommends ordering: Patch C (1+2 wrapped) high-risk-first, Patches D+E
as small individually-verifiable cleanups, explicit don't-touch list.
Notes ieee80211_rx_list contract verification (task #19) blocks Patch C.
Sonnet (general-purpose subagent, model=sonnet) reviewed
~/src/besser/bes2600-dkms-mobian/bes2600/ given the Phase 0 measurement
context. Output: 8-item ranked restructuring map, file:line cited.
Headline:
- Item 1: collapse sdio_rx_work relay into BH loop (~5x workqueue
dispatch reduction, medium effort)
- Item 2: batch deliver via ieee80211_rx_list (small effort, removes
per-frame softirq)
- Items 1 + 2 together collapse "9 workqueue events per delivered
frame" to ~1.
Items 3-5 clean up next-layer overhead (TX-side queue_work,
per-frame ba_lock, ps_state_lock under known-dead PSM). Items 6-8
are follow-ons to be re-measured after 1-3 land.
Phase 4 plan locking the lead candidate(s) follows in a separate PR.
Follow-up ftrace measurement (post-reboot, 3-min 4MB/s capture):
- workqueue_execute_start: 5,643/sec ← dominates
- wsm_cmd_send: only 13/sec (host-to-chip command path NOT the hotspot)
- lock contention: 50/sec (modest)
The throughput floor is set by per-SDIO-transaction workqueue dispatch
overhead. Surgical patches B5-1/B5-2/B5-3 from the prior Phase 4 plan
all targeted the wrong layer; deferring those until an architectural
restructuring map is produced.
Promoting the Sonnet architect review from "backlog" to
"blocking on Bug #5" — the next step is a restructuring assessment,
not another patch.
Phase 0 anchored at N=3 reps (10min @ 4MB/s pv-cap on 2.4GHz):
- rep1+2: ~700 KB/s sustained (10% of link capacity)
- rep3: link death at ~9 min in (passive mode, beacon-loss cascade)
Hot symbol identified: _raw_spin_unlock_irqrestore at ~20% CPU in both
healthy and failed reps, callstack process_one_work → wsm_configuration
→ wsm_cmd_send → bes2600_bh.isra.0 → spin-unlock.
Phase 1 metric locked: ≥2 MB/s sustained throughput, <10% CPU in lock-
cycling, no link death under 30 min continuous load.
Three Phase 4 candidates drafted (B5-1: shrink wsm_cmd_send lock scope;
B5-2: coalesce vif_list_lock in BH dispatcher; B5-3: SPSC ringbuffer for
WSM commands). Locking pending review.
Phase 7 verification of cleanups + Patch A + Patch B (srcversion
1B3B3ED0) on ohm 2026-05-07 12:48 → 15:13 CEST under netcat load
ramped 1 MB/s → 4 MB/s on 2.4GHz newton.
Patch A: predicted delta CONFIRMED at N=2 reproductions.
- 13:47:56 storm → 1 s reassoc, no AP-deauth-6 escalation
- 13:49:26 storm → 1 s reassoc, no AP-deauth-6 escalation
Patch B: installed, untriggered. 2 api_connection_loss events spaced
91 s apart, never tripping the 3-in-60s threshold. No false positives,
no spurious bus_resets. Recovery delta unobserved (no harm done).
Trigger C: 17-frame AP-deauth-6 cluster at 12:53 with no patch hooks
firing — bes2600 TX-side glitch suspect. Recovery via mac80211 reauth
in ~4 s. New backlog item.
Bug #5 documented separately (RX path degrades under throughput
pressure; possible root of the original Phase-0 YouTube frame drops).
Observed 2026-05-07: bumping the netcat sender from 1 MB/s to 4 MB/s
DECREASED ohm's observed RX rate (1015 KB/s → 563 KB/s) and degraded
the link (signal -57 → -67 dBm, MCS 4 → 3). Chip can't sustain near-
link-rate RX even though theoretical capacity is ~8 MB/s.
Hypothesis: driver/firmware lock contention or busy-wait on the RX
SDIO path. Plausibly explains the original Phase-0 observation that
YouTube DASH chunks drop ~10 frames per chunk fetch — chunk fetch is
a brief near-line-rate burst that this bug would be triggered by.
Drafts Patch A (decrypt-storm fast-recover, Trigger B) at txrx.c:1696
with sliding-window threshold + ieee80211_connection_loss reassoc.
Patch B (beacon-loss / Trigger A) parked behind one more diagnostic
rep with 10s snap-loop cadence on the beacon-loss counter.
Folds reviewer feedback from PR #3 + the new Trigger-A finding
(post-resume P1 = api_connection_loss-driven, two reps captured today
at 17:23 and 18:03) into a revised Phase 1 metric counting three
event classes.
Pending Phase 5 second-model review of the plan before Phase 6
implementation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Suppresses the WSM-scan WARN cascade in bes2600_scan_work +
wsm_handle_rx by (a) pre-checking BT A2DP coex state and
backing off after N consecutive firmware rejections, and
(b) demoting the WARN() in wsm_generic_confirm to bes_devel
(the upstream-caller's wiphy_warn with request-id is kept).
Deployed + verified on ohm (srcversion A5C8146A…): WARN splat
count 0 per 1 min boot (pre-patch: 32 per 25 min). WiFi
immediately roamed from ch1 signal-47 to ch48 5240 MHz newton
because scan completions now land cleanly with mac80211.
Other counters still 0. Net: +83/-2 lines across 3 files.
Standalone single-patch series in both Mobian-paths and
drivers/staging/bes2600/ paths variants, checkpatch --strict
clean.
Salsa-CI's lintian stage flagged the pre-existing boilerplate
paragraph in debian/copyright as 'old-fsf-address-in-copyright-
file' when cleanups first hit CI. Replace the '51 Franklin
Street, Fifth Floor, Boston, MA 02110-1301 USA' literal with a
'https://www.gnu.org/licenses/' reference; the
/usr/share/common-licenses/LGPL-2.1 reference a few lines later
is unchanged, so license-text location is still covered.
Pushed to salsa and gitea as commit f31c57a on branch cleanups
and as standalone topic debian/copyright-fsf-address on gitea.
Demote 'wait pm ind timeout' from bes_err() to bes_devel() in
bes2600_pwr_enter_lp_mode(). The cascade this used to warn about
is already suppressed by c2 (pm-gate-on-handshake); the remaining
log line is benign steady-state noise (3-9 events per 10-min
uptime on PineTab2). Deployed + verified on ohm (srcversion
ED89A26…): err-priority count 0, WiFi associated, no
regression. 1-line patch.
Completes the filp_open/kernel_read/kernel_write removal pass
across the driver. Deletes bes_fw.c DATA_DUMP_OBSERVE blocks
(4 #ifdefs gated on a commented-out #define, dead by default;
would fail to build on modern kernels due to removed
get_fs/set_fs) and main.c's orphan access_file() helper
(no callers in-tree, also relies on get_fs/set_fs).
With c1.2 + c1.3 + c1.4 combined: zero filp_open /
kernel_read / kernel_write / vfs_read / vfs_write references
anywhere in the driver -- precondition for a linux-wireless
RFC for drivers/staging/bes2600/ unlocked.
Deployed + verified on ohm (srcversion 12BAFB9C…): WiFi
associated, no KFENCE / sdio_tx_work / RX failure / PS Mode
Error / factory cali data get failed. Net: -69 lines.
Follow-up to staging-prep-series + factory-drop-kernel-write:
remove the BES2600_WRITE_DPD_TO_FILE-gated filp_open / kernel_read /
kernel_write DPD file paths from bes_chardev.c, including the three
functions, the no_dpd module_param, and the associated Makefile
knob + three PATH macros (BES2600_DPD_PATH, BES2600_DEFAULT_DPD_PATH,
BES2600_DPD_GOLDEN_PATH).
Deployed + verified on ohm (srcversion C1283A24…): WiFi associated,
KFENCE / sdio_tx_work splat / RX failure / PS Mode Error / factory
cali data get failed = 0, no DPD regression. Net: -155 lines.
Standalone single-patch series in both Mobian-paths and
drivers/staging/bes2600/ paths variants, checkpatch --strict clean.
Follow-up to staging-prep-series: remove kernel_write() +
filp_open(O_CREAT) from factory_section_write_file() in
bes2600_factory.c. Serialised blob now lives only in-memory +
file_buffer for the session; no longer persists to
/lib/firmware/bes2600/bes2600_factory.txt across reboots.
Deployed + verified on ohm (srcversion BD0D1AEC...): WiFi
associated, KFENCE / sdio_tx_work splat / RX failure / PS Mode
Error / factory cali data get failed all 0. Standalone single-
patch series in both Mobian-paths and drivers/staging/bes2600/
paths variants, checkpatch --strict clean.
Fold bes2600/tx-sdio-dma-oob into the linear series as 7/7.
Re-cover-letter and update testing matrix. Update UPSTREAM.md table
and submission route to list the 7th branch. The PS Mode Error
residual note is removed from the known-limitations section -- it
stopped recurring after 7/7 deployed.
Both staging-prep-series/ (Mobian paths) and staging-prep-series-
danctnix/ (drivers/staging/bes2600/ paths) variants regenerated;
all 14 patch files checkpatch --strict clean (0/0/0).
Net: +117 / -550 lines across 9 files.
Diagnosed via KFENCE splat on ohm (6.19.10-danctnix1, PineTab2):
sdio_tx_work passes block-size-aligned length to dma_map_sg while
the underlying skb linear head is only tx_buffer->len long. Fix:
driver-owned DMA bounce page, memcpy + zero-pad per TX buffer.
Both Mobian paths and drivers/staging/bes2600/ paths variants,
checkpatch-clean.
The danctnix-series cover letter had an invalid 41-hex-char
placeholder SHA on its 'From' header line (40 zeros with an
f1d22ab0 prefix) -- a relic of manual generation. Replaced with
the valid 40-char SHA already used by the Mobian-series cover
letter. The value is cosmetic (cover letters aren't real commits)
but an invalid SHA will confuse 'git am'.
Em-dashes (U+2014), en-dashes (U+2013), and arrow glyphs (U+2192,
U+2194) leaked into commit messages and the cover letter. Linux
kernel patches are expected to be plain ASCII. Substitute:
-- for em-dash
- for en-dash
-> for rightward arrow
<-> for left-right arrow
Applied to both staging-prep-series/ (Mobian paths) and
staging-prep-series-danctnix/ (drivers/staging/bes2600/ paths).
checkpatch.pl --no-tree --strict: 0/0/0 on all 12 patches.
Danctnix is the de-facto upstream for PineTab2 Arch Linux ARM images and
carries the bes2600 driver in-tree at drivers/staging/bes2600/ in
codeberg.org/DanctNIX/linux-pinetab2 (tag v6.19.10-danctnix1).
Same 6 commits as the Mobian series (staging-prep-series/), regenerated
with paths rooted at drivers/staging/bes2600/ so 'git am' applies
cleanly onto a fresh v6.19.10-danctnix1 clone with no path mangling.
Per-patch content is byte-identical to the Mobian series; the
commit-message bodies are preserved. checkpatch.pl --no-tree --strict
passes for all six.
UPSTREAM.md extended with a 'Near-term alt: danctnix linux-pinetab2
(codeberg)' section covering the submission route (codeberg fork +
PR, or Danct12-direct).
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
UPSTREAM.md captures the submission-ready state:
- patch-by-patch intent and testing status
- CW1200 lineage narrative
- branch-to-patch mapping on marfrit/bes2600-dkms
- submission routes (near-term Mobian MR, longer-term linux-wireless
RFC for drivers/staging/)
- known limitations left for follow-up
- recommended CC list for a future linux-wireless RFC
patches/staging-prep-series/ contains the linear 6-patch series with
cover letter, generated from the bes2600/staging-prep-series branch on
marfrit/bes2600-dkms (cherry-picked off mobian in dependency order).
All patches checkpatch.pl --no-tree --strict clean.
Branch mapping:
1/6 bes2600/factory-request-firmware (c1)
2/6 bes2600/factory-no-efuse-flag (c5, stacked on c1)
3/6 bes2600/factory-thread-dev (c1.1, stacked on c1+c5)
4/6 bes2600/pm-gate-on-handshake (c2, standalone)
5/6 bes2600/remove-chardev-user-interface (c3, standalone)
6/6 bes2600/enable-testmode (c4, standalone)
Total: 79 insertions, 549 deletions. Net -470 lines.
Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>