Skip to content

RTL8814AU: pcapng-fidelity USB diff + replay tooling#55

Merged
josephnef merged 2 commits into
masterfrom
8814au-usb-wire-diff-tools
May 28, 2026
Merged

RTL8814AU: pcapng-fidelity USB diff + replay tooling#55
josephnef merged 2 commits into
masterfrom
8814au-usb-wire-diff-tools

Conversation

@josephnef
Copy link
Copy Markdown
Collaborator

Summary

Adds a binary-fidelity USB diff toolchain to compare kernel aircrack-ng/88XXau TX vs devourer's on RTL8814AU, plus three env-gated diagnostic switches in WiFiDriverTxDemo and 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 write flag_setup=0 (not ' '/0x20 as 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.cbuild/usbmon_replayUSBDEVFS_SUBMITURB verbatim replay. --dry-run, --disconnect, capped inter-URB gaps, bulk-OUT default URB_ZERO_PACKET matching RtlUsbAdapter::send_packet.
  • Unit + roundtrip tests (synthetic pcaps, no hardware) — 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 to REG_DUMMY (0x04FC) bracketing init, so --phase-split can 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's CONFIG_USB_INTERRUPT_IN_PIPE codepath is not load-bearing for the TX gate. Kept as a diagnostic.

Library change

RtlUsbAdapter::ReadEFuseByte: mirror the kernel's per-byte-read REG_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-testrig VM (host kernel 6.18 cannot build aircrack-ng/88XXau); devourer-side on the host with chip then handed back.

Axis Kernel Devourer Δ
Realtek ctrl writes 6146 5399 +747
Realtek ctrl reads 2337 1651 +686
Bulk-IN URBs (EP 0x81) 8 × 32 KB 0 +8
Reg 0x1998 (BB cal loop) 1029 781 +248 ← biggest single-reg
Reg 0x0034 (REG_EFUSE_TEST) 312 0 → 312 (after patch) closed
Path-A/B/C/D LSSI 370 / 296 / 296 / 296 354 / 282 / 282 / 282 +14 each

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 -j clean
  • python3 tests/test_usbmon_pcap_diff.py — 8 cases pass
  • python3 tests/test_urbscript_roundtrip.py — pcap → urbs → replay --dry-run pass
  • WiFiDriverTxDemo with DEVOURER_USB_SENTINEL=1 runs init+TX with 2 visible sentinel writes
  • Real pcapng captured via tshark -i usbmon4 -s 0 parses cleanly; phase-split lands on sentinel boundary deterministically
  • Kernel-side cold-init capture inside devourer-testrig VM, diff against host devourer-side cold-init produces concrete divergence numbers (table above)
  • Followup PR: investigate and address the 0x1998 248-write deficit

🤖 Generated with Claude Code

josephnef and others added 2 commits May 28, 2026 20:41
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>
@josephnef josephnef merged commit 90970ca into master May 28, 2026
5 checks passed
@josephnef josephnef deleted the 8814au-usb-wire-diff-tools branch May 28, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant