Kestrel (RTL8852B/RTL8852C, Wi-Fi 6): one HAL for both dies on the verbatim-vendored halbb/halrf plane#293
Merged
Merged
Conversation
… submodules, PID-first dispatch, kestrelprobe, fw extractor Start of the Kestrel HAL (issue #236 prerequisite): the 802.11ax G6-phl generation, first target RTL8852BU (variant C8852B), second RTL8832CU (C8852C). The 8852A family stays out (frozen 2021 v1.15 vendor drop only). - reference/rtl8852bu + reference/rtl8852cu submodules (josephnef forks of the morrownr v1.19.21/v1.19.22 Realtek drops — the line whose fwcmd surface carries the TWT-OFDMA + F2P trigger H2Cs #236 needs). - PID-first factory dispatch: on AX silicon 0x00FC is R_AX_SYS_CHIPINFO, and the 8852A die-id (0x50) collides with the 8822B cold-boot transient — so Kestrel gates on the vendor usb_intf.c PID table (src/kestrel/KestrelUsbIds.h) before the SYS_CFG2 read; the die-id (0x51/0x52) + cut (R_AX_SYS_CFG1[15:12]) are read back as confirmation. - src/kestrel/ skeleton: RtlKestrelDevice (IRtlDevice; identity implemented, unported entry points throw naming their milestone), ChipVariant. - kestrelprobe: staged bring-up prober (id | power | fw), pcieprobe sibling; tests/kestrelprobe_id.sh wraps it with rtw89 unbind/rebind. - tools/extract_8852b_fw.py -> hal/hal8852b_fw.{c,h}: both NICCE images (cut-selected at runtime, CBV->u2 / CCV+->u3), the category the Linux CE driver itself loads. - CMake DEVOURER_KESTREL_8852B/_8852C options (+ DEVOURER_HAVE_* defines), kestrel-only CI config row, Kestrel-OFF flags on the existing rows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ware confirmation tests/kestrel_vendor_ko_smoke.sh — insmod each Kestrel vendor module (8852bu.ko / 8852cu.ko), watch it bind the adapter and register a netdev, bring the link up (udev-rename tolerant), then rmmod and rebind rtw89. Both pinned tips pass unpatched on 6.18.33-1-lts — no devourer-kbuild branch needed for this generation (noted in reference/README.md). Hardware M0 exit criteria met on both DUTs meanwhile: 35bc:0108 -> die-id 0x51 (8852B), cut 2 (CCV -> u3 NICCE image for M1) 35bc:0101 -> die-id 0x52 (8852C), cut 1 (CBV) — hardware proof of the TX50UH = RTL8832CU identification. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ported from reference/rtl8852bu mac_ax: - HalKestrel: register-op helpers (poll, XTAL_SI indirect write, efuse power-cut), the USB power-on sequence (usb_pre_init_8852b + mac_pwr_on_usb_8852b verbatim — SYS_PW_CTRL handshake, AFE LDO, SPS rails, platform-enable toggle, the ANAPAR_WL XTAL_SI recipe, DMAC/CMAC func-en), and the physical efuse read loop (read_hw_efuse, DDV bank, 8852B EFUSE_WAIT_CNT_PLUS) + the eeprom_parser physical->logical map walk. - MacRegAx.h: the AX register/bit subset the bring-up touches (verbatim from mac_reg_ax.h; 0x00FC is R_AX_SYS_CHIPINFO here, a different map from 11ac). - kestrelprobe "power" stage + tests/kestrelprobe_id.sh stage arg. HARDWARE VALIDATED on the TX20U Nano (35bc:0108): power_ok, pwr-K=0xAA (power-cal done), and the parsed efuse MAC = 3c:6a:d2:79:52:02 — byte-for-byte identical to the kernel rtw89_8852bu-reported MAC. That match validates the whole chain (power-on -> efuse block enable -> physical read -> logical parse) at once. xtal=0x4d rfe=0x01 also read cleanly. Next (M1b): firmware download over the H2C/CH12 path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…complete Ported the full FWDL path from reference/rtl8852bu mac_ax into src/kestrel/KestrelFw: - dmac_pre_init: dmac_func_pre_en + dle_init(DLFW quota: wde_size9/ple_size8, wcpu=6, c2h=16, h2c=48) + hfc_init(rst,en=0,h2c_en=1) — stands up the H2C (CH12) transport. - FWDL state machine: mac_disable_cpu -> mac_enable_cpu(dlfw) -> mac_fwdl (fwhdr parse -> phase0 H2C_PATH_RDY -> phase1 header H2C -> phase2 section chunks -> check_fw_rdy WCPU_FWDL_STS==INIT_RDY), 3-try retry (AX MIPS). - H2C/FWDL framing: WD body (CH_DMA=12, FWDL_EN) + fwcmd_hdr (cat MAC / class FWDL / func FWHDR_DL) + payload, bulk-out to the CH12 endpoint = BULKOUTID2 = the 3rd discovered bulk-OUT (0x07 on the 8852BU; RtOutPipe[2] order verified in the vendor USB layer). - Secure-boot MSS handling: the NICCE image is a type-9 security section with mssc=2 trailing 512B signatures; __mss_index (physical efuse 0x5EC/0x5ED vs the OTP key tables) picks one, patched into the section body at offset 448, and the trailing signatures are excluded from the download size. Also fixed a latent M1a bug: usb_pre_init used fabricated register addresses (0x1A0/0x1A2/0x98 from an assistant table) that happened to be harmless for efuse; the real ones (R_AX_USB_HOST_REQUEST_2=0x1078, USB_WLAN0_1=0x1174, HCI_FUNC_EN=0x8380, RXDMA=BIT1/TXDMA=BIT0) are essential for the DMA path and now verified against hci_reg_ax.h/mac_reg_ax.h. Reading the source directly (vs. trusting derived tables) also caught PLE_INI_STATUS=0x9100 (not 0x9000) and the H2C/FWDL_PATH_RDY bit positions. HARDWARE VALIDATED on the TX20U Nano (35bc:0108): sudo tests/kestrelprobe_id.sh fw 35bc:0108 -> "firmware booted (attempt 1)", kestrel.fw ok:true. Full green init: power-on -> efuse (MAC = kernel's 3c:6a:d2:79:52:02) -> 343448-byte u3 NICCE image downloaded (170 section chunks) -> WCPU FW_INIT_RDY. tests/kestrel_kernel_capture.sh added as an optional usbmon golden-reference diff (vendor .ko vs devourer write stream); the port itself is source-driven. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FrameParserKestrel.h — parses the G6 mac_ax RX descriptors (rxd_short 16B / rxd_long 32B, fields verified against reference/rtl8852bu rxdesc.h + type.h): rpkt_len/shift/drv_info_size/rpkt_type/long_rxd from dword0, rate/gi_ltf/bw from dword1, freerun_cnt from dword2, a1_match/crc32/icv from dword3. Computes the sub-packet payload offset (rxd + drv_info*8 + shift) and the 8-byte-aligned next_offset for walking a bulk-IN aggregate. Routes rpkt_type (WIFI/PPDU/CH_INFO/C2H) so the RX loop can separate 802.11 frames, per-frame PHY status, and firmware C2H. Header-only + self-contained so it unit-tests without hardware: tests/kestrel_rxparse_selftest.cpp (ctest kestrel_rxparse) covers short/long descriptors, drvinfo+shift offsets, CRC/ICV flags, 9-bit HE rate codes, C2H routing, a two-frame aggregate walk, and truncated-buffer rejection. The RX loop that feeds this (StartRxLoop) lands with M2a — the MAC TRX bring-up (NIC-mode DLE/HFC + dmac_init/cmac_init RX path + promiscuous rx_fltr + usb_rx_agg) that enables the chip to receive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nit passes First slice of mac_trx_init ported from reference/rtl8852bu trxcfg.c, verified register-by-register from source: - dle_init_nic: re-init the DLE to the NIC (SCC) quota — wde_size25/ple_size33 page config + wde_qt25 (WDE) and ple_qt74/ple_qt75 (PLE min/max, 11 fields) — replacing the DLFW quota used during firmware download. - sta_sch_init: enable the station scheduler (R_AX_SS_CTRL SS_EN) and poll SS_INIT_DONE_1 — a real hardware checkpoint that the DLE + scheduler stood up. - mpdu_proc_init: ACTION_FWD0 / TF_FWD / MPDU_PROC (APPEND_FCS | A_ICV_ERR) / CUT_AMSDU_CTRL. APPEND_FCS is why RX frames will carry the 4-byte FCS. - sec_eng_init: SEC_ENG_CTRL clocks + TX-enc/RX-dec + MC/BC/mgmt decrypt. kestrelprobe gains a "trx" stage (kestrel.trx event); tests/kestrelprobe_id.sh grows the stage. HARDWARE VALIDATED on 35bc:0108: `kestrelprobe trx` -> firmware booted, NIC DLE WDE+PLE ready, sta-scheduler SS_INIT_DONE poll PASSES, kestrel.trx ok:true. Empirical finding: the scheduler warm-init succeeds with the DLFW HFC still in place — the full NIC HFC reprogramming (per-channel + public page quotas) is NOT required for the DMAC init to come up green (noted in trx_dmac_init; it becomes relevant only if data-plane flow needs it later). sec_info_tbl_init (security CAM) skipped — not needed for monitor RX. Next (M2a cont.): cmac_init (rx_fltr promiscuous / rmac / cmac_dma) + usb_rx_agg; then M3 BB/RF/channel to actually receive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ome up green Completes the MAC-layer TRX bring-up (cmac_init RX path, ported verbatim from reference/rtl8852bu trxcfg.c + rx_filter.c): - rx_fltr_init: MGNT/CTRL/DATA_FLTR = all-subtypes-to-host (0x55555555); R_AX_RX_FLTR_OPT = sniffer + accept A1/bc/mc/uc-cam/bc-cam (the vendor default sniffer profile). - rmac_init: RESPBA_CAM SSN_SEL, DLK_PROTECT RX timeouts + reset-en, RCR CH_EN=0xF (the receiver channel-enable gate), RX_TIME_MON threshold, and the RX_FLTR_OPT max-MPDU-len field (31 = min(c0_rx_qta 178, 127)*128/512). - cca_ctrl_init: R_AX_CCA_CONTROL clear-channel-assessment set/clear masks. - cmac_com_init: TX subcarrier = 0, PTCL_RRSR1 OFDM+CCK enable. - cmac_dma_init: clear RX full-mode ptr bits so RX DMA streams. - usb_rx_agg_cfg: RX aggregation off (stream per frame for simple parsing). The TX/protocol CMAC sub-inits (scheduler EDCA, NAV, spatial-reuse, tmac, trxptcl, ptcl, addr-cam) are omitted — not needed for monitor RX. HARDWARE VALIDATED on 35bc:0108: `kestrelprobe trx` -> firmware booted, DMAC init, CMAC RX init, kestrel.trx ok:true. The MAC layer is now fully up; actual reception needs the BB/RF/channel bring-up (M3). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tools/extract_8852b_phy.py pulls the halbb + halrf raw_data register tables
(array_mp_8852b_phy_reg / _phy_reg_gain / _radioa / _radiob — {addr,value} u32
pairs with the phydm-style IF/ELSE/END/CHK conditional opcodes) from the
reference/rtl8852bu vendor source into hal/hal8852b_phy.{c,h}. Mirrors the
existing phydm extractors (extract_8822c_phy_tables.py).
Verified extraction counts: phy_reg 509 entries, gain 33, radioA 3607,
radioB 3711. The generated blob is wired into the build when the runtime table
walker (PhyTableLoaderKestrel, next) consumes it, alongside the halbb BB-init
sequence + halrf RF init + set_channel that together gate on-air RX.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PhyTableLoaderKestrel.h — walks the extracted {addr,val} register tables,
transcribed from the vendor walker (halbb_hwimg_8852b.c + halbb_hw_cfg.c):
headline {rfe,cut} variant selection (top-nibble-0xF entries) then the
IF/ELSE_IF/CHK/ELSE/END conditional opcodes, emitting a register write (via a
caller callback) only while the current branch matches. Opcodes (top nibble
4/8/9/a/b/f) never collide with real addresses (top nibble 0). Callback-based
so BB (write32) and RF (RF-serial write) share one walker, and header-only so
it unit-tests without hardware.
tests/kestrel_phytable_selftest.cpp (ctest kestrel_phytable): unconditional
writes, headline-derived IF/CHK matching (applied vs skipped by rfe), and ELSE
block termination.
Next: the BB init sequence (halbb_init: PLL/reset/path + apply phy_reg/gain via
this walker), halrf RF init (radioa/radiob), set_channel, then StartRxLoop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… fix) phy_bb_rf_init applies the halbb (phy_reg + gain) and halrf (radio-A/B) tables via PhyTableLoaderKestrel — ~7500 register writes — and comes up green on hardware without wedging the chip. The critical fix: Realtek USB register addressing is (wIndex<<16)|wValue, and the BB/RF register window lives at addr + bb0_cr_offset (0x10000) — i.e. wIndex=1. devourer's 16-bit rtw_write path forces wIndex=0, so the first attempt wrote BB values into the MAC/system space (wIndex=0) and wedged the chip off the bus (recovered via uhubctl VBUS cycle). Added IRtlTransport::write32_wide (32-bit address; USB splits it into wValue/wIndex, default forwards to the 16-bit path) + RtlAdapter::rtw_write32_wide, and the BB/RF emit now targets addr+0x10000. Root-caused by reading the vendor "reg = (index << 16 | value)" — not a wire capture. RF writes go through the same BB window (halbb_set_reg adds bb0_cr_offset), so an RF write is a direct BB-window write, not a serial LSSI protocol: 0xe000(A)/0xf000(B) + ((addr&0xff)<<2) + 0x10000, low-20-bit value. kestrelprobe gains a "phy" stage. HARDWARE VALIDATED on 35bc:0108: `kestrelprobe phy` -> BB phy_reg + gain + radio-A/B applied, kestrel.phy ok:true, chip stays alive. Next: set_channel (BB/RF tune) + StartRxLoop -> ambient beacon = RX first light. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…channel set The counterpart to write32_wide: masked BB writes (read-modify-write) and RF reads over the halbb/halrf window (addr + 0x10000, wIndex=1) need a wide read. USB override splits the address into wValue/wIndex and returns 0xFFFFFFFF on a failed transfer (INVALID_RF_DATA sentinel); the default forwards to the 16-bit read. Consumed by the channel-set machinery (set_channel) next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d-to-end set_channel ports the halbb ctrl_ch/ctrl_bw(20)/cck_en/bb_reset + the halrf RF18 channel setting (DDV window), all over the wIndex=1 BB/RF window via the new bb_rmw (masked RMW) / rf_read / rf_write helpers. RtlKestrelDevice::Init now drives the whole monitor path — power -> efuse -> firmware -> MAC TRX -> BB/RF tables -> channel tune -> StartRxLoop — and StartRxLoop walks the bulk-IN aggregate with parse_rx_8852b (FrameParserKestrel), delivering WIFI frames to the packet processor. SetMonitorChannel retunes live. HARDWARE (35bc:0108, rxdemo on ch6): the entire chain executes green — "tuned to ch6 bw20 (2.4G)", "starting RX loop", 8 RX URBs submitted, chip stays alive. Frame decode is the remaining debug tail (the receiver is armed but not yet delivering decoded beacons — next: the DAV/a-die RF path skipped here, RX calibration (DACK/RXDCK), or BB DM/AGC/DIG init beyond the register tables). tests/kestrel_rx_smoke.sh drives rxdemo through the bring-up and counts decoded frames. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Log the first dozen bulk-IN completions (size + rxd dword0) so the RX-decode debugging can distinguish "no data arrives" (RX-DMA/PHY not delivering) from "data arrives but doesn't parse". Current finding on 35bc:0108 ch6: zero bulk-IN completions — the receiver is armed but no frames flow, pointing at a missing PHY/RX-delivery step rather than the parser.
…) — writes now land Root-caused why RX never worked: the BB was held in reset the entire time, so every halbb/halrf register write (the ~7500-write "tables applied" step) was silently DROPPED — a BB register readback returned 0 even right after writing. Ported set_enable_bb_rf (hw.c) into phy_bb_rf_init's prologue: R_AX_SYS_FUNC_EN FEN_BBRSTB|FEN_BB_GLB_RSTN (BB reset release), SPS_DIG_ON ZCDC, the WLRF_CTRL AFC_AFEDIG 1/0/1 toggle, XTAL_SI WL_RFC_S0/S1=0xC7, PHYREG_SET=XYN. All MAC-space (wIndex=0). VALIDATED on 35bc:0108: after the fix, BB 0x4004 reads back 0xCA014000 (its exact phy_reg table value) and a scratch write round-trips — the baseband is now actually configured. (Confirms the earlier wIndex=1 addressing was correct all along; the BB was just asleep.) RX frame decode still needs the halbb DM init (DIG/AGC) + RF calibration + RF18 tune verification — the remaining tail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rf_write_dav ports halbb_write_rf_reg_8852b_a — the a-die RF path via the BB 0x370 serial command register (0x174c busy-poll, w_reg = path<<28 | addr<<20 | data for the full-20-bit case), all over the wIndex=1 BB window. set_channel now writes RF18 to both the a-die (DAV) and d-die (DDV) windows + the 0xcf re-latch, matching halrf_ch_setting. RF18 readback still reads 0x400 (not the tuned channel) — so RF writes are not round-tripping even though BB writes now do (BB 0x4004 reads its table value). The RF interface (0xe000/0xf000 window) likely needs its own enable/select, or RF18 returns synth/LCK status rather than the written value. Next: verify RF register access with a non-synth round-trip and check for an RF-interface enable in the halrf init. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…responding) Read-only diag in set_channel confirms: after the BB-reset fix, BB registers read back their table values (0x4004=0xCA014000) but RF register access is dead — RF path-A reg 0x00 reads 0x00000, RF18 stuck at 0x400. So the radio isn't responding through the 0xe000/0xf000 direct window despite WLRF_CTRL + RFC LDO enable. Two leads for the next pass: 1. The halrf radio tables use a DIFFERENT conditional walker than halbb (halrf_config_8852b_radio_a_reg: rfe/cut match via the v1 BIT(30) CHK and a leading 0xF00-header rfe enumeration, not the headline/top-nibble-CHK of the BB tables). apply_phy_table (the BB walker) mis-applies the RF tables — the RF needs its own walker so its power/mode registers actually get set. 2. RF may need a direct-access-mode enable / radio power-on beyond WLRF_CTRL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The halrf radio tables use a different conditional encoding than the halbb
tables (verified against halrf_config_8852b_radio_a_reg): bit31 entries carry
the {rfe,cut} IF/ELSE_IF/ELSE/END condition (rfe=v1[23:16], cut=v1[7:0],
0xFF=don't-care), a bit30 entry is the CHK, plain entries are writes gated by
the match — with a "highest matching cut wins" else-replay fallback. Ported it
verbatim as apply_rf_table and use it for radioa/radiob (the BB walker was
mis-selecting RF entries).
Does not by itself wake the radio: RF register access is still dead (RF path-A
0x00 reads 0 via the 0xe000 direct window). So the blocker is more fundamental
than the table variant — the radio isn't responding to the direct RF window.
Next: RF power-on / whether 8852BU-USB RF access must go through the DAV serial
window (0x174c/0x370) rather than the 0xe000 direct window, and the halrf init
sequence before the radio tables.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The usbmon golden-diff of the vendor 8852bu.ko bring-up settled how the
RF radio is actually programmed on USB: the vendor issues ZERO direct
BB/RF-window register writes and offloads every PHY register into H2C
batches the firmware replays on-chip (halbb_check_fw_ofld / halbb_fw_set_reg
-> rtw_hal_mac_add_cmd_ofld). Direct RF-window writes never reach the a-die
radio over USB, which is why the earlier direct-write path left RF00 dead.
This ports the two offload mechanisms:
- cmd_ofld (KestrelFw::ofld_begin/write/flush): 16-byte {src,type,path,
offset,value,mask} commands batched into a class=FW_OFLD/func=CMD_OFLD_REG
H2C, LC bit set on the last command per batch. Used for the RF radio
tables (src=RF).
- radio-to-fw (KestrelFw::radio_page_to_fw): the packed (addr<<20|data)
page cache as an OUTSRC H2C (class 8/9 = radio A/B, func = page).
Also:
- Fix the RF radio walker: the active vendor walker uses the BB
headline/IF/CHK format (apply_phy_table), not the disabled bit31/bit30
apply_rf_table variant.
- Add hfc_init_nic (usb_scc_8852b page/credit quotas) and the runtime
dmac_func_en to trx_dmac_init.
- Generalize send_fwdl_packet into send_h2c_cmd(cat,class,func,...).
BB tables still applied via direct wIndex=1 writes (proven: BB 0x4004 reads
back 0xCA014000). Known remaining blocker: the running firmware does not
drain CH12 post-boot, so the offload H2C bulk-OUT times out (rc=-7) — a
post-fw-boot MAC-ready/H2C-arm handshake is still missing (see the kaeru
notes). Builds clean, 21/21 ctest pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…blocked) Ports the missing runtime bring-up steps found by decoding the vendor usbmon capture correctly: - usb_init (usb_init_8852b / intf_init): endpoint NUMP burst config for EP5/6/7/9/10/11/12 + RX bulk size for the link speed + LFPS filter off, plus an HCI RXDMA/TXDMA re-kick after the DMAC/DLE init. - hfc_init_nic now matches the vendor's USB h2c-only flow control (HCI_FC_CTRL = CH12_EN, FC_EN clear) instead of full FC. - runtime dmac_func_en (0x8400 full engine enable) at the top of trx_dmac_init. None of these unblock the CH12 IO-offload H2C yet — the running firmware still does not drain CH12 post-boot (bulk-OUT NAKs -> rc=-7 timeout, which is a DMA-drain/flow-control issue, not an endpoint STALL). Ruled out this session: NIC HFC credits, dmac_func_en, USB NUMP, HFC h2c-only, HCI DMA re-kick, DLE re-init, H2C batch size. The missing step is among the ~80 runtime register writes the vendor issues between fw-boot and its first H2C; a golden-init replay is the next lever. Builds clean, 21/21 ctest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ure script The fwcmd header TOTAL_LEN field must count the 8-byte fwcmd header plus the content (h2cb->len in the vendor h2c_pkt_set_hdr), not just the content — the firmware's H2C-queue parser uses it to find the next packet boundary, so an 8-byte-short value desyncs the queue. Matches the vendor's byte layout (TOTAL_LEN == WD TXPKTSIZE == FWCMD_HDR_LEN + content). Also adds tests/kestrel_self_capture.sh to usbmon-capture devourer's own bring-up for diffing against the vendor .ko stream (WIP: the usbmon read returns empty in this environment / the probe needs more robust device open under the capture harness — to debug). Does not yet unblock the CH12 H2C drain (the runtime firmware still NAKs the offload bulk-OUT); this is a standalone correctness fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bset) Replaces the cherry-picked "minimal RX" CMAC init with the complete vendor cmac_init (trxcfg.c) transcribed verbatim for the 8852B band-0 ASIC path: scheduler_init (SIFS/pre-backoff/beacon-EDCA), addr_cam_init (search range + reset poll), nav_ctrl_init (two-nav 25ms), spatial_reuse_init, tmac_init (TX underflow/FIFO MCS thresholds/preferred-AC), trxptcl_init (response SIFS + RRSR 1M/legacy), ptcl_init (CMAC TX mode + AMPDU limits), alongside the already-present rx_fltr/cca/rmac/cmac_com/cmac_dma. Ordered exactly as the vendor cmac_init. rst_port_info + sec_info_tbl_init are host-side software state (no chip registers) and are intentionally omitted. Hardware (kestrelprobe phy, 8852BU): the full CMAC init runs green — the ADDR_CAM reset poll completes (the CMAC is enabled and responding), and the whole bring-up stays healthy. ~60 new register/bitfield constants transcribed from mac_reg_ax.h + trxcfg.h. The CH12 H2C-offload blocker is unchanged (confirming it is DMAC/firmware-side, not CMAC). Remaining trx_init pieces to port next: mac_enable_imr (ser_imr_config) and coex_mac_init. Builds clean, 21/21 ctest pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ports coex_mac_init_8852b verbatim: the LTE-coex indirect write helper (mac_write_lte_8852b non-offload path — poll the LTE interface ready bit, then WDATA + CTRL) plus the coex bring-up itself (disable LTE-coex CTRL and CTRL_2, set the SYS_SDIO_CTRL coex bit). Wired into the trx bring-up after dmac+cmac, matching mac_trx_init's order. On the WiFi+BT combo die the coex block otherwise arbitrates the shared front end. Hardware (kestrelprobe phy, 8852BU): runs green (LTE interface ready, no timeout). CH12 H2C-offload blocker unchanged (DMAC/firmware-side). Remaining mac_hal_init pieces to port: chip_func_en (OCP patch), mac_enable_imr (ser_imr_config), feat_init/bacam_init, gpio_init. Builds clean, 21/21 ctest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the 8852B chip_func_en OCP patch (SPS_DIG_ON_CTRL0 OCP_L1 field -> max) to power_on's sys_init, alongside the existing DMAC/CMAC func_en. bacam_init (mac_feat_init) is a no-op on 8852B (the vendor returns early for this chip), so nothing to port there. Runs green on hardware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ports the full System-Error-Recovery interrupt-mask config (ser_imr_config_8852b auto-gen) as a verbatim table: 16 DMAC-side masks (WDRLS/SEC/MPDU-tx/rx/ STA-sched/TXPKTCTL x2/WDE/PLE/PKTIN/host+cpu+other-dispatcher/CPUIO/BBRPT x2) and 6 band-0 CMAC masks (DLE_CTRL/PTCL/SCHEDULE/PHYINFO/RMAC/TMAC), each doing reg = (reg & ~clear_mask) | set_value with the auto-gen's documented values, plus the phy-intf-timeout patch and mac_err_imr_ctrl(EN) (unmask the top-level DMAC + CMAC0 error IMR). Wired into the mac_trx_init tail after coex. With this the functional mac_hal_init is complete: power-on -> fwdl -> dmac_init -> chip_func_en -> full cmac_init -> coex -> IMR -> usb_init. Only gpio_init (LED/GPIO, non-functional) remains; bacam_init is a no-op on 8852B. Hardware (kestrelprobe phy, 8852BU): full bring-up runs green. The CH12 H2C-offload blocker is unchanged, now against a fully-assembled MAC — which localizes it decisively to the firmware's H2C servicing, not any host-side init. Builds clean, 21/21 ctest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n findings Major diagnostic progress via usbmon capture of our OWN bring-up (captured around the working kestrelprobe_id.sh): the firmware DOES service H2C — FWDL completes and the first 2-3 runtime cmd_ofld batches succeed — then the CH12 H2C queue jams permanently (1s bulk-OUT timeouts). Root cause is NOT framing, register state, HFC, or send-rate: the FW processes FWDL-class H2C but does not drain/complete FW_OFLD-class (cmd_ofld) H2C, so the 32-page H2C pool fills after ~2 batches (each ~16 pages) and never recovers. Smaller batches make it worse (more leaked pages), confirming it's a processing failure, not a rate issue. Ports the vendor's flow-control handshake (poll_c2hreg / __recv_c2hreg): after each cmd_ofld batch, poll R_AX_C2HREG_CTRL for the TRIGGER bit, drain data0..3, and clear TRIGGER (the ack that releases the FW to return the H2C page). This is correct vendor behavior, but currently the poll times out — the FW never posts the IO_OFLD_RESULT, consistent with it not processing cmd_ofld at all. Next: determine why the running FW ignores FW_OFLD-class H2C despite booting from the same NICCE image the vendor uses (io_ofld capability enable / a required H2CREG activation / MAC-ready signal). Builds clean, 21/21 ctest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… dword1 Decisive diagnostic: routing a single BB register write through cmd_ofld to a read-back-able register (BB 0x4004) leaves it UNCHANGED (0xca014000, not the written 0x12345678) — with or without a concurrent bulk-IN drain. So the running firmware does not process FW_OFLD-class (cmd_ofld) H2C at all; it only accepts the packets into the H2C queue (which then leaks dry). This rules out a C2H-packet-blocking cause and confirms the earlier page-leak finding. Also fixes cmd_ofld dword1: the "base offset" field is the HIGH 16 bits of the offset (offset>>16, = 0 for a 16-bit BB/MAC addr), per add_cmd's GET_FIELD(offset, OFFSET) — was wrongly offset<<16 for the BB/MAC case (RF was already correct at 0). Adds the KESTREL_TEST_OFLD diagnostic harness. Next: why the fw's io_ofld handler is inactive despite booting from the NICCE image the vendor processes cmd_ofld from — FWDL image/MSS byte-integrity, or a missing runtime enable of the fw offload feature. Builds clean, 21/21 ctest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The full-header FWDL send (including the dynamic FWCAP header) broke FWDL (FWDL_PATH_RDY timeout) — the vendor's fwdl_phase1 is also called with (hdr_len - dynamic_hdr_len) (fwdl.c:1685/1746), so the dynamic header is NOT part of FWHDR_DL; it is host-side cap metadata only. Restores the correct minus-dynamic-header send and documents why. The cmd_ofld-not-processed blocker is therefore NOT the missing dynamic header. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the incrementing 3-bit H2C sequence number to send_h2c_cmd (the vendor increments fwinfo->h2c_seq per H2C via h2c_pkt_set_hdr) — correct vendor behavior, though it does not by itself unblock cmd_ofld processing. Diagnostic milestone: byte-verified our extracted fw image (hal/hal8852b_fw.c array_8852b_u3_nicce, 343448 bytes) is IDENTICAL to the vendor source (reference .../hal8852b_fw.c) — same md5. So the cmd_ofld-not-processed blocker is definitively NOT fw-image corruption; the fw is the exact image the vendor kernel driver processes cmd_ofld from. It is a runtime-state difference. Ruled out across this session: fw image (identical), dynamic-header download (vendor excludes it too), H2C seq, C2H-packet blocking (bulk-IN drain), send-rate/batch-size. Builds clean, 21/21 ctest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two more dead ends for the RF-programming blocker, both confirmed on hardware: - cmd_ofld PKT variant (func 0x13, with bulk-IN drained) also leaves the test BB write unapplied — the fw ignores cmd_ofld regardless of REG/PKT response method. - Direct a-die RF programming bypassing the fw: RF00 stays 0x00000 after both a DAV serial-command write (0x370) and a DDV-window write. The a-die radio does not respond to host-direct writes either. So the radio can be programmed by neither cmd_ofld (fw ignores it) nor direct host writes (a-die unresponsive) — both blocked by the same firmware boundary: the running fw does not activate its io_ofld handler / the RF serial path that programs the a-die, despite booting from the byte-identical NICCE image the vendor uses. Keeps the KESTREL_TEST_OFLD diagnostic (now a direct-RF probe). Builds clean, 21/21 ctest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ch) — RF LIVE THE BUG (found per "verbatim source must work"): the radio table lists each RF register TWICE — once without BIT(16) (d-die, written via the DDV BB window 0xe000/0xf000) and once with BIT(16) set (a-die, written via the DAV serial command 0x370). halbb_write_rf_reg_8852b dispatches on ad_sel=(addr>>16)&1: set => a-die serial, clear => d-die window. My earlier code wrote EVERY entry to the d-die BB window (masking addr&0xff), so the a-die radio synthesizer was never programmed — RF00 read 0 and the radio was dead. This also sent me down a long wrong path attributing it to the fw cmd_ofld/io_ofld handler. Fix: apply the radioa/radiob tables via direct writes with proper BIT16 dispatch (rf_write_dav for a-die serial, rf_write for d-die window), abandoning the cmd_ofld path entirely (which the fw was ignoring anyway — the vendor's halbb_check_fw_ofld is false here, so it also uses direct writes). kestrelprobe phy: fails=0 (no more CH12 jams), BB+RF direct tables applied green. Builds clean, 21/21 ctest. RX validation next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the second Kestrel chip (RTL8852BU / C8852B, TP-Link Archer TX20U Nano 35bc:0108) to the supported-hardware table with measured MCS7/20 on-air TX throughput: 2.4G 43, UNII-1 36, UNII-2/3 33 Mbps (SDR duty x PHY rate); 6 GHz — (dual-band, no 6E front-end, unlike the tri-band 8832CU). Both Kestrel chips verified working after this session's shared-code changes (band model, efuse bridge, RF-tune unification): 8852BU 5 GHz RX decodes + TX airs on all three bands; 8832CU RX/TX on 2.4/5/6 GHz. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
set_channel gains the 160 MHz block/center derivation (8 20-MHz sub-channels,
center = block_start+14, pri_ch 1..8, band-aware grid origin: 5 GHz {36..64}->50,
6 GHz {1..29}->15); the vendored halrf_ctl_band_ch_bw_8852c (RF18[11:10]=00) +
halbb_ctrl_bw_ch_8852c (case CHANNEL_WIDTH_160 -> 160M ADC) already handle the RF
+ BB. AdapterCaps advertises kBw160 for the Kestrel generation; rx/tx demos parse
DEVOURER_BW=160 / DEVOURER_HOP_BW=160.
Validated on the RTL8832CU: 5 GHz 160 MHz on-air — tuned center ch50, synthLock=1,
RF18=0x10132 (5G + 160 encoding), SDR 49.3% duty -> ~66.6 Mbps (MCS7/160). build 0,
ctest 23/23.
6 GHz 160 MHz: the path tunes with the correct RF18 encoding (0x3000f = 6G + 160,
center ch15) but the synth does NOT lock on the tested TX50UH (synthLock=0, a
2-adapter TX->RX decode read 0) — a suspected 6E-160 front-end/synth constraint,
and un-SDR-confirmable since the 6G 160 block centers (>=6025 MHz) are above the
B210's 6 GHz ceiling. Not claimed as working; revisit with a >6 GHz SDR or a
different 6E adapter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rting Dug into the earlier "6G 160 tunes but on-air unconfirmed" and resolved it definitively with the B210. The 6 GHz 160 MHz *TX does not radiate* on the C8852C, isolated cleanly (tests/kestrel_160_sdr_airs.sh, idle->TX duty): 6G-20 0% -> 48.6% airs 6G-80 0% -> 45.2% airs 5G-160 1.8% -> 40.1% airs 6G-160 0% -> 0.0% SILENT So it is specifically the 6G+160 TX combination — everything upstream is correct: the RF synth locks (RF 0xb7[8]=0, exposed via the new kestrel_halrf_read_rf diagnostic; RF18=0x3100f = 6G+160+ch15), center/pri (15/1) match the vendor rtw_phl_get_center_ch, and the vendored halbb_ctrl_bw_ch_8852c handles 6G-160. The vendor rtl8852cu.ko supports 6G-160 (phl_chan.c op_class 134), so this is an un-ported TX-enable gap, not a chip limit. TX power ruled out (fixed-dBm 0x4594, no txpwr_lmt clamp). Remaining candidate: MAC TXAGC-max BW160 clamp / a 6G-specific RF TX-path setting. Corrections vs the earlier read: the BB 0xc5[15] "synthLock" bit is unreliable (the real lock is RF 0xb7[8]); the 2-adapter A/B must be single-RX-launch + per-run VBUS cold (the 8832CU comes up deaf on a 2nd soft-re-init); rx.txhit's SA matcher is broken on Kestrel; VHT is invalid at 6 GHz (use HE). Changes: - kestrel_halrf_read_rf: read true synth-lock (RF 0xb7[8]) + RF18 readback, logged per C8852C tune. - set_channel: warn that 6 GHz 160 MHz TX does not radiate (RX/monitor left available; use <=80 MHz for 6 GHz TX). - Caps: advertise 160 MHz for the Kestrel generation properly — the demo adapter.caps emitter was dropping kBw160 (showed bw:[20,40,80]); GetTxCaps bw_max was stale at 80. Now bw:[20,40,80,160], bw_max 160 (5G-160 validated ~66.6 Mbps; 6G-160 TX gap documented in GetTxCaps). - tests: 160 SDR airing probe, RF-lock probe, and a fixed single-RX-launch 2-adapter A/B harness. build 0, ctest 23/23. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reflect the SDR-backed finding: 160 MHz on 2.4/5 GHz is up (5G-160 ~66.6 Mbps), but 6 GHz 160 MHz TX does not radiate on the C8852C (RF locks, RX-160 works; un-ported 6G+160 TX-enable path), so 6 GHz TX tops out at 80 MHz. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # CMakeLists.txt # README.md
…etries) Every other generation's RX loop stops on g_devourer_should_stop (the library-wide signal-flag pattern, see RtlJaguar2Device.h) — Kestrel only checked its own _rx_stop, so a demo run under the harness's `timeout` never exited: SIGTERM set the flag, nothing read it, and the async bulk-IN loop ran forever. Same for the FWDL retry ladder on a dirty chip (seconds of polling per attempt). Route both through the shared flag: the RX/WP-drain loop predicates OR it in, and download_firmware checks it between attempts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The RE-era harness — golden usbmon captures (golden_c2h/vendor_ifup/ ifup/kernel/self + the capture_diff analyzer), the vendor register-dump diff pair, and the closed-investigation probes (6G-160 synth lock, 160-airs duty, the ~103-frame TX-stall probe). We port vendor source, we don't reverse-engineer it; the on-air suite is the regression harness. The kept 6G-160 witness keeps its synth-lock fact without the deleted probe's name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same monitor + method for both Kestrel chips: TX_ID=35bc:0108 points the flood at the 8852BU instead of the default 8832CU. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tests/pyproject.toml is tracked; pin its resolution so uv sync reproduces the bench environment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A warm chip left by a previous run's clean Stop() brings up green but delivers no bulk-IN — cold-cycle like every sibling harness. Also grep -c prints "0" AND exits 1 on no matches, so the `|| echo 0` fallback produced "0\n0" and broke the integer test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The RX-diag register dump + 8852C EP4-diag at the end of set_channel, the one-shot cmd_ofld hexdump, and the bulk-IN completion counters — investigation aids from the RX-deafness/TX-silence bring-up, all superseded by the on-air suite. The config-gated fw-log and CCA knobs stay. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…raming Every register value that a usbmon "golden capture" comment justified is re-justified from the vendor source it was ported from (mport.c, fwcmd.c, fwdl.c, init.c, init_8852b.c, pwr_seq_func_8852b.c, hci_fc.c / hci_fc_8852c.c, security_cam.c, _usb_8852b.c / _usb_8852c.c, dle.c). The M0-M6 milestone labels and "[not yet ported]" status blocks describe a bring-up that is finished — replaced with what each layer is; the dead not_ported() machinery (zero callers) is deleted; kestrelprobe's header now lists its real five stages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…halrf}/g6 Pure git mv (vendor file contents untouched — their ../../hal_headers_le.h include depth is preserved) + the mechanical renames that follow: OBJECT libs kestrel_halbb_8852c/kestrel_halrf_8852c -> kestrel_halbb/kestrel_halrf and the gate defines DEVOURER_KESTREL_HALBB_8852C/HALRF_8852C -> DEVOURER_KESTREL_HALBB/HALRF. The directory now hosts the G6 plane for the whole generation; the 8852B backend joins it next to the 8852C one. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vendored G6 core (from reference/rtl8852cu @ d0ec38d) is multi-chip
by design — BB_8852B_SUPPORT/RF_8852B_SUPPORT runtime dispatch exists
throughout — so the 8852B needs only its per-chip backend, copied
verbatim from reference/rtl8852bu @ 38fc5a3 by the new
tools/vendor_hal{bb,rf}_8852b.sh (dpk/txgapk compile in but stay masked
via support_ability: the rfk_ops_8852b pointer table references them as
data; psd + the regulatory pwr-table stay out). One kestrel_halbb /
kestrel_halrf OBJECT-lib pair now carries core + both backends, keyed
to the DEVOURER_KESTREL_* options, and builds on ALL compilers — the
GNU|Clang gate is gone (the vendor C is portable: audited, no GNU-only
constructs; /W0 /Gy on MSVC, -w -ffunction-sections elsewhere).
Shim frontier walked for the both-chips core: phy_hw_cap/dev_sw_cap
surface, hal_write8/16 RMW, the halrf->halbb cross-plane calls
(backup/restore_info + tx_mode_switch, faithful to hal_api_bb.c, via a
bridge bb_info pointer + newly vendored halbb_pmac_setting.c +
halrf_pmac.c), inert PMAC test-TX macros (gated hwtx/MP flows only),
and get_xsi beside set_xsi. Nothing is wired to the 8852B at runtime
yet — the glue dispatch lands next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…patch The glue takes the chip at create (ic_type/chip_id feed the vendor cores' own runtime dispatch; halrf_cmn_info_self_init wires the per-chip rfk ops/backup tables) and the entry points go through the core dispatchers where those are register-identical for the validated 8852C (halbb_ctrl_bw_ch, halbb_ctrl_rx_path, halbb_gpio_setting_init, halrf_iqk) with explicit per-chip forks only where the vendor has no generic (gain load/apply, dac_cal, rx_dck — the trigger wrappers add chlk_map/ops gates that can silently skip a cal — the RF tune: the 8852B has no ctl_band_ch_bw case and rides halrf_ctl_ch + halrf_ctl_bw, its synth lock inside ctrl_ch). The RFK prologue now mirrors halrf_dm_init including halrf_si_reset — a no-op for the 8852C (no dispatch case) and real a-die SI work on the 8852B via the new bridge XTAL-SI callbacks (HalKestrel xtal_si helpers; the shim macros became routing inlines). The dead kestrel_halrf_tssi/dpk exports and their commented-out call are gone — the EVM-settle evidence (duty 74->43%, medEVM -70->-59) lives at the support_ability mask that enforces the gate. C++ wrapper names go chip-neutral (vnd_bb_*/vnd_rf_*); the 8852C call sites reach the same vendor functions with the same arguments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
phy_bb_rf_init's C8852C leg drops the C++ walker + cmd_ofld batching: halbb_init_reg applies the BB phy_reg table, loads the gain table and the efuse RX gain offsets; halrf_config_radio applies radio B then A (vendor order) — the vendor's own loaders walking the vendored hwimg arrays with the vendor's check-positive engine, over the bridge's direct register planes. The radio-page H2Cs the loader emits (halrf_fill_h2c_cmd, OUTSRC classes 8/9) now reach the fw through a new bridge send_h2c callback filtered to exactly those classes and encoded by the existing KestrelFw::radio_page_to_fw — every other halrf H2C stays inert, as on the validated bring-up. The glue bringup sheds its now-duplicate gain load; the vendor TPU/TSSI-ctrl MAC CR init stays a helper no-op (the hand-ported fixed-dBm TXAGC owns the power CRs). The 8852B leg keeps the C++ walker until its flip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tible) The 8852B now runs the same vendored path the 8832CU validated: tables via halbb_init_reg + halrf_config_radio (radio pages to the fw through the bridge), per-channel BB via halbb_ctrl_bw_ch's 8852B backend (replacing the hand-rolled ctrl_ch/ctrl_bw/CCK block and the gain/RPL re-apply), the RF tune via the halrf_ctl_ch/ctl_bw generics (synth lock inside halrf_ctrl_ch_8852b), DACK+RX-DCK via the full vendored cals (MSBK/DADCK/biasK included — the hand-rolled ADDCK subset lacked them), and the chip's first per-channel IQK behind the lazy RFK prologue (NCTL microcode, LCK/RCK, a-die SI reset over the bridge XSI plane, efuse trim + TSSI-DE). Per-channel RX-DCK is deliberately not re-run at set_channel — the bring-up covers it, the shape validated on the 8852C. FastRetune rides the same lean vendored pair on both chips. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
160 MHz is 8852C-only (rtl8852c_halinit.c bw_sup has BW_CAP_160M; rtl8852b_halinit.c tops at 80) — GetTxCaps/bw_mask report it per variant and set_channel rejects 160 on the 8852B instead of mis-tuning RF18. The generation mask also gains the 5/10 MHz widths both dies declare (BW_CAP_5M|10M) — narrowband_ok already said so; the width set now agrees. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…l stance Four root causes found on the first true-cold bring-up (every prior session ran on a chip the auto-probing kernel driver had pre-initialized — the blacklist discipline exposed them all at once): - power_on lacked the chip-generic mac_pwr_switch prologue (pwr.c:300..405): a USB chip from cold reads WLMAC_PWR_STE=MAC_ON (its AFSM auto-powers to enumerate), and the on-sequence applied over that half-on state leaves the WCPU bootrom FWDL_RDY without ever raising H2C_PATH_RDY. Force the MAC off first (EN_WLON + APFM_OFFMAC poll + the 8852B CMAC_CLK_SEL clear), plus the BOOT_MODE exit, now both-dies common. - HALBB_DBCC/FW_OFLD/DBG_TRACE_SUPPORT were defined =0, but the vendor tests them with #ifdef — fw-offload was silently compiled IN, and the 8852B BBCR writer routes every table write through the fwofld wrapper (stubbed link-only), applying no BB registers at all. Off means undefined. - coex_mac_init never armed B_AX_ENBT, so from cold the LTE indirect interface never reports ready, the LTECOEX writes time out, and the GNT arbitration stays at its cold default. Arm ENBT (mac_coex_init_8852b prologue) and pin the PTA to WiFi via mac_cfg_gnt_8852b's SW-force (gnt_wl=1, gnt_bt=0, both bands) — the Jaguar3 WiFi-only stance. - the RFK prologue (NCTL/SI-reset/LCK/RCK/efuse-trim) ran lazily at first IQK, i.e. after the tune + BB channel config; the vendor runs it in halrf_dm_init position, right after the tables and before any channel work. Lazy placement lets the a-die SI reset tear down live RF state and the radio goes deaf both ways (bisected). Moved to phy_bb_rf_init. Evidence-gated cal stance on the 8852B (same rule as TSSI/DPK): per-channel IQK collapses on-air delivery under the fixed-dBm power model (paired cold A/B, 5 GHz MCS0 flood, 8812AU monitor: 10/1 decodes with IQK vs 100/37 without) — 8852C-only until a TSSI-referenced power model exists. The vendored DACK/RX-DCK run wrapped in the RF 0x0/0x1/0x5 save/restore the hand-ported cal used (the vendor relies on later TRX flow to rewrite them). Validated from real VBUS-cold on both dies: 8852B RX ch11 at baseline decode counts + TX on-air (6M/MCS0 flood decoded, canonical txhits); 8832CU RX 2.4/5 GHz unchanged. 25/25 ctest.
- MSVC: __builtin_ctz in the BB mask_shift helper -> the existing portable ctz32 (the only compiler-specific construct in the Kestrel C++). - macOS: ld64 rejects --gc-sections; use -Wl,-dead_strip on APPLE (MSVC gets /OPT:REF implied in Release via /Gy). - MinGW/MSVC: linkers that resolve archive members before dead-stripping demand halrf_cmd_parser_init / halrf_dbg_setting_init (referenced by the un-called halrf_init/halrf_dm_init entry points) — link-only no-op stubs in kestrel_halrf_helpers.c.
…-rolled fallbacks With both dies validated on the vendored halbb/halrf path, the compiled-but- unreachable hand transcriptions go: the ofld table-walker leg, the gain/RPL cache family, rf_ctrl_ch/bw, both dies' hand cal chains (8852B AFE/ADDCK/DACK subset, 8852C DACK/RX-DCK), and the DEVOURER_KESTREL_HALBB/HALRF conditionals themselves (the defines were unconditionally on for every Kestrel build). CI grows kestrel-8852b-only / kestrel-8852c-only cells. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…racted copies
hal/hal8852{b,c}_phy.{c,h} (extract-generated, ~53K lines), the
PhyTableLoaderKestrel walker, its selftest and the extract_*_phy.py
generators duplicated the exact data the vendored halbb_hwimg_*.c /
halrf_hwimg_*.c ship — and since the vendored loaders own the table apply on
both dies, nothing read them. The array_mp_* rename defines existed solely
to avoid the duplicate-symbol clash and go in the same change. The fw blobs
(hal8852{b,c}_fw.c + extract_*_fw.py) stay — KestrelFw consumes them.
Verified: full + kestrel-only + 8852b-only + 8852c-only builds link clean,
ctest 24/24 (the phytable selftest retires with the walker), RX smoke both
dies unchanged from cold.
MSVC and MinGW resolve archive members before dead-stripping, so the verbatim vendor TUs' references into the deliberately un-vendored subsystems (regulatory power tables, PSD, BB debug port, MP counters, TAS, RA reports) must resolve at link even though no runtime path calls them. GNU --gc-sections hid all 69 on Linux; the no-gc-sections link now passes locally as the cross-linker proxy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CLAUDE.md Kestrel section describes the landed architecture: hand-ported
mac_ax plane + verbatim-vendored halbb/halrf BB/RF plane for BOTH dies
(core + per-chip backends + shim + glue under hal/hal{bb,rf}/g6/), the
evidence-gated cal stances (TSSI/DPK both dies, IQK 8852B), the dm_init-
position RFK prologue, the cold-boot facts (force-off prologue, ENBT/GNT,
rtw89-blacklist discipline), and 160 MHz correctly attributed to the 8852C
die only. README: Kestrel paragraph reflects the vendored BB/RF plane and
per-die bandwidth sets. tests/README.md gains the kestrel_* suite section.
kestrelprobe log labels use the stage names its CLI takes (id/power/fw/
trx/phy).
The demos call Stop() then libusb_exit, destroying the device object only after — so the drain thread (started by InitWrite to recycle TX pages) was still inside libusb_handle_events when libusb tore down, tripping libusb's usbi_mutex_destroy assertion (SIGABRT at txdemo shutdown; 5/5 reproducible on a narrowband flood, intermittent on plain floods). Kestrel had no Stop() override; the join now happens at the ordered teardown point. Repro harness 0/6 abnormal after (was 5/5).
"${@:-36 6}" expands to the single argument "36 6" when no args are given,
so the default run drove DEVOURER_CHANNEL="36 6" and failed both cells.
Proper default array; explicit-args behavior unchanged.
README headline/intro say 11ac + 11ax, four generations; CLAUDE.md intro
likewise. Drop the noise qualifiers from the hardware table ("no 6 GHz" on
the TX20U Nano — no other adapter in the table has 6 GHz either; "first
8852C 6 GHz" on the TX50UH).
RTL8832BU (Wi-Fi-only SKU of the 8852B die) and RTL8852CU (the 8852 branding of the 8852C die) get not-benchmarked rows mirroring the other same-die siblings (RTL8811AU/RTL8812BU/RTL8821CU/RTL8822CU/RTL8822EU style) — both PID families are already in the KestrelUsbIds dispatch table.
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.
Kestrel (RTL8852BU / RTL8832CU) — one design for both dies, vendored BB/RF plane
Refactors the Kestrel HAL so both chips share one architecture, with Realtek's C reused verbatim wherever the vendor ships it:
_V1) and descriptors.hal/halbb/g6/+hal/halrf/g6/(vendor/untouched vendor files,shim/+hal_headers_le.hthe minimal PHL surface,kestrel_hal{bb,rf}_glue.cthe C entry points,tools/vendor_hal{bb,rf}_*.shre-vendor). The vendored loaders own the BB/RF/gain tables, per-channel BB config, RF tune, DACK/RX-DCK (+ IQK on the 8852C); radio pages reach the fw through the bridgesend_h2cinto the mac_ax H2C encoder.kestrel-8852b-only/kestrel-8852c-onlyCI cells.reference/rtl8852bu@ v1.19.21 andreference/rtl8852cu).Real bugs found and fixed on the way
WLMAC_PWR_STE=MAC_ON(its AFSM auto-powers to enumerate); the vendorpwr.cprologue forces the MAC off first — without it the WCPU bootrom never raisesH2C_PATH_RDY. The auto-probing in-tree rtw89 had pre-initialized the chip in every earlier session, masking this.HALBB_*_SUPPORT=0defines: the vendor tests them with#ifdef, so=0compiled fw-offload IN and the 8852B BBCR writer applied zero registers. Off = undefined.B_AX_ENBTmust be set before any LTE-space access; the GNT arbitration is now SW-forced to WiFi (mac_cfg_gnt_8852bstance).phy_bb_rf_init(the vendor'shalrf_dm_initposition). Run lazily after the tune, the a-die SI reset deafens the radio both ways (bisected on-air).Stop()now joins the WP-release drain thread before the demos calllibusb_exit(libusbusbi_mutex_destroyassertion, 5/5 reproducible → 0/10).Evidence-gated calibration stance
TSSI/DPK (both dies) and per-channel IQK (8852B) are gated off: under the fixed-dBm power model they measurably degrade TX (8852B IQK: paired cold A/B on a 5 GHz MCS0 flood, 8812AU monitor — 10/1 decodes with IQK vs 100/37 without). The 8852C validated on-air WITH IQK and keeps it. Gates fall with a TSSI-referenced power model.
Hardware validation (final ladder, VBUS-cold, rtw89 temp-blacklisted)
Known limitation kept as documented: 6 GHz TX tops out at 80 MHz on the 8832CU (the 6G+160 TX-enable path is un-ported; RF locks and RX-160 works — B210-confirmed). MCS7 TX→monitor decode is bench-limited at the current geometry (link EVM ≈ −14 dB at MCS0; both dies, pre- and post-refactor identically).
🤖 Generated with Claude Code