diff --git a/CLAUDE.md b/CLAUDE.md index 611f297..d40e8ec 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,19 +21,18 @@ 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, 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 + at 20/40/80 MHz plus **5/10 MHz narrowband on both variants** (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. The 8822B RF synth only re-latches + on an RF18 *value edge*, so the narrowband path writes RF18 twice — + same-value rewrites do nothing; see the `set_channel_bw` NB branch). + 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). Per-rate bandwidth-aware efuse TX power clamped to generated `txpwr_lmt` tables - (narrowband folds to the 20 MHz column). + (narrowband folds to the 20 MHz column). Golden-init replay + (`DEVOURER_REPLAY_WSEQ`, a captured kernel write stream applied verbatim + at the end of Init) is the debugging lever that found the RF18-edge bug. - **Jaguar3** (`src/jaguar3/`): rtl8822c (RTL8812CU/8822CU, chip-id `0x13`) and rtl8822e (RTL8812EU/8822EU, chip-id `0x17`). **5/10 MHz narrowband** (its re-clock lives in the `0x9b0`/`0x9b4` dividers; Jaguar1 silicon has no diff --git a/README.md b/README.md index 25ce243..7694508 100644 --- a/README.md +++ b/README.md @@ -29,8 +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 - Jaguar3 chips and the Jaguar2 8821C — half/quarter the bandwidth, more - range from the same power. + Jaguar2 and Jaguar3 chips — 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, @@ -53,7 +53,7 @@ Bandwidth cells are devourer's measured on-air TX throughput (Mbps, HT MCS7, | **RTL8811AU** | 1T1R | — | — | — | 1T1R cut of 8812 silicon; rides the 8812 code path. Not benchmarked | | **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) | | **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`) | +| **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 | | **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 | diff --git a/examples/common/env_config.cpp b/examples/common/env_config.cpp index b243ef2..b6b6f41 100644 --- a/examples/common/env_config.cpp +++ b/examples/common/env_config.cpp @@ -133,6 +133,8 @@ devourer::DeviceConfig devourer_config_from_env() { cfg.debug.efuse_dump = env_flag("DEVOURER_EFUSE_DUMP"); cfg.debug.log_writes = env_flag("DEVOURER_LOG_WRITES"); cfg.debug.log_txpwr = env_flag("DEVOURER_LOG_TXPWR"); + if (const char *e = env_str("DEVOURER_REPLAY_WSEQ")) + cfg.debug.replay_wseq = e; cfg.debug.hop_prof = env_flag("DEVOURER_HOP_PROF"); cfg.debug.gaintab_dbg = env_flag("DEVOURER_GAINTAB_DBG"); diff --git a/src/AdapterCaps.h b/src/AdapterCaps.h index c60a661..bc11f75 100644 --- a/src/AdapterCaps.h +++ b/src/AdapterCaps.h @@ -59,10 +59,9 @@ constexpr uint8_t kBw80 = 1u << 4; /* 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. */ + * concept; the J2 8822B additionally needs an RF18 re-latch edge after the + * re-clock). 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::Jaguar1 ? ac @@ -114,7 +113,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 (Jaguar2 8821C, Jaguar3) */ + bool narrowband_ok = false; /* 5/10 MHz re-clock (Jaguar2/Jaguar3) */ bool fastretune_ok = false; /* lean FastRetune override exists */ bool per_chain_rssi = false; /* frame parser fills per-chain rssi (>=2ch) */ }; diff --git a/src/DeviceConfig.h b/src/DeviceConfig.h index dc31414..36b9e93 100644 --- a/src/DeviceConfig.h +++ b/src/DeviceConfig.h @@ -207,6 +207,13 @@ struct DeviceConfig { bool hop_prof = false; /* env: DEVOURER_GAINTAB_DBG — 8822E: log the TXGAPK gain-table backup. */ bool gaintab_dbg = false; + /* env: DEVOURER_REPLAY_WSEQ — golden-init replay (Jaguar2): path to a + * register write-sequence file (lines: " + * ", e.g. extracted from a kernel-driver usbmon capture). + * Applied verbatim at the end of Init, OVERRIDING devourer's own + * configuration — a debugging lever for hardware-diffing devourer + * against the vendor driver's end state. */ + std::string replay_wseq; } debug; /* ---- USB / process environment -------------------------------------- */ diff --git a/src/jaguar2/HalJaguar2.cpp b/src/jaguar2/HalJaguar2.cpp index ebd316e..102dd3a 100644 --- a/src/jaguar2/HalJaguar2.cpp +++ b/src/jaguar2/HalJaguar2.cpp @@ -12,8 +12,10 @@ #if defined(DEVOURER_HAVE_JAGUAR2_8821C) #include "Hal8821c_TxpwrLmt.h" /* generated: hal8821c_txpwr_lmt() WW-min limits */ #endif +#include "ChannelFreq.h" /* chan_to_freq for the spur-notch center */ #include "HopProf.h" /* DEVOURER_HOP_PROF fast-retune stage timing */ #include "PhyTableLoader.h" +#include "ToneMask.h" /* NBI notch + CSI mask appliers (spur calibration) */ namespace jaguar2 { @@ -514,6 +516,272 @@ void HalJaguar2::igi_toggle() { _device.phy_set_bb_reg(0x0e50, 0x7f, igi); } +void HalJaguar2::kfree_init() { + /* halrf_kfree.c port (8822B): phydm_get_power_trim_offset_8822b + + * phydm_get_pa_bias_offset_8822b. All reads are PHYSICAL efuse bytes (the + * PPG region above the logical map). */ + if (_variant == ChipVariant::C8821C) + return; /* 8821C kfree variant not ported yet */ + auto rd = [this](uint16_t a) -> uint8_t { + uint8_t d = 0xFF; + return _device.efuse_OneByteRead(a, &d) ? d : 0xFF; + }; + + /* Power trim (per-band-group TX gain): 2G at 0x3EE (A low nibble / B high), + * 5G groups at 0x3EC..0x3DB per path. 0xFF = unprogrammed. */ + const uint8_t pg2 = rd(0x3EE); + if (pg2 != 0xFF) { + _kfree_bb_gain[0][0] = pg2 & 0xF; + _kfree_bb_gain[0][1] = (pg2 & 0xF0) >> 4; + _kfree_2g = true; + } + const uint8_t g5l1a = rd(0x3EC); + if (g5l1a != 0xFF) { + static const uint16_t offs_a[5] = {0x3EC, 0x3E8, 0x3E4, 0x3E0, 0x3DC}; + static const uint16_t offs_b[5] = {0x3EB, 0x3E7, 0x3E3, 0x3DF, 0x3DB}; + for (int g = 0; g < 5; g++) { + _kfree_bb_gain[1 + g][0] = rd(offs_a[g]); + _kfree_bb_gain[1 + g][1] = rd(offs_b[g]); + } + _kfree_5g = true; + } + _logger->info("Jaguar2 kfree: power trim 2g={} 5g={} (0x3EE={:#04x})", + _kfree_2g, _kfree_5g, pg2); + + /* PA bias (phydm_get_pa_bias_offset_8822b + set_pa_bias_to_rf): efuse + * 0x3D5 (path A) / 0x3D6 (path B), sign-magnitude nibble, folded into the + * RF 0x3f PA-bias word recomposed from RF 0x51/0x52 and written into RF + * LUT entries 0/1/3 through the 0xef[10] window — WRITE-ONLY state (this + * block is the 0x3f=0x4bb sequence in the kernel's usbmon golden init). */ + const uint8_t pgba = rd(0x3D5); + if (pgba == 0xFF) + return; + for (uint8_t path = 0; path < (_ver.rf_2t2r ? 2u : 1u); path++) { + uint8_t pg = rd(path == 0 ? 0x3D5 : 0x3D6) & 0xF; + int8_t bias = (pg & 1) ? static_cast(pg >> 1) + : static_cast(-(pg >> 1)); + /* Close any LUT window the table apply left open — with 0xef nonzero the + * 0x51/0x52 reads alias LUT content, not the PA config (read back wrong + * as 0x1211 instead of the true 0x4bb until this was added). */ + rf_write(path, 0xef, 0x00000); + const uint32_t r51 = rf_read(path, 0x51); + const uint32_t r52 = rf_read(path, 0x52); + uint32_t r3f = ((r52 & 0xe0000) >> 17) | (((r52 & 0x18000) >> 15) << 3) | + ((r52 & 0xf) << 5) | (((r51 & 0x78) >> 3) << 9) | + (((r52 & 0x2000) >> 13) << 13); + int pa = static_cast((r3f & 0x1e00) >> 9) + bias; + if (pa < 0) + pa = 0; + else if (pa > 7) + pa = 7; + r3f = (r3f & 0xfe1ff) | (static_cast(pa) << 9); + rf_set(path, 0xef, (1u << 10), 0x1); + rf_write(path, 0x33, 0x0); + rf_write(path, 0x3f, r3f); + rf_set(path, 0x33, (1u << 0), 0x1); + rf_write(path, 0x3f, r3f); + rf_set(path, 0x33, (1u << 1), 0x1); + rf_write(path, 0x3f, r3f); + rf_set(path, 0x33, 0x3, 0x3); + rf_write(path, 0x3f, r3f); + rf_set(path, 0xef, (1u << 10), 0x0); + _logger->info("Jaguar2 kfree: PA bias path {} = {} (r51={:#07x} " + "r52={:#07x} rf3f={:#07x})", + path, bias, r51, r52, r3f); + } +} + +void HalJaguar2::kfree_apply(uint8_t channel) { + /* phydm_config_kfree / phydm_do_kfree (8822B): per-channel-group TX gain + * trim via RF 0xde/0x65/0x55; clear-to-default when the efuse carries no + * trim for the band. */ + if (_variant == ChipVariant::C8821C) + return; + const bool g2 = channel <= 14; + int idx = -1; + if (g2 && _kfree_2g) { + idx = 0; + } else if (!g2 && _kfree_5g) { + if (channel >= 36 && channel <= 48) + idx = 1; + else if (channel >= 52 && channel <= 64) + idx = 2; + else if (channel >= 100 && channel <= 120) + idx = 3; + else if (channel >= 122 && channel <= 144) + idx = 4; + else if (channel >= 149) + idx = 5; + } + for (uint8_t path = 0; path < (_ver.rf_2t2r ? 2u : 1u); path++) { + const uint8_t data = idx >= 0 ? _kfree_bb_gain[idx][path] : 0; + /* set (phydm_set_kfree_to_rf_8822b) — the clear variant appends the + * de-select tail. */ + rf_set(path, 0xde, (1u << 0), 1); + rf_set(path, 0xde, (1u << 4), 1); + rf_set(path, 0x65, 0xffff, 0x9000); + rf_set(path, 0x55, (1u << 5), 1); + rf_set(path, 0x55, (1u << 19), data & 1); + rf_set(path, 0x55, 0x7c000, (data & 0x1f) >> 1); + if (idx < 0) { /* phydm_clear_kfree_to_rf_8822b tail */ + rf_set(path, 0xde, (1u << 0), 0); + rf_set(path, 0xde, (1u << 4), 1); + rf_set(path, 0x65, 0xffff, 0x9000); + rf_set(path, 0x55, (1u << 5), 0); + rf_set(path, 0x55, (1u << 7), 0); + } + } +} + +void HalJaguar2::spur_calibration(uint8_t channel, uint8_t cch, uint8_t bw) { + /* phydm_spur_calibration_8822b + phydm_dynamic_spur_det_eliminate port + * (8822B only; CONFIG_8822B_SPUR_CALIBRATION is on in the CE vendor + * build). The chip's own clock spurs land inside a fixed set of channels; + * on those the vendor PSD-scans the spur frequency and, above threshold, + * programs the NBI notch + CSI mask. On every other channel this reduces + * to the reset half (clear masks + notch disables) — still vendor parity + * on every channel set. */ + if (_variant == ChipVariant::C8821C) + return; + const bool r2t2r = _ver.rf_2t2r != 0; + + /* Entry disables + dsde_init: clear the CSI mask bank and both notch + * enables. */ + _device.phy_set_bb_reg(0x087c, (1u << 13), 0x0); + _device.phy_set_bb_reg(0x0c20, (1u << 28), 0x0); + _device.phy_set_bb_reg(0x0e20, (1u << 28), 0x0); + for (uint16_t a = 0x880; a <= 0x89c; a += 4) + _device.rtw_write32(a, 0); + _device.phy_set_bb_reg(0x0874, 0x1, 0x0); + + /* phydm_dsde_ch_idx: the spur-channel table. idx 16 = no spur here. */ + const bool g2 = channel <= 14; + uint8_t idx = 16; + if (g2) { + if (bw == 0) { /* 20M: ch 5..8 -> 0..3, ch 13 -> 4 */ + if (channel >= 5 && channel <= 8) + idx = static_cast(channel - 5); + else if (channel == 13) + idx = 4; + } else if (bw == 1) { /* 40M: ch 3..11 -> 5..13 */ + if (channel >= 3 && channel <= 11) + idx = static_cast(channel + 2); + } + } else { + switch (channel) { + case 153: idx = 0; break; + case 161: idx = 1; break; + case 54: idx = 2; break; + case 118: idx = 3; break; + case 151: idx = 4; break; + case 159: idx = 5; break; + case 58: idx = 6; break; + case 122: idx = 7; break; + case 155: idx = 8; break; + default: break; + } + } + if (idx > 13) + return; + + /* PSD scan at the spur frequency point ±1 (3 samples each): 3-wire off, + * one RX path at a time, PSD engine via 0x910 BIT(22)|freq_pt, report at + * 0xf44[15:0]. Restores 3-wire / 0x910[15:12] / RX paths and toggles IGI + * so the RF re-enters RX (the BB sends no 3-wire on path re-enable). */ + static const uint32_t freq_2g[14] = {0xFC67, 0xFC27, 0xFFE6, 0xFFA6, 0xFC67, + 0xFCE7, 0xFCA7, 0xFC67, 0xFC27, 0xFFE6, + 0xFFA6, 0xFF66, 0xFF26, 0xFCE7}; + static const uint32_t freq_5g[10] = {0xFFC0, 0xFFC0, 0xFC81, 0xFC81, 0xFC41, + 0xFC40, 0xFF80, 0xFF80, 0xFF40, 0xFD42}; + if (!g2 && idx >= 10) + return; + const uint32_t f_base = g2 ? freq_2g[idx] : freq_5g[idx]; + uint32_t max_a = 0, max_b = 0; + for (int k = 0; k < 3; k++) { + const uint32_t f_pt = f_base - 1 + static_cast(k); + for (int j = 0; j < 3; j++) { + _device.phy_set_bb_reg(0x0c00, 0xff, 0x4); /* 3-wire off */ + _device.phy_set_bb_reg(0x0e00, 0xff, 0x4); + const uint32_t r910_15_12 = + (_device.rtw_read32(0x0910) >> 12) & 0xf; + /* path A */ + _device.phy_set_bb_reg(0x0808, 0xff, 0x11); + _device.phy_set_bb_reg(0x0910, 0xffffffff, (1u << 22) | f_pt); + std::this_thread::sleep_for(std::chrono::microseconds(500)); + uint32_t v = _device.rtw_read32(0x0f44) & 0xffff; + if (v > max_a) + max_a = v; + _device.phy_set_bb_reg(0x0910, (1u << 22), 0x0); + if (r2t2r) { /* path B: freq_pt | BIT(16) */ + _device.phy_set_bb_reg(0x0808, 0xff, 0x22); + _device.phy_set_bb_reg(0x0910, 0xffffffff, + (1u << 22) | (1u << 16) | f_pt); + std::this_thread::sleep_for(std::chrono::microseconds(500)); + v = _device.rtw_read32(0x0f44) & 0xffff; + if (v > max_b) + max_b = v; + _device.phy_set_bb_reg(0x0910, (1u << 22), 0x0); + } + _device.phy_set_bb_reg(0x0c00, 0xff, 0x7); /* 3-wire on */ + _device.phy_set_bb_reg(0x0e00, 0xff, 0x7); + _device.phy_set_bb_reg(0x0910, 0xf000, r910_15_12); + const uint8_t rx_ant = r2t2r ? 0x3 : 0x1; + _device.phy_set_bb_reg(0x0808, 0xff, + static_cast(rx_ant << 4) | rx_ant); + igi_toggle(); + } + } + + constexpr uint32_t kThreshold = 0x8D; /* NBI and CSI share it */ + const bool do_nbi = max_a >= kThreshold || max_b >= kThreshold; + if (!do_nbi) { + _logger->info("Jaguar2 spur-cal: ch{} bw{} psd A={} B={} — below " + "threshold, no notch", + channel, bw, max_a, max_b); + return; + } + + /* phydm_dsde_nbi: CCA tweak + notch at the spur frequency (MHz). The spur + * frequency per channel/BW, from the vendor tables. */ + _device.phy_set_bb_reg(0x082c, 0xff000, 0x97); + _device.phy_set_bb_reg(0x082c, 0xf000, 0x7); + uint32_t f_int = 0; + const uint32_t bw_mhz = bw == 1 ? 40 : bw == 2 ? 80 : 20; + if (bw == 0) { + if (channel == 153) f_int = 5760; + else if (channel == 161) f_int = 5800; + else if (channel >= 5 && channel <= 8) f_int = 2440; + else if (channel == 13) f_int = 2480; + } else if (bw == 1) { + if (channel == 54) f_int = 5280; + else if (channel == 118) f_int = 5600; + else if (channel == 151) f_int = 5760; + else if (channel == 159) f_int = 5800; + else if (channel >= 4 && channel <= 6) f_int = 2440; + else if (channel == 11) f_int = 2480; + } else if (bw == 2) { + if (channel == 58) f_int = 5280; + else if (channel == 122) f_int = 5600; + else if (channel == 155) f_int = 5760; + } + if (f_int == 0) + return; + const uint32_t fc = devourer::chan_to_freq(cch); + devourer::tonemask::nbi_apply_11ac(_device, + devourer::tonemask::Family::AC2_8822B, fc, + bw_mhz, f_int, r2t2r); + /* phydm_dsde_csi: mask the tones around the spur (±600 kHz ~ the vendor's + * tone+neighbor weights). */ + devourer::tonemask::CsiMaskSpec spec{}; + spec.valid = true; + spec.f_lo_khz = static_cast(f_int) * 1000 - 600; + spec.f_hi_khz = static_cast(f_int) * 1000 + 600; + devourer::tonemask::csi_mask_apply_11ac(_device, fc, bw_mhz, spec); + _logger->info("Jaguar2 spur-cal: ch{} bw{} psd A={} B={} — NBI+CSI notch " + "at {} MHz", + channel, bw, max_a, max_b, f_int); +} + void HalJaguar2::bb_reset() { /* MAC 0x0 BIT16 (SYS_FUNC_EN FEN_BBRSTB) 0->1 — the _iqk_bb_reset_8822b / * _8821c mechanism; relatches the BB DAC/DFE clock tree. */ @@ -615,6 +883,22 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, _device.phy_set_bb_reg(0x0860, 0x1ffe0000, 0x412); } + /* config_phydm_switch_band_8822b RxHP-with-SoML block (SoML is enabled at + * config_trx_mode): 5G SoML-on -> 0x8cc=0x08108000, 0x8d8[27]=0, + * 0xc04/0xe04[21|18]=0. 2.4G SoML-on with rfe_type 3/5/8/17 keeps the + * 0x08108492 defaults (vendor branch), other RFEs take the same values as + * 5G. Kernel-parity state read back from the working driver at ch44. */ + _device.phy_set_bb_reg(0x0c04, (1u << 21) | (1u << 18), 0x0); + _device.phy_set_bb_reg(0x0e04, (1u << 21) | (1u << 18), 0x0); + if (!g2 || !(rfe_type == 3 || rfe_type == 5 || rfe_type == 8 || + rfe_type == 17)) { + _device.phy_set_bb_reg(0x08cc, 0xffffffff, 0x08108000); + _device.phy_set_bb_reg(0x08d8, (1u << 27), 0x0); + } else { + _device.phy_set_bb_reg(0x08cc, 0xffffffff, 0x08108492); + _device.phy_set_bb_reg(0x08d8, (1u << 27), 0x1); + } + /* RF 0xBE[17:15] per-channel phase-noise / VCO-band setting (shared with * fast_retune). Omitting it (writing 0 on 5G) leaves the 5G VCO mis-tuned so * 5G RX/TX fail entirely — this was the sole gap blocking 5G. Path A only @@ -663,22 +947,15 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, * config_phydm_switch_bandwidth_8822b CHANNEL_WIDTH_5/10 — verbatim, * incl. its masks keeping the ADC/DAC fields from the previous BW set). * - * EXPERIMENTAL on the 8822B (caps report narrowband_ok=false): with this - * recipe the chip syncs only ~10% of NB frames on RX and airs no valid NB - * TX, while the OpenHD kernel module on the same dongle does full-rate NB - * with the SAME firmware blob and the same page-8 BB state (hardware - * A/B'd via reference/rtl88x2bu/88x2bu_ohd.ko + write_reg bisect). A - * usbmon capture of the kernel's 20->10 MHz switch plus the following - * 5 s shows NO H2C and NO host RF write beyond RF18 — yet the kernel's - * RF 0x82/0xf2 (RXBB) converge to NB values within ~3 s (0x44 -> 0x50 -> - * 0x52), i.e. the on-chip FIRMWARE retunes the RX front-end for small-BW - * autonomously — and it does so under the kernel but not under devourer, - * whose FW never receives the interface-up H2C state (media status / - * macid / RA) that arms the FW dynamic engine. Forcing the converged RF - * values statically does NOT recover RX (hardware-tested), so the FW's - * runtime assist — not the final register values — is the operative - * delta. The 8821C variant has no such dependence and is fully - * validated. */ + * The 8822B additionally requires an RF18 RE-LATCH EDGE after the + * re-clock — see the comment at the RF18 write below. (Found by + * golden-init replay: usbmon-capture the working kernel driver's full + * post-DLFW write stream, replay it verbatim from devourer via + * DEVOURER_REPLAY_WSEQ, delta-debug the 3630-write stream down to the + * causal pair. Every readable-register hypothesis had been eliminated + * first by A/B against reference/rtl88x2bu/88x2bu_ohd.ko: kernel NB + * survives devourer's entire differing MAC+BB+RF state and the removal + * of its only H2C traffic.) */ const bool is5 = (bw == 5); v8ac &= is5 ? 0xEFEEFE00 : 0xEFFEFF00; v8ac |= is5 ? (1u << 6) : (1u << 7); @@ -737,6 +1014,22 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, if (g2) rf18 &= 0x00060cffu; + /* 5/10 MHz: the RF synth only re-latches its internal channel state on an + * RF18 VALUE EDGE — a same-value rewrite does nothing, and after the + * baseband ADC/DAC re-clock the RF stays latched to pre-re-clock internals + * (RX deaf, TX invalid). The kernel never hits this because its flow always + * tunes through a channel/band change around a bandwidth switch; devourer's + * end-of-bring-up narrowband re-clock rewrites RF18 with the identical + * value. Force the edge: intermediate write with a different channel byte, + * then the real value. Isolated by golden-init replay bisection down to + * exactly this write pair (23300 vs 9 NB frames on the bench). */ + if (bw == 5 || bw == 6) { + const uint32_t rf18_alt = (rf18 & ~0xffu) | (cch == 1 ? 2u : 1u); + rf_write(0, 0x18, rf18_alt); + if (r2t2r) + rf_write(1, 0x18, rf18_alt); + } + rf_write(0, 0x18, rf18); if (r2t2r) rf_write(1, 0x18, rf18); @@ -754,8 +1047,12 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, _device.phy_set_bb_reg(0x0c20, (1u << 31), 0x1); _device.phy_set_bb_reg(0x0e20, (1u << 31), 0x1); - /* --- phydm_ccapar_by_rfe_8822b (CCA thresholds; rfe_type 0 => iFEM C-cut) --- - * col: 2G/5G x 1R/2R. reg82c/830/838 from cca_ifem_ccut. */ + /* --- phydm_ccapar_by_rfe_8822b (CCA thresholds) --- col: 2G/5G x 1R/2R. + * RFE types 3/5/12/15/16/17/19 (external-FEM boards, incl. the T3U's + * rfe_type 3) take the cca_ifem_ccut_rfe table — devourer previously + * hardcoded the plain iFEM C-cut column for every board, leaving eFEM + * boards with mis-set carrier-sense thresholds (kernel-parity write- + * sequence diff). */ { const int col = g2 ? (r2t2r ? 1 : 0) : (r2t2r ? 3 : 2); static const uint32_t cca_ifem[3][4] = { @@ -763,9 +1060,21 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, {0x79a0eaaa, 0x79A0EAAC, 0x79a0eaaa, 0x79a0eaaa}, /* 0x830 */ {0x87765541, 0x87746341, 0x87765541, 0x87746341}, /* 0x838 */ }; - _device.phy_set_bb_reg(0x082c, 0xffffffff, cca_ifem[0][col]); - _device.phy_set_bb_reg(0x0830, 0xffffffff, cca_ifem[1][col]); - _device.phy_set_bb_reg(0x0838, 0xffffffff, cca_ifem[2][col]); + static const uint32_t cca_ifem_rfe[3][4] = { + {0x75da8010, 0x75da8010, 0x75da8010, 0x75da8010}, /* 0x82c */ + {0x79a0eaaa, 0x97A0EAAC, 0x79a0eaaa, 0x79a0eaaa}, /* 0x830 */ + {0x87765541, 0x86666341, 0x87765561, 0x86666361}, /* 0x838 */ + }; + const bool rfe_tab = rfe_type == 3 || rfe_type == 5 || rfe_type == 12 || + rfe_type == 15 || rfe_type == 16 || rfe_type == 17 || + rfe_type == 19; + const auto &cca = rfe_tab ? cca_ifem_rfe : cca_ifem; + _device.phy_set_bb_reg(0x082c, 0xffffffff, cca[0][col]); + _device.phy_set_bb_reg(0x0830, 0xffffffff, cca[1][col]); + _device.phy_set_bb_reg(0x0838, 0xffffffff, cca[2][col]); + /* 20 MHz DFS-channel CCA adjust (vendor tail). */ + if (bw == 0 && !g2 && ((cch >= 52 && cch <= 64) || (cch >= 100 && cch <= 144))) + _device.phy_set_bb_reg(0x0838, 0xf0, 0x5); } /* RX-path toggle (0x808 MASKBYTE0) to leave RX dead-zone, then IGI. rx_ant @@ -775,6 +1084,10 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, _device.phy_set_bb_reg(0x0808, 0xff, rx_ant | (rx_ant << 4)); igi_toggle(); + /* phydm_spur_calibration_8822b — reset the NBI/CSI notch state, and on the + * chip's own spur channels PSD-detect + notch (vendor switch_channel tail). */ + spur_calibration(channel, cch, bw); + /* 5/10 MHz: BB reset (MAC 0x0 BIT16 toggle — the same _iqk_bb_reset_8822b * mechanism) so the DAC/DFE relatch at the new sample rate. Hardware-taught * on Jaguar3 (RadioManagementJaguar3::set_bandwidth_dividers): without it @@ -782,6 +1095,11 @@ void HalJaguar2::set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, if (bw == 5 || bw == 6) bb_reset(); + /* phydm_config_kfree — per-channel-group efuse TX gain trim, applied by the + * vendor after every channel/BW set. Group-keyed, so FastRetune (intra-band + * hop) rides the last full set's trim. */ + kfree_apply(channel); + _last_tuned_ch = channel; _logger->info("Jaguar2: channel set ch={} bw={} (rf18=0x{:05x})", channel, (int)bw, rf18); @@ -1052,6 +1370,13 @@ void HalJaguar2::config_trx_mode() { _device.phy_set_bb_reg(0x0940, 0xff00, 0x0); } + /* phydm_somlrxhp_setting(true) — SoML (adaptive ML detection) ON, the + * kernel's steady state on the 8822B (0x19a8 = 0xd90a0000; devourer + * previously left the table default 0 = SoML off, a state the vendor only + * uses for specific RFE types). The band-specific RxHP companion values + * (0x8cc/0x8d8) are set in set_channel_bw's band block. */ + _device.phy_set_bb_reg(0x19a8, 0xffffffff, 0xd90a0000); + /* --- phydm_config_rx_path_8822b(AB) --- */ _device.phy_set_bb_reg(0x0a2c, (1u << 22), 0x0); /* disable MRC CCK CCA */ _device.phy_set_bb_reg(0x0a2c, (1u << 18), 0x0); /* disable MRC CCK barker */ diff --git a/src/jaguar2/HalJaguar2.h b/src/jaguar2/HalJaguar2.h index a6fbf82..ef53c9e 100644 --- a/src/jaguar2/HalJaguar2.h +++ b/src/jaguar2/HalJaguar2.h @@ -124,6 +124,23 @@ class HalJaguar2 { void set_channel_bw(uint8_t channel, uint8_t bw, uint8_t rfe_type, uint8_t primary_ch_idx = 0); + /* halrf kfree (efuse power/PA-bias trim), 8822B port. kfree_init reads the + * PPG physical-efuse bytes (2G/5G per-band-group TX gain trims + * 0x3DB..0x3EE, 2G PA bias 0x3D5/0x3D6) and applies the PA-bias RF LUT + * correction (RF 0x51/0x52 -> 0x3f via the 0xef[10] window, entries 0/1/3 + * — write-only LUT state a readable-register diff can never see). + * kfree_apply programs the per-channel-group TX gain trim (RF 0xde/0x65/ + * 0x55), phydm_config_kfree parity; run by every full channel set. */ + void kfree_init(); + void kfree_apply(uint8_t channel); + + /* phydm_spur_calibration_8822b port: clear the NBI/CSI notch state on every + * channel set, and on the fixed spur-channel set (2G 5-8/13 @20M, 3-11 + * @40M; 5G 153/161/54/118/151/159/58/122/155) PSD-scan the chip's own + * clock spur and program the NBI notch + CSI mask above threshold. 8822B + * only. `cch` = central channel (notch center frequency). */ + void spur_calibration(uint8_t channel, uint8_t cch, uint8_t bw); + /* Lean intra-band, same-bandwidth hop retune — the Jaguar2 FastRetune core * (see docs/frequency-hopping.md), variant-dispatched like set_channel_bw. * Only the per-hop essentials run: one cached full-register RF18 write per @@ -245,6 +262,7 @@ class HalJaguar2 { * mechanism) — relatches the BB clock tree; required after the 5/10 MHz * ADC/DAC re-clock (as on Jaguar3). */ void bb_reset(); + /* Central channel of the wide channel (shared full/fast paths): 20 MHz -> * primary; 40 MHz -> ±2 by primary_ch_idx; 80 MHz -> +6/+2/-2/-6. */ static uint8_t central_ch(uint8_t channel, uint8_t bw, uint8_t primary_ch_idx); @@ -272,6 +290,11 @@ class HalJaguar2 { int _last_cck_key = -1; /* 2G spur (8822B) / CCK-filter (8821C) key: ch14? */ bool _warned_uncharacterized = false; /* one-shot extended-channel warning */ + /* kfree (efuse power-trim) state — see kfree_init/kfree_apply. bb_gain rows: + * 0=2G, 1..5 = 5G L1/L2/M1/M2/H per the PHYDM_5G* channel groups. */ + uint8_t _kfree_bb_gain[6][2] = {}; + bool _kfree_2g = false, _kfree_5g = false; + /* --- 8821C-specific (C8821C variant) channel/RF-set/LCK, transcribed from * reference/8821cu phydm_hal_api8821c.c + halrf_8821c.c. The 8822B channel-set * reuse diverges materially for the 1T1R 8821C (BTG/WLG RF-set mux, 0xc1c AGC diff --git a/src/jaguar2/HalmacJaguar2MacInit.cpp b/src/jaguar2/HalmacJaguar2MacInit.cpp index 6600727..8c45db3 100644 --- a/src/jaguar2/HalmacJaguar2MacInit.cpp +++ b/src/jaguar2/HalmacJaguar2MacInit.cpp @@ -1,9 +1,12 @@ #include "HalmacJaguar2MacInit.h" #include +#include #include #include +#include "FrameParserJaguar2.h" /* H2C-queue txdesc macros + checksum */ + /* These names are also #define macros in the Realtek hal_com_reg.h pulled in via * RtlAdapter.h; #undef them so the scoped constexpr below compile (this .cpp * uses no other definition of them). */ @@ -421,6 +424,87 @@ bool HalmacJaguar2MacInit::priority_queue_cfg() { return true; } +bool HalmacJaguar2MacInit::send_h2c_pkt(const uint8_t pkt[32]) { + /* PLTFM_SEND_H2C_PKT (rtl8822bu_halmac.c): 48-byte txdesc with TXPKTSIZE=32 + * + QSEL 0x13 (H2C queue), checksum, no OFFSET field — byte-verified against + * a usbmon capture of the kernel driver's GENERAL_INFO packet (desc + * 0x00000020 / 0x00001300 / ... / checksum 0x1320). */ + uint8_t frame[TXDESC_SIZE_8822B + 32] = {0}; + SET_TX_DESC_TXPKTSIZE_8822B(frame, 32); + SET_TX_DESC_QSEL_8822B(frame, 0x13); + cal_txdesc_chksum_8822b(frame); + std::memcpy(frame + TXDESC_SIZE_8822B, pkt, 32); + const int got = _device.bulk_send_sync_ep( + _device.first_bulk_out_ep(), frame, static_cast(sizeof(frame)), + 1000); + return got == static_cast(sizeof(frame)); +} + +bool HalmacJaguar2MacInit::send_fw_general_info(uint8_t rfe_type, bool r2t2r, + uint8_t cut_ver, + uint8_t package_type) { + /* halmac h2c-pkt wire header (set_h2c_pkt_hdr_88xx, byte-verified via + * usbmon): {0x01, CMD_ID_FW_OFFLOAD=0xff, sub_cmd, 0, total_len, 0, + * seq_lo, seq_hi}; content from offset 8. */ + const FifoParams fp = fifo_params(_variant, _device.is_usb()); + const uint16_t tx_fifo_pg_num = fp.tx_fifo_size >> TX_PAGE_SHIFT; + const uint16_t rsvd_pg_num = + RSVD_PG_DRV_NUM + RSVD_PG_H2C_EXTRAINFO_NUM + RSVD_PG_H2C_STATICINFO_NUM + + RSVD_PG_H2CQ_NUM + RSVD_PG_CPU_INSTRUCTION_NUM + RSVD_PG_FW_TXBUF_NUM + + fp.rsvd_csibuf_num; + const uint16_t rsvd_boundary = + static_cast(tx_fifo_pg_num - rsvd_pg_num); + const uint16_t fw_txbuf_addr = static_cast( + tx_fifo_pg_num - fp.rsvd_csibuf_num - RSVD_PG_FW_TXBUF_NUM); + + /* GENERAL_INFO (0x0d): FW TX-buffer page offset above the rsvd boundary + * (proc_send_general_info_88xx: rsvd_fw_txbuf_addr - rsvd_boundary). */ + uint8_t gen[32] = {0}; + gen[0] = 0x01; + gen[1] = 0xff; + gen[2] = 0x0d; /* SUB_CMD_ID_GENERAL_INFO */ + gen[4] = 8 + 4; + gen[6] = _h2c_seq++; + gen[10] = static_cast(fw_txbuf_addr - rsvd_boundary); + if (!send_h2c_pkt(gen)) { + _logger->error("Jaguar2: GENERAL_INFO H2C send failed"); + return false; + } + + /* PHYDM_INFO (0x11): rfe / rf-type (halmac enum: 2T2R=2, 1T1R=4) / cut / + * per-nibble RX|TX antenna masks / ext_pa / package type / mp_mode. */ + uint8_t phy[32] = {0}; + phy[0] = 0x01; + phy[1] = 0xff; + phy[2] = 0x11; /* SUB_CMD_ID_PHYDM_INFO */ + phy[4] = 8 + 8; + phy[6] = _h2c_seq++; + phy[8] = rfe_type; + phy[9] = r2t2r ? 0x02 : 0x04; + phy[10] = cut_ver; + phy[11] = r2t2r ? 0x33 : 0x11; /* rx_ant | tx_ant<<4 */ + phy[12] = 0x00; /* ext_pa */ + phy[13] = package_type; + phy[14] = 0x00; /* mp_mode */ + if (!send_h2c_pkt(phy)) { + _logger->error("Jaguar2: PHYDM_INFO H2C send failed"); + return false; + } + /* FW-consumption check: the MAC advances H2C_HEAD as packets enter the + * queue and the FW advances H2C_READ_ADDR as it consumes them — equal + * pointers mean the FW has read everything (get_h2c_buf_free_space_88xx + * semantics). */ + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + const uint32_t head = _device.rtw_read32(REG_H2C_HEAD) & 0x3ffff; + const uint32_t rd = _device.rtw_read32(REG_H2C_READ_ADDR) & 0x3ffff; + _logger->info("Jaguar2: FW general/phydm info H2C sent (rfe={} rf={} cut={} " + "pkg={} fw_txbuf_off={} h2cq head=0x{:x} read=0x{:x}{})", + rfe_type, r2t2r ? "2T2R" : "1T1R", cut_ver, package_type, + fw_txbuf_addr - rsvd_boundary, head, rd, + head == rd ? " CONSUMED" : " PENDING"); + return true; +} + void HalmacJaguar2MacInit::init_h2c() { const FifoParams fp = fifo_params(_variant, _device.is_usb()); const uint16_t tx_fifo_pg_num = fp.tx_fifo_size >> TX_PAGE_SHIFT; @@ -453,6 +537,17 @@ bool HalmacJaguar2MacInit::init_mac_cfg(ChannelWidth_t bw) { (void)bw; if (!init_trx_cfg(/*set_bcn_boundary=*/true)) return false; + /* halmac cfg_mac_clk_88xx (run by every kernel cfg_bw): MAC clock select + * 80 MHz (REG_AFE_CTRL1 0x24[21:20]=0) + the TSF/EDCA microsecond-tick + * dividers (0x55c/0x638 = 80). The chip reset defaults are WRONG for the + * running clock (0x55c=0x64, 0x638=0x28 read back on the 8822BU): with the + * µs ticks desynced 1.25-2x, ordinary 20 MHz frames survive but the 2-4x + * longer-in-air 5/10 MHz narrowband frames hit mistimed MAC RX limits + * (kernel-parity register diff; the working kernel runs 0x50/0x50). */ + _device.rtw_write32(0x0024, + _device.rtw_read32(0x0024) & ~((1u << 20) | (1u << 21))); + _device.rtw_write8(0x055c, 80); + _device.rtw_write8(0x0638, 80); init_protocol_cfg(); init_edca_cfg(); init_wmac_cfg(); diff --git a/src/jaguar2/HalmacJaguar2MacInit.h b/src/jaguar2/HalmacJaguar2MacInit.h index 3890cbe..972677e 100644 --- a/src/jaguar2/HalmacJaguar2MacInit.h +++ b/src/jaguar2/HalmacJaguar2MacInit.h @@ -62,9 +62,23 @@ class HalmacJaguar2MacInit { * PCIe backend only; run once before the first power-on. */ void pcie_phy_cfg(); + /* halmac send_general_info_88xx: hand the booted firmware its PHY/RF + * identity — two 32-byte H2C packets on the H2C queue (qsel 0x13): + * GENERAL_INFO (FW TX-buffer page offset) + PHYDM_INFO (rfe_type / rf_type / + * cut / TX+RX antenna status / package type). The kernel driver sends this + * pair right after DLFW and again on every channel/BW switch; without it + * the FW's dynamic engine never arms (usbmon-verified as the only H2C + * traffic the working 8822B kernel driver produces — the FW-side RXBB + * narrowband retune depends on it). Requires init_mac_cfg (H2C queue) to + * have run. */ + bool send_fw_general_info(uint8_t rfe_type, bool r2t2r, uint8_t cut_ver, + uint8_t package_type); + private: bool priority_queue_cfg(); void init_h2c(); + /* One 32-byte halmac H2C packet -> H2C queue (48-byte txdesc, qsel 0x13). */ + bool send_h2c_pkt(const uint8_t pkt[32]); void init_protocol_cfg(); void init_edca_cfg(); void init_wmac_cfg(); @@ -74,6 +88,7 @@ class HalmacJaguar2MacInit { ChipVariant _variant; uint16_t _rsvd_boundary = 0; bool _set_bcn_boundary = true; + uint8_t _h2c_seq = 0; /* halmac h2c_info.seq_num */ }; } /* namespace jaguar2 */ diff --git a/src/jaguar2/RtlJaguar2Device.cpp b/src/jaguar2/RtlJaguar2Device.cpp index 206c8fa..8ea3f38 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -104,9 +104,30 @@ void RtlJaguar2Device::bring_up(SelectedChannel channel) { if (_cfg.tuning.rfe_type) rfe = *_cfg.tuning.rfe_type; _rfe = rfe; /* cache for SetMonitorChannel retune */ + + /* Arm the FW dynamic engine: halmac send_general_info (GENERAL_INFO + + * PHYDM_INFO H2C pair) — the only H2C traffic the kernel driver produces + * (usbmon-verified), sent right after DLFW. Without it the 8822B FW never + * runs its bandwidth-keyed RXBB assist and narrowband RX/TX is dead. + * package_type is FW-reported via the mac-hidden C2H in the kernel flow + * (7 on the bench 8822BU); devourer doesn't parse that C2H yet — 7 for the + * 8822B, 0 (unknown) for the 8821C. */ + { + const bool r2t2r = _hal.chip_version().rf_2t2r != 0; + const uint8_t pkg = _variant == jaguar2::ChipVariant::C8822B ? 7 : 0; + _macinit.send_fw_general_info(rfe, r2t2r, _hal.chip_version().cut, pkg); + } + _hal.apply_bb_rf_agc_tables(rfe); _logger->info("RtlJaguar2Device: PHY tables applied"); + /* halrf kfree init: read the PPG efuse trims and apply the PA-bias RF LUT + * correction (write-only LUT state — the 0x3f sequence visible in the + * kernel's golden init). Vendor order: right after the table apply and + * BEFORE config_trx_mode — the mode-table window writes there (RF 0xef + * toggles) alias the RF 0x51/0x52 reads the PA-bias word is recomposed + * from. */ + _hal.kfree_init(); _hal.config_trx_mode(); /* RF mode table + TX/RX antenna-path HW blocks */ _hal.set_channel_bw(static_cast(channel.Channel), bw, rfe, channel.ChannelOffset); @@ -279,6 +300,38 @@ void RtlJaguar2Device::Init(Action_ParsedRadioPacket packetProcessor, } } + if (!_cfg.debug.replay_wseq.empty()) { + /* Golden-init replay: apply a captured register write sequence verbatim + * (e.g. the kernel driver's post-DLFW init from a usbmon capture), + * OVERRIDING everything devourer configured above. Debug lever for + * hardware-diffing devourer's init against the vendor's — if a behavior + * gap survives an identical write stream, it is not host-register state. */ + FILE *fp = fopen(_cfg.debug.replay_wseq.c_str(), "r"); + if (!fp) { + _logger->error("replay_wseq: cannot open {}", _cfg.debug.replay_wseq); + } else { + unsigned addr, width; + unsigned long long val; + size_t n = 0; + while (fscanf(fp, "%x %u %llx", &addr, &width, &val) == 3) { + if (width == 1) + _device.rtw_write8(static_cast(addr), + static_cast(val)); + else if (width == 2) + _device.rtw_write16(static_cast(addr), + static_cast(val)); + else + _device.rtw_write32(static_cast(addr), + static_cast(val)); + if (++n % 1000 == 0) + _logger->info("replay_wseq: {} writes applied", n); + } + fclose(fp); + _logger->info("replay_wseq: DONE — {} writes from {}", n, + _cfg.debug.replay_wseq); + } + } + if (_cfg.debug.bb_dump) { /* Full BB/RF register dump in the vendor rtw_proc format for canary diff * against the kernel driver (tests/jaguar2_rx_canary.sh). */ @@ -747,20 +800,12 @@ devourer::AdapterCaps RtlJaguar2Device::GetAdapterCaps() { c.rx_chains = chains; c.per_chain_rssi = chains >= 2; c.bw_mask = devourer::bw_mask_for_generation(c.generation); - /* 5/10 MHz baseband re-clock via the 0x8ac small-BW/clock word. 8821C only: - * hardware-validated at both widths cross-generation against Jaguar3. The - * 8822B path carries the same (vendor-parity) register recipe but the chip - * comes up with ~10% RX sync rate and dead NB TX — the OpenHD kernel module - * on the same dongle does full-rate NB with the SAME firmware blob and the - * same BB register state, so the missing piece is a runtime FW interaction - * (the kernel's NB switch retunes the RF RXBB LPF — RF 0x82/0xf2 move with - * bandwidth without any driver register write). Until that is ported the - * 8822B does not advertise narrowband. */ - if (_variant == jaguar2::ChipVariant::C8821C) { - c.narrowband_ok = true; - } else { - c.bw_mask &= static_cast(~(devourer::kBw5 | devourer::kBw10)); - } + /* 5/10 MHz baseband re-clock via the 0x8ac small-BW/clock word — both + * variants, hardware-validated at both widths, both directions and both + * bands cross-generation against Jaguar3. The 8822B additionally needs the + * RF18 re-latch edge after the re-clock (see the set_channel_bw narrowband + * branch). */ + c.narrowband_ok = true; c.fastretune_ok = true; c.per_packet_txpower = true; /* TX descriptor TXPWR_OFSET LUT — Jaguar2 only */ devourer::set_standard_freq_ranges(c); diff --git a/tests/jaguar2_narrowband_sdr.sh b/tests/jaguar2_narrowband_sdr.sh index 9f4b8ea..e200247 100755 --- a/tests/jaguar2_narrowband_sdr.sh +++ b/tests/jaguar2_narrowband_sdr.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash # jaguar2_narrowband_sdr.sh — 5/10 MHz narrowband validation for Jaguar2 -# (RTL8811CU/8821C default — the validated variant; CHIP=8822b selects the -# RTL8822BU, whose NB is EXPERIMENTAL/gated: partial RX, no NB TX — see the -# set_channel_bw NB comment in HalJaguar2.cpp). The Jaguar2 twin of +# (RTL8811CU/8821C default; CHIP=8822b selects the RTL8822BU). The Jaguar2 +# twin of # tests/jaguar3_narrowband_sdr.sh: radiotap stays 20 MHz in narrowband mode, so # the ONLY witness is an SDR. Measures the OCCUPIED BANDWIDTH of devourer's # continuous TX at 20 / 10 / 5 MHz with the USRP B210 and confirms it halves / diff --git a/tests/narrowband_cross_rx.sh b/tests/narrowband_cross_rx.sh index bab63a6..0ee5ff0 100755 --- a/tests/narrowband_cross_rx.sh +++ b/tests/narrowband_cross_rx.sh @@ -7,8 +7,7 @@ # Cells: a 20 MHz control brackets the bench, then 10 and 5 MHz with both ends # re-clocked. Defaults pair Jaguar3 TX (8812CU, whose narrowband is already # SDR-validated) with Jaguar2 8821C RX (8811CU) — run the reverse direction -# too. The 8822BU (2357:012d) can be substituted but its NB is -# EXPERIMENTAL/gated (partial RX, no NB TX): +# and the 8822BU (2357:012d) variants too: # # sudo tests/narrowband_cross_rx.sh # J3 c812 -> 8811CU # sudo tests/narrowband_cross_rx.sh 0bda:c811 0bda:c812 # 8811CU -> J3 c812