Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,31 @@ construction from the `SYS_CFG2` chip-id (see **Architecture**):

Naming traps: **RTL8821AU is Jaguar1** (not Jaguar2, despite the Jaguar2
RTL8821C's similar name); RTL8822**B**U (Jaguar2) ≠ RTL8822**C**U (Jaguar3).
The 8822BE (PCIe) and the Kestrel 11ax families are out of scope. Full chip /
bench-throughput table: README **Supported hardware**.
The Kestrel 11ax families are out of scope. Full chip / bench-throughput
table: README **Supported hardware**.

**PCIe** (`DEVOURER_PCIE=ON`, Linux-only, default OFF): the RTL8821CE — the
PCIe sibling of the 8821CU — rides the same Jaguar2 HAL through a vfio-pci
transport (`src/PcieTransport.{h,cpp}`): registers are BAR2 MMIO (same
0x0000..0xFFFF space the USB vendor-control path addresses), TX/RX are the
88xx buffer-descriptor DMA rings (rtw88 pci.{c,h} layout), RX completion is
polled by default (MSI+eventfd wakeups when available). USB and PCIe are
independent transports behind `devourer::IRtlTransport`
(`src/RtlTransport.h`): `UsbTransport` (libusb) and `PcieTransport` each
implement the register + frame planes, and the bus-neutral `RtlAdapter` value
type the HALs hold forwards to whichever it was built with. The few genuinely
bus-specific bring-up steps gate on `is_usb()` (PCIe power-seq rows, PQ map,
no USB RX-agg, no DLFW 512-pad) or ride `hci_setup()` (pre-power TRX ring
programming, no-op on USB). Factory:
`WiFiDriver::CreateRtlDevicePcie(PcieTransport::Open(bdf, logger))` — the
caller owns vfio like it owns libusb. Demos: `DEVOURER_PCIE_BDF=0000:01:00.0`
on rxdemo and txdemo (TX = the data/MGMT BD rings behind the unchanged
`send_packet`); `pcieprobe <bdf> [id|power|fw]` validates the layers
bottom-up.
Bind/restore: `tests/pcie_vfio_bind.sh` (driver_override, not new_id — the
in-tree rtw88 auto-probe race). Validation: `sudo python3
tests/pcie_rx_smoke.py` on the radxa-x4 (`ssh radxa-x4`, 8821CE at
0000:01:00.0, IOMMU group 12) — ambient beacons CRC-clean on ch 6 + 36.

## Build

Expand All @@ -48,7 +71,9 @@ libusb-1.0 via `pkg-config` (Linux/macOS) or vcpkg (`VCPKG_ROOT`, Windows).

Per-chip options, all default ON: `DEVOURER_JAGUAR1`, `DEVOURER_8814` (requires
JAGUAR1), `DEVOURER_JAGUAR2_8822B`, `DEVOURER_JAGUAR2_8821C`,
`DEVOURER_JAGUAR3_8822C`, `DEVOURER_JAGUAR3_8822E`. Turning groups off drops
`DEVOURER_JAGUAR3_8822C`, `DEVOURER_JAGUAR3_8822E`. `DEVOURER_PCIE` (default
OFF, Linux-only, requires JAGUAR2_8821C) adds the vfio-pci transport +
`pcieprobe`; OFF builds are byte-identical to before it existed. Turning groups off drops
their firmware blobs + PHY tables (an 8812AU-only `rxdemo` is ~1.0 MB vs
~2.6 MB). Configure fails on no-chip-selected or 8814-without-JAGUAR1. Each
group exports a PUBLIC `DEVOURER_HAVE_*` define; sites referencing a dropped
Expand Down Expand Up @@ -265,8 +290,10 @@ Generation-agnostic core in `src/` (always compiled; depends on no HAL):
- `WiFiDriver` — the factory (`CreateRtlDevice`).
- `DeviceConfig.h` — construction-time configuration struct; every component
copies the sub-struct it consumes at construction.
- `RtlUsbAdapter` — libusb wrapper (vendor control + bulk transfers); a
copyable value type shared by every component.
- `RtlAdapter` — the bus-neutral register/frame accessor; a copyable value
type shared by every component, forwarding to the `IRtlTransport` it was
built with (`UsbTransport` = libusb vendor control + bulk; `PcieTransport` =
BAR2 MMIO + DMA rings). `RtlUsbAdapter` is a deprecated alias.
- `Radiotap.c` — radiotap iterator. TX buffers passed to `send_packet` **must**
begin with a radiotap header; rate/MCS/VHT/STBC/LDPC/SGI/bandwidth are read
from it.
Expand Down
52 changes: 50 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ option(DEVOURER_JAGUAR2_8822B "RTL8822BU (Jaguar2, rtl8822b, 2T2R USB)"
option(DEVOURER_JAGUAR2_8821C "RTL8811CU / 8821CU (Jaguar2, rtl8821c, 1T1R)" ON)
option(DEVOURER_JAGUAR3_8822C "RTL8812CU / 8822CU (Jaguar3, rtl8822c)" ON)
option(DEVOURER_JAGUAR3_8822E "RTL8812EU / 8822EU (Jaguar3, rtl8822e)" ON)
# PCIe transport (vfio-pci). Linux-only. The transport implements the HalMAC-
# generation (Jaguar2/3) 88xx buffer-descriptor DMA rings; the RTL8821CE is
# the only chip with a PCIe bring-up implemented + hardware-validated so far,
# hence the DEVOURER_JAGUAR2_8821C requirement below. Other HalMAC PCIe parts
# (RTL8822BE / RTL8822CE) could ride the same rings with per-chip HAL gates;
# Jaguar1 PCIe parts (RTL8812AE/8821AE) CANNOT — they are pre-HalMAC silicon
# with the older rtlwifi-style own-bit descriptor DMA, a different engine.
# OFF by default — libusb-only builds are bit-identical.
option(DEVOURER_PCIE "PCIe transport via vfio-pci (Linux; RTL8821CE)" OFF)

# Compile-time diagnostics floor (src/logger.h). Calls below the floor —
# including their argument expressions at DVR_TRACE/DVR_DEBUG sites — compile
Expand Down Expand Up @@ -67,6 +76,23 @@ if(NOT DEVOURER_JAGUAR1 AND NOT DEVOURER_JAGUAR2 AND NOT DEVOURER_JAGUAR3_8822C
"DEVOURER_JAGUAR2_8822B / DEVOURER_JAGUAR2_8821C / DEVOURER_JAGUAR3_8822C "
"/ DEVOURER_JAGUAR3_8822E.")
endif()
if(DEVOURER_PCIE)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(FATAL_ERROR "DEVOURER_PCIE=ON requires Linux (vfio-pci).")
endif()
if(NOT DEVOURER_JAGUAR2_8821C)
message(FATAL_ERROR
"DEVOURER_PCIE=ON requires DEVOURER_JAGUAR2_8821C=ON: the RTL8821CE "
"(Jaguar2 8821C) is currently the only chip with a PCIe bring-up "
"implemented and hardware-validated, so a PCIe build without it has "
"no usable chip. Not an architectural limit for the HalMAC families "
"— RTL8822BE (Jaguar2) / RTL8822CE (Jaguar3) could reuse the same "
"buffer-descriptor ring transport with per-chip HAL gates. Jaguar1 "
"PCIe parts (RTL8812AE/8821AE) are out of reach of this transport: "
"pre-HalMAC silicon with rtlwifi-style own-bit descriptor DMA, a "
"different engine entirely.")
endif()
endif()

# Find pkg-config and then use it to locate libusb.
find_package(PkgConfig REQUIRED)
Expand All @@ -83,8 +109,12 @@ add_library(devourer
src/Radiotap.c
src/RadiotapBuilder.cpp
src/RadiotapBuilder.h
src/RtlUsbAdapter.cpp
src/RtlUsbAdapter.h
src/RtlTransport.h
src/RtlAdapter.cpp
src/RtlAdapter.h
src/RtlUsbAdapter.h # compat alias shim (RtlUsbAdapter = RtlAdapter)
src/UsbTransport.cpp
src/UsbTransport.h
src/UsbDeviceLock.cpp
src/UsbDeviceLock.h
src/UsbOpen.cpp
Expand Down Expand Up @@ -215,6 +245,13 @@ if(DEVOURER_JAGUAR2_8821C)
target_compile_definitions(devourer PUBLIC DEVOURER_HAVE_JAGUAR2_8821C=1)
endif()

# --- PCIe transport (vfio-pci; see src/PcieTransport.h) ---
if(DEVOURER_PCIE)
target_sources(devourer PRIVATE
src/PcieTransport.cpp src/PcieTransport.h)
target_compile_definitions(devourer PUBLIC DEVOURER_HAVE_PCIE=1)
endif()

if(DEVOURER_JAGUAR3)
# Shared Jaguar3 port (compiled when either variant is on). Phy8822cTables.cpp
# and Halrf8822c.cpp also carry the make_jaguar3_*() dispatchers, so they are
Expand Down Expand Up @@ -254,6 +291,17 @@ if(DEVOURER_JAGUAR3_8822E)
endif()


# pcieprobe — staged PCIe bring-up driver for the RTL8821CE milestones
# (chip-id/MMIO sanity -> power-on + EFUSE -> firmware DLFW). rxdemo's
# DEVOURER_PCIE_BDF branch is the full-RX consumer; this probe validates the
# layers below it one at a time. Linux/vfio only.
if(DEVOURER_PCIE)
add_executable(pcieprobe
examples/pcieprobe/main.cpp
)
target_link_libraries(pcieprobe PUBLIC devourer)
endif()

add_executable(rxdemo
examples/rx/main.cpp
examples/common/env_config.cpp
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,13 @@ Bandwidth cells are devourer's measured on-air TX throughput (Mbps, HT MCS7,
| **RTL8822CU** | 2T2R + BT | — | — | — | not benchmarked (`0bda:c82c`) |
| **RTL8812EU** | 2T2R | 8 | 51 | 47 | LB-LINK BL-M8812EU2 (`0bda:a81a`); bare 5 GHz FPV module. 5/10 MHz capable |
| **RTL8822EU** | 2T2R + BT | — | — | — | not benchmarked. 5/10 MHz capable |
| **RTL8821CE** (PCIe) | 1T1R + BT | — | — | — | Radxa X4 onboard Wi-Fi (`10ec:c821`); not benchmarked |

`†` = works on-air but the reading varies run-to-run (bracketed = best clean
reading).

Out of scope: the PCIe siblings (e.g. 8822BE) and the 11ax "Kestrel"
generation — same branding, different bus or baseband.
Out of scope: the pre-HalMAC PCIe parts (RTL8812AE/8821AE) and the 11ax
"Kestrel" generation — same branding, different bus or baseband.

> Heads up — some Realtek sticks ship in "ZeroCD" mode and first enumerate as
> a USB flash drive holding a Windows installer (`0bda:1a2b` is the canonical
Expand Down
5 changes: 4 additions & 1 deletion examples/duplex/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
#include <vector>

#if defined(_MSC_VER)
/* libusb.h explicitly: the pre-seam RtlUsbAdapter.h used to pull it in
* for every consumer; the bus-neutral RtlAdapter.h no longer does. */
#include <libusb.h>
#include <io.h>
#include <fcntl.h>
#include <windows.h>
Expand All @@ -60,7 +63,7 @@

#include "RxPacket.h"
#include "RadiotapBuilder.h"
#include "RtlUsbAdapter.h"
#include "RtlAdapter.h"
#if defined(DEVOURER_HAVE_JAGUAR1)
#include "jaguar1/RtlJaguarDevice.h"
#endif
Expand Down
115 changes: 115 additions & 0 deletions examples/pcieprobe/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* pcieprobe — staged bring-up driver for the PCIe transport (RTL8821CE).
*
* Validates the PCIe milestones one layer at a time, bottom-up:
* id (M0) vfio open + BAR2 MMIO: chip-id @0xFC must read 0x09,
* SYS_CFG1/REG_CR sanity.
* power (M1) + TRX ring registers, pre-init, PCIe power-on sequence,
* chip version, EFUSE logical map (MAC @0xD0 must match the
* address the kernel driver reported).
* fw (M2) + init_system_cfg + firmware DLFW over the BCN TX ring
* (pass = REG_MCUFW_CTRL 0x80 == 0xC078).
* Full RX (M3) lives in rxdemo via DEVOURER_PCIE_BDF.
*
* Usage: sudo pcieprobe <bdf> [id|power|fw] (default stage: id)
* The device must be bound to vfio-pci first: tests/pcie_vfio_bind.sh <bdf>.
*
* Events (stdout JSONL): pcie.id / pcie.power / pcie.fw with ok:true|false —
* exit code 0 only if the requested stage passed. */

#include <cstdio>
#include <cstring>
#include <memory>
#include <string>
#include <vector>

#include "Event.h"
#include "PcieTransport.h"
#include "RtlAdapter.h"
#include "logger.h"

#include "jaguar2/ChipVariant.h"
#include "jaguar2/HalJaguar2.h"
#include "jaguar2/HalmacJaguar2Fw.h"
#include "jaguar2/HalmacJaguar2MacInit.h"

int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "usage: %s <bdf e.g. 0000:01:00.0> [id|power|fw]\n",
argv[0]);
return 2;
}
const std::string bdf = argv[1];
const std::string stage = argc > 2 ? argv[2] : "id";
const int want = stage == "fw" ? 2 : stage == "power" ? 1 : 0;

auto logger = std::make_shared<Logger>();

auto transport = devourer::PcieTransport::Open(bdf, logger);
if (!transport) {
devourer::Ev(logger->events(), "pcie.id").f("ok", false).f("why", "open");
return 1;
}

/* ---- stage id (M0): pure MMIO register plane, no power, no DMA ---- */
RtlAdapter adapter(transport, logger, {});
const uint8_t chip_id = adapter.rtw_read8(0x00FC);
const uint32_t sys_cfg1 = adapter.rtw_read32(0x00F0);
const uint8_t cr = adapter.rtw_read8(0x0100);
const bool id_ok = chip_id == 0x09;
logger->info("M0: chip-id=0x{:02x} (want 0x09) SYS_CFG1=0x{:08x} CR=0x{:02x}",
chip_id, sys_cfg1, cr);
devourer::Ev(logger->events(), "pcie.id")
.f("ok", id_ok)
.hexf("chip_id", chip_id, 2)
.hexf("sys_cfg1", sys_cfg1, 8)
.hexf("cr", cr, 2);
if (!id_ok || want < 1)
return id_ok ? 0 : 1;

/* ---- stage power (M1): rings -> pre-init -> PCIe power-on -> EFUSE ---- */
jaguar2::HalJaguar2 hal(adapter, logger, jaguar2::ChipVariant::C8821C, {});
jaguar2::HalmacJaguar2MacInit macinit(adapter, logger,
jaguar2::ChipVariant::C8821C);
bool power_ok = false;
std::vector<uint8_t> efuse(0x200, 0xFF);
try {
/* rtw88 order: hci_setup (ring registers) precedes mac_power_on. */
transport->setup_trx_rings();
macinit.pre_init_system_cfg();
hal.power_on();
hal.read_chip_version();
hal.read_efuse_logical_map(efuse.data(), efuse.size(), /*dump=*/false);
power_ok = true;
} catch (const std::exception &e) {
logger->error("M1: power-on failed: {}", e.what());
}
/* 8821CE efuse: MAC at logical 0xD0 (rtw8821ce_efuse; the USB variant keeps
* it elsewhere). Cross-check against the kernel-reported MAC. */
char mac[18];
snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x", efuse[0xD0],
efuse[0xD1], efuse[0xD2], efuse[0xD3], efuse[0xD4], efuse[0xD5]);
const uint16_t efuse_id =
static_cast<uint16_t>(efuse[0] | (efuse[1] << 8));
logger->info("M1: power_ok={} efuse id=0x{:04x} MAC(0xD0)={}", power_ok,
efuse_id, mac);
devourer::Ev(logger->events(), "pcie.power")
.f("ok", power_ok)
.hexf("efuse_id", efuse_id, 4)
.f("mac", mac);
if (!power_ok || want < 2)
return power_ok ? 0 : 1;

/* ---- stage fw (M2): system cfg + DLFW over the BCN ring ---- */
bool fw_ok = false;
try {
macinit.init_system_cfg(CHANNEL_WIDTH_20, hal.chip_version().cut);
jaguar2::HalmacJaguar2Fw fw(adapter, logger, jaguar2::ChipVariant::C8821C);
fw_ok = fw.download_default_firmware();
} catch (const std::exception &e) {
logger->error("M2: DLFW failed: {}", e.what());
}
const uint16_t mcufw = adapter.rtw_read16(0x0080);
logger->info("M2: fw_ok={} MCUFW_CTRL=0x{:04x} (want 0xC078)", fw_ok, mcufw);
devourer::Ev(logger->events(), "pcie.fw").f("ok", fw_ok).hexf("mcufw", mcufw, 4);
return fw_ok ? 0 : 1;
}
5 changes: 4 additions & 1 deletion examples/precoder/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
#include <vector>

#if defined(_MSC_VER)
/* libusb.h explicitly: the pre-seam RtlUsbAdapter.h used to pull it in
* for every consumer; the bus-neutral RtlAdapter.h no longer does. */
#include <libusb.h>
#include <windows.h>
#include <process.h>
typedef int pid_t;
Expand All @@ -53,7 +56,7 @@
#include <libusb-1.0/libusb.h>
#endif

#include "RtlUsbAdapter.h"
#include "RtlAdapter.h"
#include "UsbOpen.h"
#include "WiFiDriver.h"
#include "env_config.h"
Expand Down
Loading
Loading