Skip to content

8822B narrowband: fix RF18 re-latch edge (full 5/10 MHz on both Jaguar2 variants) + kernel-parity ports#219

Merged
josephnef merged 4 commits into
masterfrom
jaguar2-kernel-parity
Jul 8, 2026
Merged

8822B narrowband: fix RF18 re-latch edge (full 5/10 MHz on both Jaguar2 variants) + kernel-parity ports#219
josephnef merged 4 commits into
masterfrom
jaguar2-kernel-parity

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

What

PR #216 shipped Jaguar2 5/10 MHz narrowband but gated the 8822B — it synced ~0.5% of NB frames on RX and aired nothing on TX, while the OpenHD kernel module did full-rate NB on the same dongle with the same firmware. This PR finds and fixes the root cause, and lands the vendor-parity gaps discovered along the way.

Root cause: RF18 re-latch edge

The RTL8822B RF synthesizer only re-latches its internal channel state on an RF18 (RF 0x18) value edge — a same-value rewrite is a no-op. Devourer applies narrowband as an end-of-bring-up re-clock and rewrote RF18 with the identical value, leaving the RF latched to its pre-re-clock internals: RX deaf, TX invalid, band-independent. The kernel driver never exposes this because its flow always tunes through a channel/band change (an RF18 edge) around any bandwidth switch.

This also retroactively explains the earlier "firmware-autonomous RF 0x82/0xf2 retune" observation — those are RF-internal LUT outputs refreshed by the RF18 edge, not FW action.

Fix (set_channel_bw, 8822B NB branch): write RF18 once with a substitute channel byte, then the real value, on both RF paths.

How it was found — golden-init replay

Every readable-state hypothesis had already been eliminated by A/B against the vendored kernel module (a 116-register MAC+BB flip and removal of the module's only H2C traffic both left kernel NB working). The latch is invisible to state diffing because it keys on the write event, not the value. So:

  1. usbmon-capture the working kernel's complete post-DLFW register write stream (3630 writes incl. the 10 MHz switch).
  2. Replay it verbatim from devourer via a new DEVOURER_REPLAY_WSEQ debug knob (applied at end of Init, overriding devourer's own config) → NB unlocks (39100 hits vs ~10) → causal state is host-writable.
  3. Delta-debug the stream (~25 automated probes) down to the causal RF18 write pair. Edge pair alone: 23300 hits; same-value rewrite alone: 9.

DEVOURER_REPLAY_WSEQ is kept as a permanent lever — it converts any "same registers, different behavior" mystery into a mechanical bisection.

Hardware validation (T3U 8822BU ↔ Jaguar3 8812CU, true frame counts)

  • ch44 10 MHz RX 7700 / TX 12600; ch44 5 MHz RX 6500; 2.4 GHz ch6 10 MHz RX 7700
  • scripted tests/narrowband_cross_rx.sh: 20M=3900 / 10M=4000 / 5M=3900 — full control parity, both widths
  • 20/40/80 smokes + ctest pass; the RF18 edge is narrowband-gated (wide paths byte-unchanged)

Caps ungated: narrowband_ok=true on both Jaguar2 variants, bw_mask keeps 5/10.

Also in this branch (kernel-parity ports found by the same comparison)

These were prerequisites/gap-closures uncovered while diffing devourer's init against the kernel's write stream. Each is independently vendor-parity and measured; together they roughly doubled 8822B 20 MHz RX (~4700 → ~8100 hits/cell) before the NB fix:

  • halrf kfree — efuse power/PA-bias trim: the PA-bias RF LUT write (RF 0x51/0x520x3f via the 0xef[10] window) + per-channel-group TX gain trim (RF 0xde/0x65/0x55). Gotcha ported with it: close the RF LUT window before the 0x51/0x52 reads (the table apply leaves it open, aliasing the reads).
  • eFEM CCA table fix — RFE types 3/5/12/15/16/17/19 (external-FEM boards incl. the bench T3U) need cca_ifem_ccut_rfe; devourer hardcoded the plain iFEM column for every board. Plus the 20 MHz DFS-channel CCA adjust.
  • phydm spur calibration — reset the NBI/CSI notch state on every channel set, and PSD-detect + notch the chip's own clock spur on the fixed spur channels (validated engaging at ch6/2440 MHz).
  • halmac cfg_mac_clk — the MAC µs-tick dividers (0x55c/0x638); chip reset defaults are wrong for the running clock.
  • SoML enable + RxHP band block, and the FW GENERAL_INFO/PHYDM_INFO H2C (send_fw_general_info) — the kernel's post-DLFW FW identity handshake.

Test notes

  • DEVOURER_REPLAY_WSEQ=<file> — golden-init replay (lines: <addr_hex> <width 1|2|4> <val_hex>).
  • tests/narrowband_cross_rx.sh / tests/jaguar2_narrowband_sdr.sh now cover both Jaguar2 variants.

🤖 Generated with Claude Code

josephnef and others added 4 commits July 8, 2026 15:58
…gap narrowed to unreadable state

Systematic devourer-vs-kernel comparison on the 8822BU (per the
incomplete-port pattern of issue #184), driven by hardware A/B against
the vendored OpenHD module (patched rebuilds + write_reg flips):

- Port halmac send_general_info: the GENERAL_INFO + PHYDM_INFO H2C pair
  (32-byte h2c packets over the H2C queue, qsel 0x13, wire format
  byte-verified via usbmon) sent after DLFW — the only H2C traffic the
  kernel driver produces. Hands the FW its rfe/rf-type/cut/antenna/
  package identity; groundwork for FW-side features even though the NB
  arming theory it came from was FALSIFIED (a kernel build with the pair
  patched out keeps full-rate NB).

- Port halmac cfg_mac_clk: MAC clock select 80 MHz + TSF/EDCA
  microsecond-tick dividers (0x55c/0x638 = 80). The chip reset defaults
  are wrong for the running clock (0x64/0x28 read back); the kernel
  rewrites them on every bandwidth config, devourer never did.

- Enable SoML (adaptive ML detection, 0x19a8 = 0xd90a0000 — the kernel's
  steady 8822B state; devourer sat in the SoML-off world the vendor uses
  only for specific RFE types) + the config_phydm_switch_band RxHP
  companion block (0x8cc/0x8d8/0xc04/0xe04 by band and RFE).

Measured: 8822B 20 MHz RX ~2x better (8200 vs ~4700 hits per cell);
8821C narrowband at its best reading yet (10000 hits). 20/40/80 smokes
and ctest pass on both variants.

8822B narrowband itself remains gated: the elimination now proves the
kernel keeps full-rate NB with devourer's ENTIRE differing readable
register state (116-register MAC+BB flip + RF filter values), so the
causal delta is unreadable state (gain LUTs behind write-only ports /
sequencing latches). Next experiment documented in the NB branch
comment: golden-init replay of the kernel's captured post-DLFW write
sequence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Found by diffing the kernel driver's complete init write sequence
(usbmon pcap, 7836 vendor writes) against devourer's own write log
(DEVOURER_LOG_WRITES) — the systematic source-level comparison:

- halrf kfree port: kfree_init reads the PPG physical-efuse trims
  (2G/5G per-band-group TX gain 0x3DB..0x3EE, 2G PA bias 0x3D5/0x3D6)
  and applies the PA-bias RF LUT correction — the RF 0x51/0x52 word
  recomposed and written into RF LUT entries 0/1/3 through the 0xef[10]
  window (write-only LUT state; this is the 0x3f=0x4bb block in the
  kernel's golden init that devourer never produced). kfree_apply
  programs the per-channel-group TX gain trim (RF 0xde/0x65/0x55) on
  every full channel set, phydm_config_kfree parity. Gotcha ported with
  it: RF LUT windows left open by the table apply alias the 0x51/0x52
  reads — close 0xef before reading.

- CCA threshold table: RFE types 3/5/12/15/16/17/19 (external-FEM
  boards — including the bench T3U, rfe_type 3) must use the
  cca_ifem_ccut_rfe table (0x82c=0x75da8010, distinct 0x830/0x838
  columns); devourer hardcoded the plain iFEM C-cut column for every
  board, leaving eFEM boards with mis-set carrier-sense thresholds.
  Plus the vendor's 20 MHz DFS-channel CCA adjust (0x838[7:4]=5 for
  ch52-144).

Regression: 20/40/80 smokes + ctest pass on both variants; 8822B
20 MHz on-air RX holds at the improved level (8100 hits/cell).
8822B narrowband remains gated — these close real parity gaps but the
NB sync deficit persists; the remaining un-ported channel-set component
is the phydm spur-calibration/PSD machinery, and the composite
golden-init replay stays the definitive next experiment (see the NB
branch comment).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… notch)

The last un-ported component of the vendor's channel-set tail
(phydm_spur_calibration_8822b -> phydm_dynamic_spur_det_eliminate,
enabled by CONFIG_8822B_SPUR_CALIBRATION in the CE build):

- Reset half on every channel set: clear the CSI mask bank
  (0x880..0x89c, 0x874[0]) and both NBI notch enables (0x87c[13],
  0xc20[28]/0xe20[28]) — vendor parity for the mask-clear writes the
  kernel produces on every switch.

- Detection half on the chip's own spur channels (2G 5-8/13 @20M,
  3-11 @40m; 5G 153/161/54/118/151/159/58/122/155): PSD-scan the spur
  frequency point +/-1 (3-wire off, per-path 0x808 select, PSD engine
  0x910 BIT(22)|freq_pt, report 0xf44[15:0], IGI toggle to re-enter
  RX), and above the vendor threshold (0x8D) program the NBI notch +
  CSI mask at the spur frequency (2440/2480/5280/5600/5760/5800 MHz
  per channel/BW) through the existing ToneMask appliers, plus the
  dsde CCA tweak (0x82c[19:12]).

Hardware-validated on the T3U at ch6 (a real spur channel): the scan
measures PSD A=634/B=515 >= threshold and engages the notch at
2440 MHz; 20 MHz RX healthy with the notch active (7400 hits/cell);
non-spur ch44 rides the reset-only path (7200 hits/cell); smokes +
ctest pass on both variants. Narrowband bw 5/6 takes the reset-only
path (matches the vendor, whose dsde tables only key 20/40/80) — the
8822B NB sync deficit is unchanged and remains gated; also measured
this session: the deficit is band-independent (2.4 GHz ch6 NB reads
the same ~10 sync as 5 GHz).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… + golden-init replay lever

Root cause of the 8822B 5/10 MHz deadness, isolated by golden-init
replay: capture the working kernel driver's complete post-DLFW register
write stream from usbmon (3630 writes), replay it verbatim from devourer
(new DEVOURER_REPLAY_WSEQ debug knob — applied at the end of Init,
overriding devourer's own config), confirm NB unlocks (39100 hits vs
~10), then delta-debug the stream down to a single causal write pair:

  RF 0x18 = <different channel byte>
  RF 0x18 = <real value>

The 8822B RF synthesizer only re-latches its internal channel state on
an RF18 VALUE EDGE — a same-value rewrite does nothing. The kernel never
hits this because its flow always tunes through a channel/band change
around a bandwidth switch; devourer's end-of-bring-up narrowband
re-clock rewrote RF18 with the identical value, leaving the RF latched
to pre-re-clock internals (RX deaf, TX invalid, band-independent).
Verified minimally: the edge pair alone recovers 23300 hits; the
same-value rewrite alone stays at 9.

Fix: in the 8822B narrowband path, write RF18 once with a substitute
channel byte before the real value (both RF paths).

Hardware validation (T3U 8822BU x Jaguar3 8812CU, true frame counts):
- ch44 10M RX 7700, TX 12600; ch44 5M RX 6500; 2.4G ch6 10M RX 7700
- scripted cross-RX: 20M=3900 / 10M=4000 / 5M=3900 — full control parity
- 20/40/80 smokes + ctest pass; the edge is NB-gated (wide paths
  untouched)

Caps ungated: narrowband_ok=true on both Jaguar2 variants, bw_mask keeps
kBw5|kBw10. Docs/tests updated to current state; DEVOURER_REPLAY_WSEQ
kept as a permanent debugging lever (the tool that found this).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef josephnef merged commit a86a16f into master Jul 8, 2026
15 checks passed
@josephnef josephnef deleted the jaguar2-kernel-parity branch July 8, 2026 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant