From 20ac94de1accf62c2af51dbf64c26f5112dcffb5 Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:33:47 +0300 Subject: [PATCH 01/11] =?UTF-8?q?PCIe=20transport=20(vfio-pci)=20=E2=80=94?= =?UTF-8?q?=20RTL8821CE=20RX=20end-to-end?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CLAUDE.md | 27 +- CMakeLists.txt | 32 ++ examples/pcieprobe/main.cpp | 115 ++++++ examples/rx/main.cpp | 63 +++ src/DeviceConfig.h | 9 + src/PcieTransport.cpp | 562 +++++++++++++++++++++++++++ src/PcieTransport.h | 161 ++++++++ src/RtlUsbAdapter.cpp | 88 +++++ src/RtlUsbAdapter.h | 67 +++- src/WiFiDriver.cpp | 38 ++ src/WiFiDriver.h | 16 + src/jaguar2/HalJaguar2.cpp | 102 ++++- src/jaguar2/HalmacJaguar2Fw.cpp | 4 +- src/jaguar2/HalmacJaguar2MacInit.cpp | 71 +++- src/jaguar2/HalmacJaguar2MacInit.h | 7 +- src/jaguar2/RtlJaguar2Device.cpp | 18 +- tests/pcie_rx_smoke.py | 91 +++++ tests/pcie_vfio_bind.sh | 53 +++ 18 files changed, 1497 insertions(+), 27 deletions(-) create mode 100644 examples/pcieprobe/main.cpp create mode 100644 src/PcieTransport.cpp create mode 100644 src/PcieTransport.h create mode 100644 tests/pcie_rx_smoke.py create mode 100644 tests/pcie_vfio_bind.sh diff --git a/CLAUDE.md b/CLAUDE.md index ccfa375..b7e9e63 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,8 +34,27 @@ 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 (no interrupts). `RtlUsbAdapter` is bus-dual — an `_mmio` backend +dispatches register I/O and the bulk TX/RX entry points, so the ~165 HAL call +sites are untouched; 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, RCR +bits 11-13 cleared — on this MAC generation they are TA_BCN/RPFM_CAM, not the +Jaguar1 accept bits, and TA_BCN drops every beacon). 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; `pcieprobe [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 @@ -48,7 +67,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 diff --git a/CMakeLists.txt b/CMakeLists.txt index fbf3bbd..54e3e78 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,10 @@ 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 only supported PCIe chip is the +# RTL8821CE (the PCIe sibling of the RTL8821CU), so it requires the 8821C +# variant. 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 @@ -67,6 +71,16 @@ 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 supported " + "PCIe chip, RTL8821CE, is the Jaguar2 8821C).") + endif() +endif() # Find pkg-config and then use it to locate libusb. find_package(PkgConfig REQUIRED) @@ -215,6 +229,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 @@ -254,6 +275,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 diff --git a/examples/pcieprobe/main.cpp b/examples/pcieprobe/main.cpp new file mode 100644 index 0000000..359fe94 --- /dev/null +++ b/examples/pcieprobe/main.cpp @@ -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 [id|power|fw] (default stage: id) + * The device must be bound to vfio-pci first: tests/pcie_vfio_bind.sh . + * + * Events (stdout JSONL): pcie.id / pcie.power / pcie.fw with ok:true|false — + * exit code 0 only if the requested stage passed. */ + +#include +#include +#include +#include +#include + +#include "Event.h" +#include "PcieTransport.h" +#include "RtlUsbAdapter.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 [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(); + + 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 ---- */ + RtlUsbAdapter 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 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(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; +} diff --git a/examples/rx/main.cpp b/examples/rx/main.cpp index 011a251..4c0432c 100644 --- a/examples/rx/main.cpp +++ b/examples/rx/main.cpp @@ -23,6 +23,9 @@ #include "UsbOpen.h" #include "WiFiDriver.h" #include "env_config.h" +#if defined(DEVOURER_HAVE_PCIE) +#include "PcieTransport.h" +#endif #define USB_VENDOR_ID 0x0bda @@ -340,7 +343,13 @@ static void packetProcessor(const Packet &packet) { const int rssi[2] = {packet.RxAtrib.rssi[0], packet.RxAtrib.rssi[1]}; const int evm[2] = {packet.RxAtrib.evm[0], packet.RxAtrib.evm[1]}; const int snr[2] = {packet.RxAtrib.snr[0], packet.RxAtrib.snr[1]}; + /* Frame-control word + addr2 lead byte: lets the survey classify frame + * types (beacon 0x80, ACK 0xd4, ...) without carrying whole bodies. */ + const unsigned fc = packet.Data.size() >= 2 + ? (packet.Data[0] | (packet.Data[1] << 8)) + : 0; devourer::Ev(*g_ev, "rx.corrupt") + .hexf("fc", fc, 4) .f("len", packet.Data.size()) .f("crc", packet.RxAtrib.crc_err ? 1 : 0) .f("icv", packet.RxAtrib.icv_err ? 1 : 0) @@ -526,6 +535,60 @@ int main() { * `timeout` SIGTERM killed us mid-RX, leaving the chip's USB core hung. */ install_devourer_signal_handlers(); +#if defined(DEVOURER_HAVE_PCIE) + /* DEVOURER_PCIE_BDF=0000:01:00.0 — drive a PCIe adapter (RTL8821CE) through + * the vfio transport instead of libusb. The device must be bound to vfio-pci + * (tests/pcie_vfio_bind.sh). Minimal RX flow: transport -> factory -> Init; + * the USB-side pollers/sweep extras are not wired on this branch (yet). */ + if (const char *bdf = std::getenv("DEVOURER_PCIE_BDF")) { + devourer::PcieTransport::Config pcfg; + if (const char *pp = std::getenv("DEVOURER_PCIE_RX_POLL_US")) + pcfg.rx_poll_us = std::atoi(pp); + auto transport = devourer::PcieTransport::Open(bdf, logger, pcfg); + if (!transport) + return 1; + devourer::Ev(*g_ev, "init.timing") + .f("stage", "demo.open_device") + .f("ms", ms_since_start()); + WiFiDriver wifi_driver(logger); + auto dev = wifi_driver.CreateRtlDevicePcie(std::move(transport), + devourer_config_from_env()); + if (!dev) { + logger->error("No driver for this PCIe chip in this build — exiting"); + return 1; + } + devourer::Ev(*g_ev, "init.timing") + .f("stage", "demo.create_device") + .f("ms", ms_since_start()); + int pch = 36; + if (const char *ch_env = std::getenv("DEVOURER_CHANNEL")) + pch = std::atoi(ch_env); + ChannelWidth_t pwidth = CHANNEL_WIDTH_20; + uint8_t poff = 0; + if (const char *bw_env = std::getenv("DEVOURER_BW")) { + int bw = std::atoi(bw_env); + if (bw == 40 || bw == 80) { + pwidth = (bw == 40) ? CHANNEL_WIDTH_40 : CHANNEL_WIDTH_80; + poff = 1; + if (const char *off_env = std::getenv("DEVOURER_CHOFFSET")) + poff = static_cast(std::atoi(off_env)); + } + } + logger->info("PCIe RX: {} ch={} bw={}", bdf, pch, (int)pwidth); + try { + dev->Init(packetProcessor, + SelectedChannel{.Channel = static_cast(pch), + .ChannelOffset = poff, + .ChannelWidth = pwidth}); + } catch (const std::exception &e) { + logger->error("PCIe bring-up failed: {}", e.what()); + return 1; + } + dev->Stop(); + return 0; + } +#endif /* DEVOURER_HAVE_PCIE */ + rc = libusb_init(&ctx); if (rc < 0) { return rc; diff --git a/src/DeviceConfig.h b/src/DeviceConfig.h index 47f22e8..0e43fc9 100644 --- a/src/DeviceConfig.h +++ b/src/DeviceConfig.h @@ -203,6 +203,15 @@ struct DeviceConfig { * devourer-usb-*.lock files. Empty = "/tmp". */ std::string lock_dir; } usb; + + /* ---- PCIe (DEVOURER_PCIE builds; see src/PcieTransport.h) ------------ */ + struct Pcie { + /* env: DEVOURER_PCIE_RX_POLL_US — RX ring hardware-index poll interval in + * microseconds (unset = the transport default, 200). Demo-side only: the + * demos fold it into PcieTransport::Config before Open(); device selection + * (DEVOURER_PCIE_BDF) is likewise demo-local, like USB device selection. */ + std::optional rx_poll_us; + } pcie; }; } // namespace devourer diff --git a/src/PcieTransport.cpp b/src/PcieTransport.cpp new file mode 100644 index 0000000..c58c82b --- /dev/null +++ b/src/PcieTransport.cpp @@ -0,0 +1,562 @@ +#include "PcieTransport.h" + +/* Linux-only (vfio). Compiled only when DEVOURER_PCIE=ON — the CMake option is + * gated on Linux. */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace devourer { + +namespace { + +/* ---- 88xx PCIe TRX ring register map (rtw88 pci.h, v6.12) ---- */ +constexpr uint16_t RTK_PCI_CTRL = 0x300; +constexpr uint32_t BIT_RST_TRXDMA_INTF = 1u << 20; +constexpr uint32_t BIT_RX_TAG_EN = 1u << 15; + +constexpr uint16_t RTK_PCI_TXBD_DESA_BCNQ = 0x308; +constexpr uint16_t RTK_PCI_TXBD_DESA_H2CQ = 0x1320; +constexpr uint16_t RTK_PCI_TXBD_DESA_MGMTQ = 0x310; +constexpr uint16_t RTK_PCI_TXBD_DESA_BKQ = 0x330; +constexpr uint16_t RTK_PCI_TXBD_DESA_BEQ = 0x328; +constexpr uint16_t RTK_PCI_TXBD_DESA_VIQ = 0x320; +constexpr uint16_t RTK_PCI_TXBD_DESA_VOQ = 0x318; +constexpr uint16_t RTK_PCI_TXBD_DESA_HI0Q = 0x340; +constexpr uint16_t RTK_PCI_RXBD_DESA_MPDUQ = 0x338; + +constexpr uint16_t RTK_PCI_TXBD_NUM_H2CQ = 0x1328; +constexpr uint16_t RTK_PCI_TXBD_NUM_MGMTQ = 0x380; +constexpr uint16_t RTK_PCI_TXBD_NUM_BKQ = 0x38A; +constexpr uint16_t RTK_PCI_TXBD_NUM_BEQ = 0x388; +constexpr uint16_t RTK_PCI_TXBD_NUM_VIQ = 0x386; +constexpr uint16_t RTK_PCI_TXBD_NUM_VOQ = 0x384; +constexpr uint16_t RTK_PCI_TXBD_NUM_HI0Q = 0x38C; +constexpr uint16_t RTK_PCI_RXBD_NUM_MPDUQ = 0x382; + +constexpr uint16_t RTK_PCI_TXBD_IDX_H2CQ = 0x132C; +constexpr uint16_t RTK_PCI_TXBD_IDX_MGMTQ = 0x3B0; +constexpr uint16_t RTK_PCI_TXBD_IDX_BKQ = 0x3AC; +constexpr uint16_t RTK_PCI_TXBD_IDX_BEQ = 0x3A8; +constexpr uint16_t RTK_PCI_TXBD_IDX_VIQ = 0x3A4; +constexpr uint16_t RTK_PCI_TXBD_IDX_VOQ = 0x3A0; +constexpr uint16_t RTK_PCI_TXBD_IDX_HI0Q = 0x3B8; +constexpr uint16_t RTK_PCI_RXBD_IDX_MPDUQ = 0x3B4; + +constexpr uint16_t RTK_PCI_TXBD_RWPTR_CLR = 0x39C; +constexpr uint16_t RTK_PCI_TXBD_H2CQ_CSR = 0x1330; +constexpr uint32_t BIT_CLR_H2CQ_HOST_IDX = 1u << 16; +constexpr uint32_t BIT_CLR_H2CQ_HW_IDX = 1u << 8; + +constexpr uint16_t RTK_PCI_TXBD_BCN_WORK = 0x383; +constexpr uint8_t BIT_PCI_BCNQ_FLAG = 1u << 4; + +constexpr uint32_t TRX_BD_IDX_MASK = 0xFFF; + +/* 8821C (and all wcpu-11ac rtw88 chips we care about): 48-byte tx pkt desc, + * 16-byte TX BD slot (a PAIR of 8-byte entries), 8-byte RX BD. */ +constexpr uint32_t TX_PKT_DESC_SZ = 48; +constexpr uint32_t TX_BD_SLOT_SZ = 16; +constexpr uint32_t RX_BD_SZ = 8; + +constexpr uint32_t RING_LEN_DEFAULT = 128; /* RTK_DEFAULT_TX_DESC_NUM */ +constexpr uint32_t RING_LEN_BE = 256; /* RTK_BEQ_TX_DESC_NUM */ +constexpr uint32_t RING_LEN_BCN = 1; +constexpr uint32_t TX_BOUNCE_SZ = 32 * 1024; + +constexpr size_t PAGE_SZ = 4096; +inline size_t page_align(size_t v) { return (v + PAGE_SZ - 1) & ~(PAGE_SZ - 1); } + +/* 8-byte buffer-descriptor entry accessors (volatile LE stores). */ +inline void bd_write(volatile uint8_t *e, uint16_t buf_size, uint16_t psb_len, + uint32_t dma) { + e[0] = static_cast(buf_size); + e[1] = static_cast(buf_size >> 8); + e[2] = static_cast(psb_len); + e[3] = static_cast(psb_len >> 8); + e[4] = static_cast(dma); + e[5] = static_cast(dma >> 8); + e[6] = static_cast(dma >> 16); + e[7] = static_cast(dma >> 24); +} + +void sleep_us(unsigned us) { + std::this_thread::sleep_for(std::chrono::microseconds(us)); +} + +} /* namespace */ + +std::shared_ptr PcieTransport::Open(const std::string &bdf, + Logger_t logger) { + return Open(bdf, std::move(logger), Config{}); +} + +std::shared_ptr PcieTransport::Open(const std::string &bdf, + Logger_t logger, + const Config &cfg) { + std::shared_ptr t(new PcieTransport(logger, cfg)); + t->_bdf = bdf; + if (!t->open_vfio(bdf)) + return nullptr; + if (!t->map_bar2()) + return nullptr; + if (!t->setup_config_space()) + return nullptr; + if (!t->init_dma()) + return nullptr; + logger->info("PcieTransport: {} ready (BAR2 {} KiB, DMA slab {} KiB @ IOVA " + "0x{:x})", + bdf, t->_mmio_len / 1024, t->_slab_len / 1024, + t->_cfg.iova_base); + return t; +} + +PcieTransport::~PcieTransport() { + if (_mmio) + munmap(const_cast(_mmio), _mmio_len); + if (_slab && _container >= 0) { + struct vfio_iommu_type1_dma_unmap um{}; + um.argsz = sizeof(um); + um.iova = _cfg.iova_base; + um.size = _slab_len; + ioctl(_container, VFIO_IOMMU_UNMAP_DMA, &um); + } + if (_slab) + munmap(_slab, _slab_len); + if (_device >= 0) + close(_device); + if (_group >= 0) + close(_group); + if (_container >= 0) + close(_container); +} + +bool PcieTransport::open_vfio(const std::string &bdf) { + /* IOMMU group number from sysfs. */ + std::string link = "/sys/bus/pci/devices/" + bdf + "/iommu_group"; + char buf[256]; + ssize_t n = readlink(link.c_str(), buf, sizeof(buf) - 1); + if (n <= 0) { + _logger->error("PcieTransport: readlink({}) failed: {}", link, + strerror(errno)); + return false; + } + buf[n] = 0; + const char *slash = strrchr(buf, '/'); + std::string group_num = slash ? slash + 1 : buf; + + _container = open("/dev/vfio/vfio", O_RDWR); + if (_container < 0) { + _logger->error("PcieTransport: open /dev/vfio/vfio failed: {} (modprobe " + "vfio-pci?)", + strerror(errno)); + return false; + } + if (ioctl(_container, VFIO_GET_API_VERSION) != VFIO_API_VERSION) { + _logger->error("PcieTransport: VFIO API version mismatch"); + return false; + } + int iommu_type = 0; + if (ioctl(_container, VFIO_CHECK_EXTENSION, VFIO_TYPE1v2_IOMMU) == 1) + iommu_type = VFIO_TYPE1v2_IOMMU; + else if (ioctl(_container, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU) == 1) + iommu_type = VFIO_TYPE1_IOMMU; + else { + _logger->error("PcieTransport: no Type1 IOMMU support"); + return false; + } + + std::string group_path = "/dev/vfio/" + group_num; + _group = open(group_path.c_str(), O_RDWR); + if (_group < 0) { + _logger->error("PcieTransport: open {} failed: {} (device bound to " + "vfio-pci? permissions?)", + group_path, strerror(errno)); + return false; + } + struct vfio_group_status st{}; + st.argsz = sizeof(st); + if (ioctl(_group, VFIO_GROUP_GET_STATUS, &st) < 0 || + !(st.flags & VFIO_GROUP_FLAGS_VIABLE)) { + _logger->error("PcieTransport: IOMMU group {} not viable (all devices in " + "the group must be bound to vfio-pci)", + group_num); + return false; + } + if (ioctl(_group, VFIO_GROUP_SET_CONTAINER, &_container) < 0) { + _logger->error("PcieTransport: GROUP_SET_CONTAINER failed: {}", + strerror(errno)); + return false; + } + if (ioctl(_container, VFIO_SET_IOMMU, iommu_type) < 0) { + _logger->error("PcieTransport: SET_IOMMU failed: {}", strerror(errno)); + return false; + } + _device = ioctl(_group, VFIO_GROUP_GET_DEVICE_FD, bdf.c_str()); + if (_device < 0) { + _logger->error("PcieTransport: GET_DEVICE_FD({}) failed: {}", bdf, + strerror(errno)); + return false; + } + _logger->info("PcieTransport: vfio group {} opened for {}", group_num, bdf); + return true; +} + +bool PcieTransport::map_bar2() { + struct vfio_region_info reg{}; + reg.argsz = sizeof(reg); + reg.index = VFIO_PCI_BAR2_REGION_INDEX; + if (ioctl(_device, VFIO_DEVICE_GET_REGION_INFO, ®) < 0) { + _logger->error("PcieTransport: BAR2 region info failed: {}", + strerror(errno)); + return false; + } + if (!(reg.flags & VFIO_REGION_INFO_FLAG_MMAP) || reg.size == 0) { + _logger->error("PcieTransport: BAR2 not mmap-able (size={} flags={:#x})", + (unsigned long long)reg.size, reg.flags); + return false; + } + void *p = mmap(nullptr, reg.size, PROT_READ | PROT_WRITE, MAP_SHARED, + _device, reg.offset); + if (p == MAP_FAILED) { + _logger->error("PcieTransport: BAR2 mmap failed: {}", strerror(errno)); + return false; + } + _mmio = static_cast(p); + _mmio_len = reg.size; + return true; +} + +bool PcieTransport::cfg_read(uint32_t off, void *buf, size_t len) { + return pread(_device, buf, len, _cfg_region_off + off) == + static_cast(len); +} + +bool PcieTransport::cfg_write(uint32_t off, const void *buf, size_t len) { + return pwrite(_device, buf, len, _cfg_region_off + off) == + static_cast(len); +} + +bool PcieTransport::setup_config_space() { + struct vfio_region_info reg{}; + reg.argsz = sizeof(reg); + reg.index = VFIO_PCI_CONFIG_REGION_INDEX; + if (ioctl(_device, VFIO_DEVICE_GET_REGION_INFO, ®) < 0) { + _logger->error("PcieTransport: config region info failed: {}", + strerror(errno)); + return false; + } + _cfg_region_off = reg.offset; + _cfg_region_len = reg.size; + + uint16_t vid = 0, did = 0; + cfg_read(0x00, &vid, 2); + cfg_read(0x02, &did, 2); + if (vid == 0xFFFF) { + _logger->error("PcieTransport: config space reads 0xFFFF — link down?"); + return false; + } + _logger->info("PcieTransport: PCI device {:04x}:{:04x}", vid, did); + + /* Memory + bus-master enable. Without bus-master every register read works + * but no DMA moves — the classic silent-failure trap. */ + uint16_t cmd = 0; + cfg_read(0x04, &cmd, 2); + cmd |= 0x2 /* MEMORY */ | 0x4 /* MASTER */; + cfg_write(0x04, &cmd, 2); + + /* Find the PCI Express capability (id 0x10) for LNKCTL / DEVCTL2. */ + uint8_t cap_ptr = 0; + cfg_read(0x34, &cap_ptr, 1); + uint32_t pcie_cap = 0; + for (int guard = 0; cap_ptr && guard < 48; guard++) { + uint8_t id = 0, next = 0; + cfg_read(cap_ptr, &id, 1); + cfg_read(cap_ptr + 1, &next, 1); + if (id == 0x10) { + pcie_cap = cap_ptr; + break; + } + cap_ptr = next; + } + if (pcie_cap) { + /* Clear ASPM (LNKCTL[1:0]) during bring-up — L1 entry on a half-configured + * link is a known hang source. */ + uint16_t lnkctl = 0; + cfg_read(pcie_cap + 0x10, &lnkctl, 2); + if (lnkctl & 0x3) { + lnkctl &= ~0x3; + cfg_write(pcie_cap + 0x10, &lnkctl, 2); + _logger->info("PcieTransport: ASPM disabled for bring-up"); + } + /* Disable completion timeout (DEVCTL2 bit4) — rtw88 does this specifically + * for the 8821C (rtw_pci_phy_cfg). */ + uint16_t devctl2 = 0; + cfg_read(pcie_cap + 0x28, &devctl2, 2); + devctl2 |= 1u << 4; + cfg_write(pcie_cap + 0x28, &devctl2, 2); + } else { + _logger->warn("PcieTransport: PCIe capability not found — skipping " + "ASPM/completion-timeout config"); + } + return true; +} + +bool PcieTransport::init_dma() { + /* Slab layout (page-aligned sections): + * [0] 8 × TX BD rings (4 KiB each — BE at 256×16 = 4 KiB is the max) + * [1] 8 × TX bounce (32 KiB each; sync TX = one in flight per queue) + * [2] RX BD ring (rx_ring_len × 8) + * [3] RX buffers (rx_ring_len × rx_buf_size) */ + const uint32_t rxn = _cfg.rx_ring_len; + size_t off_txbd = 0; + size_t off_bounce = off_txbd + Q_MAX * PAGE_SZ; + size_t off_rxbd = off_bounce + Q_MAX * TX_BOUNCE_SZ; + size_t off_rxbuf = off_rxbd + page_align(rxn * RX_BD_SZ); + _slab_len = page_align(off_rxbuf + static_cast(rxn) * _cfg.rx_buf_size); + + void *p = mmap(nullptr, _slab_len, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (p == MAP_FAILED) { + _logger->error("PcieTransport: DMA slab mmap({} KiB) failed: {}", + _slab_len / 1024, strerror(errno)); + return false; + } + _slab = static_cast(p); + memset(_slab, 0, _slab_len); + + if (_cfg.iova_base + _slab_len > (1ull << 32)) { + _logger->error("PcieTransport: IOVA base 0x{:x} + slab exceeds 4 GiB (the " + "descriptor dma fields are 32-bit)", + _cfg.iova_base); + return false; + } + struct vfio_iommu_type1_dma_map m{}; + m.argsz = sizeof(m); + m.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE; + m.vaddr = reinterpret_cast(_slab); + m.iova = _cfg.iova_base; + m.size = _slab_len; + if (ioctl(_container, VFIO_IOMMU_MAP_DMA, &m) < 0) { + _logger->error("PcieTransport: VFIO_IOMMU_MAP_DMA failed: {}", + strerror(errno)); + return false; + } + + auto va = [&](size_t off) { return _slab + off; }; + auto iova = [&](size_t off) { return _cfg.iova_base + off; }; + + static const struct { + uint32_t len; + uint16_t desa, num, idx; + } kQ[Q_MAX] = { + /* Q_BCN */ {RING_LEN_BCN, RTK_PCI_TXBD_DESA_BCNQ, 0, 0}, + /* Q_MGMT */ + {RING_LEN_DEFAULT, RTK_PCI_TXBD_DESA_MGMTQ, RTK_PCI_TXBD_NUM_MGMTQ, + RTK_PCI_TXBD_IDX_MGMTQ}, + /* Q_VO */ + {RING_LEN_DEFAULT, RTK_PCI_TXBD_DESA_VOQ, RTK_PCI_TXBD_NUM_VOQ, + RTK_PCI_TXBD_IDX_VOQ}, + /* Q_VI */ + {RING_LEN_DEFAULT, RTK_PCI_TXBD_DESA_VIQ, RTK_PCI_TXBD_NUM_VIQ, + RTK_PCI_TXBD_IDX_VIQ}, + /* Q_BE */ + {RING_LEN_BE, RTK_PCI_TXBD_DESA_BEQ, RTK_PCI_TXBD_NUM_BEQ, + RTK_PCI_TXBD_IDX_BEQ}, + /* Q_BK */ + {RING_LEN_DEFAULT, RTK_PCI_TXBD_DESA_BKQ, RTK_PCI_TXBD_NUM_BKQ, + RTK_PCI_TXBD_IDX_BKQ}, + /* Q_HI0 */ + {RING_LEN_DEFAULT, RTK_PCI_TXBD_DESA_HI0Q, RTK_PCI_TXBD_NUM_HI0Q, + RTK_PCI_TXBD_IDX_HI0Q}, + /* Q_H2C */ + {RING_LEN_DEFAULT, RTK_PCI_TXBD_DESA_H2CQ, RTK_PCI_TXBD_NUM_H2CQ, + RTK_PCI_TXBD_IDX_H2CQ}, + }; + for (int q = 0; q < Q_MAX; q++) { + size_t bd_off = off_txbd + static_cast(q) * PAGE_SZ; + size_t bo_off = off_bounce + static_cast(q) * TX_BOUNCE_SZ; + _tx[q].bd = va(bd_off); + _tx[q].bd_iova = iova(bd_off); + _tx[q].bounce = va(bo_off); + _tx[q].bounce_iova = iova(bo_off); + _tx[q].bounce_len = TX_BOUNCE_SZ; + _tx[q].len = kQ[q].len; + _tx[q].wp = 0; + _tx[q].reg_desa = kQ[q].desa; + _tx[q].reg_num = kQ[q].num; + _tx[q].reg_idx = kQ[q].idx; + } + + _rx.bd = va(off_rxbd); + _rx.bd_iova = iova(off_rxbd); + _rx.bufs = va(off_rxbuf); + _rx.bufs_iova = iova(off_rxbuf); + _rx.len = rxn; + _rx.rp = 0; + for (uint32_t i = 0; i < rxn; i++) + arm_rx_bd(i); + return true; +} + +void PcieTransport::arm_rx_bd(uint32_t idx) { + /* {buf_size, total_pkt_size = 0 (HW write-back), dma} — port of + * rtw_pci_reset_rx_desc. */ + bd_write(_rx.bd + static_cast(idx) * RX_BD_SZ, + static_cast(_cfg.rx_buf_size), 0, + static_cast(_rx.bufs_iova + + static_cast(idx) * _cfg.rx_buf_size)); +} + +void PcieTransport::setup_trx_rings() { + /* Port of rtw_pci_reset_buf_desc — EXACT order (register-level quirks: + * 0x300+3 |= 0xf7 first, RWPTR clear + H2CQ CSR clear last), then + * rtw_pci_dma_reset. */ + mw(RTK_PCI_CTRL + 3, + static_cast(mr(RTK_PCI_CTRL + 3) | 0xf7)); + + /* BCN has no NUM register ("specialized for rsvd page"). */ + mw(RTK_PCI_TXBD_DESA_BCNQ, static_cast(_tx[Q_BCN].bd_iova)); + + /* H2C (wcpu-11ac chips). */ + _tx[Q_H2C].wp = 0; + mw(RTK_PCI_TXBD_NUM_H2CQ, + static_cast(_tx[Q_H2C].len & TRX_BD_IDX_MASK)); + mw(RTK_PCI_TXBD_DESA_H2CQ, static_cast(_tx[Q_H2C].bd_iova)); + + for (int q : {Q_BK, Q_BE, Q_VO, Q_VI, Q_MGMT, Q_HI0}) { + _tx[q].wp = 0; + mw(_tx[q].reg_num, + static_cast(_tx[q].len & TRX_BD_IDX_MASK)); + mw(_tx[q].reg_desa, static_cast(_tx[q].bd_iova)); + } + + _rx.rp = 0; + for (uint32_t i = 0; i < _rx.len; i++) + arm_rx_bd(i); + mw(RTK_PCI_RXBD_NUM_MPDUQ, + static_cast(_rx.len & TRX_BD_IDX_MASK)); + mw(RTK_PCI_RXBD_DESA_MPDUQ, static_cast(_rx.bd_iova)); + + /* reset read/write pointers */ + mw(RTK_PCI_TXBD_RWPTR_CLR, 0xffffffff); + /* reset H2C queue indices in a single write (wcpu-11ac) */ + mw(RTK_PCI_TXBD_H2CQ_CSR, + mr(RTK_PCI_TXBD_H2CQ_CSR) | BIT_CLR_H2CQ_HOST_IDX | + BIT_CLR_H2CQ_HW_IDX); + + /* rtw_pci_dma_reset */ + mw(RTK_PCI_CTRL, mr(RTK_PCI_CTRL) | BIT_RST_TRXDMA_INTF | + BIT_RX_TAG_EN); + _logger->info("PcieTransport: TRX rings programmed (RXBD {} slots @ IOVA " + "0x{:x})", + _rx.len, _rx.bd_iova); +} + +int PcieTransport::tx_submit_sync(int queue, const uint8_t *buf, size_t len, + int timeout_ms) { + if (queue < 0 || queue >= Q_MAX) + return -1; + TxRing &r = _tx[queue]; + if (len < TX_PKT_DESC_SZ || len > r.bounce_len) { + _logger->error("PcieTransport: tx_submit_sync len {} out of range", len); + return -1; + } + + memcpy(r.bounce, buf, len); + + /* BD slot = a pair of 8-byte entries: entry0 -> the 48-byte tx desc (with + * psb_len = total length in 128-byte units, plus OWN on the BCN queue), + * entry1 -> the payload. Port of rtw_pci_tx_write_data. */ + uint16_t psb_len = static_cast((len - 1) / 128 + 1); + if (queue == Q_BCN) + psb_len |= 1u << 15; /* RTK_PCI_TXBD_OWN_OFFSET */ + + volatile uint8_t *slot = + r.bd + static_cast(queue == Q_BCN ? 0 : r.wp) * TX_BD_SLOT_SZ; + bd_write(slot, TX_PKT_DESC_SZ, psb_len, static_cast(r.bounce_iova)); + bd_write(slot + RX_BD_SZ, static_cast(len - TX_PKT_DESC_SZ), 0, + static_cast(r.bounce_iova + TX_PKT_DESC_SZ)); + + std::atomic_thread_fence(std::memory_order_seq_cst); + + if (queue == Q_BCN) { + /* Kick the beacon queue; completion is the caller-polled bcn-valid latch + * (REG_FIFOPAGE_CTRL_2+1 bit7), same as the USB rsvd-page contract. */ + mw(RTK_PCI_TXBD_BCN_WORK, + static_cast(mr(RTK_PCI_TXBD_BCN_WORK) | + BIT_PCI_BCNQ_FLAG)); + return static_cast(len); + } + + r.wp = (r.wp + 1) % r.len; + mw(r.reg_idx, static_cast(r.wp & TRX_BD_IDX_MASK)); + + /* Sync semantics: wait for the hardware read pointer to consume the slot. */ + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(timeout_ms > 0 ? timeout_ms : 20); + for (;;) { + uint32_t idx = mr(r.reg_idx); + uint32_t hw_rp = (idx >> 16) & TRX_BD_IDX_MASK; + if (hw_rp == r.wp) + return static_cast(len); + if (std::chrono::steady_clock::now() > deadline) { + _logger->error("PcieTransport: TX q{} completion timeout (wp={} hw_rp={})", + queue, r.wp, hw_rp); + return -1; + } + sleep_us(20); + } +} + +void PcieTransport::rx_poll_loop( + const std::function &on_data, + const std::function &should_stop) { + _logger->info("PcieTransport: RX poll loop started (poll {} us)", + _cfg.rx_poll_us); + uint64_t reaped = 0; + while (!should_stop()) { + uint32_t v = mr(RTK_PCI_RXBD_IDX_MPDUQ); + uint32_t hw_wp = (v >> 16) & TRX_BD_IDX_MASK; + if (hw_wp == _rx.rp) { + sleep_us(static_cast(_cfg.rx_poll_us)); + continue; + } + std::atomic_thread_fence(std::memory_order_acquire); + while (_rx.rp != hw_wp && !should_stop()) { + const uint8_t *buf = _rx.bufs + static_cast(_rx.rp) * _cfg.rx_buf_size; + /* Exactly one MPDU per RX BD on PCIe (no USB aggregation): usable length + * = 24-byte rx desc + drvinfo + shift + pkt_len, computed from the + * descriptor itself (the BD's total_pkt_size write-back carries the DMA + * tag when RX_TAG_EN is set, not a length). */ + uint32_t d0 = static_cast(buf[0]) | (buf[1] << 8) | + (buf[2] << 16) | (static_cast(buf[3]) << 24); + uint32_t pkt_len = d0 & 0x3FFF; + uint32_t drvinfo = ((d0 >> 16) & 0xF) * 8; + uint32_t shift = (d0 >> 24) & 0x3; + uint32_t used = 24 + drvinfo + shift + pkt_len; + if (pkt_len != 0 && used <= _cfg.rx_buf_size) + on_data(buf, static_cast(used)); + + arm_rx_bd(_rx.rp); /* return the slot to hardware */ + _rx.rp = (_rx.rp + 1) % _rx.len; + ++reaped; + std::atomic_thread_fence(std::memory_order_release); + mw(RTK_PCI_RXBD_IDX_MPDUQ, + static_cast(_rx.rp & TRX_BD_IDX_MASK)); + } + } + _logger->info("PcieTransport: RX poll loop exited ({} BDs reaped)", reaped); +} + +} /* namespace devourer */ diff --git a/src/PcieTransport.h b/src/PcieTransport.h new file mode 100644 index 0000000..dbd5433 --- /dev/null +++ b/src/PcieTransport.h @@ -0,0 +1,161 @@ +#pragma once + +/* PcieTransport — vfio-pci userspace transport for the PCIe RTL88xx variants + * (first target: RTL8821CE, the PCIe sibling of the RTL8821CU). + * + * The caller owns vfio, mirroring the USB doctrine ("the caller owns libusb"): + * PcieTransport::Open(bdf) is the recommended open path — it walks + * /sys/bus/pci/devices//iommu_group, opens the vfio container + group, + * maps BAR2 (the 64 KiB MMIO window exposing the same 0x0000..0xFFFF register + * space the USB vendor-control path addresses), enables PCI bus mastering, and + * DMA-maps one anonymous slab for the TX/RX buffer-descriptor rings + RX + * buffers. The device must already be bound to vfio-pci + * (tests/pcie_vfio_bind.sh). + * + * Registers: plain volatile loads/stores on the BAR2 mapping (rtw88 pci.c maps + * bar_id=2 and uses readb/w/l at the register address). + * + * TRX: the 88xx PCIe buffer-descriptor rings, ported from rtw88 pci.{c,h} + * (v6.12) — 8-byte BD entries, 16-byte TX BD slots (entry0 = 48-byte tx desc, + * entry1 = payload), ring base/num/idx registers at 0x300..0x3B8 + the H2C + * ring at 0x1320. RX completion is detected by polling the hardware write + * index in RTK_PCI_RXBD_IDX_MPDUQ (0x3B4) — no interrupts (MSI/eventfd is a + * later optimization; monitor-mode RX at beacon rates is comfortably within a + * sub-millisecond poll). + * + * All descriptor `dma` fields and the DESA registers are 32-bit, so the slab + * is mapped at a fixed IOVA below 4 GiB (VT-d lets us choose). x86 is + * DMA-coherent — no cache sync beyond compiler ordering (volatile BD access + + * the strongly-ordered MMIO doorbell write). */ + +#include +#include +#include +#include + +#include "logger.h" + +namespace devourer { + +class PcieTransport { +public: + /* TX queues, indexing _tx_rings. Order is fixed (ring register map). */ + enum Queue : int { + Q_BCN = 0, /* beacon / reserved-page (the DLFW path) */ + Q_MGMT, + Q_VO, + Q_VI, + Q_BE, + Q_BK, + Q_HI0, + Q_H2C, + Q_MAX + }; + + struct Config { + uint32_t rx_ring_len = 512; /* RTK_MAX_RX_DESC_NUM */ + uint32_t rx_buf_size = 11480; /* RTK_PCI_RX_BUF_SIZE (11478) 8-aligned */ + uint64_t iova_base = 0x10000000; /* slab IOVA; must stay < 4 GiB */ + int rx_poll_us = 200; /* RX hw-index poll interval */ + }; + + /* Open the vfio-pci device at `bdf` ("0000:01:00.0"). Returns null and logs + * on any failure (group not viable, BAR2 map failed, DMA map failed...). + * Bus mastering + ASPM-off + completion-timeout-disable are applied here. + * (Two overloads instead of a defaulted Config arg: a nested class with + * default member initializers cannot be a default argument inside its own + * enclosing class.) */ + static std::shared_ptr Open(const std::string &bdf, + Logger_t logger, + const Config &cfg); + static std::shared_ptr Open(const std::string &bdf, + Logger_t logger); + ~PcieTransport(); + PcieTransport(const PcieTransport &) = delete; + PcieTransport &operator=(const PcieTransport &) = delete; + + /* ---- register plane (BAR2 MMIO) ---- */ + volatile uint8_t *mmio() const { return _mmio; } + size_t mmio_len() const { return _mmio_len; } + + /* ---- PCI config space (via the vfio config region) ---- */ + bool cfg_read(uint32_t off, void *buf, size_t len); + bool cfg_write(uint32_t off, const void *buf, size_t len); + + /* ---- TRX rings ---- */ + /* Program the ring base/num/idx registers — port of rtw_pci_reset_buf_desc + * + rtw_pci_dma_reset (exact order). Call per bring-up attempt, BEFORE the + * power-on sequence (rtw88: rtw_hci_setup precedes rtw_mac_power_on). */ + void setup_trx_rings(); + + /* Synchronous TX submit on `queue`: copy into the queue's bounce buffer, + * fill the BD slot, kick the doorbell. Non-BCN queues wait for the hardware + * read pointer to consume the slot (timeout_ms); the BCN queue returns after + * the kick — its completion signal is the caller-polled bcn-valid latch + * (same contract as the USB DLFW path). Returns bytes submitted or <0. */ + int tx_submit_sync(int queue, const uint8_t *buf, size_t len, int timeout_ms); + + /* Poll-driven RX reap loop: read the hw write index at 0x3B4, hand each + * ready MPDU buffer (rx desc + drvinfo + PSDU — NO USB-style aggregation, + * exactly one MPDU per BD) to on_data, re-arm the BD, advance the host + * index. Runs until should_stop(). */ + void rx_poll_loop(const std::function &on_data, + const std::function &should_stop); + + const Config &config() const { return _cfg; } + const std::string &bdf() const { return _bdf; } + +private: + PcieTransport(Logger_t logger, Config cfg) : _logger(std::move(logger)), _cfg(cfg) {} + + bool open_vfio(const std::string &bdf); + bool map_bar2(); + bool setup_config_space(); + bool init_dma(); + + template T mr(uint16_t reg) { + return *reinterpret_cast(_mmio + reg); + } + template void mw(uint16_t reg, T v) { + *reinterpret_cast(_mmio + reg) = v; + } + + struct TxRing { + volatile uint8_t *bd = nullptr; /* BD slots (16 B each) in the DMA slab */ + uint64_t bd_iova = 0; + uint8_t *bounce = nullptr; /* one in-flight frame per queue (sync TX) */ + uint64_t bounce_iova = 0; + uint32_t bounce_len = 0; + uint32_t len = 0; /* slots */ + uint32_t wp = 0; + uint16_t reg_desa = 0, reg_num = 0, reg_idx = 0; + }; + struct RxRing { + volatile uint8_t *bd = nullptr; /* 8-byte BDs */ + uint64_t bd_iova = 0; + uint8_t *bufs = nullptr; /* rx_ring_len × rx_buf_size */ + uint64_t bufs_iova = 0; + uint32_t len = 0; + uint32_t rp = 0; + }; + + void arm_rx_bd(uint32_t idx); + + Logger_t _logger; + Config _cfg; + std::string _bdf; + + int _container = -1, _group = -1, _device = -1; + volatile uint8_t *_mmio = nullptr; + size_t _mmio_len = 0; + uint64_t _cfg_region_off = 0; + size_t _cfg_region_len = 0; + + uint8_t *_slab = nullptr; /* DMA slab VA (anonymous, VFIO-pinned) */ + size_t _slab_len = 0; + + TxRing _tx[Q_MAX]; + RxRing _rx; +}; + +} /* namespace devourer */ diff --git a/src/RtlUsbAdapter.cpp b/src/RtlUsbAdapter.cpp index 63713af..07f7bc0 100644 --- a/src/RtlUsbAdapter.cpp +++ b/src/RtlUsbAdapter.cpp @@ -14,6 +14,10 @@ #include #include +#if defined(DEVOURER_HAVE_PCIE) +#include "PcieTransport.h" +#endif + using namespace std::chrono_literals; namespace { @@ -42,6 +46,17 @@ void RtlUsbAdapter::bulk_read_async_loop( int buf_size, int n_urbs, const std::function &on_data, const std::function &should_stop) { +#if defined(DEVOURER_HAVE_PCIE) + if (_pcie) { + /* PCIe: poll/reap the RX buffer-descriptor ring. One MPDU per on_data + * call (no USB aggregation); buf_size/n_urbs are USB tuning knobs and do + * not apply (ring depth is fixed at transport creation). */ + (void)buf_size; + (void)n_urbs; + _pcie->rx_poll_loop(on_data, should_stop); + return; + } +#endif AsyncRxShared sh{&on_data, &should_stop}; std::vector xfers; std::vector> bufs(n_urbs, @@ -121,6 +136,62 @@ RtlUsbAdapter::RtlUsbAdapter(libusb_device_handle *dev_handle, Logger_t logger, (AutoloadFailFlag ? "Fail" : "OK")); } +#if defined(DEVOURER_HAVE_PCIE) +RtlUsbAdapter::RtlUsbAdapter(std::shared_ptr transport, + Logger_t logger, + const devourer::DeviceConfig &cfg) + : _logger{logger}, _log_writes{cfg.debug.log_writes}, + _tx_timeout_ms{cfg.tx.timeout_ms.value_or(USB_TIMEOUT)}, + _mmio{transport->mmio()}, _pcie{std::move(transport)} { + /* No USB descriptor walk / endpoint discovery — the endpoint members stay + * at defaults and are only consumed by USB-gated code paths. */ + _logger->info("PCIe device {} (BAR2 MMIO register backend)", _pcie->bdf()); + + uint8_t eeValue = rtw_read8(REG_9346CR); + EepromOrEfuse = (eeValue & BOOT_FROM_EEPROM) != 0; + AutoloadFailFlag = (eeValue & EEPROM_EN) == 0; + _logger->info("Boot from {}, Autoload {} !", + EepromOrEfuse ? "EEPROM" : "EFUSE", + (AutoloadFailFlag ? "Fail" : "OK")); +} + +int RtlUsbAdapter::pcie_tx_dispatch(uint8_t *packet, size_t length, + int timeout_ms) { + /* QSEL lives in tx-desc dword1 bits [12:8] = byte 5 bits [4:0] on every + * 88xx descriptor this library builds (8822B/8822C layouts agree). */ + if (length < 48) + return -1; + const uint8_t qsel = packet[5] & 0x1F; + int queue; + switch (qsel) { + case 0x10: /* QSEL_BEACON — rsvd page / DLFW */ + queue = devourer::PcieTransport::Q_BCN; + break; + case 0x11: /* high */ + queue = devourer::PcieTransport::Q_HI0; + break; + case 0x12: /* mgmt (the monitor-inject descriptor) */ + queue = devourer::PcieTransport::Q_MGMT; + break; + case 0x13: /* h2c command */ + queue = devourer::PcieTransport::Q_H2C; + break; + default: /* AC data */ + queue = devourer::PcieTransport::Q_BE; + break; + } + int rc = _pcie->tx_submit_sync(queue, packet, length, timeout_ms); + _tx_stats->submitted.fetch_add(1, std::memory_order_relaxed); + if (rc < 0) { + _tx_stats->failed.fetch_add(1, std::memory_order_relaxed); + _tx_stats->last_rc.store(rc, std::memory_order_relaxed); + _tx_stats->last_timeout.store(true, std::memory_order_relaxed); + devourer::Ev(_logger->events(), "tx.fail").f("rc", rc).f("timeout", true); + } + return rc; +} +#endif /* DEVOURER_HAVE_PCIE */ + /* $ lsusb -v -d 0bda:8812 Endpoint Descriptor: @@ -136,6 +207,13 @@ RtlUsbAdapter::RtlUsbAdapter(libusb_device_handle *dev_handle, Logger_t logger, */ bool RtlUsbAdapter::WriteBytes(uint16_t reg_num, uint8_t *ptr, size_t size) { + if (_mmio) { + /* MMIO burst: plain byte stores (register space has no width side-effects + * for the multi-byte users of this path — MAC address / key material). */ + for (size_t i = 0; i < size; i++) + *reinterpret_cast(_mmio + reg_num + i) = ptr[i]; + return true; + } if (libusb_control_transfer(_dev_handle, REALTEK_USB_VENQT_WRITE, 5, reg_num, 0, ptr, size, USB_TIMEOUT) == size) { return true; @@ -458,6 +536,10 @@ void RtlUsbAdapter::transfer_callback(struct libusb_transfer *transfer) { } bool RtlUsbAdapter::send_packet(uint8_t *packet, size_t length) { +#if defined(DEVOURER_HAVE_PCIE) + if (_pcie) + return pcie_tx_dispatch(packet, length, _tx_timeout_ms) >= 0; +#endif libusb_transfer *transfer = libusb_alloc_transfer(0); if (!transfer) { @@ -565,6 +647,12 @@ int RtlUsbAdapter::bulk_send_sync(uint8_t *packet, size_t length, int RtlUsbAdapter::bulk_send_sync_ep(uint8_t ep, uint8_t *packet, size_t length, int timeout_ms) { +#if defined(DEVOURER_HAVE_PCIE) + if (_pcie) { + (void)ep; /* endpoint is USB addressing; the ring is chosen by QSEL */ + return pcie_tx_dispatch(packet, length, timeout_ms); + } +#endif /* No libusb_clear_halt here. rtw88_8814au's usbmon shows the first bulk * OUT is preceded by 0 CLEAR_FEATUREs; later CLEAR_FEATUREs happen during * normal TX-queue operation, not the per-send hot path. Resetting the diff --git a/src/RtlUsbAdapter.h b/src/RtlUsbAdapter.h index ec23763..befd2dd 100644 --- a/src/RtlUsbAdapter.h +++ b/src/RtlUsbAdapter.h @@ -20,6 +20,7 @@ namespace devourer { class UsbDeviceLock; +class PcieTransport; } #define rtw_read8 rtw_read @@ -42,10 +43,23 @@ enum TxSele { }; class RtlUsbAdapter { - libusb_device_handle *_dev_handle; + libusb_device_handle *_dev_handle = nullptr; /* null on the PCIe backend */ libusb_context *_ctx = nullptr; Logger_t _logger; + /* ---- PCIe backend (DEVOURER_PCIE) ---------------------------------- + * The adapter is bus-dual: register I/O dispatches on `_mmio` — when set, + * rtw_read/rtw_write are plain volatile loads/stores on the BAR2 mapping + * (the same 0x0000..0xFFFF register space the USB vendor-control transfers + * address), and the bulk TX/RX entry points route to the PCIe TRX rings. + * `_mmio` is a raw pointer (cheap to copy — the adapter is a copyable value + * type); `_pcie` keeps the vfio fds + DMA rings alive until the last adapter + * copy dies, the same lifetime rule `_usb_lock` encodes for USB. Null on + * USB; empty/null on non-PCIe builds (a forward-declared shared_ptr member + * costs nothing). */ + volatile uint8_t *_mmio = nullptr; + std::shared_ptr _pcie; + enum libusb_speed usbSpeed; uint8_t numOutPipes = 0; /* Bulk-IN endpoint address used by infinite_read(). 8812AU / 8814AU expose @@ -108,6 +122,20 @@ class RtlUsbAdapter { std::shared_ptr usb_lock = nullptr, const devourer::DeviceConfig &cfg = {}); +#if defined(DEVOURER_HAVE_PCIE) + /* PCIe backend: registers via BAR2 MMIO, TX/RX via the transport's DMA + * rings. No USB descriptor walk / endpoint state — the endpoint-flavoured + * members stay at their defaults and are only consumed by USB-gated code. */ + RtlUsbAdapter(std::shared_ptr transport, + Logger_t logger, const devourer::DeviceConfig &cfg = {}); +#endif + + /* True on the libusb backend, false on PCIe. The HAL bring-up gates its few + * genuinely bus-specific steps (USB RX-aggregation config, 0xFExx USB-page + * workarounds, power-seq table choice) on this. */ + bool is_usb() const { return _dev_handle != nullptr; } + devourer::PcieTransport *pcie() const { return _pcie.get(); } + /* Kernel-style async RX: keep n_urbs concurrent bulk-IN transfers in flight on * the discovered bulk-IN endpoint, invoking on_data(buf,len) for each non-empty * completion and resubmitting, until `stop` is set. Mirrors the rtw88 RX URB @@ -144,7 +172,10 @@ class RtlUsbAdapter { /* Synchronous bulk-OUT transfer that blocks until completion or timeout. * Returns the number of bytes actually transferred, or negative on error. */ int bulk_send_sync(uint8_t *packet, size_t length, int timeout_ms); - void bulk_clear_halt(uint8_t ep) { libusb_clear_halt(_dev_handle, ep); } + void bulk_clear_halt(uint8_t ep) { + if (_dev_handle) + libusb_clear_halt(_dev_handle, ep); + } int bulk_send_sync_ep(uint8_t ep, uint8_t *packet, size_t length, int timeout_ms); @@ -163,6 +194,8 @@ class RtlUsbAdapter { * (or a negative libusb error). For chip families whose RX descriptor is not * the Jaguar1 layout (e.g. Jaguar3), the caller parses the buffer itself. */ int bulk_read_raw(uint8_t *buf, int len, int timeout_ms) { + if (!_dev_handle) + return -1; /* PCIe RX goes through bulk_read_async_loop (ring reap) */ int actual = 0; int rc = libusb_bulk_transfer(_dev_handle, _bulk_in_ep, buf, len, &actual, timeout_ms); @@ -172,6 +205,18 @@ class RtlUsbAdapter { void phy_set_bb_reg(uint16_t regAddr, uint32_t bitMask, uint32_t data); template T rtw_read(uint16_t reg_num) { + if (_mmio) { + /* 0xFE00..0xFEFF is USB-page register space — undefined over MMIO. The + * few jaguar users (0xFE5B/0xFE10/0xFE11) are is_usb()-gated; catch any + * stragglers instead of poking a hole in the BAR. */ + if (reg_num >= 0xFE00) { + _logger->warn("rtw_read(0x{:04x}) on PCIe: USB-page register, " + "returning 0", + reg_num); + return 0; + } + return *reinterpret_cast(_mmio + reg_num); + } T data = 0; if (libusb_control_transfer(_dev_handle, REALTEK_USB_VENQT_READ, 5, reg_num, 0, (uint8_t *)&data, sizeof(T), @@ -187,12 +232,23 @@ class RtlUsbAdapter { template bool rtw_write(uint16_t reg_num, T value) { /* debug.log_writes: emit every vendor reg write as a debug.wreg event * (addr/width/val mirror tests/decode_wseq.py's tuple) so devourer's - * bring-up write set can be diffed against the kernel golden. */ + * bring-up write set can be diffed against the kernel golden. Applies to + * both backends — the kernel-diff tooling works unchanged over MMIO. */ if (_log_writes) devourer::Ev(_logger->events(), "debug.wreg") .hexf("addr", reg_num, 4) .f("width", sizeof(T)) .hexf("val", (unsigned long long)value, (int)(sizeof(T) * 2)); + if (_mmio) { + if (reg_num >= 0xFE00) { + _logger->warn("rtw_write(0x{:04x}) on PCIe: USB-page register, " + "dropped", + reg_num); + return false; + } + *reinterpret_cast(_mmio + reg_num) = value; + return true; + } if (libusb_control_transfer(_dev_handle, REALTEK_USB_VENQT_WRITE, 5, reg_num, 0, (uint8_t *)&value, sizeof(T), USB_TIMEOUT) == sizeof(T)) { @@ -212,6 +268,11 @@ class RtlUsbAdapter { * it can flag _tx_wedged on a non-OK completion; a static member (not a free * function) to reach that private state. */ static void transfer_callback(struct libusb_transfer *transfer); + /* PCIe TX: sniff QSEL from the tx descriptor at packet[0] and submit on the + * matching TRX ring (QSEL_BEACON -> BCN ring = the DLFW rsvd-page path, + * exactly rtw88's write_data_rsvd_page -> RTW_TX_QUEUE_BCN mapping). + * Defined only in DEVOURER_PCIE builds; every call site is _pcie-gated. */ + int pcie_tx_dispatch(uint8_t *packet, size_t length, int timeout_ms); void InitDvObj(); const char *strUsbSpeed(); void GetChipOutEP8812(); diff --git a/src/WiFiDriver.cpp b/src/WiFiDriver.cpp index 11ed03b..49d2f0e 100644 --- a/src/WiFiDriver.cpp +++ b/src/WiFiDriver.cpp @@ -9,6 +9,9 @@ #include "RtlUsbAdapter.h" #include "UsbDeviceLock.h" +#if defined(DEVOURER_HAVE_PCIE) +#include "PcieTransport.h" +#endif #if defined(DEVOURER_HAVE_JAGUAR1) #include "jaguar1/RtlJaguarDevice.h" #endif @@ -184,3 +187,38 @@ WiFiDriver::CreateRtlDevice(libusb_device_handle *dev_handle, return nullptr; #endif } + +#if defined(DEVOURER_HAVE_PCIE) +std::unique_ptr WiFiDriver::CreateRtlDevicePcie( + std::shared_ptr transport, + const devourer::DeviceConfig &cfg) { + if (!transport) { + _logger->error("CreateRtlDevicePcie: null transport"); + return nullptr; + } + /* Chip identity from SYS_CFG2 (0x00FC) over BAR2 MMIO — the same dispatch + * signal the USB factory reads via a vendor control transfer. */ + const uint8_t chip_id = + *reinterpret_cast(transport->mmio() + 0x00FC); + + if (chip_id == kChipId8821C) { +#if defined(DEVOURER_HAVE_JAGUAR2_8821C) + _logger->info("Creating RtlJaguar2Device C8821C over PCIe ({}; chip-id " + "0x{:02x})", + transport->bdf(), chip_id); + return std::make_unique( + RtlUsbAdapter(std::move(transport), _logger, cfg), _logger, + jaguar2::ChipVariant::C8821C, cfg); +#else + _logger->error("RTL8821C[E] (chip-id 0x09) detected but 8821C support not " + "compiled in (DEVOURER_JAGUAR2_8821C=OFF)"); + return nullptr; +#endif + } + + _logger->error("PCIe chip-id 0x{:02x} at {} is not supported over PCIe " + "(only RTL8821C[E], chip-id 0x09, for now)", + chip_id, transport->bdf()); + return nullptr; +} +#endif /* DEVOURER_HAVE_PCIE */ diff --git a/src/WiFiDriver.h b/src/WiFiDriver.h index 012b22b..5f48aed 100644 --- a/src/WiFiDriver.h +++ b/src/WiFiDriver.h @@ -12,6 +12,7 @@ struct libusb_context; namespace devourer { class UsbDeviceLock; +class PcieTransport; } class WiFiDriver { @@ -39,6 +40,21 @@ class WiFiDriver { libusb_context *ctx = nullptr, std::shared_ptr usb_lock = nullptr, const devourer::DeviceConfig &cfg = {}); + +#if defined(DEVOURER_HAVE_PCIE) + /* PCIe counterpart (DEVOURER_PCIE=ON, Linux/vfio only). The caller owns + * vfio, mirroring the USB doctrine: open the transport first — + * devourer::PcieTransport::Open("0000:01:00.0", logger) is the recommended + * path (the device must be bound to vfio-pci, see tests/pcie_vfio_bind.sh) — + * then hand it in here. Chip identity is read from SYS_CFG2 over MMIO; + * currently only chip-id 0x09 (RTL8821C — the RTL8821CE) is accepted, + * anything else logs and returns nullptr (same contract as an unsupported + * chip on USB). No UsbDeviceLock: a vfio device fd is exclusive by + * construction (a second open fails). */ + std::unique_ptr + CreateRtlDevicePcie(std::shared_ptr transport, + const devourer::DeviceConfig &cfg = {}); +#endif }; #endif /* WIFI_DRIVER_H */ diff --git a/src/jaguar2/HalJaguar2.cpp b/src/jaguar2/HalJaguar2.cpp index 10bb490..eb56b61 100644 --- a/src/jaguar2/HalJaguar2.cpp +++ b/src/jaguar2/HalJaguar2.cpp @@ -133,8 +133,71 @@ const PwrCfg kPwrOff8821cUsb[] = { {0, PC_END, 0, 0}, }; +#if defined(DEVOURER_HAVE_PCIE) +/* PCIe (RTL8821CE) power sequences, transcribed from the in-tree rtw88 + * rtw8821c.c tables (v6.12 — the driver proven on this exact silicon), keeping + * the PCI- and ALL-interface rows and dropping USB/SDIO-only steps. The + * PCI-only enable rows gate the PCIe reference clock/power (0x75[0] toggle + * around the 0x06[1] poll, 0x74[5], 0x22[1] clear, 0x62/0x61 bit dance) — + * without them the power-on polls fail on the E-variant. */ +const PwrCfg kPwrOn8821cPcie[] = { + /* --- card-disable -> card-emulation --- */ + {0x0005, PC_WRITE, static_cast(B(3) | B(4) | B(7)), 0}, + {0x0300, PC_WRITE, 0xFF, 0}, + {0x0301, PC_WRITE, 0xFF, 0}, + /* --- card-emulation -> active --- */ + {0x0005, PC_WRITE, static_cast(B(4) | B(3) | B(2)), 0}, + {0x0075, PC_WRITE, B(0), B(0)}, + {0x0006, PC_POLL, B(1), B(1)}, + {0x0075, PC_WRITE, B(0), 0}, + {0x0006, PC_WRITE, B(0), B(0)}, + {0x0005, PC_WRITE, B(7), 0}, + {0x0005, PC_WRITE, static_cast(B(4) | B(3)), 0}, + {0x0005, PC_WRITE, B(0), B(0)}, + {0x0005, PC_POLL, B(0), 0}, + {0x0020, PC_WRITE, B(3), B(3)}, + {0x0074, PC_WRITE, B(5), B(5)}, + {0x0022, PC_WRITE, B(1), 0}, + {0x0062, PC_WRITE, static_cast(B(7) | B(6) | B(5)), + static_cast(B(7) | B(6) | B(5))}, + {0x0061, PC_WRITE, static_cast(B(7) | B(6) | B(5)), 0}, + {0x007C, PC_WRITE, B(1), 0}, + {0, PC_END, 0, 0}, +}; + +const PwrCfg kPwrOff8821cPcie[] = { + /* --- active -> card-emulation --- */ + {0x0093, PC_WRITE, B(3), 0}, + {0x001F, PC_WRITE, 0xFF, 0}, + {0x0049, PC_WRITE, B(1), 0}, + {0x0006, PC_WRITE, B(0), B(0)}, + {0x0002, PC_WRITE, B(1), 0}, + {0x0005, PC_WRITE, B(1), B(1)}, + {0x0005, PC_POLL, B(1), 0}, + {0x0020, PC_WRITE, B(3), 0}, + /* --- card-emulation -> card-disable --- */ + {0x0067, PC_WRITE, B(5), 0}, + {0x0005, PC_WRITE, B(2), B(2)}, + {0x0081, PC_WRITE, static_cast(B(7) | B(6)), 0}, + {0x0090, PC_WRITE, B(1), 0}, + {0, PC_END, 0, 0}, +}; +#endif /* DEVOURER_HAVE_PCIE */ + void run_pwr_seq(RtlUsbAdapter &dev, const PwrCfg *seq, uint32_t poll_max, bool poll_fatal) { + /* rtw88 rtw_pwr_cmd_polling PCIe quirk: a timed-out POLL step is retried + * once after toggling BIT_PFM_WOWL in REG_SYS_PW_CTRL (0x04[3]) set->clear — + * a known 8821CE cold-boot fixer. Applied on the PCIe backend only. */ + auto poll_once = [&](const PwrCfg *p) -> bool { + uint32_t cnt = poll_max; + while ((dev.rtw_read8(p->off) & p->msk) != (p->val & p->msk)) { + if (--cnt == 0) + return false; + std::this_thread::sleep_for(std::chrono::microseconds(10)); + } + return true; + }; for (const PwrCfg *p = seq; p->cmd != PC_END; ++p) { if (p->cmd == PC_WRITE) { uint8_t v = dev.rtw_read8(p->off); @@ -143,16 +206,16 @@ void run_pwr_seq(RtlUsbAdapter &dev, const PwrCfg *seq, uint32_t poll_max, } else if (p->cmd == PC_DELAY) { std::this_thread::sleep_for(std::chrono::milliseconds(p->val)); } else { /* PC_POLL */ - uint32_t cnt = poll_max; - while ((dev.rtw_read8(p->off) & p->msk) != (p->val & p->msk)) { - if (--cnt == 0) { - if (poll_fatal) - throw std::runtime_error( - "Jaguar2: power-on poll timeout (chip not responding)"); - break; - } - std::this_thread::sleep_for(std::chrono::microseconds(10)); + bool ok = poll_once(p); + if (!ok && !dev.is_usb()) { + uint8_t v = dev.rtw_read8(0x0004); + dev.rtw_write8(0x0004, static_cast(v | (1u << 3))); + dev.rtw_write8(0x0004, static_cast(v & ~(1u << 3))); + ok = poll_once(p); } + if (!ok && poll_fatal) + throw std::runtime_error( + "Jaguar2: power-on poll timeout (chip not responding)"); } } } @@ -166,6 +229,10 @@ HalJaguar2::HalJaguar2(RtlUsbAdapter device, Logger_t logger, void HalJaguar2::power_off() { const PwrCfg *seq = _variant == ChipVariant::C8821C ? kPwrOff8821cUsb : kPwrOff8822bUsb; +#if defined(DEVOURER_HAVE_PCIE) + if (!_device.is_usb()) + seq = kPwrOff8821cPcie; /* only the 8821CE exists on PCIe */ +#endif run_pwr_seq(_device, seq, /*poll_max=*/2000, /*poll_fatal=*/false); _logger->info("Jaguar2: power-off (card-disable) sequence applied"); } @@ -174,6 +241,10 @@ void HalJaguar2::power_on() { power_off(); /* reset from any prior (kernel-left active) state first */ const PwrCfg *seq = _variant == ChipVariant::C8821C ? kPwrOn8821cUsb : kPwrOn8822bUsb; +#if defined(DEVOURER_HAVE_PCIE) + if (!_device.is_usb()) + seq = kPwrOn8821cPcie; +#endif run_pwr_seq(_device, seq, /*poll_max=*/5000, /*poll_fatal=*/true); _logger->info("Jaguar2: power-on sequence complete (card active)"); } @@ -1655,6 +1726,19 @@ void HalJaguar2::enable_rx() { * so control frames are captured too: * 0x7000282F | ACF(0x1000) = 0x7000382F. */ uint32_t rcr = 0x7000382Fu; +#if defined(DEVOURER_HAVE_PCIE) + /* PCIe (8821CE): clear bits 11/12/13. On this MAC generation they are NOT + * the Jaguar1 ADF/ACF/AMF accept bits — rtw88 reg.h names BIT(11) + * BIT_TA_BCN (TA-gated beacon accept) and BIT(12) BIT_RPFM_CAM_ENABLE — and + * with BIT(11) set and no TA programmed the WMAC drops EVERY beacon / + * management frame (hardware-bisected on the RTL8821CE, 2026-07-07: RCR + * 0x7000382F -> 0 mgmt frames; clearing bit 11 -> beacons at the kernel + * driver's rate; ctrl/data unaffected, so the bits are not needed as accept + * gates here). The USB path keeps the vendor-monitor value byte-identical — + * on the 8822B the same bits were empirically required for RX delivery. */ + if (!_device.is_usb()) + rcr &= ~((1u << 11) | (1u << 12) | (1u << 13)); +#endif /* 8821C: drop APP_PHYST_RXFF (BIT28). The 8821C appends a 32-byte PHY-status * block before each RX frame in the RXFF, but its RX descriptor reports * drv_info_size=0 (unlike the 8822B, which counts it) — so the shared parser diff --git a/src/jaguar2/HalmacJaguar2Fw.cpp b/src/jaguar2/HalmacJaguar2Fw.cpp index b6f4b2c..6f526ef 100644 --- a/src/jaguar2/HalmacJaguar2Fw.cpp +++ b/src/jaguar2/HalmacJaguar2Fw.cpp @@ -250,7 +250,9 @@ bool HalmacJaguar2Fw::send_fw_page(uint16_t pg_addr, const uint8_t *chunk, constexpr uint32_t PACKET_OFFSET_SZ = 8; const uint32_t desclen = TXDESC_SIZE_8822B; uint32_t len = desclen + size; - const bool add_pkt_offset = ((len % 512u) == 0); + /* The 512-multiple pad exists to break USB bulk max-packet alignment; on the + * PCIe BCN-ring path there is no such framing (rtw88 never pads) — gate it. */ + const bool add_pkt_offset = _device.is_usb() && ((len % 512u) == 0); if (add_pkt_offset) len += PACKET_OFFSET_SZ; /* Carry the packet-offset to the iddma: the beacon download writes diff --git a/src/jaguar2/HalmacJaguar2MacInit.cpp b/src/jaguar2/HalmacJaguar2MacInit.cpp index d8fd3c5..b573874 100644 --- a/src/jaguar2/HalmacJaguar2MacInit.cpp +++ b/src/jaguar2/HalmacJaguar2MacInit.cpp @@ -1,5 +1,7 @@ #include "HalmacJaguar2MacInit.h" +#include +#include #include /* These names are also #define macros in the Realtek hal_com_reg.h pulled in via @@ -222,11 +224,15 @@ struct FifoParams { uint32_t tx_fifo_size, rx_fifo_size; uint16_t rsvd_csibuf_num, pg_hq, pg_nq, pg_lq, pg_exq, pg_gap; }; -constexpr FifoParams fifo_params(ChipVariant v) { - return v == ChipVariant::C8821C - ? FifoParams{65536, 16384, 0, 16, 16, 16, 0, 1} - : FifoParams{TX_FIFO_SIZE, RX_FIFO_SIZE, RSVD_PG_CSIBUF_NUM, - PG_HQ, PG_NQ, PG_LQ, PG_EXQ, PG_GAP}; +constexpr FifoParams fifo_params(ChipVariant v, bool is_usb = true) { + /* 8821C PCIe (RTL8821CE) page allocation differs only in the extra queue: + * rtw88 page_table_8821c[1] (PCIE) = hq/nq/lq/exq/gap 16/16/16/14/1 vs the + * 3-bulkout USB 16/16/16/0/1. */ + if (v == ChipVariant::C8821C) + return is_usb ? FifoParams{65536, 16384, 0, 16, 16, 16, 0, 1} + : FifoParams{65536, 16384, 0, 16, 16, 16, 14, 1}; + return FifoParams{TX_FIFO_SIZE, RX_FIFO_SIZE, RSVD_PG_CSIBUF_NUM, + PG_HQ, PG_NQ, PG_LQ, PG_EXQ, PG_GAP}; } bool is_5m(ChannelWidth_t bw) { @@ -261,10 +267,16 @@ void HalmacJaguar2MacInit::enable_bb_rf(bool enable) { void HalmacJaguar2MacInit::pre_init_system_cfg() { _device.rtw_write8(REG_RSV_CTRL, 0); - /* USB: REG_SYS_CFG2+3 == 0x20 workaround */ - if (_device.rtw_read8(REG_SYS_CFG2 + 3) == 0x20) + /* USB: REG_SYS_CFG2+3 == 0x20 workaround (0xFExx is USB-page register space — + * undefined over PCIe MMIO, hence the gate). */ + if (_device.is_usb() && _device.rtw_read8(REG_SYS_CFG2 + 3) == 0x20) _device.rtw_write8(0xFE5B, _device.rtw_read8(0xFE5B) | (1u << 4)); + /* PCIe: rtw_mac_pre_system_cfg sets REG_HCI_OPT_CTRL BIT_USB_SUS_DIS + * (0x74[8]) — yes, on PCIe too in rtw88. */ + if (!_device.is_usb()) + _device.rtw_write32(0x0074, _device.rtw_read32(0x0074) | (1u << 8)); + /* pinmux */ uint32_t v = _device.rtw_read32(REG_PAD_CTRL1) & ~((1u << 28) | (1u << 29)); v |= (1u << 28) | (1u << 29); @@ -322,6 +334,12 @@ bool HalmacJaguar2MacInit::init_trx_cfg(bool set_bcn_boundary) { _set_bcn_boundary = set_bcn_boundary; uint16_t pqmap = (3u << 14) | (3u << 12) | (1u << 10) | (1u << 8) | (2u << 6) | (2u << 4); /* VO/VI->NQ, BE/BK->LQ, MG/HI->HQ */ + /* PCIe queue mapping (rtw88 rqpn_table_8821c[1]): vo/vi->NQ, be/bk->LQ, + * mg->EXQ, hi->HQ — MG rides the extra queue (whose 14 pages the PCIe + * fifo_params allocate) instead of sharing HQ. */ + if (!_device.is_usb()) + pqmap = (3u << 14) | (0u << 12) | (1u << 10) | (1u << 8) | (2u << 6) | + (2u << 4); _device.rtw_write16(REG_TXDMA_PQ_MAP, pqmap); uint8_t en_fwff = _device.rtw_read8(REG_WMAC_FWPKT_CR) & BIT_FWEN; @@ -343,7 +361,7 @@ bool HalmacJaguar2MacInit::init_trx_cfg(bool set_bcn_boundary) { } bool HalmacJaguar2MacInit::priority_queue_cfg() { - const FifoParams fp = fifo_params(_variant); + const FifoParams fp = fifo_params(_variant, _device.is_usb()); const uint16_t tx_fifo_pg_num = fp.tx_fifo_size >> TX_PAGE_SHIFT; /* 2048 */ const uint16_t rsvd_pg_num = RSVD_PG_DRV_NUM + RSVD_PG_H2C_EXTRAINFO_NUM + RSVD_PG_H2C_STATICINFO_NUM + @@ -407,7 +425,7 @@ bool HalmacJaguar2MacInit::priority_queue_cfg() { } void HalmacJaguar2MacInit::init_h2c() { - const FifoParams fp = fifo_params(_variant); + const FifoParams fp = fifo_params(_variant, _device.is_usb()); const uint16_t tx_fifo_pg_num = fp.tx_fifo_size >> TX_PAGE_SHIFT; uint16_t cur = tx_fifo_pg_num - fp.rsvd_csibuf_num - RSVD_PG_FW_TXBUF_NUM - RSVD_PG_CPU_INSTRUCTION_NUM - RSVD_PG_H2CQ_NUM; @@ -510,6 +528,15 @@ void HalmacJaguar2MacInit::init_edca_cfg() { /* init_wmac_cfg_8822b (init_low_pwr is a no-op on 8822B) */ void HalmacJaguar2MacInit::init_wmac_cfg() { + /* Group-address hash filter wide open (REG_MAR = all-ones), as the Jaguar3 + * init_wmac_cfg and halmac init_wmac_cfg_88xx do. With MAR at 0, every + * broadcast/multicast frame — i.e. all beacons — is dropped regardless of + * RCR AB/AM. The USB bring-up got away without it because the chip's warm + * state carried a set MAR; the PCIe path (post card-disable / FLR) starts + * from 0 and received no management frames until this write + * (hardware-bisected on the RTL8821CE, 2026-07-07). */ + _device.rtw_write32(0x0620 /* REG_MAR */, 0xFFFFFFFF); + _device.rtw_write32(0x0624, 0xFFFFFFFF); _device.rtw_write32(REG_RXFLTMAP0, WLAN_RX_FILTER0); _device.rtw_write16(REG_RXFLTMAP2, WLAN_RX_FILTER2); /* RXFLTMAP1 (0x06A2) is the control-frame subtype filter. halmac's @@ -570,4 +597,30 @@ void HalmacJaguar2MacInit::init_usb_cfg() { _logger->info("Jaguar2: init_usb_cfg (RXDMA_MODE=0x{:02x}, RX-agg EN)", v); } +/* PCIe interface-PHY config (rtw_pci_phy_cfg): the 8821C gen1 intf table is a + * single MDIO write {addr 0x09, val 0x6380} (gen2 is empty), issued through + * the REG_MDIO_V1 (0x3F4) / REG_PCIE_MIX_CFG (0x3F8) window — normal MAC + * registers, so plain rtw_read/write works. ASPM/CLKREQ tuning is skipped for + * bring-up (power saving only; the transport already clears ASPM in LNKCTL). */ +void HalmacJaguar2MacInit::pcie_phy_cfg() { + constexpr uint16_t REG_MDIO_V1 = 0x03F4; + constexpr uint16_t REG_PCIE_MIX_CFG = 0x03F8; + const uint8_t addr = 0x09; + const uint16_t data = 0x6380; + _device.rtw_write16(REG_MDIO_V1, data); + /* page = (addr < 32 ? 0 : 1) + G1 page offset (0). */ + _device.rtw_write8(REG_PCIE_MIX_CFG, addr & 0x1F); + _device.rtw_write8(REG_PCIE_MIX_CFG + 3, 0); + _device.rtw_write32(REG_PCIE_MIX_CFG, + _device.rtw_read32(REG_PCIE_MIX_CFG) | (1u << 5)); + for (int i = 0; i < 20; i++) { + if ((_device.rtw_read32(REG_PCIE_MIX_CFG) & (1u << 5)) == 0) { + _logger->info("Jaguar2: PCIe MDIO gen1 cfg applied (0x09=0x6380)"); + return; + } + std::this_thread::sleep_for(std::chrono::microseconds(10)); + } + _logger->warn("Jaguar2: PCIe MDIO write did not complete (non-fatal)"); +} + } /* namespace jaguar2 */ diff --git a/src/jaguar2/HalmacJaguar2MacInit.h b/src/jaguar2/HalmacJaguar2MacInit.h index 77569a1..98e8938 100644 --- a/src/jaguar2/HalmacJaguar2MacInit.h +++ b/src/jaguar2/HalmacJaguar2MacInit.h @@ -54,9 +54,14 @@ class HalmacJaguar2MacInit { bool init_mac_cfg(ChannelWidth_t bw); /* USB RX-DMA mode + RX aggregation (init_usb_cfg_88xx) so received frames are - * delivered to the bulk-IN endpoint. */ + * delivered to the bulk-IN endpoint. USB backend only — the PCIe RX path is + * the buffer-descriptor ring the transport programs pre-power-on. */ void init_usb_cfg(); + /* PCIe interface-PHY config (rtw_pci_phy_cfg's 8821C gen1 MDIO write). + * PCIe backend only; run once before the first power-on. */ + void pcie_phy_cfg(); + private: bool priority_queue_cfg(); void init_h2c(); diff --git a/src/jaguar2/RtlJaguar2Device.cpp b/src/jaguar2/RtlJaguar2Device.cpp index 8a37aba..8dd4b3c 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -20,6 +20,9 @@ #include "RateDefinitions.h" #include "RxPacket.h" #include "SignalStop.h" /* g_devourer_should_stop */ +#if defined(DEVOURER_HAVE_PCIE) +#include "PcieTransport.h" /* setup_trx_rings in the PCIe bring-up gate */ +#endif extern "C" { #include "ieee80211_radiotap.h" /* MRateToHwRate + radiotap iterator */ } @@ -56,11 +59,23 @@ void RtlJaguar2Device::bring_up(SelectedChannel channel) { * pre-init -> power-on -> system-cfg -> DLFW, giving the chip a clean slate. */ bool fw_ok = false; constexpr int kBringupTries = 4; +#if defined(DEVOURER_HAVE_PCIE) + /* rtw88 applies the PCIe interface-PHY MDIO config once at probe. */ + if (!_device.is_usb()) + _macinit.pcie_phy_cfg(); +#endif for (int attempt = 0; attempt < kBringupTries && !fw_ok; attempt++) { if (attempt > 0) _logger->error("RtlJaguar2Device: DLFW failed — full power-cycle retry " "{}/{}", attempt + 1, kBringupTries); +#if defined(DEVOURER_HAVE_PCIE) + /* rtw88 order: rtw_hci_setup (TRX buffer-descriptor ring registers) + * precedes rtw_mac_power_on — and the DLFW rsvd-page path needs the BCN + * ring live. Re-programmed per attempt (the power seq touches 0x300). */ + if (!_device.is_usb()) + _device.pcie()->setup_trx_rings(); +#endif _macinit.pre_init_system_cfg(); _hal.power_on(); _hal.read_chip_version(); @@ -73,7 +88,8 @@ void RtlJaguar2Device::bring_up(SelectedChannel channel) { if (!_macinit.init_mac_cfg(channel.ChannelWidth)) throw std::runtime_error("RtlJaguar2Device: init_mac_cfg failed"); - _macinit.init_usb_cfg(); + if (_device.is_usb()) + _macinit.init_usb_cfg(); /* PCIe RX = the BD ring, no RX-DMA agg cfg */ _macinit.enable_bb_rf(true); _logger->info("RtlJaguar2Device: MAC cfg + BB/RF enabled"); diff --git a/tests/pcie_rx_smoke.py b/tests/pcie_rx_smoke.py new file mode 100644 index 0000000..a2300e9 --- /dev/null +++ b/tests/pcie_rx_smoke.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""pcie_rx_smoke.py — ambient-beacon RX validation for the PCIe transport +(RTL8821CE via vfio-pci). Single-adapter: no 2x2 matrix is possible on the +PCIe rig, so the pass signal is ambient traffic — management beacons decoded +CRC-clean on both bands. + + sudo python3 tests/pcie_rx_smoke.py [--bdf 0000:01:00.0] \ + [--rxdemo build/rxdemo] [--secs 15] [--channels 6,36] + +Per channel: bind to vfio-pci (tests/pcie_vfio_bind.sh), run rxdemo with +DEVOURER_PCIE_BDF + DEVOURER_RX_DUMP_ALL, parse the rx.corrupt event stream +(fc = 802.11 frame-control word), and require >= MIN_BEACONS frames with +fc 0x0080 (beacon) and a zero chip-CRC-fail count among them. Exit 0 = all +channels pass. The adapter is left bound to vfio-pci (use +tests/pcie_vfio_bind.sh --restore to hand it back to rtw88). +""" + +import argparse +import json +import os +import subprocess +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +MIN_BEACONS = 5 # per channel; ambient APs beacon at ~10/s each + + +def run_channel(rxdemo, bdf, channel, secs): + env = dict(os.environ) + env.update({ + "DEVOURER_PCIE_BDF": bdf, + "DEVOURER_CHANNEL": str(channel), + "DEVOURER_RX_DUMP_ALL": "1", + "DEVOURER_LOG_LEVEL": "warn", + }) + proc = subprocess.run( + ["timeout", str(secs + 30), rxdemo], + env=env, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, + timeout=secs + 60, + ) + # rxdemo runs until killed; wrap with `timeout` for the dwell. + total = beacons = beacon_crc_fail = 0 + for line in proc.stdout.decode(errors="replace").splitlines(): + if not line.startswith('{"ev":"rx.corrupt"'): + continue + try: + ev = json.loads(line) + except json.JSONDecodeError: + continue + total += 1 + if ev.get("fc") == "0x0080": + beacons += 1 + if ev.get("crc"): + beacon_crc_fail += 1 + return total, beacons, beacon_crc_fail + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--bdf", default="0000:01:00.0") + ap.add_argument("--rxdemo", default=os.path.join(HERE, "..", "build", "rxdemo")) + ap.add_argument("--secs", type=int, default=15) + ap.add_argument("--channels", default="6,36") + args = ap.parse_args() + + if os.geteuid() != 0: + print("ERROR: run as root (vfio + register access)", file=sys.stderr) + return 2 + + bind = subprocess.run( + ["bash", os.path.join(HERE, "pcie_vfio_bind.sh"), args.bdf]) + if bind.returncode != 0: + print("FAIL: vfio bind", file=sys.stderr) + return 1 + + ok = True + for ch in [int(c) for c in args.channels.split(",")]: + # `timeout` sends SIGTERM; rxdemo installs handlers and exits cleanly. + total, beacons, bcrc = run_channel(args.rxdemo, args.bdf, ch, args.secs) + verdict = "PASS" if (beacons >= MIN_BEACONS and bcrc == 0) else "FAIL" + print(f"ch{ch}: frames={total} beacons={beacons} " + f"beacon_crc_fail={bcrc} -> {verdict}") + if verdict == "FAIL": + ok = False + + print("RESULT:", "PASS" if ok else "FAIL") + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/pcie_vfio_bind.sh b/tests/pcie_vfio_bind.sh new file mode 100644 index 0000000..9a0d607 --- /dev/null +++ b/tests/pcie_vfio_bind.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# pcie_vfio_bind.sh — move a PCIe Realtek NIC between its kernel driver and +# vfio-pci for the devourer PCIe transport (pcieprobe / rxdemo DEVOURER_PCIE_BDF). +# +# sudo tests/pcie_vfio_bind.sh 0000:01:00.0 # -> vfio-pci +# sudo tests/pcie_vfio_bind.sh 0000:01:00.0 --restore # -> kernel driver +# +# Uses driver_override (race-free vs. the in-tree rtw88 auto-probe: new_id +# would still let rtw88 grab the device on a rescan; driver_override pins it). +# --restore clears the override and reprobes, handing it back to rtw88. +set -euo pipefail + +BDF="${1:?usage: $0 [--restore]}" +MODE="${2:-bind}" +DEV="/sys/bus/pci/devices/$BDF" + +[ -d "$DEV" ] || { echo "ERROR: no PCI device $BDF" >&2; exit 1; } +[ "$(id -u)" = 0 ] || { echo "ERROR: run as root" >&2; exit 1; } + +current_driver() { + basename "$(readlink -f "$DEV/driver" 2>/dev/null)" 2>/dev/null || echo none +} + +if [ "$MODE" = "--restore" ]; then + echo "" > "$DEV/driver_override" + if [ -e "$DEV/driver" ]; then + echo "$BDF" > "$DEV/driver/unbind" + fi + echo "$BDF" > /sys/bus/pci/drivers_probe + echo "restored: $BDF -> $(current_driver)" + exit 0 +fi + +modprobe vfio-pci + +cur="$(current_driver)" +if [ "$cur" = vfio-pci ]; then + echo "already bound: $BDF -> vfio-pci" + exit 0 +fi +if [ "$cur" != none ]; then + echo "unbinding $BDF from $cur" + echo "$BDF" > "$DEV/driver/unbind" +fi + +echo vfio-pci > "$DEV/driver_override" +echo "$BDF" > /sys/bus/pci/drivers_probe + +new="$(current_driver)" +[ "$new" = vfio-pci ] || { echo "ERROR: bind failed ($BDF -> $new)" >&2; exit 1; } + +group="$(basename "$(readlink -f "$DEV/iommu_group")")" +echo "bound: $BDF -> vfio-pci (IOMMU group $group, /dev/vfio/$group)" From 986866d27b242e2c3185d4c77ceca9eb318ff48f Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:38:28 +0300 Subject: [PATCH 02/11] =?UTF-8?q?PCIe=20TX=20=E2=80=94=20txdemo=20DEVOURER?= =?UTF-8?q?=5FPCIE=5FBDF=20branch=20(RTL8821CE=20on-air)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CLAUDE.md | 4 +- examples/tx/main.cpp | 102 +++++++++++++++++++++++++++++-------------- 2 files changed, 72 insertions(+), 34 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b7e9e63..6811a12 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,7 +50,9 @@ bits 11-13 cleared — on this MAC generation they are TA_BCN/RPFM_CAM, not the Jaguar1 accept bits, and TA_BCN drops every beacon). 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; `pcieprobe [id|power|fw]` validates the layers bottom-up. +on rxdemo and txdemo (TX = the data/MGMT BD rings behind the unchanged +`send_packet`); `pcieprobe [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 diff --git a/examples/tx/main.cpp b/examples/tx/main.cpp index 3ebec58..68733a9 100644 --- a/examples/tx/main.cpp +++ b/examples/tx/main.cpp @@ -43,6 +43,9 @@ #include "RtlUsbAdapter.h" #include "SignalStop.h" #include "UsbOpen.h" +#if defined(DEVOURER_HAVE_PCIE) +#include "PcieTransport.h" +#endif #include "WiFiDriver.h" #include "env_config.h" #include "RadiotapBuilder.h" @@ -196,7 +199,17 @@ int main(int argc, char **argv) { long fd = (argc >= 2) ? std::strtol(argv[1], nullptr, 0) : 0; const bool termux_mode = (fd > 0); - if (termux_mode) { + /* DEVOURER_PCIE_BDF=0000:01:00.0 — drive a PCIe adapter (RTL8821CE) through + * the vfio transport instead of libusb (DEVOURER_PCIE builds; mirrors the RX + * demo). USB open/claim is skipped; libusb is still initialised so the + * (idle) event thread below runs unchanged. */ + const char *pcie_bdf = std::getenv("DEVOURER_PCIE_BDF"); + + if (pcie_bdf) { + rc = libusb_init(&context); + if (rc < 0) + return rc; + } else if (termux_mode) { logger->info("Termux mode: wrapping fd {}", fd); libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY); libusb_set_option(NULL, LIBUSB_OPTION_WEAK_AUTHORITY); @@ -280,31 +293,33 @@ int main(int argc, char **argv) { } } - libusb_device *device = libusb_get_device(handle); - libusb_device_descriptor desc{}; - libusb_get_device_descriptor(device, &desc); - logger->info("Vendor/Product ID: {:04x}:{:04x}", desc.idVendor, - desc.idProduct); - - devourer::Ev(*g_ev, "init.timing") - .f("stage", "txdemo.open_device") - .f("ms", ms_since_start()); - /* Claim-before-reset (see src/UsbOpen.h): the exclusive interface claim is the - * primary guard — a second devourer on this adapter gets BUSY and we bail here - * before the reset, so it can't re-enumerate the adapter out from under the - * owner. Reset skipped in termux_mode (forked child shares the fd) and for - * DEVOURER_SKIP_RESET (warm pickup). */ std::shared_ptr usb_lock; - rc = devourer::claim_interface_then_reset( - handle, 0, logger, - !termux_mode && std::getenv("DEVOURER_SKIP_RESET") == nullptr, usb_lock); - devourer::Ev(*g_ev, "init.timing") - .f("stage", "txdemo.usb_reset") - .f("ms", ms_since_start()); - if (rc != 0) { - libusb_close(handle); - libusb_exit(context); - return 1; + if (!pcie_bdf) { + libusb_device *device = libusb_get_device(handle); + libusb_device_descriptor desc{}; + libusb_get_device_descriptor(device, &desc); + logger->info("Vendor/Product ID: {:04x}:{:04x}", desc.idVendor, + desc.idProduct); + + devourer::Ev(*g_ev, "init.timing") + .f("stage", "txdemo.open_device") + .f("ms", ms_since_start()); + /* Claim-before-reset (see src/UsbOpen.h): the exclusive interface claim is + * the primary guard — a second devourer on this adapter gets BUSY and we + * bail here before the reset, so it can't re-enumerate the adapter out from + * under the owner. Reset skipped in termux_mode (forked child shares the + * fd) and for DEVOURER_SKIP_RESET (warm pickup). */ + rc = devourer::claim_interface_then_reset( + handle, 0, logger, + !termux_mode && std::getenv("DEVOURER_SKIP_RESET") == nullptr, usb_lock); + devourer::Ev(*g_ev, "init.timing") + .f("stage", "txdemo.usb_reset") + .f("ms", ms_since_start()); + if (rc != 0) { + libusb_close(handle); + libusb_exit(context); + return 1; + } } /* USB-wire sentinel writes — gated behind DEVOURER_USB_SENTINEL=1. Used by @@ -413,8 +428,27 @@ int main(int argc, char **argv) { } WiFiDriver wifi_driver{logger}; - auto rtlDevice = wifi_driver.CreateRtlDevice(handle, nullptr, usb_lock, - devourer_config_from_env()); + std::unique_ptr rtlDevice; +#if defined(DEVOURER_HAVE_PCIE) + if (pcie_bdf) { + auto transport = devourer::PcieTransport::Open(pcie_bdf, logger); + if (!transport) + return 1; + devourer::Ev(*g_ev, "init.timing") + .f("stage", "txdemo.open_device") + .f("ms", ms_since_start()); + rtlDevice = wifi_driver.CreateRtlDevicePcie(std::move(transport), + devourer_config_from_env()); + } else +#endif + { + if (pcie_bdf) { + logger->error("DEVOURER_PCIE_BDF set but this build has DEVOURER_PCIE=OFF"); + return 1; + } + rtlDevice = wifi_driver.CreateRtlDevice(handle, nullptr, usb_lock, + devourer_config_from_env()); + } if (!rtlDevice) { /* Factory returns null when this chip's generation wasn't compiled in * (per-chip CMake options); it already logged which. */ @@ -1097,12 +1131,14 @@ int main(int argc, char **argv) { rtlDevice->Stop(); /* Tolerant teardown: if the chip already dropped off the bus (e.g. a TX-path - * wedge), release_interface returns an error — log it, don't assert/abort. */ - rc = libusb_release_interface(handle, 0); - if (rc != 0) - logger->info("libusb_release_interface rc={} (device already gone?)", rc); - - libusb_close(handle); + * wedge), release_interface returns an error — log it, don't assert/abort. + * PCIe runs have no USB handle; the transport tears down with the device. */ + if (handle != nullptr) { + rc = libusb_release_interface(handle, 0); + if (rc != 0) + logger->info("libusb_release_interface rc={} (device already gone?)", rc); + libusb_close(handle); + } libusb_exit(context); return 0; } From 07eb1312b7318f2ae033a2a1bfda7d9667cb8c26 Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:54:03 +0300 Subject: [PATCH 03/11] =?UTF-8?q?Jaguar2=20monitor=20RCR:=20drop=20bits=20?= =?UTF-8?q?11-13=20=E2=80=94=20ambient=20beacons=20were=20dropped=20on=20U?= =?UTF-8?q?SB=20too?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/jaguar2/HalJaguar2.cpp | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/jaguar2/HalJaguar2.cpp b/src/jaguar2/HalJaguar2.cpp index eb56b61..d3192e9 100644 --- a/src/jaguar2/HalJaguar2.cpp +++ b/src/jaguar2/HalJaguar2.cpp @@ -1718,27 +1718,21 @@ void HalJaguar2::enable_rx() { * (+ENSWBCN), matching the jaguar3 RX-enable value 0x06FF. init_mac_cfg only * set the DMA bits; without MACRXEN (BIT7) the MAC RX engine never runs. */ _device.rtw_write16(0x0100, 0x06FF); - /* Promiscuous RX for monitor. The frame-type-accept bits are the critical - * ones: ADF(BIT11)/ACF(BIT12)/AMF(BIT13) gate data/control/management frames - * at WMAC — without them the BB decodes frames (CRC-OK) but the MAC RX FIFO - * stays empty (RXPKT_NUM=0). Value = the vendor monitor RCR (hal_com.c: - * RCR_AAP|APM|AM|AB|APWRMGT|ADF|AMF|APP_PHYST_RXFF|APP_MIC|APP_ICV) plus ACF - * so control frames are captured too: - * 0x7000282F | ACF(0x1000) = 0x7000382F. */ - uint32_t rcr = 0x7000382Fu; -#if defined(DEVOURER_HAVE_PCIE) - /* PCIe (8821CE): clear bits 11/12/13. On this MAC generation they are NOT - * the Jaguar1 ADF/ACF/AMF accept bits — rtw88 reg.h names BIT(11) - * BIT_TA_BCN (TA-gated beacon accept) and BIT(12) BIT_RPFM_CAM_ENABLE — and - * with BIT(11) set and no TA programmed the WMAC drops EVERY beacon / - * management frame (hardware-bisected on the RTL8821CE, 2026-07-07: RCR - * 0x7000382F -> 0 mgmt frames; clearing bit 11 -> beacons at the kernel - * driver's rate; ctrl/data unaffected, so the bits are not needed as accept - * gates here). The USB path keeps the vendor-monitor value byte-identical — - * on the 8822B the same bits were empirically required for RX delivery. */ - if (!_device.is_usb()) - rcr &= ~((1u << 11) | (1u << 12) | (1u << 13)); -#endif + /* Promiscuous RX for monitor: the vendor monitor RCR (hal_com.c: + * RCR_AAP|APM|AM|AB|APWRMGT|APP_PHYST_RXFF|APP_MIC|APP_ICV = 0x7000002F) + * WITHOUT the legacy "accept" trio at bits 11/12/13. On this MAC generation + * those are NOT the Jaguar1 ADF/ACF/AMF accept bits — rtw88 reg.h names + * BIT(11) BIT_TA_BCN (TA-gated beacon accept) and BIT(12) + * BIT_RPFM_CAM_ENABLE — and with BIT(11) set and no TA programmed the WMAC + * drops EVERY ambient beacon / management frame. Hardware-bisected on all + * three Jaguar2 parts (2026-07-07): 0x7000382F -> 0 ambient beacons on the + * RTL8821CE (PCIe), RTL8811CU and RTL8822BU (USB); with the bits cleared + * each receives ambient beacons at the kernel driver's rate, and ctrl/data + * delivery is unchanged — so the bits gate nothing monitor RX needs + * (rtw88's own default RCR never sets 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. */ + uint32_t rcr = 0x7000002Fu; /* 8821C: drop APP_PHYST_RXFF (BIT28). The 8821C appends a 32-byte PHY-status * block before each RX frame in the RXFF, but its RX descriptor reports * drv_info_size=0 (unlike the 8822B, which counts it) — so the shared parser From 23badb9d00a0de6991e4b308740b1f22063cdad2 Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:02:52 +0300 Subject: [PATCH 04/11] PcieTransport: MSI-via-eventfd RX wakeups (default; polled fallback) 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 --- examples/rx/main.cpp | 4 ++ src/PcieTransport.cpp | 85 ++++++++++++++++++++++++++++++++++++++++--- src/PcieTransport.h | 7 ++++ 3 files changed, 90 insertions(+), 6 deletions(-) diff --git a/examples/rx/main.cpp b/examples/rx/main.cpp index 4c0432c..c2d9a6e 100644 --- a/examples/rx/main.cpp +++ b/examples/rx/main.cpp @@ -544,6 +544,10 @@ int main() { devourer::PcieTransport::Config pcfg; if (const char *pp = std::getenv("DEVOURER_PCIE_RX_POLL_US")) pcfg.rx_poll_us = std::atoi(pp); + /* DEVOURER_PCIE_NO_MSI=1 — force the fixed-interval polled RX loop + * (A/B escape hatch; MSI+eventfd is the default). */ + if (std::getenv("DEVOURER_PCIE_NO_MSI")) + pcfg.use_msi = false; auto transport = devourer::PcieTransport::Open(bdf, logger, pcfg); if (!transport) return 1; diff --git a/src/PcieTransport.cpp b/src/PcieTransport.cpp index c58c82b..d542c14 100644 --- a/src/PcieTransport.cpp +++ b/src/PcieTransport.cpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include #include @@ -61,6 +63,12 @@ constexpr uint32_t BIT_CLR_H2CQ_HW_IDX = 1u << 8; constexpr uint16_t RTK_PCI_TXBD_BCN_WORK = 0x383; constexpr uint8_t BIT_PCI_BCNQ_FLAG = 1u << 4; +/* Interrupt registers (RX-relevant subset). */ +constexpr uint16_t RTK_PCI_HIMR0 = 0x0B0; +constexpr uint16_t RTK_PCI_HISR0 = 0x0B4; +constexpr uint32_t IMR_ROK = 1u << 0; /* RX DMA OK */ +constexpr uint32_t IMR_RDU = 1u << 1; /* RX descriptor unavailable */ + constexpr uint32_t TRX_BD_IDX_MASK = 0xFFF; /* 8821C (and all wcpu-11ac rtw88 chips we care about): 48-byte tx pkt desc, @@ -114,14 +122,57 @@ std::shared_ptr PcieTransport::Open(const std::string &bdf, return nullptr; if (!t->init_dma()) return nullptr; + if (cfg.use_msi && !t->setup_msi()) + logger->warn("PcieTransport: MSI setup failed — RX falls back to polling"); logger->info("PcieTransport: {} ready (BAR2 {} KiB, DMA slab {} KiB @ IOVA " - "0x{:x})", - bdf, t->_mmio_len / 1024, t->_slab_len / 1024, - t->_cfg.iova_base); + "0x{:x}, RX {})", + bdf, t->_mmio_len / 1024, t->_slab_len / 1024, t->_cfg.iova_base, + t->_msi_evt >= 0 ? "MSI+eventfd" : "polled"); return t; } +bool PcieTransport::setup_msi() { + struct vfio_irq_info info{}; + info.argsz = sizeof(info); + info.index = VFIO_PCI_MSI_IRQ_INDEX; + if (ioctl(_device, VFIO_DEVICE_GET_IRQ_INFO, &info) < 0 || info.count < 1) { + _logger->warn("PcieTransport: no MSI IRQ available"); + return false; + } + int evt = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (evt < 0) + return false; + /* One MSI vector -> the eventfd. */ + char buf[sizeof(struct vfio_irq_set) + sizeof(int32_t)] = {}; + auto *is = reinterpret_cast(buf); + is->argsz = sizeof(buf); + is->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; + is->index = VFIO_PCI_MSI_IRQ_INDEX; + is->start = 0; + is->count = 1; + memcpy(is->data, &evt, sizeof(int32_t)); + if (ioctl(_device, VFIO_DEVICE_SET_IRQS, is) < 0) { + _logger->warn("PcieTransport: VFIO_DEVICE_SET_IRQS(MSI) failed: {}", + strerror(errno)); + close(evt); + return false; + } + _msi_evt = evt; + return true; +} + PcieTransport::~PcieTransport() { + if (_msi_evt >= 0) { + if (_mmio) + mw(RTK_PCI_HIMR0, 0); /* mask before dropping the vector */ + struct vfio_irq_set off{}; + off.argsz = sizeof(off); + off.flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_TRIGGER; + off.index = VFIO_PCI_MSI_IRQ_INDEX; + off.count = 0; + ioctl(_device, VFIO_DEVICE_SET_IRQS, &off); + close(_msi_evt); + } if (_mmio) munmap(const_cast(_mmio), _mmio_len); if (_slab && _container >= 0) { @@ -522,13 +573,33 @@ int PcieTransport::tx_submit_sync(int queue, const uint8_t *buf, size_t len, void PcieTransport::rx_poll_loop( const std::function &on_data, const std::function &should_stop) { - _logger->info("PcieTransport: RX poll loop started (poll {} us)", - _cfg.rx_poll_us); + const bool msi = _msi_evt >= 0; + _logger->info("PcieTransport: RX loop started ({})", + msi ? "MSI+eventfd, 100 ms safety timeout" + : "polled"); + if (msi) { + /* Clear any latched status, then unmask RX-OK + ring-underrun. */ + mw(RTK_PCI_HISR0, mr(RTK_PCI_HISR0)); + mw(RTK_PCI_HIMR0, IMR_ROK | IMR_RDU); + } uint64_t reaped = 0; while (!should_stop()) { uint32_t v = mr(RTK_PCI_RXBD_IDX_MPDUQ); uint32_t hw_wp = (v >> 16) & TRX_BD_IDX_MASK; if (hw_wp == _rx.rp) { + if (msi) { + /* Wait for the MSI edge. 100 ms timeout = safety net against a lost + * edge (the ring index re-check above makes a spurious/late wake + * harmless); always drain the eventfd counter and W1C the HISR so the + * next frame generates a fresh edge. */ + struct pollfd pfd {_msi_evt, POLLIN, 0}; + (void)::poll(&pfd, 1, 100); + uint64_t cnt; + while (read(_msi_evt, &cnt, sizeof(cnt)) == sizeof(cnt)) { + } + mw(RTK_PCI_HISR0, mr(RTK_PCI_HISR0)); + continue; + } sleep_us(static_cast(_cfg.rx_poll_us)); continue; } @@ -556,7 +627,9 @@ void PcieTransport::rx_poll_loop( static_cast(_rx.rp & TRX_BD_IDX_MASK)); } } - _logger->info("PcieTransport: RX poll loop exited ({} BDs reaped)", reaped); + if (msi) + mw(RTK_PCI_HIMR0, 0); /* re-mask on exit */ + _logger->info("PcieTransport: RX loop exited ({} BDs reaped)", reaped); } } /* namespace devourer */ diff --git a/src/PcieTransport.h b/src/PcieTransport.h index dbd5433..8c5d7bb 100644 --- a/src/PcieTransport.h +++ b/src/PcieTransport.h @@ -57,6 +57,11 @@ class PcieTransport { uint32_t rx_buf_size = 11480; /* RTK_PCI_RX_BUF_SIZE (11478) 8-aligned */ uint64_t iova_base = 0x10000000; /* slab IOVA; must stay < 4 GiB */ int rx_poll_us = 200; /* RX hw-index poll interval */ + /* MSI-via-eventfd RX wakeups (VFIO_DEVICE_SET_IRQS). The reap logic is + * identical; MSI only replaces the fixed-interval sleep with an eventfd + * wait (100 ms safety timeout keeps a lost edge from ever stalling RX). + * Falls back to pure polling automatically when MSI setup fails. */ + bool use_msi = true; }; /* Open the vfio-pci device at `bdf` ("0000:01:00.0"). Returns null and logs @@ -112,6 +117,7 @@ class PcieTransport { bool map_bar2(); bool setup_config_space(); bool init_dma(); + bool setup_msi(); template T mr(uint16_t reg) { return *reinterpret_cast(_mmio + reg); @@ -146,6 +152,7 @@ class PcieTransport { std::string _bdf; int _container = -1, _group = -1, _device = -1; + int _msi_evt = -1; /* eventfd signalled per MSI; -1 = polling mode */ volatile uint8_t *_mmio = nullptr; size_t _mmio_len = 0; uint64_t _cfg_region_off = 0; From edc2da305e7122c54b4d2b8f53ce099281a99992 Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:48:26 +0300 Subject: [PATCH 05/11] CMake: self-documenting DEVOURER_PCIE constraint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CMakeLists.txt | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 54e3e78..86ec2c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,9 +25,14 @@ 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 only supported PCIe chip is the -# RTL8821CE (the PCIe sibling of the RTL8821CU), so it requires the 8821C -# variant. OFF by default — libusb-only builds are bit-identical. +# 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 — @@ -77,8 +82,15 @@ if(DEVOURER_PCIE) endif() if(NOT DEVOURER_JAGUAR2_8821C) message(FATAL_ERROR - "DEVOURER_PCIE=ON requires DEVOURER_JAGUAR2_8821C=ON (the supported " - "PCIe chip, RTL8821CE, is the Jaguar2 8821C).") + "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() From 9cdb7798d8088ea15013a085f73ac8311a2ba692 Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 7 Jul 2026 17:12:23 +0300 Subject: [PATCH 06/11] Transport seam: independent USB/PCIe transports behind IRtlTransport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CLAUDE.md | 20 +- CMakeLists.txt | 8 +- examples/duplex/main.cpp | 2 +- examples/pcieprobe/main.cpp | 4 +- examples/precoder/main.cpp | 2 +- examples/rx/main.cpp | 2 +- examples/streamtx/main.cpp | 2 +- examples/svctx/main.cpp | 2 +- examples/tx/main.cpp | 4 +- src/BeamformingSounder.h | 12 +- src/PcieTransport.cpp | 47 ++ src/PcieTransport.h | 71 ++- src/RtlAdapter.cpp | 264 ++++++++++ src/RtlAdapter.h | 180 +++++++ src/RtlTransport.h | 88 ++++ src/RtlUsbAdapter.cpp | 696 ------------------------- src/RtlUsbAdapter.h | 282 +--------- src/ToneMask.h | 20 +- src/TxStats.h | 2 +- src/UsbTransport.cpp | 350 +++++++++++++ src/UsbTransport.h | 107 ++++ src/WiFiDriver.cpp | 19 +- src/jaguar1/BbDbgportReader.cpp | 2 +- src/jaguar1/BbDbgportReader.h | 6 +- src/jaguar1/EepromManager.cpp | 8 +- src/jaguar1/EepromManager.h | 12 +- src/jaguar1/FirmwareManager.cpp | 2 +- src/jaguar1/FirmwareManager.h | 6 +- src/jaguar1/HalModule.cpp | 4 +- src/jaguar1/HalModule.h | 6 +- src/jaguar1/Iqk8812a.cpp | 2 +- src/jaguar1/Iqk8812a.h | 6 +- src/jaguar1/Iqk8814a.cpp | 2 +- src/jaguar1/Iqk8814a.h | 6 +- src/jaguar1/PhydmWatchdog.cpp | 2 +- src/jaguar1/PhydmWatchdog.h | 6 +- src/jaguar1/PowerTracking8812a.cpp | 2 +- src/jaguar1/PowerTracking8812a.h | 6 +- src/jaguar1/RadioManagementModule.cpp | 2 +- src/jaguar1/RadioManagementModule.h | 6 +- src/jaguar1/RtlJaguarDevice.cpp | 2 +- src/jaguar1/RtlJaguarDevice.h | 4 +- src/jaguar2/HalJaguar2.cpp | 4 +- src/jaguar2/HalJaguar2.h | 6 +- src/jaguar2/HalmacJaguar2Fw.cpp | 2 +- src/jaguar2/HalmacJaguar2Fw.h | 6 +- src/jaguar2/HalmacJaguar2MacInit.cpp | 4 +- src/jaguar2/HalmacJaguar2MacInit.h | 6 +- src/jaguar2/HalmacJaguar2Regs.h | 2 +- src/jaguar2/Halrf8821c.cpp | 4 +- src/jaguar2/Halrf8821c.h | 6 +- src/jaguar2/Halrf8822b.cpp | 6 +- src/jaguar2/Halrf8822b.h | 6 +- src/jaguar2/Jaguar2Calibration.h | 4 +- src/jaguar2/RtlJaguar2Device.cpp | 20 +- src/jaguar2/RtlJaguar2Device.h | 6 +- src/jaguar3/HalJaguar3.cpp | 6 +- src/jaguar3/HalJaguar3.h | 6 +- src/jaguar3/HalmacJaguar3Fw.cpp | 2 +- src/jaguar3/HalmacJaguar3Fw.h | 6 +- src/jaguar3/HalmacJaguar3MacInit.cpp | 2 +- src/jaguar3/HalmacJaguar3MacInit.h | 6 +- src/jaguar3/HalmacJaguar3Regs.h | 2 +- src/jaguar3/Halrf8822c.cpp | 6 +- src/jaguar3/Halrf8822c.h | 8 +- src/jaguar3/Halrf8822e.cpp | 4 +- src/jaguar3/Halrf8822e.h | 6 +- src/jaguar3/Jaguar3Calibration.h | 4 +- src/jaguar3/RadioManagementJaguar3.cpp | 2 +- src/jaguar3/RadioManagementJaguar3.h | 6 +- src/jaguar3/RtlJaguar3Device.cpp | 2 +- src/jaguar3/RtlJaguar3Device.h | 6 +- 72 files changed, 1285 insertions(+), 1147 deletions(-) create mode 100644 src/RtlAdapter.cpp create mode 100644 src/RtlAdapter.h create mode 100644 src/RtlTransport.h delete mode 100644 src/RtlUsbAdapter.cpp create mode 100644 src/UsbTransport.cpp create mode 100644 src/UsbTransport.h diff --git a/CLAUDE.md b/CLAUDE.md index 6811a12..2348f46 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,12 +42,14 @@ 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 (no interrupts). `RtlUsbAdapter` is bus-dual — an `_mmio` backend -dispatches register I/O and the bulk TX/RX entry points, so the ~165 HAL call -sites are untouched; 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, RCR -bits 11-13 cleared — on this MAC generation they are TA_BCN/RPFM_CAM, not the -Jaguar1 accept bits, and TA_BCN drops every beacon). Factory: +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 @@ -288,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. diff --git a/CMakeLists.txt b/CMakeLists.txt index 86ec2c2..0272b82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,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 diff --git a/examples/duplex/main.cpp b/examples/duplex/main.cpp index ce60514..a68b3b2 100644 --- a/examples/duplex/main.cpp +++ b/examples/duplex/main.cpp @@ -60,7 +60,7 @@ #include "RxPacket.h" #include "RadiotapBuilder.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #if defined(DEVOURER_HAVE_JAGUAR1) #include "jaguar1/RtlJaguarDevice.h" #endif diff --git a/examples/pcieprobe/main.cpp b/examples/pcieprobe/main.cpp index 359fe94..6e7bbfb 100644 --- a/examples/pcieprobe/main.cpp +++ b/examples/pcieprobe/main.cpp @@ -24,7 +24,7 @@ #include "Event.h" #include "PcieTransport.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "logger.h" #include "jaguar2/ChipVariant.h" @@ -51,7 +51,7 @@ int main(int argc, char **argv) { } /* ---- stage id (M0): pure MMIO register plane, no power, no DMA ---- */ - RtlUsbAdapter adapter(transport, logger, {}); + 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); diff --git a/examples/precoder/main.cpp b/examples/precoder/main.cpp index aa6cb8f..e94ea56 100644 --- a/examples/precoder/main.cpp +++ b/examples/precoder/main.cpp @@ -53,7 +53,7 @@ #include #endif -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "UsbOpen.h" #include "WiFiDriver.h" #include "env_config.h" diff --git a/examples/rx/main.cpp b/examples/rx/main.cpp index c2d9a6e..3f90351 100644 --- a/examples/rx/main.cpp +++ b/examples/rx/main.cpp @@ -18,7 +18,7 @@ #if defined(DEVOURER_HAVE_JAGUAR1) #include "jaguar1/RtlJaguarDevice.h" #endif -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SignalStop.h" #include "UsbOpen.h" #include "WiFiDriver.h" diff --git a/examples/streamtx/main.cpp b/examples/streamtx/main.cpp index 0be0d26..802a293 100644 --- a/examples/streamtx/main.cpp +++ b/examples/streamtx/main.cpp @@ -64,7 +64,7 @@ #endif #include "RadiotapBuilder.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #if defined(DEVOURER_HAVE_JAGUAR1) #include "jaguar1/RtlJaguarDevice.h" #endif diff --git a/examples/svctx/main.cpp b/examples/svctx/main.cpp index c711483..282185f 100644 --- a/examples/svctx/main.cpp +++ b/examples/svctx/main.cpp @@ -57,7 +57,7 @@ #endif #include "RadiotapBuilder.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "UsbOpen.h" #include "WiFiDriver.h" #include "env_config.h" diff --git a/examples/tx/main.cpp b/examples/tx/main.cpp index 68733a9..51dc4eb 100644 --- a/examples/tx/main.cpp +++ b/examples/tx/main.cpp @@ -40,7 +40,7 @@ #if defined(DEVOURER_HAVE_JAGUAR3) #include "jaguar3/RtlJaguar3Device.h" #endif -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SignalStop.h" #include "UsbOpen.h" #if defined(DEVOURER_HAVE_PCIE) @@ -405,7 +405,7 @@ int main(int argc, char **argv) { if (reads <= 20 || (reads % 100) == 0) { char hex[64 * 2 + 1] = {0}; /* Explicit template arg so MSVC's `windows.h` `min` macro doesn't - * mangle this — same pattern as RtlUsbAdapter.cpp:435. */ + * mangle this — same pattern as RtlAdapter.cpp:435. */ int hex_len = std::min(actual, 32); for (int k = 0; k < hex_len; ++k) { static const char hd[] = "0123456789abcdef"; diff --git a/src/BeamformingSounder.h b/src/BeamformingSounder.h index 17e3f20..002d5d2 100644 --- a/src/BeamformingSounder.h +++ b/src/BeamformingSounder.h @@ -31,7 +31,7 @@ #include -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" namespace devourer { namespace bf { @@ -95,7 +95,7 @@ constexpr BfeeConfig kBfeeJaguar23{ * Beamformee entry 0, P_AID = 0 (unassociated). The one per-generation byte is * the sounding-protocol control (0x718): 0xCB on Jaguar-1 * (hal_txbf_jaguar_enter), 0xDB on Jaguar-2/3 (hal_txbf_8822b_enter). */ -inline void arm_sounder(RtlUsbAdapter& dev, uint8_t snd_ptcl_ctrl = 0xCB) { +inline void arm_sounder(RtlAdapter& dev, uint8_t snd_ptcl_ctrl = 0xCB) { dev.rtw_write8(kSndPtclCtrl, snd_ptcl_ctrl); dev.rtw_write8(kSndNdpStandby, 0x50); dev.rtw_write16(kTxbfCtrl, 0x0000); /* P_AID = 0 */ @@ -109,7 +109,7 @@ inline void arm_sounder(RtlUsbAdapter& dev, uint8_t snd_ptcl_ctrl = 0xCB) { /* Beamformee side: arm the hardware CSI responder to reply to NDPA+NDP from * `beamformer_mac` (matched against the NDPA TA), no association required. * `cfg` selects the per-generation register recipe. */ -inline void arm_beamformee(RtlUsbAdapter& dev, const uint8_t beamformer_mac[6], +inline void arm_beamformee(RtlAdapter& dev, const uint8_t beamformer_mac[6], const BfeeConfig& cfg) { dev.rtw_write8(kSndPtclCtrl, cfg.snd_ptcl_ctrl); dev.rtw_write8(kSndNdpStandby, cfg.ndp_standby); @@ -145,7 +145,7 @@ inline void arm_beamformee(RtlUsbAdapter& dev, const uint8_t beamformer_mac[6], * into BFMER0_INFO). `rx_nss` = GET_RX_NSS (2 on the 2T2R 8822B/C/E). Registers * are family-shared, so this is generation-neutral like arm_sounder. Apply is * NOT enabled here — call apply_vmatrix() once a CBR has been ingested. */ -inline void arm_beamformer_entry(RtlUsbAdapter& dev, const uint8_t peer_mac[6], +inline void arm_beamformer_entry(RtlAdapter& dev, const uint8_t peer_mac[6], uint8_t rx_nss = 2, uint8_t g_id = 0) { for (uint16_t i = 0; i < 6; ++i) dev.rtw_write8(kBfmer0Info + i, peer_mac[i]); @@ -166,7 +166,7 @@ inline void arm_beamformer_entry(RtlUsbAdapter& dev, const uint8_t peer_mac[6], * +BIT10=40, +BIT11=80) + DIS_NDP_BFEN (BIT15). Enable ONLY after the WMAC has * ingested a valid CBR (bf_monitor_enable_txbf gates on csi_matrix_len>0) — * enabling with no V degrades the link. `bw`: 0=20, 1=40, 2=80. */ -inline void apply_vmatrix(RtlUsbAdapter& dev, bool enable, uint8_t bw) { +inline void apply_vmatrix(RtlAdapter& dev, bool enable, uint8_t bw) { uint32_t txbf = dev.rtw_read(kTxbfCtrl); txbf &= ~((1u << 9) | (1u << 10) | (1u << 11)); if (enable) { @@ -197,7 +197,7 @@ enum : uint16_t { * the over-the-air VHT Group ID Management handshake the vendor normally does. * Recipe from hal_txbf_8822b_enter() MU BFee branch (haltxbf8822b.c:484-598), * MU register index 0: gid_valid=0x7fe, user_position_l=0x111110, p_aid=0. */ -inline void arm_beamformee_mu(RtlUsbAdapter& dev, const uint8_t beamformer_mac[6], +inline void arm_beamformee_mu(RtlAdapter& dev, const uint8_t beamformer_mac[6], const BfeeConfig& cfg) { arm_beamformee(dev, beamformer_mac, cfg); /* SU responder base */ diff --git a/src/PcieTransport.cpp b/src/PcieTransport.cpp index d542c14..189fc85 100644 --- a/src/PcieTransport.cpp +++ b/src/PcieTransport.cpp @@ -18,6 +18,8 @@ #include +#include "Event.h" + namespace devourer { namespace { @@ -514,6 +516,51 @@ void PcieTransport::setup_trx_rings() { _rx.len, _rx.bd_iova); } +int PcieTransport::tx_sync(uint8_t ep, uint8_t *buf, size_t len, + int timeout_ms) { + (void)ep; /* USB addressing; the ring is chosen by descriptor QSEL */ + if (len < 48) { + _tx_failed.fetch_add(1, std::memory_order_relaxed); + return -1; + } + const uint8_t qsel = buf[5] & 0x1F; + int queue; + switch (qsel) { + case 0x10: /* QSEL_BEACON — rsvd page / DLFW */ + queue = Q_BCN; + break; + case 0x11: /* high */ + queue = Q_HI0; + break; + case 0x12: /* mgmt (the monitor-inject descriptor) */ + queue = Q_MGMT; + break; + case 0x13: /* h2c command */ + queue = Q_H2C; + break; + default: /* AC data */ + queue = Q_BE; + break; + } + _tx_submitted.fetch_add(1, std::memory_order_relaxed); + int rc = tx_submit_sync(queue, buf, len, timeout_ms); + if (rc < 0) { + _tx_failed.fetch_add(1, std::memory_order_relaxed); + _tx_last_rc.store(rc, std::memory_order_relaxed); + devourer::Ev(_logger->events(), "tx.fail").f("rc", rc).f("timeout", true); + } + return rc; +} + +devourer::TxStats PcieTransport::tx_stats() const { + devourer::TxStats s; + s.submitted = _tx_submitted.load(std::memory_order_relaxed); + s.failed = _tx_failed.load(std::memory_order_relaxed); + s.last_error_rc = _tx_last_rc.load(std::memory_order_relaxed); + s.last_was_timeout = s.failed != 0; + return s; +} + int PcieTransport::tx_submit_sync(int queue, const uint8_t *buf, size_t len, int timeout_ms) { if (queue < 0 || queue >= Q_MAX) diff --git a/src/PcieTransport.h b/src/PcieTransport.h index 8c5d7bb..2bae832 100644 --- a/src/PcieTransport.h +++ b/src/PcieTransport.h @@ -28,16 +28,18 @@ * DMA-coherent — no cache sync beyond compiler ordering (volatile BD access + * the strongly-ordered MMIO doorbell write). */ +#include #include #include #include #include +#include "RtlTransport.h" #include "logger.h" namespace devourer { -class PcieTransport { +class PcieTransport final : public IRtlTransport { public: /* TX queues, indexing _tx_rings. Order is fixed (ring register map). */ enum Queue : int { @@ -75,11 +77,48 @@ class PcieTransport { const Config &cfg); static std::shared_ptr Open(const std::string &bdf, Logger_t logger); - ~PcieTransport(); + ~PcieTransport() override; PcieTransport(const PcieTransport &) = delete; PcieTransport &operator=(const PcieTransport &) = delete; - /* ---- register plane (BAR2 MMIO) ---- */ + /* ---- IRtlTransport: register plane (BAR2 MMIO) ---- */ + bool is_usb() const override { return false; } + uint8_t read8(uint16_t reg) override { return guarded_read(reg); } + uint16_t read16(uint16_t reg) override { return guarded_read(reg); } + uint32_t read32(uint16_t reg) override { return guarded_read(reg); } + bool write8(uint16_t reg, uint8_t v) override { return guarded_write(reg, v); } + bool write16(uint16_t reg, uint16_t v) override { return guarded_write(reg, v); } + bool write32(uint16_t reg, uint32_t v) override { return guarded_write(reg, v); } + bool write_bytes(uint16_t reg, const uint8_t *p, size_t n) override { + /* MMIO burst: plain byte stores (the multi-byte users of this path — MAC + * address / key material — have no width side-effects). */ + for (size_t i = 0; i < n; i++) + if (!guarded_write(reg + static_cast(i), p[i])) + return false; + return true; + } + + /* ---- IRtlTransport: frame plane (88xx BD rings) ---- */ + /* The ring is chosen from the tx-descriptor QSEL at buf[5] bits [4:0] + * (identical position on every 88xx descriptor this library builds); the + * `ep` hint is USB addressing and ignored. QSEL_BEACON -> BCN ring is the + * DLFW rsvd-page path, exactly rtw88's write_data_rsvd_page -> + * RTW_TX_QUEUE_BCN mapping. */ + bool tx_async(uint8_t ep, uint8_t *buf, size_t len, + unsigned timeout_ms) override { + return tx_sync(ep, buf, len, static_cast(timeout_ms)) >= 0; + } + int tx_sync(uint8_t ep, uint8_t *buf, size_t len, int timeout_ms) override; + void rx_loop(int buf_size, int n_xfers, + const std::function &on_data, + const std::function &should_stop) override { + (void)buf_size; /* USB URB tuning; the ring depth is fixed at creation */ + (void)n_xfers; + rx_poll_loop(on_data, should_stop); + } + void hci_setup() override { setup_trx_rings(); } + TxStats tx_stats() const override; + volatile uint8_t *mmio() const { return _mmio; } size_t mmio_len() const { return _mmio_len; } @@ -126,6 +165,27 @@ class PcieTransport { *reinterpret_cast(_mmio + reg) = v; } + /* Register access with the USB-page guard: 0xFE00..0xFEFF is USB-only + * register space — undefined over MMIO. The jaguar users (0xFE5B/0xFE10/ + * 0xFE11) are is_usb()-gated; catch any stragglers instead of poking a + * hole in the BAR. */ + template T guarded_read(uint16_t reg) { + if (reg >= 0xFE00) { + _logger->warn("read(0x{:04x}) on PCIe: USB-page register, returning 0", + reg); + return 0; + } + return mr(reg); + } + template bool guarded_write(uint16_t reg, T v) { + if (reg >= 0xFE00) { + _logger->warn("write(0x{:04x}) on PCIe: USB-page register, dropped", reg); + return false; + } + mw(reg, v); + return true; + } + struct TxRing { volatile uint8_t *bd = nullptr; /* BD slots (16 B each) in the DMA slab */ uint64_t bd_iova = 0; @@ -161,6 +221,11 @@ class PcieTransport { uint8_t *_slab = nullptr; /* DMA slab VA (anonymous, VFIO-pinned) */ size_t _slab_len = 0; + /* TX submission counters (TxStats.h contract, like the USB transport). */ + std::atomic _tx_submitted{0}; + std::atomic _tx_failed{0}; + std::atomic _tx_last_rc{0}; + TxRing _tx[Q_MAX]; RxRing _rx; }; diff --git a/src/RtlAdapter.cpp b/src/RtlAdapter.cpp new file mode 100644 index 0000000..1c11b73 --- /dev/null +++ b/src/RtlAdapter.cpp @@ -0,0 +1,264 @@ +#include "RtlAdapter.h" + +#include +#include +#include + +#include "Hal8812PhyReg.h" +#include "UsbTransport.h" + +using namespace std::chrono_literals; + +RtlAdapter::RtlAdapter(libusb_device_handle *dev_handle, Logger_t logger, + libusb_context *ctx, + std::shared_ptr usb_lock, + const devourer::DeviceConfig &cfg) + : _transport{std::make_shared( + dev_handle, logger, ctx, std::move(usb_lock))}, + _logger{std::move(logger)} { + init_from_transport(cfg); +} + +RtlAdapter::RtlAdapter(std::shared_ptr transport, + Logger_t logger, const devourer::DeviceConfig &cfg) + : _transport{std::move(transport)}, _logger{std::move(logger)} { + init_from_transport(cfg); +} + +void RtlAdapter::init_from_transport(const devourer::DeviceConfig &cfg) { + _log_writes = cfg.debug.log_writes; + _tx_ep_override = cfg.tx.ep; + _tx_timeout_ms = cfg.tx.timeout_ms.value_or(500); + _usb = _transport->usb_info(); + + if (_usb.valid) { + if (_usb.speed > 3 /* > LIBUSB_SPEED_HIGH: USB 3.0 */) { + rxagg_usb_size = 0x3; /* 16KB */ + rxagg_usb_timeout = 0x01; + } else { + /* the setting to reduce RX FIFO overflow on USB2.0 and increase rx + * throughput */ + rxagg_usb_size = 0x1; /* 8KB */ + rxagg_usb_timeout = 0x01; + } + + /* Was GetChipOutEP8812: derive the TX queue selector from the number of + * discovered bulk-OUT pipes. */ + switch (_usb.bulk_out_eps.size()) { + case 4: + OutEpQueueSel = TX_SELE_HQ | TX_SELE_LQ | TX_SELE_NQ | TX_SELE_EQ; + OutEpNumber = 4; + break; + case 3: + OutEpQueueSel = TX_SELE_HQ | TX_SELE_LQ | TX_SELE_NQ; + OutEpNumber = 3; + break; + case 2: + OutEpQueueSel = TX_SELE_HQ | TX_SELE_NQ; + OutEpNumber = 2; + break; + case 1: + OutEpQueueSel = TX_SELE_HQ; + OutEpNumber = 1; + break; + default: + break; + } + _logger->info("OutEpQueueSel({}), OutEpNumber({})", (int)OutEpQueueSel, + (int)OutEpNumber); + } + + uint8_t eeValue = rtw_read8(REG_9346CR); + EepromOrEfuse = (eeValue & BOOT_FROM_EEPROM) != 0; + AutoloadFailFlag = (eeValue & EEPROM_EN) == 0; + + _logger->info("Boot from {}, Autoload {} !", + EepromOrEfuse ? "EEPROM" : "EFUSE", + (AutoloadFailFlag ? "Fail" : "OK")); +} + +bool RtlAdapter::send_packet(uint8_t *packet, size_t length) { + /* TX endpoint selection (USB semantics; the PCIe transport ignores it): + * DeviceConfig tx.ep override > first discovered OUT endpoint > historic + * 8812AU default (0x02). */ + const uint8_t tx_ep = _tx_ep_override ? *_tx_ep_override + : first_bulk_out_ep(); + return _transport->tx_async(tx_ep, packet, length, _tx_timeout_ms); +} + +void RtlAdapter::rtl8812au_hw_reset() { + uint32_t reg_val = 0; + + if ((rtw_read8(REG_MCUFWDL) & BIT7) != 0) { + _8051Reset8812(); + rtw_write8(REG_MCUFWDL, 0x00); + + /* before BB reset should do clock gated */ + rtw_write32(rFPGA0_XCD_RFPara, rtw_read32(rFPGA0_XCD_RFPara) | (BIT6)); + + /* reset BB */ + reg_val = rtw_read8(REG_SYS_FUNC_EN); + reg_val = (uint8_t)(reg_val & ~(BIT0 | BIT1)); + rtw_write8(REG_SYS_FUNC_EN, (uint8_t)reg_val); + + /* reset RF */ + rtw_write8(REG_RF_CTRL, 0); + + /* reset TRX path */ + rtw_write16(REG_CR, 0); + + /* reset MAC */ + reg_val = rtw_read8(REG_APS_FSMCO + 1); + reg_val |= BIT1; + rtw_write8(REG_APS_FSMCO + 1, + (uint8_t)reg_val); /* reg0x5[1] ,auto FSM off */ + + reg_val = rtw_read8(REG_APS_FSMCO + 1); + + /* check if reg0x5[1] auto cleared */ + while ((reg_val & BIT1) != 0) { + std::this_thread::sleep_for(1ms); + reg_val = rtw_read8(REG_APS_FSMCO + 1); + } + + reg_val |= BIT0; + rtw_write8(REG_APS_FSMCO + 1, + (uint8_t)reg_val); /* reg0x5[0] ,auto FSM on */ + + reg_val = rtw_read8(REG_SYS_FUNC_EN + 1); + reg_val = (uint8_t)(reg_val & ~(BIT4 | BIT7)); + rtw_write8(REG_SYS_FUNC_EN + 1, (uint8_t)reg_val); + reg_val = rtw_read8(REG_SYS_FUNC_EN + 1); + reg_val = (uint8_t)(reg_val | BIT4 | BIT7); + rtw_write8(REG_SYS_FUNC_EN + 1, (uint8_t)reg_val); + } +} + +void RtlAdapter::_8051Reset8812() { + uint8_t u1bTmp, u1bTmp2; + + /* Reset MCU IO Wrapper- sugggest by SD1-Gimmy */ + u1bTmp2 = rtw_read8(REG_RSV_CTRL); + rtw_write8(REG_RSV_CTRL, (uint8_t)(u1bTmp2 & (~BIT1))); + u1bTmp2 = rtw_read8(REG_RSV_CTRL + 1); + rtw_write8(REG_RSV_CTRL + 1, (uint8_t)(u1bTmp2 & (~BIT3))); + + u1bTmp = rtw_read8(REG_SYS_FUNC_EN + 1); + rtw_write8(REG_SYS_FUNC_EN + 1, (uint8_t)(u1bTmp & (~BIT2))); + + /* Enable MCU IO Wrapper */ + u1bTmp2 = rtw_read8(REG_RSV_CTRL); + rtw_write8(REG_RSV_CTRL, (uint8_t)(u1bTmp2 & (~BIT1))); + u1bTmp2 = rtw_read8(REG_RSV_CTRL + 1); + rtw_write8(REG_RSV_CTRL + 1, (uint8_t)(u1bTmp2 | (BIT3))); + + rtw_write8(REG_SYS_FUNC_EN + 1, (uint8_t)(u1bTmp | (BIT2))); + + _logger->info("=====> _8051Reset8812(): 8051 reset success ."); +} + +/* 11/16/2008 MH Read one byte from real Efuse. */ +uint8_t RtlAdapter::efuse_OneByteRead(uint16_t addr, uint8_t *data) { + u32 tmpidx = 0; + u8 bResult; + u8 readbyte; + + /* -----------------e-fuse reg ctrl --------------------------------- */ + /* address */ + rtw_write8(EFUSE_CTRL + 1, (u8)(addr & 0xff)); + rtw_write8(EFUSE_CTRL + 2, + ((u8)((addr >> 8) & 0x03)) | (rtw_read8(EFUSE_CTRL + 2) & 0xFC)); + + /* rtw_write8(pAdapter, EFUSE_CTRL+3, 0x72); */ /* read cmd */ + /* Write bit 32 0 */ + readbyte = rtw_read8(EFUSE_CTRL + 3); + rtw_write8(EFUSE_CTRL + 3, (readbyte & 0x7f)); + + while (!(0x80 & rtw_read8(EFUSE_CTRL + 3)) && (tmpidx < 1000)) { + std::this_thread::sleep_for(1ms); + tmpidx++; + } + if (tmpidx < 100) { + *data = rtw_read8(EFUSE_CTRL); + bResult = true; + } else { + *data = 0xff; + bResult = false; + _logger->error("addr=0x{:x} bResult={} time out 1s !!!", addr, bResult); + _logger->error("EFUSE_CTRL =0x{:08x} !!!", rtw_read32(EFUSE_CTRL)); + } + + return bResult; +} + +void RtlAdapter::ReadEFuseByte(uint16_t _offset, uint8_t *pbuf) { + uint32_t value32; + uint8_t readbyte; + uint16_t retry; + + /* Match the kernel `88XXau` driver's per-iteration EFUSE_TEST clear. + * Cold-init usbmon diff (2026-05-28, devourer-testrig VM kernel-side + * vs host devourer-side) shows the kernel does an RD-then-WR sequence + * at REG_EFUSE_TEST (0x0034) = 0x0000 (16-bit) BEFORE every EFUSE byte + * read, 312 times per init; devourer never touched 0x0034. We mirror + * the sequence so the EFUSE state machine sees identical wire shape + * across all 312 byte reads. Empirically harmless on its own (does + * NOT fix the RTL8814AU TX-on-air gate per a sniffer run with this + * patch + bulk-IN drainer enabled) but removes a known concrete + * wire-level divergence flagged by tools/usbmon_pcap_diff.py. */ + (void)rtw_read16(REG_EFUSE_TEST); + rtw_write16(REG_EFUSE_TEST, 0x0000); + + /* Write Address */ + rtw_write8(EFUSE_CTRL + 1, (uint8_t)(_offset & 0xff)); + readbyte = rtw_read8(EFUSE_CTRL + 2); + rtw_write8(EFUSE_CTRL + 2, + (uint8_t)(((_offset >> 8) & 0x03) | (readbyte & 0xfc))); + + /* Write bit 32 0 */ + readbyte = rtw_read8(EFUSE_CTRL + 3); + rtw_write8(EFUSE_CTRL + 3, (uint8_t)(readbyte & 0x7f)); + + /* Check bit 32 read-ready */ + retry = 0; + value32 = rtw_read32(EFUSE_CTRL); + /* while(!(((value32 >> 24) & 0xff) & 0x80) && (retry<10)) */ + while ((((value32 >> 24) & 0xff) & 0x80) == 0 && (retry < 10000)) { + value32 = rtw_read32(EFUSE_CTRL); + retry++; + } + + /* 20100205 Joseph: Add delay suggested by SD1 Victor. */ + /* This fix the problem that Efuse read error in high temperature condition. + */ + /* Designer says that there shall be some delay after ready bit is set, or the + */ + /* result will always stay on last data we read. */ + + // TODO: decide to we really need it? + // std::this_thread::sleep_for(50ms); + value32 = rtw_read32(EFUSE_CTRL); + + pbuf[0] = (uint8_t)(value32 & 0xff); +} + +void RtlAdapter::phy_set_bb_reg(uint16_t regAddr, uint32_t bitMask, + uint32_t data) { + PHY_SetBBReg8812(regAddr, bitMask, data); +} + +void RtlAdapter::PHY_SetBBReg8812(uint16_t regAddr, uint32_t bitMask, + uint32_t dataOriginal) { + uint32_t data = dataOriginal; + if (bitMask != bMaskDWord) { + /* if not "double word" write */ + auto OriginalValue = rtw_read32(regAddr); + auto BitShift = PHY_CalculateBitShift(bitMask); + data = ((OriginalValue) & (~bitMask)) | + (((dataOriginal << (int)BitShift)) & bitMask); + } + + rtw_write32(regAddr, data); + + /* RTW_INFO("BBW MASK=0x%x Addr[0x%x]=0x%x\n", BitMask, RegAddr, Data); */ +} diff --git a/src/RtlAdapter.h b/src/RtlAdapter.h new file mode 100644 index 0000000..15707fe --- /dev/null +++ b/src/RtlAdapter.h @@ -0,0 +1,180 @@ +#ifndef RTLADAPTER_H +#define RTLADAPTER_H + +/* RtlAdapter — the bus-neutral register/frame accessor every HAL component + * holds BY VALUE (a copyable value type; every copy shares the one transport + * via shared_ptr, so the transport — and with it the USB device lock or the + * vfio fds + DMA rings — lives until the last copy dies). + * + * The bus specifics live behind IRtlTransport (src/RtlTransport.h): USB = + * devourer::UsbTransport (libusb vendor-control registers + bulk endpoints), + * PCIe = devourer::PcieTransport (BAR2 MMIO registers + 88xx DMA rings). The + * adapter itself carries only bus-neutral chip helpers built on the register + * plane (EFUSE byte reads, phy_set_bb_reg, the 8812 resets) plus the + * config-derived knobs, and forwards the frame plane. + * + * (Historically this class WAS the USB transport; the old `RtlUsbAdapter` + * name remains as a deprecated alias in RtlUsbAdapter.h.) */ + +#include +#include +#include +#include + +#include "DeviceConfig.h" +#include "RtlTransport.h" +#include "TxStats.h" +#include "drv_types.h" +#include "hal_com_reg.h" +#include "logger.h" + +struct libusb_device_handle; +struct libusb_context; +namespace devourer { +class UsbDeviceLock; +} + +#define rtw_read8 rtw_read +#define rtw_read16 rtw_read +#define rtw_read32 rtw_read + +#define rtw_write8 rtw_write +#define rtw_write16 rtw_write +#define rtw_write32 rtw_write + +enum TxSele { + TX_SELE_HQ = 1 << (0), /* High Queue */ + TX_SELE_LQ = 1 << (1), /* Low Queue */ + TX_SELE_NQ = 1 << (2), /* Normal Queue */ + TX_SELE_EQ = 1 << (3), /* Extern Queue */ +}; + +class RtlAdapter { + std::shared_ptr _transport; + Logger_t _logger; + + /* USB-descriptor-derived facts (defaults on PCIe), mirrored at construction + * for the HALs that consume them (Jaguar1 queue mapping, RX-agg sizing). */ + devourer::UsbLinkInfo _usb; + + /* From DeviceConfig (see there for semantics). Plain values — RtlAdapter + * is a copyable value type, every copy carries them. */ + bool _log_writes = false; /* debug.log_writes */ + std::optional _tx_ep_override; /* tx.ep */ + unsigned _tx_timeout_ms = 500; /* tx.timeout_ms */ + +public: + /* USB: wraps the handle in a devourer::UsbTransport. The signature is the + * historic one so USB construction sites are unchanged. */ + RtlAdapter(libusb_device_handle *dev_handle, Logger_t logger, + libusb_context *ctx = nullptr, + std::shared_ptr usb_lock = nullptr, + const devourer::DeviceConfig &cfg = {}); + /* Any transport (the PCIe factory path; also the seam for future buses). */ + RtlAdapter(std::shared_ptr transport, + Logger_t logger, const devourer::DeviceConfig &cfg = {}); + + bool is_usb() const { return _transport->is_usb(); } + /* Pre-power-on HCI programming (rtw88 rtw_hci_setup slot): PCIe TRX ring + * registers; no-op on USB. Call per bring-up attempt, before power-on. */ + void hci_setup() { _transport->hci_setup(); } + + /* Kernel-style async RX: keep n_urbs concurrent bulk-IN transfers in flight + * (USB) or reap the RX buffer-descriptor ring (PCIe), invoking + * on_data(buf,len) per delivery until `stop`. */ + void bulk_read_async_loop(int buf_size, int n_urbs, + const std::function &on_data, + const std::function &should_stop) { + _transport->rx_loop(buf_size, n_urbs, on_data, should_stop); + } + /* Convenience overload for callers whose stop condition is a single flag. */ + void bulk_read_async_loop(int buf_size, int n_urbs, + const std::function &on_data, + const volatile bool &stop) { + bulk_read_async_loop(buf_size, n_urbs, on_data, + [&stop]() -> bool { return stop; }); + } + + uint16_t idVendor() const { return _usb.vid; } + uint16_t idProduct() const { return _usb.pid; } + int speed() const { return _usb.speed; } + /* First discovered bulk-OUT endpoint (HQ-equivalent), or 0x02 if none were + * discovered. Used by the DLFW rsvd-page download; ignored by the PCIe + * transport (its ring is chosen from the descriptor QSEL). */ + uint8_t first_bulk_out_ep() const { + return _usb.bulk_out_eps.empty() ? 0x02 : _usb.bulk_out_eps[0]; + } + + bool AutoloadFailFlag = false; + bool EepromOrEfuse = false; + uint8_t OutEpQueueSel = 0; + uint8_t OutEpNumber = 0; + uint8_t rxagg_usb_size = 0; + uint8_t rxagg_usb_timeout = 0; + + bool send_packet(uint8_t *packet, size_t length); + /* Synchronous TX that blocks until completion or timeout. Returns bytes + * submitted, or negative on error. */ + int bulk_send_sync(uint8_t *packet, size_t length, int timeout_ms) { + return bulk_send_sync_ep(0x02, packet, length, timeout_ms); + } + int bulk_send_sync_ep(uint8_t ep, uint8_t *packet, size_t length, + int timeout_ms) { + return _transport->tx_sync(ep, packet, length, timeout_ms); + } + void bulk_clear_halt(uint8_t ep) { _transport->clear_halt(ep); } + + /* Snapshot of the TX submission counters (see TxStats.h). */ + devourer::TxStats GetTxStats() const { return _transport->tx_stats(); } + + /* Raw single-shot RX read (USB bulk-IN; -1 on PCIe — its RX is the ring + * reap in bulk_read_async_loop). */ + int bulk_read_raw(uint8_t *buf, int len, int timeout_ms) { + return _transport->rx_raw(buf, len, timeout_ms); + } + + uint8_t efuse_OneByteRead(uint16_t addr, uint8_t *data); + void phy_set_bb_reg(uint16_t regAddr, uint32_t bitMask, uint32_t data); + + template T rtw_read(uint16_t reg_num) { + if constexpr (sizeof(T) == 1) + return _transport->read8(reg_num); + else if constexpr (sizeof(T) == 2) + return _transport->read16(reg_num); + else + return _transport->read32(reg_num); + } + + template bool rtw_write(uint16_t reg_num, T value) { + /* debug.log_writes: emit every register write as a debug.wreg event + * (addr/width/val mirror tests/decode_wseq.py's tuple) so devourer's + * bring-up write set can be diffed against the kernel golden — on either + * bus. */ + if (_log_writes) + devourer::Ev(_logger->events(), "debug.wreg") + .hexf("addr", reg_num, 4) + .f("width", sizeof(T)) + .hexf("val", (unsigned long long)value, (int)(sizeof(T) * 2)); + if constexpr (sizeof(T) == 1) + return _transport->write8(reg_num, value); + else if constexpr (sizeof(T) == 2) + return _transport->write16(reg_num, value); + else + return _transport->write32(reg_num, value); + } + + bool WriteBytes(uint16_t reg_num, uint8_t *ptr, size_t size) { + return _transport->write_bytes(reg_num, ptr, size); + } + + void rtl8812au_hw_reset(); + void _8051Reset8812(); + void ReadEFuseByte(uint16_t _offset, uint8_t *pbuf); + +private: + void init_from_transport(const devourer::DeviceConfig &cfg); + void PHY_SetBBReg8812(uint16_t regAddr, uint32_t bitMask, + uint32_t dataOriginal); +}; + +#endif /* RTLADAPTER_H */ diff --git a/src/RtlTransport.h b/src/RtlTransport.h new file mode 100644 index 0000000..64388d7 --- /dev/null +++ b/src/RtlTransport.h @@ -0,0 +1,88 @@ +#pragma once + +/* IRtlTransport — the bus seam. USB (libusb) and PCIe (vfio) are independent + * transports implementing this one interface; RtlAdapter (the copyable value + * type every HAL holds) owns a shared_ptr to one of them and forwards. Nothing + * here depends on libusb or vfio. + * + * Two planes, mirroring how the chips are built: + * - register plane: 8/16/32-bit accesses into the 0x0000..0xFFFF MAC/BB + * register space (USB: vendor control transfers; PCIe: BAR2 MMIO). + * - frame plane: TX submissions and the blocking RX delivery loop + * (USB: bulk endpoints; PCIe: 88xx buffer-descriptor DMA rings). + * + * hci_setup() is the rtw88-style pre-power hook: programming that must happen + * before the power-on sequence each bring-up attempt (PCIe TRX ring + * registers; nothing on USB). */ + +#include +#include +#include +#include + +#include "TxStats.h" + +namespace devourer { + +/* USB-descriptor-derived link facts consumed by the HALs (queue mapping, + * RX-aggregation sizing). A PCIe transport returns the defaults. */ +/* libusb_speed numeric values, mirrored so HAL code comparing link speed does + * not need libusb.h (the whole point of the transport seam). */ +constexpr int kUsbSpeedHigh = 3; /* LIBUSB_SPEED_HIGH (480 MBit/s) */ +constexpr int kUsbSpeedSuper = 4; /* LIBUSB_SPEED_SUPER (5 GBit/s) */ + +struct UsbLinkInfo { + bool valid = false; /* true only on the USB transport */ + int speed = 0; /* libusb_speed numeric value */ + uint16_t vid = 0, pid = 0; + uint8_t bulk_in_ep = 0x81; + std::vector bulk_out_eps; /* descriptor order */ +}; + +class IRtlTransport { +public: + virtual ~IRtlTransport() = default; + + virtual bool is_usb() const = 0; + + /* ---- register plane ---- */ + virtual uint8_t read8(uint16_t reg) = 0; + virtual uint16_t read16(uint16_t reg) = 0; + virtual uint32_t read32(uint16_t reg) = 0; + virtual bool write8(uint16_t reg, uint8_t v) = 0; + virtual bool write16(uint16_t reg, uint16_t v) = 0; + virtual bool write32(uint16_t reg, uint32_t v) = 0; + virtual bool write_bytes(uint16_t reg, const uint8_t *p, size_t n) = 0; + + /* ---- frame plane ---- */ + /* Fire-and-forget data TX (the send_packet hot path). `ep` is the USB + * bulk-OUT endpoint choice; the PCIe transport ignores it (the ring is + * chosen from the descriptor's QSEL). */ + virtual bool tx_async(uint8_t ep, uint8_t *buf, size_t len, + unsigned timeout_ms) = 0; + /* Synchronous TX that blocks until the transport confirms consumption + * (USB: bulk completion; PCIe: hardware read-pointer / BCN kick). Returns + * bytes submitted or a negative error. */ + virtual int tx_sync(uint8_t ep, uint8_t *buf, size_t len, int timeout_ms) = 0; + /* Blocking RX delivery loop until should_stop(). buf_size/n_xfers are USB + * URB-queue tuning; the PCIe ring depth is fixed at transport creation. */ + virtual void rx_loop(int buf_size, int n_xfers, + const std::function &on_data, + const std::function &should_stop) = 0; + /* Single-shot raw RX read (USB bulk-IN); unsupported (-1) on PCIe. */ + virtual int rx_raw(uint8_t *buf, int len, int timeout_ms) { + (void)buf; (void)len; (void)timeout_ms; + return -1; + } + virtual void clear_halt(uint8_t ep) { (void)ep; } + + /* ---- lifecycle / info ---- */ + /* Pre-power-on HCI programming, re-run per bring-up attempt (rtw88's + * rtw_hci_setup slot: PCIe TRX buffer-descriptor ring registers; no-op on + * USB). */ + virtual void hci_setup() {} + virtual UsbLinkInfo usb_info() const { return {}; } + virtual TxStats tx_stats() const { return {}; } +}; + +} /* namespace devourer */ diff --git a/src/RtlUsbAdapter.cpp b/src/RtlUsbAdapter.cpp deleted file mode 100644 index 07f7bc0..0000000 --- a/src/RtlUsbAdapter.cpp +++ /dev/null @@ -1,696 +0,0 @@ -#include "RtlUsbAdapter.h" - -#include -#include -#include -#if defined(__ANDROID__) || defined(_MSC_VER) || defined(__APPLE__) -#include -#else -#include -#endif -#include "Hal8812PhyReg.h" -#include "logger.h" -#include -#include -#include - -#if defined(DEVOURER_HAVE_PCIE) -#include "PcieTransport.h" -#endif - -using namespace std::chrono_literals; - -namespace { -/* Shared state for the async RX URB queue. */ -struct AsyncRxShared { - const std::function *cb; - const std::function *stop; - /* Atomic: in co-running (TX + self-capture RX) mode both the RX loop's own - * event pump and the TX event loop may run this callback, so `active` is - * written from the pump thread while the loop below reads it. */ - std::atomic active{0}; -}; -extern "C" void LIBUSB_CALL devourer_rx_cb(libusb_transfer *t) { - auto *s = static_cast(t->user_data); - if (t->status == LIBUSB_TRANSFER_COMPLETED && t->actual_length > 0) - (*s->cb)(t->buffer, t->actual_length); - bool resubmit = !(*s->stop)() && (t->status == LIBUSB_TRANSFER_COMPLETED || - t->status == LIBUSB_TRANSFER_TIMED_OUT); - if (resubmit && libusb_submit_transfer(t) == 0) - return; - s->active--; /* not resubmitted -> this URB is done */ -} -} // namespace - -void RtlUsbAdapter::bulk_read_async_loop( - int buf_size, int n_urbs, - const std::function &on_data, - const std::function &should_stop) { -#if defined(DEVOURER_HAVE_PCIE) - if (_pcie) { - /* PCIe: poll/reap the RX buffer-descriptor ring. One MPDU per on_data - * call (no USB aggregation); buf_size/n_urbs are USB tuning knobs and do - * not apply (ring depth is fixed at transport creation). */ - (void)buf_size; - (void)n_urbs; - _pcie->rx_poll_loop(on_data, should_stop); - return; - } -#endif - AsyncRxShared sh{&on_data, &should_stop}; - std::vector xfers; - std::vector> bufs(n_urbs, - std::vector(buf_size)); - for (int i = 0; i < n_urbs; i++) { - libusb_transfer *t = libusb_alloc_transfer(0); - /* timeout=0 (infinite): a persistent RX ring — each URB stays posted until - * a frame arrives (COMPLETED), the queue is torn down (CANCELLED, below), - * or the device errors. This is the kernel rtw88 RX-URB idiom. A finite - * timeout here would fire once per idle interval on a quiet channel, and - * libusb's darwin backend logs every LIBUSB_TRANSFER_TIMED_OUT at WARNING - * level — a continuous "transfer error: timed out" flood that carries no - * information (RX is healthy; bulk-IN is simply idle) and can bloat a long - * capture's stderr. The devourer_rx_cb resubmit-on-TIMED_OUT branch is kept - * as a defensive no-op should a backend still surface a timeout. */ - libusb_fill_bulk_transfer(t, _dev_handle, _bulk_in_ep, bufs[i].data(), - buf_size, devourer_rx_cb, &sh, 0); - if (libusb_submit_transfer(t) == 0) { - xfers.push_back(t); - sh.active++; - } else { - libusb_free_transfer(t); - } - } - _logger->info("RX: async queue of {} URBs submitted", sh.active.load()); - while (!should_stop() && sh.active > 0) { - struct timeval tv {0, 100000}; - libusb_handle_events_timeout_completed(_ctx, &tv, nullptr); - } - for (auto *t : xfers) - libusb_cancel_transfer(t); - while (sh.active > 0) { - struct timeval tv {0, 100000}; - libusb_handle_events_timeout_completed(_ctx, &tv, nullptr); - } - for (auto *t : xfers) - libusb_free_transfer(t); -} - -RtlUsbAdapter::RtlUsbAdapter(libusb_device_handle *dev_handle, Logger_t logger, - libusb_context *ctx, - std::shared_ptr usb_lock, - const devourer::DeviceConfig &cfg) - : _dev_handle{dev_handle}, _ctx{ctx}, _logger{logger}, - _log_writes{cfg.debug.log_writes}, _tx_ep_override{cfg.tx.ep}, - _tx_timeout_ms{cfg.tx.timeout_ms.value_or(USB_TIMEOUT)}, - _usb_lock{std::move(usb_lock)} { - libusb_device_descriptor desc{}; - if (libusb_get_device_descriptor(libusb_get_device(_dev_handle), &desc) == - LIBUSB_SUCCESS) { - _idVendor = desc.idVendor; - _idProduct = desc.idProduct; - _logger->info("USB device {:04x}:{:04x}", _idVendor, _idProduct); - } - - InitDvObj(); - - if (usbSpeed > LIBUSB_SPEED_HIGH) // USB 3.0 - { - rxagg_usb_size = 0x3; // 16KB - rxagg_usb_timeout = 0x01; - } else { - /* the setting to reduce RX FIFO overflow on USB2.0 and increase rx - * throughput */ - rxagg_usb_size = 0x1; // 8KB - rxagg_usb_timeout = 0x01; - } - - GetChipOutEP8812(); - - uint8_t eeValue = rtw_read8(REG_9346CR); - EepromOrEfuse = (eeValue & BOOT_FROM_EEPROM) != 0; - AutoloadFailFlag = (eeValue & EEPROM_EN) == 0; - - _logger->info("Boot from {}, Autoload {} !", - EepromOrEfuse ? "EEPROM" : "EFUSE", - (AutoloadFailFlag ? "Fail" : "OK")); -} - -#if defined(DEVOURER_HAVE_PCIE) -RtlUsbAdapter::RtlUsbAdapter(std::shared_ptr transport, - Logger_t logger, - const devourer::DeviceConfig &cfg) - : _logger{logger}, _log_writes{cfg.debug.log_writes}, - _tx_timeout_ms{cfg.tx.timeout_ms.value_or(USB_TIMEOUT)}, - _mmio{transport->mmio()}, _pcie{std::move(transport)} { - /* No USB descriptor walk / endpoint discovery — the endpoint members stay - * at defaults and are only consumed by USB-gated code paths. */ - _logger->info("PCIe device {} (BAR2 MMIO register backend)", _pcie->bdf()); - - uint8_t eeValue = rtw_read8(REG_9346CR); - EepromOrEfuse = (eeValue & BOOT_FROM_EEPROM) != 0; - AutoloadFailFlag = (eeValue & EEPROM_EN) == 0; - _logger->info("Boot from {}, Autoload {} !", - EepromOrEfuse ? "EEPROM" : "EFUSE", - (AutoloadFailFlag ? "Fail" : "OK")); -} - -int RtlUsbAdapter::pcie_tx_dispatch(uint8_t *packet, size_t length, - int timeout_ms) { - /* QSEL lives in tx-desc dword1 bits [12:8] = byte 5 bits [4:0] on every - * 88xx descriptor this library builds (8822B/8822C layouts agree). */ - if (length < 48) - return -1; - const uint8_t qsel = packet[5] & 0x1F; - int queue; - switch (qsel) { - case 0x10: /* QSEL_BEACON — rsvd page / DLFW */ - queue = devourer::PcieTransport::Q_BCN; - break; - case 0x11: /* high */ - queue = devourer::PcieTransport::Q_HI0; - break; - case 0x12: /* mgmt (the monitor-inject descriptor) */ - queue = devourer::PcieTransport::Q_MGMT; - break; - case 0x13: /* h2c command */ - queue = devourer::PcieTransport::Q_H2C; - break; - default: /* AC data */ - queue = devourer::PcieTransport::Q_BE; - break; - } - int rc = _pcie->tx_submit_sync(queue, packet, length, timeout_ms); - _tx_stats->submitted.fetch_add(1, std::memory_order_relaxed); - if (rc < 0) { - _tx_stats->failed.fetch_add(1, std::memory_order_relaxed); - _tx_stats->last_rc.store(rc, std::memory_order_relaxed); - _tx_stats->last_timeout.store(true, std::memory_order_relaxed); - devourer::Ev(_logger->events(), "tx.fail").f("rc", rc).f("timeout", true); - } - return rc; -} -#endif /* DEVOURER_HAVE_PCIE */ - -/* -$ lsusb -v -d 0bda:8812 - Endpoint Descriptor: - bLength 7 - bDescriptorType 5 - bEndpointAddress 0x81 EP 1 IN - bmAttributes 2 - Transfer Type Bulk - Synch Type None - Usage Type Data - wMaxPacketSize 0x0200 1x 512 bytes - bInterval 0 -*/ - -bool RtlUsbAdapter::WriteBytes(uint16_t reg_num, uint8_t *ptr, size_t size) { - if (_mmio) { - /* MMIO burst: plain byte stores (register space has no width side-effects - * for the multi-byte users of this path — MAC address / key material). */ - for (size_t i = 0; i < size; i++) - *reinterpret_cast(_mmio + reg_num + i) = ptr[i]; - return true; - } - if (libusb_control_transfer(_dev_handle, REALTEK_USB_VENQT_WRITE, 5, reg_num, - 0, ptr, size, USB_TIMEOUT) == size) { - return true; - } - return false; -} - -void RtlUsbAdapter::rtl8812au_hw_reset() { - uint32_t reg_val = 0; - - if ((rtw_read8(REG_MCUFWDL) & BIT7) != 0) { - _8051Reset8812(); - rtw_write8(REG_MCUFWDL, 0x00); - - /* before BB reset should do clock gated */ - rtw_write32(rFPGA0_XCD_RFPara, rtw_read32(rFPGA0_XCD_RFPara) | (BIT6)); - - /* reset BB */ - reg_val = rtw_read8(REG_SYS_FUNC_EN); - reg_val = (uint8_t)(reg_val & ~(BIT0 | BIT1)); - rtw_write8(REG_SYS_FUNC_EN, (uint8_t)reg_val); - - /* reset RF */ - rtw_write8(REG_RF_CTRL, 0); - - /* reset TRX path */ - rtw_write16(REG_CR, 0); - - /* reset MAC */ - reg_val = rtw_read8(REG_APS_FSMCO + 1); - reg_val |= BIT1; - rtw_write8(REG_APS_FSMCO + 1, - (uint8_t)reg_val); /* reg0x5[1] ,auto FSM off */ - - reg_val = rtw_read8(REG_APS_FSMCO + 1); - - /* check if reg0x5[1] auto cleared */ - while ((reg_val & BIT1) != 0) { - std::this_thread::sleep_for(1ms); - reg_val = rtw_read8(REG_APS_FSMCO + 1); - } - - reg_val |= BIT0; - rtw_write8(REG_APS_FSMCO + 1, - (uint8_t)reg_val); /* reg0x5[0] ,auto FSM on */ - - reg_val = rtw_read8(REG_SYS_FUNC_EN + 1); - reg_val = (uint8_t)(reg_val & ~(BIT4 | BIT7)); - rtw_write8(REG_SYS_FUNC_EN + 1, (uint8_t)reg_val); - reg_val = rtw_read8(REG_SYS_FUNC_EN + 1); - reg_val = (uint8_t)(reg_val | BIT4 | BIT7); - rtw_write8(REG_SYS_FUNC_EN + 1, (uint8_t)reg_val); - } -} - -void RtlUsbAdapter::_8051Reset8812() { - uint8_t u1bTmp, u1bTmp2; - - /* Reset MCU IO Wrapper- sugggest by SD1-Gimmy */ - u1bTmp2 = rtw_read8(REG_RSV_CTRL); - rtw_write8(REG_RSV_CTRL, (uint8_t)(u1bTmp2 & (~BIT1))); - u1bTmp2 = rtw_read8(REG_RSV_CTRL + 1); - rtw_write8(REG_RSV_CTRL + 1, (uint8_t)(u1bTmp2 & (~BIT3))); - - u1bTmp = rtw_read8(REG_SYS_FUNC_EN + 1); - rtw_write8(REG_SYS_FUNC_EN + 1, (uint8_t)(u1bTmp & (~BIT2))); - - /* Enable MCU IO Wrapper */ - u1bTmp2 = rtw_read8(REG_RSV_CTRL); - rtw_write8(REG_RSV_CTRL, (uint8_t)(u1bTmp2 & (~BIT1))); - u1bTmp2 = rtw_read8(REG_RSV_CTRL + 1); - rtw_write8(REG_RSV_CTRL + 1, (uint8_t)(u1bTmp2 | (BIT3))); - - rtw_write8(REG_SYS_FUNC_EN + 1, (uint8_t)(u1bTmp | (BIT2))); - - _logger->info("=====> _8051Reset8812(): 8051 reset success ."); -} - -/* 11/16/2008 MH Read one byte from real Efuse. */ -uint8_t RtlUsbAdapter::efuse_OneByteRead(uint16_t addr, uint8_t *data) { - u32 tmpidx = 0; - u8 bResult; - u8 readbyte; - - /* -----------------e-fuse reg ctrl --------------------------------- */ - /* address */ - rtw_write8(EFUSE_CTRL + 1, (u8)(addr & 0xff)); - rtw_write8(EFUSE_CTRL + 2, - ((u8)((addr >> 8) & 0x03)) | (rtw_read8(EFUSE_CTRL + 2) & 0xFC)); - - /* rtw_write8(pAdapter, EFUSE_CTRL+3, 0x72); */ /* read cmd */ - /* Write bit 32 0 */ - readbyte = rtw_read8(EFUSE_CTRL + 3); - rtw_write8(EFUSE_CTRL + 3, (readbyte & 0x7f)); - - while (!(0x80 & rtw_read8(EFUSE_CTRL + 3)) && (tmpidx < 1000)) { - std::this_thread::sleep_for(1ms); - tmpidx++; - } - if (tmpidx < 100) { - *data = rtw_read8(EFUSE_CTRL); - bResult = true; - } else { - *data = 0xff; - bResult = false; - _logger->error("addr=0x{:x} bResult={} time out 1s !!!", addr, bResult); - _logger->error("EFUSE_CTRL =0x{:08x} !!!", rtw_read32(EFUSE_CTRL)); - } - - return bResult; -} - -void RtlUsbAdapter::ReadEFuseByte(uint16_t _offset, uint8_t *pbuf) { - uint32_t value32; - uint8_t readbyte; - uint16_t retry; - - /* Match the kernel `88XXau` driver's per-iteration EFUSE_TEST clear. - * Cold-init usbmon diff (2026-05-28, devourer-testrig VM kernel-side - * vs host devourer-side) shows the kernel does an RD-then-WR sequence - * at REG_EFUSE_TEST (0x0034) = 0x0000 (16-bit) BEFORE every EFUSE byte - * read, 312 times per init; devourer never touched 0x0034. We mirror - * the sequence so the EFUSE state machine sees identical wire shape - * across all 312 byte reads. Empirically harmless on its own (does - * NOT fix the RTL8814AU TX-on-air gate per a sniffer run with this - * patch + bulk-IN drainer enabled) but removes a known concrete - * wire-level divergence flagged by tools/usbmon_pcap_diff.py. */ - (void)rtw_read16(REG_EFUSE_TEST); - rtw_write16(REG_EFUSE_TEST, 0x0000); - - /* Write Address */ - rtw_write8(EFUSE_CTRL + 1, (uint8_t)(_offset & 0xff)); - readbyte = rtw_read8(EFUSE_CTRL + 2); - rtw_write8(EFUSE_CTRL + 2, - (uint8_t)(((_offset >> 8) & 0x03) | (readbyte & 0xfc))); - - /* Write bit 32 0 */ - readbyte = rtw_read8(EFUSE_CTRL + 3); - rtw_write8(EFUSE_CTRL + 3, (uint8_t)(readbyte & 0x7f)); - - /* Check bit 32 read-ready */ - retry = 0; - value32 = rtw_read32(EFUSE_CTRL); - /* while(!(((value32 >> 24) & 0xff) & 0x80) && (retry<10)) */ - while ((((value32 >> 24) & 0xff) & 0x80) == 0 && (retry < 10000)) { - value32 = rtw_read32(EFUSE_CTRL); - retry++; - } - - /* 20100205 Joseph: Add delay suggested by SD1 Victor. */ - /* This fix the problem that Efuse read error in high temperature condition. - */ - /* Designer says that there shall be some delay after ready bit is set, or the - */ - /* result will always stay on last data we read. */ - - // TODO: decide to we really need it? - // std::this_thread::sleep_for(50ms); - value32 = rtw_read32(EFUSE_CTRL); - - pbuf[0] = (uint8_t)(value32 & 0xff); -} - -const char *RtlUsbAdapter::strUsbSpeed() { - switch (usbSpeed) { - case LIBUSB_SPEED_UNKNOWN: - return "UNKNOWN"; - case LIBUSB_SPEED_LOW: - return "1.5MBit/s"; - case LIBUSB_SPEED_FULL: - return "12MBit/s"; - case LIBUSB_SPEED_HIGH: - return "480MBit/s"; - case LIBUSB_SPEED_SUPER: - return "5000MBit/s"; - case LIBUSB_SPEED_SUPER_PLUS: - return "10000MBit/s"; - default: - return NULL; - } -} - -void RtlUsbAdapter::InitDvObj() { - libusb_device *dev = libusb_get_device(_dev_handle); - usbSpeed = (enum libusb_speed)libusb_get_device_speed(dev); - _logger->info("Running USB bus at {}", strUsbSpeed()); - - libusb_device_descriptor desc; - int ret = libusb_get_device_descriptor(dev, &desc); - if (ret < 0) { - return; - } - - for (uint8_t k = 0; k < desc.bNumConfigurations; k++) { - libusb_config_descriptor *config; - ret = libusb_get_config_descriptor(dev, k, &config); - if (LIBUSB_SUCCESS != ret) { - continue; - } - - if (!config->bNumInterfaces) { - continue; - } - const libusb_interface *interface = &config->interface[0]; - - if (!interface->altsetting) { - continue; - } - const libusb_interface_descriptor *interface_desc = - &interface->altsetting[0]; - - bool found_bulk_in = false; - for (uint8_t j = 0; j < interface_desc->bNumEndpoints; j++) { - const libusb_endpoint_descriptor *endpoint = &interface_desc->endpoint[j]; - uint8_t endPointAddr = endpoint->bEndpointAddress; - const bool is_bulk = (endpoint->bmAttributes & 0b11) == - LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK; - _logger->info("endpoint[{}]: addr=0x{:X} attrs=0x{:X} bulk={} in={}", - (int)j, (int)endPointAddr, (int)endpoint->bmAttributes, - is_bulk ? 1 : 0, - (endPointAddr & LIBUSB_ENDPOINT_IN) ? 1 : 0); - - if (is_bulk && !(endPointAddr & LIBUSB_ENDPOINT_IN)) { - numOutPipes++; - _bulk_out_eps.push_back(endPointAddr); - } - /* First bulk IN endpoint wins. 8812AU/8814AU expose 0x81; 8821AU's - * descriptor offers a different IN endpoint, so libusb's - * submit_bulk_transfer to 0x81 would return "endpoint not found on any - * open interface". Capture whatever IN endpoint the chip actually - * exposes and use it in bulk_read_raw(). */ - if (is_bulk && (endPointAddr & LIBUSB_ENDPOINT_IN) && !found_bulk_in) { - _bulk_in_ep = endPointAddr; - found_bulk_in = true; - _logger->info("selected bulk IN endpoint: 0x{:X}", (int)_bulk_in_ep); - } - } - if (!_bulk_out_eps.empty()) { - std::string ep_list; - for (auto ep : _bulk_out_eps) { - char buf[8]; - snprintf(buf, sizeof(buf), "0x%02X ", ep); - ep_list += buf; - } - _logger->info("bulk OUT endpoints: {}", ep_list); - } - /* Clear any HALT state on the bulk IN endpoint. The fwdl sequence and - * USB reset can leave the IN EP in a stalled state from the chip side; - * without clear_halt the chip's USB engine would never push RX bytes - * even though the host's libusb_bulk_transfer succeeds at submission. */ - if (found_bulk_in) { - int hr = libusb_clear_halt(_dev_handle, _bulk_in_ep); - _logger->info("libusb_clear_halt(bulk IN 0x{:X}) rc={}", (int)_bulk_in_ep, - hr); - } - - libusb_free_config_descriptor(config); - break; - } -} - -void RtlUsbAdapter::GetChipOutEP8812() { - OutEpQueueSel = 0; - OutEpNumber = 0; - - switch (numOutPipes) { - case 4: - OutEpQueueSel = TxSele::TX_SELE_HQ | TxSele::TX_SELE_LQ | - TxSele::TX_SELE_NQ | TxSele::TX_SELE_EQ; - OutEpNumber = 4; - break; - case 3: - OutEpQueueSel = - TxSele::TX_SELE_HQ | TxSele::TX_SELE_LQ | TxSele::TX_SELE_NQ; - OutEpNumber = 3; - break; - case 2: - OutEpQueueSel = TxSele::TX_SELE_HQ | TxSele::TX_SELE_NQ; - OutEpNumber = 2; - break; - case 1: - OutEpQueueSel = TxSele::TX_SELE_HQ; - OutEpNumber = 1; - break; - default: - break; - } - - _logger->info("OutEpQueueSel({}), OutEpNumber({})", (int)OutEpQueueSel, - (int)OutEpNumber); -} - -void RtlUsbAdapter::transfer_callback(struct libusb_transfer *transfer) { - auto *self = static_cast(transfer->user_data); - if (transfer->status == LIBUSB_TRANSFER_COMPLETED && - transfer->actual_length == transfer->length) { - DVR_DEBUG(self->_logger, "Packet sent successfully, length: {}", - transfer->length); - } else { - /* Flag the bulk-OUT as possibly halted so the next send_packet (on the TX - * thread) re-clear_halts it before the following frame. */ - self->_tx_wedged->store(true, std::memory_order_relaxed); - /* Async completion failure — the real drop for the async TX path. A - * TIMED_OUT status is the FIFO-full back-pressure (congestion); anything - * else is a hard error. Record the negated status as the rc. */ - self->_tx_stats->failed.fetch_add(1, std::memory_order_relaxed); - self->_tx_stats->last_rc.store(-transfer->status, std::memory_order_relaxed); - self->_tx_stats->last_timeout.store( - transfer->status == LIBUSB_TRANSFER_TIMED_OUT, - std::memory_order_relaxed); - self->_logger->error("Failed to send packet, status: {}, actual length: {}", - transfer->status, transfer->actual_length); - /* machine-readable mirror of the failure (tests/regress.py keys on it) */ - devourer::Ev(self->_logger->events(), "tx.fail") - .f("status", (long long)transfer->status) - .f("actual_len", transfer->actual_length) - .f("timeout", transfer->status == LIBUSB_TRANSFER_TIMED_OUT); - } - libusb_free_transfer(transfer); -} - -bool RtlUsbAdapter::send_packet(uint8_t *packet, size_t length) { -#if defined(DEVOURER_HAVE_PCIE) - if (_pcie) - return pcie_tx_dispatch(packet, length, _tx_timeout_ms) >= 0; -#endif - - libusb_transfer *transfer = libusb_alloc_transfer(0); - if (!transfer) { - _logger->error("Failed to allocate transfer"); - return false; - } - - /* TX bulk OUT endpoint selection: DeviceConfig tx.ep override > first - * discovered OUT endpoint > historic 8812AU default (0x02). */ - const uint8_t tx_ep = _tx_ep_override - ? *_tx_ep_override - : (!_bulk_out_eps.empty() ? _bulk_out_eps[0] - : uint8_t{0x02}); - - /* Recover a bulk-OUT that a prior async TX wedged (TIMED_OUT / stall). Only - * the first send used to clear_halt; a mid-stream stall (e.g. hardware NDP - * generation on some xhci hosts) then stayed wedged forever. */ - if (_tx_wedged->exchange(false)) { - int hr = libusb_clear_halt(_dev_handle, tx_ep); - _logger->info("TX EP 0x{:02X} re-clear_halt after wedge rc={}", (int)tx_ep, - hr); - } - - /* TX bulk-OUT timeout (ms), DeviceConfig tx.timeout_ms. Scoped to TX only — - * control transfers keep USB_TIMEOUT. */ - const unsigned tx_timeout_ms = _tx_timeout_ms; - - /* On the FIRST send only, dump the bulk-OUT bytes to compare against - * the OOT-driver wire trace. */ - static bool first_pkt_dump = true; - if (first_pkt_dump) { - first_pkt_dump = false; - /* Clear any HALT state on the TX EP. The fwdl process can leave the - * TX EP in a stalled state from the chip side; without clear_halt the - * USB controller would NAK every subsequent bulk OUT URB. */ - int chr = libusb_clear_halt(_dev_handle, tx_ep); - _logger->info("libusb_clear_halt(EP 0x{:02X}) rc={}", (int)tx_ep, chr); - size_t dump_len = std::min(length, 64); - char hex[64 * 2 + 1] = {0}; - for (size_t k = 0; k < dump_len; ++k) { - static const char hd[] = "0123456789abcdef"; - hex[2*k] = hd[packet[k] >> 4]; - hex[2*k+1] = hd[packet[k] & 0xF]; - } - _logger->info("first TX bulk-OUT len={} bytes: {}", length, hex); - } - - /* On the FIRST send only, dump chip state via vendor reads. Surfaces any - * register clobber between init-end and first TX (e.g. SetMonitorChannel - * could be resetting REG_CR or related). */ - static bool first_dump = true; - if (first_dump) { - first_dump = false; - uint16_t cr = rtw_read16(0x0100); - uint8_t txpause = rtw_read8(0x0522); - uint32_t txdma_off_chk = rtw_read32(0x020C); - uint32_t fwhw_txq = rtw_read32(0x0420); - uint32_t mcufwdl = rtw_read32(0x0080); - uint32_t hci_susp = rtw_read32(0xFE10); /* USB_HCPWM / USB suspend ctrl */ - _logger->info("pre-1st-TX: CR=0x{:04x} TXPAUSE=0x{:02x} TXDMA_OFFC=0x{:08x}", - cr, txpause, txdma_off_chk); - _logger->info("pre-1st-TX: FWHW_TXQ=0x{:08x} MCUFWDL=0x{:08x} HCIPWR=0x{:08x}", - fwhw_txq, mcufwdl, hci_susp); - } - - libusb_fill_bulk_transfer(transfer, _dev_handle, tx_ep, packet, length, - &RtlUsbAdapter::transfer_callback, (void *)this, - tx_timeout_ms); - /* Upstream OOT (rtl8814a/usb/rtl8814au_xmit.c) sets URB_ZERO_PACKET on - * every TX URB. libusb equivalent: LIBUSB_TRANSFER_ADD_ZERO_PACKET. - * Without it the chip's SuperSpeed bulk OUT controller can wait - * indefinitely for transfer-end signaling and NAK every URB until libusb - * cancels — matches the usbmon trace we captured: 6977 submitted URBs, - * every completion with status=-2 (ENOENT/cancelled), data_len=0. */ - transfer->flags |= LIBUSB_TRANSFER_ADD_ZERO_PACKET; - auto start = std::chrono::high_resolution_clock::now(); - /* Count the submission here; async completion (incl. TIMED_OUT) is counted in - * transfer_callback, a submit error just below. */ - _tx_stats->submitted.fetch_add(1, std::memory_order_relaxed); - int rc = rc = libusb_submit_transfer(transfer); - auto end = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = end - start; - if (rc == LIBUSB_SUCCESS) { - DVR_DEBUG(_logger, "Packet sent successfully, length: {},used time {}ms", length, - elapsed.count()); - return true; - } else { - _tx_stats->failed.fetch_add(1, std::memory_order_relaxed); - _tx_stats->last_rc.store(rc, std::memory_order_relaxed); - _tx_stats->last_timeout.store(rc == LIBUSB_ERROR_TIMEOUT, - std::memory_order_relaxed); - _logger->error("Failed to send packet, error code: {}", rc); - devourer::Ev(_logger->events(), "tx.fail") - .f("rc", rc) - .f("timeout", rc == LIBUSB_ERROR_TIMEOUT); - libusb_free_transfer(transfer); - return false; - } -} - -int RtlUsbAdapter::bulk_send_sync(uint8_t *packet, size_t length, - int timeout_ms) { - return bulk_send_sync_ep(0x02, packet, length, timeout_ms); -} - -int RtlUsbAdapter::bulk_send_sync_ep(uint8_t ep, uint8_t *packet, size_t length, - int timeout_ms) { -#if defined(DEVOURER_HAVE_PCIE) - if (_pcie) { - (void)ep; /* endpoint is USB addressing; the ring is chosen by QSEL */ - return pcie_tx_dispatch(packet, length, timeout_ms); - } -#endif - /* No libusb_clear_halt here. rtw88_8814au's usbmon shows the first bulk - * OUT is preceded by 0 CLEAR_FEATUREs; later CLEAR_FEATUREs happen during - * normal TX-queue operation, not the per-send hot path. Resetting the - * data toggle bit corrupts the chip's state machine. */ - int actual = 0; - _tx_stats->submitted.fetch_add(1, std::memory_order_relaxed); - int rc = libusb_bulk_transfer(_dev_handle, ep, packet, - static_cast(length), &actual, timeout_ms); - if (rc != LIBUSB_SUCCESS) { - _tx_stats->failed.fetch_add(1, std::memory_order_relaxed); - _tx_stats->last_rc.store(rc, std::memory_order_relaxed); - _tx_stats->last_timeout.store(rc == LIBUSB_ERROR_TIMEOUT, - std::memory_order_relaxed); - _logger->error("bulk_send EP {} FAIL rc={} got {}/{}", (int)ep, rc, - actual, (int)length); - return rc; - } - _logger->info("bulk_send EP {} OK {} bytes", (int)ep, actual); - return actual; -} - -void RtlUsbAdapter::phy_set_bb_reg(uint16_t regAddr, uint32_t bitMask, - uint32_t data) { - PHY_SetBBReg8812(regAddr, bitMask, data); -} - -void RtlUsbAdapter::PHY_SetBBReg8812(uint16_t regAddr, uint32_t bitMask, - uint32_t dataOriginal) { - uint32_t data = dataOriginal; - if (bitMask != bMaskDWord) { - /* if not "double word" write */ - auto OriginalValue = rtw_read32(regAddr); - auto BitShift = PHY_CalculateBitShift(bitMask); - data = ((OriginalValue) & (~bitMask)) | - (((dataOriginal << (int)BitShift)) & bitMask); - } - - rtw_write32(regAddr, data); - - /* RTW_INFO("BBW MASK=0x%x Addr[0x%x]=0x%x\n", BitMask, RegAddr, Data); */ -} diff --git a/src/RtlUsbAdapter.h b/src/RtlUsbAdapter.h index befd2dd..7227312 100644 --- a/src/RtlUsbAdapter.h +++ b/src/RtlUsbAdapter.h @@ -1,283 +1,13 @@ #ifndef RTLUSBADAPTER_H #define RTLUSBADAPTER_H -#include +/* Compatibility shim. The class once named RtlUsbAdapter is now the + * bus-neutral RtlAdapter (src/RtlAdapter.h); the USB wire specifics moved to + * devourer::UsbTransport (src/UsbTransport.h) behind the IRtlTransport seam + * (src/RtlTransport.h). Prefer RtlAdapter in new code. */ -#include -#include -#include -#include -#include -#include -#include -#include +#include "RtlAdapter.h" -#include "DeviceConfig.h" -#include "drv_types.h" -#include "hal_com_reg.h" -#include "logger.h" -#include "TxStats.h" - -namespace devourer { -class UsbDeviceLock; -class PcieTransport; -} - -#define rtw_read8 rtw_read -#define rtw_read16 rtw_read -#define rtw_read32 rtw_read - -#define rtw_write8 rtw_write -#define rtw_write16 rtw_write -#define rtw_write32 rtw_write - -#define REALTEK_USB_VENQT_READ 0xC0 -#define REALTEK_USB_VENQT_WRITE 0x40 -#define USB_TIMEOUT 500 - -enum TxSele { - TX_SELE_HQ = 1 << (0), /* High Queue */ - TX_SELE_LQ = 1 << (1), /* Low Queue */ - TX_SELE_NQ = 1 << (2), /* Normal Queue */ - TX_SELE_EQ = 1 << (3), /* Extern Queue */ -}; - -class RtlUsbAdapter { - libusb_device_handle *_dev_handle = nullptr; /* null on the PCIe backend */ - libusb_context *_ctx = nullptr; - Logger_t _logger; - - /* ---- PCIe backend (DEVOURER_PCIE) ---------------------------------- - * The adapter is bus-dual: register I/O dispatches on `_mmio` — when set, - * rtw_read/rtw_write are plain volatile loads/stores on the BAR2 mapping - * (the same 0x0000..0xFFFF register space the USB vendor-control transfers - * address), and the bulk TX/RX entry points route to the PCIe TRX rings. - * `_mmio` is a raw pointer (cheap to copy — the adapter is a copyable value - * type); `_pcie` keeps the vfio fds + DMA rings alive until the last adapter - * copy dies, the same lifetime rule `_usb_lock` encodes for USB. Null on - * USB; empty/null on non-PCIe builds (a forward-declared shared_ptr member - * costs nothing). */ - volatile uint8_t *_mmio = nullptr; - std::shared_ptr _pcie; - - enum libusb_speed usbSpeed; - uint8_t numOutPipes = 0; - /* Bulk-IN endpoint address used by infinite_read(). 8812AU / 8814AU expose - * IN endpoint at 0x81; 8821AU has a different descriptor (no 0x81), so we - * pick this up from the interface descriptor at InitDvObj time instead of - * hardcoding. Default 0x81 keeps existing behaviour for 88[12,14]AU when - * the descriptor walk doesn't override. */ - uint8_t _bulk_in_ep = 0x81; - /* Bulk-OUT endpoint addresses, in descriptor order. 8812AU exposes 4 OUT - * EPs at 0x02/0x03/0x04/0x05 (HQ/NQ/LQ/EQ); 8821AU exposes 0x05/0x06/0x08/ - * 0x09. send_packet picks index 0 (HQ-equivalent) by default; the - * DeviceConfig tx.ep override wins for diagnostic bisection. */ - std::vector _bulk_out_eps; - - /* From DeviceConfig (see there for semantics). Plain values — RtlUsbAdapter - * is a copyable value type, every copy carries them. */ - bool _log_writes = false; /* debug.log_writes */ - std::optional _tx_ep_override; /* tx.ep */ - unsigned _tx_timeout_ms = USB_TIMEOUT; /* tx.timeout_ms */ - - uint16_t _idVendor = 0; - uint16_t _idProduct = 0; - - /* Set by transfer_callback when an async TX bulk-OUT completes non-OK - * (TIMED_OUT / stall). Consumed at the top of the next send_packet on the TX - * thread to re-clear_halt the endpoint — a mid-stream stall (e.g. hardware - * NDP generation on some xhci hosts) would otherwise stay wedged, since the - * first-send clear_halt only runs once. Heap-owned via shared_ptr because - * RtlUsbAdapter is a copyable value type (passed by value throughout); an - * atomic member would delete the copy ctor. All copies of an adapter share - * the one flag (only the TX-driving copy touches it). */ - std::shared_ptr> _tx_wedged = - std::make_shared>(false); - - /* TX submission counters (the driver-drop / congestion signal, see - * TxStats.h). Heap-shared for the same reason as _tx_wedged: RtlUsbAdapter is - * a copyable value type, and the async transfer_callback runs on whichever - * copy submitted — all copies must increment the one set of counters. */ - struct TxStatsCounters { - std::atomic submitted{0}; - std::atomic failed{0}; - std::atomic last_rc{0}; - std::atomic last_timeout{false}; - }; - std::shared_ptr _tx_stats = - std::make_shared(); - - /* Exclusive per-adapter USB lock, acquired by WiFiDriver::CreateRtlDevice and - * held for the device's lifetime (see UsbDeviceLock.h). shared_ptr because - * RtlUsbAdapter is a copyable value type copied into every sub-manager - * (EepromManager / RadioManagementModule / HalModule / the device itself); - * all copies share the one lock, so it releases only when the last copy — and - * thus the whole device — is destroyed. Null when no lock was taken (graceful - * degradation on a lock-infrastructure error). */ - std::shared_ptr _usb_lock; - -public: - RtlUsbAdapter(libusb_device_handle *dev_handle, Logger_t logger, - libusb_context *ctx = nullptr, - std::shared_ptr usb_lock = nullptr, - const devourer::DeviceConfig &cfg = {}); - -#if defined(DEVOURER_HAVE_PCIE) - /* PCIe backend: registers via BAR2 MMIO, TX/RX via the transport's DMA - * rings. No USB descriptor walk / endpoint state — the endpoint-flavoured - * members stay at their defaults and are only consumed by USB-gated code. */ - RtlUsbAdapter(std::shared_ptr transport, - Logger_t logger, const devourer::DeviceConfig &cfg = {}); -#endif - - /* True on the libusb backend, false on PCIe. The HAL bring-up gates its few - * genuinely bus-specific steps (USB RX-aggregation config, 0xFExx USB-page - * workarounds, power-seq table choice) on this. */ - bool is_usb() const { return _dev_handle != nullptr; } - devourer::PcieTransport *pcie() const { return _pcie.get(); } - - /* Kernel-style async RX: keep n_urbs concurrent bulk-IN transfers in flight on - * the discovered bulk-IN endpoint, invoking on_data(buf,len) for each non-empty - * completion and resubmitting, until `stop` is set. Mirrors the rtw88 RX URB - * queue (the single-shot bulk_read_raw can miss the chip's RX delivery window). - * Requires the libusb context the handle belongs to (passed at construction). */ - void bulk_read_async_loop(int buf_size, int n_urbs, - const std::function &on_data, - const std::function &should_stop); - /* Convenience overload for callers whose stop condition is a single flag. */ - void bulk_read_async_loop(int buf_size, int n_urbs, - const std::function &on_data, - const volatile bool &stop) { - bulk_read_async_loop(buf_size, n_urbs, on_data, - [&stop]() -> bool { return stop; }); - } - - uint16_t idVendor() const { return _idVendor; } - uint16_t idProduct() const { return _idProduct; } - enum libusb_speed speed() const { return usbSpeed; } - /* First discovered bulk-OUT endpoint (HQ-equivalent), or 0x02 if none were - * discovered. Used by the Jaguar3 DLFW rsvd-page download, whose chip - * (e.g. RTL8812CU) has no 0x02 endpoint — it exposes 0x05/0x06/0x08. */ - uint8_t first_bulk_out_ep() const { - return _bulk_out_eps.empty() ? 0x02 : _bulk_out_eps[0]; - } - - bool AutoloadFailFlag = false; - bool EepromOrEfuse = false; - uint8_t OutEpQueueSel; - uint8_t OutEpNumber; - uint8_t rxagg_usb_size; - uint8_t rxagg_usb_timeout; - bool send_packet(uint8_t* packet, size_t length); - /* Synchronous bulk-OUT transfer that blocks until completion or timeout. - * Returns the number of bytes actually transferred, or negative on error. */ - int bulk_send_sync(uint8_t *packet, size_t length, int timeout_ms); - void bulk_clear_halt(uint8_t ep) { - if (_dev_handle) - libusb_clear_halt(_dev_handle, ep); - } - int bulk_send_sync_ep(uint8_t ep, uint8_t *packet, size_t length, - int timeout_ms); - - /* Snapshot of the TX submission counters (see TxStats.h). Safe from any - * thread — the fields are read from atomics (a torn read across the four is - * harmless for a monitoring counter). */ - devourer::TxStats GetTxStats() const { - devourer::TxStats s; - s.submitted = _tx_stats->submitted.load(std::memory_order_relaxed); - s.failed = _tx_stats->failed.load(std::memory_order_relaxed); - s.last_error_rc = _tx_stats->last_rc.load(std::memory_order_relaxed); - s.last_was_timeout = _tx_stats->last_timeout.load(std::memory_order_relaxed); - return s; - } - /* Raw bulk-IN read on the discovered bulk-IN endpoint, returning bytes read - * (or a negative libusb error). For chip families whose RX descriptor is not - * the Jaguar1 layout (e.g. Jaguar3), the caller parses the buffer itself. */ - int bulk_read_raw(uint8_t *buf, int len, int timeout_ms) { - if (!_dev_handle) - return -1; /* PCIe RX goes through bulk_read_async_loop (ring reap) */ - int actual = 0; - int rc = libusb_bulk_transfer(_dev_handle, _bulk_in_ep, buf, len, &actual, - timeout_ms); - return rc < 0 ? rc : actual; - } - uint8_t efuse_OneByteRead(uint16_t addr, uint8_t *data); - void phy_set_bb_reg(uint16_t regAddr, uint32_t bitMask, uint32_t data); - - template T rtw_read(uint16_t reg_num) { - if (_mmio) { - /* 0xFE00..0xFEFF is USB-page register space — undefined over MMIO. The - * few jaguar users (0xFE5B/0xFE10/0xFE11) are is_usb()-gated; catch any - * stragglers instead of poking a hole in the BAR. */ - if (reg_num >= 0xFE00) { - _logger->warn("rtw_read(0x{:04x}) on PCIe: USB-page register, " - "returning 0", - reg_num); - return 0; - } - return *reinterpret_cast(_mmio + reg_num); - } - T data = 0; - if (libusb_control_transfer(_dev_handle, REALTEK_USB_VENQT_READ, 5, reg_num, - 0, (uint8_t *)&data, sizeof(T), - USB_TIMEOUT) == sizeof(T)) { - return data; - } - - _logger->error("rtw_read({:04x}), sizeof(T) = {}", reg_num, sizeof(T)); - throw std::ios_base::failure("rtw_read"); - return 0; - } - - template bool rtw_write(uint16_t reg_num, T value) { - /* debug.log_writes: emit every vendor reg write as a debug.wreg event - * (addr/width/val mirror tests/decode_wseq.py's tuple) so devourer's - * bring-up write set can be diffed against the kernel golden. Applies to - * both backends — the kernel-diff tooling works unchanged over MMIO. */ - if (_log_writes) - devourer::Ev(_logger->events(), "debug.wreg") - .hexf("addr", reg_num, 4) - .f("width", sizeof(T)) - .hexf("val", (unsigned long long)value, (int)(sizeof(T) * 2)); - if (_mmio) { - if (reg_num >= 0xFE00) { - _logger->warn("rtw_write(0x{:04x}) on PCIe: USB-page register, " - "dropped", - reg_num); - return false; - } - *reinterpret_cast(_mmio + reg_num) = value; - return true; - } - if (libusb_control_transfer(_dev_handle, REALTEK_USB_VENQT_WRITE, 5, - reg_num, 0, (uint8_t *)&value, sizeof(T), - USB_TIMEOUT) == sizeof(T)) { - return true; - } - return false; - } - - bool WriteBytes(uint16_t reg_num, uint8_t *ptr, size_t size); - - void rtl8812au_hw_reset(); - void _8051Reset8812(); - void ReadEFuseByte(uint16_t _offset, uint8_t *pbuf); - -private: - /* Async TX bulk-OUT completion callback. user_data is the RtlUsbAdapter* so - * it can flag _tx_wedged on a non-OK completion; a static member (not a free - * function) to reach that private state. */ - static void transfer_callback(struct libusb_transfer *transfer); - /* PCIe TX: sniff QSEL from the tx descriptor at packet[0] and submit on the - * matching TRX ring (QSEL_BEACON -> BCN ring = the DLFW rsvd-page path, - * exactly rtw88's write_data_rsvd_page -> RTW_TX_QUEUE_BCN mapping). - * Defined only in DEVOURER_PCIE builds; every call site is _pcie-gated. */ - int pcie_tx_dispatch(uint8_t *packet, size_t length, int timeout_ms); - void InitDvObj(); - const char *strUsbSpeed(); - void GetChipOutEP8812(); - void PHY_SetBBReg8812(uint16_t regAddr, uint32_t bitMask, - uint32_t dataOriginal); -}; +using RtlUsbAdapter = RtlAdapter; #endif /* RTLUSBADAPTER_H */ diff --git a/src/ToneMask.h b/src/ToneMask.h index 9967eed..75a73ee 100644 --- a/src/ToneMask.h +++ b/src/ToneMask.h @@ -47,7 +47,7 @@ #include #include -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" #include "logger.h" @@ -192,7 +192,7 @@ inline CsiMaskSpec parse_csi_spec(const char *s) { * subcarrier), 0x890-0x89F = negative (bit index 128 - |k|). Builds the full * 32-byte shadow host-side and writes 8 dwords, then sets the 0x874[0] enable. * Returns the number of tones masked. */ -inline size_t csi_mask_apply_11ac(RtlUsbAdapter &dev, uint32_t fc_mhz, +inline size_t csi_mask_apply_11ac(RtlAdapter &dev, uint32_t fc_mhz, uint32_t bw_mhz, const CsiMaskSpec &spec) { auto tones = enumerate_tones(fc_mhz, bw_mhz, spec.f_lo_khz, spec.f_hi_khz); uint8_t shadow[32] = {}; @@ -217,7 +217,7 @@ inline size_t csi_mask_apply_11ac(RtlUsbAdapter &dev, uint32_t fc_mhz, return tones.size(); } -inline void csi_mask_clear_11ac(RtlUsbAdapter &dev) { +inline void csi_mask_clear_11ac(RtlAdapter &dev) { for (int i = 0; i < 8; i++) dev.rtw_write32(uint16_t(0x880 + i * 4), 0); dev.phy_set_bb_reg(0x874, 0x1, 0); @@ -226,7 +226,7 @@ inline void csi_mask_clear_11ac(RtlUsbAdapter &dev) { /* NBI notch, 11ac: quantize the tone offset (x10 fixed point, vendor units) * into the register LUT and write 0x87C[19:14] + the per-family enables. * f_intf must fall inside the tuned bandwidth. */ -inline bool nbi_apply_11ac(RtlUsbAdapter &dev, Family fam, uint32_t fc_mhz, +inline bool nbi_apply_11ac(RtlAdapter &dev, Family fam, uint32_t fc_mhz, uint32_t bw_mhz, uint32_t f_intf_mhz, bool paths_ge_2t) { /* tone_idx x10 units */ @@ -266,7 +266,7 @@ inline bool nbi_apply_11ac(RtlUsbAdapter &dev, Family fam, uint32_t fc_mhz, return true; } -inline void nbi_disable_11ac(RtlUsbAdapter &dev, Family fam, +inline void nbi_disable_11ac(RtlAdapter &dev, Family fam, bool paths_ge_2t) { dev.phy_set_bb_reg(0x87c, 1u << 13, 0); if (fam == Family::AC2_8822B) { @@ -284,7 +284,7 @@ inline void nbi_disable_11ac(RtlUsbAdapter &dev, Family fam, * 0x9B0[3:2] (RF80 -> 128, else 64). Adjacent tones share a table byte, so the * per-address byte is accumulated host-side first — the vendor's one-tone * setter would clobber its neighbour's nibble when looped over a range. */ -inline size_t csi_mask_apply_jgr3(RtlUsbAdapter &dev, uint32_t fc_mhz, +inline size_t csi_mask_apply_jgr3(RtlAdapter &dev, uint32_t fc_mhz, uint32_t bw_mhz, const CsiMaskSpec &spec) { uint8_t rf_bw = dev.rtw_read8(0x9b0); uint32_t tone_num = (((rf_bw & 0xc) >> 2) == 0x2) ? 128 : 64; @@ -317,7 +317,7 @@ inline size_t csi_mask_apply_jgr3(RtlUsbAdapter &dev, uint32_t fc_mhz, return tones.size(); } -inline void csi_mask_clear_jgr3(RtlUsbAdapter &dev) { +inline void csi_mask_clear_jgr3(RtlAdapter &dev) { dev.phy_set_bb_reg(0x1ee8, 0x3, 0x3); dev.phy_set_bb_reg(0x1d94, 0xc0000000, 0x1); for (uint32_t a = 0; a < 128; a++) { @@ -331,7 +331,7 @@ inline void csi_mask_clear_jgr3(RtlUsbAdapter &dev) { /* NBI notch, Jaguar-3: tone index (312.5 kHz units, kHz-exact per the x2cu * tree) into 0x1944/0x4044[20:12] per path; enable 0x818[3]=0 (inverted on * 8822C/E) + 0x818[11]=1 + 0x1D3C[30:27]=0xF + 0x1940/0x4040[31]. */ -inline bool nbi_apply_jgr3(RtlUsbAdapter &dev, uint32_t fc_mhz, +inline bool nbi_apply_jgr3(RtlAdapter &dev, uint32_t fc_mhz, uint32_t bw_mhz, uint32_t f_intf_khz, int n_paths) { uint32_t fc_khz = fc_mhz * 1000; uint32_t bw_up = fc_khz + bw_mhz * 500, bw_low = fc_khz - bw_mhz * 500; @@ -364,7 +364,7 @@ inline bool nbi_apply_jgr3(RtlUsbAdapter &dev, uint32_t fc_mhz, } /* phydm_nbi_reset_jgr3 (8822C/E subset). */ -inline void nbi_disable_jgr3(RtlUsbAdapter &dev) { +inline void nbi_disable_jgr3(RtlAdapter &dev) { dev.phy_set_bb_reg(0x818, 1u << 3, 1); dev.phy_set_bb_reg(0x1d3c, 0x78000000, 0); dev.phy_set_bb_reg(0x818, 1u << 3, 0); @@ -380,7 +380,7 @@ inline void nbi_disable_jgr3(RtlUsbAdapter &dev) { * rx.path_spec, the mask is the final word for a single-channel capture; a * later channel switch does not re-derive it). n_paths: RX chains to arm for * the Jaguar-3 NBI / the 8822B second-path enable. */ -inline void apply(RtlUsbAdapter &dev, Logger_t logger, Family fam, +inline void apply(RtlAdapter &dev, Logger_t logger, Family fam, const SelectedChannel &ch, int n_paths, const char *csi_env, const char *nbi_env) { if (!csi_env && !nbi_env) return; diff --git a/src/TxStats.h b/src/TxStats.h index a0a1cf7..8613f98 100644 --- a/src/TxStats.h +++ b/src/TxStats.h @@ -8,7 +8,7 @@ namespace devourer { /* TX submission health — the driver-side drop signal an adaptive-link * controller uses to detect congestion (the "xtx" cut-bitrate trigger). Counted * at the USB bulk-OUT layer, so it is family-agnostic (all three HALs push TX - * through the same RtlUsbAdapter). + * through the same RtlAdapter). * * `submitted` counts every frame handed to the USB stack; `failed` counts those * that did not complete OK — either a synchronous submit/transfer error or an diff --git a/src/UsbTransport.cpp b/src/UsbTransport.cpp new file mode 100644 index 0000000..03915a3 --- /dev/null +++ b/src/UsbTransport.cpp @@ -0,0 +1,350 @@ +#include "UsbTransport.h" + +#include +#include +#include +#include + +#include "Event.h" +#include "UsbDeviceLock.h" + +namespace devourer { + +namespace { +/* Shared state for the async RX URB queue. */ +struct AsyncRxShared { + const std::function *cb; + const std::function *stop; + /* Atomic: in co-running (TX + self-capture RX) mode both the RX loop's own + * event pump and the TX event loop may run this callback, so `active` is + * written from the pump thread while the loop below reads it. */ + std::atomic active{0}; +}; +extern "C" void LIBUSB_CALL devourer_rx_cb(libusb_transfer *t) { + auto *s = static_cast(t->user_data); + if (t->status == LIBUSB_TRANSFER_COMPLETED && t->actual_length > 0) + (*s->cb)(t->buffer, t->actual_length); + bool resubmit = !(*s->stop)() && (t->status == LIBUSB_TRANSFER_COMPLETED || + t->status == LIBUSB_TRANSFER_TIMED_OUT); + if (resubmit && libusb_submit_transfer(t) == 0) + return; + s->active--; /* not resubmitted -> this URB is done */ +} +} // namespace + +UsbTransport::UsbTransport(libusb_device_handle *dev_handle, Logger_t logger, + libusb_context *ctx, + std::shared_ptr usb_lock) + : _dev_handle{dev_handle}, _ctx{ctx}, _logger{std::move(logger)}, + _usb_lock{std::move(usb_lock)} { + libusb_device_descriptor desc{}; + if (libusb_get_device_descriptor(libusb_get_device(_dev_handle), &desc) == + LIBUSB_SUCCESS) { + _info.vid = desc.idVendor; + _info.pid = desc.idProduct; + _logger->info("USB device {:04x}:{:04x}", _info.vid, _info.pid); + } + discover_endpoints(); + _info.valid = true; +} + +bool UsbTransport::write_bytes(uint16_t reg_num, const uint8_t *ptr, size_t n) { + return libusb_control_transfer(_dev_handle, REALTEK_USB_VENQT_WRITE, 5, + reg_num, 0, const_cast(ptr), n, + USB_TIMEOUT) == static_cast(n); +} + +void UsbTransport::rx_loop( + int buf_size, int n_urbs, + const std::function &on_data, + const std::function &should_stop) { + AsyncRxShared sh{&on_data, &should_stop}; + std::vector xfers; + std::vector> bufs(n_urbs, + std::vector(buf_size)); + for (int i = 0; i < n_urbs; i++) { + libusb_transfer *t = libusb_alloc_transfer(0); + /* timeout=0 (infinite): a persistent RX ring — each URB stays posted until + * a frame arrives (COMPLETED), the queue is torn down (CANCELLED, below), + * or the device errors. This is the kernel rtw88 RX-URB idiom. A finite + * timeout here would fire once per idle interval on a quiet channel, and + * libusb's darwin backend logs every LIBUSB_TRANSFER_TIMED_OUT at WARNING + * level — a continuous "transfer error: timed out" flood that carries no + * information (RX is healthy; bulk-IN is simply idle) and can bloat a long + * capture's stderr. The devourer_rx_cb resubmit-on-TIMED_OUT branch is kept + * as a defensive no-op should a backend still surface a timeout. */ + libusb_fill_bulk_transfer(t, _dev_handle, _info.bulk_in_ep, bufs[i].data(), + buf_size, devourer_rx_cb, &sh, 0); + if (libusb_submit_transfer(t) == 0) { + xfers.push_back(t); + sh.active++; + } else { + libusb_free_transfer(t); + } + } + _logger->info("RX: async queue of {} URBs submitted", sh.active.load()); + while (!should_stop() && sh.active > 0) { + struct timeval tv {0, 100000}; + libusb_handle_events_timeout_completed(_ctx, &tv, nullptr); + } + for (auto *t : xfers) + libusb_cancel_transfer(t); + while (sh.active > 0) { + struct timeval tv {0, 100000}; + libusb_handle_events_timeout_completed(_ctx, &tv, nullptr); + } + for (auto *t : xfers) + libusb_free_transfer(t); +} + +int UsbTransport::rx_raw(uint8_t *buf, int len, int timeout_ms) { + int actual = 0; + int rc = libusb_bulk_transfer(_dev_handle, _info.bulk_in_ep, buf, len, + &actual, timeout_ms); + return rc < 0 ? rc : actual; +} + +const char *UsbTransport::speed_str() const { + switch (_info.speed) { + case LIBUSB_SPEED_UNKNOWN: + return "UNKNOWN"; + case LIBUSB_SPEED_LOW: + return "1.5MBit/s"; + case LIBUSB_SPEED_FULL: + return "12MBit/s"; + case LIBUSB_SPEED_HIGH: + return "480MBit/s"; + case LIBUSB_SPEED_SUPER: + return "5000MBit/s"; + case LIBUSB_SPEED_SUPER_PLUS: + return "10000MBit/s"; + default: + return NULL; + } +} + +void UsbTransport::discover_endpoints() { + libusb_device *dev = libusb_get_device(_dev_handle); + _info.speed = libusb_get_device_speed(dev); + _logger->info("Running USB bus at {}", speed_str()); + + libusb_device_descriptor desc; + int ret = libusb_get_device_descriptor(dev, &desc); + if (ret < 0) { + return; + } + + for (uint8_t k = 0; k < desc.bNumConfigurations; k++) { + libusb_config_descriptor *config; + ret = libusb_get_config_descriptor(dev, k, &config); + if (LIBUSB_SUCCESS != ret) { + continue; + } + + if (!config->bNumInterfaces) { + continue; + } + const libusb_interface *interface = &config->interface[0]; + + if (!interface->altsetting) { + continue; + } + const libusb_interface_descriptor *interface_desc = + &interface->altsetting[0]; + + bool found_bulk_in = false; + for (uint8_t j = 0; j < interface_desc->bNumEndpoints; j++) { + const libusb_endpoint_descriptor *endpoint = &interface_desc->endpoint[j]; + uint8_t endPointAddr = endpoint->bEndpointAddress; + const bool is_bulk = (endpoint->bmAttributes & 0b11) == + LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK; + _logger->info("endpoint[{}]: addr=0x{:X} attrs=0x{:X} bulk={} in={}", + (int)j, (int)endPointAddr, (int)endpoint->bmAttributes, + is_bulk ? 1 : 0, + (endPointAddr & LIBUSB_ENDPOINT_IN) ? 1 : 0); + + if (is_bulk && !(endPointAddr & LIBUSB_ENDPOINT_IN)) { + _info.bulk_out_eps.push_back(endPointAddr); + } + /* First bulk IN endpoint wins. 8812AU/8814AU expose 0x81; 8821AU's + * descriptor offers a different IN endpoint, so libusb's + * submit_bulk_transfer to 0x81 would return "endpoint not found on any + * open interface". Capture whatever IN endpoint the chip actually + * exposes and use it in rx_raw()/rx_loop(). */ + if (is_bulk && (endPointAddr & LIBUSB_ENDPOINT_IN) && !found_bulk_in) { + _info.bulk_in_ep = endPointAddr; + found_bulk_in = true; + _logger->info("selected bulk IN endpoint: 0x{:X}", + (int)_info.bulk_in_ep); + } + } + if (!_info.bulk_out_eps.empty()) { + std::string ep_list; + for (auto ep : _info.bulk_out_eps) { + char buf[8]; + snprintf(buf, sizeof(buf), "0x%02X ", ep); + ep_list += buf; + } + _logger->info("bulk OUT endpoints: {}", ep_list); + } + /* Clear any HALT state on the bulk IN endpoint. The fwdl sequence and + * USB reset can leave the IN EP in a stalled state from the chip side; + * without clear_halt the chip's USB engine would never push RX bytes + * even though the host's libusb_bulk_transfer succeeds at submission. */ + if (found_bulk_in) { + int hr = libusb_clear_halt(_dev_handle, _info.bulk_in_ep); + _logger->info("libusb_clear_halt(bulk IN 0x{:X}) rc={}", + (int)_info.bulk_in_ep, hr); + } + + libusb_free_config_descriptor(config); + break; + } +} + +void UsbTransport::transfer_callback(struct libusb_transfer *transfer) { + auto *self = static_cast(transfer->user_data); + if (transfer->status == LIBUSB_TRANSFER_COMPLETED && + transfer->actual_length == transfer->length) { + DVR_DEBUG(self->_logger, "Packet sent successfully, length: {}", + transfer->length); + } else { + /* Flag the bulk-OUT as possibly halted so the next tx_async (on the TX + * thread) re-clear_halts it before the following frame. */ + self->_tx_wedged.store(true, std::memory_order_relaxed); + /* Async completion failure — the real drop for the async TX path. A + * TIMED_OUT status is the FIFO-full back-pressure (congestion); anything + * else is a hard error. Record the negated status as the rc. */ + self->_tx_failed.fetch_add(1, std::memory_order_relaxed); + self->_tx_last_rc.store(-transfer->status, std::memory_order_relaxed); + self->_tx_last_timeout.store( + transfer->status == LIBUSB_TRANSFER_TIMED_OUT, + std::memory_order_relaxed); + self->_logger->error("Failed to send packet, status: {}, actual length: {}", + transfer->status, transfer->actual_length); + /* machine-readable mirror of the failure (tests/regress.py keys on it) */ + devourer::Ev(self->_logger->events(), "tx.fail") + .f("status", (long long)transfer->status) + .f("actual_len", transfer->actual_length) + .f("timeout", transfer->status == LIBUSB_TRANSFER_TIMED_OUT); + } + libusb_free_transfer(transfer); +} + +bool UsbTransport::tx_async(uint8_t tx_ep, uint8_t *packet, size_t length, + unsigned timeout_ms) { + libusb_transfer *transfer = libusb_alloc_transfer(0); + if (!transfer) { + _logger->error("Failed to allocate transfer"); + return false; + } + + /* Recover a bulk-OUT that a prior async TX wedged (TIMED_OUT / stall). Only + * the first send used to clear_halt; a mid-stream stall (e.g. hardware NDP + * generation on some xhci hosts) then stayed wedged forever. */ + if (_tx_wedged.exchange(false)) { + int hr = libusb_clear_halt(_dev_handle, tx_ep); + _logger->info("TX EP 0x{:02X} re-clear_halt after wedge rc={}", (int)tx_ep, + hr); + } + + /* On the FIRST send only, dump the bulk-OUT bytes to compare against + * the OOT-driver wire trace. */ + static bool first_pkt_dump = true; + if (first_pkt_dump) { + first_pkt_dump = false; + /* Clear any HALT state on the TX EP. The fwdl process can leave the + * TX EP in a stalled state from the chip side; without clear_halt the + * USB controller would NAK every subsequent bulk OUT URB. */ + int chr = libusb_clear_halt(_dev_handle, tx_ep); + _logger->info("libusb_clear_halt(EP 0x{:02X}) rc={}", (int)tx_ep, chr); + size_t dump_len = std::min(length, 64); + char hex[64 * 2 + 1] = {0}; + for (size_t k = 0; k < dump_len; ++k) { + static const char hd[] = "0123456789abcdef"; + hex[2 * k] = hd[packet[k] >> 4]; + hex[2 * k + 1] = hd[packet[k] & 0xF]; + } + _logger->info("first TX bulk-OUT len={} bytes: {}", length, hex); + } + + /* On the FIRST send only, dump chip state via vendor reads. Surfaces any + * register clobber between init-end and first TX (e.g. SetMonitorChannel + * could be resetting REG_CR or related). */ + static bool first_dump = true; + if (first_dump) { + first_dump = false; + uint16_t cr = ctrl_read(0x0100); + uint8_t txpause = ctrl_read(0x0522); + uint32_t txdma_off_chk = ctrl_read(0x020C); + uint32_t fwhw_txq = ctrl_read(0x0420); + uint32_t mcufwdl = ctrl_read(0x0080); + uint32_t hci_susp = ctrl_read(0xFE10); /* USB suspend ctrl */ + _logger->info("pre-1st-TX: CR=0x{:04x} TXPAUSE=0x{:02x} TXDMA_OFFC=0x{:08x}", + cr, txpause, txdma_off_chk); + _logger->info("pre-1st-TX: FWHW_TXQ=0x{:08x} MCUFWDL=0x{:08x} HCIPWR=0x{:08x}", + fwhw_txq, mcufwdl, hci_susp); + } + + libusb_fill_bulk_transfer(transfer, _dev_handle, tx_ep, packet, length, + &UsbTransport::transfer_callback, (void *)this, + timeout_ms); + /* Upstream OOT (rtl8814a/usb/rtl8814au_xmit.c) sets URB_ZERO_PACKET on + * every TX URB. libusb equivalent: LIBUSB_TRANSFER_ADD_ZERO_PACKET. + * Without it the chip's SuperSpeed bulk OUT controller can wait + * indefinitely for transfer-end signaling and NAK every URB until libusb + * cancels — matches the usbmon trace we captured: 6977 submitted URBs, + * every completion with status=-2 (ENOENT/cancelled), data_len=0. */ + transfer->flags |= LIBUSB_TRANSFER_ADD_ZERO_PACKET; + /* Count the submission here; async completion (incl. TIMED_OUT) is counted + * in transfer_callback, a submit error just below. */ + _tx_submitted.fetch_add(1, std::memory_order_relaxed); + int rc = libusb_submit_transfer(transfer); + if (rc == LIBUSB_SUCCESS) { + DVR_DEBUG(_logger, "Packet sent successfully, length: {}", length); + return true; + } + _tx_failed.fetch_add(1, std::memory_order_relaxed); + _tx_last_rc.store(rc, std::memory_order_relaxed); + _tx_last_timeout.store(rc == LIBUSB_ERROR_TIMEOUT, std::memory_order_relaxed); + _logger->error("Failed to send packet, error code: {}", rc); + devourer::Ev(_logger->events(), "tx.fail") + .f("rc", rc) + .f("timeout", rc == LIBUSB_ERROR_TIMEOUT); + libusb_free_transfer(transfer); + return false; +} + +int UsbTransport::tx_sync(uint8_t ep, uint8_t *packet, size_t length, + int timeout_ms) { + /* No libusb_clear_halt here. rtw88_8814au's usbmon shows the first bulk + * OUT is preceded by 0 CLEAR_FEATUREs; later CLEAR_FEATUREs happen during + * normal TX-queue operation, not the per-send hot path. Resetting the + * data toggle bit corrupts the chip's state machine. */ + int actual = 0; + _tx_submitted.fetch_add(1, std::memory_order_relaxed); + int rc = libusb_bulk_transfer(_dev_handle, ep, packet, + static_cast(length), &actual, timeout_ms); + if (rc != LIBUSB_SUCCESS) { + _tx_failed.fetch_add(1, std::memory_order_relaxed); + _tx_last_rc.store(rc, std::memory_order_relaxed); + _tx_last_timeout.store(rc == LIBUSB_ERROR_TIMEOUT, + std::memory_order_relaxed); + _logger->error("bulk_send EP {} FAIL rc={} got {}/{}", (int)ep, rc, actual, + (int)length); + return rc; + } + _logger->info("bulk_send EP {} OK {} bytes", (int)ep, actual); + return actual; +} + +TxStats UsbTransport::tx_stats() const { + TxStats s; + s.submitted = _tx_submitted.load(std::memory_order_relaxed); + s.failed = _tx_failed.load(std::memory_order_relaxed); + s.last_error_rc = _tx_last_rc.load(std::memory_order_relaxed); + s.last_was_timeout = _tx_last_timeout.load(std::memory_order_relaxed); + return s; +} + +} /* namespace devourer */ diff --git a/src/UsbTransport.h b/src/UsbTransport.h new file mode 100644 index 0000000..e5f805d --- /dev/null +++ b/src/UsbTransport.h @@ -0,0 +1,107 @@ +#pragma once + +/* UsbTransport — the libusb implementation of IRtlTransport. Everything + * USB-wire-specific that used to live inside the adapter is here: vendor + * control transfers for the register plane, sync/async bulk-OUT TX with the + * wedge (mid-stream stall) recovery and TX submission counters, the + * kernel-rtw88-style async RX URB queue, and the interface-descriptor walk + * that discovers the bulk endpoints. The exclusive per-adapter UsbDeviceLock + * rides here too — its lifetime is the transport's. */ + +#include +#include + +#include + +#include "DeviceConfig.h" +#include "RtlTransport.h" +#include "logger.h" + +namespace devourer { +class UsbDeviceLock; +} + +#define REALTEK_USB_VENQT_READ 0xC0 +#define REALTEK_USB_VENQT_WRITE 0x40 +#define USB_TIMEOUT 500 + +namespace devourer { + +class UsbTransport final : public IRtlTransport { +public: + UsbTransport(libusb_device_handle *dev_handle, Logger_t logger, + libusb_context *ctx = nullptr, + std::shared_ptr usb_lock = nullptr); + + bool is_usb() const override { return true; } + + uint8_t read8(uint16_t reg) override { return ctrl_read(reg); } + uint16_t read16(uint16_t reg) override { return ctrl_read(reg); } + uint32_t read32(uint16_t reg) override { return ctrl_read(reg); } + bool write8(uint16_t reg, uint8_t v) override { return ctrl_write(reg, v); } + bool write16(uint16_t reg, uint16_t v) override { return ctrl_write(reg, v); } + bool write32(uint16_t reg, uint32_t v) override { return ctrl_write(reg, v); } + bool write_bytes(uint16_t reg, const uint8_t *p, size_t n) override; + + bool tx_async(uint8_t ep, uint8_t *buf, size_t len, + unsigned timeout_ms) override; + int tx_sync(uint8_t ep, uint8_t *buf, size_t len, int timeout_ms) override; + void rx_loop(int buf_size, int n_urbs, + const std::function &on_data, + const std::function &should_stop) override; + int rx_raw(uint8_t *buf, int len, int timeout_ms) override; + void clear_halt(uint8_t ep) override { libusb_clear_halt(_dev_handle, ep); } + + UsbLinkInfo usb_info() const override { return _info; } + TxStats tx_stats() const override; + +private: + template T ctrl_read(uint16_t reg); + template bool ctrl_write(uint16_t reg, T value); + void discover_endpoints(); /* was InitDvObj */ + const char *speed_str() const; + static void transfer_callback(struct libusb_transfer *transfer); + + libusb_device_handle *_dev_handle; + libusb_context *_ctx = nullptr; + Logger_t _logger; + UsbLinkInfo _info; + + /* Set by transfer_callback when an async TX bulk-OUT completes non-OK + * (TIMED_OUT / stall). Consumed at the top of the next tx_async on the TX + * thread to re-clear_halt the endpoint — a mid-stream stall (e.g. hardware + * NDP generation on some xhci hosts) would otherwise stay wedged, since the + * first-send clear_halt only runs once. */ + std::atomic _tx_wedged{false}; + + /* TX submission counters (the driver-drop / congestion signal, TxStats.h). + * The async transfer_callback increments them from the libusb event thread. */ + std::atomic _tx_submitted{0}; + std::atomic _tx_failed{0}; + std::atomic _tx_last_rc{0}; + std::atomic _tx_last_timeout{false}; + + /* Exclusive per-adapter USB lock (UsbDeviceLock.h), held for the transport + * lifetime; released when the device (and thus the transport) dies. */ + std::shared_ptr _usb_lock; +}; + +template T UsbTransport::ctrl_read(uint16_t reg_num) { + T data = 0; + if (libusb_control_transfer(_dev_handle, REALTEK_USB_VENQT_READ, 5, reg_num, + 0, (uint8_t *)&data, sizeof(T), + USB_TIMEOUT) == sizeof(T)) { + return data; + } + _logger->error("rtw_read({:04x}), sizeof(T) = {}", reg_num, sizeof(T)); + throw std::ios_base::failure("rtw_read"); + return 0; +} + +template bool UsbTransport::ctrl_write(uint16_t reg_num, T value) { + return libusb_control_transfer(_dev_handle, REALTEK_USB_VENQT_WRITE, 5, + reg_num, 0, (uint8_t *)&value, sizeof(T), + USB_TIMEOUT) == sizeof(T); +} + +} /* namespace devourer */ diff --git a/src/WiFiDriver.cpp b/src/WiFiDriver.cpp index 49d2f0e..6e9ca55 100644 --- a/src/WiFiDriver.cpp +++ b/src/WiFiDriver.cpp @@ -7,8 +7,9 @@ #include -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "UsbDeviceLock.h" +#include "UsbTransport.h" /* REALTEK_USB_VENQT_READ / USB_TIMEOUT for read_chip_id */ #if defined(DEVOURER_HAVE_PCIE) #include "PcieTransport.h" #endif @@ -25,7 +26,7 @@ namespace { /* Hardware chip-id at REG_SYS_CFG2 (0x00FC), read with a single vendor control - * transfer (no full RtlUsbAdapter, so the Jaguar1 construction path is unchanged + * transfer (no full RtlAdapter, so the Jaguar1 construction path is unchanged * and only one extra control read is added). Port of halmac get_chip_info * (chip_id = REG_READ_8(REG_SYS_CFG2)): * 0x04 / 0x05 / 0x08 = 8812A / 8821A / 8814A -> Jaguar1 @@ -87,7 +88,7 @@ WiFiDriver::CreateRtlDevice(libusb_device_handle *dev_handle, * passes null, so acquire our own here as a best-effort second gate: genuine * contention -> refuse (return nullptr, same contract as an unsupported chip); * a lock-infrastructure error degrades to a warning. Either way the lock rides - * into the RtlUsbAdapter below and is released at device destruction. */ + * into the RtlAdapter below and is released at device destruction. */ if (!usb_lock) { auto lock = std::make_shared(); std::string lock_why; @@ -113,7 +114,7 @@ WiFiDriver::CreateRtlDevice(libusb_device_handle *dev_handle, _logger->info("Creating RtlJaguar2Device (PID 0x{:04x}, chip-id 0x{:02x})", pid, chip_id); return std::make_unique( - RtlUsbAdapter(dev_handle, _logger, ctx, usb_lock, cfg), _logger, + RtlAdapter(dev_handle, _logger, ctx, usb_lock, cfg), _logger, jaguar2::ChipVariant::C8822B, cfg); #else _logger->error("RTL8822B (chip-id 0x{:02x}) detected but Jaguar2 support " @@ -129,7 +130,7 @@ WiFiDriver::CreateRtlDevice(libusb_device_handle *dev_handle, "0x{:02x})", pid, chip_id); return std::make_unique( - RtlUsbAdapter(dev_handle, _logger, ctx, usb_lock, cfg), _logger, + RtlAdapter(dev_handle, _logger, ctx, usb_lock, cfg), _logger, jaguar2::ChipVariant::C8821C, cfg); #else _logger->error("RTL8821C (chip-id 0x09) detected but 8821C support not " @@ -162,7 +163,7 @@ WiFiDriver::CreateRtlDevice(libusb_device_handle *dev_handle, _logger->info("Creating RtlJaguar3Device (PID 0x{:04x}, chip-id 0x{:02x})", pid, chip_id); return std::make_unique( - RtlUsbAdapter(dev_handle, _logger, ctx, usb_lock, cfg), _logger, variant, + RtlAdapter(dev_handle, _logger, ctx, usb_lock, cfg), _logger, variant, cfg); #else _logger->error("Jaguar3 chip (chip-id 0x{:02x}) detected but Jaguar3 " @@ -175,11 +176,11 @@ WiFiDriver::CreateRtlDevice(libusb_device_handle *dev_handle, #if defined(DEVOURER_HAVE_JAGUAR1) _logger->info("Creating RtlJaguarDevice (PID 0x{:04x}, chip-id 0x{:02x})", pid, chip_id); - /* Pass ctx so RtlUsbAdapter::_ctx is set: the Jaguar1 RX loop now drives an + /* Pass ctx so RtlAdapter::_ctx is set: the Jaguar1 RX loop now drives an * async URB queue (bulk_read_async_loop) whose event pump needs the same * libusb context the handle was opened on (as Jaguar2/3 already do above). */ return std::make_unique( - RtlUsbAdapter(dev_handle, _logger, ctx, usb_lock, cfg), _logger, cfg); + RtlAdapter(dev_handle, _logger, ctx, usb_lock, cfg), _logger, cfg); #else _logger->error("Jaguar1 chip (PID 0x{:04x}, chip-id 0x{:02x}) detected but " "Jaguar1 support not compiled in", @@ -207,7 +208,7 @@ std::unique_ptr WiFiDriver::CreateRtlDevicePcie( "0x{:02x})", transport->bdf(), chip_id); return std::make_unique( - RtlUsbAdapter(std::move(transport), _logger, cfg), _logger, + RtlAdapter(std::move(transport), _logger, cfg), _logger, jaguar2::ChipVariant::C8821C, cfg); #else _logger->error("RTL8821C[E] (chip-id 0x09) detected but 8821C support not " diff --git a/src/jaguar1/BbDbgportReader.cpp b/src/jaguar1/BbDbgportReader.cpp index 5448877..466fa2e 100644 --- a/src/jaguar1/BbDbgportReader.cpp +++ b/src/jaguar1/BbDbgportReader.cpp @@ -6,7 +6,7 @@ namespace { constexpr uint16_t kRegSysCfg = 0x00F0; // hal/hal_com_reg.h:101 } // namespace -BbDbgportReader::BbDbgportReader(RtlUsbAdapter& device, Logger_t logger) +BbDbgportReader::BbDbgportReader(RtlAdapter& device, Logger_t logger) : _device{device}, _logger{logger} {} bool BbDbgportReader::is_chip_alive() { diff --git a/src/jaguar1/BbDbgportReader.h b/src/jaguar1/BbDbgportReader.h index 17e3c34..aa9c6ff 100644 --- a/src/jaguar1/BbDbgportReader.h +++ b/src/jaguar1/BbDbgportReader.h @@ -39,7 +39,7 @@ #include -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "logger.h" namespace devourer { @@ -53,7 +53,7 @@ class BbDbgportReader { static constexpr uint16_t kDbgPortSelectorReg = 0x08FC; static constexpr uint16_t kDbgPortReadbackReg = 0x0FA0; - BbDbgportReader(RtlUsbAdapter& device, Logger_t logger); + BbDbgportReader(RtlAdapter& device, Logger_t logger); /* Save 0x8FC, write `selector`, read 0xFA0, restore 0x8FC. Returns the * 32-bit value read from 0xFA0. Pre-condition: caller has already @@ -78,7 +78,7 @@ class BbDbgportReader { bool is_wedged() const { return _wedged; } private: - RtlUsbAdapter& _device; + RtlAdapter& _device; Logger_t _logger; bool _wedged = false; }; diff --git a/src/jaguar1/EepromManager.cpp b/src/jaguar1/EepromManager.cpp index dce577f..376bb7f 100644 --- a/src/jaguar1/EepromManager.cpp +++ b/src/jaguar1/EepromManager.cpp @@ -25,7 +25,7 @@ static int devourer_strcaseeq(const char *a, const char *b) { return *a == *b; } -EepromManager::EepromManager(RtlUsbAdapter device, Logger_t logger, +EepromManager::EepromManager(RtlAdapter device, Logger_t logger, const devourer::DeviceConfig &cfg) : _device{device}, _logger{logger}, _tuning{cfg.tuning}, _log_txpwr{cfg.debug.log_txpwr} { @@ -177,7 +177,7 @@ static bool is_rtl8821a_pid(uint16_t vid, uint16_t pid) { } } -void EepromManager::read_chip_version_8812a(RtlUsbAdapter device) { +void EepromManager::read_chip_version_8812a(RtlAdapter device) { uint32_t value32 = device.rtw_read32(REG_SYS_CFG); _logger->info("read_chip_version_8812a SYS_CFG(0x{:X})=0x{:08X}", REG_SYS_CFG, value32); @@ -1569,7 +1569,7 @@ void EepromManager::Hal_EfuseParseIDCode8812A() { #define EEPROM_DEFAULT_VERSION 0 #define EEPROM_VERSION_8812 0xC4 -uint8_t EepromManager::Hal_ReadPROMVersion8812A(RtlUsbAdapter device, +uint8_t EepromManager::Hal_ReadPROMVersion8812A(RtlAdapter device, uint8_t *efuse_eeprom_data) { uint8_t EEPROMVersion; if (device.AutoloadFailFlag) { @@ -1589,7 +1589,7 @@ uint8_t EepromManager::Hal_ReadPROMVersion8812A(RtlUsbAdapter device, #define EEPROM_RF_BOARD_OPTION_8812 0xC1 #define EEPROM_DEFAULT_BOARD_OPTION 0x00 -uint8_t EepromManager::Hal_ReadTxPowerInfo8812A(RtlUsbAdapter device, +uint8_t EepromManager::Hal_ReadTxPowerInfo8812A(RtlAdapter device, uint8_t *efuse_eeprom_data) { uint8_t EEPROMRegulatory; /* 2010/10/19 MH Add Regulator recognize for CU. */ diff --git a/src/jaguar1/EepromManager.h b/src/jaguar1/EepromManager.h index 0560599..cb4bdd9 100644 --- a/src/jaguar1/EepromManager.h +++ b/src/jaguar1/EepromManager.h @@ -2,7 +2,7 @@ #define EEPROMMANAGER_H #include "PhyTableLoader.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "logger.h" #include "HalVerDef.h" @@ -12,7 +12,7 @@ typedef unsigned short ushort; class EepromManager { - RtlUsbAdapter _device; + RtlAdapter _device; Logger_t _logger; devourer::DeviceConfig::Tuning _tuning; /* regulation / txpwr_by_rate */ bool _log_txpwr = false; /* debug.log_txpwr */ @@ -31,7 +31,7 @@ class EepromManager { uint8_t external_lna_5g; public: - EepromManager(RtlUsbAdapter device, Logger_t logger, + EepromManager(RtlAdapter device, Logger_t logger, const devourer::DeviceConfig &cfg = {}); uint8_t GetBoardType(); void efuse_ShadowRead1Byte(uint16_t Offset, uint8_t *Value); @@ -173,7 +173,7 @@ class EepromManager { void LoadTxPowerLimit(); private: - void read_chip_version_8812a(RtlUsbAdapter device); + void read_chip_version_8812a(RtlAdapter device); void dump_chip_info(HAL_VERSION ChipVersion); void rtw_hal_config_rftype(); void hal_InitPGData_8812A(); @@ -187,9 +187,9 @@ class EepromManager { uint8_t *pbuf); void rtw_dump_cur_efuse(); void Hal_EfuseParseIDCode8812A(); - uint8_t Hal_ReadPROMVersion8812A(RtlUsbAdapter device, + uint8_t Hal_ReadPROMVersion8812A(RtlAdapter device, uint8_t *efuse_eeprom_data); - uint8_t Hal_ReadTxPowerInfo8812A(RtlUsbAdapter device, + uint8_t Hal_ReadTxPowerInfo8812A(RtlAdapter device, uint8_t *efuse_eeprom_data); void Hal_EfuseParseBTCoexistInfo8812A(); void Hal_EfuseParseXtal_8812A(); diff --git a/src/jaguar1/FirmwareManager.cpp b/src/jaguar1/FirmwareManager.cpp index 0fca4e4..fcdff49 100644 --- a/src/jaguar1/FirmwareManager.cpp +++ b/src/jaguar1/FirmwareManager.cpp @@ -54,7 +54,7 @@ auto since(std::chrono::time_point const &start) { return std::chrono::duration_cast(clock_t::now() - start); } -FirmwareManager::FirmwareManager(RtlUsbAdapter device, Logger_t logger, +FirmwareManager::FirmwareManager(RtlAdapter device, Logger_t logger, const devourer::DeviceConfig &cfg) : _device{device}, _logger{logger}, _tuning{cfg.tuning} {} diff --git a/src/jaguar1/FirmwareManager.h b/src/jaguar1/FirmwareManager.h index 72ddfb0..74bd222 100644 --- a/src/jaguar1/FirmwareManager.h +++ b/src/jaguar1/FirmwareManager.h @@ -3,17 +3,17 @@ #include "AdapterHealth.h" #include "HalVerDef.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "logger.h" class FirmwareManager { - RtlUsbAdapter _device; + RtlAdapter _device; Logger_t _logger; devourer::DeviceConfig::Tuning _tuning; /* fwdl_8814 / fwdl_8814_chunk */ devourer::FwBootStatus _boot; /* outcome of the last download */ public: - FirmwareManager(RtlUsbAdapter device, Logger_t logger, + FirmwareManager(RtlAdapter device, Logger_t logger, const devourer::DeviceConfig &cfg = {}); /* Downloads the NIC firmware blob matched to `ic_type`. Header parsing / * download protocol are common across Jaguar (8812 / 8811 / 8814) — only the diff --git a/src/jaguar1/HalModule.cpp b/src/jaguar1/HalModule.cpp index 4b858a1..36cb580 100644 --- a/src/jaguar1/HalModule.cpp +++ b/src/jaguar1/HalModule.cpp @@ -69,7 +69,7 @@ constexpr uint16_t RX_DMA_BOUNDARY_8814A = MAX_RX_DMA_BUFFER_SIZE_8814A - 1; #define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) HalModule::HalModule( - RtlUsbAdapter device, std::shared_ptr eepromManager, + RtlAdapter device, std::shared_ptr eepromManager, std::shared_ptr radioManagementModule, Logger_t logger, const devourer::DeviceConfig &cfg) : _device{device}, _cfg{cfg}, @@ -1988,7 +1988,7 @@ void HalModule::_InitBurstPktLen_8814A() { if (!supportUsb3) { /* USB2/1.1 Mode */ /* Kernel keys this off UsbBulkOutSize (512 on high-speed, 64 on * full-speed). */ - if (_device.speed() == LIBUSB_SPEED_HIGH) { + if (_device.speed() == devourer::kUsbSpeedHigh) { /* set burst pkt len=512B */ _device.rtw_write8(REG_RXDMA_MODE_8814A, 0x1e); } else { diff --git a/src/jaguar1/HalModule.h b/src/jaguar1/HalModule.h index 13321b0..9d9ea1f 100644 --- a/src/jaguar1/HalModule.h +++ b/src/jaguar1/HalModule.h @@ -10,7 +10,7 @@ extern "C"{ } #include "PhydmWatchdog.h" #include "RadioManagementModule.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" #include "logger.h" @@ -39,7 +39,7 @@ enum odm_rf_config_type { }; class HalModule { - RtlUsbAdapter _device; + RtlAdapter _device; devourer::DeviceConfig _cfg; /* phydm_watchdog gate + FirmwareManager forward */ std::shared_ptr _eepromManager; std::shared_ptr _radioManagementModule; @@ -62,7 +62,7 @@ class HalModule { devourer::FwBootStatus _fwBoot; public: - HalModule(RtlUsbAdapter device, std::shared_ptr eepromManager, + HalModule(RtlAdapter device, std::shared_ptr eepromManager, std::shared_ptr _radioManagementModule, Logger_t logger, const devourer::DeviceConfig &cfg = {}); bool rtw_hal_init(SelectedChannel selectedChannel); diff --git a/src/jaguar1/Iqk8812a.cpp b/src/jaguar1/Iqk8812a.cpp index 438a5d6..602c3fc 100644 --- a/src/jaguar1/Iqk8812a.cpp +++ b/src/jaguar1/Iqk8812a.cpp @@ -31,7 +31,7 @@ inline void DelayMs(int ms) { } // namespace -Iqk8812a::Iqk8812a(RtlUsbAdapter device, +Iqk8812a::Iqk8812a(RtlAdapter device, std::shared_ptr eepromManager, RadioManagementModule *radio, Logger_t logger) : _device(device), _eepromManager(eepromManager), _radio(radio), diff --git a/src/jaguar1/Iqk8812a.h b/src/jaguar1/Iqk8812a.h index 5bcf187..6e3836a 100644 --- a/src/jaguar1/Iqk8812a.h +++ b/src/jaguar1/Iqk8812a.h @@ -3,7 +3,7 @@ #include "EepromManager.h" #include "RfPath.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "logger.h" #include @@ -51,7 +51,7 @@ enum class BandType; */ class Iqk8812a { public: - Iqk8812a(RtlUsbAdapter device, std::shared_ptr eepromManager, + Iqk8812a(RtlAdapter device, std::shared_ptr eepromManager, RadioManagementModule *radio, Logger_t logger); /* Run a full I/Q calibration. Channel is used by upstream's `chnl_idx` @@ -62,7 +62,7 @@ class Iqk8812a { void Calibrate(uint8_t channel, BandType band, bool is_recovery); private: - RtlUsbAdapter _device; + RtlAdapter _device; std::shared_ptr _eepromManager; RadioManagementModule *_radio; Logger_t _logger; diff --git a/src/jaguar1/Iqk8814a.cpp b/src/jaguar1/Iqk8814a.cpp index 67342d4..ef0ac75 100644 --- a/src/jaguar1/Iqk8814a.cpp +++ b/src/jaguar1/Iqk8814a.cpp @@ -21,7 +21,7 @@ inline uint32_t Bit(int n) { return 1u << n; } } // namespace -Iqk8814a::Iqk8814a(RtlUsbAdapter device, +Iqk8814a::Iqk8814a(RtlAdapter device, std::shared_ptr eepromManager, RadioManagementModule *radio, Logger_t logger) : _device(device), _eepromManager(eepromManager), _radio(radio), diff --git a/src/jaguar1/Iqk8814a.h b/src/jaguar1/Iqk8814a.h index 89d65ec..95f913d 100644 --- a/src/jaguar1/Iqk8814a.h +++ b/src/jaguar1/Iqk8814a.h @@ -3,7 +3,7 @@ #include "EepromManager.h" #include "RfPath.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "logger.h" #include @@ -42,7 +42,7 @@ enum class BandType; */ class Iqk8814a { public: - Iqk8814a(RtlUsbAdapter device, std::shared_ptr eepromManager, + Iqk8814a(RtlAdapter device, std::shared_ptr eepromManager, RadioManagementModule *radio, Logger_t logger); /* Run a full I/Q calibration for 8814A. is_recovery == false runs @@ -51,7 +51,7 @@ class Iqk8814a { void Calibrate(uint8_t channel, BandType band, bool is_recovery); private: - RtlUsbAdapter _device; + RtlAdapter _device; std::shared_ptr _eepromManager; RadioManagementModule *_radio; Logger_t _logger; diff --git a/src/jaguar1/PhydmWatchdog.cpp b/src/jaguar1/PhydmWatchdog.cpp index e9168f1..f0bd3a3 100644 --- a/src/jaguar1/PhydmWatchdog.cpp +++ b/src/jaguar1/PhydmWatchdog.cpp @@ -37,7 +37,7 @@ constexpr auto kTickInterval = std::chrono::seconds(2); } // namespace -PhydmWatchdog::PhydmWatchdog(RtlUsbAdapter device, +PhydmWatchdog::PhydmWatchdog(RtlAdapter device, std::shared_ptr eepromManager, RadioManagementModule *radio, Logger_t logger) : _device(device), _eepromManager(eepromManager), _radio(radio), diff --git a/src/jaguar1/PhydmWatchdog.h b/src/jaguar1/PhydmWatchdog.h index d69dfbe..4b2797a 100644 --- a/src/jaguar1/PhydmWatchdog.h +++ b/src/jaguar1/PhydmWatchdog.h @@ -2,7 +2,7 @@ #define PHYDM_WATCHDOG_H #include "EepromManager.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "logger.h" #include @@ -41,7 +41,7 @@ class RadioManagementModule; * canary capture sees post-watchdog state). */ class PhydmWatchdog { public: - PhydmWatchdog(RtlUsbAdapter device, + PhydmWatchdog(RtlAdapter device, std::shared_ptr eepromManager, RadioManagementModule *radio, Logger_t logger); ~PhydmWatchdog(); @@ -92,7 +92,7 @@ class PhydmWatchdog { void DigTick(uint32_t fa_cnt); void DigWriteIgi(uint8_t igi); - RtlUsbAdapter _device; + RtlAdapter _device; std::shared_ptr _eepromManager; RadioManagementModule *_radio; Logger_t _logger; diff --git a/src/jaguar1/PowerTracking8812a.cpp b/src/jaguar1/PowerTracking8812a.cpp index 9b09911..10f3ce7 100644 --- a/src/jaguar1/PowerTracking8812a.cpp +++ b/src/jaguar1/PowerTracking8812a.cpp @@ -6,7 +6,7 @@ #include "RfPath.h" PowerTracking8812a::PowerTracking8812a( - RtlUsbAdapter device, std::shared_ptr eepromManager, + RtlAdapter device, std::shared_ptr eepromManager, RadioManagementModule *radio, Logger_t logger) : _device(device), _eepromManager(eepromManager), _radio(radio), _logger(logger) {} diff --git a/src/jaguar1/PowerTracking8812a.h b/src/jaguar1/PowerTracking8812a.h index 87be98a..262b83e 100644 --- a/src/jaguar1/PowerTracking8812a.h +++ b/src/jaguar1/PowerTracking8812a.h @@ -3,7 +3,7 @@ #include "EepromManager.h" #include "RfPath.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" #include "logger.h" @@ -54,7 +54,7 @@ class RadioManagementModule; */ class PowerTracking8812a { public: - PowerTracking8812a(RtlUsbAdapter device, + PowerTracking8812a(RtlAdapter device, std::shared_ptr eepromManager, RadioManagementModule *radio, Logger_t logger); @@ -79,7 +79,7 @@ class PowerTracking8812a { void ClearState(); private: - RtlUsbAdapter _device; + RtlAdapter _device; std::shared_ptr _eepromManager; RadioManagementModule *_radio; /* non-owning back-pointer for RF reads */ Logger_t _logger; diff --git a/src/jaguar1/RadioManagementModule.cpp b/src/jaguar1/RadioManagementModule.cpp index 0619845..4e7ab91 100644 --- a/src/jaguar1/RadioManagementModule.cpp +++ b/src/jaguar1/RadioManagementModule.cpp @@ -48,7 +48,7 @@ int get_40mhz_center_channel(int channel) { } RadioManagementModule::RadioManagementModule( - RtlUsbAdapter device, std::shared_ptr eepromManager, + RtlAdapter device, std::shared_ptr eepromManager, Logger_t logger, const devourer::DeviceConfig &cfg) : _device{device}, _tuning{cfg.tuning}, _keep_corrupted{cfg.rx.keep_corrupted}, diff --git a/src/jaguar1/RadioManagementModule.h b/src/jaguar1/RadioManagementModule.h index dbc4c40..033e760 100644 --- a/src/jaguar1/RadioManagementModule.h +++ b/src/jaguar1/RadioManagementModule.h @@ -12,7 +12,7 @@ #endif #include "PowerTracking8812a.h" #include "RfPath.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" #include "logger.h" @@ -62,7 +62,7 @@ using ThermalStatus = devourer::ThermalStatus; using devourer::ThermalBucket; class RadioManagementModule { - RtlUsbAdapter _device; + RtlAdapter _device; devourer::DeviceConfig::Tuning _tuning; /* skip_txpwr / force_iqk / disable_iqk */ bool _keep_corrupted = false; /* rx.keep_corrupted */ bool _dump_canary = false; /* debug.dump_canary */ @@ -125,7 +125,7 @@ class RadioManagementModule { #endif public: - RadioManagementModule(RtlUsbAdapter device, + RadioManagementModule(RtlAdapter device, std::shared_ptr eepromManager, Logger_t logger, const devourer::DeviceConfig &cfg = {}); diff --git a/src/jaguar1/RtlJaguarDevice.cpp b/src/jaguar1/RtlJaguarDevice.cpp index 2039f87..2131243 100644 --- a/src/jaguar1/RtlJaguarDevice.cpp +++ b/src/jaguar1/RtlJaguarDevice.cpp @@ -49,7 +49,7 @@ static constexpr uint16_t RF_LNA_LOW_GAIN_3 = 0x58; static constexpr uint16_t rC_TxScale_8814 = 0x181C; static constexpr uint16_t rD_TxScale_8814 = 0x1A1C; -RtlJaguarDevice::RtlJaguarDevice(RtlUsbAdapter device, Logger_t logger, +RtlJaguarDevice::RtlJaguarDevice(RtlAdapter device, Logger_t logger, devourer::DeviceConfig cfg) : _cfg{std::move(cfg)}, _eepromManager{std::make_shared(device, logger, _cfg)}, diff --git a/src/jaguar1/RtlJaguarDevice.h b/src/jaguar1/RtlJaguarDevice.h index 3398b29..aab7bee 100644 --- a/src/jaguar1/RtlJaguarDevice.h +++ b/src/jaguar1/RtlJaguarDevice.h @@ -44,7 +44,7 @@ class RtlJaguarDevice : public IRtlDevice { * 5GHz CCK clamp in send_packet reads Channel=0 (clamp off) rather than * indeterminate garbage before the first channel set. */ SelectedChannel _channel{}; - RtlUsbAdapter _device; + RtlAdapter _device; HalModule _halModule; Logger_t _logger; uint8_t debug; @@ -75,7 +75,7 @@ class RtlJaguarDevice : public IRtlDevice { devourer::RxQualityAccumulator _rxq; public: - RtlJaguarDevice(RtlUsbAdapter device, Logger_t logger, + RtlJaguarDevice(RtlAdapter device, Logger_t logger, devourer::DeviceConfig cfg = {}); ~RtlJaguarDevice() override; void Init(Action_ParsedRadioPacket packetProcessor, diff --git a/src/jaguar2/HalJaguar2.cpp b/src/jaguar2/HalJaguar2.cpp index d3192e9..6de94ee 100644 --- a/src/jaguar2/HalJaguar2.cpp +++ b/src/jaguar2/HalJaguar2.cpp @@ -184,7 +184,7 @@ const PwrCfg kPwrOff8821cPcie[] = { }; #endif /* DEVOURER_HAVE_PCIE */ -void run_pwr_seq(RtlUsbAdapter &dev, const PwrCfg *seq, uint32_t poll_max, +void run_pwr_seq(RtlAdapter &dev, const PwrCfg *seq, uint32_t poll_max, bool poll_fatal) { /* rtw88 rtw_pwr_cmd_polling PCIe quirk: a timed-out POLL step is retried * once after toggling BIT_PFM_WOWL in REG_SYS_PW_CTRL (0x04[3]) set->clear — @@ -221,7 +221,7 @@ void run_pwr_seq(RtlUsbAdapter &dev, const PwrCfg *seq, uint32_t poll_max, } } /* namespace */ -HalJaguar2::HalJaguar2(RtlUsbAdapter device, Logger_t logger, +HalJaguar2::HalJaguar2(RtlAdapter device, Logger_t logger, ChipVariant variant, const devourer::DeviceConfig &cfg) : _device{std::move(device)}, _logger{std::move(logger)}, _cfg{cfg}, _variant{variant}, _tables{make_jaguar2_phy_tables(variant)} {} diff --git a/src/jaguar2/HalJaguar2.h b/src/jaguar2/HalJaguar2.h index d71a863..f554963 100644 --- a/src/jaguar2/HalJaguar2.h +++ b/src/jaguar2/HalJaguar2.h @@ -6,7 +6,7 @@ #include #include "logger.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "RxSense.h" #include "ChipVariant.h" #include "Jaguar2PhyTables.h" @@ -19,7 +19,7 @@ namespace jaguar2 { * selected via ChipVariant. */ class HalJaguar2 { public: - HalJaguar2(RtlUsbAdapter device, Logger_t logger, + HalJaguar2(RtlAdapter device, Logger_t logger, ChipVariant variant = ChipVariant::C8822B, const devourer::DeviceConfig &cfg = {}); @@ -275,7 +275,7 @@ class HalJaguar2 { /* _phy_lc_calibrate_8821c: RF-firmware LCK (RF 0xcc/0xc4), no poll. */ void do_lck_8821c(); - RtlUsbAdapter _device; + RtlAdapter _device; devourer::DeviceConfig _cfg; /* dump_canary / efuse_dump / hop_prof / phy_status_8821c / igi / keep_corrupted */ Logger_t _logger; diff --git a/src/jaguar2/HalmacJaguar2Fw.cpp b/src/jaguar2/HalmacJaguar2Fw.cpp index 6f526ef..ca5d5cb 100644 --- a/src/jaguar2/HalmacJaguar2Fw.cpp +++ b/src/jaguar2/HalmacJaguar2Fw.cpp @@ -31,7 +31,7 @@ void delay_us(uint32_t us) { } } /* namespace */ -HalmacJaguar2Fw::HalmacJaguar2Fw(RtlUsbAdapter device, Logger_t logger, +HalmacJaguar2Fw::HalmacJaguar2Fw(RtlAdapter device, Logger_t logger, ChipVariant variant) : _device{device}, _logger{std::move(logger)}, _variant{variant} {} diff --git a/src/jaguar2/HalmacJaguar2Fw.h b/src/jaguar2/HalmacJaguar2Fw.h index 336061e..c211536 100644 --- a/src/jaguar2/HalmacJaguar2Fw.h +++ b/src/jaguar2/HalmacJaguar2Fw.h @@ -6,7 +6,7 @@ #include "AdapterHealth.h" #include "logger.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "ChipVariant.h" namespace jaguar2 { @@ -24,7 +24,7 @@ namespace jaguar2 { * have run first — supplied by HalmacJaguar2MacInit. */ class HalmacJaguar2Fw { public: - HalmacJaguar2Fw(RtlUsbAdapter device, Logger_t logger, + HalmacJaguar2Fw(RtlAdapter device, Logger_t logger, ChipVariant variant = ChipVariant::C8822B); bool download_firmware(const uint8_t *fw_bin, size_t size); @@ -74,7 +74,7 @@ class HalmacJaguar2Fw { void w32(uint16_t reg, uint32_t v); void w32_set(uint16_t reg, uint32_t bits); - RtlUsbAdapter _device; + RtlAdapter _device; Logger_t _logger; ChipVariant _variant; devourer::FwBootStatus _boot; /* last download_firmware outcome */ diff --git a/src/jaguar2/HalmacJaguar2MacInit.cpp b/src/jaguar2/HalmacJaguar2MacInit.cpp index b573874..4c0a62f 100644 --- a/src/jaguar2/HalmacJaguar2MacInit.cpp +++ b/src/jaguar2/HalmacJaguar2MacInit.cpp @@ -5,7 +5,7 @@ #include /* These names are also #define macros in the Realtek hal_com_reg.h pulled in via - * RtlUsbAdapter.h; #undef them so the scoped constexpr below compile (this .cpp + * RtlAdapter.h; #undef them so the scoped constexpr below compile (this .cpp * uses no other definition of them). */ #undef REG_RSV_CTRL #undef REG_RF_CTRL @@ -243,7 +243,7 @@ bool is_10m(ChannelWidth_t bw) { } } /* namespace */ -HalmacJaguar2MacInit::HalmacJaguar2MacInit(RtlUsbAdapter device, Logger_t logger, +HalmacJaguar2MacInit::HalmacJaguar2MacInit(RtlAdapter device, Logger_t logger, ChipVariant variant) : _device{std::move(device)}, _logger{std::move(logger)}, _variant{variant} {} diff --git a/src/jaguar2/HalmacJaguar2MacInit.h b/src/jaguar2/HalmacJaguar2MacInit.h index 98e8938..3890cbe 100644 --- a/src/jaguar2/HalmacJaguar2MacInit.h +++ b/src/jaguar2/HalmacJaguar2MacInit.h @@ -4,7 +4,7 @@ #include #include "logger.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" #include "ChipVariant.h" @@ -18,7 +18,7 @@ namespace jaguar2 { * chips diverge. */ class HalmacJaguar2MacInit { public: - HalmacJaguar2MacInit(RtlUsbAdapter device, Logger_t logger, + HalmacJaguar2MacInit(RtlAdapter device, Logger_t logger, ChipVariant variant = ChipVariant::C8822B); /* Pinmux / LED / GPIO + BB-RF disabled. Runs BEFORE power_on. */ @@ -69,7 +69,7 @@ class HalmacJaguar2MacInit { void init_edca_cfg(); void init_wmac_cfg(); - RtlUsbAdapter _device; + RtlAdapter _device; Logger_t _logger; ChipVariant _variant; uint16_t _rsvd_boundary = 0; diff --git a/src/jaguar2/HalmacJaguar2Regs.h b/src/jaguar2/HalmacJaguar2Regs.h index 6e0bf28..3ee6eb3 100644 --- a/src/jaguar2/HalmacJaguar2Regs.h +++ b/src/jaguar2/HalmacJaguar2Regs.h @@ -11,7 +11,7 @@ * 0x0204, H2CQ_CSR 0x1330, RQPN_CTRL_2 0x022C, ...). * * As in HalmacJaguar3Regs.h, several names collide with #defines from the - * Realtek hal_com_reg.h pulled in via RtlUsbAdapter.h; #undef them first and + * Realtek hal_com_reg.h pulled in via RtlAdapter.h; #undef them first and * re-introduce as scoped constexpr. Included ONLY by HalmacJaguar2Fw.cpp. */ #undef REG_SYS_FUNC_EN #undef REG_SYS_CLK_CTRL diff --git a/src/jaguar2/Halrf8821c.cpp b/src/jaguar2/Halrf8821c.cpp index bb5088a..95c2a82 100644 --- a/src/jaguar2/Halrf8821c.cpp +++ b/src/jaguar2/Halrf8821c.cpp @@ -34,7 +34,7 @@ uint32_t bshift(uint32_t mask) { } } /* namespace */ -Halrf8821c::Halrf8821c(RtlUsbAdapter device, Logger_t logger, uint8_t cut, +Halrf8821c::Halrf8821c(RtlAdapter device, Logger_t logger, uint8_t cut, bool is_2t2r) : _device{std::move(device)}, _logger{std::move(logger)}, _cut{cut}, _2t2r{is_2t2r} {} @@ -850,7 +850,7 @@ void Halrf8821c::iqk_trigger(bool band2g) { /* Calibration-factory hook, called by make_jaguar2_calibration() in * Halrf8822b.cpp when the ChipVariant is C8821C. */ std::unique_ptr -make_calibration_8821c(RtlUsbAdapter device, Logger_t logger, uint8_t cut, +make_calibration_8821c(RtlAdapter device, Logger_t logger, uint8_t cut, bool is_2t2r) { return std::make_unique(std::move(device), std::move(logger), cut, is_2t2r); diff --git a/src/jaguar2/Halrf8821c.h b/src/jaguar2/Halrf8821c.h index 2a7f4f3..4c5b58e 100644 --- a/src/jaguar2/Halrf8821c.h +++ b/src/jaguar2/Halrf8821c.h @@ -4,7 +4,7 @@ #include #include "logger.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "Jaguar2Calibration.h" namespace jaguar2 { @@ -30,7 +30,7 @@ namespace jaguar2 { * pass with retries capped exactly as the vendor caps them. */ class Halrf8821c : public Jaguar2Calibration { public: - Halrf8821c(RtlUsbAdapter device, Logger_t logger, uint8_t cut, bool is_2t2r); + Halrf8821c(RtlAdapter device, Logger_t logger, uint8_t cut, bool is_2t2r); /* phy_iq_calibrate_8821c entry (SW path). band2g: true=2.4G. */ void iqk_trigger(bool band2g) override; @@ -80,7 +80,7 @@ class Halrf8821c : public Jaguar2Calibration { void iqk_init(); void fill_iqk_report(); - RtlUsbAdapter _device; + RtlAdapter _device; Logger_t _logger; uint8_t _cut; bool _2t2r; diff --git a/src/jaguar2/Halrf8822b.cpp b/src/jaguar2/Halrf8822b.cpp index 352186c..c03d1be 100644 --- a/src/jaguar2/Halrf8822b.cpp +++ b/src/jaguar2/Halrf8822b.cpp @@ -24,7 +24,7 @@ uint32_t bshift(uint32_t mask) { } } /* namespace */ -Halrf8822b::Halrf8822b(RtlUsbAdapter device, Logger_t logger, uint8_t cut, +Halrf8822b::Halrf8822b(RtlAdapter device, Logger_t logger, uint8_t cut, bool is_2t2r) : _device{std::move(device)}, _logger{std::move(logger)}, _cut{cut}, _2t2r{is_2t2r} {} @@ -680,12 +680,12 @@ void Halrf8822b::iqk_trigger(bool band2g) { #if defined(DEVOURER_HAVE_JAGUAR2_8821C) /* Defined in Halrf8821c.cpp. */ std::unique_ptr -make_calibration_8821c(RtlUsbAdapter device, Logger_t logger, uint8_t cut, +make_calibration_8821c(RtlAdapter device, Logger_t logger, uint8_t cut, bool is_2t2r); #endif std::unique_ptr -make_jaguar2_calibration(ChipVariant variant, RtlUsbAdapter device, +make_jaguar2_calibration(ChipVariant variant, RtlAdapter device, Logger_t logger, uint8_t cut, bool is_2t2r) { #if defined(DEVOURER_HAVE_JAGUAR2_8821C) if (variant == ChipVariant::C8821C) diff --git a/src/jaguar2/Halrf8822b.h b/src/jaguar2/Halrf8822b.h index f42cbf5..51f7181 100644 --- a/src/jaguar2/Halrf8822b.h +++ b/src/jaguar2/Halrf8822b.h @@ -4,7 +4,7 @@ #include #include "logger.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "Jaguar2Calibration.h" namespace jaguar2 { @@ -23,7 +23,7 @@ namespace jaguar2 { * / rtw_read32. */ class Halrf8822b : public Jaguar2Calibration { public: - Halrf8822b(RtlUsbAdapter device, Logger_t logger, uint8_t cut, bool is_2t2r); + Halrf8822b(RtlAdapter device, Logger_t logger, uint8_t cut, bool is_2t2r); /* phy_iq_calibrate_8822b entry (SW path). band2g: true=2.4G. Runs the full * per-path LOK/TXK/RXK, backing up and restoring MAC/BB/RF around it. */ @@ -74,7 +74,7 @@ class Halrf8822b : public Jaguar2Calibration { void start_iqk(); void iqk_init(); - RtlUsbAdapter _device; + RtlAdapter _device; Logger_t _logger; uint8_t _cut; bool _2t2r; diff --git a/src/jaguar2/Jaguar2Calibration.h b/src/jaguar2/Jaguar2Calibration.h index b239bf8..1dc8d86 100644 --- a/src/jaguar2/Jaguar2Calibration.h +++ b/src/jaguar2/Jaguar2Calibration.h @@ -5,7 +5,7 @@ #include #include "ChipVariant.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "logger.h" namespace jaguar2 { @@ -32,7 +32,7 @@ class Jaguar2Calibration { * from HalJaguar2::chip_version(), so this is called during bring-up (after * read_chip_version), not at device construction. */ std::unique_ptr -make_jaguar2_calibration(ChipVariant variant, RtlUsbAdapter device, +make_jaguar2_calibration(ChipVariant variant, RtlAdapter device, Logger_t logger, uint8_t cut, bool is_2t2r); } /* namespace jaguar2 */ diff --git a/src/jaguar2/RtlJaguar2Device.cpp b/src/jaguar2/RtlJaguar2Device.cpp index 8dd4b3c..9e00871 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -1,5 +1,6 @@ #include "RtlJaguar2Device.h" +#include #include #include #include @@ -20,14 +21,11 @@ #include "RateDefinitions.h" #include "RxPacket.h" #include "SignalStop.h" /* g_devourer_should_stop */ -#if defined(DEVOURER_HAVE_PCIE) -#include "PcieTransport.h" /* setup_trx_rings in the PCIe bring-up gate */ -#endif extern "C" { #include "ieee80211_radiotap.h" /* MRateToHwRate + radiotap iterator */ } -RtlJaguar2Device::RtlJaguar2Device(RtlUsbAdapter device, Logger_t logger, +RtlJaguar2Device::RtlJaguar2Device(RtlAdapter device, Logger_t logger, jaguar2::ChipVariant variant, devourer::DeviceConfig cfg) : _device{device}, _cfg{std::move(cfg)}, _logger{logger}, @@ -59,23 +57,19 @@ void RtlJaguar2Device::bring_up(SelectedChannel channel) { * pre-init -> power-on -> system-cfg -> DLFW, giving the chip a clean slate. */ bool fw_ok = false; constexpr int kBringupTries = 4; -#if defined(DEVOURER_HAVE_PCIE) /* rtw88 applies the PCIe interface-PHY MDIO config once at probe. */ if (!_device.is_usb()) _macinit.pcie_phy_cfg(); -#endif for (int attempt = 0; attempt < kBringupTries && !fw_ok; attempt++) { if (attempt > 0) _logger->error("RtlJaguar2Device: DLFW failed — full power-cycle retry " "{}/{}", attempt + 1, kBringupTries); -#if defined(DEVOURER_HAVE_PCIE) - /* rtw88 order: rtw_hci_setup (TRX buffer-descriptor ring registers) - * precedes rtw_mac_power_on — and the DLFW rsvd-page path needs the BCN - * ring live. Re-programmed per attempt (the power seq touches 0x300). */ - if (!_device.is_usb()) - _device.pcie()->setup_trx_rings(); -#endif + /* rtw88 order: rtw_hci_setup precedes rtw_mac_power_on — on PCIe this + * programs the TRX buffer-descriptor ring registers (the DLFW rsvd-page + * path needs the BCN ring live), and it re-runs per attempt because the + * power seq touches 0x300. No-op on USB. */ + _device.hci_setup(); _macinit.pre_init_system_cfg(); _hal.power_on(); _hal.read_chip_version(); diff --git a/src/jaguar2/RtlJaguar2Device.h b/src/jaguar2/RtlJaguar2Device.h index afc4dba..2f342a3 100644 --- a/src/jaguar2/RtlJaguar2Device.h +++ b/src/jaguar2/RtlJaguar2Device.h @@ -8,7 +8,7 @@ #include "logger.h" #include "IRtlDevice.h" #include "TxMode.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" #include "HalJaguar2.h" #include "HalmacJaguar2MacInit.h" @@ -34,7 +34,7 @@ * Jaguar3 8822C/8822E HAL. */ class RtlJaguar2Device : public IRtlDevice { public: - RtlJaguar2Device(RtlUsbAdapter device, Logger_t logger, + RtlJaguar2Device(RtlAdapter device, Logger_t logger, jaguar2::ChipVariant variant = jaguar2::ChipVariant::C8822B, devourer::DeviceConfig cfg = {}); ~RtlJaguar2Device() override; @@ -141,7 +141,7 @@ class RtlJaguar2Device : public IRtlDevice { } private: - RtlUsbAdapter _device; + RtlAdapter _device; const devourer::DeviceConfig _cfg; /* Rolling per-frame RX link-quality aggregate (GetRxQuality). */ devourer::RxQualityAccumulator _rxq; diff --git a/src/jaguar3/HalJaguar3.cpp b/src/jaguar3/HalJaguar3.cpp index e0bf5a9..8f45635 100644 --- a/src/jaguar3/HalJaguar3.cpp +++ b/src/jaguar3/HalJaguar3.cpp @@ -38,7 +38,7 @@ void retry_cal(Logger_t &logger, const char *what, F &&step, int tries = 3) { /* Writer for BB / AGC tables: handles the 0xf9..0xfe pseudo-address delay * encoding (see odm_config_bb_phy_8822c) and otherwise does a full-dword BB * write. */ -void write_bb(RtlUsbAdapter &dev, uint32_t addr, uint32_t data) { +void write_bb(RtlAdapter &dev, uint32_t addr, uint32_t data) { switch (addr) { case 0xfe: std::this_thread::sleep_for(std::chrono::milliseconds(50)); return; case 0xfd: std::this_thread::sleep_for(std::chrono::milliseconds(5)); return; @@ -51,7 +51,7 @@ void write_bb(RtlUsbAdapter &dev, uint32_t addr, uint32_t data) { } } /* namespace */ -HalJaguar3::HalJaguar3(RtlUsbAdapter device, Logger_t logger, +HalJaguar3::HalJaguar3(RtlAdapter device, Logger_t logger, ChipVariant variant, const devourer::DeviceConfig &cfg) : _device{device}, _cfg{cfg}, _logger{logger}, _fw{device, logger, variant}, _macinit{device, logger}, @@ -507,7 +507,7 @@ void HalJaguar3::read_chip_version() { * of enable/disable_efuse_sw_pwr_cut (halmac_efuse_8822e.c), read variant * (is_write=0, so the PMC unlock-code write is skipped). The 8822E OTP is only * readable while EV2EF power is cut in to the efuse macro via REG_SYS_ISO_CTRL; - * the 8822C/Jaguar1 reader (RtlUsbAdapter::efuse_OneByteRead) never does this, so + * the 8822C/Jaguar1 reader (RtlAdapter::efuse_OneByteRead) never does this, so * 8822E efuse reads return 0xff. Bracket every physical read range with on/off. */ void HalJaguar3::efuse_pwr_cut_8822e(bool on) { constexpr uint16_t SYS_ISO = 0x0000, PMC = 0x00CC; diff --git a/src/jaguar3/HalJaguar3.h b/src/jaguar3/HalJaguar3.h index cc98014..2644cd7 100644 --- a/src/jaguar3/HalJaguar3.h +++ b/src/jaguar3/HalJaguar3.h @@ -5,7 +5,7 @@ #include "AdapterHealth.h" #include "logger.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" #include "ChipVariant.h" #include "Jaguar3PhyTables.h" @@ -25,7 +25,7 @@ namespace jaguar3 { * HalMAC port in HalmacJaguar3Fw. */ class HalJaguar3 { public: - HalJaguar3(RtlUsbAdapter device, Logger_t logger, + HalJaguar3(RtlAdapter device, Logger_t logger, ChipVariant variant = ChipVariant::C8822C, const devourer::DeviceConfig &cfg = {}); @@ -195,7 +195,7 @@ class HalJaguar3 { uint8_t _efuse_cache[0x100]; bool _efuse_cache_valid = false; - RtlUsbAdapter _device; + RtlAdapter _device; devourer::DeviceConfig _cfg; /* skip_iqk + calibration forward */ Logger_t _logger; HalmacJaguar3Fw _fw; diff --git a/src/jaguar3/HalmacJaguar3Fw.cpp b/src/jaguar3/HalmacJaguar3Fw.cpp index 5ba008e..d7bf16d 100644 --- a/src/jaguar3/HalmacJaguar3Fw.cpp +++ b/src/jaguar3/HalmacJaguar3Fw.cpp @@ -32,7 +32,7 @@ void delay_us(uint32_t us) { } } /* namespace */ -HalmacJaguar3Fw::HalmacJaguar3Fw(RtlUsbAdapter device, Logger_t logger, +HalmacJaguar3Fw::HalmacJaguar3Fw(RtlAdapter device, Logger_t logger, ChipVariant variant) : _device{device}, _logger{std::move(logger)}, _variant{variant} {} diff --git a/src/jaguar3/HalmacJaguar3Fw.h b/src/jaguar3/HalmacJaguar3Fw.h index 3f05dcd..e2f2145 100644 --- a/src/jaguar3/HalmacJaguar3Fw.h +++ b/src/jaguar3/HalmacJaguar3Fw.h @@ -6,7 +6,7 @@ #include "AdapterHealth.h" #include "logger.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "ChipVariant.h" namespace jaguar3 { @@ -27,7 +27,7 @@ namespace jaguar3 { * confirmed via usbmon diff vs the kernel rtl88x2eu driver. */ class HalmacJaguar3Fw { public: - HalmacJaguar3Fw(RtlUsbAdapter device, Logger_t logger, + HalmacJaguar3Fw(RtlAdapter device, Logger_t logger, ChipVariant variant = ChipVariant::C8822C); /* Download + boot the WLAN firmware image (fw_bin/size = the full @@ -72,7 +72,7 @@ class HalmacJaguar3Fw { void w32(uint16_t reg, uint32_t v); void w32_set(uint16_t reg, uint32_t bits); - RtlUsbAdapter _device; + RtlAdapter _device; Logger_t _logger; ChipVariant _variant; /* selects the firmware blob (8822c vs 8822e) */ devourer::FwBootStatus _boot; /* last download_firmware outcome */ diff --git a/src/jaguar3/HalmacJaguar3MacInit.cpp b/src/jaguar3/HalmacJaguar3MacInit.cpp index eec2547..13102b9 100644 --- a/src/jaguar3/HalmacJaguar3MacInit.cpp +++ b/src/jaguar3/HalmacJaguar3MacInit.cpp @@ -361,7 +361,7 @@ inline bool is_5m(ChannelWidth_t b) { return b == CHANNEL_WIDTH_5; } inline bool is_10m(ChannelWidth_t b) { return b == CHANNEL_WIDTH_10; } } /* namespace */ -HalmacJaguar3MacInit::HalmacJaguar3MacInit(RtlUsbAdapter device, Logger_t logger) +HalmacJaguar3MacInit::HalmacJaguar3MacInit(RtlAdapter device, Logger_t logger) : _device{device}, _logger{logger} {} /* enable_bb_rf_88xx */ diff --git a/src/jaguar3/HalmacJaguar3MacInit.h b/src/jaguar3/HalmacJaguar3MacInit.h index 7a2c090..07bff8e 100644 --- a/src/jaguar3/HalmacJaguar3MacInit.h +++ b/src/jaguar3/HalmacJaguar3MacInit.h @@ -2,7 +2,7 @@ #define HALMAC_8822C_MAC_INIT_H #include "logger.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" namespace jaguar3 { @@ -18,7 +18,7 @@ namespace jaguar3 { * correct MAC timing (not just the baseband divider). */ class HalmacJaguar3MacInit { public: - HalmacJaguar3MacInit(RtlUsbAdapter device, Logger_t logger); + HalmacJaguar3MacInit(RtlAdapter device, Logger_t logger); /* pre_init_system_cfg_8822c: pinmux / LED / GPIO + BB-RF disabled. Runs * BEFORE the mac power switch (card_en_flow). */ @@ -52,7 +52,7 @@ class HalmacJaguar3MacInit { void init_rate_fallback_ctrl(); void cfg_mac_clk(); - RtlUsbAdapter _device; + RtlAdapter _device; Logger_t _logger; }; diff --git a/src/jaguar3/HalmacJaguar3Regs.h b/src/jaguar3/HalmacJaguar3Regs.h index 51ac5d2..078a636 100644 --- a/src/jaguar3/HalmacJaguar3Regs.h +++ b/src/jaguar3/HalmacJaguar3Regs.h @@ -15,7 +15,7 @@ * minimal-subset philosophy. Only what HalmacJaguar3Fw uses lives here. * * Several of these names (REG_CR, REG_BCN_CTRL, ...) are also #define macros in - * the Realtek hal_com_reg.h pulled in via RtlUsbAdapter.h. The preprocessor + * the Realtek hal_com_reg.h pulled in via RtlAdapter.h. The preprocessor * ignores namespaces, so we #undef them first and re-introduce them as scoped * constexpr constants. This header is included ONLY by HalmacJaguar3Fw.cpp (which * uses no other definition of these names), so the #undef is local and safe. */ diff --git a/src/jaguar3/Halrf8822c.cpp b/src/jaguar3/Halrf8822c.cpp index 7ae8cd3..1cff0cc 100644 --- a/src/jaguar3/Halrf8822c.cpp +++ b/src/jaguar3/Halrf8822c.cpp @@ -40,20 +40,20 @@ inline uint32_t mask_shift(uint32_t mask) { } } /* namespace */ -Halrf8822c::Halrf8822c(RtlUsbAdapter device, Logger_t logger) +Halrf8822c::Halrf8822c(RtlAdapter device, Logger_t logger) : _device{device}, _logger{logger} {} #endif /* DEVOURER_HAVE_JAGUAR3_8822C */ #if defined(DEVOURER_HAVE_JAGUAR3_8822E) /* Defined in Halrf8822e.cpp. */ std::unique_ptr -make_halrf_8822e(RtlUsbAdapter device, Logger_t logger, +make_halrf_8822e(RtlAdapter device, Logger_t logger, const devourer::DeviceConfig &cfg); #endif /* Calibration strategy factory: pick the per-generation halrf impl. */ std::unique_ptr -make_jaguar3_calibration(ChipVariant variant, RtlUsbAdapter device, +make_jaguar3_calibration(ChipVariant variant, RtlAdapter device, Logger_t logger, const devourer::DeviceConfig &cfg) { #if defined(DEVOURER_HAVE_JAGUAR3_8822E) if (variant == ChipVariant::C8822E) diff --git a/src/jaguar3/Halrf8822c.h b/src/jaguar3/Halrf8822c.h index 6161fcd..2c1e5df 100644 --- a/src/jaguar3/Halrf8822c.h +++ b/src/jaguar3/Halrf8822c.h @@ -4,7 +4,7 @@ #include #include "logger.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" #include "Jaguar3Calibration.h" @@ -18,12 +18,12 @@ namespace jaguar3 { * across boots (the cause of the flaky structured-path RX). * * This is a large multi-part port. The phydm dm_struct is replaced by a thin - * shim over RtlUsbAdapter: BB/MAC registers via rtw_read32/write32, RF registers + * shim over RtlAdapter: BB/MAC registers via rtw_read32/write32, RF registers * via the 8822C direct window (config_phydm_*_rf_reg_8822c: BB[0x3c00|0x4c00 + * (addr<<2)], 20-bit). The dm->IQK_info calibration state lives in IqkInfo. */ class Halrf8822c : public Jaguar3Calibration { public: - Halrf8822c(RtlUsbAdapter device, Logger_t logger); + Halrf8822c(RtlAdapter device, Logger_t logger); /* phy_iq_calibrate_8822c entry — runs the full IQK (backup -> setup -> LOK/ * TXK/RXK per path -> restore -> fill report). bw selects NB-IQK (5/10 MHz); @@ -156,7 +156,7 @@ class Halrf8822c : public Jaguar3Calibration { void restore_mac_bb(const uint32_t *mac, const uint32_t *bb); void restore_rf(const uint32_t rf[][2]); - RtlUsbAdapter _device; + RtlAdapter _device; Logger_t _logger; /* pwr_track thermal reference (per RF path), captured on the first pwr_track() diff --git a/src/jaguar3/Halrf8822e.cpp b/src/jaguar3/Halrf8822e.cpp index e397aeb..6f75391 100644 --- a/src/jaguar3/Halrf8822e.cpp +++ b/src/jaguar3/Halrf8822e.cpp @@ -35,7 +35,7 @@ uint32_t mask_shift(uint32_t mask) { } } /* namespace */ -Halrf8822e::Halrf8822e(RtlUsbAdapter device, Logger_t logger, +Halrf8822e::Halrf8822e(RtlAdapter device, Logger_t logger, const devourer::DeviceConfig &cfg) : _device{device}, _logger{logger}, _skip_txgapk{cfg.tuning.skip_txgapk}, _gaintab_dbg{cfg.debug.gaintab_dbg} {} @@ -1636,7 +1636,7 @@ void Halrf8822e::do_txgapk(uint8_t channel) { /* Maker, dispatched from make_jaguar3_calibration() in Halrf8822c.cpp. */ std::unique_ptr -make_halrf_8822e(RtlUsbAdapter device, Logger_t logger, +make_halrf_8822e(RtlAdapter device, Logger_t logger, const devourer::DeviceConfig &cfg) { return std::make_unique(device, logger, cfg); } diff --git a/src/jaguar3/Halrf8822e.h b/src/jaguar3/Halrf8822e.h index 2ca0b88..16bed0b 100644 --- a/src/jaguar3/Halrf8822e.h +++ b/src/jaguar3/Halrf8822e.h @@ -4,7 +4,7 @@ #include #include "logger.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" #include "Jaguar3Calibration.h" @@ -26,7 +26,7 @@ namespace jaguar3 { */ class Halrf8822e : public Jaguar3Calibration { public: - Halrf8822e(RtlUsbAdapter device, Logger_t logger, + Halrf8822e(RtlAdapter device, Logger_t logger, const devourer::DeviceConfig &cfg = {}); void phy_iq_calibrate(ChannelWidth_t bw, uint8_t channel) override; @@ -242,7 +242,7 @@ class Halrf8822e : public Jaguar3Calibration { uint8_t _therm_avg_cnt[2] = {0, 0}; int _last_swing[2] = {0x7fff, 0x7fff}; /* log-on-change sentinel */ - RtlUsbAdapter _device; + RtlAdapter _device; Logger_t _logger; bool _skip_txgapk = false; /* tuning.skip_txgapk */ bool _gaintab_dbg = false; /* debug.gaintab_dbg */ diff --git a/src/jaguar3/Jaguar3Calibration.h b/src/jaguar3/Jaguar3Calibration.h index 70a3a55..288424c 100644 --- a/src/jaguar3/Jaguar3Calibration.h +++ b/src/jaguar3/Jaguar3Calibration.h @@ -6,7 +6,7 @@ #include "ChipVariant.h" #include "SelectedChannel.h" /* ChannelWidth_t */ -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "logger.h" namespace jaguar3 { @@ -59,7 +59,7 @@ class Jaguar3Calibration { /* Factory: returns the calibration impl for the given generation. (8822e is * added in a later phase; until then both variants return the 8822c impl.) */ std::unique_ptr -make_jaguar3_calibration(ChipVariant variant, RtlUsbAdapter device, +make_jaguar3_calibration(ChipVariant variant, RtlAdapter device, Logger_t logger, const devourer::DeviceConfig &cfg = {}); diff --git a/src/jaguar3/RadioManagementJaguar3.cpp b/src/jaguar3/RadioManagementJaguar3.cpp index 77c789b..860abf3 100644 --- a/src/jaguar3/RadioManagementJaguar3.cpp +++ b/src/jaguar3/RadioManagementJaguar3.cpp @@ -17,7 +17,7 @@ extern "C" { namespace jaguar3 { RadioManagementJaguar3::RadioManagementJaguar3( - RtlUsbAdapter device, Logger_t logger, ChipVariant variant, + RtlAdapter device, Logger_t logger, ChipVariant variant, const devourer::DeviceConfig &cfg) : _device{device}, _cfg{cfg}, _logger{std::move(logger)}, _variant{variant} {} diff --git a/src/jaguar3/RadioManagementJaguar3.h b/src/jaguar3/RadioManagementJaguar3.h index 3613691..90e1303 100644 --- a/src/jaguar3/RadioManagementJaguar3.h +++ b/src/jaguar3/RadioManagementJaguar3.h @@ -4,7 +4,7 @@ #include #include "logger.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" #include "ChipVariant.h" @@ -23,7 +23,7 @@ namespace jaguar3 { * NB_* registers below (SDR-validated). */ class RadioManagementJaguar3 { public: - RadioManagementJaguar3(RtlUsbAdapter device, Logger_t logger, + RadioManagementJaguar3(RtlAdapter device, Logger_t logger, ChipVariant variant = ChipVariant::C8822C, const devourer::DeviceConfig &cfg = {}); @@ -155,7 +155,7 @@ class RadioManagementJaguar3 { * bracket. */ void apply_rxbb(ChannelWidth_t bwmode); - RtlUsbAdapter _device; + RtlAdapter _device; devourer::DeviceConfig _cfg; /* dump_canary / hop_prof / nb_dac / tx rf_bw */ Logger_t _logger; ChipVariant _variant; diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index 0455ab8..7cdafee 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -30,7 +30,7 @@ static constexpr uint8_t JAGUAR3_TXPWR_REF_BASE = 0x4b; * setting a default. */ static constexpr uint8_t JAGUAR3_TXPWR_REF_BASE_8822C = 0x28; -RtlJaguar3Device::RtlJaguar3Device(RtlUsbAdapter device, Logger_t logger, +RtlJaguar3Device::RtlJaguar3Device(RtlAdapter device, Logger_t logger, jaguar3::ChipVariant variant, devourer::DeviceConfig cfg) : _device{device}, _cfg{std::move(cfg)}, _logger{logger}, diff --git a/src/jaguar3/RtlJaguar3Device.h b/src/jaguar3/RtlJaguar3Device.h index 4c542ba..f111721 100644 --- a/src/jaguar3/RtlJaguar3Device.h +++ b/src/jaguar3/RtlJaguar3Device.h @@ -9,7 +9,7 @@ #include "logger.h" #include "IRtlDevice.h" #include "TxMode.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #include "SelectedChannel.h" #include "ChipVariant.h" #include "HalJaguar3.h" @@ -27,7 +27,7 @@ * (coex_runtime_loop) — see CLAUDE.md. */ class RtlJaguar3Device : public IRtlDevice { public: - RtlJaguar3Device(RtlUsbAdapter device, Logger_t logger, + RtlJaguar3Device(RtlAdapter device, Logger_t logger, jaguar3::ChipVariant variant = jaguar3::ChipVariant::C8822C, devourer::DeviceConfig cfg = {}); ~RtlJaguar3Device() override; @@ -154,7 +154,7 @@ class RtlJaguar3Device : public IRtlDevice { bool should_stop = false; private: - RtlUsbAdapter _device; + RtlAdapter _device; const devourer::DeviceConfig _cfg; Logger_t _logger; jaguar3::ChipVariant _variant; From 9fa7009a4398fe912e8b9a7f9021005ea910ac90 Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 7 Jul 2026 17:29:45 +0300 Subject: [PATCH 07/11] Fix Windows builds: libusb/windows.h before the hal macro headers in 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 --- src/RtlAdapter.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/RtlAdapter.cpp b/src/RtlAdapter.cpp index 1c11b73..0308485 100644 --- a/src/RtlAdapter.cpp +++ b/src/RtlAdapter.cpp @@ -1,3 +1,11 @@ +/* UsbTransport.h (-> libusb.h -> windows.h on Windows) MUST precede the hal + * headers: Hal8812PhyReg.h #defines identifiers like `bEnable` that + * winuser.h uses as parameter names, so hal-first poisons the Windows headers + * (MSVC C2143 inside winuser.h / mingw "expected ','" pointed at the + * #define). The pre-seam RtlUsbAdapter.h guaranteed this order by including + * libusb.h first; this TU is the only one mixing both after the split. */ +#include "UsbTransport.h" + #include "RtlAdapter.h" #include @@ -5,7 +13,6 @@ #include #include "Hal8812PhyReg.h" -#include "UsbTransport.h" using namespace std::chrono_literals; From 5074862c262c3972c6e5171ebec629f89a53272f Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 7 Jul 2026 17:34:03 +0300 Subject: [PATCH 08/11] Fix mingw build: hal headers need windows.h first on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/RtlAdapter.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/RtlAdapter.h b/src/RtlAdapter.h index 15707fe..dc54f48 100644 --- a/src/RtlAdapter.h +++ b/src/RtlAdapter.h @@ -21,6 +21,16 @@ #include #include +#ifdef _WIN32 +/* The hal headers below lean on Windows types (BOOLEAN/VOID in rtw_efuse.h) + * and #define identifiers winuser.h uses as parameter names (bEnable in + * Hal8812PhyReg.h) — windows.h must be included BEFORE them, project-wide. + * The pre-seam RtlUsbAdapter.h guaranteed this implicitly by including + * libusb.h (-> windows.h) first; with libusb gone from this header, make the + * dependency explicit. */ +#include +#endif + #include "DeviceConfig.h" #include "RtlTransport.h" #include "TxStats.h" From 8d61c6e2b5f5c03c9d2937475d166d97d9299eda Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:18:48 +0300 Subject: [PATCH 09/11] =?UTF-8?q?README:=20PCIe=20is=20in=20scope=20?= =?UTF-8?q?=E2=80=94=20RTL8821CE=20supported,=208822BE/CE=20recipe=20in=20?= =?UTF-8?q?#214?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 98726e9..07bb182 100644 --- a/README.md +++ b/README.md @@ -59,12 +59,19 @@ 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 | — | — | — | M.2 sibling of the 8821CU (`10ec:c821`), driven via vfio-pci (`DEVOURER_PCIE=ON`, Linux-only). RX + TX validated on-air; not throughput-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. +PCIe: the RTL8821CE is supported through a vfio-pci transport (build with +`-DDEVOURER_PCIE=ON`, bind the card with `tests/pcie_vfio_bind.sh`, then +`DEVOURER_PCIE_BDF=0000:01:00.0` on the demos). The other HalMAC PCIe +siblings (RTL8822BE / RTL8822CE) can ride the same transport — bring-up +recipe in [#214](https://github.com/OpenIPC/devourer/issues/214). Out of +scope: Jaguar1 PCIe parts (RTL8812AE/8821AE — pre-HalMAC silicon with a +different DMA architecture) 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 From ea382702c932b1bf93a8c3dcdfcbec2c714c40b1 Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:27:37 +0300 Subject: [PATCH 10/11] Fix MSVC example builds: include libusb.h in the _MSC_VER branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- examples/duplex/main.cpp | 3 +++ examples/precoder/main.cpp | 3 +++ examples/streamtx/main.cpp | 3 +++ examples/svctx/main.cpp | 3 +++ examples/tx/main.cpp | 3 +++ 5 files changed, 15 insertions(+) diff --git a/examples/duplex/main.cpp b/examples/duplex/main.cpp index a68b3b2..ad9506b 100644 --- a/examples/duplex/main.cpp +++ b/examples/duplex/main.cpp @@ -36,6 +36,9 @@ #include #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 #include #include #include diff --git a/examples/precoder/main.cpp b/examples/precoder/main.cpp index e94ea56..baa80e6 100644 --- a/examples/precoder/main.cpp +++ b/examples/precoder/main.cpp @@ -39,6 +39,9 @@ #include #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 #include #include typedef int pid_t; diff --git a/examples/streamtx/main.cpp b/examples/streamtx/main.cpp index 802a293..13592d6 100644 --- a/examples/streamtx/main.cpp +++ b/examples/streamtx/main.cpp @@ -40,6 +40,9 @@ #include #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 #include #include #include diff --git a/examples/svctx/main.cpp b/examples/svctx/main.cpp index 282185f..90d0353 100644 --- a/examples/svctx/main.cpp +++ b/examples/svctx/main.cpp @@ -35,6 +35,9 @@ #include #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 #include #include #include diff --git a/examples/tx/main.cpp b/examples/tx/main.cpp index 51dc4eb..2d2e373 100644 --- a/examples/tx/main.cpp +++ b/examples/tx/main.cpp @@ -12,6 +12,9 @@ #include #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 #include #include typedef int pid_t; From 6d069886fe5c3098f46f6020ca0e8080c7819ad5 Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:33:16 +0300 Subject: [PATCH 11/11] README: trim the 8821CE row + PCIe scope note to table style Co-Authored-By: Claude Opus 4.8 --- README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 07bb182..ee57aed 100644 --- a/README.md +++ b/README.md @@ -59,19 +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 | — | — | — | M.2 sibling of the 8821CU (`10ec:c821`), driven via vfio-pci (`DEVOURER_PCIE=ON`, Linux-only). RX + TX validated on-air; not throughput-benchmarked | +| **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). -PCIe: the RTL8821CE is supported through a vfio-pci transport (build with -`-DDEVOURER_PCIE=ON`, bind the card with `tests/pcie_vfio_bind.sh`, then -`DEVOURER_PCIE_BDF=0000:01:00.0` on the demos). The other HalMAC PCIe -siblings (RTL8822BE / RTL8822CE) can ride the same transport — bring-up -recipe in [#214](https://github.com/OpenIPC/devourer/issues/214). Out of -scope: Jaguar1 PCIe parts (RTL8812AE/8821AE — pre-HalMAC silicon with a -different DMA architecture) 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