Repository navigation
DEVOURER_TX_RETRY_FALLBACK=off: pin retries at the descriptor rate; floor form measured and rejected (#362) - #371
Conversation
…loor form measured and rejected Every generation's inject path leaves the firmware retry rate-fallback ladder enabled (measured on the 8812CU: retried frames step MCS3 -> 54M -> 24M -> 18M -> 9M -> 6M, ~10% of retried frames finishing below the original rate). That is the right default for weak-signal links and the wrong one for constant-rate FPV, where a 6M re-air of an MCS3 frame costs ~4x airtime — the new knob's "off" sets DISDATAFB / DISABLE_FB and pins every retry at the descriptor DATARATE (measured: 1,200/1,200 retried frames re-aired at the original rate; default arm replicates the ladder; both at retry_limit 8, ~60k reports/arm). A DATA_RTY_LOWEST_RATE floor form was implemented, measured, and REJECTED: the fw reinterprets the bound inside the RA-group rate space (inject runs RA-group 9) — an MCS0 floor sent retried HT frames wandering into VHT rates (final_rate 45 = VHT1SS_MCS1) with a 20x retry inflation and a point of delivery loss. The enum carries the finding, the FrameParser headers keep the field position documented as deliberately unwritten, and the env parse warns on any non-"off" value instead of surprising the link. Knob on all three generations (same descriptor-bit semantics; J1's NDPA branch keeps its own unconditional disable). docs/scheduled-mac.md carries the measured distributions next to the retry-limit guidance; arq_e2e_delivery.sh grows the DRONE_FALLBACK passthrough. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ff knob Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR Summary by QodoAdd DEVOURER_TX_RETRY_FALLBACK=off to pin TX retries to descriptor rate
AI Description
Diagram
High-Level Assessment
Files changed (9)
|
Code Review by Qodo
1.
|
…ment remnants Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ssion-scoped floor arm - DISDATAFB=1 was written on every 8822C frame, which silently turned the fw retry ladder (OpenIPC#362/OpenIPC#371, RetryFallback::Default) off at 20 MHz too. It now covers 40/80 MHz frames only. Measured on the CU, unACKed unicast MCS3 limit 8: - 20 MHz: MCS3 x4 > MCS2 > 6M restored, 1223/1224. - 40 MHz: pinned at MCS3, 1208/1209. - 20 MHz frames with DISDATAFB=0 on a 40 MHz tune: 14163 / 0 failed, so the wedge keys on frame width. DeviceConfig.h's RetryFallback note says the 8822C's wide frames have no ladder at either setting. - The floor arm is now "this session has sent a wide frame" (_wide_tx_seen): - Set once, published after the floor is applied, cleared by Init, InitWrite and Stop. - The floor follows the tuned width, read under _reg_mu on both sides. - A retune racing the first wide frame can no longer skip it, and a re-Init no longer inherits it. - A 40 MHz frame on a 20 MHz tune still takes the lock once per session. - Dropping the floor clamps IGI back into the channel's DIG window at once, instead of leaving 0x26 until the next tick. Keeping it at 0 touches no register, so the 8822E path is unchanged. Not exercised on air: the drop clamp and the race. ctest 78/78. HT40 CU and EU and HT20 cells re-run, 0 failed (experiment doc). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ssion-scoped floor arm - DISDATAFB=1 was written on every 8822C frame, which silently turned the fw retry ladder (OpenIPC#362/OpenIPC#371, RetryFallback::Default) off at 20 MHz too. It now covers 40/80 MHz frames only. Measured on the CU, unACKed unicast MCS3 limit 8: - 20 MHz: MCS3 x4 > MCS2 > 6M restored, 1223/1224. - 40 MHz: pinned at MCS3, 1208/1209. - 20 MHz frames with DISDATAFB=0 on a 40 MHz tune: 14163 / 0 failed, so the wedge keys on frame width. DeviceConfig.h's RetryFallback note says the 8822C's wide frames have no ladder at either setting. - The floor arm is now "this session has sent a wide frame" (_wide_tx_seen): - Set once, published after the floor is applied, cleared by Init, InitWrite and Stop. - The floor follows the tuned width, read under _reg_mu on both sides. - A retune racing the first wide frame can no longer skip it, and a re-Init no longer inherits it. - A 40 MHz frame on a 20 MHz tune still takes the lock once per session. - Dropping the floor clamps IGI back into the channel's DIG window at once, instead of leaving 0x26 until the next tick. Keeping it at 0 touches no register, so the 8822E path is unchanged. Not exercised on air: the drop clamp and the race. ctest 78/78. HT40 CU and EU and HT20 cells re-run, 0 failed (experiment doc). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…or on the C die (#484) ## The problem The RTL8812CU (8822C) could not air a single 40 MHz injected frame on most 5 GHz channels, though the same card does HT40 under mainline rtw88. On 144(-), 36(+) and 149(+), bulk-OUT accepted 257 frames and then failed with rc=-7, and nothing went on air. At the abort the TX FIFO was full (`LQ 64/0 PUB 1745/19`): the MAC never completed the **first** 40 MHz frame. HT20 was unaffected. There were two independent faults, and fixing either alone is not enough. ## The change Both parts apply only to the 8822C. The 8822E's descriptor and DIG are byte-identical to before. - **DISDATAFB=1 on 40/80 MHz inject descriptors** (`fill_data_tx_desc_8822c`, new `dis_data_fb` argument). - With USE_RATE=1, the MAC's per-frame rate-fallback lookup still consults the firmware's per-MACID rate entry, which an injecting MACID does not have. - On the 8822C that wedges a 40 MHz frame outright. DISDATAFB=1 is what the vendor driver's fixed-rate xmit path writes. - 20 MHz frames keep DISDATAFB=0 and with it the firmware retry ladder (#362/#371). The 8822C's wide frames have no ladder at either `DEVOURER_TX_RETRY_FALLBACK` setting, and `DeviceConfig.h` says so. - **A DIG floor at 40/80 MHz** (`DIG_MIN_8822C_WIDE 0x26`, `PhydmRuntimeJaguar3::set_igi_floor`). - On a *quiet* channel the receiver parks below IGI 0x24, in a gain state where the secondary 20 MHz never reads idle, so the MAC holds every 40 MHz frame. - Busy channels self-heal, because received frames re-run the AGC. That is why the fault hides on a busy bench channel. - The floor is armed for the session by the device's first >= 40 MHz frame. Init, InitWrite and `Stop()` clear the arm. - From then on it follows the tuned width, re-applied under `_reg_mu` by every tune (Init / InitWrite / SetMonitorChannel / FastRetune / FastSetBandwidth). It is on at 40/80 MHz. At a narrower width it drops, with IGI clamped straight back into the normal DIG window. - A device that only receives at 40 MHz is never armed. That includes a CU ground station sending 20 MHz control frames, which keeps its normal floor. - Arming takes `_reg_mu` once per session and never on the steady-state send path. The flag is published only after the floor is applied, so neither a racing retune nor a concurrent sender can skip it. Record, including the dead ends (firmware RA_INFO in every layout, firmware version, IQK, TX power, CCA, AGC lower bound): `docs/experiments/8822c-ht40-tx-stall.md`. ## Measured txdemo on the CU, with an 8812EU witness on rxdemo at the same channel and width. 1 kB QoS-data frames, MCS3/40/LDPC, 50 s per run, no knobs. "Aired" means CRC-ok frames the witness decoded with `bw:1`. | channel | width | submitted | failed | aired (witness) | |---|---|---|---|---| | 144(-) | 40 | 20552 | 0 | 20500 MCS3 bw:1 | | 52(+) | 40 | 20525 | 0 | 20425 MCS3 bw:1 | | 149(+) | 40 | 20590 | 0 | 20477 MCS3 bw:1 | | 36(+) | 40 | 20483 | 0 | 19723 MCS3 bw:1 | | 144(-) | 20 | 20679 | 0 | 20629 MCS3 bw:0 (IGI stays 0x20) | Before the change, the same 40 MHz runs aired nothing. How each half was isolated: - With only DISDATAFB=1, 36(+) worked but the silent channels 52/100/120/144 hung after 0–130 frames. - On silent 144, pinning IGI at 0x35, 0x2c, 0x26 or 0x24 gave about 20.6k frames with 0 failures each. 0x22 gave 22 frames, then a hang. - On silent 144, a second card sending MCS0/20 at 50 fps also cleared the hang (20.4k frames, 0 failed). - With the floor armed, a 20 MHz frame into a 40 MHz tune ran 9928 / 0 at IGI 0x20, and an RX-only session at 40 MHz stayed at 0x20. The counterpart: the floor costs idle-channel RX sensitivity at 40/80 MHz on a CU that transmits wide. That cost is **estimated from the 1 dB IGI step, not measured**: 6 dB over the DFS floor 0x20, and 4–8 dB over the 0x1e..0x22 coverage window on a non-DFS channel. Each row is a single 50 s run. The effect size (0 vs ~20k) is far outside probe-to-probe noise, but the 36(+) row's lower witness count (96.3% vs ~99.5% elsewhere) was not repeated. **Re-test on the final code** (both adapters local, near-field, silent channels confirmed by an 8 s scan beforehand, 30 s per cell): | cell | DUT -> witness | tune | frames | submitted | failed | witness | IGI | |---|---|---|---|---|---|---|---| | 144(-) | CU -> EU | 40 | MCS3/40 | 14161 | 0 | 14109 MCS3 bw:1 | 0x26 | | 149(+) | CU -> EU | 40 | MCS3/40 | 14146 | 0 | 14094 MCS3 bw:1 | 0x26 | | 144 | CU -> EU | **20** | MCS3/40 | 14282 | 0 | 0 (expected) | 0x20 (unarmed) | | 144 | CU -> EU | 20 | MCS3/20 | 14198 | 0 | 14142 MCS3 bw:0 | 0x20 | | 144(-) | **EU** -> CU | 40 | MCS3/40 | 14455 | 0 | 14321 MCS3 bw:1 | 0x20 | - In the last row the 8822E transmits HT40 unchanged. The CU, receiving only at 40 MHz as the witness, also stays at 0x20. - The send rate is gap-limited (~460 fps), so these runs do not measure the removed per-frame lock directly. The third row checks only that the floor stays unarmed. **After the review (f87521b)**, same bench, silent 144: | cell | tune | frames | submitted | failed | witness | IGI | |---|---|---|---|---|---|---| | CU -> EU, 144(-) | 40 | MCS3/40 | 14117 | 0 | 14067 MCS3 bw:1 | 0x26 | | CU -> EU, 144(-) | 40 | **MCS3/20** (DISDATAFB=0) | 14163 | 0 | 14111 MCS3 bw:0 | 0x20 | | CU -> EU, 144 | 20 | MCS3/20 | 14285 | 0 | 14232 MCS3 bw:0 | 0x20 | | EU -> CU, 144(-) | 40 | MCS3/40 | 14456 | 0 | 14406 MCS3 bw:1 | 0x20 | Retry ladder, `tests/retry_ladder_probe.sh` shape: unACKed unicast, MCS3, limit 8. - **20 MHz:** 1223/1224 frames flew MCS3 x4 > MCS2 > 6M x4, the #371 ladder. - **40 MHz:** 1208/1209 pinned at MCS3. Headless: ctest full 78/78, without Jaguar3 78/78, 8822C-only 69/69. ## Not covered - **80 MHz** inherits the floor untested. - **2.4 GHz HT40 (ch 6)** drained on the TX side, but the witness received none of it. This is unexplained, and the change does not address it. - **The RX-sensitivity cost** above was not measured. - **Not exercised on air:** the IGI clamp when the floor drops (no demo changes width mid-session), and the retune/first-frame race, which rests on lock ordering. - **Range:** the re-test witness was near-field, so those runs are pass/fail, not range numbers. The original table used a separate ground-station 8812EU. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Closes #362 — with this, all four of the issue's acceptance items are met: the retry-limit curve and FEC dimensioning landed in #367 (steps 1+4), and steps 2–3 now have measured go/no-go with vendor citations plus the step-2 knob implemented.
What the measurements found (recorded sweep data + three fresh arms)
The fw fallback ladder is already on — every generation's inject path leaves DISDATAFB/DISABLE_FB at 0, and retried frames measurably step down: MCS3 → 54M → 24M → 18M → 9M → 6M, ~10% of retried frames finishing below the original rate. (My first issue comment quoted the vendor's injection path, which hard-disables fallback — devourer diverged; and a claimed J1 asymmetry was an NDPA-branch misread. Both corrected on the issue.)
What ships
DEVOURER_TX_RETRY_FALLBACK=off(DeviceConfig::Tx::retry_fallback) on all three generations — sets DISDATAFB/DISABLE_FB so retries re-air at the descriptor DATARATE, for constant-rate links where a 6M re-air of an MCS3 frame costs ~4× airtime. Unset = byte-identical current behaviour.docs/scheduled-mac.md— measured distributions beside the retry-limit guidance;DRONE_FALLBACKpassthrough in the arq harness.Step 3's disposition (also on the issue, vendor-cited): per-packet deadline NO-GO (no descriptor lifetime field on Jaguar3); per-AC PARTIAL via REG_PKT_LIFE_TIME 0x4C0 (1024 µs units) — effectively per-link since injection rides one queue; the fw's CCX state-2/3 lifetime signature must be bench-verified before anything trusts it.
Validation
Three fresh on-air arms (table above) + an off-arm hygiene smoke after the floor removal (888/888 pinned);
ctest49/49; default path byte-identical by construction.🤖 Generated with Claude Code