diff --git a/CLAUDE.md b/CLAUDE.md index ccfa375..2348f46 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,8 +34,31 @@ construction from the `SYS_CFG2` chip-id (see **Architecture**): Naming traps: **RTL8821AU is Jaguar1** (not Jaguar2, despite the Jaguar2 RTL8821C's similar name); RTL8822**B**U (Jaguar2) ≠ RTL8822**C**U (Jaguar3). -The 8822BE (PCIe) and the Kestrel 11ax families are out of scope. Full chip / -bench-throughput table: README **Supported hardware**. +The Kestrel 11ax families are out of scope. Full chip / bench-throughput +table: README **Supported hardware**. + +**PCIe** (`DEVOURER_PCIE=ON`, Linux-only, default OFF): the RTL8821CE — the +PCIe sibling of the 8821CU — rides the same Jaguar2 HAL through a vfio-pci +transport (`src/PcieTransport.{h,cpp}`): registers are BAR2 MMIO (same +0x0000..0xFFFF space the USB vendor-control path addresses), TX/RX are the +88xx buffer-descriptor DMA rings (rtw88 pci.{c,h} layout), RX completion is +polled by default (MSI+eventfd wakeups when available). USB and PCIe are +independent transports behind `devourer::IRtlTransport` +(`src/RtlTransport.h`): `UsbTransport` (libusb) and `PcieTransport` each +implement the register + frame planes, and the bus-neutral `RtlAdapter` value +type the HALs hold forwards to whichever it was built with. The few genuinely +bus-specific bring-up steps gate on `is_usb()` (PCIe power-seq rows, PQ map, +no USB RX-agg, no DLFW 512-pad) or ride `hci_setup()` (pre-power TRX ring +programming, no-op on USB). Factory: +`WiFiDriver::CreateRtlDevicePcie(PcieTransport::Open(bdf, logger))` — the +caller owns vfio like it owns libusb. Demos: `DEVOURER_PCIE_BDF=0000:01:00.0` +on rxdemo and txdemo (TX = the data/MGMT BD rings behind the unchanged +`send_packet`); `pcieprobe [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 +71,9 @@ libusb-1.0 via `pkg-config` (Linux/macOS) or vcpkg (`VCPKG_ROOT`, Windows). Per-chip options, all default ON: `DEVOURER_JAGUAR1`, `DEVOURER_8814` (requires JAGUAR1), `DEVOURER_JAGUAR2_8822B`, `DEVOURER_JAGUAR2_8821C`, -`DEVOURER_JAGUAR3_8822C`, `DEVOURER_JAGUAR3_8822E`. Turning groups off drops +`DEVOURER_JAGUAR3_8822C`, `DEVOURER_JAGUAR3_8822E`. `DEVOURER_PCIE` (default +OFF, Linux-only, requires JAGUAR2_8821C) adds the vfio-pci transport + +`pcieprobe`; OFF builds are byte-identical to before it existed. Turning groups off drops their firmware blobs + PHY tables (an 8812AU-only `rxdemo` is ~1.0 MB vs ~2.6 MB). Configure fails on no-chip-selected or 8814-without-JAGUAR1. Each group exports a PUBLIC `DEVOURER_HAVE_*` define; sites referencing a dropped @@ -265,8 +290,10 @@ Generation-agnostic core in `src/` (always compiled; depends on no HAL): - `WiFiDriver` — the factory (`CreateRtlDevice`). - `DeviceConfig.h` — construction-time configuration struct; every component copies the sub-struct it consumes at construction. -- `RtlUsbAdapter` — libusb wrapper (vendor control + bulk transfers); a - copyable value type shared by every component. +- `RtlAdapter` — the bus-neutral register/frame accessor; a copyable value + type shared by every component, forwarding to the `IRtlTransport` it was + built with (`UsbTransport` = libusb vendor control + bulk; `PcieTransport` = + BAR2 MMIO + DMA rings). `RtlUsbAdapter` is a deprecated alias. - `Radiotap.c` — radiotap iterator. TX buffers passed to `send_packet` **must** begin with a radiotap header; rate/MCS/VHT/STBC/LDPC/SGI/bandwidth are read from it. diff --git a/CMakeLists.txt b/CMakeLists.txt index fbf3bbd..0272b82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,15 @@ option(DEVOURER_JAGUAR2_8822B "RTL8822BU (Jaguar2, rtl8822b, 2T2R USB)" option(DEVOURER_JAGUAR2_8821C "RTL8811CU / 8821CU (Jaguar2, rtl8821c, 1T1R)" ON) option(DEVOURER_JAGUAR3_8822C "RTL8812CU / 8822CU (Jaguar3, rtl8822c)" ON) option(DEVOURER_JAGUAR3_8822E "RTL8812EU / 8822EU (Jaguar3, rtl8822e)" ON) +# PCIe transport (vfio-pci). Linux-only. The transport implements the HalMAC- +# generation (Jaguar2/3) 88xx buffer-descriptor DMA rings; the RTL8821CE is +# the only chip with a PCIe bring-up implemented + hardware-validated so far, +# hence the DEVOURER_JAGUAR2_8821C requirement below. Other HalMAC PCIe parts +# (RTL8822BE / RTL8822CE) could ride the same rings with per-chip HAL gates; +# Jaguar1 PCIe parts (RTL8812AE/8821AE) CANNOT — they are pre-HalMAC silicon +# with the older rtlwifi-style own-bit descriptor DMA, a different engine. +# OFF by default — libusb-only builds are bit-identical. +option(DEVOURER_PCIE "PCIe transport via vfio-pci (Linux; RTL8821CE)" OFF) # Compile-time diagnostics floor (src/logger.h). Calls below the floor — # including their argument expressions at DVR_TRACE/DVR_DEBUG sites — compile @@ -67,6 +76,23 @@ if(NOT DEVOURER_JAGUAR1 AND NOT DEVOURER_JAGUAR2 AND NOT DEVOURER_JAGUAR3_8822C "DEVOURER_JAGUAR2_8822B / DEVOURER_JAGUAR2_8821C / DEVOURER_JAGUAR3_8822C " "/ DEVOURER_JAGUAR3_8822E.") endif() +if(DEVOURER_PCIE) + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + message(FATAL_ERROR "DEVOURER_PCIE=ON requires Linux (vfio-pci).") + endif() + if(NOT DEVOURER_JAGUAR2_8821C) + message(FATAL_ERROR + "DEVOURER_PCIE=ON requires DEVOURER_JAGUAR2_8821C=ON: the RTL8821CE " + "(Jaguar2 8821C) is currently the only chip with a PCIe bring-up " + "implemented and hardware-validated, so a PCIe build without it has " + "no usable chip. Not an architectural limit for the HalMAC families " + "— RTL8822BE (Jaguar2) / RTL8822CE (Jaguar3) could reuse the same " + "buffer-descriptor ring transport with per-chip HAL gates. Jaguar1 " + "PCIe parts (RTL8812AE/8821AE) are out of reach of this transport: " + "pre-HalMAC silicon with rtlwifi-style own-bit descriptor DMA, a " + "different engine entirely.") + endif() +endif() # Find pkg-config and then use it to locate libusb. find_package(PkgConfig REQUIRED) @@ -83,8 +109,12 @@ add_library(devourer src/Radiotap.c src/RadiotapBuilder.cpp src/RadiotapBuilder.h - src/RtlUsbAdapter.cpp - src/RtlUsbAdapter.h + src/RtlTransport.h + src/RtlAdapter.cpp + src/RtlAdapter.h + src/RtlUsbAdapter.h # compat alias shim (RtlUsbAdapter = RtlAdapter) + src/UsbTransport.cpp + src/UsbTransport.h src/UsbDeviceLock.cpp src/UsbDeviceLock.h src/UsbOpen.cpp @@ -215,6 +245,13 @@ if(DEVOURER_JAGUAR2_8821C) target_compile_definitions(devourer PUBLIC DEVOURER_HAVE_JAGUAR2_8821C=1) endif() +# --- PCIe transport (vfio-pci; see src/PcieTransport.h) --- +if(DEVOURER_PCIE) + target_sources(devourer PRIVATE + src/PcieTransport.cpp src/PcieTransport.h) + target_compile_definitions(devourer PUBLIC DEVOURER_HAVE_PCIE=1) +endif() + if(DEVOURER_JAGUAR3) # Shared Jaguar3 port (compiled when either variant is on). Phy8822cTables.cpp # and Halrf8822c.cpp also carry the make_jaguar3_*() dispatchers, so they are @@ -254,6 +291,17 @@ if(DEVOURER_JAGUAR3_8822E) endif() +# pcieprobe — staged PCIe bring-up driver for the RTL8821CE milestones +# (chip-id/MMIO sanity -> power-on + EFUSE -> firmware DLFW). rxdemo's +# DEVOURER_PCIE_BDF branch is the full-RX consumer; this probe validates the +# layers below it one at a time. Linux/vfio only. +if(DEVOURER_PCIE) + add_executable(pcieprobe + examples/pcieprobe/main.cpp + ) + target_link_libraries(pcieprobe PUBLIC devourer) +endif() + add_executable(rxdemo examples/rx/main.cpp examples/common/env_config.cpp diff --git a/README.md b/README.md index 98726e9..ee57aed 100644 --- a/README.md +++ b/README.md @@ -59,12 +59,13 @@ Bandwidth cells are devourer's measured on-air TX throughput (Mbps, HT MCS7, | **RTL8822CU** | 2T2R + BT | — | — | — | not benchmarked (`0bda:c82c`) | | **RTL8812EU** | 2T2R | 8 | 51 | 47 | LB-LINK BL-M8812EU2 (`0bda:a81a`); bare 5 GHz FPV module. 5/10 MHz capable | | **RTL8822EU** | 2T2R + BT | — | — | — | not benchmarked. 5/10 MHz capable | +| **RTL8821CE** (PCIe) | 1T1R + BT | — | — | — | Radxa X4 onboard Wi-Fi (`10ec:c821`); not benchmarked | `†` = works on-air but the reading varies run-to-run (bracketed = best clean reading). -Out of scope: the PCIe siblings (e.g. 8822BE) and the 11ax "Kestrel" -generation — same branding, different bus or baseband. +Out of scope: the pre-HalMAC PCIe parts (RTL8812AE/8821AE) and the 11ax +"Kestrel" generation — same branding, different bus or baseband. > Heads up — some Realtek sticks ship in "ZeroCD" mode and first enumerate as > a USB flash drive holding a Windows installer (`0bda:1a2b` is the canonical diff --git a/examples/duplex/main.cpp b/examples/duplex/main.cpp index ce60514..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 @@ -60,7 +63,7 @@ #include "RxPacket.h" #include "RadiotapBuilder.h" -#include "RtlUsbAdapter.h" +#include "RtlAdapter.h" #if defined(DEVOURER_HAVE_JAGUAR1) #include "jaguar1/RtlJaguarDevice.h" #endif diff --git a/examples/pcieprobe/main.cpp b/examples/pcieprobe/main.cpp new file mode 100644 index 0000000..6e7bbfb --- /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 "RtlAdapter.h" +#include "logger.h" + +#include "jaguar2/ChipVariant.h" +#include "jaguar2/HalJaguar2.h" +#include "jaguar2/HalmacJaguar2Fw.h" +#include "jaguar2/HalmacJaguar2MacInit.h" + +int main(int argc, char **argv) { + if (argc < 2) { + fprintf(stderr, "usage: %s [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 ---- */ + RtlAdapter adapter(transport, logger, {}); + const uint8_t chip_id = adapter.rtw_read8(0x00FC); + const uint32_t sys_cfg1 = adapter.rtw_read32(0x00F0); + const uint8_t cr = adapter.rtw_read8(0x0100); + const bool id_ok = chip_id == 0x09; + logger->info("M0: chip-id=0x{:02x} (want 0x09) SYS_CFG1=0x{:08x} CR=0x{:02x}", + chip_id, sys_cfg1, cr); + devourer::Ev(logger->events(), "pcie.id") + .f("ok", id_ok) + .hexf("chip_id", chip_id, 2) + .hexf("sys_cfg1", sys_cfg1, 8) + .hexf("cr", cr, 2); + if (!id_ok || want < 1) + return id_ok ? 0 : 1; + + /* ---- stage power (M1): rings -> pre-init -> PCIe power-on -> EFUSE ---- */ + jaguar2::HalJaguar2 hal(adapter, logger, jaguar2::ChipVariant::C8821C, {}); + jaguar2::HalmacJaguar2MacInit macinit(adapter, logger, + jaguar2::ChipVariant::C8821C); + bool power_ok = false; + std::vector 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/precoder/main.cpp b/examples/precoder/main.cpp index aa6cb8f..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; @@ -53,7 +56,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 011a251..3f90351 100644 --- a/examples/rx/main.cpp +++ b/examples/rx/main.cpp @@ -18,11 +18,14 @@ #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" #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,64 @@ 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); + /* 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; + 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/examples/streamtx/main.cpp b/examples/streamtx/main.cpp index 0be0d26..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 @@ -64,7 +67,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..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 @@ -57,7 +60,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 3ebec58..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; @@ -40,9 +43,12 @@ #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) +#include "PcieTransport.h" +#endif #include "WiFiDriver.h" #include "env_config.h" #include "RadiotapBuilder.h" @@ -196,7 +202,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 +296,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 @@ -390,7 +408,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"; @@ -413,8 +431,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 +1134,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; } 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/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..189fc85 --- /dev/null +++ b/src/PcieTransport.cpp @@ -0,0 +1,682 @@ +#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 +#include + +#include + +#include "Event.h" + +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; + +/* 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, + * 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; + 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}, 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) { + 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_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) + 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) { + 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; + } + 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)); + } + } + 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 new file mode 100644 index 0000000..2bae832 --- /dev/null +++ b/src/PcieTransport.h @@ -0,0 +1,233 @@ +#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 + +#include "RtlTransport.h" +#include "logger.h" + +namespace devourer { + +class PcieTransport final : public IRtlTransport { +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 */ + /* 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 + * 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() override; + PcieTransport(const PcieTransport &) = delete; + PcieTransport &operator=(const PcieTransport &) = delete; + + /* ---- 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; } + + /* ---- 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(); + bool setup_msi(); + + template T mr(uint16_t reg) { + return *reinterpret_cast(_mmio + reg); + } + template void mw(uint16_t reg, T v) { + *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; + 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; + 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; + size_t _cfg_region_len = 0; + + 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; +}; + +} /* namespace devourer */ diff --git a/src/RtlAdapter.cpp b/src/RtlAdapter.cpp new file mode 100644 index 0000000..0308485 --- /dev/null +++ b/src/RtlAdapter.cpp @@ -0,0 +1,271 @@ +/* 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 +#include +#include + +#include "Hal8812PhyReg.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..dc54f48 --- /dev/null +++ b/src/RtlAdapter.h @@ -0,0 +1,190 @@ +#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 + +#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" +#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 63713af..0000000 --- a/src/RtlUsbAdapter.cpp +++ /dev/null @@ -1,608 +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 - -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) { - 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")); -} - -/* -$ 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 (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) { - - 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) { - /* 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 ec23763..7227312 100644 --- a/src/RtlUsbAdapter.h +++ b/src/RtlUsbAdapter.h @@ -1,222 +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; -} - -#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; - libusb_context *_ctx = nullptr; - Logger_t _logger; - - 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 = {}); - - /* 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) { 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) { - 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) { - 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. */ - 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 (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); - 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 11ed03b..6e9ca55 100644 --- a/src/WiFiDriver.cpp +++ b/src/WiFiDriver.cpp @@ -7,8 +7,12 @@ #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 #if defined(DEVOURER_HAVE_JAGUAR1) #include "jaguar1/RtlJaguarDevice.h" #endif @@ -22,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 @@ -84,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; @@ -110,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 " @@ -126,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 " @@ -159,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 " @@ -172,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", @@ -184,3 +188,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( + RtlAdapter(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/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 10bb490..6de94ee 100644 --- a/src/jaguar2/HalJaguar2.cpp +++ b/src/jaguar2/HalJaguar2.cpp @@ -133,8 +133,71 @@ const PwrCfg kPwrOff8821cUsb[] = { {0, PC_END, 0, 0}, }; -void run_pwr_seq(RtlUsbAdapter &dev, const PwrCfg *seq, uint32_t poll_max, +#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(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 — + * 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,22 +206,22 @@ 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)"); } } } } /* 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)} {} @@ -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)"); } @@ -1647,14 +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; + /* 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 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 b6f4b2c..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} {} @@ -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/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 d8fd3c5..4c0a62f 100644 --- a/src/jaguar2/HalmacJaguar2MacInit.cpp +++ b/src/jaguar2/HalmacJaguar2MacInit.cpp @@ -1,9 +1,11 @@ #include "HalmacJaguar2MacInit.h" +#include +#include #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 @@ -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) { @@ -237,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} {} @@ -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..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. */ @@ -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(); @@ -64,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 8a37aba..9e00871 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -1,5 +1,6 @@ #include "RtlJaguar2Device.h" +#include #include #include #include @@ -24,7 +25,7 @@ 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}, @@ -56,11 +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; + /* rtw88 applies the PCIe interface-PHY MDIO config once at probe. */ + if (!_device.is_usb()) + _macinit.pcie_phy_cfg(); for (int attempt = 0; attempt < kBringupTries && !fw_ok; attempt++) { if (attempt > 0) _logger->error("RtlJaguar2Device: DLFW failed — full power-cycle retry " "{}/{}", attempt + 1, kBringupTries); + /* 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(); @@ -73,7 +82,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/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; 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)"