Beamforming self-sounding: per-subcarrier CSI over the air (Jaguar-1 + Jaguar-3)#154
Merged
Conversation
802.11ac explicit sounding leaks per-subcarrier channel state: a beamformee
estimates H(k) from a sounding NDP, compresses the steering matrix V(k) into
Givens angles, and transmits it back over the air as a VHT Compressed
Beamforming report. On Jaguar silicon that pipeline is hardware-terminated
(a chip can't read back its own estimate), but the report is addressed to the
beamformer — so with devourer driving both link ends, a monitor RX captures
per-subcarrier CSI the chip otherwise hides.
Adds a generation-neutral BeamformingSounder helper: the MAC sounding registers
(0x718/0x6E4/0x6F4/0x714/0x42C) are byte-identical across the Realtek AC family,
so arm_sounder() is fully shared; per-generation beamformee differences (0xCB vs
0xDB, the 0x9B4 BB-CSI-content that is the narrowband clock divider on Jaguar-3,
the RX-filter/own-AID gates) sit behind BfeeConfig presets (kBfeeJaguar1,
kBfeeJaguar23). Recipes transcribed from the vendor hal_txbf_jaguar_enter /
hal_txbf_8822b_enter.
Env-gated, no-op by default (like the other DEVOURER_* debug knobs):
DEVOURER_BF_ARM_SOUNDER arm the MAC sounding engine (beamformer)
DEVOURER_TX_NDPA mark the injected frame as an NDPA in the TX desc
DEVOURER_TX_NDPA_RA=mac txdemo builds a VHT NDPA to this beamformee
DEVOURER_BF_ARM_BFEE=mac arm the hardware CSI responder (beamformee), no
association — wired for Jaguar-1 and Jaguar-3
DEVOURER_BF_DETECT_REPORT report detector in WiFiDriverDemo (1 summary /
2 frame survey / 3 CSI hexdump / 4 raw for decode)
Validated on hardware: 8812AU/8821AU (Jaguar-1) and 8822CU/8822EU (Jaguar-3)
beamformees reply with VHT Compressed Beamforming reports to an unassociated
sounder; unarmed control yields zero.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tools/bf_report_decode.py decodes captured VHT Compressed Beamforming reports
into per-subcarrier CSI: parses the MIMO-control field (Nc/Nr/BW/Ng/codebook),
extracts per-stream avg SNR, unpacks the Givens angle bitstream, and
reconstructs the per-tone steering vector V(k). The angle bit-split is chosen
by cross-frame stability and validated against the standard phi=psi+2 Givens
structure; it also flags a flat (frequency-nonselective) channel. Realtek's
reports use a compact 10-bit/subcarrier codebook (phi=6, psi=4) rather than the
textbook 12/16-bit VHT sizes. The decoder is chip-agnostic — it reads
over-the-air standard frames, so the same tool decodes Jaguar-1 and Jaguar-3
reports.
Validation harnesses (out-of-band, like tests/regress.py):
bf_ndpa_probe.py / bf_ndpa_onair.sh B210 burst-pair probe: does the TX-desc
NDPA bit make the MAC emit a HW NDP?
bf_selfsound_onair.sh B210 burst-chain check of the responder
bf_report_sniff.sh 3-adapter decode-level confirmation
bf_selfsound_jaguar3.sh cross-generation (8822C/E) beamformee
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Documents the NDPA/NDP/report exchange, why the register recipe is generation-neutral, the per-generation beamformee differences, how to drive it via the demo env vars, and the measured 2x1 report byte layout (52 subcarriers x 10-bit compact codebook) that the decoder consumes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
josephnef
added a commit
that referenced
this pull request
Jul 3, 2026
## What Follow-up to #154 (SU self-sounding). An SU beamforming report carries only the per-tone steering **direction** plus a per-stream **scalar** SNR. An **MU** report additionally appends the *MU Exclusive Beamforming Report* — genuine **per-subcarrier SNR** — which is the per-tone channel quality that maps to a per-tone modulation order (the textbook "256-QAM on clean tones, QPSK on faded tones" picture). ## How - **`arm_beamformee_mu()`** (`src/BeamformingSounder.h`) layers the MU group-table registers (`0x14C0` MU-TX-CTL, `0x14C4`/`0x14C8`/`0x14CC` group/user-position, entry `0x1684`) on top of the SU responder base. For *self*-sounding we program the group/user-position directly, so no over-the-air VHT Group ID Management handshake is needed. Recipe from the vendor `hal_txbf_8822b_enter()` MU branch (identical across 8822B/C/E). - Wired on Jaguar-3 via **`DEVOURER_BF_ARM_BFEE_MU=1`**; the NDPA MU feedback bit (STA-info bit 12) via **`DEVOURER_TX_NDPA_MU=1`** in `WiFiDriverTxDemo`. - **`tools/bf_report_decode.py`** parses the MU report. Realtek packs the SNR not as the spec's 4-bit deltas but as 8-bit values in pairs — series A (even bytes) is the per-tone SNR that swings with the channel. The decoder extracts it, maps to dB (`22 + 0.25·int8`) and to a modulation per tone, and trims devourer's trailing chip-FCS/RX bytes where the smooth series collapses. `--operating-snr N` re-centres the *measured* per-tone shape to a stated link budget, turning the real frequency-selectivity into the textbook QAM ladder even on a strong bench link. ## Result (8822CU MU-beamformee, ch100, 20 MHz) Reports grow 99 → **153 bytes**; the per-tone SNR series A is a smooth **45–53 dB** curve with ~8 dB of real frequency-selective swing: ``` as measured: SNR 53..45 dB → all 256-QAM (link is uniformly strong) --operating-snr 26 (same shape, weaker link): SNR: 30 29 29 28 28 28 27 27 27 26 26 26 25 24 24 24 24 23 23 22 dB QAM: + + + : : : : : : : : : : : : : : : : : (+ 16-QAM / : QPSK) ``` Note: `DEVOURER_TX_PWR` does **not** attenuate the link — the NDP is hardware-generated by the sounding engine, not sent through `send_packet` — hence the operating-point model rather than a physical power sweep. `ctest` green; all new code is env-gated and no-op by default. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Recovers per-subcarrier channel state from Realtek Jaguar silicon that the chip otherwise terminates in hardware, by driving both ends of a link with devourer as an 802.11ac beamforming sounder + beamformee.
A beamformee estimates the channel H(k) per subcarrier from a sounding NDP, compresses the steering matrix V(k) into Givens angles, and transmits it back over the air as a VHT Compressed Beamforming report. The chip can't read back its own estimate — but the report is addressed to the beamformer, so a monitor RX captures it. With two adapters we sound our own link on demand and decode per-subcarrier CSI.
How
src/BeamformingSounder.h— generation-neutral helper. The MAC sounding registers (0x718/0x6E4/0x6F4/0x714/0x42C) are byte-identical across the Realtek AC family, soarm_sounder()is fully shared. Per-generation beamformee differences (sounding-control0xCBvs0xDB; the0x9B4BB CSI-content register that is the narrowband clock divider on Jaguar-3 and must not be written there; the RX-filter + own-AID gates) sit behindBfeeConfigpresets. Recipes transcribed from the vendorhal_txbf_jaguar_enter/hal_txbf_8822b_enter.RtlJaguarDevice(Jaguar-1) andRtlJaguar3Device(Jaguar-3), env-gated and no-op by default like the otherDEVOURER_*debug knobs.tools/bf_report_decode.py— chip-agnostic decoder: parses the MIMO-control field, extracts per-stream avg SNR, unpacks the angle bitstream, reconstructs the per-tone V(k). Auto-selects the bit-split by cross-frame stability and flags a flat channel. (Realtek uses a compact 10-bit/subcarrier codebook — 6-bit φ, 4-bit ψ — not the textbook 12/16-bit VHT sizes.)docs/beamforming-self-sounding.md— mechanism, recipe, report byte layout.Env vars (opt-in)
DEVOURER_BF_ARM_SOUNDER=1DEVOURER_TX_NDPA=1DEVOURER_TX_NDPA_RA=<mac>WiFiDriverTxDemobuilds a VHT NDPA to this beamformeeDEVOURER_BF_ARM_BFEE=<mac>DEVOURER_BF_DETECT_REPORT=1..4WiFiDriverDemo(1 summary … 4 raw for the decoder)Validation (on hardware, ch100)
Nc=1 Nr=2; zero when unarmed.Nc=2 Nr=2; zero when unarmed.ctestgreen. All new library code is behind env gates and compiles into every per-chip build subset. Jaguar-2 (8822BU) is a straightforward follow-up (the recipe is already the sharedkBfeeJaguar23) once that port lands.🤖 Generated with Claude Code