Skip to content

RTL8814AU: end-to-end TX via opt-in kernel-driver init replay#29

Merged
josephnef merged 1 commit into
masterfrom
feat/8814au-tx-replay-from-kernel-trace
May 22, 2026
Merged

RTL8814AU: end-to-end TX via opt-in kernel-driver init replay#29
josephnef merged 1 commit into
masterfrom
feat/8814au-tx-replay-from-kernel-trace

Conversation

@josephnef
Copy link
Copy Markdown
Collaborator

Summary

Two changes that together let an 8814AU chip actually transmit on-air under devourer's monitor-mode injection path:

1. TX descriptor byte-identical to kernel-driver

Verified by usbmon capture of an aircrack-ng/morrownr 8814au kernel-driver session injecting a probe-request frame on the same chip and channel, diffed against devourer's descriptor. Seven fields differed:

Field Was Now Rationale
MACID 0 1 broadcast/default CAM
RATE_ID (non-VHT) 7 8 rate-table index
GID 0 63 (0x3F) no-group default
SW_DEFINE 0 1 DriverFixedRate flag
RETRY_LIMIT_ENABLE 0 1 mgmt-frame default
DATA_RETRY_LIMIT 0 12 upstream rtl8814au_xmit.c:267
SPE_RPT 1 0 kernel does not set
DISABLE_FB 1 0 kernel does not set

Devourer's first TX bulk-OUT now reads 64002885 01120800 0000003f 00010000 00003200 00000000 01000000 76a90000 — byte-identical to the kernel-driver's TX descriptor.

2. Opt-in DEVOURER_OOT_REPLAY=1

Runs a verbatim replay of the kernel-driver's post-fwdl vendor-write sequence (4464 writes between the last fwdl bulk chunk and first TX bulk OUT, captured via usbmon) at end of init.

Devourer's HAL init even after PRs #25/#26/#27 leaves the chip in a state that diverges from the kernel-driver in many small ways which combine to wedge the chip's USB controller — bulk OUT EP 0x02 NAKs every TX URB. With the replay applied, devourer's chip-state matches the kernel byte-for-byte (verified via live pyusb register dump) and TX URBs drain.

Authoritative usbmon capture, 5-second steady-state TX window:

140-byte bulk OUT submitted:    566
completed status=0:             566
completed status<0:               0

(Repeatable across multiple runs.)

With replay disabled (default), bulk OUT continues to time out at the 500ms USB_TIMEOUT — unchanged behaviour vs prior master.

Why opt-in and not default-on

The replay's BB writes significantly slow the chip's RX throughput (RX-packet rate drops ~10× in a 60-second window). The trade-off is acceptable for TX-only workloads (injection-only monitor mode); RX-only users keep current behaviour by leaving the env var unset.

Long-term path

Replace the verbatim replay by porting the equivalent upstream init functions individually (rtl8814a_hal_init.c + usb_halinit.c) so TX works without the RX trade-off and without 130 KB of opaque trace data shipped in the binary. The verbatim replay is the minimum that actually unblocks TX today and serves as a regression checkpoint while the functions get ported.

How to use

# 8814AU TX from monitor mode:
sudo DEVOURER_PID=0x8813 DEVOURER_CHANNEL=6 DEVOURER_OOT_REPLAY=1 \
  ./build/WiFiDriverTxDemo

Verification done

  • Build green on macOS + Arch Linux 6.18
  • Default (no env var): 8814 RX unchanged from master (WiFiDriverDemo on 0bda:8813)
  • DEVOURER_OOT_REPLAY=1: bulk OUT URBs complete status=0 from the chip (usbmon-verified across multiple runs)
  • TX descriptor byte-identical to kernel-driver TX (usbmon-verified)
  • Live pyusb register dump confirms chip state matches kernel-driver byte-for-byte at all 23 addresses previously diverging

Not verified

On-air sniffer verification was not possible in the current lab setup — the aircrack-ng 88XXau OOT driver needed for the 8812 sniffer fails to build against kernel 6.18. The combined evidence (usbmon-verified URB completions + byte-identical chip-state + byte-identical descriptor as a known-working kernel-driver TX session) supports the end-to-end TX claim, but air-side verification on a receiving adapter is a follow-up.

🤖 Generated with Claude Code

Two changes that together let an 8814AU chip actually transmit on-air
under devourer's monitor-mode injection path:

1. TX descriptor now byte-identical to the kernel-driver descriptor.
   Verified by usbmon capture of an aircrack-ng/morrownr 8814au
   kernel-driver session injecting a probe-request frame on the same
   chip and channel, diffed against devourer's descriptor. Seven fields
   differed; values previously came from speculative WIP comments that
   didn't hold up empirically:

     MACID            = 1   (was 0)         broadcast/default CAM
     RATE_ID (non-VHT)= 8   (was 7)         rate-table index
     GID              = 63  (was 0)         no-group default
     SW_DEFINE        = 1   (was 0)         DriverFixedRate flag
     RETRY_LIMIT_ENABLE=1, DATA_RETRY_LIMIT=12  (were both 0)
     SPE_RPT          = 0   (was 1)         kernel does not set
     DISABLE_FB       = 0   (was 1)         kernel does not set

   Devourer's first TX bulk-OUT now reads:
     64002885 01120800 0000003f 00010000 00003200 00000000 \
     01000000 76a90000
   Byte-identical to the kernel-driver's TX descriptor.

2. Opt-in `DEVOURER_OOT_REPLAY=1` runs a verbatim replay of the
   kernel-driver's post-fwdl vendor-write sequence at end of init.

   Devourer's HAL init (even after PR #25/#26/#27) leaves the chip in
   a state that diverges from the working kernel-driver in many small
   ways which combine to wedge the chip's USB controller — bulk OUT
   EP 0x02 NAKs every TX URB. With the replay applied, devourer's
   chip-state matches the kernel byte-for-byte (verified via live
   pyusb register dump) and TX URBs drain. Authoritative usbmon
   capture during a 5-second steady-state TX window:

     140-byte bulk OUT submitted:    566
     completed status=0:             566
     completed status<0:               0

   With replay disabled (default), bulk OUT continues to time out at
   the 500ms USB_TIMEOUT (unchanged behaviour vs prior master).

   The replay table (hal/Hal8814_PostFwdlReplay.h, 4464 entries
   covering the writes between the last fwdl bulk chunk and the first
   TX bulk OUT in a kernel-driver usbmon capture) is opt-in because
   it also includes BB writes that significantly slow the chip's RX
   throughput (RX-packet rate drops ~10x in a 60-second window). The
   tradeoff is acceptable for TX-only workloads (e.g. injection-only
   monitor mode); RX-only users keep current behaviour by leaving the
   env var unset.

   Long-term, the replay should be replaced by porting the
   equivalent upstream init functions individually
   (rtl8814a_hal_init.c + usb_halinit.c) so TX works without the RX
   trade-off and without 130KB of opaque trace data shipped in the
   binary. The verbatim replay is the minimum that actually unblocks
   TX today and serves as a regression checkpoint.

Verified on CF-938AC (0bda:8813, channel 6):
  - Default (no env var): 8814 RX unchanged from master.
  - DEVOURER_OOT_REPLAY=1: bulk OUT URBs complete status=0 from
    the chip (usbmon-verified, repeatable across runs).
  - TX descriptor matches kernel-driver byte-for-byte.

Note: on-air sniffer verification was not possible in the current lab
setup (the aircrack-ng 88XXau OOT driver needed for the 8812 sniffer
fails to build against kernel 6.18). The combined evidence
(usbmon-verified URB completions + byte-identical chip-state +
byte-identical descriptor as a known-working kernel-driver TX
session) supports the end-to-end TX claim, but air-side verification
on the receiving adapter is a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@josephnef josephnef merged commit 5de6d62 into master May 22, 2026
5 checks passed
@josephnef josephnef deleted the feat/8814au-tx-replay-from-kernel-trace branch May 22, 2026 15:32
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