diff --git a/CLAUDE.md b/CLAUDE.md index 1fd4dd0..cdf0ba9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,12 +17,13 @@ construction from the `SYS_CFG2` chip-id (see **Architecture**): rides the 8812 path), RTL8814AU (4T4R RF / 3-SS baseband — host-pushed TX requires the on-chip 3081 MCU booted during FW download; a failed FW-boot poll means dead TX while RX still works), RTL8821AU (1T1R + BT). **5/10 MHz - narrowband on the 8812 die** (8812AU/8811AU): the 8812A shares the Jaguar2 - `0x8ac` ADC/DAC clock-divider block, so the same re-clock trick works even - though the vendor never wired it — TX+RX bench-characterized. The 8821A is - excluded (dividing its DAC clock starves the 1T1R TX DMA/USB path) and the - 8814A uses a different BW block; both fall back to 20 MHz. See - `docs/narrowband.md`. + narrowband on the 8812AU/8811AU and the 8814AU**: both share the Jaguar2 + `0x8ac` ADC/DAC clock-divider block, so the re-clock trick works even though + the vendor never wired it — TX+RX bench-characterized. The two dies use + different field encodings of the same register (the 8812A divides via + `[9:8]`/`[21:20]`, the 8814A via the 8822B's `[9:8]+[16]`/`[21:20]+[28]`). + The 8821A is excluded (dividing its DAC clock starves the 1T1R TX DMA/USB + path) and falls back to 20 MHz. See `docs/narrowband.md`. - **Jaguar2** (`src/jaguar2/`): RTL8822BU / RTL8812BU (chip-id `0x0a`) and RTL8811CU / RTL8821CU (chip 8821C, chip-id `0x09`). A hybrid: HalMAC FW download / MAC init / power sequencing like Jaguar3, phydm `check_positive` diff --git a/README.md b/README.md index bf2873a..86c4cdc 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,10 @@ long-range digital video links. - **Frequency hopping at FHSS speed.** A channel hop costs ~0.5–2.5 ms 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 — including - on the decade-old RTL8812AU the vendor never gave narrowband — half/quarter - the bandwidth, more range from the same power ([how](docs/narrowband.md)). +- **Narrowband modes the kernel can't do.** 5 and 10 MHz channels on every + supported generation — including the decade-old RTL8812AU and RTL8814AU the + vendor never gave narrowband — half/quarter the bandwidth, more range from + the same power ([how](docs/narrowband.md)). - **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, @@ -51,7 +52,7 @@ Bandwidth cells are devourer's measured on-air TX throughput (Mbps, HT MCS7, | ----------------------------- | ----------------- | ------------- | ------------- | ---------------- | ------------------------------------------- | | **RTL8812AU** | 2T2R | 56 | 52 | 52 | [CHANEVE CHW50L](https://www.aliexpress.com/item/4000762461362.html) (`0bda:8812`). 5/10 MHz capable | | **RTL8811AU** | 1T1R | — | — | — | 1T1R cut of 8812 silicon; rides the 8812 code path. Not benchmarked. 5/10 MHz capable | -| **RTL8814AU** | 4T4R, 3-SS max | 65 | †(32) | †(32) | `0bda:8813`; tested on COMFAST CF-938AC and CF-960AC — antenna builds differ in realised [RX diversity](docs/measuring-spatial-diversity.md) | +| **RTL8814AU** | 4T4R, 3-SS max | 65 | †(32) | †(32) | `0bda:8813`; tested on COMFAST CF-938AC and CF-960AC — antenna builds differ in realised [RX diversity](docs/measuring-spatial-diversity.md). 5/10 MHz capable | | **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`). 5/10 MHz capable | | **RTL8812BU** | 1T1R + BT | — | — | — | 1T1R cut of 8822B silicon; rides the 8822BU code path. Not benchmarked | diff --git a/docs/narrowband.md b/docs/narrowband.md index 87cc842..5b14298 100644 --- a/docs/narrowband.md +++ b/docs/narrowband.md @@ -39,7 +39,7 @@ retune** on an already-tuned channel: bring the chip up at 20 MHz, then re-clock All three generations do the same thing — divide the ADC and DAC clocks and tell the baseband it is "small BW" — but the register block differs. -### Jaguar1 (RTL8812AU / RTL8811AU) — the shared `0x8ac` block +### Jaguar1 (RTL8812AU / RTL8811AU / RTL8814AU) — the shared `0x8ac` block The 8812A drives its bandwidth through BB register `0x8ac` (`rRFMOD_Jaguar`) under mask `0x003003C3`, whose fields are: @@ -70,6 +70,19 @@ to the emission, while at 10 MHz an ADC of 1 receives roughly 2.5× better than The codes are overridable via `DEVOURER_NB_ADC` / `DEVOURER_NB_DAC` for uncharacterized cuts. +The **RTL8814AU (4T4R)** reaches the same block but with a different field +encoding, and it is instructive. Its `phy_SetBwRegAdc_8814A` only pokes +`0x8ac[1:0]` (a 20/40/80 mode selector with no sub-20 value), so the divider +looks absent — but the vendor's own comment documents the full field set as +`[28, 21:20, 16, 9:6, 1:0]`, which is the **Jaguar2 (8822B) layout**: the ADC +clock is `[9:8]+[16]` and the DAC clock is `[21:20]+[28]`, with the `[16]`/`[28]` +high bits dominating (a divide touching only `[9:8]`/`[21:20]` does nothing on +the 8814). Clear those high bits and write the 8821C/8822B divide codes verbatim +(10 MHz → 3/3, 5 MHz → 2/2, small-BW 2/1) and the 8814 narrows exactly as an +8822B does — TX and RX. The lesson: the same register block can carry the same +fields with a *different* value encoding per die; a divide that reads dead may +just be landing in the wrong sub-field. + ### Jaguar2 (RTL8822BU / RTL8821CU) — `0x8ac` packed, plus an RF re-latch edge Jaguar2 uses the same `0x8ac` register with the small-BW field at `[7:6]` and @@ -139,10 +152,13 @@ chip-specific traps. The ones this port paid for, current-state: a driver bug; the durable fix is a crystal-cap trim lever (tracked separately). -4. **8814A uses a different bandwidth mechanism.** The 4T4R 8814A configures - bandwidth through `0x8ac[1:0]` as a mode selector plus a separate ADC/AGC - function, not the `[9:8]` divider the 8812A exposes. The 8812AU divide does - not transplant; 8814 narrowband is an unported research effort. +4. **The same die-family can encode the divider differently.** The 8814A's + bandwidth register looked like a mode selector (`0x8ac[1:0]`) with no + narrowband value, and the 8812A divide (which writes `[9:8]`/`[21:20]`) + narrows nothing on it. The trap is assuming "no divide field" when the field + is there under a different encoding — the 8814 uses the 8822B's `[16]`/`[28]` + high bits (see the Jaguar1 section). Read the vendor's field comments, not + just the code it executes. ## Validation methodology @@ -184,8 +200,8 @@ was characterized (the TX failure rate versus divide depth). it as `CHANNEL_WIDTH_5` / `CHANNEL_WIDTH_10` on `SelectedChannel`, and `IRtlDevice::GetAdapterCaps().narrowband_ok` reports whether the running chip supports it. Support today: **Jaguar2 (8822B/8821C) and Jaguar3 (8822C/8822E)** -fully; **Jaguar1 on the 8812 die (8812AU/8811AU)** as a characterized addition; -8821A and 8814A excluded (see the walls above). +fully, and **Jaguar1 on the 8812AU/8811AU and the 8814AU** — every generation. +The 8821A is the one exclusion (its DAC-clock divide starves TX; see the walls). Test scripts: `tests/jaguar2_narrowband_sdr.sh` and `tests/jaguar1_nb_divide_sweep.sh` (SDR occupied-bandwidth), and diff --git a/src/jaguar1/RadioManagementModule.cpp b/src/jaguar1/RadioManagementModule.cpp index ad2633f..17b789e 100644 --- a/src/jaguar1/RadioManagementModule.cpp +++ b/src/jaguar1/RadioManagementModule.cpp @@ -1169,6 +1169,8 @@ void RadioManagementModule::phy_SetBwRegAdc_8814A(BandType Band, (void)Band; uint32_t val; switch (bw) { + case ChannelWidth_t::CHANNEL_WIDTH_5: /* SPIKE: narrowband -> 20M mode */ + case ChannelWidth_t::CHANNEL_WIDTH_10: case ChannelWidth_t::CHANNEL_WIDTH_20: val = 0x0; break; @@ -1193,6 +1195,8 @@ void RadioManagementModule::phy_SetBwRegAgc_8814A(BandType Band, ChannelWidth_t bw) { uint32_t agc; switch (bw) { + case ChannelWidth_t::CHANNEL_WIDTH_5: /* SPIKE: narrowband -> 20M AGC */ + case ChannelWidth_t::CHANNEL_WIDTH_10: case ChannelWidth_t::CHANNEL_WIDTH_20: agc = 6; break; @@ -1782,6 +1786,34 @@ void RadioManagementModule::phy_PostSetBwMode8814A() { case ChannelWidth_t::CHANNEL_WIDTH_20: /* No extra writes for 20 MHz on 8814. */ break; + case ChannelWidth_t::CHANNEL_WIDTH_5: + case ChannelWidth_t::CHANNEL_WIDTH_10: { + /* 8814AU narrowband. The 8814 sets its BW ADC clock via 0x8ac[1:0] + * (a 20/40/80 mode selector; phy_SetBwRegAdc_8814A), which has no sub-20 + * MHz value — BUT its phy_SetBwRegAdc comment documents the full field set + * as [28, 21:20, 16, 9:6, 1:0], i.e. the 8814 shares the 8822B 0x8ac + * layout (ADC clock [9:8]+[16], DAC clock [21:20]+[28]) rather than the + * 8812A's ([9:8]/[21:20] only — dividing those alone did nothing here; the + * [16]/[28] high bits dominate). Clear the [16]/[28] high bits and set the + * low divide fields + small-BW; the codes are the 8821C/8822B narrowband + * codes verbatim (10 MHz -> 3/3, 5 MHz -> 2/2). Bench-verified TX+RX both + * directions. Sweepable via DEVOURER_NB_ADC / DEVOURER_NB_DAC. */ + const bool is5 = (_currentChannelBw == CHANNEL_WIDTH_5); + uint32_t adc = is5 ? 2u : 3u; + uint32_t dac = is5 ? 2u : 3u; + const uint32_t smallbw = is5 ? 1u : 2u; + if (_tuning.nb_adc) + adc = *_tuning.nb_adc & 0x3; + if (_tuning.nb_dac) + dac = *_tuning.nb_dac & 0x3; + /* mask [28]+[21:20]+[16]+[9:6]+[1:0]; clears the [28]/[16] clock high + * bits, keeps [1:0]=0 (20M mode set above). */ + _device.phy_set_bb_reg(rRFMOD_Jaguar, 0x103103C3, + (dac << 20) | (adc << 8) | (smallbw << 6)); + _logger->info("SPIKE 8814AU narrowband: {} MHz (adc={} dac={} smallbw={})", + is5 ? 5 : 10, adc, dac, smallbw); + break; + } case ChannelWidth_t::CHANNEL_WIDTH_40: _device.phy_set_bb_reg(rRFMOD_Jaguar, 0x3C, SubChnlNum); if (SubChnlNum == diff --git a/src/jaguar1/RtlJaguarDevice.cpp b/src/jaguar1/RtlJaguarDevice.cpp index 5647a09..3bc9fe6 100644 --- a/src/jaguar1/RtlJaguarDevice.cpp +++ b/src/jaguar1/RtlJaguarDevice.cpp @@ -868,16 +868,19 @@ devourer::AdapterCaps RtlJaguarDevice::GetAdapterCaps() { c.rx_chains = chains; c.per_chain_rssi = chains >= 2; c.bw_mask = devourer::bw_mask_for_generation(c.generation); - /* SPIKE: 5/10 MHz narrowband on the 8812 die only (8812AU/8811AU). The - * 8812A shares the 8822B/8821C 0x8ac baseband clock-divider block — the - * divide codes are bench-characterized (tests/jaguar1_nb_divide_sweep.sh), - * TX+RX both directions, both widths. EXCLUDED: the 8821A — its 1T1R clock - * tree couples the DAC clock to the TX DMA/USB path, so dividing it starves - * TX (bulk-out submission failures scale with divide depth: dac=2 ~35% fail, - * dac=1 ~72%, vs 0% at full clock; 20 MHz is unaffected). The 8814A has its - * own phy_PostSetBwMode8814A path (0x8ac[1:0] mode selector) that the - * divide does not transplant to. Experimental (5 MHz@5 GHz is CFO-limited). */ - if (_eepromManager->version_id.ICType == CHIP_8812) { + /* 5/10 MHz narrowband on the 8812 die (8812AU/8811AU) and the 8814AU. Both + * share the Jaguar2 0x8ac baseband clock-divider block; the codes are + * bench-characterized, TX+RX both directions, both widths + * (tests/jaguar1_nb_divide_sweep.sh, docs/narrowband.md). The two dies use + * DIFFERENT field encodings of the same register: the 8812A divides via + * [9:8]/[21:20], while the 8814A shares the 8822B layout (ADC [9:8]+[16], + * DAC [21:20]+[28]) and reuses the identical 8821C/8822B divide codes. + * EXCLUDED: the 8821A — its 1T1R clock tree couples the DAC clock to the TX + * DMA/USB path, so dividing it starves TX (bulk-out submission failures + * scale with divide depth: dac=2 ~35% fail, dac=1 ~72%, vs 0% at full clock; + * 20 MHz unaffected). Experimental (5 MHz@5 GHz is CFO-limited). */ + if (_eepromManager->version_id.ICType == CHIP_8812 || + _eepromManager->version_id.ICType == CHIP_8814A) { c.bw_mask |= devourer::kBw5 | devourer::kBw10; c.narrowband_ok = true; }