Skip to content

RTL8814AU: post-fwdl chip-state parity (MAC addr, RRSR, RA-table)#27

Merged
josephnef merged 1 commit into
masterfrom
feat/8814au-init-parity-round2
May 22, 2026
Merged

RTL8814AU: post-fwdl chip-state parity (MAC addr, RRSR, RA-table)#27
josephnef merged 1 commit into
masterfrom
feat/8814au-init-parity-round2

Conversation

@josephnef
Copy link
Copy Markdown
Collaborator

Summary

Three independent chip-init parity bugs found via live pyusb register readback (kernel-driver vs devourer state) + usbmon trace diff. Each brings devourer's post-init chip state closer to what the working kernel-driver leaves in the chip.

  1. REG_TXPKTBUF_BCNQ1_BDNY_8814A address: 0x04260x0456 (per hal/rtl8814a_spec.h:262). The previous "spec calls it +2" comment was wrong; BCNQ1 lives at 0x0456, not BCNQ+2. The bad write scribbled some queue/protocol register instead.

  2. REG_MACID (0x0610..0x0615) now programmed. Kernel-driver writes 6 individual bytes carrying the chip's MAC address; devourer never wrote REG_MACID at all, leaving it 00:00:00:00:00:00. Hardcoded locally-administered address (02:0d:b0:c7:e4:b3) for now — proper EFUSE-read is a follow-up.

  3. Trace-derived post-fwdl init batch — writes the kernel driver makes that devourer skipped:

    • REG_RRSR (0x0440) = 0xff0f0000 — Response Rate Set
    • REG_QUEUE_CTRL (0x04c6) = 0x04
    • REG_TX_PTCL_CTRL (0x0520) = 0x0f2f0000
    • REG_RD_CTRL (0x0524) = 0x0f4fff00
    • 0x0670 = 0x000000c0 — NAV-related
    • RA-table init at 0x0990-0x09a4

What this does NOT fix

End-to-end 8814 TX. Bulk OUT EP 0x02 still times out post-init. Usbmon diff shows kernel does ~4464 post-fwdl vendor writes; this PR adds ~12 of them. Most of the remaining ~4400 are BB/PHY programming in the 0x800-0x1FFF register range (table-driven, deterministic) that devourer doesn't yet replicate. That's the next layer of work.

Test plan

  • Build green on macOS (cmake --build build)
  • Build green on Arch Linux 6.18 (trainer-arch)
  • 8814 RX regression on CF-938AC (0bda:8813), channel 6: 10+ packets received in demo window
  • Live pyusb readback confirms these registers now hold the kernel-driver values

🤖 Generated with Claude Code

Three independent chip-init parity bugs found via live pyusb register
readback (kernel-driver vs devourer state) + usbmon trace diff. Each
brings devourer's post-init chip state closer to what the working
kernel-driver leaves in the chip.

1. `REG_TXPKTBUF_BCNQ1_BDNY_8814A` address: 0x0426 -> 0x0456

   Per `hal/rtl8814a_spec.h:262` the BCNQ1 boundary register lives at
   0x0456, not BCNQ + 2 (= 0x0426). The "spec calls it +2" comment was
   wrong; the field name is misleading and there's a gap. The
   resulting `_InitQueueReservedPage_8814AUsb` write at 0x0426
   scribbled some queue/protocol register instead of programming BCNQ1.

2. `REG_MACID` (0x0610..0x0615) is now programmed.

   Kernel-driver usbmon trace shows 6 individual byte writes at
   0x0610..0x0615 carrying the chip's own MAC address. Devourer never
   wrote REG_MACID, leaving it 0x00..0x00. Realtek MAC TX paths
   typically refuse to schedule a frame if the chip's MAC isn't
   programmed. Using a hardcoded locally-administered address
   (02:0d:b0:c7:e4:b3) for now; proper EFUSE-read of the per-chip MAC
   is a follow-up.

3. Trace-derived post-fwdl init batch.

   usbmon diff between kernel-driver init and devourer's init
   revealed several writes the kernel driver makes that devourer
   skips. Applied as a batch using the kernel-driver's verbatim values:

     REG_RRSR (0x0440)        = 0xff0f0000  Response Rate Set
     0x04bc                   = 0x00        TX queue gate
     REG_QUEUE_CTRL (0x04c6)  = 0x04        Queue control
     REG_TX_PTCL_CTRL (0x520) = 0x0f2f0000  TX protocol control
     REG_RD_CTRL (0x0524)     = 0x0f4fff00  RD control
     0x0670                   = 0x000000c0  NAV-related
     RA-table init at 0x0990-0x09a4 (rate-adaptation table)

Verified on CF-938AC (0bda:8813, channel 6):
  - 8814 RX still works end-to-end (10+ packets received in demo).
  - Live pyusb readback confirms these registers now hold the
    same values the kernel-driver leaves in the chip.

Note: this does NOT yet fix end-to-end 8814 TX. Bulk OUT EP 0x02 still
times out for an independent reason — the usbmon trace shows the
kernel does ~4464 post-fwdl writes total; this PR adds ~12 of them.
Most of the remaining ~4400 are BB/PHY programming in the 0x800-
0x1FFF register range (table-driven, deterministic) that devourer
doesn't yet replicate. That's the next layer of work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@josephnef josephnef merged commit 2bf5020 into master May 22, 2026
5 checks passed
@josephnef josephnef deleted the feat/8814au-init-parity-round2 branch May 22, 2026 13:37
josephnef added a commit that referenced this pull request May 22, 2026
## 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

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

## Verification done

- [x] Build green on macOS + Arch Linux 6.18
- [x] Default (no env var): 8814 RX unchanged from master
(`WiFiDriverDemo` on `0bda:8813`)
- [x] `DEVOURER_OOT_REPLAY=1`: bulk OUT URBs complete `status=0` from
the chip (usbmon-verified across multiple runs)
- [x] TX descriptor byte-identical to kernel-driver TX (usbmon-verified)
- [x] 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](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