Skip to content

PCIe transport (vfio-pci) — RTL8821CE, RX + TX end-to-end#213

Merged
josephnef merged 11 commits into
masterfrom
pcie-transport
Jul 7, 2026
Merged

PCIe transport (vfio-pci) — RTL8821CE, RX + TX end-to-end#213
josephnef merged 11 commits into
masterfrom
pcie-transport

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Adds a PCIe transport to devourer, bringing the RTL8821CE — the PCIe sibling of the RTL8821CU — to full RX and TX parity with the USB adapters, plus a transport-seam refactor that makes USB and PCIe independent backends behind one interface.

What

Transport (src/PcieTransport.{h,cpp}, DEVOURER_PCIE=ON, Linux-only, default OFF — OFF builds unchanged):

  • vfio-pci: container/group/device, BAR2 MMIO for the register plane (the same 0x0000..0xFFFF space the USB vendor-control transfers address), bus-master + completion-timeout config, one DMA slab VFIO-mapped below 4 GiB (the descriptor dma fields are 32-bit).
  • TX/RX = the 88xx buffer-descriptor rings (rtw88 pci.{c,h} layout); firmware DLFW rides the BCN ring behind the same bcn-valid latch contract as USB; the TX ring is chosen from the descriptor QSEL.
  • RX wakeups: MSI-via-eventfd by default (idle-channel CPU ~12× lower than polling), automatic fallback to polling, DEVOURER_PCIE_NO_MSI for A/B.

Architecture (src/RtlTransport.h): USB and PCIe are independent transports implementing devourer::IRtlTransport (register plane + frame plane + the rtw88-style hci_setup() pre-power hook). All libusb code moved into devourer::UsbTransport; the bus-neutral RtlAdapter value type the HALs hold forwards to whichever transport it was built with (RtlUsbAdapter remains as a deprecated alias). HAL code carries no bus #ifdefs — the few genuinely bus-specific bring-up steps gate on is_usb().

Jaguar2 bring-up over PCIe: PCIe power-seq tables (incl. the PFM_WOWL poll-retry cold-boot fixer), PCIe PQ map + extra-queue pages, interface-PHY MDIO config, USB-only steps gated (RX-agg config, 0xFExx workarounds, DLFW 512-pad).

Consumer surface: WiFiDriver::CreateRtlDevicePcie(PcieTransport::Open(bdf)) — the caller owns vfio like it owns libusb; DEVOURER_PCIE_BDF on rxdemo/txdemo; staged pcieprobe (id/power/fw); tests/pcie_vfio_bind.sh (driver_override bind/restore); tests/pcie_rx_smoke.py (ambient-beacon validation).

Fixes found on the way (hardware-bisected)

  • Jaguar2 monitor RCR bits 11–13 dropped every ambient beacon — on USB too. On this MAC generation BIT(11) is BIT_TA_BCN (TA-gated beacon accept; drops all beacons with no TA programmed) and BIT(12) BIT_RPFM_CAM_ENABLE, not the Jaguar1 ADF/ACF/AMF accept trio the port assumed. Verified on RTL8821CE (PCIe), RTL8811CU and RTL8822BU (USB): 0 ambient beacons before, kernel-driver beacon rates after; ctrl/data delivery unchanged. Injected canonical-SA beacons passed either way, which is how regress.py never saw it.
  • REG_MAR (broadcast group-hash filter) was never written by the Jaguar2 port — USB survived on warm chip state; a PCIe cold start begins at 0 (all broadcast dropped). Now all-ones per halmac init_wmac_cfg_88xx.

Validation (all on hardware)

Check Result
PCIe RX smoke (RTL8821CE, radxa-x4) 1060/1593 ambient beacons ch 6/ch 36, 0 CRC fail
PCIe TX on-air (→ 8814AU monitor) 10 000 injected beacons, 0 submit failures, ~96% received
FastRetune hopping over PCIe (36/40/44) received on all three channels; ~170–200 µs/hop over MMIO
MSI vs polled RX identical delivery; idle CPU 24→2 ticks/8 s
USB regression tests/regress.py 4/4 cells, both pairings (8811CU and 8822BU as RX DUT)
BF self-sounding (tests/bf_selfsound_jaguar2.sh) beamformee 0 unarmed / 130–197 armed; sounder self-capture up to 13k reports
ctest 12/12 in both DEVOURER_PCIE configs

Extending to other PCIe parts: RTL8822BE/8822CE could reuse this transport with per-chip HAL gates; Jaguar1 PCIe parts (RTL8812AE/8821AE) cannot — pre-HalMAC silicon with rtlwifi-style own-bit descriptor DMA (the CMake option documents this).

🤖 Generated with Claude Code

josephnef and others added 8 commits July 7, 2026 15:33
DEVOURER_PCIE=ON (Linux-only, default OFF) adds src/PcieTransport: vfio-pci
container/group/device, BAR2 MMIO register backend (same 0x0000..0xFFFF space
as USB vendor-control), one <4GiB-IOVA DMA slab carrying the 88xx TX/RX
buffer-descriptor rings (rtw88 pci.{c,h} layout), polled RX. RtlUsbAdapter is
now bus-dual — an _mmio branch in rtw_read/rtw_write plus QSEL->ring dispatch
in the bulk TX/RX entry points — so the HAL call sites are untouched; the few
bus-specific bring-up steps gate on is_usb() (PCIe power-seq rows transcribed
from rtw88 rtl8821c.c incl. the PFM_WOWL poll retry, PCIe PQ map + exq pages,
PCIe MDIO gen1 cfg, no USB RX-agg, no DLFW 512-pad). FW DLFW rides the BCN
ring behind the same bcn-valid latch contract. Factory:
WiFiDriver::CreateRtlDevicePcie(PcieTransport::Open(bdf)); demos:
DEVOURER_PCIE_BDF on rxdemo + the staged pcieprobe (id/power/fw);
tests/pcie_vfio_bind.sh + tests/pcie_rx_smoke.py.

Two RX bugs hardware-bisected on the 8821CE along the way:
- RCR bit 11 is BIT_TA_BCN on this MAC generation (not Jaguar1's accept-data)
  and drops every management frame; bits 11-13 cleared on the PCIe path (USB
  kept byte-identical — injected-beacon RX there works with them set).
- REG_MAR (broadcast group-hash filter) was never written by the Jaguar2 port
  (USB survived on warm chip state; PCIe card-disable starts at 0 = all
  broadcast dropped). Now all-ones per halmac init_wmac_cfg_88xx, all paths.

Validated on radxa-x4 (8821CE @ 0000:01:00.0): chip-id/EFUSE-MAC over MMIO,
power-on, FW boot 0xC078 over DMA, 1117/1240 ambient beacons CRC-clean on
ch 6/36. USB regression: ctest 12/12 both configs; tests/regress.py 4/4 cells
in both pairings incl. the 8811CU as RX DUT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
txdemo gains the same vfio-transport branch as rxdemo: skip the USB
open/claim, create the device via CreateRtlDevicePcie, keep the (idle) libusb
event thread and the rest of the TX loop unchanged — send_packet already
reaches the PCIe MGMT buffer-descriptor ring through the adapter's QSEL
dispatch.

On-air validated radxa-x4 -> local rig: 10000 canonical-SA beacons injected
over the 8821CE PCIe TX ring on ch 36 (0 submit failures, ring completions
clean), 9600 received by an 8814AU monitor across the air (~96%%).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n USB too

The bisect that fixed beacon RX on the PCIe 8821CE generalizes: with ambient
APs confirmed audible at the rig (a Jaguar1 8812AU hears 79-236 beacons per
2.4 GHz channel), the USB RTL8811CU and RTL8822BU with the old RCR 0x7000382F
receive ZERO ambient beacons; with bits 11-13 cleared (0x7000002F) they
receive 246/152 per 10 s on ch36 (and the 8811CU's "silent 2.4 GHz" turns out
to be this bug: 51 beacons on ch6 after the fix). On this MAC generation
BIT(11) is BIT_TA_BCN (TA-gated beacon accept, drops every beacon with no TA
programmed) and BIT(12) BIT_RPFM_CAM_ENABLE — not the Jaguar1 ADF/ACF/AMF
accept trio the port assumed; ctrl/data delivery is unchanged without them.
Injected canonical-SA beacons passed even with the bits set, which is how
regress.py stayed green while every real AP's beacons were silently dropped.

Validated: ambient beacons on 8811CU + 8822BU USB (ch6 + ch36) and the 8821CE
PCIe smoke (1086/1243 beacons, 0 CRC fail); tests/regress.py 4/4 cells with
the 8811CU and the 8822BU as RX DUTs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
One MSI vector routed to an eventfd (VFIO_DEVICE_SET_IRQS); the RX loop waits
on it instead of the fixed-interval sleep, with HIMR0 = ROK|RDU unmasked and
HISR0 write-1-cleared per wake. A 100 ms poll timeout keeps a lost edge from
ever stalling RX (the ring-index re-check makes late wakes harmless), and MSI
setup failure falls back to pure polling automatically
(DEVOURER_PCIE_NO_MSI=1 forces it for A/B).

Measured on the RTL8821CE @ radxa-x4: identical frame delivery on a busy
channel; idle-channel CPU drops 24 -> 2 ticks per 8 s (~12x). Smoke re-run
green (1061/1675 beacons ch6/ch36, 0 CRC fail).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PCIE->JAGUAR2_8821C requirement read like an architectural coupling; the
message and option comment now say what it is: the RTL8821CE is simply the
only chip with a PCIe bring-up implemented + hardware-validated. Other HalMAC
PCIe parts (RTL8822BE/8822CE) could reuse the same 88xx buffer-descriptor
ring transport with per-chip HAL gates; Jaguar1 PCIe parts (RTL8812AE/8821AE)
cannot — pre-HalMAC silicon with rtlwifi-style own-bit descriptor DMA.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PCIe backend no longer lives inside the USB adapter — the containment is
inverted. devourer::IRtlTransport (src/RtlTransport.h) is the bus seam: a
register plane (read/write 8/16/32, write_bytes) and a frame plane (tx_async,
tx_sync, rx_loop, rx_raw) plus the rtw88-style hci_setup() pre-power hook and
usb_info()/tx_stats(). Two independent implementations:

- devourer::UsbTransport (src/UsbTransport.{h,cpp}) — all libusb code moved
  out of the adapter: vendor-control registers, sync/async bulk-OUT with the
  wedge recovery + TX counters, the async RX URB queue, endpoint discovery,
  and the UsbDeviceLock lifetime.
- devourer::PcieTransport — now implements the same interface over BAR2 MMIO
  (with the USB-page guard) and the 88xx BD rings; the QSEL->ring dispatch
  moved in here from the adapter.

RtlAdapter (renamed from RtlUsbAdapter; old name stays as a deprecated alias
in the RtlUsbAdapter.h shim) is the bus-neutral copyable value type the HALs
hold: it forwards both planes and keeps only the chip-level helpers built on
registers (EFUSE reads, phy_set_bb_reg, 8812 resets) and config knobs. The
Jaguar2 bring-up's #ifdef PCIe block collapses into an unconditional
_device.hci_setup() (no-op on USB). HAL code no longer needs libusb.h for
link-speed checks (devourer::kUsbSpeedHigh).

Revalidated on hardware: ctest 12/12; tests/regress.py 4/4 (8812AU TX /
8811CU RX); PCIe smoke PASS (1060/1593 beacons ch6/ch36, 0 CRC fail); PCIe TX
on-air 8000 frames, 0 submit failures, ~92% received cross-machine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…RtlAdapter.cpp

Hal8812PhyReg.h #defines identifiers (bEnable/bDisable) that winuser.h uses
as parameter names; RtlAdapter.cpp included it before UsbTransport.h
(-> libusb.h -> windows.h), poisoning the Windows headers (MSVC C2143 in
winuser.h, mingw "expected ','" at the #define). The pre-seam RtlUsbAdapter.h
guaranteed libusb-first ordering; restore it in the one TU that mixes both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rtw_efuse.h uses BOOLEAN/VOID, which every HAL TU previously received from
windows.h via the old RtlUsbAdapter.h's leading libusb.h include. With libusb
gone from the neutral facade header, include windows.h explicitly (guarded)
before the hal headers in RtlAdapter.h — the same project-wide ordering the
pre-seam header guaranteed implicitly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
josephnef and others added 3 commits July 7, 2026 18:18


The "Out of scope: the PCIe siblings" line predates the vfio-pci transport.
Add the 8821CE row to the supported-hardware table and scope the exclusion to
what is actually out of reach: Jaguar1 PCIe parts (different DMA architecture)
and Kestrel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Five example TUs (txdemo, duplex, precoder, streamtx, svctx) call libusb but
their _MSC_VER include branch never included libusb.h — they silently relied
on the pre-seam RtlUsbAdapter.h pulling it in for every consumer. The
bus-neutral RtlAdapter.h no longer does; include it explicitly (after
windows.h, per the ordering rule).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef josephnef merged commit 40e3a2a into master Jul 7, 2026
15 checks passed
@josephnef josephnef deleted the pcie-transport branch July 7, 2026 15:40
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