Kestrel: HE ER SU + DCM extended range; per-die RX drv_info unit fix#295
Merged
Conversation
HE ER SU (802.11ax Extended Range Single User) as a first-class TX/RX
feature on both Kestrel dies, full kit: ER 242-tone (MCS0-2), ER 106-tone
(DATA_BW_ER, MCS0), and DCM, each per-packet radiotap-driven
(HE FORMAT=EXT_SU / data5 RU code / data3 DCM bit) or as a session default
via the TX-mode grammar (/ER, /ER106, /DCM — HE rates only). Descriptor
bits follow the vendor split: DATA_ER/DATA_BW_ER in wd_info dword0 on both
dies, DCM in wd_info dword0 on the 8852B but DATA_DCM_V1 (BIT30) in the
wd_body dword7 rate word on the 8852C. Out-of-spec combos (NSS>1, MCS above
the mode ceiling, DCM+STBC) are clamped with a W diagnostic instead of
stalling the AX scheduler. The SetTxMode fallback now expresses HE natively
(it silently aired VHT before — exposed by the new RX-side witness).
RX surfaces the AX descriptor's PPDU-format nibble (7=HE_SU, 8=HE_ERSU) as
RxAtrib.ppdu_type (0xff pre-AX) and in the rx.txhit event — the only honest
on-air proof of the format, since the payload rate cannot distinguish ER
MCS0 from SU MCS0. AdapterCaps grows he_er_su_ok (adapter.caps: he_er_su).
Validating the 8852C as the RX side exposed a latent RX-descriptor parsing
bug: the payload offset is rxd + shift*2 + drv_info*UNIT (+4 when
mac_info_vld on non-PPDU-status packets), where UNIT is 8 bytes on the
8852B but 16 on the 8852C (vendor hal_trx_8852{b,c}). With the B's unit the
C die "receives" frames with correct lengths but unreadable bodies — an
SA-matching harness reads it as a deaf receiver. The parser now takes the
per-die unit and applies the full vendor formula.
The demos' DEVOURER_USB_BUS/PORT topology selection is now strict: matching
no device is fatal instead of silently falling through to the unrestricted
VID:PID open (which grabbed the wrong adapter in same-VID:PID pairs, the
exact ambiguity the knob exists to resolve).
On-air validation (tests/he_er_su_cross_rx.sh, ch6, 15 s cells,
2x RTL8832CU + 1x RTL8852BU): SU control classifies 7; ER-242, ER-106 and
ER+DCM deliver 3.3-5.7k hits/cell with uniform ppdu_type=8 in every
pairing — cross-die both directions and same-die 8852C->8852C — and ER
MCS1/MCS2 decode in every pairing (a same-die point-blank pair needs
DEVOURER_TX_PWR=6: near-field QPSK saturation, not a protocol limit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
josephnef
added a commit
that referenced
this pull request
Jul 16, 2026
… discovery (#294) (#296) Fixes the mechanical half of #294 (the deafness half was the RX-descriptor walk fix already merged in #295 — full root-cause analysis in the [issue comment](#294 (comment))). ## Problem `libusb_reset_device` on a Kestrel with running (or SIGKILL-orphaned) firmware crashes it to bootrom: the device re-enumerates (`device firmware changed`), the open dies with a stale handle, and the ROM path can land in the dead ZeroCD DISK id (`0bda:1a2b` — `bInterfaceNumber` 255, unclaimable by libusb *and* kernel usbfs, so no userspace eject exists; only a VBUS power-cycle recovers). Any harness cycling the demos hit retry roulette; every zero read looked like a deaf receiver. ## Fix - **`claim_interface_then_reset` skips the USB reset for any `kestrel::variant_for_usb_id` match.** Kestrel `power_on` forces the MAC off from any retained state — the reset added nothing but the ROM-drop risk. Covers all demos at once. Bench: SIGKILL-churn 6/6 clean without the reset vs ZeroCD roulette with it. - **New `devourer::claim_interface_reset_reopen`** (used by rxdemo/txdemo/streamtx): when a reset does re-enumerate (other chips / forced), close the stale handle, wait bounded for the *same bus+port* to return under the original VID:PID (transient ZeroCD ids are not a match), re-open and re-claim **without** a second reset — the re-enumeration itself reloaded the firmware. - **Bounded topology discovery**: with `DEVOURER_USB_BUS/PORT` set, the demos poll the named socket for up to 15 s instead of failing on the first empty scan (a closed/killed predecessor leaves the chip re-enumerating for seconds). Strict no-fall-through behavior unchanged. - Selftest: locks the issue-#294 aggregate-walk regression — `[PPDU_STS(drv_info, unit 16)][WIFI]`: the trailing WIFI frame must be reached (with the old offset formula the walk broke after the phy-status sub-packet → green bring-up, zero frames). ## Validation - SIGKILL-churn ×6 on the 8832CU, default env: 6/6 bring-ups deliver (700–1000 frames/15 s), **0 ZeroCD landings, 0 stale handles** — vs 5/6 failing before. - From VBUS cold: clean. - `tests/he_er_su_cross_rx.sh` same-die (8852C→8852C) and cross-die (8852B→8852C), ch6: **8/8 cells pass on the first attempt**, zero retries, zero VBUS cycles — vs all-day retry roulette. - 24/24 ctest. 🤖 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
HE ER SU (802.11ax Extended Range) + DCM on both Kestrel dies — the standards-based range lever of the AX generation, full kit:
DATA_BW_ER, MCS0): payload power concentrated in ~half the tones, ~3 dB moreAPI follows the established devourer pattern — no new
IRtlDevicemethod:FORMAT=EXT_SU,data5RU code (6=106 / 7=242),data3DCM bitDEVOURER_TX_RATE=HE1SS_MCS0/ER[/DCM],/ER106(HE-only suffixes);TxMode.he_er/TxMode.he_dcmprogrammaticallyAdapterCaps.he_er_su_ok(adapter.caps→he_er_su)RxAtrib.ppdu_type(7=HE_SU, 8=HE_ERSU; 0xff pre-AX) and inrx.txhit— payload rate alone cannot distinguish ER MCS0 from SU MCS0Descriptor bits are vendor-verified:
DATA_ER/DATA_BW_ERin wd_info dword0 on both dies; DCM moves with the rate word on the 8852C (DATA_DCM_V1, wd_body dword7 BIT30). Out-of-spec combos (NSS>1, MCS over the mode ceiling, DCM+STBC) clamp with aWdiagnostic instead of stalling the AX scheduler. TheSetTxModefallback now expresses HE natively — it silently aired VHT before, which the new ppdu_type witness exposed.Bug found while validating: 8852C RX payload offset
Judging the 8852C as receiver exposed a latent parser bug: the RX payload offset is
rxd + shift*2 + drv_info_size*UNIT (+4 when mac_info_vld, non-PPDU-status), and UNIT is 16 bytes on the 8852C vs 8 on the 8852B (vendorhal_trx_8852{b,c}). With the B's unit the C die "receives" frames with correct lengths but unreadable bodies (fc=0x0000, zeroed SA/RSSI) — an SA-matching harness reads that as a deaf receiver.parse_rx_8852bnow takes the per-die unit and applies the full vendor formula; selftests lock all three terms.Also: the demos'
DEVOURER_USB_BUS/PORTtopology selection is now strict — matching no device is fatal instead of silently falling through to the unrestricted VID:PID open, which grabbed the wrong adapter in same-VID:PID pairs (the exact ambiguity the knob exists to resolve).Validation
Headless:
he_radiotap_selftest(grammar → radiotap → decode round-trip + clamp rules),kestrel_rxparse_selftest(ppdu_type nibble, per-die drv_info unit, shift×2, mac_info +4) — 24/24 ctest.On-air (
tests/he_er_su_cross_rx.sh, ch6, 15 s cells, 2× RTL8832CU + 1× RTL8852BU):ER MCS1/MCS2 decode in every pairing (3-adapter triangulation). One bench trap documented: a same-die pair at point-blank range fails QPSK at the default 20 dBm while BPSK passes — near-field saturation (
DEVOURER_TX_PWR=6restores delivery), not a protocol limit.Docs:
docs/he-extended-range.md(range ladder, API, hardware mechanics, measured matrix),docs/logging.mdschema rows, README/CLAUDE.md notes.🤖 Generated with Claude Code