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
23 changes: 18 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,25 @@ construction from the `SYS_CFG2` chip-id (see **Architecture**):
RTL8811CU / RTL8821CU (chip 8821C, chip-id `0x09`). A hybrid: HalMAC FW
download / MAC init / power sequencing like Jaguar3, phydm `check_positive`
register tables like Jaguar1 (shared `PhyTableLoader`). RX + TX on 2.4/5 GHz
at 20/40/80 MHz, per-rate bandwidth-aware efuse TX power clamped to generated
`txpwr_lmt` tables.
at 20/40/80 MHz, plus **5/10 MHz narrowband on the 8821C variant** (a
baseband ADC/DAC re-clock packed into BB `0x8ac`; the RF stays in 20 MHz
mode; applied as an end-of-bring-up retune, kernel-flow parity). 5 MHz at
5 GHz is CFO-limited: subcarrier spacing shrinks 4× and a far-offset
TX/RX crystal pair syncs bimodally per bring-up — at 2.4 GHz the same
pair is stable (`tests/narrowband_cross_rx.sh` header). The 8822B
carries the same NB register recipe but is gated off (`narrowband_ok=false`):
its NB RX syncs ~10% and NB TX airs nothing, while the OpenHD kernel module
does full-rate NB on the same dongle with the same firmware — the kernel's
NB switch retunes the RF RXBB LPF via a runtime FW interaction not yet
ported (see the `set_channel_bw` NB branch comment). Per-rate
bandwidth-aware efuse TX power clamped to generated `txpwr_lmt` tables
(narrowband folds to the 20 MHz column).
- **Jaguar3** (`src/jaguar3/`): rtl8822c (RTL8812CU/8822CU, chip-id `0x13`) and
rtl8822e (RTL8812EU/8822EU, chip-id `0x17`). Adds **5/10 MHz narrowband**
the Jaguar1 silicon lacks, 80 MHz (incl. a 40-in-80 frame via TX-descriptor
DATA_SC), and halrf calibration (DACK/IQK/TXGAPK/thermal tracking).
rtl8822e (RTL8812EU/8822EU, chip-id `0x17`). **5/10 MHz narrowband** (its
re-clock lives in the `0x9b0`/`0x9b4` dividers; Jaguar1 silicon has no
narrowband — the vendor drivers carry only dead enum values), 80 MHz (incl.
a 40-in-80 frame via TX-descriptor DATA_SC), and halrf calibration
(DACK/IQK/TXGAPK/thermal tracking).
Sustained 5 GHz TX needs the **coex runtime thread**
(`RtlJaguar3Device::coex_runtime_loop`, started in `InitWrite`) — without its
~2 s WiFi-only coex re-apply + FW heartbeats, the combo chip's coex firmware
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ long-range digital video links.
depending on chip — fast enough to hop on every packet
([how](docs/frequency-hopping.md)).
- **Narrowband modes the kernel can't do.** 5 and 10 MHz channels on the
newest chips — half/quarter the bandwidth, more range from the same power.
Jaguar3 chips and the Jaguar2 8821C — half/quarter the bandwidth, more
range from the same power.
- **A radio lab in a dongle.** Channel sounding, per-antenna signal quality,
beamforming report capture (enough to do
[motion sensing](docs/beamforming-victim-sensing.md)), spectrum sweeps,
Expand All @@ -54,9 +55,10 @@ Bandwidth cells are devourer's measured on-air TX throughput (Mbps, HT MCS7,
| **RTL8821AU** | 1T1R AC + BT | 54 | 32 | 28 | TP-Link Archer T2U Plus (`2357:0120`) |
| **RTL8822BU** | 2T2R + BT | 52 | 50 | 49 | TP-Link Archer T3U (`2357:012d`) |
| **RTL8812BU** | 1T1R + BT | — | — | — | 1T1R cut of 8822B silicon; rides the 8822BU code path. Not benchmarked |
| **RTL8811CU** | 1T1R + BT | 36 | 29 | 28 | COMFAST CF-811AC (`0bda:c811`) |
| **RTL8812CU** | 2T2R | 65 | 60 | 60 | LB-LINK WDN1300H (`0bda:c812`) |
| **RTL8822CU** | 2T2R + BT | — | — | — | not benchmarked (`0bda:c82c`) |
| **RTL8811CU** | 1T1R + BT | 36 | 29 | 28 | COMFAST CF-811AC (`0bda:c811`). 5/10 MHz capable |
| **RTL8821CU** | 1T1R + BT | — | — | — | rides the 8811CU (8821C) code path. 5/10 MHz capable |
| **RTL8812CU** | 2T2R | 65 | 60 | 60 | LB-LINK WDN1300H (`0bda:c812`). 5/10 MHz capable |
| **RTL8822CU** | 2T2R + BT | — | — | — | not benchmarked (`0bda:c82c`). 5/10 MHz capable |
| **RTL8812EU** | 2T2R | 8 | 51 | 47 | LB-LINK BL-M8812EU2 (`0bda:a81a`); bare 5 GHz FPV module. 5/10 MHz capable |
| **RTL8822EU** | 2T2R + BT | — | — | — | not benchmarked. 5/10 MHz capable |
| **RTL8821CE** (PCIe) | 1T1R + BT | — | — | — | Radxa X4 onboard Wi-Fi (`10ec:c821`); not benchmarked |
Expand Down
5 changes: 4 additions & 1 deletion docs/frequency-hopping.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,10 @@ rounds (three of them on the 8821C's band/channel/BW split) — the 8821CU hop i
a **single LSSI write**; AGC index, CFO fc, the 8822B RF 0xBE VCO band / ch144
RF 0xDF flag / 2G spur registers and the 8821C 2G CCK filter are composed
writes on bucket change. RFE pins and the 8821C switch-band/RF-set block are
band-keyed and stay untouched.
band-keyed and stay untouched. 5/10 MHz narrowband (8821C) survives fast hops
for free: the re-clock state lives in the bandwidth-keyed
`0x8ac`/`0x8c4`/`0x8c8` block the hop never touches, and the NB RF18 BW bits
equal the 20 MHz encoding, so the cached RF18 write is already correct.

**Jaguar3** (`RadioManagementJaguar3::fast_retune`, both variants): the RF18
window write inside its 3-wire bracket + force-anapar + BB reset every hop —
Expand Down
5 changes: 3 additions & 2 deletions docs/rx-spectrum-sensing.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ What *is* available is **scalar, channel-wide** energy:
the whole channel, available only on frames that arrive.

To turn scalar energy into a coarse *spectrum*, sweep the channel/bandwidth and
sample the energy per bin (narrowband down to 5 MHz on Jaguar3). Per-tone
sample the energy per bin (narrowband down to 5 MHz on Jaguar3 and the
Jaguar2 8821C). Per-tone
interference localisation is possible through a different mechanism entirely —
the self-sounding beamforming report (see `docs/beamforming-self-sounding.md`),
whose per-tone SNR / V-angle variance localises an interferer to ~1 MHz.
Expand Down Expand Up @@ -126,7 +127,7 @@ The aggregate is drained at each dwell start, so retune-transient frames never
leak into a bin.

The resolution is the channel grid: 20 MHz on the 2.4/5 GHz plan, and down to
~5 MHz on Jaguar3 (`DEVOURER_NB_BW=5` — the 2.4 GHz channels are 5 MHz apart, so
~5 MHz on Jaguar3 and the Jaguar2 8821C (`DEVOURER_NB_BW=5` — the 2.4 GHz channels are 5 MHz apart, so
stepping them at 5 MHz bandwidth gives 5 MHz bins; fast dwells preserve the
narrowband dividers, so an NB sweep never re-runs the re-clock recipe). This is
a scalar-energy spectrum, not an FFT — there is no sub-channel structure within
Expand Down
2 changes: 2 additions & 0 deletions examples/common/env_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ devourer::DeviceConfig devourer_config_from_env() {
cfg.tuning.rfe_type = static_cast<uint8_t>(v);
if (env_long("DEVOURER_NB_DAC", &v))
cfg.tuning.nb_dac = static_cast<uint8_t>(v & 0x7);
if (env_long("DEVOURER_NB_ADC", &v))
cfg.tuning.nb_adc = static_cast<uint8_t>(v & 0x7);
if (const char *e = env_str("DEVOURER_REGULATION")) {
if (str_ieq(e, "ETSI"))
cfg.tuning.regulation = devourer::Regulation::ETSI;
Expand Down
3 changes: 2 additions & 1 deletion examples/rx/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,8 @@ int main() {
/* RX bandwidth: 20 MHz by default. DEVOURER_BW=40|80 selects a wide monitor
* channel (for receiving HT40 / VHT80 frames); DEVOURER_CHOFFSET picks the
* secondary half (1 = secondary above the primary, 2 = secondary below).
* DEVOURER_NB_BW=5|10 re-clocks the baseband to narrowband (Jaguar3 only). */
* DEVOURER_NB_BW=5|10 re-clocks the baseband to narrowband (Jaguar2/3;
* check the adapter.caps narrowband_ok flag). */
ChannelWidth_t width = CHANNEL_WIDTH_20;
uint8_t ch_offset = 0;
if (const char *bw_env = std::getenv("DEVOURER_BW")) {
Expand Down
3 changes: 2 additions & 1 deletion examples/tx/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,8 @@ int main(int argc, char **argv) {
/* Bandwidth for init + hopping. DEVOURER_HOP_BW = 20|40|80 (default 20),
* DEVOURER_HOP_OFFSET = primary-channel offset (0=DONT_CARE, 1=LOWER/HT40+,
* 2=UPPER/HT40-) for 40/80. FastRetune reuses the device's bandwidth.
* DEVOURER_NB_BW = 5|10 re-clocks the baseband to narrowband (Jaguar3 only). */
* DEVOURER_NB_BW = 5|10 re-clocks the baseband to narrowband (Jaguar2/3;
* check the adapter.caps narrowband_ok flag). */
ChannelWidth_t init_width = CHANNEL_WIDTH_20;
uint8_t init_offset = 0;
if (const char *e = std::getenv("DEVOURER_HOP_BW")) {
Expand Down
18 changes: 11 additions & 7 deletions src/AdapterCaps.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,25 @@ inline const char *generation_name(ChipGeneration g) {
}

/* Supported channel widths, one bit per width (MHz). A mask, not a max, because
* the set is not contiguous per family: Jaguar3 adds 5/10 MHz narrowband BELOW
* the 20/40/80 the AC families do. */
* the set is not contiguous per family: Jaguar2/Jaguar3 add 5/10 MHz narrowband
* BELOW the 20/40/80 all AC families do. */
constexpr uint8_t kBw5 = 1u << 0;
constexpr uint8_t kBw10 = 1u << 1;
constexpr uint8_t kBw20 = 1u << 2;
constexpr uint8_t kBw40 = 1u << 3;
constexpr uint8_t kBw80 = 1u << 4;

/* J1/J2 do 20/40/80; J3 adds the 5/10 MHz narrowband re-clock. Pure;
* unit-tested in tests/adapter_caps_selftest.cpp. */
/* J1 does 20/40/80; J2 and J3 add the 5/10 MHz narrowband re-clock (J2 packs
* the ADC/DAC clock word into 0x8ac, J3 into 0x9b0/0x9b4 — same RF-stays-20MHz
* concept). Within J2 only the 8821C variant has working narrowband — the
* 8822B device fill clears kBw5|kBw10 (see RtlJaguar2Device::GetAdapterCaps).
* J1 has no vendor narrowband reference (the rtl8812au trees carry only dead
* enum values). Pure; unit-tested in tests/adapter_caps_selftest.cpp. */
inline uint8_t bw_mask_for_generation(ChipGeneration g) {
const uint8_t ac = kBw20 | kBw40 | kBw80;
return g == ChipGeneration::Jaguar3 ? (ac | kBw5 | kBw10)
return g == ChipGeneration::Jaguar1 ? ac
: g == ChipGeneration::Unknown ? 0
: ac;
: (ac | kBw5 | kBw10);
}

/* A tunable / characterized frequency span (MHz). valid=false = band absent. */
Expand Down Expand Up @@ -110,7 +114,7 @@ struct AdapterCaps {

/* --- feature flags --- */
bool per_packet_txpower = false; /* Jaguar2 descriptor TXPWR_OFSET LUT only */
bool narrowband_ok = false; /* 5/10 MHz re-clock (Jaguar3) */
bool narrowband_ok = false; /* 5/10 MHz re-clock (Jaguar2 8821C, Jaguar3) */
bool fastretune_ok = false; /* lean FastRetune override exists */
bool per_chain_rssi = false; /* frame parser fills per-chain rssi (>=2ch) */
};
Expand Down
10 changes: 8 additions & 2 deletions src/DeviceConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,15 @@ struct DeviceConfig {
/* env: DEVOURER_RFE — Jaguar2 RFE type override (antenna/LNA switch
* variant; unset = efuse, blank efuse falls back per vendor). */
std::optional<uint8_t> rfe_type;
/* env: DEVOURER_NB_DAC — Jaguar3 5/10 MHz: force the 3-bit DAC-divider
* code (divider-mapping experiments only). */
/* env: DEVOURER_NB_DAC — 5/10 MHz divider-mapping experiments only.
* Jaguar3: force the 3-bit DAC-divider code (0x9b4[10:8]). Jaguar2:
* force the 0x8ac DAC clock field — bits [1:0] -> 0x8ac[21:20],
* bit 2 -> 0x8ac[28]. */
std::optional<uint8_t> nb_dac;
/* env: DEVOURER_NB_ADC — Jaguar2 5/10 MHz: force the 0x8ac ADC clock
* field — bits [1:0] -> 0x8ac[9:8], bit 2 -> 0x8ac[16] (divider-mapping
* experiments only). */
std::optional<uint8_t> nb_adc;
/* env: DEVOURER_REGULATION — Jaguar1 regulatory domain override for the
* TX-power limit tables (unset = efuse). */
std::optional<Regulation> regulation;
Expand Down
Loading
Loading