RTL8814AU: pcapng-fidelity USB diff + replay tooling#55
Merged
Conversation
Adds a binary-fidelity USB diff toolchain and three env-gated diagnostic gates in WiFiDriverTxDemo, used to compare the kernel `aircrack-ng/88XXau` TX path on RTL8814AU against devourer's. Existing `tools/usbmon_diff.py` (text format, bulk-OUT only) cannot represent EP0 control transfers (`bRequest=5` Realtek vendor reads/writes — the carrier for ~5000 init register pokes), full payloads, URB flags, or IN URBs / chip-side C2H interrupts. Without those axes, "the wire matches" diff conclusions are not load-bearing. Tooling: * `tools/usbmon_pcap_diff.py` — pcapng-aware diff. Parses Linux usbmon binary headers (LINKTYPE_USB_LINUX_MMAPPED, 220) from `tshark -i usbmonN`. Surfaces setup packet, full payload bytes + SHA-256, URB flags, status, timestamps, and IN URBs as first-class records. Modes: default position-aligned diff with lookahead resync; `--offload-probe` classifies a single capture as FW-offload vs per-write for the path-A LSSI register window; `--phase-split` finds init/TX boundary by sentinel write or largest inter-URB gap; `--aggregate` for histograms-only. Note: modern kernels write `flag_setup=0` (not `' '/0x20` as the old docs say) for valid setup packets — the parser decodes setup unconditionally on CTRL submits to handle that. * `tools/pcapng_to_urbscript.py` — emit a binary URB script from a pcapng. Two-stage pipeline complement to the C replay tool. * `tools/usbmon_replay.c` — `USBDEVFS_SUBMITURB` verbatim replay of a URB script against `/dev/bus/usb/BBB/DDD`. Preserves setup packets, payloads, EP, direction, inter-URB gaps (capped at 100ms). Bulk OUT defaults to `USBDEVFS_URB_ZERO_PACKET` (matches the existing RtlUsbAdapter TX-OUT pattern). `--dry-run` for offline validation; `--disconnect` kicks the kernel driver off the interface first. * `tests/test_usbmon_pcap_diff.py` — 8 cases against synthetic pcaps covering parser, aggregate, offload-probe, phase-split (sentinel + gap), diff (extra URB, payload divergence). * `tests/test_urbscript_roundtrip.py` — end-to-end pipeline test (synthetic pcap → urbscript → replay --dry-run); confirms ctrl / bulk / intr by-kind & by-dir counts survive the roundtrip. * `tools/usbmon_diff.py` — doc-banner update pointing at the new tool for cases the text-format script cannot handle. Diagnostic gates in `WiFiDriverTxDemo` (all OFF by default): * `DEVOURER_USB_SENTINEL=1` — write 0xDEAD to `REG_DUMMY (0x04FC)` before init and 0xBEEF at init-done, so `usbmon_pcap_diff.py --phase-split` can split devourer-side captures by sentinel rather than relying on the gap heuristic. * `DEVOURER_DRAIN_BULK_IN=1` — background thread that keeps bulk-IN URBs in flight on EP 0x81 (16 KB each). Kernel `88XXau` pre-arms eight 32 KB bulk-IN URBs at end-of-init; devourer in TX-only mode never does, leaving the chip with no place to deliver C2H / RX status. With this gate the chip starts pushing 176–390 byte messages on EP 0x81 within a few hundred ms. Empirically did NOT unblock on-air TX in a sniffer-attached run, so the EP 0x81 path is necessary infrastructure but not the sole gate; left in as a diagnostic for future investigations. * `DEVOURER_POLL_INTR_IN=1` — background poller on EP 0x85 (the Interrupt IN endpoint exposed by the 8814AU descriptor, bInterval 1). Confirmed empirically that the chip does NOT push messages on EP 0x85 during devourer init under cold-init conditions, so upstream's `CONFIG_USB_INTERRUPT_IN_PIPE` codepath is not load-bearing for the TX gate. Kept as a diagnostic. Library change in `RtlUsbAdapter::ReadEFuseByte`: Mirror the kernel's per-iteration `REG_EFUSE_TEST (0x0034) = 0x0000` (16-bit RD-then-WR) sequence that runs before every EFUSE byte read (312 times per cold init). Removes a known concrete wire-level divergence flagged by the new diff tool. Empirically harmless on its own — does NOT close the TX-on-air gate even with bulk-IN drainage enabled — but matches upstream wire shape exactly and unblocks further bisection. Validation: synthetic pcap tests pass. Real-capture validation on a plugged 8814AU (0bda:8813) plus AR9271 (0cf3:9271) sniffer + a kernel-side cold-init capture taken inside `devourer-testrig` VM (the host kernel is 6.18 — aircrack-ng/88XXau cannot build there). New quantitative ground truth from the first watertight kernel-vs-devourer URB diff on 8814AU is captured in kaeru episode `8814au-kernel-vs-devourer-wire-diff-2026-05-28-vm-session`. Strongest remaining single-register divergence (not yet addressed): register 0x1998 has a 248-write deficit (devourer 781 vs kernel 1029) — a BB calibration loop at `hal/phydm/rtl8814a/Hal8814_PhyTables.c:3607+`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`<windows.h>` (included on MSVC via the existing _MSC_VER guard at txdemo/main.cpp:14) defines `min` as a macro, so the unqualified `std::min(actual, 32)` in the EP 0x85 poller expands to `std::((actual)<(32)?(actual):(32))`, which cascades into "missing function header" / "redefinition" errors for the rest of the file. Use `std::min<int>(actual, 32)` — explicit template arg keeps the macro from matching, same pattern as RtlUsbAdapter.cpp:435. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a binary-fidelity USB diff toolchain to compare kernel
aircrack-ng/88XXauTX vs devourer's on RTL8814AU, plus three env-gated diagnostic switches inWiFiDriverTxDemoand one library change to close a concrete kernel-vs-devourer wire-level divergence.The existing
tools/usbmon_diff.py(#53, text-format) is bulk-OUT only and cannot represent EP0 control transfers — the carrier for ~5000 init register pokes per chip cold-init. Without those axes, "the wire matches" diff conclusions are not load-bearing. The new tool was the difference between "the gate is somewhere in path-A RF table application" (the previous narrowing, indirect) and a concrete URB-level diff against a real kernel-side capture (this PR, direct).Tooling
tools/usbmon_pcap_diff.py— pcapng-aware diff (Linux usbmon binary,LINKTYPE_USB_LINUX_MMAPPED = 220). Surfaces setup packet, full payload + SHA-256, URB flags, status, timestamps, and IN URBs as first-class records. Modes: default position-aligned diff w/ lookahead resync,--offload-probe,--phase-split,--aggregate. Real-capture caveat handled: modern kernels writeflag_setup=0(not' '/0x20as the old docs say) for valid setup packets — parser decodes setup unconditionally on CTRL submits.tools/pcapng_to_urbscript.py— pcapng → binary URB script emitter.tools/usbmon_replay.c→build/usbmon_replay—USBDEVFS_SUBMITURBverbatim replay.--dry-run,--disconnect, capped inter-URB gaps, bulk-OUT defaultURB_ZERO_PACKETmatchingRtlUsbAdapter::send_packet.tests/test_usbmon_pcap_diff.py,tests/test_urbscript_roundtrip.py.tools/usbmon_diff.py— banner update pointing forward.Diagnostic gates in
WiFiDriverTxDemo(all OFF by default)DEVOURER_USB_SENTINEL=1— 0xDEAD/0xBEEF writes toREG_DUMMY (0x04FC)bracketing init, so--phase-splitcan use sentinels instead of the gap heuristic.DEVOURER_DRAIN_BULK_IN=1— background bulk-IN drainer on EP 0x81 (kernel pre-arms 8×32KB; devourer in TX-only mode never had any IN URBs in flight). With this gate the chip starts pushing 176–390 B C2H/status messages back. Empirically necessary but not sufficient — does NOT alone unblock on-air TX.DEVOURER_POLL_INTR_IN=1— EP 0x85 interrupt-IN poller. Confirmed empirically the chip does NOT push on EP 0x85 during devourer init; upstream'sCONFIG_USB_INTERRUPT_IN_PIPEcodepath is not load-bearing for the TX gate. Kept as a diagnostic.Library change
RtlUsbAdapter::ReadEFuseByte: mirror the kernel's per-byte-readREG_EFUSE_TEST (0x0034) = 0x0000(16-bit RD-then-WR), 312 times per init. Removes a known concrete divergence flagged by the new diff. Empirically harmless on its own (does NOT close the TX-on-air gate) but matches upstream wire shape — useful as bisection ground truth.Real-capture findings (first watertight kernel-vs-devourer diff on 8814AU)
Kernel cold-init capture taken inside
devourer-testrigVM (host kernel 6.18 cannot buildaircrack-ng/88XXau); devourer-side on the host with chip then handed back.REG_EFUSE_TEST)Hypotheses tested & falsified this session: FW-offload of path-A RF table; EP 0x85 interrupt-IN polling; REG_CR missing MAC enables; EFUSE_TEST 0x0034 missing writes; bulk-IN drainage as sufficient cause.
Strongest remaining open candidate: register 0x1998 (248-write deficit) at
hal/phydm/rtl8814a/Hal8814_PhyTables.c:3607+— a BB calibration loop whose phydm conditional evaluates a different branch between kernel and devourer.Test plan
cmake --build build -jcleanpython3 tests/test_usbmon_pcap_diff.py— 8 cases passpython3 tests/test_urbscript_roundtrip.py— pcap → urbs → replay --dry-run passWiFiDriverTxDemowithDEVOURER_USB_SENTINEL=1runs init+TX with 2 visible sentinel writestshark -i usbmon4 -s 0parses cleanly; phase-split lands on sentinel boundary deterministicallydevourer-testrigVM, diff against host devourer-side cold-init produces concrete divergence numbers (table above)🤖 Generated with Claude Code