radiotap TX: honour HT LDPC/STBC known bits on Jaguar2/3#291
Conversation
J2/J3 send_packet ignored the MCS field's FEC/STBC known bits, so HT frames tagged LDPC/STBC (e.g. mabur's MAX_RANGE robustness flags) silently flew as plain BCC SISO; only the VHT case honoured them. J1 already carried the fix. Shared decode in RadiotapTxFlags.h + headless builder/parser contract test.
josephnef
left a comment
There was a problem hiding this comment.
Approving — the fix is correct and I verified it end-to-end.
Correctness: the shared decode is bit-exact against the radiotap spec and behaviorally identical to Jaguar1's proven copy; ldpc/stbc init to 0, the SetTxMode default still applies only to rate-less frames after the walk, both STBC-cap guards sit downstream, and MCS+VHT dual-field frames stay correct (VHT iterates after MCS and only ever sets). The old J2/J3 behavior of not gating sgi/bw on their known bits is preserved, so no semantic regressions.
Impact check: the encoding matrix missed this because txdemo's rate-less beacon rides the SetTxMode default path (already correct). The real victims are streamtx/svctx/tdma/timesync/duplex, which embed the mode into per-frame radiotap via build_stream_radiotap — every HT /LDPC stream link on Jaguar2/3 silently aired BCC.
On-air A/B (streamtx MCS1/LDPC/STBC → 8812AU rx.txhit decode, master vs PR build):
| arm | frames | decoded |
|---|---|---|
| 8812EU (J3), master | 12 | ldpc=0 stbc=0 all |
| 8812EU (J3), PR | 10 | ldpc=1 stbc=1 all |
| 8812BU (J2), master | 99 | ldpc=0 stbc=0 all |
| 8812BU (J2), PR | 82 | ldpc=1 stbc=1 all |
Unanimous truth table on both generations; rate stays MCS1 throughout. ctest 21/21.
Nits (will handle as follow-ups on master, no action needed here): RadiotapTxFlags.h is included twice in RtlJaguar3Device.cpp; the decoder could use the named IEEE80211_RADIOTAP_MCS_* constants; and Jaguar1 can adopt the shared decoder so the copies truly can't diverge.
…#292) ## Summary Follow-ups from the #291 review, in one pass: - **Jaguar1 adopts `decode_radiotap_mcs_field()`** — the last private copy of the HT MCS known/flag reading is gone, so the three generations' `send_packet` parsers genuinely can't diverge. Behavior-identical: `bwidth` starts at 20 MHz, so J1's explicit BW_20/20L/20U branch was a no-op, and its ldpc/stbc/sgi semantics already matched the shared decode. - **Named constants** — the decoder spells its bit tests with `IEEE80211_RADIOTAP_MCS_HAVE_FEC`/`FEC_LDPC`/`HAVE_STBC`/`STBC_MASK`/`STBC_SHIFT`/`SGI` instead of raw masks and shifts. - **Duplicate include dropped** — `RadiotapTxFlags.h` was included twice in `RtlJaguar3Device.cpp`. - **Selftest**: new raw 3-byte MCS field case (BW code 20U, STBC stream count 2, LDPC, no SGI) pinning the decoder's bit positions independently of what `build_stream_radiotap` can emit — a direct guard on the constants refactor. ## Testing - `ctest` 21/21 green. - Bench-verified on air (streamtx `MCS1/LDPC/STBC` → `rx.txhit` decode): - 8812AU (J1 — the swapped path) → 8822B RX, ch6: **107/107** frames `ldpc=1 stbc=1`, rate MCS1. - 8812EU (J3 regression on the refactored decoder) → 8812AU RX, ch36: **19/19** `ldpc=1 stbc=1`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Summary
build_tx_blockon Jaguar2/3 read only bw/sgi/mcs from the HT MCS radiotap field and dropped the FEC/STBC known bits, so an HT frame requesting LDPC or STBC aired as plain BCC / no-STBC — only VHT frames were honoured. This adds a shared decode helper and wires it into both the Jaguar2 and Jaguar3 HT paths so HT injects apply LDPC/STBC too.The descriptor fill and STBC-cap guard were already correct:
DATA_LDPCdword5[7],DATA_STBCdword5[9:8] & 0x3 — byte-identical to the vendor driver (rtl88x2eu-20230815hal/rtl8822e/usb/rtl8822eu_xmit.c,SET_TX_DESC_DATA_STBC_8822E(.., pattrib->stbc & 3)).Changes
src/RadiotapTxFlags.h—decode_radiotap_mcs_field(): one shared reading of the HT MCS known/flag/index bytes (bw/sgi/mcs + LDPC/STBC).build_tx_blockIEEE80211_RADIOTAP_MCScases now use it; the STBC-cap guard below still clamps to what the chip can do.Testing
tests/radiotap_txflags_selftest.cppcovers the decode.ctest21/21 green.rxdemo: HT MCS0 injects decodedstbc=0 ldpc=0before this change andstbc=1 ldpc=1after; live video stayed clean at MCS5 with STBC+LDPC active.🤖 Generated with Claude Code