Skip to content

tools: add usbmon-text diff for bulk-OUT investigations#53

Merged
josephnef merged 1 commit into
masterfrom
tools/usbmon-diff
May 26, 2026
Merged

tools: add usbmon-text diff for bulk-OUT investigations#53
josephnef merged 1 commit into
masterfrom
tools/usbmon-diff

Conversation

@josephnef
Copy link
Copy Markdown
Collaborator

Summary

Test plan

  • Smoke-tested on real captures from a 2026-05-26 8814AU TX session — produced the metrics quoted in #36 comment-4544760338 and #50 comment-4544468999.
  • Handles kernel-6.18 text-format usbmon at /sys/kernel/debug/usb/usbmon/Nu (note: on this kernel the u suffix emits text, not binary).
  • No build impact — Python script under tools/.

Limitations

Text usbmon truncates URB data at 32 bytes and strips URB flags (URB_NO_INTERRUPT, URB_ZERO_PACKET). For flag-level diffs, use Wireshark/tshark on usbmonN binary output and the usb.copy_of_transfer_flags field. This tool stays useful for latency / status / cadence comparison without needing pcap-ng.

🤖 Generated with Claude Code

Standalone Python utility parsing the text-format usbmon output from
/sys/kernel/debug/usb/usbmon/Nu (kernel 6.18 emits text via this path).
Filters bulk-OUT URBs to a specific (bus, devnum, endpoint), pairs
submit ('S') with complete ('C') records by URB id, and reports
per-file metrics: count, mean/p50/p99 submit->complete latency, status
distribution, length distribution, inter-submit interval. Cross-file
diff highlights metrics where two captures disagree.

Built and used during the #36 (root cause isolated to REG_CR=0 wedge)
and #50 (8814AU on-air silence) investigations. Compares devourer's
libusb-routed URBs against qemu USB-host-passthrough URBs from a VM-
side kernel driver -- both pass through the host's xhci-hcd so a
host-side usbmon capture sees both.

Text format strips URB flags (URB_NO_INTERRUPT, URB_ZERO_PACKET, etc)
and truncates data at 32 bytes -- for flag-level diffs, use Wireshark
on usbmonN binary output and the usb.copy_of_transfer_flags field.
This tool stays useful for latency/status/cadence comparison without
needing pcap-ng.

Usage:
  sudo cat /sys/kernel/debug/usb/usbmon/4u > /tmp/A.txt &
  # ... run workload A ...
  sudo cat /sys/kernel/debug/usb/usbmon/4u > /tmp/B.txt &
  # ... run workload B ...
  python3 tools/usbmon_diff.py --file-a /tmp/A.txt --file-b /tmp/B.txt \
      --ep 0x02 --devnum-a 2 --devnum-b 2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@josephnef josephnef force-pushed the tools/usbmon-diff branch from 02e5efa to b30f1a8 Compare May 26, 2026 17:53
@josephnef josephnef merged commit 4d6e63b into master May 26, 2026
5 checks passed
@josephnef josephnef deleted the tools/usbmon-diff branch May 26, 2026 17:57
josephnef added a commit that referenced this pull request May 28, 2026
## 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.c` → `build/usbmon_replay` — `USBDEVFS_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

- [x] `cmake --build build -j` clean
- [x] `python3 tests/test_usbmon_pcap_diff.py` — 8 cases pass
- [x] `python3 tests/test_urbscript_roundtrip.py` — pcap → urbs → replay
--dry-run pass
- [x] `WiFiDriverTxDemo` with `DEVOURER_USB_SENTINEL=1` runs init+TX
with 2 visible sentinel writes
- [x] Real pcapng captured via `tshark -i usbmon4 -s 0` parses cleanly;
phase-split lands on sentinel boundary deterministically
- [x] 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](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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