diff --git a/CLAUDE.md b/CLAUDE.md index a40f139..a86946d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -94,9 +94,9 @@ Both `WiFiDriverDemo` and `WiFiDriverTxDemo` honour: - `DEVOURER_CHANNEL=N` — override monitor channel. - `DEVOURER_SKIP_RESET=1` — skip `libusb_reset_device` before claim; useful when picking up a chip whose firmware is already running. -- `DEVOURER_FORCE_TXPWR=1` — force the per-rate TX-power loop during channel - switch. Skipped by default in 8814 monitor mode (~300 vendor ctrl - transfers, indices are unused for RX-only). +- `DEVOURER_SKIP_TXPWR=1` — skip the per-rate TX-power loop during channel + switch (runs by default on every chip; escape hatch for RX-only + experiments). - `DEVOURER_USB_QUIET=1` — downgrade libusb log level from DEBUG to WARNING (DEBUG produces ~7 MB per 15 s and has filled `/tmp` mid-capture). diff --git a/README.md b/README.md index 6c5b3b8..fd424bf 100644 --- a/README.md +++ b/README.md @@ -101,13 +101,10 @@ Common to both demos: - `DEVOURER_SKIP_RESET=1` — skip `libusb_reset_device` before claim. Useful when picking up a chip whose firmware is already running (e.g. after unbinding a kernel driver that left fw state intact). -- `DEVOURER_FORCE_TXPWR=1` — force the per-rate TX-power loop to run during - channel switch. Skipped by default in 8814 monitor mode: the loop issues - ~300 vendor control transfers and the resulting per-rate indices are - unused for RX-only operation. - `DEVOURER_SKIP_TXPWR=1` — skip the per-rate TX-power loop entirely on - every chip. Useful for fast iteration during BB/RF debugging when the - per-rate indices aren't relevant to what you're measuring. + every chip (it runs by default, including 8814). Useful for fast + iteration during BB/RF debugging when the per-rate indices aren't + relevant to what you're measuring. - `DEVOURER_FORCE_IQK=1` — run phydm I/Q calibration on every channel-set, not just band transitions. For 8814, IQK is otherwise off by default — the kernel doesn't run it on `iw set channel` either, and devourer diff --git a/docs/8814-port-audit.md b/docs/8814-port-audit.md new file mode 100644 index 0000000..d3f12cd --- /dev/null +++ b/docs/8814-port-audit.md @@ -0,0 +1,152 @@ +# RTL8814AU port audit — devourer vs aircrack-ng/rtl8814au + +Systematic source-level comparison of devourer against its kernel reference +(`aircrack-ng/rtl8814au` @ `8926414`, `CONFIG_RTL8814A=y`, USB, BT/WoWLAN off, +module-param defaults), motivated by 8814 TX being silent on-air and by the +suspicion that the port carries mistakes. Ten work packages covered every +devourer 8814 code path; each finding below was verified against both sources +at the cited lines before classification. Several were found independently by +two blind audit passes. + +Classes: **C1** unambiguous porting error (fixed) · **C2** intentional +adaptation (kept, documented) · **C3** kernel feature not ported (risk-assessed) +· **C4** ambiguous (documented experiment, no speculative fix). + +Method: static function-pair diff with kernel `#ifdef`s resolved per the real +build config, plus table-parity regeneration. usbmon/canary lenses and on-air +validation are listed under *Hardware validation* — **none of the fixes below +have been hardware-validated yet**. + +## C1 fixes (27 commits, each independently buildable) + +Ranked by suspected relevance to the motivating "USB accepts, 0 frames on air" +symptom. + +| # | Commit | Finding | Kernel ref | +|---|---|---|---| +| 1 | `c5bb4ad` | 8812 `_InitBurstPktLen` body ran on 8814: its `0x456=0x70` (AMPDU_MAX_TIME on 8812) clobbered `REG_TXPKTBUF_BCNQ1_BDNY_8814A` — the TX-buffer boundary programmed to 0x7F6 moments earlier. Ported the 8814 body (FAST_EDCA, RXDMA burst mode, `0xf002=0` "avoid usb 3.0 H2C fail", LDPC-pre-TX off); removed PIFS-zeroing, USTIME, MAX_AGGR 0x1f1f, RSV_CTRL/ARFR/0xf050/0x288/0x289 8812-isms | `usb_halinit.c:122-166` | +| 2 | `f3188ea` | The 5GHz CCK→OFDM TX clamp gated on `_channel.Channel` — a member **never assigned anywhere** (read of indeterminate memory; the 5G-TX fix fired nondeterministically per build/run). Channel now stored in `SetMonitorChannel`; member value-initialised | `core/rtw_mlme_ext.c:1058` (kernel keys off maintained `cur_channel`) | +| 3 | `2931ad6` | FW-boot poll was vacuous: accepted `byte0==0x78` of REG_MCUFWDL — which devourer itself writes in the 0x6078 kick — so a never-booted 3081 looked like success. Now requires chip-set `CPU_DL_READY` (BIT15) and errors loudly on timeout. **A failure here on a virgin chip is the smoking gun for the TX silence** | `rtl8814a_hal_init.c:649-656` | +| 4 | `dbeb720` | 8814 RF reads used the 8812 3-wire serial mechanism; the kernel reads via per-path direct BB shadow blocks (`0x2800/0x2C00/0x3800/0x3C00 + reg*4`). Paths C/D returned garbage from the serial path, so every masked RF RMW (channel + BW writes of RF 0x18) corrupted C/D tuning on **every channel set**. Overturns the old "C/D write-only by design" note | `rtl8814a_phycfg.c:86-122` | +| 5 | `1a8ea83` | Crystal-cap trim wrote 8812 bit positions on every chip — on 8814 the XTAL trim landed 4 bits high (real field untouched, `0x2C[30:27]` clobbered) ⇒ uncorrected carrier-frequency offset on TX+RX. Also fixed: 8821's distinct mask, the `(uint8_t)` truncation of the 12-bit pattern (hurt 8812 too), and the missing RCK1 A→B/C/D RC-trim sync | `phydm_cfotracking.c:230-249`, `rtl8814a_rf6052.c:143-146` | +| 6 | `993e0d9` | `USTIME_TSF/EDCA` forced to 0x50 (8812's 80MHz tick); 8814 MAC runs 100MHz, table value 0x64 — all µs-derived TX timing ran ~25% fast | `usb_halinit.c:577-579` (writes commented out) | +| 7 | `cffeba5` | `NAV_UPPER` left 0 after the init zero-write (kernel restores `ceil(30000/128)=0xEB`) — MAC honoured arbitrarily long NAV; can defer TX indefinitely on busy air | `rtl8814a_hal_init.c:3794-3807` | +| 8 | `8b45e23` | USB TX-agg block-descriptor config never armed (`TDECTRL[7:4]=3`, `0x20B=0x06` — kernel always does) | `usb_halinit.c:654-676` | +| 9 | `0cbeea5` | Per-byte aggregation limits `0x4CA/0x4CB=0x36` from `_InitMacConfigure_8814A` tail (devourer ported the old 8812 pair the kernel folded away) | `usb_halinit.c:487-554` | +| 10 | `e834ce5` | Bulk-IN host buffer 16KB vs the 20KB chip-side RX-aggregation threshold (kernel: 32KB × 8 async URBs). Oversize aggregates split with no short packet → tail parsed as a descriptor, remainder dropped. *Companion to #1, which introduced the kernel 20K threshold* | `rtl8814a_recv.h:25` | +| 11 | `f3b8c8c` | 5G EFUSE PG diff block parsed with the 2.4G two-bytes-per-Ntx shape — every field from relative byte 16 wrong-sourced (BW20/40 3S/4S ↔ OFDM bytes; BW80 diffs read BW160 nibbles). Found independently by two passes | `hal_com_phycfg.c:848-953` | +| 12 | `b3929ef` | Per-Ntx power diffs are **cumulative** upstream (MCS16-31 = `[0]+[1]+[2]`; VHT2SS+ adds `[1]`); devourer used exclusive windows, and 5G had no VHT clauses at all | `hal_com_phycfg.c:2490-2601` | +| 13 | `95d1b97` | Kernel 8814 RFE decision tree ported: unburnt/BIT7 `0xCA` → rfe_type **1** on AU (was 0 via 8812 parse), `&0x7F`, amplifier state derived from rfe_type. Removed the now-wrong `GetPhyContext` 0→1 patch. *Runtime-inert on the CF-938AC (0xCA=0x01 → 1 either way)* | `rtl8814a_hal_init.c:1474-1568` | +| 14 | `c9394a4` | 8812 TX-power-training writes ran on 8814 each channel set (kernel 8814 has none; paths B/C/D collapsed last-writer-wins onto 0xE54) | `rtl8814a_phycfg.c:636-673` | +| 15 | `559f39a` | IGI floor wrote paths A/B only — 4dB initial-gain imbalance vs C/D on the 4-path chip (RX/MRC skew) | phydm `ODM_IC_AC_4SS` DIG | +| 16 | `217fb76` | Power-seq poll retry 10ms vs kernel `udelay(10)` — failing poll cost ~50s instead of ~50ms | `HalPwrSeqCmd.c:134` | +| 17 | `be22303` | Deinit-before-init scaffold made faithful to `hal_carddisable_8814`: `REG_CR=0` stop-rx prologue, gated on the warm-boot detect (kernel never feeds `ACT_TO_CARDEMU` to a cold chip), kernel's constant `0xFE` cut mask | `usb_halinit.c:1386-1455` | +| 18 | `5dde6cc` | `0x577=0x03` secondary-CCA control port | `usb_halinit.c:1250` | +| 19 | `abe4988` | `USB_AGG_EN` (0x283 bit7) now explicitly cleared like the kernel RX-agg setup | `usb_halinit.c:705-727` | +| 20 | `9c38f80` | VHT radiotap bandwidth never reached the descriptor (MHz literals compared against `CHANNEL_WIDTH` enums) — every VHT frame TXed at 20MHz | `rtl8814a_xmit.c:443` | +| 21 | `e280b92` | rfe_type-2 5G pinmux constant `0x37173717` (copy slip; latent — rfe-2 boards only) | `PHY_SetRFEReg8814A` | +| 22 | `e20afaa` | `REG_USB_HRPWM` init write skipped on 8814 (kernel has it commented out) | `usb_halinit.c:1354` | +| 23 | `6021880` | fw version logged from double-offset header read (always 0; blob is v33, md5-identical to kernel's) | `rtl8814a_hal.h:76` | +| 24 | `10927e6` | RX parse hardening: PHY-status memcpy gated (payload bytes were decoded as RSSI/EVM/SNR on physt-less frames + tail over-read), short-fragment guard, 8814 DWORD4 trap documented, spurious log demoted | `usb_ops_linux.c:179` | +| 25 | `3c958ce` | fwdl comment register labels corrected (0x0230 is FIFOPAGE_INFO_1/HPQ count, 0x0210 TXDMA_STATUS, …) — debug-risk only | `rtl8814a_spec.h` | +| 26 | `3597aef` | Hygiene: `0x670` labeled REG_CAMCMD clear-all (was "NAV-related"); dead BIT0 LLT port removed with the adjudication recorded | — | +| 27 | `334d04f` | Docs: stale `DEVOURER_FORCE_TXPWR` removed (only `DEVOURER_SKIP_TXPWR` exists) | — | + +## Key negative results (settled — don't re-chase) + +- **The kernel sends ZERO H2C commands in monitor bring-up/channel-set/inject** + under this config (exhaustive caller-traced inventory; `RegFWOffload` is + never assigned). Devourer's zero-H2C is *not* a divergence; missing H2C + cannot explain "kernel TX works, devourer silent". The surviving FW axes are + boot verification (#3, now instrumented) and the rtw88-trace download + protocol itself. +- **IQK-off in monitor is parity**: the kernel's init IQK block is commented + out, the channel-set trigger requires `bNeedIQK` (join/AP/DFS only), and the + watchdog IQK is `#if 0`. +- **PHY tables are byte-identical** (vendored inputs match the kernel tree; + generated arrays in parity) and the table walker + `check_positive` are + semantically exact — including both drivers ignoring cond2/3/4 (GLNA/GPA/ + ALNA/APA never select 8814 table branches in either tree). +- **PWR_SEQ arrays + parser semantics** are byte-identical/faithful. The + historical "cut-mask filter broke fwdl" mystery has a mechanism: the kernel + passes the chip-independent **constant** `~PWR_CUT_TESTCHIP_MSK` (0xFE) — + deriving the mask from EEPROM cut_version (cut ≥ H overflows past BIT7 → + mask 0) silently no-ops the whole flow. +- **Auto-LLT trigger is BIT16** of 0x208 (the only structured in-tree + definition; HW-verified 2ms self-clear). The vendor BIT0 function polls a + stale variable and verifies nothing. Invariant: FIFOPAGE_INFO/RQPN latch + immediately precedes the BIT16 trigger, both after fwdl (devourer's + rsvd-page fwdl transport requires post-fwdl LLT, unlike the kernel's DDMA + position). +- **TX descriptor field map**: every field devourer sets has identical + (dword, bit, width) in the `_8812` and `_8814A` macro sets; checksum domain + (16 LE16 words, dwords 8-9 excluded) exact; QSEL/MACID/EP mapping exact. +- Kernel `ip link down` does **not** HW-deinit (call commented out, IPS off); + virsh surprise-removal also skips card-disable — only rmmod/clean-unbind + powers the chip down. Devourer has no teardown at all (C3): the env-gated + deinit-before-init scaffold is the mitigation. + +## Residual C3 risks (not ported; assessed) + +| Area | Risk | +|---|---| +| Thermal power-tracking + LCK (8814) — kernel watchdog compensates TX AGC/BB-swing per thermal delta with **no link gating** | TX power drifts as the chip heats during sustained injection (long-range video duty cycles); LCK never re-runs. Highest-value C3 to port next | +| DIG / CCK-PD / EDCCA / FA-reset watchdog slices | IGI frozen at floor; CCK PD fixed; EDCCA thresholds stay at table defaults — RX adaptation in noisy environments (watchdog exists but is env-gated off for measured USB-contention reasons; ports only FA+DIG) | +| `phy_SpurCalibration_8814A` (skip premise was wrong) | ch153 NBI/CSI (any rfe) and ch140 swap (rfe 0) are live at 20MHz — RX spur masking missing on those channels | +| Vendor-request single-shot (kernel: 10× retry + io-error escalation) | One EP0 hiccup aborts a session (read) or silently skips a register write — credible #36 (passthrough-cycle) contributor | +| Sync 1×32KB polling read (kernel: 8×32KB async URBs) | IN-token gaps while parsing + 50ms error sleep → RX loss under load only | +| `Index5G_BW80_Base` (kernel averages adjacent BW40 groups) | 80MHz TXAGC base off by up to half the inter-group delta — 80MHz only | +| 0xC9 TRX-antenna option / USB2→2T4R policy | TX nss policy on USB2 ports / non-0xFF 0xC9 boards | +| TX-power validity check (wrong offset, throws on blank maps; kernel falls back to default tables) | Crash on blank-TX-power EFUSE boards | +| Skip-fwdl-if-running (`MCUFWDL==0x78`) has no kernel counterpart | Warm runs skip ALL fw arming. Entangled with the deinit experiment (scaffold makes the chip cold → skip not taken) | +| Bulk-boundary padding (kernel pads 8B via PKT_OFFSET when `(40+sz)%512==0`; devourer sends exact-multiple + ZLP) | Deterministic per frame size; a 472/984-byte payload sweep settles chip ZLP tolerance | +| EFUSE physical walk 512 vs 1024; EFUSE mask unapplied; 0x8129 ID-check (kernel dropped it) | Odd/heavily-reprogrammed boards only | + +## C4 experiments (no speculative fixes made) + +1. **HWSEQ for injected frames**: this kernel does `HWSEQ_EN=0` + copies the + frame seqnum (`monitor_overwrite_seqnum=0`); devourer does `HWSEQ_EN=1` + (matches the 88XXau byte-match instead — two GPL trees disagree, same story + for `DATA_RETRY_LIMIT` 12-vs-0). HW currently overwrites injected seqnums — + matters for wfb-ng-style consumers. Experiment: flip to kernel behaviour, + verify on-air seq == injected seq, watch for TX regressions. +2. **HWSEQ_CTRL byte3**: devourer forces 0xFF via 32-bit RMW (source intent); + the working kernel chip ends at 0x03 (its 8-bit write no-ops on silicon). + Experiment: drop the force, byte-match the kernel chip state. +3. **0x283 bit7 reset value** — one vendor-read on live HW settles whether the + new explicit clear ever mattered. +4. **RX-wedge falsification**: re-run 8814 RX under dense traffic with the + 32KB buffer (and optionally the old ≤12K threshold) to see whether the + split-aggregate mechanism was the "~10 frames then bulk-IN timeout" wedge. +5. **rtw88-mimic ops with no vendor counterpart** (`0x0064/0x004C/0x00EC/ + 0x1103/0x1330/0x01A0/0x0009` + `0x10C2` BIT5): resolvable only against the + rtw88 source; candidates for byte-state diffing if the FW-boot check (#3) + fails on a virgin chip. + +## Coverage map + +| Devourer 8814 path | Audited in | +|---|---| +| Init flow order + warm-boot detect (`HalModule::rtl8812au_hal_init`) | WP3 (45-item ordered checklist) | +| Power-on / fwdl mimic / FW vars / H2C (`FirmwareManager`) | WP2, WP7 | +| Queue/page/EP/boundary, MAC config, EDCA/retry/agg/beacon/burst | WP1 | +| Deinit scaffold + PWR_SEQ arrays + parser | WP4 | +| TX descriptor + radiotap mapping + USB xmit rules (`RtlJaguarDevice`, `FrameParser` TX) | WP5 | +| Channel/BW/band/RFE/TXAGC/power chain (`RadioManagementModule`) | WP6 | +| EFUSE machinery + derived config (`EepromManager`) | WP7 | +| PHY tables + walker (`PhyTableLoader`, `hal/phydm/rtl8814a/*`) | Lens D + WP8 | +| DM defaults, watchdog slices, IQK (`PhydmWatchdog`, `Iqk8814a`) | WP9 | +| RX descriptor/aggregation/filters + USB primitives (`FrameParser` RX, `RtlUsbAdapter`) | WP10 | + +## Hardware validation (pending — user-assisted) + +1. `cmake --build build -j` — done for every commit. +2. **Virgin-chip ch6 on-air check** (AR9271 sniffer, fresh Vbus power-cycle): + watch the new log lines — `8814A firmware boot NOT confirmed` = FW-arming + axis confirmed; frames on air = the BCNQ1/USTIME/NAV cluster was the gate. +3. Canary re-diff at ch6 + ch36 (extend set with 0x2C, 0x456, 0x55C/0x638, + 0x652, 0x577, 0x4CA/B, 0x208/0x20B, 0x283, RF 0x18 on C/D via the new + direct read). +4. `sudo python3 tests/regress.py --vm-name devourer-testrig --vm-ssh …` + default matrix; `--channel 36` spot-check; full matrix + encoding-matrix if + 8814 TX goes green. diff --git a/src/EepromManager.cpp b/src/EepromManager.cpp index 5b8d436..8137a0b 100644 --- a/src/EepromManager.cpp +++ b/src/EepromManager.cpp @@ -99,8 +99,11 @@ void EepromManager::LateInitFor8814A() { Hal_EfuseParseBTCoexistInfo8812A(); Hal_EfuseParseXtal_8812A(); Hal_ReadThermalMeter_8812A(); - Hal_ReadAmplifierType_8812A(); - Hal_ReadRFEType_8812A(); + /* 8814 derives amplifier state from rfe_type (kernel + * hal_ReadRFEType_8814A -> hal_ReadAmplifierType_8814A) — the 8812 + * pair (EFUSE-parsed amplifier + 8812 RFE heuristic) resolved the + * wrong fallback (0 instead of 1) on unburnt 0xCA boards. */ + Hal_ReadRFEType_8814A(); _logger->info("8814A LateInit: rfe_type={} crystal_cap=0x{:X} " "PA_2G/5G=0x{:X}/0x{:X} LNA_2G/5G=0x{:X}/0x{:X}", rfe_type, crystal_cap, PAType_2G, PAType_5G, @@ -481,30 +484,32 @@ void EepromManager::LoadTxPowerInfo() { BW20_5G_Diff[path][0] = pg_msb_diff(v); OFDM_5G_Diff[path][0] = pg_lsb_diff(v); } - /* Ntx=2..4: 2 bytes each (BW40|BW20, OFDM|-) */ + /* Ntx=2..4: ONE byte each (MSB=BW40, LSB=BW20). Unlike the 2.4G + * block, 5G packs the OFDM diffs separately below — the previous + * two-bytes-per-Ntx parse reused the 2.4G shape and shifted every + * field from byte 16 onward (kernel hal_load_pg_txpwr_info_path_5g, + * hal_com_phycfg.c:848-953). */ for (int t = 1; t < 4; t++) { uint8_t v = efuse_eeprom_data[off++]; BW40_5G_Diff[path][t] = pg_msb_diff(v); BW20_5G_Diff[path][t] = pg_lsb_diff(v); - v = efuse_eeprom_data[off++]; - OFDM_5G_Diff[path][t] = pg_msb_diff(v); - /* LSB nibble of this byte is unused for 5G (no CCK on 5G). */ } - /* 3 bytes BW80 diffs, Ntx=1..3 stored as nibble pairs: - * byte 0: MSB=Ntx2-BW80, LSB=Ntx1-BW80 - * byte 1: MSB=Ntx4-BW80, LSB=Ntx3-BW80 - * byte 2: reserved - * Upstream uses a different layout per IC; the 8812 path packs as - * above per `hal_load_pg_txpwr_info_path_5g`. */ + /* OFDM diff 2T~3T: one byte (MSB=2T, LSB=3T). */ { uint8_t v = efuse_eeprom_data[off++]; - BW80_5G_Diff[path][1] = pg_msb_diff(v); - BW80_5G_Diff[path][0] = pg_lsb_diff(v); - v = efuse_eeprom_data[off++]; - BW80_5G_Diff[path][3] = pg_msb_diff(v); - BW80_5G_Diff[path][2] = pg_lsb_diff(v); - /* third byte ignored */ - off++; + OFDM_5G_Diff[path][1] = pg_msb_diff(v); + OFDM_5G_Diff[path][2] = pg_lsb_diff(v); + } + /* OFDM diff 4T: one byte, LSB nibble only. */ + { + uint8_t v = efuse_eeprom_data[off++]; + OFDM_5G_Diff[path][3] = pg_lsb_diff(v); + } + /* BW80|BW160 diffs: four bytes, tx 0..3 (MSB=BW80, LSB=BW160 — no + * 160MHz support here, the LSB nibble is consumed for layout only). */ + for (int t = 0; t < 4; t++) { + uint8_t v = efuse_eeprom_data[off++]; + BW80_5G_Diff[path][t] = pg_msb_diff(v); } } @@ -778,25 +783,29 @@ uint8_t EepromManager::GetTxPowerIndexBase(uint8_t path, uint8_t rate, goto clamp_and_return; } /* MCS / VHT — pick BW20 / BW40 (BW80 falls through to BW40 per upstream - * comment "Willis suggest adopt BW 40M power index while in BW 80 mode"). */ - if (bandwidth == 0) { /* BW20 */ - if (is_mcs0_7 (rate) || is_vht1ss(rate) || is_vht2ss(rate) || - is_vht3ss (rate) || is_vht4ss(rate)) txPower += BW20_24G_Diff[path][0]; - if (is_mcs8_15 (rate) || (ntx_idx >= 1 && (is_vht2ss(rate) || is_vht3ss(rate) || is_vht4ss(rate)))) - txPower += BW20_24G_Diff[path][1]; - if (is_mcs16_23(rate) || (ntx_idx >= 2 && (is_vht3ss(rate) || is_vht4ss(rate)))) - txPower += BW20_24G_Diff[path][2]; - if (is_mcs24_31(rate) || (ntx_idx >= 3 && is_vht4ss(rate))) - txPower += BW20_24G_Diff[path][3]; - } else { /* BW40 or BW80 */ - if (is_mcs0_7 (rate) || is_vht1ss(rate) || is_vht2ss(rate) || - is_vht3ss (rate) || is_vht4ss(rate)) txPower += BW40_24G_Diff[path][0]; - if (is_mcs8_15 (rate) || (ntx_idx >= 1 && (is_vht2ss(rate) || is_vht3ss(rate) || is_vht4ss(rate)))) - txPower += BW40_24G_Diff[path][1]; - if (is_mcs16_23(rate) || (ntx_idx >= 2 && (is_vht3ss(rate) || is_vht4ss(rate)))) - txPower += BW40_24G_Diff[path][2]; - if (is_mcs24_31(rate) || (ntx_idx >= 3 && is_vht4ss(rate))) - txPower += BW40_24G_Diff[path][3]; + * comment "Willis suggest adopt BW 40M power index while in BW 80 mode"). + * + * Kernel accumulation is CUMULATIVE over rate ranges + * (hal_com_phycfg.c:2490-2496): MCS8-31 adds [0]+[1], MCS16-31 adds + * [0]+[1]+[2], VHT2SS+ adds [1], VHT3SS+ adds [2], etc. The previous + * exclusive windows gave e.g. MCS16-23 only [2]. */ + { + const bool ge_1s = is_mcs0_7(rate) || is_mcs8_15(rate) || + is_mcs16_23(rate) || is_mcs24_31(rate) || + is_vht1ss(rate) || is_vht2ss(rate) || + is_vht3ss(rate) || is_vht4ss(rate); + const bool ge_2s = is_mcs8_15(rate) || is_mcs16_23(rate) || + is_mcs24_31(rate) || is_vht2ss(rate) || + is_vht3ss(rate) || is_vht4ss(rate); + const bool ge_3s = is_mcs16_23(rate) || is_mcs24_31(rate) || + is_vht3ss(rate) || is_vht4ss(rate); + const bool ge_4s = is_mcs24_31(rate) || is_vht4ss(rate); + const int8_t *diff = + (bandwidth == 0) ? BW20_24G_Diff[path] : BW40_24G_Diff[path]; + if (ge_1s) txPower += diff[0]; + if (ge_2s) txPower += diff[1]; + if (ge_3s) txPower += diff[2]; + if (ge_4s) txPower += diff[3]; } } else { /* 5G — no CCK */ @@ -810,25 +819,28 @@ uint8_t EepromManager::GetTxPowerIndexBase(uint8_t path, uint8_t rate, if (ntx_idx >= 3) txPower += OFDM_5G_Diff[path][3]; goto clamp_and_return; } - /* MCS / VHT BW20 / BW40 / BW80. */ - if (bandwidth == 0) { - if (is_mcs0_7 (rate) || is_vht1ss(rate) || is_vht2ss(rate) || - is_vht3ss (rate) || is_vht4ss(rate)) txPower += BW20_5G_Diff[path][0]; - if (is_mcs8_15 (rate)) txPower += BW20_5G_Diff[path][1]; - if (is_mcs16_23(rate)) txPower += BW20_5G_Diff[path][2]; - if (is_mcs24_31(rate)) txPower += BW20_5G_Diff[path][3]; - } else if (bandwidth == 1) { - if (is_mcs0_7 (rate) || is_vht1ss(rate) || is_vht2ss(rate) || - is_vht3ss (rate) || is_vht4ss(rate)) txPower += BW40_5G_Diff[path][0]; - if (is_mcs8_15 (rate)) txPower += BW40_5G_Diff[path][1]; - if (is_mcs16_23(rate)) txPower += BW40_5G_Diff[path][2]; - if (is_mcs24_31(rate)) txPower += BW40_5G_Diff[path][3]; - } else { /* BW80 */ - if (is_mcs0_7 (rate) || is_vht1ss(rate) || is_vht2ss(rate) || - is_vht3ss (rate) || is_vht4ss(rate)) txPower += BW80_5G_Diff[path][0]; - if (is_mcs8_15 (rate)) txPower += BW80_5G_Diff[path][1]; - if (is_mcs16_23(rate)) txPower += BW80_5G_Diff[path][2]; - if (is_mcs24_31(rate)) txPower += BW80_5G_Diff[path][3]; + /* MCS / VHT BW20 / BW40 / BW80 — cumulative over rate ranges, same + * scheme as 2.4G (kernel hal_com_phycfg.c:2550-2601). The previous + * code had no VHT clauses beyond [0] at all on 5G, so VHT2SS missed + * [1] and VHT3SS missed [1]+[2]. */ + { + const bool ge_1s = is_mcs0_7(rate) || is_mcs8_15(rate) || + is_mcs16_23(rate) || is_mcs24_31(rate) || + is_vht1ss(rate) || is_vht2ss(rate) || + is_vht3ss(rate) || is_vht4ss(rate); + const bool ge_2s = is_mcs8_15(rate) || is_mcs16_23(rate) || + is_mcs24_31(rate) || is_vht2ss(rate) || + is_vht3ss(rate) || is_vht4ss(rate); + const bool ge_3s = is_mcs16_23(rate) || is_mcs24_31(rate) || + is_vht3ss(rate) || is_vht4ss(rate); + const bool ge_4s = is_mcs24_31(rate) || is_vht4ss(rate); + const int8_t *diff = (bandwidth == 0) ? BW20_5G_Diff[path] + : (bandwidth == 1) ? BW40_5G_Diff[path] + : BW80_5G_Diff[path]; + if (ge_1s) txPower += diff[0]; + if (ge_2s) txPower += diff[1]; + if (ge_3s) txPower += diff[2]; + if (ge_4s) txPower += diff[3]; } } @@ -948,18 +960,12 @@ JaguarPhyContext EepromManager::GetPhyContext() const { constexpr uint8_t kOdmItrfUsb = 0x02; constexpr uint8_t kOdmCe = 0x04; - /* Every conditional block in array_mp_8814a_phy_reg + _agc_tab requires - * a non-zero rfe_type (122 + 52 blocks, low-byte values 1..11). If - * LateInitFor8814A hasn't run yet — or the chip's EFUSE doesn't carry - * a board RFE — fall back to rfe_type=1 so at least the BB/AGC tables - * apply. The board's RFE pinmux may then be slightly off, but the chip - * will still receive: verified on CF-938AC, even with the fallback - * value the chip lands on the same RFE_PIN_0824 = 0x00033E40 that - * rtw88's working trace shows. */ - const uint8_t rfe_for_ctx = - (version_id.ICType == CHIP_8814A && rfe_type == 0) - ? 1 - : static_cast(rfe_type); + /* Pass the resolved rfe_type straight through. Hal_ReadRFEType_8814A now + * mirrors the kernel decision tree (unburnt/BIT7 0xCA -> 1 on 8814AU, + * incl. the autoload-fail branch), so the old 0->1 ctx patch-up is + * redundant — and would mis-map a board whose EFUSE legitimately burns + * rfe_type=0. */ + const uint8_t rfe_for_ctx = static_cast(rfe_type); return JaguarPhyContext{ .cut_version = static_cast(version_id.CUTVersion), @@ -1731,6 +1737,92 @@ void EepromManager::Hal_ReadRFEType_8812A() { _logger->info("RFE Type: 0x{:X}", rfe_type); } +/* Kernel hal_ReadAmplifierType_8814A (rtl8814a_hal_init.c:1474-1525): on + * 8814 the PA/LNA state is DERIVED from rfe_type, not parsed from the + * 0xBC-0xC0 EFUSE bytes (the byte-parsing hal_ReadPAType_8814A is dead + * code upstream — no caller). Note: neither driver's 8814 table + * check_positive consumes the Type* words (cond2 ignored both sides), so + * these mostly matter for logging/diagnostics parity. */ +void EepromManager::hal_ReadAmplifierType_8814A() { + switch (rfe_type) { + case 1: /* 8814AU */ + external_pa_5g = external_lna_5g = 1; + TypeAPA = TypeALNA = 0; + break; + case 2: /* socket board 8814AR and 8194AR */ + ExternalPA_2G = true; + ExternalLNA_2G = true; + external_pa_5g = external_lna_5g = 1; + TypeAPA = TypeALNA = 0x55; + TypeGPA = TypeGLNA = 0x55; + break; + case 3: /* high power on-board 8814AR and 8194AR */ + ExternalPA_2G = true; + ExternalLNA_2G = true; + external_pa_5g = external_lna_5g = 1; + TypeAPA = TypeALNA = 0xaa; + TypeGPA = TypeGLNA = 0xaa; + break; + case 4: /* on-board 8814AR and 8194AR */ + ExternalPA_2G = true; + ExternalLNA_2G = true; + external_pa_5g = external_lna_5g = 1; + TypeAPA = 0x55; + TypeALNA = 0xff; + TypeGPA = TypeGLNA = 0x55; + break; + case 5: + ExternalPA_2G = true; + ExternalLNA_2G = true; + external_pa_5g = external_lna_5g = 1; + TypeAPA = 0xaa; + TypeALNA = 0x5500; + TypeGPA = TypeGLNA = 0xaa; + break; + case 6: + external_lna_5g = 1; + TypeALNA = 0; + break; + case 0: + default: /* 8814AE */ + break; + } +} + +/* Kernel hal_ReadRFEType_8814A (rtl8814a_hal_init.c:1527-1568). Differs + * from the 8812 tree in three load-bearing ways: the fallback for an + * unprogrammed/BIT7 EFUSE 0xCA is rfe_type=1 on 8814AU (8812: 0 or the + * PA/LNA heuristic), the programmed-value mask is 0x7F (8812: 0x3F + the + * rfe==4 customer workaround), and the amplifier state is derived from + * the resolved rfe_type afterwards. EEPROM_RFE_OPTION is 0xCA on both + * chips. (CF-938AC ground truth: 0xCA = 0x01 -> rfe_type 1 either way; + * the fallback difference bites on unburnt boards.) */ +void EepromManager::Hal_ReadRFEType_8814A() { + if (!_device.AutoloadFailFlag) { + if (registry_priv::RFE_Type != 64 || + 0xFF == efuse_eeprom_data[EEPROM_RFE_OPTION_8812] || + (efuse_eeprom_data[EEPROM_RFE_OPTION_8812] & BIT7) != 0) { + if (registry_priv::RFE_Type != 64) { + rfe_type = registry_priv::RFE_Type; + } else { + /* IS_HARDWARE_TYPE_8814AU -> 1 (the AE/PCIe case is 0). */ + rfe_type = 1; + } + } else { + /* bit7==0 means RFE type defined by 0xCA[6:0] */ + rfe_type = + (uint16_t)(efuse_eeprom_data[EEPROM_RFE_OPTION_8812] & 0x7F); + } + } else { + rfe_type = (registry_priv::RFE_Type != 64) + ? registry_priv::RFE_Type + : 1; /* 8814AU autoload-fail default */ + } + hal_ReadAmplifierType_8814A(); + _logger->info("8814A RFE Type: 0x{:X} (ext PA_5G={} LNA_5G={})", rfe_type, + (int)external_pa_5g, (int)external_lna_5g); +} + #define EEPROM_USB_MODE_8812 0x08 void EepromManager::hal_ReadUsbType_8812AU() { diff --git a/src/EepromManager.h b/src/EepromManager.h index 240599b..04430f5 100644 --- a/src/EepromManager.h +++ b/src/EepromManager.h @@ -187,6 +187,8 @@ class EepromManager { void Hal_ReadAmplifierType_8812A(); void hal_ReadPAType_8812A(); void Hal_ReadRFEType_8812A(); + void Hal_ReadRFEType_8814A(); + void hal_ReadAmplifierType_8814A(); void hal_ReadUsbType_8812AU(); }; diff --git a/src/FirmwareManager.cpp b/src/FirmwareManager.cpp index d28d3da..5d02254 100644 --- a/src/FirmwareManager.cpp +++ b/src/FirmwareManager.cpp @@ -187,7 +187,7 @@ void FirmwareManager::FirmwareDownload_8814A() { uint32_t fw_len = static_cast(blob.len); const uint16_t firmwareVersion = - static_cast(GET_FIRMWARE_HDR_VERSION_8812(fw + 4)); + static_cast(GET_FIRMWARE_HDR_VERSION_8812(fw)); const uint16_t firmwareSignature = static_cast(GET_FIRMWARE_HDR_SIGNATURE_8812(fw)); _logger->info("FirmwareDownload_8814A: fw_ver={} sig=0x{:X} blob={} bytes", @@ -530,7 +530,7 @@ void FirmwareManager::FirmwareDownload_8814A() { /* Post-fwdl CPU kick sequence — mirrors rtw88_8814au's usbmon trace * byte-for-byte after the last fwdl IDDMA program. */ _device.rtw_write8(REG_MCUFWDL, 0x79); /* declare init ready */ - _device.rtw_write8(0x010d, 0x00); /* REG_RD_CTRL+1 */ + _device.rtw_write8(0x010d, 0x00); /* REG_TRXDMA_CTRL+1 */ /* DO NOT write 0x0100 (REG_CR) = 0 here. Bisect 2026-05-26 of #36 wedge: * zeroing REG_CR disables byte 0's DMA-enable bits (HCI_TXDMA_EN/ * HCI_RXDMA_EN/TXDMA_EN/RXDMA_EN/PROTOCOL_EN/SCHEDULE_EN). The later @@ -542,17 +542,25 @@ void FirmwareManager::FirmwareDownload_8814A() { * never writes this address with this value. With this single write * removed, devourer-TX on 8814AU goes from 0.4% completion to 100%. */ _device.rtw_write32(0x1330, 0x80000000); /* REG_3081_DCDC_CTRL */ - _device.rtw_write16(0x0230, 0x0000); /* REG_PCIE_CTRL_REG word */ - _device.rtw_write32(0x022c, 0x80000000); /* REG_BIST_CTRL */ + _device.rtw_write16(0x0230, 0x0000); /* REG_FIFOPAGE_INFO_1_8814A — + * zeroes the HPQ page count; + * restored later by + * _InitQueueReservedPage_8814AUsb */ + _device.rtw_write32(0x022c, 0x80000000); /* REG_RQPN_CTRL_2_8814A (LD_RQPN) */ _device.rtw_write8(REG_BCN_CTRL, 0x14); /* REG_BCN_CTRL */ - _device.rtw_write32(0x0210, 0x00000004); /* REG_RXFLTMAP */ + _device.rtw_write32(0x0210, 0x00000004); /* REG_TXDMA_STATUS_8814A (W1C) */ _device.rtw_write16(REG_MCUFWDL, 0x6078); /* clear FWDL_EN; kick */ - _device.rtw_write8(0x001d, 0x09); /* REG_AFE_OSC_CTRL2+1 */ - _device.rtw_write8(0x0003, 0xfe); /* REG_RSV_CTRL+1, enable 8051 */ + _device.rtw_write8(0x001d, 0x09); /* REG_RSV_CTRL+1 */ + _device.rtw_write8(0x0003, 0xfe); /* REG_SYS_FUNC_EN+1, enable 8051 */ - /* Poll for CPU_DL_READY (BIT15 of REG_MCUFWDL). The 8051 sets this bit - * once it's running and has finished its on-chip init. */ + /* Poll for CPU_DL_READY (BIT15 of REG_MCUFWDL). The chip sets this bit + * once the 3081 is running and has finished its on-chip init. */ if (!_FWFreeToGo8812(10, 5000, CHIP_8814A)) { + _logger->error( + "8814A firmware boot NOT confirmed: CPU_DL_READY (REG_MCUFWDL bit15) " + "never asserted within 5s. Final REG_MCUFWDL=0x{:08X}. The 3081 MCU " + "is likely not running — expect dead TX (and no TX reports).", + _device.rtw_read32(REG_MCUFWDL)); return; } @@ -888,11 +896,15 @@ bool FirmwareManager::_FWFreeToGo8812(uint32_t min_cnt, uint32_t timeout_ms, * upstream renames it REG_8051FW_CTRL_8814A. The "FW is alive" indicator * differs by chip: * - 8812 uses WINTINI_RDY (BIT6) as the single-bit ready flag - * - 8814 has the chip set BIT15 (FW_INIT_RDY) briefly during fw init, - * then settles to byte 0 = 0x78 (IMEM_DL_RDY|IMEM_CHKSUM_OK| - * DMEM_DL_RDY|DMEM_CHKSUM_OK, FWDL_EN cleared). Polling BIT15 in - * userspace misses the transient window; the stable post-boot state - * is byte 0 == 0x78 with FW_DW_RDY (BIT14) also set. */ + * - 8814: the chip sets CPU_DL_READY (BIT15) once the 3081 has booted, + * and the kernel polls exactly that as its TERMINAL success condition + * (FirmwareDownload8814A -> rtl8814a_hal_init.c:649-656, 50ms x 100) + * — which only works because the bit is stable once set. An earlier + * devourer revision additionally accepted byte0==0x78 as a "stable + * post-boot state", but byte0=0x78 is written BY US in the 0x6078 + * kick just before this poll, so that arm self-satisfied on the + * first read and made the check vacuous: a never-booted 8051 was + * indistinguishable from success. Kernel-parity: BIT15 only. */ if (ic_type != CHIP_8814A) { value32 = _device.rtw_read32(REG_MCUFWDL); value32 |= MCUFWDL_RDY; @@ -911,10 +923,10 @@ bool FirmwareManager::_FWFreeToGo8812(uint32_t min_cnt, uint32_t timeout_ms, cnt++; value32 = _device.rtw_read32(REG_MCUFWDL); if (ic_type == CHIP_8814A) { - /* Match either the transient FW_INIT_RDY (BIT15) OR the stable - * post-boot pattern (byte 0 == 0x78 = all DL_RDY + CHKSUM_OK, - * FWDL_EN cleared). */ - if ((value32 & (1u << 15)) || ((value32 & 0xFF) == 0x78)) { + /* CPU_DL_READY (BIT15), chip-set on 3081 boot. We poll much faster + * than the kernel's 50ms cadence, so a short-lived assertion cannot + * be missed either. */ + if ((value32 & (1u << 15)) != 0) { break; } } else if ((value32 & ready_bit) != 0) { @@ -924,7 +936,7 @@ bool FirmwareManager::_FWFreeToGo8812(uint32_t min_cnt, uint32_t timeout_ms, } while (since(start).count() < timeout_ms || cnt < min_cnt); if (ic_type == CHIP_8814A) { - if (!((value32 & (1u << 15)) || ((value32 & 0xFF) == 0x78))) { + if ((value32 & (1u << 15)) == 0) { goto exit; } } else if (!((value32 & ready_bit) != 0)) { diff --git a/src/FrameParser.cpp b/src/FrameParser.cpp index a549753..512068c 100644 --- a/src/FrameParser.cpp +++ b/src/FrameParser.cpp @@ -107,7 +107,13 @@ static rx_pkt_attrib rtl8812_query_rx_desc_status(uint8_t *pdesc) { /* Offset 12 */ pattrib.data_rate = GET_RX_STATUS_DESC_RX_RATE_8812(pdesc); - /* Offset 16 */ + /* Offset 16 — 8812/8821 ONLY. On 8814A this DWORD holds + * PATTERN_IDX[7:0] / RX_EOF[8] / RX_SCRAMBLER[15:9] + * (rtl8814a_recv.h:148-150) and the kernel's 8814 rx-desc query never + * reads SGI/LDPC/STBC/BW from the descriptor at all (it sets + * bw = CHANNEL_WIDTH_MAX). These four attribs are therefore garbage + * when the RX chip is an 8814 — no current consumer reads them, but + * don't trust them there without chip-gating first. */ pattrib.sgi = GET_RX_STATUS_DESC_SPLCP_8812(pdesc); pattrib.ldpc = GET_RX_STATUS_DESC_LDPC_8812(pdesc); pattrib.stbc = GET_RX_STATUS_DESC_STBC_8812(pdesc); @@ -151,6 +157,12 @@ std::vector FrameParser::recvbuf2recvframe(std::span ptr) { auto ret = std::vector{}; do { + /* Never parse a descriptor out of a tail fragment shorter than the + * descriptor itself (kernel rejects transfers < RXDESC_SIZE before + * parsing, os_dep usb_ops_linux.c). */ + if (pbuf.size() < RXDESC_SIZE) { + break; + } auto pattrib = rtl8812_query_rx_desc_status(pbuf.data()); auto pkt_offset = RXDESC_SIZE + pattrib.drvinfo_sz + pattrib.shift_sz + @@ -204,8 +216,19 @@ std::vector FrameParser::recvbuf2recvframe(std::span ptr) { pattrib.drvinfo_sz + RXDESC_SIZE, pattrib.pkt_len)}); - struct _phy_status_rpt_8812 driver_data; - memcpy(static_cast(&driver_data), pbuf.data() + RXDESC_SIZE, sizeof(driver_data)); + struct _phy_status_rpt_8812 driver_data = {}; + /* Only read the PHY-status report when the descriptor says one is + * present and it fits the remaining buffer. The kernel gates this + * on pattrib->physt (usb_ops_linux.c:179); drvinfo_sz >= the report + * size is the equivalent condition with the fields we carry — + * without it, frames with drvinfo_sz==0 had payload bytes decoded + * as RSSI/EVM/SNR, and a frame ending near the buffer tail + * over-read the transfer buffer. */ + if (pattrib.drvinfo_sz >= sizeof(driver_data) && + pbuf.size() >= RXDESC_SIZE + sizeof(driver_data)) { + memcpy(static_cast(&driver_data), pbuf.data() + RXDESC_SIZE, + sizeof(driver_data)); + } ret.back().RxAtrib.rssi[0] = driver_data.gain_trsw[0]; ret.back().RxAtrib.rssi[1] = driver_data.gain_trsw[1]; /* 8814AU path C/D RSSI lives in gain_trsw_cd; on 8812/8811 these bytes @@ -259,8 +282,12 @@ std::vector FrameParser::recvbuf2recvframe(std::span ptr) { pbuf = pbuf.subspan(pkt_offset, pbuf.size() - pkt_offset); } while (pbuf.size() > 0); + /* pkt_cnt (DMA_AGG_NUM from the first descriptor) is informational only: + * neither the kernel nor devourer uses it for loop control, and devourer + * never decremented it — so a non-zero value here is the norm for every + * aggregated transfer, not an error. */ if (pkt_cnt != 0) { - _logger->info("Unprocessed packets: {}", pkt_cnt); + _logger->debug("RX aggregate carried {} packets (DMA_AGG_NUM)", pkt_cnt); } //_logger->info("{} received in frame", ret.size()); diff --git a/src/HalModule.cpp b/src/HalModule.cpp index a59f219..52e56c1 100644 --- a/src/HalModule.cpp +++ b/src/HalModule.cpp @@ -28,6 +28,13 @@ constexpr uint16_t REG_TXPKTBUF_BCNQ_BDNY_8814A = 0x0424; constexpr uint16_t REG_TXPKTBUF_BCNQ1_BDNY_8814A = 0x0456; /* per rtl8814a_spec.h:262 */ constexpr uint16_t REG_MGQ_PGBNDY_8814A = 0x047A; constexpr uint16_t REG_RXFF_PTR_8814A = 0x011C; +constexpr uint16_t REG_FAST_EDCA_VOVI_SETTING_8814A = 0x1448; /* per rtl8814a_spec.h:526 */ +constexpr uint16_t REG_FAST_EDCA_BEBK_SETTING_8814A = 0x144C; /* per rtl8814a_spec.h:527 */ +constexpr uint16_t REG_RXDMA_AGG_PG_TH_8814A = 0x0280; /* per rtl8814a_spec.h:156 */ +constexpr uint16_t REG_RXDMA_MODE_8814A = 0x0290; /* per rtl8814a_spec.h:160 */ +constexpr uint16_t REG_SW_AMPDU_BURST_MODE_CTRL_8814A = 0x04BC; /* per rtl8814a_spec.h:295 */ +constexpr uint16_t REG_MAX_AGGR_NUM_8814A = 0x04CA; /* per rtl8814a_spec.h:303 */ +constexpr uint16_t REG_RTS_MAX_AGGR_NUM_8814A = 0x04CB; /* per rtl8814a_spec.h:304 */ constexpr uint32_t HPQ_PGNUM_8814A = 0x20; /* 32 pages per queue (USB) */ constexpr uint32_t LPQ_PGNUM_8814A = 0x20; @@ -108,7 +115,8 @@ bool HalModule::rtl8812au_hal_init(uint8_t init_channel) { auto regCr = _device.rtw_read8(REG_CR); _logger->info("power-on :REG_SYS_CLKR 0x09=0x{:X}. REG_CR 0x100=0x{:X}", (int)value8, (int)regCr); - if ((value8 & BIT3) != 0 && (regCr != 0 && regCr != 0xEA)) { + const bool macAlreadyOn = (value8 & BIT3) != 0 && (regCr != 0 && regCr != 0xEA); + if (macAlreadyOn) { /* pHalData.bMACFuncEnable = TRUE; */ _logger->info("MAC has already power on"); } else { @@ -133,6 +141,39 @@ bool HalModule::rtl8812au_hal_init(uint8_t init_channel) { * functions that do diverge dispatch internally on ICType. */ const bool is_8814a = _eepromManager->version_id.ICType == CHIP_8814A; const bool is_8821 = _eepromManager->version_id.ICType == CHIP_8821; + + /* Experimental, env-gated (default OFF): deinit-before-init for the 8814. + * The 8814 path below skips BOTH rtl8812au_hw_reset() (the + * "DEINIT_BEFORE_INIT" double-init guard) and InitPowerOn() — it relies on + * the 242-op rtw88-mimic inside FirmwareDownload_8814A instead. So a + * WARM/dirty chip (a re-run without a cold USB Vbus power-cycle, or after + * rtw88_8814au auto-bound it) is never reset: RX bulk-IN wedges (~10 frames + * then LIBUSB_ERROR_TIMEOUT) and TX goes 0 on-air, until a Vbus drop. The + * kernel avoids this by card_disable-on-unbind (CardDisableRTL8814AU runs + * Rtl8814A_NIC_DISABLE_FLOW); devourer never powers the chip off. Mirror it + * as a deinit-before-init here. GATED: the 8814 PWR_SEQ has a known + * cut-mask-filter interaction with fwdl, so this needs clean-rig validation + * (set the env, run devourer twice with no power-cycle, confirm the 2nd run + * does not wedge AND cold init is unaffected) before it can become default. */ + if (is_8814a && std::getenv("DEVOURER_8814_DEINIT_BEFORE_INIT") != nullptr) { + if (macAlreadyOn) { + /* Kernel card-disable prologue (hal_carddisable_8814, + * usb_halinit.c:1394-1398): quiesce MAC DMA first ("stop rx"), then + * run the disable flow. The kernel also only card-disables a chip + * whose HW init completed (usb_halinit.c:1453) — mirror that with + * the warm-boot detect above rather than feeding ACT_TO_CARDEMU to + * a cold chip, a path the kernel never exercises. */ + _logger->info("8814 deinit-before-init: running card_disable_flow"); + _device.rtw_write8(REG_CR, 0x0); /* stop rx */ + if (!HalPwrSeqCmdParsing(rtl8814A_card_disable_flow)) { + _logger->warn("8814 deinit-before-init: card_disable_flow failed"); + } + } else { + _logger->info( + "8814 deinit-before-init: chip is cold, skipping card_disable_flow"); + } + } + if (!is_8814a) { if (!is_8821) { _device.rtw_write8(REG_RF_CTRL, 5); @@ -247,6 +288,14 @@ bool HalModule::rtl8812au_hal_init(uint8_t init_channel) { _InitNetworkType_8812A(); /* set msr */ _InitWMACSetting_8812A(); _InitAdaptiveCtrl_8812AUsb(); + if (is_8814a) { + /* Tail of kernel _InitMacConfigure_8814A (usb_halinit.c:551-552): 8814 + * splits the aggregation limits into per-byte registers. The 8812-body + * 16-bit 0x4CA=0x1f1f write no longer runs on 8814 (see + * _InitBurstPktLen_8814A), so program the kernel values here. */ + _device.rtw_write8(rtl8814a::REG_MAX_AGGR_NUM_8814A, 0x36); + _device.rtw_write8(rtl8814a::REG_RTS_MAX_AGGR_NUM_8814A, 0x36); + } _InitEDCA_8812AUsb(); _InitRetryFunction_8812A(); @@ -255,7 +304,11 @@ bool HalModule::rtl8812au_hal_init(uint8_t init_channel) { _InitBeaconParameters_8812A(); _InitBeaconMaxError_8812A(); - _InitBurstPktLen(); // added by page. 20110919 + if (is_8814a) { + _InitBurstPktLen_8814A(); + } else { + _InitBurstPktLen(); // added by page. 20110919 + } // Init CR MACTXEN, MACRXEN after setting RxFF boundary REG_TRXFF_BNDY to // patch Hw bug which Hw initials RxFF boundry size to a value which is larger @@ -372,6 +425,12 @@ bool HalModule::rtl8812au_hal_init(uint8_t init_channel) { * band-set runs the correct per-chip sequence (issue #51, confirmed via * kernel-vs-devourer usbmon register diff). */ _radioManagementModule->PHY_SwitchWirelessBand8814A(init_band); + /* Kernel PHY_SetRFEReg8814A(bInit=TRUE) (usb_halinit.c:1279): select the + * GPIO pins that physically drive the external RFE (PA + T/R switch). The + * per-band band-switch above only sets the RFE pin *functions*; without + * this one-time pin-select the pins never output, so TX never reaches the + * antenna (submits OK, 0 on-air) while RX still works. */ + _radioManagementModule->InitRFEGpio8814A(); } else { _radioManagementModule->PHY_SwitchWirelessBand8812(init_band); } @@ -400,6 +459,13 @@ bool HalModule::rtl8812au_hal_init(uint8_t init_channel) { // 2010.04.09 add by hpfan _device.rtw_write32(REG_BAR_MODE_CTRL, 0x0201ffff); + if (is_8814a) { + /* Kernel usb_halinit.c:1250: REG_SECONDARY_CCA_CTRL_8814A. Gates TX + * deferral on the secondary channel; one of the few unported MAC + * writes in the TX-relevant 0x5xx block. */ + _device.rtw_write8(0x577, 0x03); + } + if (registry_priv::wifi_spec) { _device.rtw_write16(REG_FAST_EDCA_CTRL, 0); } @@ -407,6 +473,14 @@ bool HalModule::rtl8812au_hal_init(uint8_t init_channel) { // Nav limit , suggest by scott _device.rtw_write8(0x652, 0x0); + if (is_8814a) { + /* Kernel restores NAV_UPPER at the end of hal init via + * HW_VAR_NAV_UPPER (usb_halinit.c:1286 -> rtl8814a_hal_init.c:3794): + * ceil(WiFiNavUpperUs=30000 / 128us-unit) = 0xEB. Without it the + * zero-write above leaves the MAC honouring arbitrarily long NAV. */ + _device.rtw_write8(REG_NAV_UPPER, 0xEB); + } + /* 0x4c6[3] 1: RTS BW = Data BW */ /* 0: RTS BW depends on CCA / secondary CCA result. */ _device.rtw_write8(REG_QUEUE_CTRL, @@ -446,12 +520,17 @@ bool HalModule::rtl8812au_hal_init(uint8_t init_channel) { _device.rtw_write8(REG_SDIO_CTRL_8812, 0x0); _device.rtw_write8(REG_ACLK_MON, 0x0); - /* USB Host Read PWM. All chip families: write 0. Earlier hypothesis + /* USB Host Read PWM. 8812/8821: write 0. Earlier hypothesis * (8821 needing 0x84 "leave LPS" wake) was wrong — usbmon trace of * aircrack-ng/88XXau on the same T2U Plus shows kernel writes 0x00 * here, not 0x84. The LPS-leave flow in Hal8821APwrSeq.h is only - * traversed when actually leaving LPS, not during init. */ - _device.rtw_write8(REG_USB_HRPWM, 0); + * traversed when actually leaving LPS, not during init. + * 8814: the kernel has this init write commented out + * (usb_halinit.c:1354); its only live REG_USB_HRPWM writes are on the + * LPS path, which monitor mode never enters. Skip to match. */ + if (!is_8814a) { + _device.rtw_write8(REG_USB_HRPWM, 0); + } // TODO: ///* ack for xmit mgmt frames. */ @@ -559,7 +638,10 @@ bool HalModule::rtl8812au_hal_init(uint8_t init_channel) { * (kept value; no usbmon * trace for 0x0524 in * current capture set) - * 0x0670 00 00 00 c0 0xc0000000 NAV-related + * 0x0670 00 00 00 c0 0xc0000000 REG_CAMCMD: BIT31|BIT30 = + * security-CAM clear-all + * (= kernel invalidate_cam_all + * at usb_halinit.c:1236) * 0x0990 00 00 10 27 0x27100000 RA-table base * 0x0994 00 01 48 4c 0x4c480100 * 0x0998 24 28 2c 30 0x302c2824 @@ -572,7 +654,7 @@ bool HalModule::rtl8812au_hal_init(uint8_t init_channel) { _device.rtw_write8(0x04c6, 0x04); /* REG_QUEUE_CTRL */ _device.rtw_write32(0x0520, 0x00002f0fu); /* REG_TX_PTCL_CTRL */ _device.rtw_write32(0x0524, 0x0f4fff00u); /* REG_RD_CTRL — kept */ - _device.rtw_write32(0x0670, 0xc0000000u); + _device.rtw_write32(0x0670, 0xc0000000u); /* REG_CAMCMD clear-all */ /* Rate-adaptation table init (first-write values from cold-init * trace; kernel emits 3+ runtime updates from IQK that devourer * cannot reproduce — settle for the first/initial value). */ @@ -716,25 +798,15 @@ bool HalModule::InitPowerOn() { return true; } -/* 8814AU's LLT (linked-list table) for TX FIFO pages is initialized by chip - * hardware: set BIT0 of REG_AUTO_LLT (0x0208), then poll for the bit to - * clear, meaning init is done. Mirrors upstream InitLLTTable8814A in - * hal/rtl8814a/rtl8814a_hal_init.c. */ -bool HalModule::InitLLTTable8814A() { - constexpr uint16_t REG_AUTO_LLT_8814A = 0x0208; - uint8_t v = _device.rtw_read8(REG_AUTO_LLT_8814A); - _device.rtw_write8(REG_AUTO_LLT_8814A, (uint8_t)(v | BIT0)); - for (int i = 0; i < 100; ++i) { - v = _device.rtw_read8(REG_AUTO_LLT_8814A); - if (!(v & BIT0)) { - _logger->info("InitLLTTable8814A: auto-init OK after {} iters", i); - return true; - } - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - _logger->error("InitLLTTable8814A: timeout waiting for BIT0 to clear"); - return false; -} +/* NOTE: there is deliberately no BIT0-style InitLLTTable8814A here. The + * vendor function (rtl8814a_hal_init.c:71-92) writes BIT0 of an 8-bit + * access at 0x208 and its poll loop tests a stale pre-write variable, so + * it never verifies anything. The only structured in-tree definition of + * 0x208's fields on this generation says BIT_AUTO_INIT_LLT = BIT(16) + * (hal_com_reg.h "2 AUTO_LLT" block), and the BIT16 trigger was verified + * on hardware to self-clear within 2 ms. The live trigger is the 32-bit + * BIT16 RMW in rtl8812au_hal_init above; keep FIFOPAGE_INFO/RQPN + * programming immediately before it. */ bool HalModule::InitLLTTable8812A(uint8_t txpktbuf_bndy) { bool status; @@ -833,6 +905,16 @@ bool HalModule::HalPwrSeqCmdParsing(WLAN_PWR_CFG *PwrSeqCmd) { cutMask = _eepromManager->version_id.ChipType == NORMAL_CHIP ? PWR_CUT_A_MSK : PWR_CUT_TESTCHIP_MSK; + } else if (_eepromManager->version_id.ICType == CHIP_8814A) { + /* The kernel passes the chip-independent CONSTANT + * (u8)~PWR_CUT_TESTCHIP_MSK for both the 8814 enable and disable + * flows (usb_halinit.c:217, :1398) — it never derives this mask + * from the chip's real cut, so the "cut extraction from SYS_CFG is + * untrustworthy" concern above doesn't apply here. Effect: the + * test-chip-only entries (e.g. card-disable's 0x0002[0]=0 + 2us + * delay, and five analog writes in the enable flow) are skipped, + * exactly as on the kernel. */ + cutMask = (uint8_t)~PWR_CUT_TESTCHIP_MSK; } if (!(GET_PWR_CFG_CUT_MASK(PwrCfgCmd) & cutMask)) { AryIdx++; @@ -878,7 +960,10 @@ bool HalModule::HalPwrSeqCmdParsing(WLAN_PWR_CFG *PwrSeqCmd) { bPollingBit = true; } else { using namespace std::chrono_literals; - std::this_thread::sleep_for(10ms); + /* Kernel retries with rtw_udelay_os(10) — 10us, not 10ms + * (HalPwrSeqCmd.c:134). With maxPollingCnt=5000 the worst-case + * failing-poll budget is ~50ms upstream; 10ms here made it ~50s. */ + std::this_thread::sleep_for(10us); } if (pollingCount++ > maxPollingCnt) { @@ -1728,9 +1813,13 @@ void HalModule::_InitEDCA_8812AUsb() { _device.rtw_write32(REG_EDCA_VI_PARAM, 0x005EA324); _device.rtw_write32(REG_EDCA_VO_PARAM, 0x002FA226); - /* 0x50 for 80MHz clock */ - _device.rtw_write8(REG_USTIME_TSF, 0x50); - _device.rtw_write8(REG_USTIME_EDCA, 0x50); + if (_eepromManager->version_id.ICType != CHIP_8814A) { + /* 0x50 for 80MHz clock */ + _device.rtw_write8(REG_USTIME_TSF, 0x50); + _device.rtw_write8(REG_USTIME_EDCA, 0x50); + } + /* 8814A keeps the MAC-table value 0x64 (100MHz tick): the kernel's + * _InitEDCA_8814AUsb has the 0x50 writes commented out. */ } void HalModule::_InitRetryFunction_8812A() { @@ -1755,6 +1844,20 @@ void HalModule::init_UsbAggregationSetting_8812A() { } void HalModule::usb_AggSettingTxUpdate_8812A() { + if (_eepromManager->version_id.ICType == CHIP_8814A) { + /* Kernel usb_AggSettingTxUpdate_8814A runs with UsbTxAggMode=1 and + * UsbTxAggDescNum=3 (wifi_spec=0 default): program the block-descriptor + * count into REG_TDECTRL[7:4] and REG_TDECTRL+3 (0x20B) = DescNum<<1. + * Devourer still submits one frame per bulk URB, but this is part of + * the reference TXDMA state the kernel chip runs with. */ + constexpr uint8_t kUsbTxAggDescNum = 3; + uint32_t value32 = _device.rtw_read32(REG_TDECTRL); + value32 &= ~(BLK_DESC_NUM_MASK << BLK_DESC_NUM_SHIFT); + value32 |= (kUsbTxAggDescNum & BLK_DESC_NUM_MASK) << BLK_DESC_NUM_SHIFT; + _device.rtw_write32(REG_TDECTRL, value32); + _device.rtw_write8(REG_TDECTRL + 3, kUsbTxAggDescNum << 1); + return; + } if (_usbTxAggMode) { uint32_t value32 = _device.rtw_read32(REG_TDECTRL); value32 = value32 & ~(BLK_DESC_NUM_MASK << BLK_DESC_NUM_SHIFT); @@ -1795,6 +1898,17 @@ void HalModule::usb_AggSettingRxUpdate_8812A() { (uint16_t)(_device.rxagg_usb_size | (_device.rxagg_usb_timeout << 8)); _device.rtw_write16(REG_RXDMA_AGG_PG_TH, temp); } + if (_eepromManager->version_id.ICType == CHIP_8814A) { + /* Kernel usb_AggSettingRxUpdate_8814A explicitly RMW-clears + * USB_AGG_EN_8814A (BIT7 of REG_RXDMA_AGG_PG_TH+3, 0x283) in its + * default RX_AGG_DMA mode (usb_halinit.c:705-727). Devourer's + * taken path never wrote that byte, leaving USB-mode aggregation + * at whatever the reset/firmware value is — mixed DMA+USB agg + * framing would break the RND8 parse walk. */ + uint8_t valueUSB = _device.rtw_read8(REG_RXDMA_AGG_PG_TH + 3); + _device.rtw_write8(REG_RXDMA_AGG_PG_TH + 3, + (uint8_t)(valueUSB & ~BIT7)); + } break; case RX_AGG_MIX: case RX_AGG_DISABLE: @@ -1934,6 +2048,57 @@ void HalModule::_InitBurstPktLen() { _device.rtw_write32(REG_ARFR3_8812 + 4, 0xffcff000); } +/* Port of upstream 8814 _InitBurstPktLen (usb_halinit.c). The 8812 body + * above must NOT run on 8814A: its 0x456 write (REG_AMPDU_MAX_TIME_8812, + * "suggested by Zhilin") lands on REG_TXPKTBUF_BCNQ1_BDNY_8814A and + * corrupts the beacon-queue TX-buffer boundary programmed by + * _InitQueueReservedPage_8814AUsb; the USTIME_TSF/EDCA = 0x50 writes are + * 8812's 80MHz-clock value where the 8814 MAC table keeps 0x64; and the + * PIFS/MAX_AGGR/ARFR/RSV_CTRL pokes have no counterpart in the kernel's + * 8814 init. */ +void HalModule::_InitBurstPktLen_8814A() { + using namespace rtl8814a; + + /* yx_qi 131128 move to 0x1448, 144c */ + _device.rtw_write32(REG_FAST_EDCA_VOVI_SETTING_8814A, 0x08070807); + _device.rtw_write32(REG_FAST_EDCA_BEBK_SETTING_8814A, 0x08070807); + + /* check device operation speed: SS 0xff bit7 */ + const bool supportUsb3 = (_device.rtw_read8(0xff) & BIT7) == 0; + if (!supportUsb3) { /* USB2/1.1 Mode */ + /* Kernel keys this off UsbBulkOutSize (512 on high-speed, 64 on + * full-speed). */ + if (_device.speed() == LIBUSB_SPEED_HIGH) { + /* set burst pkt len=512B */ + _device.rtw_write8(REG_RXDMA_MODE_8814A, 0x1e); + } else { + /* set burst pkt len=64B */ + _device.rtw_write8(REG_RXDMA_MODE_8814A, 0x2e); + } + _device.rtw_write16(REG_RXDMA_AGG_PG_TH_8814A, 0x2005); /* dmc agg th 20K */ + } else { /* USB3 Mode */ + /* set burst pkt len=1k */ + _device.rtw_write8(REG_RXDMA_MODE_8814A, 0x0e); + _device.rtw_write16(REG_RXDMA_AGG_PG_TH_8814A, 0x0a05); /* dmc agg th 20K */ + + /* set Reg 0xf008[3:4] to 2'00 to disable U1/U2 Mode to avoid 2.5G spur + * in USB3.0. added by page, 20120712 */ + _device.rtw_write8(0xf008, (uint8_t)(_device.rtw_read8(0xf008) & 0xE7)); + /* to avoid usb 3.0 H2C fail */ + _device.rtw_write16(0xf002, 0); + + /* turn off the LDPC pre-TX */ + _device.rtw_write8( + REG_SW_AMPDU_BURST_MODE_CTRL_8814A, + (uint8_t)(_device.rtw_read8(REG_SW_AMPDU_BURST_MODE_CTRL_8814A) & + ~BIT6)); + } + + /* Upstream tail: `if (pHalData->AMPDUBurstMode) write8(0x4BC, 0x5F)` — + * AMPDUBurstMode is never assigned anywhere in the kernel tree + * (zero-initialised false), so that write never runs there either. */ +} + bool HalModule::PHY_BBConfig8812() { /* tangw check start 20120412 */ /* . APLL_EN,,APLL_320_GATEB,APLL_320BIAS, auto config by hw fsm after @@ -2047,10 +2212,28 @@ bool HalModule::odm_config_bb_with_header_file(odm_bb_config_type config_type) { void HalModule::hal_set_crystal_cap(uint8_t crystal_cap) { crystal_cap = (uint8_t)(crystal_cap & 0x3F); - - /* write 0x2C[30:25] = 0x2C[24:19] = CrystalCap */ - _device.phy_set_bb_reg(REG_MAC_PHY_CTRL, 0x7FF80000u, - (uint8_t)(crystal_cap | (crystal_cap << 6))); + const uint32_t reg_val = (uint32_t)(crystal_cap | (crystal_cap << 6)); + + /* The XTAL-trim field of 0x2C sits at different bit positions per chip + * (upstream phydm_cfotracking.c::odm_set_crystal_cap): + * 8812A: 0x2C[30:25] = 0x2C[24:19] -> mask 0x7FF80000 + * 8821A: 0x2C[23:18] = 0x2C[17:12] -> mask 0x00FFF000 + * 8814A: 0x2C[26:21] = 0x2C[20:15] -> mask 0x07FF8000 + * The 8812 mask was applied to every chip — on 8814 the cap landed 4 + * bits high (real trim field untouched, bits [30:27] clobbered), i.e. + * a carrier-frequency offset on TX and RX. */ + switch (_eepromManager->version_id.ICType) { + case CHIP_8814A: + _device.phy_set_bb_reg(REG_MAC_PHY_CTRL, 0x07FF8000u, reg_val); + break; + case CHIP_8821: + _device.phy_set_bb_reg(REG_MAC_PHY_CTRL, 0x00FFF000u, reg_val); + break; + default: + /* write 0x2C[30:25] = 0x2C[24:19] = CrystalCap */ + _device.phy_set_bb_reg(REG_MAC_PHY_CTRL, 0x7FF80000u, reg_val); + break; + } } static uint32_t array_mp_8812a_phy_reg_mp[] = { @@ -2514,6 +2697,25 @@ void HalModule::phy_RF6052_Config_ParaFile_8814() { break; } } + + /* Kernel PHY_RFConfig8814A tail (rtl8814a_rf6052.c:143-146): copy path + * A's RC-calibration word (RF_RCK1_Jaguar = RF 0x1C) to paths B/C/D so + * all chains run the same RC filter trim. Path A is readable; B/C/D + * writes take effect even though they can't be read back (see note + * below). */ + { + /* RF_RCK1_Jaguar (0x1c) comes from Hal8812PhyReg.h. */ + const uint32_t rck1 = _radioManagementModule->phy_query_rf_reg( + RfPath::RF_PATH_A, RF_RCK1_Jaguar, 0xfffff); + _radioManagementModule->phy_set_rf_reg(RfPath::RF_PATH_B, RF_RCK1_Jaguar, + 0xfffff, rck1); + _radioManagementModule->phy_set_rf_reg(RfPath::RF_PATH_C, RF_RCK1_Jaguar, + 0xfffff, rck1); + _radioManagementModule->phy_set_rf_reg(RfPath::RF_PATH_D, RF_RCK1_Jaguar, + 0xfffff, rck1); + _logger->info("8814A RCK1 sync: RF-A[0x1c]=0x{:05x} copied to B/C/D", rck1); + } + /* Verify path A/B RF reads return sensible values. NOTE: paths C/D do * not support RF read-back via the standard 3-wire SI/PI mechanism on * 8814 — rtw88's rtw88xxa_phy_read_rf only indexes paths A/B (rf_phy_num @@ -3057,6 +3259,16 @@ void HalModule::phydm_SetIgiFloor_Jaguar() { * kernel driver. Match kernel by writing the floor once here. */ _device.phy_set_bb_reg(rA_IGI_Jaguar, bMaskByte0, 0x1c); _device.phy_set_bb_reg(rB_IGI_Jaguar, bMaskByte0, 0x1c); + if (_eepromManager->version_id.ICType == CHIP_8814A) { + /* 4-path chip: kernel DIG always writes the same IGI to all four + * paths (phydm_write_dig_reg covers 0xC50/0xE50/0x1850/0x1A50 for + * ODM_IC_AC_4SS). Flooring only A/B left C/D at the 0x20 BB-table + * seed — a 4 dB per-path gain imbalance the kernel never has, which + * skews MRC combining and per-path RSSI. (0x1850/0x1A50 = + * rC_IGI_Jaguar2 / rD_IGI_Jaguar2.) */ + _device.phy_set_bb_reg(0x1850, bMaskByte0, 0x1c); + _device.phy_set_bb_reg(0x1A50, bMaskByte0, 0x1c); + } } void HalModule::PHY_BB8812_Config_1T() { diff --git a/src/HalModule.h b/src/HalModule.h index ec699bc..d1e9311 100644 --- a/src/HalModule.h +++ b/src/HalModule.h @@ -66,7 +66,6 @@ class HalModule { bool rtl8812au_hal_init(uint8_t init_channel); bool InitPowerOn(); bool InitLLTTable8812A(uint8_t txpktbuf_bndy); - bool InitLLTTable8814A(); bool _LLTWrite_8812A(uint32_t address, uint32_t data); void _InitHardwareDropIncorrectBulkOut_8812A(); bool HalPwrSeqCmdParsing(WLAN_PWR_CFG *PwrSeqCmd); @@ -120,6 +119,7 @@ class HalModule { void _InitBeaconParameters_8812A(); void _InitBeaconMaxError_8812A(); void _InitBurstPktLen(); + void _InitBurstPktLen_8814A(); bool PHY_BBConfig8812(); bool phy_BB8812_Config_ParaFile(); diff --git a/src/RadioManagementModule.cpp b/src/RadioManagementModule.cpp index 8f8f58a..c0c2a35 100644 --- a/src/RadioManagementModule.cpp +++ b/src/RadioManagementModule.cpp @@ -528,6 +528,22 @@ uint32_t RadioManagementModule::phy_query_rf_reg(RfPath eRFPath, uint32_t RadioManagementModule::phy_RFSerialRead(RfPath eRFPath, uint32_t Offset) { + if (_eepromManager->version_id.ICType == CHIP_8814A) { + /* Kernel phy_RFRead_8814A (rtl8814a_phycfg.c:86-122): 8814 RF + * registers are read back through per-path direct BB shadow blocks, + * NOT the 8812 HSSI/LSSI serial mechanism below. This is also the + * only read path that works for paths C/D — the SI readback returns + * garbage there, which corrupted every masked (read-modify-write) RF + * write on those paths: the channel and bandwidth RMWs of RF 0x18 + * destroyed all bits outside their masks on C/D at every channel + * set. */ + static constexpr uint16_t kDirectBase[4] = {0x2800, 0x2c00, 0x3800, + 0x3c00}; + const uint16_t direct_addr = (uint16_t)( + kDirectBase[static_cast(eRFPath) & 3] + (Offset & 0xff) * 4); + return phy_query_bb_reg(direct_addr, 0xfffff /* bRFRegOffsetMask */); + } + uint32_t retValue; BbRegisterDefinition pPhyReg = PhyRegDef[eRFPath]; @@ -889,9 +905,13 @@ void RadioManagementModule::phy_SetRFEReg8814A(BandType Band) { } else { switch (rfe_type) { case 2: - _device.phy_set_bb_reg(rA_RFE_Pinmux_Jaguar, bMaskDWord, 0x33173717); - _device.phy_set_bb_reg(rB_RFE_Pinmux_Jaguar, bMaskDWord, 0x33173717); - _device.phy_set_bb_reg(0x18B4, bMaskDWord, 0x33173717); + /* Kernel PHY_SetRFEReg8814A 5G case 2: 0x37173717 on A/B/C — the + * previous 0x33173717 carried rfe-1's [27:24] nibble (copy slip + * between adjacent cases; flagged independently by two audit + * passes against the rtl8814au reference). */ + _device.phy_set_bb_reg(rA_RFE_Pinmux_Jaguar, bMaskDWord, 0x37173717); + _device.phy_set_bb_reg(rB_RFE_Pinmux_Jaguar, bMaskDWord, 0x37173717); + _device.phy_set_bb_reg(0x18B4, bMaskDWord, 0x37173717); _device.phy_set_bb_reg(0x1AB4, bMaskDWord, 0x77177717); _device.phy_set_bb_reg(0x1ABC, 0x0FF00000, 0x37); break; @@ -914,6 +934,27 @@ void RadioManagementModule::phy_SetRFEReg8814A(BandType Band) { } } +void RadioManagementModule::InitRFEGpio8814A() { + /* Mirror of the kernel PHY_SetRFEReg8814A(bInit=TRUE) branch + * (rtl8814a_phycfg.c:1026-1039, called once from usb_halinit.c:1279). + * Enables the GPIO pins that physically drive the external RFE (PA + T/R + * antenna switch). devourer's per-band phy_SetRFEReg8814A programs only the + * RFE pinmux *functions* (0xCB0/0xEB0/0x18B4/0x1AB4); without this one-time + * pin-select the pins are never enabled as RFE outputs, so the external + * PA/T-R switch never engages on TX — TX submits succeed (err:0) but nothing + * reaches the air, while RX still works (issue surfaced after the chip + * stopped inheriting a prior kernel-set GPIO state). */ + const auto rfe_type = _eepromManager->rfe_type; + constexpr uint16_t REG_GPIO_IO_SEL_8814A = 0x0042; /* byte 2 of the 0x40 dword */ + _device.phy_set_bb_reg(0x1994, 0xf, 0xf); /* 0x1994[3:0] = 0xf */ + const uint8_t sel = _device.rtw_read8(REG_GPIO_IO_SEL_8814A); + /* rfe_type 1/2 -> 0x40[23:20]=0xf (0x42 |= 0xf0); type 0 -> [23:22]=11b (0xc0) */ + const uint8_t orv = (rfe_type == 0) ? 0xc0 : 0xf0; + _device.rtw_write8(REG_GPIO_IO_SEL_8814A, (uint8_t)(sel | orv)); + _logger->info("8814A RFE GPIO pin-select (rfe_type={}, 0x42 |= 0x{:02x})", + (int)rfe_type, orv); +} + /* Port of upstream `phy_SetBwRegAdc_8814A` * (rtl8814a_phycfg.c:1454). Programs rRFMOD_Jaguar (0x8AC) bits [1:0] * per bandwidth; both bands write the same value here. */ @@ -1753,10 +1794,20 @@ uint8_t RadioManagementModule::phy_GetSecondaryChnl_8812() { } void RadioManagementModule::PHY_SetTxPowerLevel8812(uint8_t Channel) { + const bool is_8814a = _eepromManager->version_id.ICType == CHIP_8814A; for (uint8_t path = 0; (uint8_t)path < _eepromManager->numTotalRfPath; path++) { phy_set_tx_power_level_by_path(Channel, (RfPath)path); - PHY_TxPowerTrainingByPath_8812((RfPath)path); + /* TX power training is an 8812 mechanism: kernel + * PHY_SetTxPowerLevel8814 (rtl8814a_phycfg.c:636) only loops + * phy_set_tx_power_level_by_path — no training write exists anywhere + * in the 8814 tree, and its BB table inits 0xC54/0xE54 to 0. Running + * the 8812 trainee on 8814 wrote a non-zero word into 0xC54 and + * collapsed paths B/C/D onto 0xE54 (last-writer-wins) on every + * channel set. */ + if (!is_8814a) { + PHY_TxPowerTrainingByPath_8812((RfPath)path); + } } } diff --git a/src/RadioManagementModule.h b/src/RadioManagementModule.h index 38859b9..b61b0c1 100644 --- a/src/RadioManagementModule.h +++ b/src/RadioManagementModule.h @@ -193,6 +193,11 @@ class RadioManagementModule { uint8_t Offset40, uint8_t Offset80); void PHY_SwitchWirelessBand8812(BandType Band); void PHY_SwitchWirelessBand8814A(BandType Band); + /* One-time RFE GPIO pin-select for the 8814 (mirror of the kernel's + * PHY_SetRFEReg8814A bInit=TRUE branch). Must run once at init, after the + * first band-set. Without it the external PA/T-R switch never engages on + * TX (submits OK, 0 on-air) even though RX works. */ + void InitRFEGpio8814A(); void SetTxPower(uint8_t p); private: diff --git a/src/RtlJaguarDevice.cpp b/src/RtlJaguarDevice.cpp index f10afca..2abdc64 100644 --- a/src/RtlJaguarDevice.cpp +++ b/src/RtlJaguarDevice.cpp @@ -125,12 +125,17 @@ bool RtlJaguarDevice::send_packet(const uint8_t *packet, size_t length) { stbc = 1; if (known & 0x40) { auto bw = iterator.this_arg[3] & 0x1f; + /* Map radiotap VHT bandwidth codes to CHANNEL_WIDTH enums — the + * descriptor BW switch below compares against the enums (as the + * HT path above does). The previous MHz literals (40/80) never + * matched CHANNEL_WIDTH_40(1)/CHANNEL_WIDTH_80(2), so VHT 40/80 + * silently transmitted as 20MHz. */ if (bw >= 1 && bw <= 3) - bwidth = 40; + bwidth = CHANNEL_WIDTH_40; else if (bw >= 4 && bw <= 10) - bwidth = 80; + bwidth = CHANNEL_WIDTH_80; else - bwidth = 20; + bwidth = CHANNEL_WIDTH_20; } if (iterator.this_arg[8] & 1) @@ -151,6 +156,19 @@ bool RtlJaguarDevice::send_packet(const uint8_t *packet, size_t length) { } } + /* CCK rates (1/2/5.5/11M) do not exist at 5GHz. The RTL8814AU silently + * drops a CCK-rated frame on a 5GHz channel — the bulk-OUT completes but + * nothing goes on-air (verified on hardware: default MGN_1M beacon = 0 + * frames at ch36/ch100, but ~14k on-air once the rate is OFDM). 2.4GHz + * CCK is fine. So on a 5GHz channel, clamp a CCK rate to the lowest OFDM + * rate. (The 8812 chip happens to auto-fall-back CCK->OFDM at 5G; the + * 8814 does not, so we must do it in software.) */ + if (_channel.Channel > 14 && + (fixed_rate == MGN_1M || fixed_rate == MGN_2M || + fixed_rate == MGN_5_5M || fixed_rate == MGN_11M)) { + fixed_rate = MGN_6M; + } + usb_frame = new uint8_t[usb_frame_length](); ptxdesc = (struct tx_desc *)usb_frame; @@ -312,6 +330,11 @@ void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor, } void RtlJaguarDevice::SetMonitorChannel(SelectedChannel channel) { + /* Keep the device-level channel state current: send_packet's 5GHz + * CCK->OFDM clamp keys off _channel.Channel. Before this assignment + * existed, _channel was never written anywhere — the clamp read an + * uninitialised member and fired nondeterministically. */ + _channel = channel; _radioManagement->set_channel_bwmode(channel.Channel, channel.ChannelOffset, channel.ChannelWidth); } diff --git a/src/RtlJaguarDevice.h b/src/RtlJaguarDevice.h index 09fb92a..a9049c0 100644 --- a/src/RtlJaguarDevice.h +++ b/src/RtlJaguarDevice.h @@ -32,7 +32,10 @@ using Action_ParsedRadioPacket = std::function; class RtlJaguarDevice { std::shared_ptr _eepromManager; std::shared_ptr _radioManagement; - SelectedChannel _channel; + /* Last channel handed to SetMonitorChannel. Value-initialised so the + * 5GHz CCK clamp in send_packet reads Channel=0 (clamp off) rather than + * indeterminate garbage before the first channel set. */ + SelectedChannel _channel{}; RtlUsbAdapter _device; HalModule _halModule; Logger_t _logger; diff --git a/src/RtlUsbAdapter.cpp b/src/RtlUsbAdapter.cpp index b9243d6..b5cd1b7 100644 --- a/src/RtlUsbAdapter.cpp +++ b/src/RtlUsbAdapter.cpp @@ -65,7 +65,14 @@ RtlUsbAdapter::RtlUsbAdapter(libusb_device_handle *dev_handle, Logger_t logger) */ std::vector RtlUsbAdapter::infinite_read() { - static constexpr int BUF_SIZE = 16 * 1024; + /* Must cover one full chip-side RX aggregate: the 8814 init programs + * REG_RXDMA_AGG_PG_TH = 0x05 ("dmc agg th 20K"), and the kernel pairs + * that with MAX_RECVBUF_SZ = 32768 (rtl8814a_recv.h:25) — the threshold + * plus the in-flight frame must fit the host read. A 16 KB buffer (an + * exact multiple of wMaxPacketSize, so no short-packet terminates the + * transfer) split >16 KB aggregates and the tail bytes were then parsed + * as an RX descriptor at the head of the next transfer. */ + static constexpr int BUF_SIZE = 32 * 1024; uint8_t buffer[BUF_SIZE] = {}; int actual_length = 0; int rc; diff --git a/src/RtlUsbAdapter.h b/src/RtlUsbAdapter.h index 1691614..624d7ce 100644 --- a/src/RtlUsbAdapter.h +++ b/src/RtlUsbAdapter.h @@ -57,6 +57,7 @@ class RtlUsbAdapter { uint16_t idVendor() const { return _idVendor; } uint16_t idProduct() const { return _idProduct; } + enum libusb_speed speed() const { return usbSpeed; } bool AutoloadFailFlag = false; bool EepromOrEfuse = false;