diff --git a/CLAUDE.md b/CLAUDE.md index 627b8f56..10d0bb1c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,7 +40,9 @@ construction from the `SYS_CFG2` chip-id (Kestrel: PID-first): and rtl8822e (RTL8812EU/8822EU, chip-id `0x17`). 5/10 MHz narrowband, 80 MHz (incl. a 40-in-80 frame), halrf calibration. Sustained 5 GHz TX needs the coex runtime thread (started in `InitWrite`) — without it the - combo chip's coex firmware silences the antenna. 8822e constraints: + combo chip's coex firmware silences the antenna. A host that owns bulk-IN + itself can opt out (`tuning.j3_runtime_thread`, see `src/jaguar3/CLAUDE.md` + for what that costs). 8822e constraints: `docs/8822e-quirks.md`. - **Kestrel** (`src/kestrel/`): the Wi-Fi 6 / 802.11ax generation — RTL8852BU/8832BU (variant C8852B) and RTL8852CU/8832CU (C8852C), one HAL diff --git a/examples/common/env_config.cpp b/examples/common/env_config.cpp index 21748e95..762ee2c6 100644 --- a/examples/common/env_config.cpp +++ b/examples/common/env_config.cpp @@ -230,6 +230,8 @@ devourer::DeviceConfig devourer_config_from_env() { cfg.tuning.skip_trx_reassert = env_flag("DEVOURER_SKIP_TRX_REASSERT"); cfg.tuning.skip_rfe_init = env_flag("DEVOURER_SKIP_RFEINIT"); cfg.tuning.skip_coex = env_flag("DEVOURER_SKIP_COEX"); + if (const char *e = env_str("DEVOURER_J3_RUNTIME")) + cfg.tuning.j3_runtime_thread = std::strcmp(e, "0") != 0; cfg.tuning.skip_dig = env_flag("DEVOURER_SKIP_DIG"); /* Default-on knob: unset = tracking on; only "0" disables it. */ if (const char *e = env_str("DEVOURER_THERMAL_TRACK")) diff --git a/src/DeviceConfig.h b/src/DeviceConfig.h index a5949432..1759a60c 100644 --- a/src/DeviceConfig.h +++ b/src/DeviceConfig.h @@ -365,7 +365,28 @@ struct DeviceConfig { bool skip_trx_reassert = false; /* env: DEVOURER_SKIP_TRX_REASSERT (J2) */ bool skip_rfe_init = false; /* env: DEVOURER_SKIP_RFEINIT (J2) */ bool skip_coex = false; /* env: DEVOURER_SKIP_COEX (J2) */ - bool skip_dig = false; /* env: DEVOURER_SKIP_DIG (J2) */ + /* env: DEVOURER_J3_RUNTIME ("0" disables) — Jaguar3: start the coex / + * phydm watchdog runtime thread after bring-up (default ON). With no RX + * loop running, that thread also keeps a bulk-IN read posted to drain + * firmware C2H reports, and every ~2 s it writes coex, TX-power tracking + * and DIG/EDCCA registers. A host that owns bulk-IN itself (raw C2H) + * turns it off. This flag covers only that post-bring-up thread: + * StartRxLoop still runs its own phydm watchdog (DIG included) when the + * thread is off, so fixed gain during an RX session also needs skip_dig. + * A TX-only session with the thread off loses all of it: nothing drains + * C2H, so no tx.report (CCX) is emitted; pwr_track (thermal TX-power + * compensation, which sustains upper 5 GHz) stops; and the coex re-apply + * and FW heartbeats stop (src/jaguar3/CLAUDE.md: sustained 5 GHz TX + * needs them). */ + bool j3_runtime_thread = true; + /* env: DEVOURER_SKIP_DIG — J2: StartRxLoop starts no DIG thread; J3: the + * phydm watchdog (runtime thread and StartRxLoop worker) skips its DIG + * step. + * On J3 the wide-TX IGI floor (wide_tx_igi_floor) still writes IGI: the + * first 40/80 MHz frame raises a lower IGI to the floor, and leaving the + * floor clamps IGI down to the DIG window top. For IGI that stays exactly + * where the host put it, also set DEVOURER_WIDE_TX_IGI_FLOOR=0. */ + bool skip_dig = false; /* env: DEVOURER_THERMAL_TRACK — Jaguar2 thermal TX-power tracking * (default ON; "0" disables). A ~2 s tick reads the RF 0x42 thermal * meter, computes the delta vs the efuse baseline, and writes the vendor diff --git a/src/jaguar3/CLAUDE.md b/src/jaguar3/CLAUDE.md index ed8bc30c..c18f85a9 100644 --- a/src/jaguar3/CLAUDE.md +++ b/src/jaguar3/CLAUDE.md @@ -19,6 +19,9 @@ narrowband dividers, RF18 encoding), strategy interfaces `Jaguar3Calibration` `InitWrite`): sustained 5 GHz TX needs it — without its ~2 s WiFi-only coex re-apply + FW heartbeats, the combo chip's coex firmware silences the antenna. It also drains C2H, so TX-only sessions still see `tx.report`. + Opt-out: `tuning.j3_runtime_thread = false` (`DEVOURER_J3_RUNTIME=0`), for + a host that owns bulk-IN itself. A TX-only session then gets no + `tx.report`, no `pwr_track` and no coex/FW heartbeats. - **5/10 MHz narrowband**: the re-clock lives in the `0x9b0`/`0x9b4` 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`. diff --git a/src/jaguar3/PhydmRuntimeJaguar3.cpp b/src/jaguar3/PhydmRuntimeJaguar3.cpp index d7e0b545..e289b710 100644 --- a/src/jaguar3/PhydmRuntimeJaguar3.cpp +++ b/src/jaguar3/PhydmRuntimeJaguar3.cpp @@ -226,9 +226,10 @@ void PhydmRuntimeJaguar3::edcca(uint8_t igi) { } } -void PhydmRuntimeJaguar3::tick(uint8_t channel, bool edcca_track) { +void PhydmRuntimeJaguar3::tick(uint8_t channel, bool edcca_track, bool dig_track) { const FaStats fa = fa_statistics_and_reset(); - dig(fa, is_dfs_channel(channel)); + if (dig_track) + dig(fa, is_dfs_channel(channel)); if (fa.cck_enabled) cck_pd(fa); if (edcca_track) diff --git a/src/jaguar3/PhydmRuntimeJaguar3.h b/src/jaguar3/PhydmRuntimeJaguar3.h index 31c6eef0..4b89794e 100644 --- a/src/jaguar3/PhydmRuntimeJaguar3.h +++ b/src/jaguar3/PhydmRuntimeJaguar3.h @@ -32,8 +32,10 @@ class PhydmRuntimeJaguar3 { /* One watchdog pass: FA/CCA statistics + reset, DIG, CCK-PD (2 GHz), * EDCCA. `channel` = current center channel (band + DFS decision), * `edcca_track` = false leaves 0x84c untouched (DEVOURER_DIS_CCA / - * SetCcaMode owns the thresholds then). */ - void tick(uint8_t channel, bool edcca_track); + * SetCcaMode owns the thresholds then); `dig_track` = false skips DIG + * (DEVOURER_SKIP_DIG; the wide-TX IGI floor still applies, see + * set_igi_floor). */ + void tick(uint8_t channel, bool edcca_track, bool dig_track); /* A channel/bandwidth change invalidates the CCK-PD applied-level cache * (the kernel re-checks bw/nrx from registers each apply; the level is diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index 1adde54e..6fdef9ee 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -300,7 +300,7 @@ void RtlJaguar3Device::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { std::lock_guard lk(_reg_mu); /* edcca_track follows the EDCCA gate alone — see the housekeeping * tick below for why the all-or-nothing flag is the wrong input. */ - _phydm.tick(_channel.Channel, !_cca_edcca_disabled); + _phydm.tick(_channel.Channel, !_cca_edcca_disabled, !_cfg.tuning.skip_dig); } catch (...) { break; /* chip gone — the RX loop will wind down too */ } @@ -578,7 +578,7 @@ void RtlJaguar3Device::coex_runtime_loop() { * thresholds at 0x84c every ~2 s in the EDCCA-off/primary-on arm, * undoing the disable the caller asked for. Jaguar1 does the same * thing via SetEdccaTrack(!edcca_disabled) at the end of apply_cca. */ - _phydm.tick(_channel.Channel, !_cca_edcca_disabled); + _phydm.tick(_channel.Channel, !_cca_edcca_disabled, !_cfg.tuning.skip_dig); _hal.fw_update_wl_phy_info(); _hal.fw_set_pwr_mode_active(); _hal.fw_coex_query_bt_info(); @@ -1177,7 +1177,10 @@ void RtlJaguar3Device::InitWrite(SelectedChannel channel) { * adapter's transfer counter, so anything emitted after it would count * that thread's register and H2C traffic as bring-up. */ timer.total(); - _coex_thread = std::thread([this] { coex_runtime_loop(); }); + if (_cfg.tuning.j3_runtime_thread) + _coex_thread = std::thread([this] { coex_runtime_loop(); }); + else + _logger->info("Jaguar3: coex/phydm runtime thread not started (tuning.j3_runtime_thread)"); if (_cfg.rx.ack_responder && !SetAckResponder(*_cfg.rx.ack_responder)) /* DEVOURER_ACK_RESPONDER */ throw std::runtime_error(