Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` /
Expand Down
28 changes: 21 additions & 7 deletions docs/frequency-hopping.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:

Expand Down
4 changes: 2 additions & 2 deletions docs/kernel-channel-switch-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
21 changes: 18 additions & 3 deletions docs/kernel-channel-switch-offload.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
15 changes: 8 additions & 7 deletions src/DeviceConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/jaguar3/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
67 changes: 64 additions & 3 deletions src/jaguar3/RadioManagementJaguar3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint16_t>(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,
Expand All @@ -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);
Expand All @@ -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<uint32_t>(central) << 8) |
(static_cast<uint32_t>((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;
Expand Down
18 changes: 18 additions & 0 deletions src/jaguar3/RadioManagementJaguar3.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define RADIO_MANAGEMENT_8822C_H

#include <cstdint>
#include <functional>

#include "logger.h"
#include "RtlAdapter.h"
Expand Down Expand Up @@ -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<void(uint32_t, uint32_t)> 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
Expand Down Expand Up @@ -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<void(uint32_t, uint32_t)> _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
Expand Down
13 changes: 13 additions & 0 deletions src/jaguar3/RtlJaguar3Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down Expand Up @@ -1182,9 +1187,17 @@ void RtlJaguar3Device::FastRetune(uint8_t channel, bool cache_rf) {
std::lock_guard<std::mutex> 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
Expand Down
Loading