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
11 changes: 11 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,17 @@ Knob-specific facts that aren't obvious from the field docs:
`crc_err`/`icv_err` set — the entry point for the fused-FEC salvage layer
(`docs/fused-fec.md`). Opt-in: a body with a corrupt tail is the worst-case
input for an IP-stack consumer that didn't ask for it.
- `DEVOURER_DIS_CCA=1` (Jaguar2/3, runtime `SetCcaMode`) disables the MAC
carrier-sense gate — **both** primary CCA (`0x520[14]`) and EDCCA (`[15]`) —
so injected/beacon TX stops deferring to a busy channel. The primary-CCA bit
is the one that matters: monitor injection is not CCA-free, it defers ~40–60%
to a co-channel 802.11 transmitter, and clearing `[14]` recovers ~1.5–2.2×
(on-air 8822EU/8812CU, `tests/dis_cca_tx_onair.sh`); the energy bit `[15]`
alone is null against a decodable preamble. **On by default on the streamtx
FPV downlink** (the link owns the channel — CSMA backoff only stutters it);
`DEVOURER_DIS_CCA=0` forces standard carrier-sense back. Does NOT apply the
vendor BB CCA-off writes (they deafen the RX). RX-decode side is a separate
null (`tests/dis_cca_onair.sh`).
- `DEVOURER_LA_CAPTURE=<trig>/<rate>M/dma0/port:0x880` (rxdemo) — one-shot
LA-mode IQ capture into the TX packet buffer (`src/LaCapture.h`): raw
complex baseband to a `DVLA` file, offline per-tone H(k) via
Expand Down
4 changes: 2 additions & 2 deletions docs/8822e-quirks.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ TX/TX+RX sessions, from a dedicated housekeeping thread in RX-only sessions
- **CCK packet detection** (type4, 2.4 GHz): CCK-FA moving average drives the
PD/CS level ladder in `0x1ac8/0x1acc/0x1ad0`.
- **EDCCA tracking**: `th_l2h = max(IGI+8, 48)`, `th_h2l = th_l2h − 8` into
`0x84c` (matches the kernel end state). `SetCcaMode`'s EDCCA-disable knob
suppresses this tracking.
`0x84c` (matches the kernel end state). `SetCcaMode`'s carrier-sense-disable
knob (primary CCA + EDCCA) suppresses this tracking.

The kernel's remaining watchdog mechanisms self-disable without a link and
are intentionally absent: CFO tracking (returns unless associated), rate
Expand Down
9 changes: 5 additions & 4 deletions docs/scheduled-mac.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ measured p99.9 between ~0.8 ms and ~3.2 ms across runs on the same channel.
the bulk of frames within tens of µs of nominal (floor RMS 11–26 µs; p90
≤ 64 µs). PCIe is the tightest (11 µs) but the USB floors are the same
order — transport choice does not gate slot design at ≥ ms slot sizes.
2. **The tail is channel deferral, and it does not go away.** Even at 5 GHz on
a mostly-idle channel, ~1% of frames air 0.1–2.4 ms late (ambient beacons +
CSMA — `SetCcaMode` relaxes energy-CCA, not preamble deferral). p99.9 sits
at ~1–3 ms on every transport.
2. **The tail is channel deferral under default carrier-sense.** Even at 5 GHz
on a mostly-idle channel, ~1% of frames air 0.1–2.4 ms late with CSMA on
(ambient beacons + carrier-sense backoff; `SetCcaMode` disables the MAC
carrier-sense gate — primary CCA + EDCCA — the lever that lets injected TX
punch through this deferral). p99.9 sits at ~1–3 ms on every transport.
3. **Go/no-go: fine (sub-ms) DL slots are REFUTED on all transports** for a
p99.9-grade deadline on a real channel. The design consequence is a
**submission-ahead scheduler** — submit a slot's frame `guard_us` before
Expand Down
7 changes: 4 additions & 3 deletions docs/time-distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,10 @@ The last limit is CSMA: a TBTT beacon still defers to carrier-sense and airs
after a variable backoff, so its scheduled-TSF stamp and delayed air time
diverge by ~hundreds of µs on a shared channel. In a time-distribution setup the
master **owns** the channel, so that backoff is pure loss — the master disables
EDCCA (`SetCcaMode`, on by default here; opt out with `DEVOURER_TSYNC_CSMA=1`)
and the beacon airs exactly on schedule. `SetCcaMode` is implemented on
Jaguar2/3 and is a deliberate no-op on Jaguar1: the J1 baseband EDCCA is
the MAC carrier-sense gate (`SetCcaMode`: primary CCA + EDCCA, on by default
here; opt out with `DEVOURER_TSYNC_CSMA=1`) and the beacon airs exactly on
schedule. `SetCcaMode` is implemented on Jaguar2/3 and is a deliberate no-op on
Jaguar1: the J1 baseband EDCCA is
already disabled by its init table (`0x8A4 = 0x7F7F7F7F`, thresholds
unreachable), and the measured J1 downlink needs no MAC-side gate — see the
bench row below. (Porting the J2 MAC-register recipe to J1 was bench-refuted:
Expand Down
10 changes: 9 additions & 1 deletion examples/streamtx/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,16 @@ int main(int argc, char **argv) {
}

WiFiDriver wifi_driver{logger};
auto stream_cfg = devourer_config_from_env();
/* FPV downlink default: disable the MAC carrier-sense gate so the video TX
* punches through co-channel traffic instead of deferring — on-air ~1.5-2.2x
* inject-rate recovery under a co-channel transmitter (issue #199, SetCcaMode /
* DEVOURER_DIS_CCA). The link owns the channel, so CSMA back-off only stutters
* it. Explicit DEVOURER_DIS_CCA=0 still forces standard carrier-sense back on. */
if (std::getenv("DEVOURER_DIS_CCA") == nullptr)
stream_cfg.tuning.disable_cca = true;
auto rtlDevice = wifi_driver.CreateRtlDevice(handle, nullptr, usb_lock,
devourer_config_from_env());
stream_cfg);
/* Jaguar1-only research features (TXAGC override, fast-retune hopping) aren't
* on the IRtlDevice contract — downcast for them; jag is null on Jaguar3, and
* the downcast plus its call sites compile out when Jaguar1 isn't built. */
Expand Down
6 changes: 4 additions & 2 deletions src/DeviceConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,10 @@ 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_DIS_CCA — Jaguar3 EDCCA-disable at bring-up (before the
* coex thread starts). Runtime equivalent: SetCcaMode. */
/* 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
* a busy channel and punches through co-channel traffic. Runtime equivalent:
* SetCcaMode. Default-on on the streamtx FPV downlink (issue #199). */
bool disable_cca = false;
/* env: DEVOURER_TXPKT_STEP_QDB — Jaguar3 per-packet power-bank step size
* in quarter-dB: the dB weight of one 0x1e70 offset-index step
Expand Down
28 changes: 17 additions & 11 deletions src/IRtlDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -380,17 +380,23 @@ class IRtlDevice {
* returns false when no beacon was active. */
virtual bool StopBeacon() { return false; }

/* Disable / restore the MAC EDCCA energy-detect gate (the vendor dis_cca
* recipe). With EDCCA off the MAC does not defer TX to carrier-sense, so a
* TBTT beacon airs exactly on schedule instead of after a CSMA backoff — the
* lever that collapses the hardware-beacon downlink residual to sub-µs on a
* shared channel (the master owns the channel). Also DEVOURER_DIS_CCA at
* construction. Implemented on Jaguar2/3; a DELIBERATE no-op on Jaguar1,
* whose baseband EDCCA is already disabled by its init table (0x8A4 =
* 0x7F7F7F7F) — its hardware-beacon downlink measures ~0.34 µs RMS on a
* crowded channel with no MAC-side gate, and porting the J2 register
* recipe was bench-refuted (no gain; the 0x524[11] clear conflicts with
* the vendor beacon-enable state). */
/* Disable / restore the MAC carrier-sense gate that defers TX — both primary
* CCA (0x520[14], carrier-sense of a decodable preamble) and EDCCA (0x520[15],
* energy detect). With it off the MAC does not defer TX to a busy channel, so
* injected/beacon TX punches through co-channel traffic instead of backing off:
* a TBTT beacon airs exactly on schedule (the sub-µs hardware-beacon downlink,
* master-owns-the-channel), and host-injected data holds its rate through a
* co-channel transmitter. Measured on-air (Jaguar3, 8822EU/8812CU): monitor
* injection otherwise defers ~40-60% to a co-channel 802.11 flooder; clearing
* the gate recovers ~1.5-2.2x, back to ~90% of the unimpeded rate
* (tests/dis_cca_tx_onair.sh, issue #199). The energy bit [15] alone is null
* against a decodable preamble — the primary-CCA bit [14] is what recovers the
* inject path. This is the MAC-gate only; the vendor's BB CCA-off writes are
* NOT applied (they deafen the RX). Also DEVOURER_DIS_CCA at construction.
* Implemented on Jaguar2/3; a DELIBERATE no-op on Jaguar1, whose baseband EDCCA
* is already disabled by its init table (0x8A4 = 0x7F7F7F7F) and whose
* hardware-beacon downlink measures ~0.34 µs RMS on a crowded channel with no
* MAC-side gate. */
virtual void SetCcaMode(bool disabled) { (void)disabled; }

/* Shift the next hardware beacon TBTT by `microseconds` (>0 = later/retard,
Expand Down
18 changes: 14 additions & 4 deletions src/jaguar2/RtlJaguar2Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ void RtlJaguar2Device::bring_up(SelectedChannel channel) {
}
_brought_up = true;

/* DEVOURER_DIS_CCA — disable the MAC carrier-sense gate at bring-up (as
* Jaguar3 does) so injected/beacon TX punches through a busy channel instead
* of deferring. Runtime equivalent: SetCcaMode. */
if (_cfg.tuning.disable_cca)
SetCcaMode(true);

/* DEVOURER_XTAL_CAP — apply the crystal-cap trim once the AFE is up
* (issue #217, the narrowband CFO lever). */
if (_cfg.tuning.xtal_cap)
Expand Down Expand Up @@ -1649,14 +1655,18 @@ int32_t RtlJaguar2Device::PinBeaconTbtt(int32_t offset_us) {

void RtlJaguar2Device::SetCcaMode(bool disabled) {
std::lock_guard<std::mutex> lk(_reg_mu);
/* Both MAC carrier-sense bits in REG_TX_PTCL_CTRL: primary CCA 0x520[14] +
* EDCCA [15], plus EDCCA_MSK_COUNTDOWN 0x524[11]. The primary-CCA bit is the
* one that stops TX deferring to a co-channel transmitter (issue #199); 0x520
* is the same HalMAC layout as the on-air-validated Jaguar3. */
uint32_t v520 = _device.rtw_read<uint32_t>(0x0520);
uint32_t v524 = _device.rtw_read<uint32_t>(0x0524);
if (disabled) { v520 |= (1u << 15); v524 &= ~(1u << 11); }
else { v520 &= ~(1u << 15); v524 |= (1u << 11); }
if (disabled) { v520 |= (1u << 15) | (1u << 14); v524 &= ~(1u << 11); }
else { v520 &= ~((1u << 15) | (1u << 14)); v524 |= (1u << 11); }
_device.rtw_write<uint32_t>(0x0520, v520);
_device.rtw_write<uint32_t>(0x0524, v524);
_logger->info("Jaguar2: MAC EDCCA {}", disabled ? "DISABLED (dis_cca)"
: "enabled (default)");
_logger->info("Jaguar2: MAC carrier-sense {}",
disabled ? "DISABLED (dis_cca: CCA+EDCCA)" : "enabled (default)");
}

uint64_t RtlJaguar2Device::ReadTsf() {
Expand Down
39 changes: 24 additions & 15 deletions src/jaguar3/RtlJaguar3Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1099,26 +1099,35 @@ RxEnergy RtlJaguar3Device::GetRxEnergy() {
return e;
}

/* Disable / restore the MAC EDCCA energy-detect gate (the vendor dis_cca proc's
* MAC half: BIT_DIS_EDCCA 0x520[15] + BIT_EDCCA_MSK_COUNTDOWN 0x524[11]). Caller
* holds _reg_mu.
/* Disable / restore the MAC carrier-sense gate that defers TX. Two 0x520 bits:
* BIT_DIS_CCA 0x520[14] (primary carrier-sense of a decodable preamble) and
* BIT_DIS_EDCCA 0x520[15] (energy detect), plus BIT_EDCCA_MSK_COUNTDOWN
* 0x524[11]. Caller holds _reg_mu.
*
* The vendor recipe ALSO writes three BB registers (0x1a9c[20], 0x1a14[9:8],
* 0x1d58[0xff8]); those are deliberately NOT done here. 0x1d58[0xff8]=0x1ff is
* the OFDM-CCA-off write (the CW-tone path uses it to stop OFDM detection for a
* bare carrier), so applying it makes the RX deaf to OFDM — MEASURED: the full
* recipe dropped 8822EU delivery from ~6800 to ~10 frames. The MAC EDCCA bit is
* the only part that's safe to touch on a live RX. See docs / the help-wanted
* issue for the measured null and why (EDCCA gates TX deferral, which devourer's
* monitor inject already bypasses). */
* The primary-CCA bit [14] is the one that matters for the host-injected data
* path: monitor injection is NOT CCA-free — it defers to a co-channel 802.11
* transmitter and drops ~40% of its submit rate (on-air, 8822EU, co-channel
* flooder vs a far-channel USB-contention control; tests/dis_cca_tx_onair.sh).
* Clearing [14] removes that deferral (~1.5x recovery, back to ~90% of the
* unimpeded rate) so injected/beacon TX punches through a busy channel — the
* "keep transmitting through interference" lever the OpenIPC-FPV community wants
* (issue #199). The energy bit [15] alone is null against a decodable preamble;
* it is kept because it also relaxes deferral to non-802.11 in-band energy.
*
* The vendor dis_cca recipe ALSO writes three BB registers (0x1a9c[20],
* 0x1a14[9:8], 0x1d58[0xff8]); those are deliberately NOT done here.
* 0x1d58[0xff8]=0x1ff is the OFDM-CCA-off write (the CW-tone path uses it to stop
* OFDM detection for a bare carrier), so applying it makes the RX deaf to OFDM —
* MEASURED: the full recipe dropped 8822EU delivery from ~6800 to ~10 frames.
* These MAC 0x520/0x524 bits gate TX only and are safe on a live RX. */
void RtlJaguar3Device::apply_cca_mode_locked(bool disabled) {
uint32_t v520 = _device.rtw_read<uint32_t>(0x0520);
uint32_t v524 = _device.rtw_read<uint32_t>(0x0524);
if (disabled) {
v520 |= (1u << 15);
v520 |= (1u << 15) | (1u << 14); /* DIS_EDCCA (energy) + DIS_CCA (carrier-sense) */
v524 &= ~(1u << 11);
} else {
v520 &= ~(1u << 15);
v520 &= ~((1u << 15) | (1u << 14));
v524 |= (1u << 11);
}
_device.rtw_write<uint32_t>(0x0520, v520);
Expand All @@ -1130,8 +1139,8 @@ void RtlJaguar3Device::SetCcaMode(bool disabled) {
_cca_disabled = disabled;
if (_brought_up)
apply_cca_mode_locked(disabled);
_logger->info("Jaguar3: MAC EDCCA {}", disabled ? "DISABLED (dis_cca)"
: "enabled (default)");
_logger->info("Jaguar3: MAC carrier-sense {}",
disabled ? "DISABLED (dis_cca: CCA+EDCCA)" : "enabled (default)");
}

void RtlJaguar3Device::SetMonitorChannel(SelectedChannel channel) {
Expand Down
32 changes: 14 additions & 18 deletions tests/dis_cca_onair.sh
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
#!/usr/bin/env bash
# MEASURE-FIRST gate for the dis_cca / EDCCA-disable knob (SetCcaMode,
# RX-SIDE measurement for the dis_cca / carrier-sense-disable knob (SetCcaMode,
# DEVOURER_DIS_CCA). The OpenIPC-FPV community reports dis_cca "doubles range /
# removes stuttering" on the rtl88x2eu. devourer injects in monitor mode, which
# already bypasses the MAC CSMA/CCA backoff on the *TX* side — so any real
# benefit must be RX-side: EDCCA muting the receiver (or forcing it to hold off)
# when in-band energy crosses the energy-detect threshold. dis_cca removes that
# gate, so the RX should keep demodulating the wanted frames through interference
# that would otherwise pin CCA busy.
# removes stuttering" on the rtl88x2eu. This script tests the RX-decode side: can
# the receiver keep demodulating wanted frames through interference that would
# otherwise pin CCA busy? The TX-side (whether injection defers to a busy channel)
# is a separate, stronger effect — see tests/dis_cca_tx_onair.sh.
#
# The test: ONE 8822EU DUT, ONE swept B210 AWGN interferer, ONE marginal beacon.
# A/B is DEVOURER_DIS_CCA off vs on at each noise gain. delivery = the final
# rx.txhit event's hits (canonical-SA beacons decoded); we also log median IGI +
# OFDM false-alarm from the rx.energy events.
#
# MEASURED RESULT (8822EU, 5-point AWGN sweep 46..76 dB): NULL. The MAC
# EDCCA-disable (DEVOURER_DIS_CCA=1) leaves delivery within 1% at every noise
# gain (33700 vs 33400 hits, ratio 0.99; IGI pinned, FA identical). The full
# vendor recipe (incl. the BB 0x1d58 OFDM-CCA-off write) instead DEAFENS the RX
# (~6800 -> ~10 hits), so it is not implemented. Mechanism: EDCCA gates TX
# deferral, not RX decode, and devourer injects in monitor mode — which already
# bypasses the CSMA/EDCCA backoff the community's "range" benefit comes from.
# So SetCcaMode is NOT promoted to IRtlDevice; the J3 knob + this harness are
# kept for anyone re-measuring on a real (non-monitor) link. This bench can't
# bury the wanted beacon under B210 AWGN (near-field front-end limit), so the
# null is "no effect in the testable regime" + the mechanism above.
# MEASURED RESULT (8822EU, 5-point AWGN sweep 46..76 dB): NULL on RX decode. The
# knob leaves delivery within 1% at every noise gain (33700 vs 33400 hits, ratio
# 0.99; IGI pinned, FA identical). The full vendor recipe (incl. the BB 0x1d58
# OFDM-CCA-off write) instead DEAFENS the RX (~6800 -> ~10 hits), so it is not
# implemented. The knob gates TX deferral, not RX decode — so no RX effect is
# expected here, and the real benefit is on the TX path (dis_cca_tx_onair.sh
# measured ~1.5-2.2x inject-rate recovery under a co-channel transmitter). This
# bench also can't bury the wanted beacon under B210 AWGN (near-field front-end
# limit), so the RX null is "no effect in the testable regime".
#
# Usage: sudo -v && tests/dis_cca_onair.sh
set -u
Expand Down
Loading
Loading