From 14fb9bbbd38efacbcd76f692b4a4ef50369645cd Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Sun, 19 Jul 2026 21:24:55 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20split=20CLAUDE.md=20per-HAL=20=E2=80=94?= =?UTF-8?q?=20cross-cutting=20root,=20per-generation=20files=20load=20on-d?= =?UTF-8?q?emand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root CLAUDE.md (40.8k chars) tripped the Claude Code 40k warning and grows with nearly every feature PR. Cross-cutting operational knowledge stays in the root (~30k); per-generation register/descriptor/mechanism facts move to src/{jaguar1,jaguar2,jaguar3,kestrel}/CLAUDE.md, which Claude Code loads on-demand when reading files in that subtree (@imports load eagerly, so a nested split is the only form that reduces startup context). No fact dropped: hex-register + backticked-token audit of old vs new corpus is clean (deliberate drops: one issue-# tag, the SipHash formula that lives in docs/fhss.md). Cross-references repointed (CMakeLists.txt, docs/8852c-quirks.md, RtlJaguar3Device.h); the stale rail-sag CLAUDE.md tag in tests/bench_onair.py dropped. Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 478 ++++++++++++--------------------- CMakeLists.txt | 4 +- docs/8852c-quirks.md | 6 +- src/jaguar1/CLAUDE.md | 49 ++++ src/jaguar2/CLAUDE.md | 41 +++ src/jaguar3/CLAUDE.md | 42 +++ src/jaguar3/RtlJaguar3Device.h | 2 +- src/kestrel/CLAUDE.md | 116 ++++++++ tests/bench_onair.py | 2 +- 9 files changed, 423 insertions(+), 317 deletions(-) create mode 100644 src/jaguar1/CLAUDE.md create mode 100644 src/jaguar2/CLAUDE.md create mode 100644 src/jaguar3/CLAUDE.md create mode 100644 src/kestrel/CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md index e58c02d4..19e82f10 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,145 +1,53 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to Claude Code (claude.ai/code) when working with +code in this repository. Per-generation deep facts (registers, descriptors, +per-chip mechanisms) live in `src/{jaguar1,jaguar2,jaguar3,kestrel}/CLAUDE.md`, +auto-loaded when working in that subtree — add new per-generation facts there, +cross-cutting facts here. ## What this is Userspace re-implementation of Realtek's USB Wi-Fi drivers (11ac RTL88xx and 11ax RTL8852 families) — speaks to the chip directly via libusb instead of a -kernel module. Static library `devourer` -(CMake target) + example executables under `examples/` (`rxdemo` and `txdemo` -are the canonical RX/TX demos). Used by the OpenIPC project for long-range -video links. +kernel module. Static library `devourer` (CMake target) + example executables +under `examples/` (`rxdemo` and `txdemo` are the canonical RX/TX demos). Used +by the OpenIPC project for long-range video links. Four chip generations, each behind its own self-contained HAL, dispatched at -construction from the `SYS_CFG2` chip-id (Kestrel: PID-first — see -**Architecture**): - -- **Jaguar1** (`src/jaguar1/`): RTL8812AU (2T2R reference), RTL8811AU (1T1R cut, - rides the 8812 path), RTL8814AU (4T4R RF / 3-SS baseband — host-pushed TX - requires the on-chip 3081 MCU booted during FW download; a failed FW-boot - poll means dead TX while RX still works), RTL8821AU (1T1R + BT). **5/10 MHz - narrowband on the 8812AU/8811AU and the 8814AU**: both share the Jaguar2 - `0x8ac` ADC/DAC clock-divider block, so the re-clock trick works even though - the vendor never wired it — TX+RX bench-characterized. The two dies use - different field encodings of the same register (the 8812A divides via - `[9:8]`/`[21:20]`, the 8814A via the 8822B's `[9:8]+[16]`/`[21:20]+[28]`). - The 8821A is excluded (dividing its DAC clock starves the 1T1R TX DMA/USB - path) and falls back to 20 MHz. See `docs/narrowband.md`. +construction from the `SYS_CFG2` chip-id (Kestrel: PID-first): + +- **Jaguar1** (`src/jaguar1/`): RTL8812AU (2T2R reference), RTL8811AU (1T1R + cut, rides the 8812 path), RTL8814AU (4T4R RF / 3-SS baseband — a failed + FW-boot poll of its 3081 MCU means dead TX while RX still works), RTL8821AU + (1T1R + BT). 5/10 MHz narrowband on the 8812AU/8811AU/8814AU + (bench-characterized TX+RX); the 8821A is excluded and falls back to + 20 MHz. See `docs/narrowband.md`. - **Jaguar2** (`src/jaguar2/`): RTL8822BU / RTL8812BU (chip-id `0x0a`) and - RTL8811CU / RTL8821CU (chip 8821C, chip-id `0x09`). A hybrid: HalMAC FW - download / MAC init / power sequencing like Jaguar3, phydm `check_positive` - register tables like Jaguar1 (shared `PhyTableLoader`). RX + TX on 2.4/5 GHz - at 20/40/80 MHz plus **5/10 MHz narrowband on both variants** (a baseband - ADC/DAC re-clock packed into BB `0x8ac`; the RF stays in 20 MHz mode; - applied as an end-of-bring-up retune. The 8822B RF synth only re-latches - on an RF18 *value edge*, so the narrowband path writes RF18 twice — - same-value rewrites do nothing; see the `set_channel_bw` NB branch). - 5 MHz at 5 GHz is CFO-limited: subcarrier spacing shrinks 4× and a - far-offset TX/RX crystal pair syncs bimodally per bring-up — at 2.4 GHz - the same pair is stable (`tests/narrowband_cross_rx.sh` header). Per-rate - bandwidth-aware efuse TX power clamped to generated `txpwr_lmt` tables - (narrowband folds to the 20 MHz column). Golden-init replay - (`DEVOURER_REPLAY_WSEQ`, a captured kernel write stream applied verbatim - at the end of Init) is the debugging lever that found the RF18-edge bug. -- **Jaguar3** (`src/jaguar3/`): rtl8822c (RTL8812CU/8822CU, chip-id `0x13`) and - rtl8822e (RTL8812EU/8822EU, chip-id `0x17`). **5/10 MHz narrowband** (its - re-clock lives in the `0x9b0`/`0x9b4` dividers, vs the `0x8ac` block the - Jaguar1/2 chips share), 80 MHz (incl. - a 40-in-80 frame via TX-descriptor DATA_SC), and halrf calibration - (DACK/IQK/TXGAPK/thermal tracking). - Sustained 5 GHz TX needs the **coex runtime thread** - (`RtlJaguar3Device::coex_runtime_loop`, started in `InitWrite`) — without its - ~2 s WiFi-only coex re-apply + FW heartbeats, the combo chip's coex firmware - silences the antenna. The rtl8822e's hardware-bisected constraints (DPDT/ - pin-mux front end, single-path 1SS TX, spur channels, LCK, the 2.4 GHz TX - kernel-parity limitation) live in `docs/8822e-quirks.md`. - + RTL8811CU / RTL8821CU (chip 8821C, chip-id `0x09`). A HalMAC + phydm + hybrid. RX + TX on 2.4/5 GHz at 20/40/80 MHz plus 5/10 MHz narrowband on + both variants; 5 MHz at 5 GHz is CFO-limited. +- **Jaguar3** (`src/jaguar3/`): rtl8822c (RTL8812CU/8822CU, chip-id `0x13`) + and rtl8822e (RTL8812EU/8822EU, chip-id `0x17`). 5/10 MHz narrowband, + 80 MHz (incl. a 40-in-80 frame), halrf calibration. Sustained 5 GHz TX + needs the coex runtime thread (started in `InitWrite`) — without it the + combo chip's coex firmware silences the antenna. 8822e constraints: + `docs/8822e-quirks.md`. - **Kestrel** (`src/kestrel/`): the Wi-Fi 6 / 802.11ax generation — RTL8852BU/8832BU (variant C8852B) and RTL8852CU/8832CU (C8852C), one HAL - serving both dies on all platforms (MSVC included). Two-plane architecture: - the **mac_ax plane** (power-on / FWDL / efuse / MAC TRX / H2C-C2H / USB / - descriptors) is hand-ported C++ from Realtek's "G6 phl" vendor trees - (`reference/rtl8852bu`, `reference/rtl8852cu` — none of the 11ac - loaders/parsers apply), while the **BB/RF plane is Realtek's halbb/halrf C - compiled VERBATIM** for both chips: one chip-agnostic core + per-chip - backends under `hal/halbb/g6/` + `hal/halrf/g6/` (`vendor/` = untouched - vendor files; `shim/` + `hal_headers_le.h` = the minimal PHL surface they - compile against; `kestrel_halbb_glue.c` / `kestrel_halrf_glue.c` = the C - entry points devourer drives; `tools/vendor_hal{bb,rf}_*.sh` re-vendor). - The vendored loaders own the BB/RF/gain tables (`halbb_init_reg`, - `halrf_config_radio` — radio pages reach the fw via the bridge `send_h2c` - into the mac_ax H2C encoder), the per-channel BB config - (`halbb_ctrl_bw_ch`), the RF tune (`halrf_ctl_ch/bw` on the B, - `halrf_ctl_band_ch_bw_8852c` on the C), and the cals: DACK + RX-DCK both - dies (wrapped in an RF 0x0/0x1/0x5 save/restore on the B — the vendor - relies on later TRX flow to rewrite them), IQK on the 8852C only. The RFK - prologue (NCTL microcode / a-die SI reset / LCK / RCK / efuse trim + TSSI - DE) runs at `phy_bb_rf_init` in the vendor's `halrf_dm_init` position — - that position is load-bearing: run after the tune, the SI reset deafens the - radio both ways. **Evidence-gated cals**: TSSI/DPK (both dies, via - `support_ability`) and IQK on the 8852B degrade TX under the fixed-dBm - power model — gates fall with a TSSI-referenced power model. - Dispatched **PID-first** (`kestrel/KestrelUsbIds.h`), not from the 0x00FC - byte: on AX silicon that register is R_AX_SYS_CHIPINFO (die-id 0x51/0x52; - the 8852A's 0x50 collides with the 8822B cold transient). Cold-boot facts - (mac_ax plane, both dies): a USB chip from real cold reads - WLMAC_PWR_STE=MAC_ON (its AFSM auto-powers to enumerate) and power_on must - force the MAC off first (`pwr.c` prologue) or the bootrom never raises - H2C_PATH_RDY; `B_AX_ENBT` must be set before any LTE-space access (arms the - LTE indirect interface) and the GNT arbitration is SW-forced to WiFi - (`mac_cfg_gnt_8852b` stance) — a kernel driver pre-initializing the chip - masks all of this, so blacklist rtw89 when testing. - On-air-validated: monitor RX (both dies, 2.4/5 GHz; the RX front-end needs - the halbb per-band LNA/TIA gain-error cache, without which 5 GHz is deaf), - TX (mgmt injection — the OpenIPC video path via `streamtx`; - legacy/HT/VHT/HE rates), channel/BW **5/10/20/40/80 MHz on both dies + - 160 MHz on the 8852C** (the 8852B die has no 160 MHz — vendor bw_sup; - caps report accordingly). 40 MHz tunes to the block center (primary ±2); - 80/160 MHz derive center/pri_ch from the channel plan (160 = an 8-wide - block, center = block_start+14). **6 GHz TX tops out at 80 MHz**: the 6G - 160 MHz TX does not radiate on the C8852C (the RF synth locks and RX-160 - works, but the 6G+160 TX-enable path is un-ported — B210-confirmed 0% duty - vs 45% at 6G-80 / 40% at 5G-160; a MAC TXAGC-max / RF-TX-path gap, not a - chip limit — the vendor drives it). 5/10 MHz narrowband is the BB "small - BW" field with the RF left in 20 MHz mode (no ADC re-clock, unlike Jaguar). - RX bulk-IN delivery requires the USB RXAGG engine enabled - (`B_AX_RXAGG_EN`). TX power is a fixed BB dBm (`halbb_set_txpwr_dbm`, - default 20 dBm, `DEVOURER_TX_PWR` override) with a runtime - `SetTxPowerOffsetQdb` lever and per-packet radiotap `DBM_TX_POWER` - (fixed-dBm rewrite between frames — see **Per-packet TX power** under - Configuration); `ReadTsf` reads the per-port MAC TSF; - `StartBeacon` drives the AX HW beacon engine. **HE ER SU + DCM extended - range** (both dies): per-packet via radiotap-HE FORMAT=EXT_SU or - `DEVOURER_TX_RATE=.../ER[/DCM]`; RX classifies the format in - `RxAtrib.ppdu_type` (7=HE_SU, 8=HE_ERSU) — `docs/he-extended-range.md`. - Async packet-C2H (bulk-IN - rpkt_type=10) delivery works — routed by `handle_c2h` on the C2H - class/func — so the #236 C2H surface (TWT/F2P reports) is reachable. TX - airs with the CMAC EDCCA/CCA gate disabled (`sch_tx_en`, TX path only) — - the intended injection/monitor-link mode. Not working: the fw's USR_TX_RPT - TX-egress-timestamp C2H (gated on a full BSS association, not just the - registered NO_LINK role). - Almost every 8852C divergence from the 8852B is a **`_V1` register-bank** - move (USB/HFC/RXAGG/HCI at 0x5xxx/0x1700/0x6000/0x7880 vs the 8852B - 0x1xxx/0x8Axx/0x8900/0x8380) plus a different descriptor: FWDL/H2C use a - 16-byte `rxd_short_t` (not the 24-byte WD body), data/mgmt TX uses the - 32-byte `wd_body_t_v1` (not 24), and the RX-descriptor drv_info unit is - 16 bytes (not 8 — `FrameParserKestrel.h`; with the B's unit the C - "receives" frames with correct lengths but unreadable bodies, i.e. looks - deaf to any SA-matching harness). The CBV-cut die loads the `u2_nic` fw - image (CAV→u1). Two 8852C-specific TX pieces the 8852B path lacks: the BB - IFFT→TX-chain routing (`halbb_ctrl_tx_path_tmac_8852c`, the 0xD800..0xD82C - "path-com" block the 8852B replaces with its per-STA CMAC antenna model), - and the V1 descriptor's rate word — `USERATE_SEL`/BW/GI/DATARATE live in - wd_body dword7, not wd_info dword0 as on the 8852B (mis-placing it left - `f_rate=0` so the scheduler stalled and the bulk-OUT NAKed). - The capstone is 11ax trigger-based UL + TWT (issue #236 — the v1.19 vendor - fwcmd surface exposes TWT-OFDMA + F2P trigger H2Cs that mainline rtw89 - lacks). The 8852A-family (RTL8832AU) is deliberately excluded (frozen 2021 - v1.15 vendor drop only). The C8852C's behavioural quirks beyond the `_V1` - bank moves (the frame-free NHM absolute floor is 2.4 GHz-only — the firmware - DIG biases the 5 GHz idle-noise measurement) live in `docs/8852c-quirks.md`. + serving both dies on all platforms (MSVC included). Two planes: hand-ported + mac_ax C++ + Realtek's halbb/halrf C compiled verbatim + (`hal/hal{bb,rf}/g6/`). Dispatched PID-first (`kestrel/KestrelUsbIds.h`) — + the 0x00FC byte is not a chip-id on AX silicon. Blacklist rtw89 when + testing: a kernel driver pre-initializing the chip masks the cold-boot + path. On-air-validated: monitor RX (both dies, 2.4/5 GHz), TX injection + (legacy/HT/VHT/HE + HE ER SU/DCM extended range — + `docs/he-extended-range.md`), 5/10/20/40/80 MHz on both dies + 160 MHz on + the 8852C (the B die has no 160 MHz); 6 GHz TX tops out at 80 MHz (the + 6G+160 TX-enable path is un-ported). TX power is a fixed BB dBm + (`DEVOURER_TX_PWR`, whole dBm on this family). The capstone is 11ax + trigger-based UL + TWT (issue #236); the 8852A-family (RTL8832AU) is + deliberately excluded. 8852C behavioural quirks: `docs/8852c-quirks.md`. Naming traps: **RTL8821AU is Jaguar1** (not Jaguar2, despite the Jaguar2 RTL8821C's similar name); RTL8822**B**U (Jaguar2) ≠ RTL8822**C**U (Jaguar3); @@ -149,24 +57,21 @@ table: README **Supported hardware**. **PCIe** (`DEVOURER_PCIE=ON`, Linux-only, default OFF): the RTL8821CE — the PCIe sibling of the 8821CU — rides the same Jaguar2 HAL through a vfio-pci -transport (`src/PcieTransport.{h,cpp}`): registers are BAR2 MMIO (same +transport (`src/PcieTransport.{h,cpp}`): registers are BAR2 MMIO (the same 0x0000..0xFFFF space the USB vendor-control path addresses), TX/RX are the -88xx buffer-descriptor DMA rings (rtw88 pci.{c,h} layout), RX completion is -polled by default (MSI+eventfd wakeups when available). USB and PCIe are -independent transports behind `devourer::IRtlTransport` -(`src/RtlTransport.h`): `UsbTransport` (libusb) and `PcieTransport` each -implement the register + frame planes, and the bus-neutral `RtlAdapter` value -type the HALs hold forwards to whichever it was built with. The few genuinely -bus-specific bring-up steps gate on `is_usb()` (PCIe power-seq rows, PQ map, -no USB RX-agg, no DLFW 512-pad) or ride `hci_setup()` (pre-power TRX ring -programming, no-op on USB). Factory: +88xx buffer-descriptor DMA rings (rtw88 pci.{c,h} layout; TX = the data/MGMT +BD rings behind the unchanged `send_packet`), RX completion polled by default +(MSI+eventfd wakeups when available). USB and PCIe are independent transports +behind `devourer::IRtlTransport` (`src/RtlTransport.h`); the bus-neutral +`RtlAdapter` value type the HALs hold forwards to whichever it was built +with. The few genuinely bus-specific bring-up steps gate on `is_usb()` (PCIe +power-seq rows, PQ map, no USB RX-agg, no DLFW 512-pad) or ride `hci_setup()` +(pre-power TRX ring programming, no-op on USB). Factory: `WiFiDriver::CreateRtlDevicePcie(PcieTransport::Open(bdf, logger))` — the caller owns vfio like it owns libusb. Demos: `DEVOURER_PCIE_BDF=0000:01:00.0` -on rxdemo and txdemo (TX = the data/MGMT BD rings behind the unchanged -`send_packet`); `pcieprobe [id|power|fw]` validates the layers -bottom-up. -Bind/restore: `tests/pcie_vfio_bind.sh` (driver_override, not new_id — the -in-tree rtw88 auto-probe race). Validation: `sudo python3 +on rxdemo and txdemo; `pcieprobe [id|power|fw]` validates the layers +bottom-up. Bind/restore: `tests/pcie_vfio_bind.sh` (driver_override, not +new_id — the in-tree rtw88 auto-probe race). Validation: `sudo python3 tests/pcie_rx_smoke.py` against a vfio-bound 8821CE (rig specifics: local `INVENTORY.md`) — ambient beacons CRC-clean on ch 6 + 36. @@ -279,26 +184,23 @@ knobs are runtime setters on `IRtlDevice` (`SetTxMode`, `SetTxPowerOffsetQdb`, returns a static aggregate of chip identity (name / generation / variant / transport / chip-id), TX/RX chain counts, the composed `GetTxCaps` + `GetTxPowerCaps`, the supported channel-width set, per-band tunable + -characterized frequency spans, and feature flags (per-packet TX power, -narrowband, fast retune, per-chain RSSI) — resolved at construction, thread-safe, -callable pre-`Init`. The demos emit it as the `adapter.caps` JSONL event. -The `ldpc_rx_ht`/`ldpc_rx_vht`/`ldpc_rx_flag` flags are the bench-derived LDPC -RX truth table (deliberately NOT the vendor `HAL_DEF_RX_LDPC`, which is -2013-era interop-advertisement policy — all-false on Jaguar1 while the 8812A -demonstrably decodes LDPC): every supported chip decodes HT+VHT LDPC except -the 8821A (VHT-LDPC RX broken — field-reported, HT fine), and the 8814A -decodes LDPC but reports no per-frame flag (`ldpc_rx_flag=0`, -`RxAtrib.ldpc` reads 0). LDPC TX is per-packet radiotap-driven on all -generations (`TxCaps.ldpc_ok`); bench-measured coding gain ≈ +3 dB at the -10%-delivery crossing, MCS7/20 MHz (`tests/ldpc_waterfall.sh`) — prefer -`/LDPC` on any link whose RX side can decode it. -`GetActiveRxPaths()` is the live companion: a best-effort per-chain-RSSI estimate -of which antennas actually carry signal (needs an RX loop + traffic). The 5 GHz -synthesizer tunes past the UNII channels (extended range ~5080–6165 MHz, chan up -to 253, `freq = 5000 + 5*chan`); out-of-band channels tune but their TX power / -per-channel constants are extrapolated from the nearest characterized channel -(one-shot `W` diagnostic). No regulatory enforcement — the caller owns -compliance. +characterized frequency spans, and feature flags — resolved at construction, +thread-safe, callable pre-`Init`; the demos emit it as the `adapter.caps` +JSONL event. The `ldpc_rx_*` flags are the bench-derived LDPC RX truth table +(deliberately NOT the vendor `HAL_DEF_RX_LDPC` interop-advertisement policy, +which reads all-false on Jaguar1 while the 8812A demonstrably decodes LDPC): +every supported chip decodes HT+VHT LDPC except the 8821A (VHT-LDPC RX +broken, HT fine) and the 8814A reports no per-frame flag. LDPC TX is +per-packet radiotap-driven on all generations (`TxCaps.ldpc_ok`); +bench-measured coding gain ≈ +3 dB at the 10%-delivery crossing, MCS7/20 MHz +(`tests/ldpc_waterfall.sh`) — prefer `/LDPC` on any link whose RX side can +decode it. `GetActiveRxPaths()` is the live companion: a best-effort +per-chain-RSSI estimate of which antennas actually carry signal (needs an RX +loop + traffic). The 5 GHz synthesizer tunes past the UNII channels (extended +range ~5080–6165 MHz, chan up to 253, `freq = 5000 + 5*chan`); out-of-band +channels tune but their TX power / per-channel constants are extrapolated +from the nearest characterized channel (one-shot `W` diagnostic). No +regulatory enforcement — the caller owns compliance. **Env vars are the demos' interface**: `examples/common/env_config.{h,cpp}` maps every library-level `DEVOURER_*` var onto `DeviceConfig`, so the test @@ -321,10 +223,8 @@ are parsed in each demo's own code. The ones needed daily: Programmatic: `SetTxMode` / `ClearTxMode`. - `DEVOURER_SKIP_RESET=1` — skip `libusb_reset_device` before claim (only helps when firmware state is intact). Kestrel adapters skip the reset - unconditionally (`claim_interface_then_reset` guard): their `power_on` - forces the MAC off from any retained state, while a USB reset on running - firmware drops the chip to ROM — a stale-handle re-enumeration that can - land in the dead ZeroCD DISK id (`0bda:1a2b`). + unconditionally — a USB reset on running firmware can land the chip in the + dead ZeroCD DISK id (`src/kestrel/CLAUDE.md`). - `DEVOURER_TX_GAP_US=N` — txdemo inter-frame gap (default 2000, ~500 fps; `0` = max duty for heating experiments). - `DEVOURER_USB_DEBUG=1` — libusb DEBUG log level (~7 MB / 15 s, has filled @@ -334,17 +234,15 @@ Knob-specific facts that aren't obvious from the field docs: - `DEVOURER_TX_WITH_RX=thread` (concurrent TX+RX on one claimed handle: `InitWrite` once, then `StartRxLoop` on a thread) must be set **before** - `InitWrite` on Jaguar3 — the bring-up keeps the RX filters open; retrofitting - RX later is unreliable. This is the - single-radio beamforming self-sounding station: pair with - `DEVOURER_BF_ARM_SOUNDER` / `DEVOURER_TX_NDPA` / `DEVOURER_BF_DETECT_REPORT` - (`docs/beamforming-self-sounding.md`). Non-`thread` values select a - `fork()` RX child that only works on Termux; on regular Linux the forked - bring-ups race and die. -- `DEVOURER_RX_PATHS` (Jaguar1 RX-chain mask, `0x808` byte 0) routes through - `SetRxPathMask` and is **sticky** across `SetMonitorChannel` (IQK - saves/restores `0x808`). Toggle spec `0xAA:0xBB[:0xCC]@` cycles masks on - a timer for mobility/MRC measurements (`docs/measuring-spatial-diversity.md`, + `InitWrite` on Jaguar3. This is the single-radio beamforming self-sounding + station: pair with `DEVOURER_BF_ARM_SOUNDER` / `DEVOURER_TX_NDPA` / + `DEVOURER_BF_DETECT_REPORT` (`docs/beamforming-self-sounding.md`). + Non-`thread` values select a `fork()` RX child that only works on Termux; + on regular Linux the forked bring-ups race and die. +- `DEVOURER_RX_PATHS` (Jaguar1 RX-chain mask) routes through `SetRxPathMask` + and is **sticky** across `SetMonitorChannel`. Toggle spec + `0xAA:0xBB[:0xCC]@` cycles masks on a timer for mobility/MRC + measurements (`docs/measuring-spatial-diversity.md`, `tests/mrc_mobility.py`). `DEVOURER_RX_ALLPATHS=1` emits per-chain RSSI/SNR/EVM as a separate `rx.path` event (C/D nonzero only on the 8814AU). @@ -358,13 +256,13 @@ Knob-specific facts that aren't obvious from the field docs: (`docs/fused-fec.md`). Opt-in: a body with a corrupt tail is the worst-case input for an IP-stack consumer that didn't ask for it. - `DEVOURER_DIS_CCA=1` (Jaguar2/3, runtime `SetCcaMode`) disables the MAC - carrier-sense gate — **both** primary CCA (`0x520[14]`) and EDCCA (`[15]`) — - so injected/beacon TX stops deferring to a busy channel. The primary-CCA bit - is the one that matters: monitor injection is not CCA-free, it defers ~40–60% - to a co-channel 802.11 transmitter, and clearing `[14]` recovers ~1.5–2.2× - (on-air 8822EU/8812CU, `tests/dis_cca_tx_onair.sh`); the energy bit `[15]` - alone is null against a decodable preamble. **On by default on the streamtx - FPV downlink** (the link owns the channel — CSMA backoff only stutters it); + carrier-sense gate — **both** primary CCA (`0x520[14]`) and EDCCA (`[15]`). + The primary-CCA bit is the one that matters: monitor injection is not + CCA-free, it defers ~40–60% to a co-channel 802.11 transmitter, and + clearing `[14]` recovers ~1.5–2.2× (on-air 8822EU/8812CU, + `tests/dis_cca_tx_onair.sh`); the energy bit `[15]` alone is null against a + decodable preamble. **On by default on the streamtx FPV downlink** (the + link owns the channel — CSMA backoff only stutters it); `DEVOURER_DIS_CCA=0` forces standard carrier-sense back. Does NOT apply the vendor BB CCA-off writes (they deafen the RX). RX-decode side is a separate null (`tests/dis_cca_onair.sh`). @@ -374,19 +272,15 @@ Knob-specific facts that aren't obvious from the field docs: `tools/la_csi.py`. 8814A/8822B/8821C/8822C/8822E (+ 8821CE PCIe); the 8812A/8821A have no LA block. Sample packing, per-chip windows, trigger semantics, validation scripts and wedge risks: `docs/la-capture.md`. -- `DEVOURER_THERMAL_POLL_MS=N` emits `thermal` events from the RF - 0x42 meter: `raw` is 0..63 thermal units (~1.5–2 °C each, **not** absolute - °C — hence bucketed status, not a fake temperature), `delta` = raw − - EFUSE baseline. Jaguar1 has no hard thermal TX shutdown — a rising delta is - the early warning. On the 8814 the baseline is read at the 8812 offset, so - absolute delta may be off; the trend is valid. +- `DEVOURER_THERMAL_POLL_MS=N` emits `thermal` events from the RF 0x42 meter: + `raw` is 0..63 thermal units (~1.5–2 °C each, **not** absolute °C), `delta` + = raw − EFUSE baseline; a rising delta is the early TX-degradation warning. - `DEVOURER_LINKHEALTH=1` (rxdemo, needs `DEVOURER_RX_ENERGY_MS=N` — the RX - energy-window cadence in ms) classifies - the RX sensor tuple into SATURATED / INTERFERENCE / WEAK / MARGINAL / - HEALTHY / NO_SIGNAL (`src/LinkHealth.h`). Its purpose: distinguish - near-field saturation (strong RSSI + poor **EVM** — back OFF power) from a - weak link (add power). EVM, not SNR, is the saturation tell. - `docs/bench-testing-near-field.md`. + energy-window cadence in ms) classifies the RX sensor tuple into SATURATED + / INTERFERENCE / WEAK / MARGINAL / HEALTHY / NO_SIGNAL + (`src/LinkHealth.h`) — distinguishing near-field saturation (strong RSSI + + poor **EVM** — back OFF power) from a weak link (add power). EVM, not SNR, + is the saturation tell. `docs/bench-testing-near-field.md`. **Runtime TX power** (all generations — the adaptive-link power lever, see `src/TxPower.h`): `SetTxPowerOffsetQdb(qdb)` shifts power in quarter-dB @@ -395,39 +289,21 @@ the rails; flags in `GetTxPowerState`); `SetTxPowerIndexOverride(idx)` forces/clears a flat index. Both apply live and stick across `SetMonitorChannel` (re-folded on the new channel) and `FastRetune` (never rewrites TXAGC). `GetTxPowerCaps` reports the family step: 0.5 dB Jaguar1/2, -0.25 dB Jaguar3. The Jaguar2 TXAGC block and the 8814A's packed port are -write-only, so their `GetTxPowerState` reports the software shadow -(`hw_readback=false`). `txpower` (examples/txpower/) is the reference -consumer; register-level validation: `tests/txpwr_offset_regcheck.sh`. +0.25 dB Jaguar3. Write-only TXAGC hardware (Jaguar2, the 8814A's packed port) +reports the software shadow (`hw_readback=false`). `txpower` +(examples/txpower/) is the reference consumer; register-level validation: +`tests/txpwr_offset_regcheck.sh`. **Per-packet TX power** (a radiotap `DBM_TX_POWER` dB-delta per frame, zero -USB cost once armed; see the `per_pkt_txpwr_*` caps): **Jaguar2** — the -8822B/8821C descriptor `TXPWR_OFSET` hardware LUT (0/-3/-7/-11/+3/+6 dB), -session default `SetTxPacketPowerStep`. **Jaguar3** — the descriptor -`TXPWR_OFSET_TYPE` is a bank *selector*: types 2/3 pick two programmable -7-bit-signed power-index offsets in BB `0x1e70[31:16]` (~1 dB/step, -`DEVOURER_TXPKT_STEP_QDB` recalibrates), LRU-managed by -`SetTxPacketPowerOffsetQdb` (`src/jaguar3/TxPktPwrBanks.h`) — the banks -reset *disabled* at BB-table load, so the descriptor field alone is inert -until programmed (types 0/1 = per-STA BB-RAM by descriptor MACID, left as -the 0 dB baseline). On-air-validated on 8822CU + 8822EU, sticky across -`SetMonitorChannel`/`FastRetune`; the E compresses deep cuts (≈−6 dB floor, -same TSSI reshape as its offset slope). **8814A** — the same 3-bit LUT field -at the 8822B position (dword5 [30:28]), `SetTxPacketPowerStep` on -`RtlJaguarDevice`. **Kestrel** — no WD-descriptor field; `send_packet` -honours the radiotap delta by rewriting the fixed-dBm BB target between -frames (2 BB RMWs on value change, free while constant; 0.25 dB steps, -clamped to the 0..23 dBm PA window around the `DEVOURER_TX_PWR` base — -whole dBm on this family; global, so a HW beacon airing between frames -follows the last-written level; a field-less frame restores the -`SetTxPowerOffsetQdb` session offset). **8812AU/8821AU** — no descriptor -field exists (per-rate -selection via radiotap is their only per-packet lever); the compensating -fast lever is `FastSetTxPowerOffsetQdb` (BB-swing TxScale `0xc1c/0xe1c`: -global per-burst, 1–4 writes, 0.5 dB steps, −12..+2 dB, folded through the -8812A thermal tracker; on-air-validated on the 8812AU). Sweep harnesses: -`tests/txpkt_pwr_ofset_onair.sh` (TX_PID/TX_VID select the DUT; Kestrel -DUTs need `TX_PWR=14`-style dBm bases), `tests/txpkt_fastswing_onair.sh`, +USB cost once armed; see the `per_pkt_txpwr_*` caps): Jaguar2 and the 8814A +use a 3-bit descriptor `TXPWR_OFSET` hardware LUT (session default +`SetTxPacketPowerStep`); Jaguar3 selects programmable BB offset banks via +`TXPWR_OFSET_TYPE` (inert until programmed); Kestrel rewrites its fixed-dBm +BB target between frames; the 8812AU/8821AU have no descriptor field — their +compensating fast lever is `FastSetTxPowerOffsetQdb` (BB-swing). Full +mechanisms: each `src//CLAUDE.md`. Sweep harnesses: +`tests/txpkt_pwr_ofset_onair.sh` (TX_PID/TX_VID select the DUT; Kestrel DUTs +need `TX_PWR=14`-style dBm bases), `tests/txpkt_fastswing_onair.sh`, `tests/txpkt_hop_persist.sh`. Per-packet unequal error protection: `svctx` classifies stdin HEVC NALs by @@ -445,31 +321,30 @@ per hop depending on chip. `send_packet` honours a radiotap `CHANNEL` field, so hopping is per-packet and radiotap-driven like rate. Demos hop via `DEVOURER_HOP_CHANNELS` (SweepSpec grammar: `1,6,11`, `36-48/4`, `5170-5250/5` MHz) + `DEVOURER_HOP_DWELL_FRAMES` / `_ROUNDS` / `_FAST` / `_RADIOTAP` / -`_BW` / `_OFFSET`; `debug.hop_prof` (env `DEVOURER_HOP_PROF=1`) emits per-stage -timing. Validation: `tests/run_hop_validation.sh`, `tests/hop_parity_check.sh` -(register parity full-vs-fast). Implementation + per-generation ports: +`_BW` / `_OFFSET`; `DEVOURER_HOP_PROF=1` emits per-stage `debug.hop_prof` +timing. Validation: +`tests/run_hop_validation.sh`, `tests/hop_parity_check.sh` (register parity +full-vs-fast). Implementation + per-generation ports: `docs/frequency-hopping.md`. **Keyed FHSS + lockstep RX** (`src/HopSchedule.h`): `DEVOURER_HOP_SLOT_MS` -selects monotonic wall-clock slots; `DEVOURER_HOP_SEED` (≤32 hex, a 128-bit key) -replaces the public round-robin with a SipHash-2-4 Fisher-Yates permutation per -round — stateless (`channel = perm(slot/N)[slot%N]`), so a receiver joins -without RNG state. Both orders share the lockstep path: in slot mode `txdemo` -(beacon) and `streamtx` (own frame every `DEVOURER_HOP_SYNC_EVERY`, FEC PSDU -untouched) emit a sync marker (fingerprint/epoch/slot/phase); `rxdemo` hops in -lockstep when `DEVOURER_HOP_CHANNELS`+`_SLOT_MS` are set (seed optional → +selects monotonic wall-clock slots; `DEVOURER_HOP_SEED` (≤32 hex, a 128-bit +key) replaces the public round-robin with a stateless SipHash-2-4 +Fisher-Yates permutation per round, so a receiver joins without RNG state. In +slot mode the TX demos (`txdemo` beacon; `streamtx` own frame every +`DEVOURER_HOP_SYNC_EVERY`, FEC PSDU untouched) emit a sync marker, and +`rxdemo` hops in lockstep when +`DEVOURER_HOP_CHANNELS`+`_SLOT_MS` are set (seed optional → keyed/sequential), emitting `hop.rx` acquire/track/retune events. Jammer -resilience: `tests/run_jammer_resilience.sh` (parked B210 A/B/C/D delivery -matrix) + `tests/sdr_follower_jammer.py` (full-duplex B210 follower, reactive vs -predictive). Article + results: `docs/fhss.md`, `docs/jammer-resilience.md`. +resilience: `tests/run_jammer_resilience.sh` + `tests/sdr_follower_jammer.py` +(B210 follower, reactive vs predictive). Article + results: `docs/fhss.md`, +`docs/jammer-resilience.md`. `IRtlDevice::FastSetBandwidth(bw)` is the bandwidth analogue — a lean -same-channel toggle between 20 MHz and 5/10 MHz narrowband (the RF stays in -20 MHz mode, so only the baseband ADC/DAC re-clock changes: a single `0x8ac` -write on Jaguar1, a cached read-free re-clock delta on Jaguar2/3). ~0.18 ms -(8812AU) vs ~90 ms for the full `SetMonitorChannel`; falls back to the full path -for a 40/80 MHz endpoint. Validation: `tests/fast_bw_parity.sh` (timing + -register parity + cross-RX decode). See `docs/narrowband.md`. +same-channel toggle between 20 MHz and 5/10 MHz narrowband (baseband re-clock +only; ~0.18 ms on the 8812AU vs ~90 ms for the full `SetMonitorChannel`); +falls back to the full path for a 40/80 MHz endpoint. Validation: +`tests/fast_bw_parity.sh`. See `docs/narrowband.md`. RX counterpart: `DEVOURER_RX_SWEEP` dwells FastRetune-cheap bins emitting per-bin energy + frame stats; `tests/sounding_sweep.sh` + `tests/sounding_map.py` @@ -483,43 +358,42 @@ per-slot FHSS. Pure caller-side logic under `src/chanmig/` (namespace `devourer::chanmig`, header-mostly, each ctest-covered; the library reads no env, the demos map it). Five composable layers: -- **Scout** (`chanscout`): a second adapter passively surveys a candidate plan - (`DEVOURER_SCOUT_PLAN`) while the primary RX stays on the video channel, - emitting versioned `survey.dwell` records with a counter-hygiene discard - barrier (the FA/CCA counters are delta-on-read). Measures only; retunes - nothing but itself. Grid-legality validation, **no regulatory DB** — the - caller owns compliance. +- **Scout** (`chanscout`): a second adapter passively surveys a candidate + plan (`DEVOURER_SCOUT_PLAN`) while the primary RX stays on the video + channel, emitting versioned `survey.dwell` records with a counter-hygiene + discard barrier (the FA/CCA counters are delta-on-read). Measures only; + retunes nothing but itself. Grid-legality + validation, **no regulatory DB** — the caller owns compliance. - **Scoring** (`ChannelScore`, `DEVOURER_SCOUT_ADVISE`): a pure two-leg - recommendation engine — the primary receiver's *delivery* is authoritative on - the active channel (scout energy there is confounded by wanted video), the - scout's occupancy on candidates — emitting explainable - `channel.recommend`/`hold` with an evidence generation + policy hash. + recommendation engine — the primary receiver's *delivery* is authoritative + on the active channel (scout energy there is confounded by wanted video), + the scout's occupancy on candidates — emitting explainable + `channel.recommend`/`hold`. - **Protocol** (`examples/chanmig --role ground|drone`): an authenticated - ground-proposes/drone-commits migration with a SipHash-MAC'd wire codec - (`MigWire.h`), pure `MigProposer`/`MigResponder` state machines, and random - per-boot epochs (no persisted in-flight state). Two rules make every - failure-matrix row converge without split-brain: the drone arms activation - only after the ground echoes its nonce, and the ground follows the drone's - authoritative STATUS rather than declaring success unilaterally. Control - frames are their own canonical-SA 802.11 frames — video PSDUs are never - touched. + ground-proposes/drone-commits migration (SipHash-MAC'd wire codec + `MigWire.h`, pure `MigProposer`/`MigResponder` state machines, random + per-boot epochs). The drone arms + activation only after the ground echoes its nonce, and the ground follows + the drone's authoritative STATUS — every failure-matrix row converges + without split-brain. Control frames are their own canonical-SA 802.11 + frames — video PSDUs are never touched. - **Automation** (`MigGate`, `DEVOURER_MIG_MODE=off|advisory|manual|automatic`, - default advisory): a pure, deterministic gate that proposes only under a - conservative conjunction, hedged with cooldown / residency / per-channel - backoff / move-cap / probation and an operator kill switch. -- **Drone-side validation** (`#280`): variant A (legality/caps checks — the - product default) is free; the variant-B pre-commit probe (`DEVOURER_MIG_PROBE`) - is opt-in research (real outage cost), variant C is the gate's probation. - -The whole thing is validated headless (`chanmig_wire_kat`, -`chanmig_proto_matrix` — a 14-row failure matrix + drop-every-message sweep, -`chan_score_policy`, `chanmig_gate_policy`, `chanmig_clock_math`) and on-air -(`tests/chanmig_endurance.sh` ≥1,000 cycles, `tests/chanscout_stress.sh` -≥10,000 retunes, `tests/chanmig_soak.sh` ≥1 h scout-impact A/B). Docs: -`docs/adaptive-channel-migration.md`, `docs/channel-migration-protocol.md`, -`docs/channel-migration-validation.md`. Near-field bench note: two adapters -~30 cm apart saturate the RX front end at full power (`link.health` SATURATED), -so migration tests reduce TX power (`DEVOURER_TX_PWR=12`). + default advisory): a pure, deterministic gate hedged with cooldown / + residency / per-channel backoff / move-cap / probation and an operator kill + switch. +- **Drone-side validation**: legality/caps checks are the free product + default; the pre-commit probe (`DEVOURER_MIG_PROBE`) is opt-in research + (real outage cost); probation is the gate's. + +Validated headless (`chanmig_wire_kat`, `chanmig_proto_matrix` — a 14-row +failure matrix + drop-every-message sweep, `chan_score_policy`, +`chanmig_gate_policy`, `chanmig_clock_math`) and on-air +(`tests/chanmig_endurance.sh`, `tests/chanscout_stress.sh`, +`tests/chanmig_soak.sh`). Docs: `docs/adaptive-channel-migration.md`, +`docs/channel-migration-protocol.md`, `docs/channel-migration-validation.md`. +Near-field bench note: two adapters ~30 cm apart saturate the RX front end at +full power (`link.health` SATURATED), so migration tests reduce TX power +(`DEVOURER_TX_PWR=12`). ## Hardware time, beacons, AP mode @@ -594,25 +468,9 @@ Generation-agnostic core in `src/` (always compiled; depends on no HAL): (`UeRxAttribution`: per-transmitter windowed RX statistics keyed by 802.11 TA); the device RX loops are untouched. -Per-generation HALs (each self-contained): - -- `src/jaguar1/`: `RtlJaguarDevice` (orchestrator), `HalModule` (bring-up, - power seq, table apply — most chip-family divergence, `_8812A`/`_8814A`/ - `_8821A` suffixed methods), `RadioManagementModule` (channel/BW/TX power, up - to 4 RF paths), `EepromManager` (EFUSE + `cut_version`/`rfe_type` for - `CheckPositive`), `FirmwareManager` (8814 = 3081-DDMA path, 8812/8821 = - page-write), `FrameParser` (RX parsing + `SET_TX_DESC_*_8812` descriptors). -- `src/jaguar2/`: shared core (`RtlJaguar2Device`, `HalJaguar2`, - `HalmacJaguar2Fw`/`MacInit`, `FrameParserJaguar2.h`) + per-variant strategy - interfaces selected by `ChipVariant`: `Jaguar2PhyTables` → - `Phy8822bTables`/`Phy8821cTables`, `Jaguar2Calibration` → - `Halrf8822b`/`Halrf8821c`. Every strategy seam defaults to `C8822B`, so the - 8822B path stays byte-identical when the 8821C variant is compiled out. -- `src/jaguar3/`: `RtlJaguar3Device`, `HalJaguar3` (power seq, table apply, - 3-wire RF, bf_init, efuse incl. 8822e OTP burst-mode), `HalmacJaguar3Fw`/ - `MacInit`, `RadioManagementJaguar3` (channel/BW/per-path power, the - `0x9b0`/`0x9b4` narrowband dividers, RF18 encoding), strategy interfaces - `Jaguar3Calibration` → `Halrf8822c`/`Halrf8822e` and `Jaguar3PhyTables`. +Per-generation HALs are self-contained under `src/jaguar1/`, `src/jaguar2/`, +`src/jaguar3/`, `src/kestrel/`; each subtree's `CLAUDE.md` maps its files, +strategy seams and chip-specific mechanisms. `hal/` holds vendor headers and tables. The per-chip PHY/limit tables and the 8821C firmware blob are **generated** by `tools/extract_*.py` — edit the @@ -647,11 +505,11 @@ The canonical test beacon (`examples/tx/main.cpp`) uses SA `tests/regress.py` (`CANONICAL_SA`). Change all three together if it ever moves. -**TX transfer mode is deliberately per-generation** (`docs/performance-tuning.md`): -Jaguar1 submits frames asynchronously (`RtlAdapter::send_packet` → `tx_async`, -caller-thread completion reaping) because its USB2 round-trip is too long for a -blocking send to saturate the link; Jaguar2/Jaguar3 send synchronously -(`bulk_send_sync_ep` → `tx_sync`) because their USB3 round-trip lets one -blocking thread saturate, and sync gives the HalMAC bring-up a clean per-send -NAK backoff. Don't unify the two onto one mode — either direction regresses -throughput or bring-up safety. +**TX transfer mode is deliberately per-generation** +(`docs/performance-tuning.md`): Jaguar1 submits asynchronously +(`RtlAdapter::send_packet` → `tx_async`, caller-thread completion reaping — +its USB2 round-trip is too long for a blocking send to saturate the link); +Jaguar2/Jaguar3 send synchronously (`bulk_send_sync_ep` → `tx_sync` — their +USB3 round-trip saturates on one blocking thread, and sync gives the HalMAC +bring-up a clean per-send NAK backoff). Don't unify the two onto one mode — +either direction regresses throughput or bring-up safety. diff --git a/CMakeLists.txt b/CMakeLists.txt index af0d5a54..10ecaf94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,8 +27,8 @@ option(DEVOURER_JAGUAR3_8822C "RTL8812CU / 8822CU (Jaguar3, rtl8822c)" option(DEVOURER_JAGUAR3_8822E "RTL8812EU / 8822EU (Jaguar3, rtl8822e)" ON) # Kestrel — the Wi-Fi 6 / 802.11ax generation (Realtek "G6 phl" architecture, # vendor trees reference/rtl8852bu + reference/rtl8852cu): monitor RX, TX -# injection and channel/BW on-air validated on both chips (see src/kestrel/ -# and the CLAUDE.md Kestrel section). +# injection and channel/BW on-air validated on both chips (see +# src/kestrel/CLAUDE.md). option(DEVOURER_KESTREL_8852B "RTL8852BU / 8832BU (Kestrel 11ax, G6)" ON) option(DEVOURER_KESTREL_8852C "RTL8852CU / 8832CU (Kestrel 11ax, G6)" ON) # PCIe transport (vfio-pci). Linux-only. The transport implements the HalMAC- diff --git a/docs/8852c-quirks.md b/docs/8852c-quirks.md index 352a0ded..8714fc11 100644 --- a/docs/8852c-quirks.md +++ b/docs/8852c-quirks.md @@ -7,9 +7,9 @@ The Kestrel **C8852C** die (WiFi-6 / 802.11ax, `35bc:0101` on the reference adapters, die-id `0x52`) as devourer drives it today. Each entry states what the chip does, what devourer does about it, the residual cost if any, and the reproducer that proves it. The 8852C shares one HAL with the C8852B; most of its -divergences are the `_V1` register-bank moves called out in the top-level -`CLAUDE.md`. This file is for the behavioural quirks that aren't a simple bank -remap. +divergences are the `_V1` register-bank moves called out in +`src/kestrel/CLAUDE.md`. This file is for the behavioural quirks that aren't a +simple bank remap. ## The frame-free NHM absolute noise floor is 2.4 GHz-only (firmware DIG) diff --git a/src/jaguar1/CLAUDE.md b/src/jaguar1/CLAUDE.md new file mode 100644 index 00000000..21e96b62 --- /dev/null +++ b/src/jaguar1/CLAUDE.md @@ -0,0 +1,49 @@ +# src/jaguar1/ — Jaguar1 (11ac gen1) working context + +Deep per-generation facts for this subtree, loaded alongside the root +CLAUDE.md. Chips: RTL8812AU (2T2R reference), RTL8811AU (1T1R cut, rides the +8812 path), RTL8814AU (4T4R RF / 3-SS baseband), RTL8821AU (1T1R + BT). + +## HAL layout + +`RtlJaguarDevice` (orchestrator), `HalModule` (bring-up, power seq, table +apply — most chip-family divergence, `_8812A`/`_8814A`/`_8821A` suffixed +methods), `RadioManagementModule` (channel/BW/TX power, up to 4 RF paths), +`EepromManager` (EFUSE + `cut_version`/`rfe_type` for `CheckPositive`), +`FirmwareManager` (8814 = 3081-DDMA path, 8812/8821 = page-write), +`FrameParser` (RX parsing + `SET_TX_DESC_*_8812` descriptors). + +## Chip facts + +- **8814AU**: host-pushed TX requires the on-chip 3081 MCU booted during FW + download; a failed FW-boot poll means dead TX while RX still works. +- **5/10 MHz narrowband** (8812AU/8811AU + 8814AU): both dies share the + Jaguar2 `0x8ac` ADC/DAC clock-divider block, so the re-clock trick works + even though the vendor never wired it — TX+RX bench-characterized. The two + dies use different field encodings of the same register: the 8812A divides + via `[9:8]`/`[21:20]`, the 8814A via the 8822B's + `[9:8]+[16]`/`[21:20]+[28]`. The 8821A is excluded (dividing its DAC clock + starves the 1T1R TX DMA/USB path) and falls back to 20 MHz. See + `docs/narrowband.md`. +- `DEVOURER_RX_PATHS` (RX-chain mask) writes `0x808` byte 0 via + `SetRxPathMask`; sticky across `SetMonitorChannel` because IQK + saves/restores `0x808`. +- Thermal (RF 0x42 meter): Jaguar1 has no hard thermal TX shutdown — a + rising delta is the early warning, hence bucketed status rather than a + fake temperature. On the 8814 the EFUSE baseline is read at the 8812 + offset, so absolute delta may be off; the trend is valid. +- The 8812A/8821A have no LA capture block (the 8814A does). +- The 8821A's VHT-LDPC RX is broken (field-reported; HT-LDPC fine); the + 8814A decodes LDPC but reports no per-frame flag (`ldpc_rx_flag=0`, + `RxAtrib.ldpc` reads 0). + +## Per-packet TX power + +- **8814A**: the 3-bit descriptor `TXPWR_OFSET` LUT at the 8822B position + (dword5 [30:28]); session default `SetTxPacketPowerStep` on + `RtlJaguarDevice`. +- **8812AU/8821AU**: no descriptor field exists — per-rate selection via + radiotap is their only per-packet lever. The compensating fast lever is + `FastSetTxPowerOffsetQdb` (BB-swing TxScale `0xc1c/0xe1c`: global + per-burst, 1–4 writes, 0.5 dB steps, −12..+2 dB, folded through the 8812A + thermal tracker; on-air-validated on the 8812AU). diff --git a/src/jaguar2/CLAUDE.md b/src/jaguar2/CLAUDE.md new file mode 100644 index 00000000..562d3d6e --- /dev/null +++ b/src/jaguar2/CLAUDE.md @@ -0,0 +1,41 @@ +# src/jaguar2/ — Jaguar2 (11ac gen2) working context + +Deep per-generation facts for this subtree, loaded alongside the root +CLAUDE.md. Chips: RTL8822BU / RTL8812BU (chip-id `0x0a`) and RTL8811CU / +RTL8821CU (chip 8821C, chip-id `0x09`); the RTL8821CE (PCIe) rides this same +HAL through `PcieTransport`. + +## HAL layout + +Shared core (`RtlJaguar2Device`, `HalJaguar2`, `HalmacJaguar2Fw`/`MacInit`, +`FrameParserJaguar2.h`) + per-variant strategy interfaces selected by +`ChipVariant`: `Jaguar2PhyTables` → `Phy8822bTables`/`Phy8821cTables`, +`Jaguar2Calibration` → `Halrf8822b`/`Halrf8821c`. Every strategy seam +defaults to `C8822B`, so the 8822B path stays byte-identical when the 8821C +variant is compiled out. A hybrid generation: HalMAC FW download / MAC init / +power sequencing like Jaguar3, phydm `check_positive` register tables like +Jaguar1 (shared `PhyTableLoader`). + +## Chip facts + +- **5/10 MHz narrowband on both variants**: a baseband ADC/DAC re-clock + packed into BB `0x8ac`; the RF stays in 20 MHz mode; applied as an + end-of-bring-up retune. The 8822B RF synth only re-latches on an RF18 + *value edge*, so the narrowband path writes RF18 twice — same-value + rewrites do nothing; see the `set_channel_bw` NB branch and + `docs/narrowband.md`. +- 5 MHz at 5 GHz is CFO-limited: subcarrier spacing shrinks 4× and a + far-offset TX/RX crystal pair syncs bimodally per bring-up — at 2.4 GHz + the same pair is stable (`tests/narrowband_cross_rx.sh` header). +- Per-rate bandwidth-aware efuse TX power clamped to generated `txpwr_lmt` + tables (narrowband folds to the 20 MHz column). +- Golden-init replay (`DEVOURER_REPLAY_WSEQ`, a captured kernel write stream + applied verbatim at the end of Init) is the debugging lever that found the + RF18-edge bug. +- The TXAGC block is write-only, so `GetTxPowerState` reports the software + shadow (`hw_readback=false`). + +## Per-packet TX power + +The 8822B/8821C descriptor `TXPWR_OFSET` is a hardware LUT +(0/-3/-7/-11/+3/+6 dB); session default via `SetTxPacketPowerStep`. diff --git a/src/jaguar3/CLAUDE.md b/src/jaguar3/CLAUDE.md new file mode 100644 index 00000000..bb3c7882 --- /dev/null +++ b/src/jaguar3/CLAUDE.md @@ -0,0 +1,42 @@ +# src/jaguar3/ — Jaguar3 (11ac gen3) working context + +Deep per-generation facts for this subtree, loaded alongside the root +CLAUDE.md. Chips: rtl8822c (RTL8812CU/8822CU, chip-id `0x13`) and rtl8822e +(RTL8812EU/8822EU, chip-id `0x17`). + +## HAL layout + +`RtlJaguar3Device`, `HalJaguar3` (power seq, table apply, 3-wire RF, bf_init, +efuse incl. 8822e OTP burst-mode), `HalmacJaguar3Fw`/`MacInit`, +`RadioManagementJaguar3` (channel/BW/per-path power, the `0x9b0`/`0x9b4` +narrowband dividers, RF18 encoding), strategy interfaces `Jaguar3Calibration` +→ `Halrf8822c`/`Halrf8822e` and `Jaguar3PhyTables` (its own +`PhyTableLoaderJaguar3`, separate from the Jaguar1/2 `PhyTableLoader`). + +## Chip facts + +- **Coex runtime thread** (`RtlJaguar3Device::coex_runtime_loop`, started in + `InitWrite`): sustained 5 GHz TX needs it — without its ~2 s WiFi-only coex + re-apply + FW heartbeats, the combo chip's coex firmware silences the + antenna. It also drains C2H, so TX-only sessions still see `tx.report`. +- **5/10 MHz narrowband**: the re-clock lives in the `0x9b0`/`0x9b4` + dividers (vs the `0x8ac` block the Jaguar1/2 chips share). 80 MHz works, + incl. a 40-in-80 frame via TX-descriptor DATA_SC. See `docs/narrowband.md`. +- halrf calibration: DACK/IQK/TXGAPK/thermal tracking. +- `DEVOURER_TX_WITH_RX=thread` must be set **before** `InitWrite` — the + bring-up keeps the RX filters open; retrofitting RX later is unreliable. +- The rtl8822e's hardware-bisected constraints (DPDT/pin-mux front end, + single-path 1SS TX, spur channels, LCK, the 2.4 GHz TX kernel-parity + limitation) live in `docs/8822e-quirks.md`. + +## Per-packet TX power + +The descriptor `TXPWR_OFSET_TYPE` is a bank *selector*: types 2/3 pick two +programmable 7-bit-signed power-index offsets in BB `0x1e70[31:16]` +(~1 dB/step, `DEVOURER_TXPKT_STEP_QDB` recalibrates), LRU-managed by +`SetTxPacketPowerOffsetQdb` (`TxPktPwrBanks.h`) — the banks reset *disabled* +at BB-table load, so the descriptor field alone is inert until programmed +(types 0/1 = per-STA BB-RAM by descriptor MACID, left as the 0 dB baseline). +On-air-validated on 8822CU + 8822EU, sticky across +`SetMonitorChannel`/`FastRetune`; the E compresses deep cuts (≈−6 dB floor, +same TSSI reshape as its offset slope). diff --git a/src/jaguar3/RtlJaguar3Device.h b/src/jaguar3/RtlJaguar3Device.h index cfe79b0b..6fa5420d 100644 --- a/src/jaguar3/RtlJaguar3Device.h +++ b/src/jaguar3/RtlJaguar3Device.h @@ -28,7 +28,7 @@ * power-on, firmware download, MAC/BB/RF config, IQK and DACK drive RX, * channel/bandwidth (incl. 5/10 MHz narrowband) and on-air TX. send_packet is * on-air; sustained continuous TX is kept alive by the coex runtime thread - * (coex_runtime_loop) — see CLAUDE.md. */ + * (coex_runtime_loop) — see src/jaguar3/CLAUDE.md. */ class RtlJaguar3Device : public IRtlDevice { public: RtlJaguar3Device(RtlAdapter device, Logger_t logger, diff --git a/src/kestrel/CLAUDE.md b/src/kestrel/CLAUDE.md new file mode 100644 index 00000000..69bde505 --- /dev/null +++ b/src/kestrel/CLAUDE.md @@ -0,0 +1,116 @@ +# src/kestrel/ — Kestrel (RTL8852, Wi-Fi 6) working context + +Deep per-generation facts for this subtree, loaded alongside the root +CLAUDE.md. Chips: RTL8852BU/8832BU (variant C8852B) and RTL8852CU/8832CU +(C8852C), one HAL serving both dies on all platforms (MSVC included). + +## Two-plane architecture + +The **mac_ax plane** (power-on / FWDL / efuse / MAC TRX / H2C-C2H / USB / +descriptors) is hand-ported C++ from Realtek's "G6 phl" vendor trees +(`reference/rtl8852bu`, `reference/rtl8852cu` — none of the 11ac +loaders/parsers apply). The **BB/RF plane is Realtek's halbb/halrf C compiled +VERBATIM** for both chips: one chip-agnostic core + per-chip backends under +`hal/halbb/g6/` + `hal/halrf/g6/` (`vendor/` = untouched vendor files; +`shim/` + `hal_headers_le.h` = the minimal PHL surface they compile against; +`kestrel_halbb_glue.c` / `kestrel_halrf_glue.c` = the C entry points devourer +drives; `tools/vendor_hal{bb,rf}_*.sh` re-vendor). + +The vendored loaders own the BB/RF/gain tables (`halbb_init_reg`, +`halrf_config_radio` — radio pages reach the fw via the bridge `send_h2c` +into the mac_ax H2C encoder), the per-channel BB config (`halbb_ctrl_bw_ch`), +the RF tune (`halrf_ctl_ch/bw` on the B, `halrf_ctl_band_ch_bw_8852c` on the +C), and the cals: DACK + RX-DCK both dies (wrapped in an RF 0x0/0x1/0x5 +save/restore on the B — the vendor relies on later TRX flow to rewrite them), +IQK on the 8852C only. The RFK prologue (NCTL microcode / a-die SI reset / +LCK / RCK / efuse trim + TSSI DE) runs at `phy_bb_rf_init` in the vendor's +`halrf_dm_init` position — that position is load-bearing: run after the tune, +the SI reset deafens the radio both ways. **Evidence-gated cals**: TSSI/DPK +(both dies, via `support_ability`) and IQK on the 8852B degrade TX under the +fixed-dBm power model — gates fall with a TSSI-referenced power model. + +## Dispatch and cold boot + +Dispatched **PID-first** (`kestrel/KestrelUsbIds.h`), not from the 0x00FC +byte: on AX silicon that register is R_AX_SYS_CHIPINFO (die-id 0x51/0x52; the +8852A's 0x50 collides with the 8822B cold transient). Cold-boot facts (mac_ax +plane, both dies): a USB chip from real cold reads WLMAC_PWR_STE=MAC_ON (its +AFSM auto-powers to enumerate) and power_on must force the MAC off first +(`pwr.c` prologue) or the bootrom never raises H2C_PATH_RDY; `B_AX_ENBT` must +be set before any LTE-space access (arms the LTE indirect interface) and the +GNT arbitration is SW-forced to WiFi (`mac_cfg_gnt_8852b` stance) — a kernel +driver pre-initializing the chip masks all of this, so blacklist rtw89 when +testing. + +USB open: Kestrel adapters skip the pre-claim `libusb_reset_device` +unconditionally (`claim_interface_then_reset` guard): `power_on` forces the +MAC off from any retained state, while a USB reset on running firmware drops +the chip to ROM — a stale-handle re-enumeration that can land in the dead +ZeroCD DISK id (`0bda:1a2b`). + +## Validated capability surface + +On-air-validated: monitor RX (both dies, 2.4/5 GHz; the RX front-end needs +the halbb per-band LNA/TIA gain-error cache, without which 5 GHz is deaf), TX +(mgmt injection — the OpenIPC video path via `streamtx`; legacy/HT/VHT/HE +rates), channel/BW **5/10/20/40/80 MHz on both dies + 160 MHz on the 8852C** +(the 8852B die has no 160 MHz — vendor bw_sup; caps report accordingly). +40 MHz tunes to the block center (primary ±2); 80/160 MHz derive +center/pri_ch from the channel plan (160 = an 8-wide block, center = +block_start+14). **6 GHz TX tops out at 80 MHz**: the 6G 160 MHz TX does not +radiate on the C8852C (the RF synth locks and RX-160 works, but the 6G+160 +TX-enable path is un-ported — B210-confirmed 0% duty vs 45% at 6G-80 / 40% at +5G-160; a MAC TXAGC-max / RF-TX-path gap, not a chip limit — the vendor +drives it). 5/10 MHz narrowband is the BB "small BW" field with the RF left +in 20 MHz mode (no ADC re-clock, unlike Jaguar). RX bulk-IN delivery requires +the USB RXAGG engine enabled (`B_AX_RXAGG_EN`). TX airs with the CMAC +EDCCA/CCA gate disabled (`sch_tx_en`, TX path only) — the intended +injection/monitor-link mode. `ReadTsf` reads the per-port MAC TSF; +`StartBeacon` drives the AX HW beacon engine. + +**HE ER SU + DCM extended range** (both dies): per-packet via radiotap-HE +FORMAT=EXT_SU or `DEVOURER_TX_RATE=.../ER[/DCM]`; RX classifies the format in +`RxAtrib.ppdu_type` (7=HE_SU, 8=HE_ERSU) — `docs/he-extended-range.md`. + +Async packet-C2H (bulk-IN rpkt_type=10) delivery works — routed by +`handle_c2h` on the C2H class/func — so the #236 C2H surface (TWT/F2P +reports) is reachable. Not working: the fw's USR_TX_RPT TX-egress-timestamp +C2H (gated on a full BSS association, not just the registered NO_LINK role). + +## TX power + +A fixed BB dBm (`halbb_set_txpwr_dbm`, default 20 dBm, `DEVOURER_TX_PWR` +override — whole dBm on this family) with the runtime `SetTxPowerOffsetQdb` +lever. Per-packet radiotap `DBM_TX_POWER`: no WD-descriptor field exists — +`send_packet` honours the radiotap delta by rewriting the fixed-dBm BB target +between frames (2 BB RMWs on value change, free while constant; 0.25 dB +steps, clamped to the 0..23 dBm PA window around the `DEVOURER_TX_PWR` base; +global, so a HW beacon airing between frames follows the last-written level; +a field-less frame restores the `SetTxPowerOffsetQdb` session offset). + +## 8852C vs 8852B divergences + +Almost every 8852C divergence is a **`_V1` register-bank** move +(USB/HFC/RXAGG/HCI at 0x5xxx/0x1700/0x6000/0x7880 vs the 8852B +0x1xxx/0x8Axx/0x8900/0x8380) plus a different descriptor: FWDL/H2C use a +16-byte `rxd_short_t` (not the 24-byte WD body), data/mgmt TX uses the +32-byte `wd_body_t_v1` (not 24), and the RX-descriptor drv_info unit is +16 bytes (not 8 — `FrameParserKestrel.h`; with the B's unit the C "receives" +frames with correct lengths but unreadable bodies, i.e. looks deaf to any +SA-matching harness). The CBV-cut die loads the `u2_nic` fw image (CAV→u1). +Two 8852C-specific TX pieces the 8852B path lacks: the BB IFFT→TX-chain +routing (`halbb_ctrl_tx_path_tmac_8852c`, the 0xD800..0xD82C "path-com" block +the 8852B replaces with its per-STA CMAC antenna model), and the V1 +descriptor's rate word — `USERATE_SEL`/BW/GI/DATARATE live in wd_body dword7, +not wd_info dword0 as on the 8852B (mis-placing it left `f_rate=0` so the +scheduler stalled and the bulk-OUT NAKed). Behavioural quirks beyond the +`_V1` bank moves (e.g. the frame-free NHM absolute floor is 2.4 GHz-only — +the firmware DIG biases the 5 GHz idle-noise measurement): +`docs/8852c-quirks.md`. + +## Scope + +The capstone is 11ax trigger-based UL + TWT (issue #236 — the v1.19 vendor +fwcmd surface exposes TWT-OFDMA + F2P trigger H2Cs that mainline rtw89 +lacks). The 8852A-family (RTL8832AU) is deliberately excluded (frozen 2021 +v1.15 vendor drop only). diff --git a/tests/bench_onair.py b/tests/bench_onair.py index 82b91080..d4682a82 100755 --- a/tests/bench_onair.py +++ b/tests/bench_onair.py @@ -133,7 +133,7 @@ def main() -> int: present = {c: v for c, v in present.items() if c.upper() in want} print(f"# chips: {', '.join(present)} | MCS{args.mcs}/{args.bw}MHz/{args.size}B\n") - # Pre-flight rail-sag guard (CLAUDE.md defence #1): the bus-powered hub rail + # Pre-flight rail-sag guard: the bus-powered hub rail # can brown out the 5 GHz PA, collapsing on-air power while 2.4 GHz still # works — making every 5 GHz number untrustworthy. Check the known-good # control adapter first so a sagging rail is flagged loudly instead of