rtl8733b: port the hardware-ARQ surface — ACK responder, retry limit, honoured ACK window - #406
Conversation
… honoured ACK window Closes the four items in #2, filed by a consumer scoping a ground->craft return path on this die. Three are now measured working; the fourth is root-caused and refused out loud instead of being faked. - **`tx.ack_timeout_us` was silently ignored.** `init_wmac()` hardcoded REG_ACKTO to 0x21 (33 us) and the config field was never read anywhere in `src/rtl8733b/` — so the one library default every generation is supposed to program identically was, on this die alone, pinned to the bottom of the 33..128 us spread that default exists to abolish. It is now applied at bring-up with the same register and 1..255 clamp the Jaguars use, and read back before it is claimed: `<unset>/128/33/200 -> 128/128/33/200`. The vendor write stays in `init_wmac()` because that MAC plane is shared verbatim with `rtl8733bprobe`, which wants the vendor recipe and carries no DeviceConfig. - **`SetAckResponder` is ported and measured** — `ack_responder_ok = true`. The `src/AckResponder.h` recipe applies unchanged, which is not an assumption: the vendor tree's own port-0 descriptor names these three registers (net_type `REG_CR_8733B + 2` = 0x0102, MACID 0x0610, BSSID 0x0618). MAC bring-up leaves net_type at No Link because `init_mac` writes only REG_CR's low half, which is exactly why a monitor radio here never ACKed. Measured against an RTL8812AU soliciting TX: armed 1736/1736 frames ACKed at retries_mean 0.00; re-armed on a different MAC, 1736/1736 again; disarmed, 0.00 with retries pinned at 12. The arm refuses a group MAC and is read back before being reported, and teardown disarms it — `_mac.stop()` clears only REG_CR's low half, so a session ending with `teardown_power_down` off would otherwise leave an unowned radio auto-ACKing (verified: after such a session the peer reads ack_rate 0.00). - **`tx.retry_limit` drives real autonomous retransmission** — `tx_retry_limit_ok = true`. It could not be measured the way the Jaguars were: that A/B reads the TX side's own CCX reports and this die has none. New `tests/rtl8733b_retry_limit_onair.sh` judges from the air instead — unicast to an unowned RA so no ACK ever returns, a passive monitor counting airings per submitted frame — and takes a dose-response rather than an on/off pair, because one pair could be ambient and a straight line through three levels cannot: 0/3/12 -> 0.93/3.93/12.27 airings per frame against an expected 1 + N, repeatable over a 0/3/12/0/12 ladder. - **CCX / `tx.report` is NOT ported, and the cause is the firmware.** Everything under this backend's control was verified correct on air — descriptor SPE_RPT and SW_DEFINE at the vendor's own bit positions, C2H decoded at the vendor's dword2[28], bulk-IN as the vendor's delivery path, and the fw-offload C2H format this firmware speaks per its own dispatch — and with an RX loop live and 3160 frames received the chip returned zero C2H packets in any format, with and without net_type armed and with the peer both ACKing and silent. The plumbing is therefore NOT shipped: descriptors stay byte-identical and the knob warns at bring-up, because a knob that looks granted while producing nothing is the exact failure this issue was filed about. The outstanding lead (halmac H2C queue + MEDIA_STATUS_RPT to register the descriptor MACID) is recorded so the next attempt starts where this one stopped. Counterparts, stated because the numbers above are uniformly favourable: one physical unit and one peer generation, no second responder die, no vendor-driver A/B. The retry figure is witnessed by a monitor that can only lose airings, never invent them, so it is a floor; its ~0.07/frame shortfall is the sampling quantization the harness documents. Verification: builds clean on default / 8733B-only / 8733B-off / ASan+UBSan with ctest green in each; lifecycle soak 6/6 HEALTHY; monitor RX unaffected by arming the responder; ASan teardown clean on hardware, and the 8733B added to `tests/teardown_gen_sanity.sh`, whose cell list predated the chip. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjcZRPjNtsy2mxfDkAWNpd
PR Summary by QodoPort hardware ARQ controls to RTL8733B
AI Description
Diagram
High-Level Assessment
Files changed (11)
|
Code Review by Qodo
1.
|
… duplication - **Partial arm could escape teardown** (Qodo OpenIPC#4, real bug). `_ack_armed` was set after `ack::enable()` returned. That call is five register writes ending with the net_type field that actually arms the engine, and any of them can throw on a stalling device — including after the arming write reached hardware. The exception unwinds into Init()/InitWrite()'s catch, which calls Stop(); with the flag still false that teardown skipped the disarm, leaving an unowned radio auto-ACKing when teardown_power_down is off. Latch before the first write instead: a redundant disarm costs one USB round-trip, a missed one costs an unowned transmitter. - **Root CLAUDE.md carried subtree facts** (Qodo #1, OpenIPC#3). The root file is cross-cutting only, and the added parenthetical also stated a favourable measured result with its counterpart a link away rather than in the same breath. Dropped — the not-ported list is updated, and the ARQ narrative stays in src/rtl8733b/CLAUDE.md where its counterparts already sit. - **ARQ prose restated the ack_timeout_us contract** (Qodo #2). Range, clamp, default and register are doc-commented at the DeviceConfig declaration; the subtree file now points there and keeps only what is specific to this backend (the vendor 0x21 that init_wmac still writes and why, the readback, the measured values). Re-verified on hardware after the change: responder 1349/1349 ACKed at retries_mean 0.00, retarget 1348/1349, disarmed control 0.00 with retries pinned at 12; teardown disarm still holds with power-down disabled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjcZRPjNtsy2mxfDkAWNpd
Pass 198 shipped both hybrid knobs off in every deploy/*.json, deliberately: arming is operator-owned. This arms the RESPONDER half only. Split from the original two-file change on purpose. The two halves are independently deployable by design (§3.0: receivers accept both frame shapes), and only one of them carries risk: - The craft half is measured HARMLESS. Arm C (responder on, ground not soliciting) was 829/901 = 92.01% against arm A's 820/900 = 91.11%, downlink 3 permille in both. A responder only answers when asked; it cannot originate traffic and cannot storm. It is pure upside the moment any ground solicits. - The ground half (policy.return.unicast) is what costs airtime, and it is held back. It has no posture for a craft that is PRESENT but cannot ACK (issue #261): the SA latch stays fresh off the craft's own video, the storm guard never fires, and the ground pays retry_limit+1 copies of every return indefinitely — measured 14 permille against a 4 permille baseline. That is the .181 8733BU today and every craft mid-rolling-upgrade, and it matters most for the usual ground use-case, which is latching whichever craft the operator picks rather than a fixed pair. Device-verified on this craft (8812EU / Jaguar3, merged f7f3a36): with a ground soliciting, tx_report_fails fell from 2796/2796 to 48/2790 (1.7%) and report delivery reached 99.89%. Numbers and geometry in docs/findings.md (2026-08-30 entries). .181 deliberately does NOT get this: SetAckResponder is unported on its 8733BU, so arming there is a logged degrade, not a responder. OpenIPC/devourer#406 ports it; revisit once vendored. Live-toggleable without a restart: POST /api/v1/air/ack_responder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015G1r7W361tynRf3aZqGPEC
Pass 198 shipped both hybrid knobs off in every deploy/*.json, deliberately: arming is operator-owned. This arms the RESPONDER half only. Split from the original two-file change on purpose. The two halves are independently deployable by design (§3.0: receivers accept both frame shapes), and only one of them carries risk: - The craft half is measured HARMLESS. Arm C (responder on, ground not soliciting) was 829/901 = 92.01% against arm A's 820/900 = 91.11%, downlink 3 permille in both. A responder only answers when asked; it cannot originate traffic and cannot storm. It is pure upside the moment any ground solicits. - The ground half (policy.return.unicast) is what costs airtime, and it is held back. It has no posture for a craft that is PRESENT but cannot ACK (issue #261): the SA latch stays fresh off the craft's own video, the storm guard never fires, and the ground pays retry_limit+1 copies of every return indefinitely — measured 14 permille against a 4 permille baseline. That is the .181 8733BU today and every craft mid-rolling-upgrade, and it matters most for the usual ground use-case, which is latching whichever craft the operator picks rather than a fixed pair. Device-verified on this craft (8812EU / Jaguar3, merged f7f3a36): with a ground soliciting, tx_report_fails fell from 2796/2796 to 48/2790 (1.7%) and report delivery reached 99.89%. Numbers and geometry in docs/findings.md (2026-08-30 entries). .181 deliberately does NOT get this: SetAckResponder is unported on its 8733BU, so arming there is a logged degrade, not a responder. OpenIPC/devourer#406 ports it; revisit once vendored. Live-toggleable without a restart: POST /api/v1/air/ack_responder. Claude-Session: https://claude.ai/code/session_015G1r7W361tynRf3aZqGPEC Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… parks, retune reverts (Pass 199) (#262) * deploy: arm the §3.0 responder half on craft .232 Pass 198 shipped both hybrid knobs off in every deploy/*.json, deliberately: arming is operator-owned. This arms the RESPONDER half only. Split from the original two-file change on purpose. The two halves are independently deployable by design (§3.0: receivers accept both frame shapes), and only one of them carries risk: - The craft half is measured HARMLESS. Arm C (responder on, ground not soliciting) was 829/901 = 92.01% against arm A's 820/900 = 91.11%, downlink 3 permille in both. A responder only answers when asked; it cannot originate traffic and cannot storm. It is pure upside the moment any ground solicits. - The ground half (policy.return.unicast) is what costs airtime, and it is held back. It has no posture for a craft that is PRESENT but cannot ACK (issue #261): the SA latch stays fresh off the craft's own video, the storm guard never fires, and the ground pays retry_limit+1 copies of every return indefinitely — measured 14 permille against a 4 permille baseline. That is the .181 8733BU today and every craft mid-rolling-upgrade, and it matters most for the usual ground use-case, which is latching whichever craft the operator picks rather than a fixed pair. Device-verified on this craft (8812EU / Jaguar3, merged f7f3a36): with a ground soliciting, tx_report_fails fell from 2796/2796 to 48/2790 (1.7%) and report delivery reached 99.89%. Numbers and geometry in docs/findings.md (2026-08-30 entries). .181 deliberately does NOT get this: SetAckResponder is unported on its 8733BU, so arming there is a logged degrade, not a responder. OpenIPC/devourer#406 ports it; revisit once vendored. Live-toggleable without a restart: POST /api/v1/air/ack_responder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015G1r7W361tynRf3aZqGPEC * spec(§11.6): a campaign's failure posture is scoped to its INTENT (Pass 199) Two operator-visible operations shared one campaign machine and one failure posture. §11.6's revert is right for a RETUNE (move the craft we already fly) and wrong for an ACQUIRE (leave this craft, take that one): reverting undoes the operator's explicit request, and lands them on a craft §15.5a has already aged out at 5 s of silence — so the "safety net" also removes the only visible target to retry from. Reported as "I cannot switch freely between my vehicles once I have claimed one". Ruled: - Campaigns carry CampaignIntent. kRetune reverts to prev_chan as before. kAcquire PARKS: stays on the target channel, adopts the target selection (a failed verify is not proof of absence, so §2 first-latch may still take it), drops the previous selection, reports select_failed. Both failure paths — kAbort (no CSA_ARMED) and kRevert (committed, no video) — take that posture. - An ACQUIRE does not move the craft. quickconnect claims on the channel the craft was FOUND on. It previously defaulted an absent target_chan to scout.emptiest(), silently making "switch to craft B" into "switch to craft B and move it elsewhere" — two operations, only the second of which can fail, and whose failure reverted the first. Pinning target == cand->chan is what makes PARK well defined: the craft is on that channel whether the campaign aborted before the commit or reverted after it, so both paths park somewhere the craft actually is. Spec-only; implementation follows in this branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015G1r7W361tynRf3aZqGPEC * feat(§11.6): acquire parks instead of reverting, and never moves the craft Spec landed in the previous commit; this is the code. - CampaignIntent in core/include/wblink/csa.h; rx_node carries the intent of the campaign in flight, set at BOTH campaign start sites (do_claim => kAcquire, /api/v1/csa => kRetune) so no path can inherit a stale value. - kRevert and kAbort branch on it. On kAcquire the ears are already on the craft's channel, so parking costs no retune: adopt the pending selection, drop the previous one, report select_failed, and log which half failed using the issuer's existing armed/landed/video evidence. - do_claim pins target == cand->chan and refuses a foreign target_chan. Moving a craft is /api/v1/csa, once it is yours. Also fixes a defect the device matrix surfaced: a parked acquire whose craft then turned up stayed at select_failed forever while video flowed at 6 permille, because promotion to "latched" was gated on selection_state == "configured" (the boot state). The UI reported a failure the operator could see was not happening. The new promotion is narrower than the adoption beside it — only the originator we parked ON promotes — so it can never adopt a different craft that happens to share the channel. Device matrix on .242 against three crafts (17 @5540, 18 @5825, 19 @5700), full table in docs/findings.md 2026-08-31: S1 foreign target_chan refused, selection untouched S2 acquire with no target_chan keeps the craft on its own channel S3 acquire back S4 forced failure PARKS on the target, does not revert S4b parked acquire promotes to latched when the craft appears S5 NEGATIVE CONTROL: a failed retune still reverts S6 4/4 WebUI clicks across three crafts, via the hub proxy S7 one scout then three clicks, no re-scout: 3/3, list stayed complete Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015G1r7W361tynRf3aZqGPEC * findings: the Pass 199 device matrix, and the scout's weak-craft gap Records the seven scenarios, the WebUI end-to-end verification, and the second defect S4 surfaced. Also names the still-OPEN scout issue, which is NOT Pass 199: the 1800 ms dwell extension is gated on a frame arriving inside a 300 ms base dwell, of which the 8812AU spends ~220-270 ms deaf after the retune, so a weak craft on a busy channel is intermittently dropped with rejects all-zero — never rejected, never heard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015G1r7W361tynRf3aZqGPEC --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
josephnef
left a comment
There was a problem hiding this comment.
The code core traced clean across the review: the AckResponder register map was verified against the vendor tree (net_type REG_CR_8733B + 2 = 0x0102, MACID 0x0610, BSSID 0x0618), the arm/teardown exception paths are coherent, the harness's event parsing matches rxdemo's actual emission, and nothing new touches the per-frame send path. The measurements hold up. Requesting changes for one runtime gap (a config-requested arm whose failure is silently discarded — the exact failure shape this PR was filed about), one harness false-verdict hole, and a cluster of doc claims that overrun what was measured. Ten findings total, nine confirmed by an adversarial verify pass; details inline.
One finding has no diff line to anchor to because the file isn't touched by the PR:
src/DeviceConfig.h — the authoritative field docs were not extended. tx.retry_limit still enumerates "the 11ac generations and Kestrel" (omitting the 8733B this PR measures live), and tx.ack_timeout_us's register footnote still lists REG_ACKTO 0x640 only for the 11ac generations and Kestrel, though the 8733B now programs the same 0x640. Root CLAUDE.md names these doc-comments the per-var reference ("do not look for a second copy here") — a caller auditing coverage from them concludes both knobs are inert on the 8733B and budgets the link around the 33 µs vendor value, re-deriving exactly the pre-PR behavior this PR fixed.
…scope the claims Runtime - A config-requested arm no longer discards its result. `DEVOURER_ACK_RESPONDER` with a group MAC (easy to reach: the canonical TX SA 57:42:.. has the I/G bit set) previously gave a green init and a session running as a plain monitor, so the operator debugged the RF link instead of the config. It now fails the bring-up. - The disarm moves into `Halmac8733bMac::stop()`, unconditional. `stop()` clears only REG_CR's low half, so net_type at 0x0102 survives it; siting the clear there means no path can reach `stop()` and leave an unowned SIFS-timed transmitter airing. This deletes the `_ack_armed` flag, its latch-ordering subtlety and the flag-guarded special case at the device layer. - `ack::is_unicast()` and `ack::verify()` join `enable()`/`disable()`, so the register map lives in one file instead of being re-hardcoded by a backend that can drift from it. Jaguar1/2/3 adopt the unicast guard — they returned `true` for a group MAC, the "silently dead responder" shape AdapterCaps.h records from the 8821AU episode. They do NOT adopt verify(): a family whose 0x0102 does not read back would start refusing healthy arms, and that wants a bench cell per die (J1 and J3 verified here, no J2 on this bench). Harness - `FRAMES` gets a floor. The rx.txhit readout quantizes to <=99 airings whatever the frame count, so `FRAMES=60` scored a healthy retry=0 arm at 0.17 against a 0.6 band — a false FAIL, the same shape the script already refuses for an arm that did not run. Docs - The ACK claim scopes to what ran: normal-ACK response to unicast singles. BlockAck response was never measured; `tests/ampdu_ba_check.sh` against this die came back indeterminate (armed and disarmed both 0% delivered at retries 0 — the control did not separate), which is the documented consequence of CCX accounting not surviving AGG_EN, not a verdict on the chip. - The quantization attribution was arithmetically wrong for the retry=12 arm: the <=99-airing bound is <=0.066/frame and covers the 0 and 3 arms, but 12.27 against 13 is 0.73/frame (~1095 airings), an order of magnitude past it. That residue is monitor loss — which is why the ratio is a floor. - `docs/scheduled-mac.md`: the 8733B's single-shot cell is marked unmeasured with the closed-loop figure parenthetical, since that column's neighbours are single-shot rates and 100%-at-retry-12 is not comparable to them; and the "J1/2/3-only" line one row below is corrected. - `src/DeviceConfig.h`, the authoritative per-knob reference, now names the 8733B for both `retry_limit` and `ack_timeout_us`/REG_ACKTO. - Changelog framing removed from the new comments and docs; the rationale halves stand on their own. Verified: builds + ctest green on default / 8733B-only / 8733B-off / ASan+UBSan. On hardware, the new Jaguar guard refuses a group MAC and still arms a unicast one on both 8812AU and 8812CU, and the pre-existing responder path is unregressed (8812AU responder, 8812CU soliciting: on 1.00, retarget 1.00, off 0.00 with retries pinned at 12). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VjcZRPjNtsy2mxfDkAWNpd
|
Addressed the unanchored |
josephnef
left a comment
There was a problem hiding this comment.
Re-reviewed at 466bf5e. All ten findings verified fixed against the actual delta, not the replies:
- Config-arm discard — a refused
DEVOURER_ACK_RESPONDERnow throws out ofbring_up_to_phy, failing the bring-up, and the unwind path is safe because the disarm is now unconditional instop(). - Disarm altitude —
ack::disablemoved intoHalmac8733bMac::stop(), unconditional;_ack_armed, its latch-ordering subtlety and theStop()special case are all deleted. Net −27 lines at the device layer. - Duplicated register recipe —
ack::is_unicast()/ack::verify()now live besideenable()/disable()inAckResponder.h; the 8733B consumes both, and the deliberate scoping ofverify()(MACID + net_type checked, BSSID written-not-checked, documented why) is the right call. The J1/J2/J3 adoption of the unicast guard — with verify() explicitly deferred pending a per-die bench cell — is exactly the split I'd have asked for, and the 8812AU/8812CU regression check covers it. - FRAMES floor — refuses below 1000 with the quantization rationale in the abort message; the header's error bound is restated at the floor (≤0.099/frame), which is correct.
- Doc scope — BlockAck claim retracted to normal-ACK singles in both docs, and the indeterminate
ampdu_ba_check.shrun is reported as indeterminate with the CCX/AGG_EN explanation rather than either suppressed or spun. The quantization attribution now splits the 0/3 arms (covered by the ≤0.066 bound) from the 12 arm's 0.73/frame residue, attributed to monitor loss and folded into the ratio-is-a-floor caveat. Arithmetic checks out. - scheduled-mac table — single-shot cell now
unmeasuredwith the closed-loop figure parenthetical, matching the 8821AU row's convention; Kestrel line corrected. - DeviceConfig.h — both field docs now name the 8733B.
- Conventions — issue tags and changelog framing gone from the new comments and docs (the one surviving
issue #inDeviceConfig.his a pre-existing line this PR never touched); the rationale halves stand alone as promised.
Independently verified on this end: default build + full ctest green (54/54) at 466bf5e.
One non-blocking observation, no action needed: the failed-verify path in SetAckResponder no longer attempts an immediate rollback — a mid-session runtime call that verify-fails with net_type latched would leave a half-armed engine until teardown. The comment documents the choice and the scenario needs a silent readback mismatch without a USB exception, so deferring to the unconditional stop() disarm is defensible; just noting the window exists.
Follow-up to #406 after its merge. ## Summary - Make ACK-responder arm/disarm fail closed: check register writes, close the gate before retargeting, verify RTL8733B rollback/disarm, and keep teardown progressing after a failed safety clear. - Propagate construction-time responder refusal on Jaguar1/2/3 instead of green-initializing a passive monitor. - Apply `tx.ack_timeout_us` to both RTL8733B response-window registers (`REG_ACKTO` and `REG_ACKTO_CCK`) and verify both readbacks. - Replace sampled/ambient-prone retry evidence with run-unique payload-counter witness ledgers; add an independent RTL8733B soliciting-TX ACK-closure test. - Add a CCX-independent three-radio BlockAck test. The witness proves A-MPDU structure and retry-copy closure, and directly decodes addressed `0x94` compressed BlockAck frames, including the bitmap. - Keep CCX wording scoped: no H2C/MACID registration transport is added here, and the absence of reports is not assigned to firmware. ## Hardware evidence Current rig: - TX: `0bda:b812`, RTL8822B/88x2BU, Jaguar2 - RTL8733B DUT: `0bda:f72b` - independent witness: `0bda:8812`, RTL8812AU/Jaguar1 BlockAck, ch36/MCS3/retry 12 (`tests/rtl8733b_blockack_onair.sh`): - active but unarmed RTL8733B: 1,605 unique aggregated payloads, 12.720 witnessed copies/payload, `paggr=0.665`, max burst 9, **0** matching BlockAcks; - armed RTL8733B: 128,702 unique aggregated payloads, 1.001 copies/payload, `paggr=1.0`, max burst 9, **14,402** addressed compressed BlockAcks; all 14,402 carried nonzero bitmaps; - the harness checks responder and witness liveness before and after TX and aborts unless the preparatory disarm and shutdown are verified. Other reruns: - RTL8733B soliciting normal-ACK TX, MCS3: responder on/off = 1.016/12.879 copies per observed payload, with 99.8%/100% witness coverage. - Retry dose response `0/3/12/0/12`: 1.00/4.00/12.34/1.00/12.34 copies per frame; verdict true. - J2 construction-time group responder: nonzero initialization failure with the unicast diagnostic; valid unicast still arms. After valid-process exit, a J1 probe measured 0/1,311 ACK successes and retries pinned at 12, so no teardown leak was inferred. - 11M CCK current J2-witness rerun separated on/off at 1.004/12.113 copies, but its one-shot on arm captured 55.7%, below the harness's fixed 60% coverage floor; this rerun is supportive, not counted as a new formal pass. The documented qualifying CCK cell used the prior J3 witness. ## Checks - Default build succeeds. - `ctest --test-dir build --output-on-failure`: 54/54 pass. - `bash -n` and `shellcheck` pass for all three RTL8733B on-air ARQ harnesses. - `git diff --check` passes. ## Scope This qualifies RTL8733B as a **BlockAck responder** only on the measured Jaguar2-to-RTL8733B combination. RTL8733B A-MPDU TX remains unported and unmeasured. CCX/`tx.report` remains unimplemented pending a separately tested H2C queue plus `MEDIA_STATUS_RPT` registration path.
Closes the four items in snokvist#2, filed by a consumer scoping a ground→craft return path on the 8733BU. Three are now measured working on hardware; the fourth is root-caused and refused out loud rather than faked.
What changed
1.
tx.ack_timeout_uswas silently ignored.init_wmac()hardcoded REG_ACKTO to0x21(33 µs) and the config field was read nowhere insrc/rtl8733b/. So the one library default every generation programs identically was, on this die alone, pinned to the bottom of the 33..128 µs spread that default exists to abolish. Now applied at bring-up with the same register and 1..255 clamp the Jaguars use, and read back before it is claimed:DEVOURER_ACK_TIMEOUT_US<unset>The vendor write stays in
init_wmac()— that MAC plane is shared verbatim withrtl8733bprobe, which wants the vendor recipe and carries noDeviceConfig.2.
SetAckResponderported and measured →ack_responder_ok = true. Thesrc/AckResponder.hrecipe applies unchanged, and that is not an assumption: the vendor tree's own port-0 descriptor names these three registers (net_typeREG_CR_8733B + 2= 0x0102, MACID 0x0610, BSSID 0x0618). MAC bring-up leaves net_type at No Link becauseinit_macwrites only REG_CR's low half — exactly why a monitor radio here never ACKed.The arm refuses a group MAC, is read back before being reported, and teardown disarms it —
_mac.stop()clears only REG_CR's low half, so a session ending withteardown_power_downoff would otherwise leave an unowned radio auto-ACKing. Verified: after such a session the peer reads ack_rate 0.00.3.
tx.retry_limitdrives real autonomous retransmission →tx_retry_limit_ok = true. This could not be measured the way the Jaguars were — that A/B reads the TX side's own CCX reports, and this die has none. Newtests/rtl8733b_retry_limit_onair.shjudges from the air: unicast to an unowned RA so no ACK ever returns, a passive monitor counting airings per submitted frame. It takes a dose-response rather than an on/off pair, because one pair could be ambient and a straight line through three levels cannot:4. CCX /
tx.reportis NOT ported — the cause is the firmware. Everything under this backend's control was verified correct on air: descriptor SPE_RPT and SW_DEFINE at the vendor's own bit positions, C2H decoded at the vendor'sdword2[28], bulk-IN as the vendor's delivery path (its USB interrupt handler is an empty stub), and the fw-offload C2H format this firmware speaks per its own dispatch. With an RX loop live and 3160 frames received, the chip returned zero C2H packets in any format — with and without net_type armed, peer both ACKing and silent.The plumbing is therefore not shipped: descriptors stay byte-identical and the knob warns at bring-up. A knob that looks granted while producing nothing is the exact failure the issue was filed about. The outstanding lead (halmac H2C queue +
MEDIA_STATUS_RPTto register the descriptor MACID — this backend has no H2C transport at all) is recorded so the next attempt starts where this one stopped.Counterparts
Stated because the numbers above are uniformly favourable: one physical unit and one peer generation (
f72bRTL8731BU vs RTL8812AU), no second responder die, no vendor-driver A/B, and the secondb733sample has not run these cells. The retry figure is witnessed by a monitor that can only lose airings, never invent them, so it is a floor — its ~0.07/frame shortfall is therx.txhitsampling quantization the harness documents.Verification
ctestgreen on default / 8733B-only / 8733B-off / ASan+UBSan (54/50/49/54 tests).tests/teardown_gen_sanity.sh, whose cell list predated the chip.docs/scheduled-mac.md's cross-generation ARQ matrix updated — the 8733B is a new combination there: closed-loop ARQ without a CCX report.🤖 Generated with Claude Code
https://claude.ai/code/session_01VjcZRPjNtsy2mxfDkAWNpd