diff --git a/CLAUDE.md b/CLAUDE.md index 5398d21..f5fe2b1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -317,10 +317,12 @@ temporal layer and injects each at its ladder's rate `IRtlDevice::FastRetune(channel)` — lean intra-band, same-bandwidth retune on every generation (RF channel switch only, write-only from a compose cache); falls back to full `SetMonitorChannel` on a band change. FHSS-grade: ~0.5–2.5 ms -per hop depending on chip. On the 8822B, `DEVOURER_FASTRETUNE_FW=1` hands the -hop to the chip firmware instead (H2C 0x1D, fire-and-confirm-later): ~1.4 ms -dead air, and `=2` extends it to **cross-band** hops (~1.9 ms vs the ~90 ms -full path) — protocol + bench: `docs/kernel-channel-switch-offload.md`. `send_packet` honours a radiotap `CHANNEL` field, so +per hop depending on chip. On the 8822B and 8822C/8822E, +`DEVOURER_FASTRETUNE_FW=1` hands the hop to the chip firmware instead (H2C +0x1D, fire-and-confirm-later): ~1.4 ms dead air on the 8822B (a tie on-air on +the 8822C, but ~3× cheaper host-side), and `=2` extends it to **cross-band** +hops (~2–2.6 ms vs the ~90 ms full path) — protocol + bench: +`docs/kernel-channel-switch-offload.md`. `send_packet` honours a radiotap `CHANNEL` field, so hopping is per-packet and radiotap-driven like rate. Demos hop via `DEVOURER_HOP_CHANNELS` (SweepSpec grammar: `1,6,11`, `36-48/4`, `5170-5250/5` MHz) + `DEVOURER_HOP_DWELL_FRAMES` / `_ROUNDS` / `_FAST` / `_RADIOTAP` / diff --git a/docs/frequency-hopping.md b/docs/frequency-hopping.md index a11f947..5bb8e8b 100644 --- a/docs/frequency-hopping.md +++ b/docs/frequency-hopping.md @@ -329,13 +329,13 @@ constants once); every subsequent same-band hop is ~1.5 ms. point (default = the full `SetMonitorChannel` at the current width/offset), and every generation overrides it with a lean path built from the tricks above: -| DUT | full path | fast (cached) | USB op cost | fast ops/hop | -|-----|-----------|---------------|-------------|--------------| -| RTL8812AU (Jaguar1) | ~277 ms | **~1.6 ms** | ~0.8 ms | 2 | -| RTL8822BU (Jaguar2) | ~65 ms | **~2.5 ms** | ~1.0 ms | 2 | -| RTL8821CU (Jaguar2) | ~30 ms | **~0.55 ms** | ~0.5 ms | 1 | -| RTL8822CU (Jaguar3) | ~12 ms | **~1.9 ms** | ~0.21 ms | 9 | -| RTL8812EU (Jaguar3) | ~12 ms | **~2.4 ms** | ~0.27 ms | 9 | +| DUT | full path | fast (cached) | fast (fw, host cost) | USB op cost | fast ops/hop | +|-----|-----------|---------------|----------------------|-------------|--------------| +| RTL8812AU (Jaguar1) | ~277 ms | **~1.6 ms** | — | ~0.8 ms | 2 | +| RTL8822BU (Jaguar2) | ~65 ms | **~2.5 ms** | ~2.6 ms | ~1.0 ms | 2 | +| RTL8821CU (Jaguar2) | ~30 ms | **~0.55 ms** | — | ~0.5 ms | 1 | +| RTL8822CU (Jaguar3) | ~12 ms | **~1.9 ms** | **~0.8 ms** | ~0.21 ms | 9 | +| RTL8812EU (Jaguar3) | ~12 ms | **~2.4 ms** | (same path, unvalidated) | ~0.27 ms | 9 | (Median `hop.dwell` switch_us over a 1/6/11 hop set; per-stage numbers from `DEVOURER_HOP_PROF=1`. Every hop microsecond is USB round-trips: one register @@ -345,6 +345,20 @@ lever is op count. FHSS-soak-validated: 12,000 (8822CU) and 9,000 (8822BU) consecutive dwell-1 per-packet hops, zero bulk-OUT failures; a kickless hopping receiver holds a constant catch rate over ~850 retunes.) +**The firmware fast path** (`DEVOURER_FASTRETUNE_FW`, 8822B + 8822C/8822E): +instead of the composed register sequence, one H2C 0x1D hands the whole +retune to the chip firmware, fire-and-confirm-later (the previous hop is +corroborated by a single RF18 read at the next one). Host cost above is the +H2C submit + confirm; the number that matters — **on-air dead time** — is +measured separately with the channel-switch oracles: on the 8822B the fw +path nearly halves it (1.44 ms median vs the sw path's 2.75), on the 8822C +the two tie (~2.3 ms — that die's RF settle dominates) but the fw path is +~3× cheaper on the bus. Mode `=2` extends the hop set **across the 2.4/5 GHz +band boundary** (~2–2.6 ms measured, vs the ~90 ms full-path fallback the sw +fast path needs) — mixed-band FHSS plans become practical. Protocol, bench +method and full distributions: +[kernel-channel-switch-offload.md](kernel-channel-switch-offload.md). + Two techniques carried the newer generations to the table above, beyond the original tricks: diff --git a/docs/kernel-channel-switch-baseline.md b/docs/kernel-channel-switch-baseline.md index 7e620fa..024d5f0 100644 --- a/docs/kernel-channel-switch-baseline.md +++ b/docs/kernel-channel-switch-baseline.md @@ -229,8 +229,8 @@ hopping primitive. ### TDLS / MCC offload paths TDLS channel-switch offload stays **blocked-hardware-absent** end-to-end -(needs two associated STAs under an AP), but the target is now fully -named from source + build evidence: `rtw_hal_ch_sw_oper_offload` +(needs two associated STAs under an AP), but the target is fully named +from source + build evidence: `rtw_hal_ch_sw_oper_offload` (`hal/hal_com.c`) composes **H2C 0x1C `CHNL_SWITCH_OPER_OFFLOAD`**, built and runtime-armed in the measured module. The MCC attempt got further than expected (capability matrix): everything up to `mcc_enable=1` works; diff --git a/docs/kernel-channel-switch-offload.md b/docs/kernel-channel-switch-offload.md index c4ef322..93adc2c 100644 --- a/docs/kernel-channel-switch-offload.md +++ b/docs/kernel-channel-switch-offload.md @@ -105,9 +105,11 @@ Reading: ## The devourer port (`DEVOURER_FASTRETUNE_FW`) -The offload is now a `FastRetune` fast path in the Jaguar2 HAL -(`HalJaguar2::fw_channel_switch`, 8822B only): the H2C rides the classic -HMEBOX mailboxes devourer already drives, and completion is +Devourer ships the offload as a `FastRetune` fast path +(`HalJaguar2::fw_channel_switch` on the 8822B; +`RadioManagementJaguar3::fast_retune`'s fw block on the 8822C/8822E): the +H2C rides the classic HMEBOX mailboxes devourer already drives, and +completion is **fire-and-confirm-later** — polling RF18 during the switch measurably *stretched* it (the PI reads contend with the firmware's RF-bus writes; on-air dead time tripled), and the vendor's C2H wait needs an RX drain a @@ -133,6 +135,19 @@ across 400 consecutive band changes (no silent-TX). Hopping RX is unaffected: identical decode counts over identical hop-RX runs with the path on and off. +**Jaguar3** wires the same H2C in both vendor drivers +(`rtl8822c/e_phy.c`), and the port covers it through the same knob (the +H2C rides `HalJaguar3`'s HMEBOX counter, shared with the coex thread's +H2Cs under the same lock). On the 8822C (8812CU, same rig/oracles): +36↔40 fw 2.28 ms med / p99 5.94 versus sw 2.40 / 5.19 — a tie on air, +because the 8822C's RF settle dominates its dark time whichever engine +sequences it; the fw win there is the per-hop host cost (~0.6 ms H2C +submit vs ~1.9 ms of composed USB writes). Cross-band 36↔6 through the +firmware: **2.63 ms median** (n=379, p99 11.0) versus the ~90 ms full +path. Hopping-RX decode identical fw vs sw. The 8822E shares the code +path (not yet on-air-validated — no E-die on the rig this session); its +spur channels conservatively decline the fast path, fw included. + ## Go/no-go for the series **Go — and the port target is devourer, not the kernel.** The offload is diff --git a/src/DeviceConfig.h b/src/DeviceConfig.h index 52076af..8af1e68 100644 --- a/src/DeviceConfig.h +++ b/src/DeviceConfig.h @@ -206,13 +206,14 @@ struct DeviceConfig { * MIX_MODE swing compensation (0xc94/0xe94 TXAGC + 0xc1c/0xe1c BB scale) * so on-air power holds flat as the PA heats over a sustained TX link. */ bool thermal_track = true; - /* env: DEVOURER_FASTRETUNE_FW — Jaguar2 8822B FastRetune firmware fast - * path (H2C 0x1D SINGLE_CHANNELSWITCH_V2, the switch the vendor driver - * gates behind rtw_ch_switch_offload): 0 = off (software compose path), - * 1 = firmware switch for intra-band 20/40 MHz hops, 2 = additionally - * accept cross-band hops (the firmware reprograms the band block; TXAGC - * baseline stays the bring-up band's — active power knobs re-fold). - * Bench + protocol: docs/kernel-channel-switch-offload.md. */ + /* env: DEVOURER_FASTRETUNE_FW — FastRetune firmware fast path (H2C 0x1D + * SINGLE_CHANNELSWITCH_V2, the switch the vendor drivers gate behind + * rtw_ch_switch_offload) on the 8822B (Jaguar2) and 8822C/8822E + * (Jaguar3): 0 = off (software compose path), 1 = firmware switch for + * intra-band 20/40 MHz hops, 2 = additionally accept cross-band hops + * (the firmware reprograms the band block; TXAGC baseline stays the + * bring-up band's — active power knobs re-fold). Bench + protocol: + * docs/kernel-channel-switch-offload.md. */ int fastretune_fw = 0; /* env: DEVOURER_DIS_CCA — Jaguar2/3 MAC carrier-sense disable at bring-up * (primary CCA 0x520[14] + EDCCA [15]): injected/beacon TX stops deferring to diff --git a/src/jaguar3/CLAUDE.md b/src/jaguar3/CLAUDE.md index bb3c788..5e83d39 100644 --- a/src/jaguar3/CLAUDE.md +++ b/src/jaguar3/CLAUDE.md @@ -23,6 +23,13 @@ narrowband dividers, RF18 encoding), strategy interfaces `Jaguar3Calibration` dividers (vs the `0x8ac` block the Jaguar1/2 chips share). 80 MHz works, incl. a 40-in-80 frame via TX-descriptor DATA_SC. See `docs/narrowband.md`. - halrf calibration: DACK/IQK/TXGAPK/thermal tracking. +- **Firmware channel switch** (H2C 0x1D via `fastretune_fw`, both dies): the + fw-switch H2C must ride `HalJaguar3::send_h2c_raw`'s HMEBOX box counter — + the coex runtime thread shares it (both callers hold `_reg_mu`); a second + counter would corrupt the mailbox rotation. On the 8822C the fw and sw + fast paths tie on-air (~2.3 ms — RF settle dominates); the fw win is ~3× + lower per-hop host/USB cost, plus ~2.6 ms cross-band. 8822E spur channels + decline every fast path, fw included. - `DEVOURER_TX_WITH_RX=thread` must be set **before** `InitWrite` — the bring-up keeps the RX filters open; retrofitting RX later is unreliable. - The rtl8822e's hardware-bisected constraints (DPDT/pin-mux front end, diff --git a/src/jaguar3/RadioManagementJaguar3.cpp b/src/jaguar3/RadioManagementJaguar3.cpp index ac28d92..5770147 100644 --- a/src/jaguar3/RadioManagementJaguar3.cpp +++ b/src/jaguar3/RadioManagementJaguar3.cpp @@ -638,6 +638,27 @@ void RadioManagementJaguar3::invalidate_fast_caches() { _last_agc_key = -1; _rxbb_asserted = false; _cw_primed = false; + _fw_sw_pending = 0; /* a full set supersedes any in-flight fw switch */ +} + +bool RadioManagementJaguar3::fw_switch_confirm() { + if (!_fw_sw_pending) + return true; + const uint8_t want = _fw_sw_pending; + _fw_sw_pending = 0; + /* One RF-window read (path A RF18 mirror) — a dwell has passed since the + * H2C, so a live firmware has long since landed the channel field. The + * landed dword re-primes the window half of the compose cache. */ + const uint32_t win_a = + _device.rtw_read32(static_cast(0x3c00 + (0x18 << 2))); + if ((win_a & 0xffu) == want) { + _cw_rfwin_a = win_a; + return true; + } + _logger->warn("Jaguar3: fw channel switch to central {} never landed " + "(RF18 win=0x{:08x}) — resyncing via the full path", + want, win_a); + return false; } bool RadioManagementJaguar3::fast_retune(uint8_t channel, @@ -646,14 +667,15 @@ bool RadioManagementJaguar3::fast_retune(uint8_t channel, if (_last_channel == 0) return false; /* never tuned — unknown band, cold BW/band state */ const bool cur_2g = _last_channel <= 14; - if (cur_2g != (channel <= 14)) - return false; /* band change needs RFE/AGC/CCK-RxIQ — full path only */ + const bool band_change = cur_2g != (channel <= 14); if (channel == _last_channel) return true; /* no-op hop */ /* Spur channels (phydm_spur_eliminate_8822e) carry per-channel NBI/CSI-mask * state that only the full path programs — a lean hop into OR out of one * would leave a stale notch/mask. Decline; the caller falls back to the - * full SetMonitorChannel. 8822E only (the spur port is 8822E-gated). */ + * full SetMonitorChannel. 8822E only (the spur port is 8822E-gated). + * Applies to the fw path too: whether the 8822E firmware programs its own + * notch on these channels is uncharacterized — stay conservative. */ if (_variant == ChipVariant::C8822E) { uint8_t cc, cp, tc, tp; central_and_pri(_last_channel, channel_offset, bwmode, cc, cp); @@ -662,6 +684,45 @@ bool RadioManagementJaguar3::fast_retune(uint8_t channel, return false; } + /* Firmware fast path (DEVOURER_FASTRETUNE_FW): the same H2C 0x1D + * SINGLE_CHANNELSWITCH_V2 the 8822B port uses — both Jaguar3 dies wire it + * in their vendor drivers (rtl8822c/e_phy.c switch_chnl_and_set_bw_by_fw). + * Fire-and-confirm-later (see the Jaguar2 port: polling RF during the + * switch contends with the firmware's RF-bus writes); mode 2 additionally + * hands over band changes. 20/40 MHz only. */ + if (_cfg.tuning.fastretune_fw > 0 && _send_h2c && + (!band_change || _cfg.tuning.fastretune_fw >= 2) && + (bwmode == CHANNEL_WIDTH_20 || bwmode == CHANNEL_WIDTH_40)) { + devourer::HopProf prof(_logger->events(), _cfg.debug.hop_prof, "j3fw", + channel); + if (!fw_switch_confirm()) + return false; /* previous fw switch never landed — full-path resync */ + prof.mark("confirm"); + uint8_t central, pri; + central_and_pri(channel, channel_offset, bwmode, central, pri); + const uint8_t bw_code = (bwmode == CHANNEL_WIDTH_40) ? 1 : 0; + const uint32_t msg = + 0x1du | (static_cast(central) << 8) | + (static_cast((pri & 0xf) | (bw_code << 4)) << 16) | + (0x02u << 24); /* IQK_UPDATE_EN; PWR_IDX_UPDATE stays 0 */ + _send_h2c(msg, 0); + prof.mark("fw"); + _fw_sw_pending = central; + _cw_primed = false; /* the fw rewrites RF18 under the compose cache */ + if (band_change) { + _last_sco = 0xffffffff; + _last_dfir = 0xffffffff; + _last_agc_key = -1; + } + _last_channel = channel; + DVR_DEBUG(_logger, "Jaguar3: fw fast retune -> ch {} (central {})", + channel, central); + return true; + } + + if (band_change) + return false; /* band change needs RFE/AGC/CCK-RxIQ — full path only */ + devourer::HopProf prof(_logger->events(), _cfg.debug.hop_prof, "j3", channel); uint8_t central, pri; diff --git a/src/jaguar3/RadioManagementJaguar3.h b/src/jaguar3/RadioManagementJaguar3.h index 14edd48..5f3902a 100644 --- a/src/jaguar3/RadioManagementJaguar3.h +++ b/src/jaguar3/RadioManagementJaguar3.h @@ -2,6 +2,7 @@ #define RADIO_MANAGEMENT_8822C_H #include +#include #include "logger.h" #include "RtlAdapter.h" @@ -50,6 +51,16 @@ class RadioManagementJaguar3 { bool fast_retune(uint8_t channel, uint8_t channel_offset, ChannelWidth_t bwmode, bool cache_rf); + /* Firmware fast-path enabler (H2C 0x1D SINGLE_CHANNELSWITCH_V2 — protocol + * + bench: docs/kernel-channel-switch-offload.md). The sender is injected + * by the device layer so the H2C rides HalJaguar3's HMEBOX box counter — + * shared with the coex runtime thread's H2Cs and serialized by the same + * _reg_mu that serializes FastRetune against the coex tick. Unset = + * fastretune_fw is inert on this radio. */ + void set_h2c_sender(std::function fn) { + _send_h2c = std::move(fn); + } + /* Channel/BW register-canary dump for tests/hop_parity_check.sh — the same * grep format as the Jaguar1 DumpCanary (BB/MAC/RF[A|B] ADDR = VALUE inside * === DEVOURER_DUMP_CANARY === markers). Emitted by both the full and fast @@ -195,6 +206,13 @@ class RadioManagementJaguar3 { uint32_t _cw_c30 = 0, _cw_808 = 0; uint32_t _cw_rfwin_a = 0, _cw_rfwin_b = 0; /* 0x3c60/0x4c60 full dwords */ void invalidate_fast_caches(); + + /* fw fast-path state (fastretune_fw): the pending central channel of a + * fire-and-confirm-later H2C 0x1D switch, corroborated at the next hop by + * one RF-window read (fw_switch_confirm). */ + std::function _send_h2c; + uint8_t _fw_sw_pending = 0; + bool fw_switch_confirm(); /* BW-keyed RXBB state, re-asserted on the first fast hop of each epoch: * the init-time halrf calibration rewrites it after the channel set * (hardware-observed on the 8812EU — IQK clears the 40 MHz TX_CCK_IND bit diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index b73c456..5231325 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -44,6 +44,11 @@ RtlJaguar3Device::RtlJaguar3Device(RtlAdapter device, Logger_t logger, : _device{device}, _cfg{std::move(cfg)}, _logger{logger}, _variant{variant}, _hal{device, logger, variant, _cfg}, _radioManagement{device, logger, variant, _cfg}, _phydm{device, logger} { + /* fastretune_fw: the fw-switch H2C must ride the HAL's HMEBOX box counter + * (the coex runtime thread sends its H2Cs through the same one; both + * callers serialize on _reg_mu). */ + _radioManagement.set_h2c_sender( + [this](uint32_t msg, uint32_t ext) { _hal.send_h2c_raw(msg, ext); }); _logger->info("RtlJaguar3Device constructed ({})", variant == jaguar3::ChipVariant::C8822E ? "8822E/EU" : "8822C/CU"); } @@ -1182,9 +1187,17 @@ void RtlJaguar3Device::FastRetune(uint8_t channel, bool cache_rf) { std::lock_guard lk(_reg_mu); if (channel == _channel.Channel) return; + const bool band_change = (_channel.Channel <= 14) != (channel <= 14); if (_radioManagement.fast_retune(channel, _channel.ChannelOffset, _channel.ChannelWidth, cache_rf)) { _channel.Channel = channel; + /* Only the fw fast path (DEVOURER_FASTRETUNE_FW=2) accepts a band + * change; the firmware retunes the chip but power folding is host-side — + * re-fold active knobs against the new band's tables (the same gating + * SetMonitorChannel applies). */ + if (band_change && _brought_up && + (_tx_pwr_offset_steps != 0 || _tx_pwr_override >= 0)) + apply_tx_power_current(/*full=*/true); return; } /* Fast path declined (band change / never tuned) — full channel set at the