PCIe transport (vfio-pci) — RTL8821CE, RX + TX end-to-end#213
Merged
Conversation
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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):dmafields are 32-bit).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.DEVOURER_PCIE_NO_MSIfor A/B.Architecture (
src/RtlTransport.h): USB and PCIe are independent transports implementingdevourer::IRtlTransport(register plane + frame plane + the rtw88-stylehci_setup()pre-power hook). All libusb code moved intodevourer::UsbTransport; the bus-neutralRtlAdaptervalue type the HALs hold forwards to whichever transport it was built with (RtlUsbAdapterremains as a deprecated alias). HAL code carries no bus#ifdefs — the few genuinely bus-specific bring-up steps gate onis_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_BDFon rxdemo/txdemo; stagedpcieprobe(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)
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 howregress.pynever 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 halmacinit_wmac_cfg_88xx.Validation (all on hardware)
tests/regress.pytests/bf_selfsound_jaguar2.sh)ctestDEVOURER_PCIEconfigsExtending 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