From e76959271f93e4324a192657bbc342347bb4b62f Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Sat, 18 Jul 2026 22:02:59 +0300 Subject: [PATCH] dis_cca: disable primary CCA (0x520[14]), not just EDCCA; streamtx default-on (#199) Monitor injection is not CCA-free. On-air (co-channel RTL flooder vs a far-channel USB-contention control, tests/dis_cca_tx_onair.sh) the DUT defers 40-60% of its submit rate to a co-channel 802.11 transmitter: 8812EU @ 5 GHz -41% -> +1.51x with dis_cca 8812CU @ 5 GHz -62% -> +2.25x 8822BU @ 5 GHz -36% -> +1.73x The existing DEVOURER_DIS_CCA disabled only EDCCA (0x520[15], energy detect), which is null against a decodable preamble; the gate that actually defers is primary CCA (0x520[14]). A pure CW-tone energy source does not defer injection at all, so EDCCA was never the inject-path gate. This refutes the prior premise that "monitor inject already bypasses the CSMA/CCA backoff." - SetCcaMode (Jaguar2/3) now clears both 0x520[14] and [15]. - Jaguar2 gains the disable_cca bring-up wiring it was missing (the knob only landed via the timesync runtime call before; DEVOURER_DIS_CCA was silently ignored on Jaguar2 injectors). - streamtx (FPV downlink) defaults it on; DEVOURER_DIS_CCA=0 opts out. The link owns the channel, so CSMA backoff only stutters it. - The vendor BB CCA-off writes (which deafen RX) are still not applied; the RX decode side remains a separate null (tests/dis_cca_onair.sh). - New TX-side harness tests/dis_cca_tx_onair.sh; corrected the "bypasses CCA" premise in comments and docs (IRtlDevice, DeviceConfig, CLAUDE, scheduled-mac, time-distribution, 8822e-quirks). Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 11 +++ docs/8822e-quirks.md | 4 +- docs/scheduled-mac.md | 9 +- docs/time-distribution.md | 7 +- examples/streamtx/main.cpp | 10 ++- src/DeviceConfig.h | 6 +- src/IRtlDevice.h | 28 ++++--- src/jaguar2/RtlJaguar2Device.cpp | 18 +++- src/jaguar3/RtlJaguar3Device.cpp | 39 +++++---- tests/dis_cca_onair.sh | 32 ++++---- tests/dis_cca_tx_onair.sh | 136 +++++++++++++++++++++++++++++++ 11 files changed, 240 insertions(+), 60 deletions(-) create mode 100755 tests/dis_cca_tx_onair.sh diff --git a/CLAUDE.md b/CLAUDE.md index e5823748..2d83526c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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=/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 diff --git a/docs/8822e-quirks.md b/docs/8822e-quirks.md index 967ded6b..c35c144d 100644 --- a/docs/8822e-quirks.md +++ b/docs/8822e-quirks.md @@ -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 diff --git a/docs/scheduled-mac.md b/docs/scheduled-mac.md index af954410..73b4e727 100644 --- a/docs/scheduled-mac.md +++ b/docs/scheduled-mac.md @@ -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 diff --git a/docs/time-distribution.md b/docs/time-distribution.md index cd8b740e..b39f3dab 100644 --- a/docs/time-distribution.md +++ b/docs/time-distribution.md @@ -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: diff --git a/examples/streamtx/main.cpp b/examples/streamtx/main.cpp index 81d2e2be..661d17bf 100644 --- a/examples/streamtx/main.cpp +++ b/examples/streamtx/main.cpp @@ -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. */ diff --git a/src/DeviceConfig.h b/src/DeviceConfig.h index 0d2cec38..a4f7c4fc 100644 --- a/src/DeviceConfig.h +++ b/src/DeviceConfig.h @@ -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 diff --git a/src/IRtlDevice.h b/src/IRtlDevice.h index c18c09d3..83c30be0 100644 --- a/src/IRtlDevice.h +++ b/src/IRtlDevice.h @@ -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, diff --git a/src/jaguar2/RtlJaguar2Device.cpp b/src/jaguar2/RtlJaguar2Device.cpp index 023ea2dc..c2d3ac68 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -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) @@ -1649,14 +1655,18 @@ int32_t RtlJaguar2Device::PinBeaconTbtt(int32_t offset_us) { void RtlJaguar2Device::SetCcaMode(bool disabled) { std::lock_guard 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(0x0520); uint32_t v524 = _device.rtw_read(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(0x0520, v520); _device.rtw_write(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() { diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index 49e014fc..88eb3d17 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -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(0x0520); uint32_t v524 = _device.rtw_read(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(0x0520, v520); @@ -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) { diff --git a/tests/dis_cca_onair.sh b/tests/dis_cca_onair.sh index b3fda078..6a0d21e1 100755 --- a/tests/dis_cca_onair.sh +++ b/tests/dis_cca_onair.sh @@ -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 diff --git a/tests/dis_cca_tx_onair.sh b/tests/dis_cca_tx_onair.sh new file mode 100755 index 00000000..a2b4b599 --- /dev/null +++ b/tests/dis_cca_tx_onair.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# TX-SIDE test for the dis_cca / carrier-sense-disable knob (SetCcaMode, +# DEVOURER_DIS_CCA) — the experiment issue #199 asks for. The sibling +# tests/dis_cca_onair.sh measured a NULL, but it ran the DUT as an RX (decoding a +# beacon through B210 AWGN) and never exercised the TX side. Carrier sense gates TX +# *deferral*, not RX decode, so the real test drives the DUT as a TX injector and +# asks: does it defer to a busy channel, and does dis_cca let it punch through? +# +# METHOD — co-channel RTL flooder (not a B210). A second RTL adapter (default the +# 8812AU) floods the DUT's channel with real 802.11 frames; the DUT's primary CCA +# (carrier-sense of the decodable preamble) then defers its injection. We measure +# the DUT's tx.stats `submitted` rate over a fixed window: +# alone -> full rate (baseline) +# co-chan + dis0 -> deferred (CCA holds the sync send back; lower submit rate) +# co-chan + dis1 -> recovered if dis_cca disables the gate +# FAR-chan + dis0 -> USB-hub-contention control (same USB load, no co-channel RF; +# both adapters share a bus, so this isolates the ~5% USB cost) +# The B210 AWGN path (tests/sdr_interferer.py) is NOT used here: on this rig it is +# too weakly coupled to the RTL front ends to trip a TX-deferring gate even at max +# gain (the #198 "near-field B210 couldn't bury the signal" limit). A co-channel +# RTL flooder couples strongly and is the reliable, rig-independent CCA trigger. +# +# FINDING (on-air, 8822EU): injection defers ~41-45% to a co-channel flooder (real +# RF, not USB — the far-channel control keeps the rate). The knob must disable +# BIT_DIS_CCA 0x520[14] (primary carrier-sense) to recover it (~1.5x, back to ~90% +# of the alone rate); BIT_DIS_EDCCA 0x520[15] alone (energy detect) is null against +# a decodable preamble. SetCcaMode disables both. +# +# Usage: sudo -v && tests/dis_cca_tx_onair.sh # all configs +# ONLY=eu5g REPS=3 sudo -v && tests/dis_cca_tx_onair.sh +set -u +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +OUT="${DISCCATX_OUT:-/tmp/devourer-dis-cca-tx}" +REPS="${REPS:-2}" # repeats per arm (reproducibility) +DUR="${DUR:-10}" # injector TX window (s) per cell +INJ_PWR="${INJ_PWR:-20}" # DUT TXAGC index +FLOOD_PWR="${FLOOD_PWR:-30}" # flooder TXAGC index (occupy the channel) +mkdir -p "$OUT" + +VID=0x0bda +EU_PID=0xa81a # RTL8812EU/8822EU — Jaguar3, the community chip +CU_PID=0xc812 # RTL8812CU — Jaguar3 rtl8822c sibling +FLOOD_PID=0x8812 # RTL8812AU — universal co-channel flooder (dual-band) + +flood_pid="" +cleanup() { [ -n "$flood_pid" ] && { kill "$flood_pid" 2>/dev/null; wait "$flood_pid" 2>/dev/null; }; true; } +trap cleanup EXIT INT TERM +plugged() { lsusb -d "$(printf '%04x:%04x' "$VID" "$1")" >/dev/null 2>&1; } + +# The in-tree rtw88 auto-probes the dongles on enumeration; unbind any it holds. +unbind_rtw88() { + local pid="$1" d p i + for d in /sys/bus/usb/devices/*/idProduct; do + p=$(cat "$d" 2>/dev/null) || continue + [ "$p" = "${pid#0x}" ] || continue + for i in "$(dirname "$d")":*; do + [ -e "$i/driver" ] && sudo sh -c "echo '$(basename "$i")' > '$i/driver/unbind'" 2>/dev/null || true + done + done +} + +# One DUT injection cell; echoes `submitted`. $1=dut_pid $2=ch $3=discca $4=tag +inject() { + local dis=""; [ "$3" = 1 ] && dis="DEVOURER_DIS_CCA=1" + sudo env DEVOURER_PID="$1" DEVOURER_VID="$VID" DEVOURER_CHANNEL="$2" \ + DEVOURER_TX_RATE=MCS3 DEVOURER_TX_PWR="$INJ_PWR" \ + DEVOURER_TX_GAP_US=0 DEVOURER_TX_USB_AGG=0 $dis \ + timeout "$DUR" "$ROOT/build/txdemo" >"$OUT/$4.log" 2>/dev/null || true + grep '"ev":"tx.stats"' "$OUT/$4.log" 2>/dev/null | tail -1 \ + | python3 -c 'import json,sys +try: print(json.loads(sys.stdin.read()).get("submitted",0)) +except: print(0)' +} +flood_start() { # $1=ch — start the co-channel/far flooder, set $flood_pid + sudo env DEVOURER_PID="$FLOOD_PID" DEVOURER_VID="$VID" DEVOURER_CHANNEL="$1" \ + DEVOURER_TX_RATE=6M DEVOURER_TX_PWR="$FLOOD_PWR" DEVOURER_TX_GAP_US=0 \ + timeout $((DUR * REPS * 2 + 60)) "$ROOT/build/txdemo" >"$OUT/flood-$1.log" 2>/dev/null & + flood_pid=$! +} +flood_stop() { [ -n "$flood_pid" ] && { kill "$flood_pid" 2>/dev/null; wait "$flood_pid" 2>/dev/null; }; flood_pid=""; } +mean() { python3 -c 'import sys; v=[int(x) for x in sys.argv[1:] if x]; print(round(sum(v)/len(v)) if v else 0)' "$@"; } + +run_config() { # $1=dut_pid $2=ch $3=farch $4=label + local dut="$1" ch="$2" farch="$3" label="$4" r + echo + echo "######## CONFIG $label DUT=$dut ch=$ch (flood=$FLOOD_PID, far=$farch) ########" + plugged "$dut" || { echo "SKIP $label: DUT $dut not plugged"; return; } + plugged "$FLOOD_PID" || { echo "SKIP $label: flooder $FLOOD_PID not plugged"; return; } + unbind_rtw88 "$dut"; unbind_rtw88 "$FLOOD_PID" + + local A=() D0=() D1=() F0=() + for r in $(seq 1 "$REPS"); do + A+=("$(inject "$dut" "$ch" 0 "$label-alone-$r")") + flood_start "$ch"; sleep 4 + D0+=("$(inject "$dut" "$ch" 0 "$label-co0-$r")") + D1+=("$(inject "$dut" "$ch" 1 "$label-co1-$r")") + flood_stop; sleep 1 + flood_start "$farch"; sleep 4 + F0+=("$(inject "$dut" "$ch" 0 "$label-far0-$r")") + flood_stop; sleep 1 + done + local a d0 d1 f0; a=$(mean "${A[@]}"); d0=$(mean "${D0[@]}"); d1=$(mean "${D1[@]}"); f0=$(mean "${F0[@]}") + printf " alone=%s co-chan/dis0=%s co-chan/dis1=%s far-chan/dis0=%s\n" "$a" "$d0" "$d1" "$f0" + printf "%s\t%s\t%s\t%s\t%s\n" "$label" "$a" "$d0" "$d1" "$f0" >>"$OUT/summary.tsv" + python3 - "$a" "$d0" "$d1" "$f0" "$label" <<'PYEOF' +import sys +a,d0,d1,f0,label=int(sys.argv[1]),int(sys.argv[2]),int(sys.argv[3]),int(sys.argv[4]),sys.argv[5] +if a==0: print(" (no baseline)"); sys.exit() +defr=100*(1-d0/a); usb=100*(1-f0/a); rec=(d1/d0) if d0 else 0 +print(f" co-chan deferral(dis0)={defr:+.0f}% USB-only(far)={usb:+.0f}% recovery dis1/dis0={rec:.2f}x dis1 vs alone={100*(1-d1/a):+.0f}%") +real = defr - usb # deferral net of USB contention +if real >= 15 and d1 >= a*0.85: + print(f" => {label}: dis_cca HELPS — real co-channel CCA deferral {real:.0f}% removed, TX back to ~alone") +elif real >= 15 and rec >= 1.25: + print(f" => {label}: dis_cca HELPS (partial) — {real:.0f}% deferral, {rec:.2f}x recovery") +elif real < 8: + print(f" => {label}: no real co-channel deferral (mostly USB) — inconclusive on this pairing") +else: + print(f" => {label}: deferral present but dis_cca does NOT recover it") +PYEOF +} + +echo "== building rxdemo + txdemo ==" +cmake --build "$ROOT/build" -j --target rxdemo txdemo >/dev/null || exit 1 +: >"$OUT/summary.tsv" + +ONLY="${ONLY:-eu5g cu5g eu24g}" +for cfg in $ONLY; do + case "$cfg" in + eu5g) run_config "$EU_PID" 36 149 "EU5G" ;; + cu5g) run_config "$CU_PID" 36 149 "CU5G" ;; + eu24g) run_config "$EU_PID" 6 149 "EU24G" ;; # 2.4 GHz DUT, 5 GHz far-control + *) echo "unknown config: $cfg" ;; + esac +done +echo; echo "raw: $OUT/summary.tsv"