From b1c1b69afc668001b45166e1a9005f91bd2193ce Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Fri, 26 Jun 2026 12:01:17 +0300 Subject: [PATCH] RX: keep multiple bulk-IN URBs in flight (mirror kernel's always-posted model) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The monitor RX loop did one synchronous libusb_bulk_transfer at a time, so between transfers no URB is posted. usbmon ground-truth shows the kernel (rtw88) instead keeps 4 bulk-IN URBs always posted and re-submits each ~20us after completion. With a single posted URB the 8814's RX-DMA ring pauses in the parse gap and, under sparse traffic, intermittently wedges after a short burst — the "8814 RX stalls after ~10 frames" symptom (8812/8821 tolerate it). Run infinite_read() from a small worker pool (default 4) instead of a single loop. infinite_read() is already reentrant — local 32KB buffer + local FrameParser, and libusb is thread-safe — so the workers just give the same always-posted behaviour the kernel has; packetProcessor is serialised under a mutex. Frame order across workers is not preserved, which is fine for monitor-mode RX. DEVOURER_RX_URBS overrides the worker count (1 restores the old single-transfer behaviour). Validated: usbmon confirms 4 bulk-IN URBs in flight; regression-clean on RTL8812AU / RTL8821AU (300+ frames) and RTL8814AU. ctest green. Note: the intermittent 8814 stall it targets is timing-dependent and was dormant at commit time, so this hardens the RX path to match the kernel's proven model rather than being confirmed against a live repro. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/RtlJaguarDevice.cpp | 42 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/RtlJaguarDevice.cpp b/src/RtlJaguarDevice.cpp index 92c46cb..e1c0d48 100644 --- a/src/RtlJaguarDevice.cpp +++ b/src/RtlJaguarDevice.cpp @@ -4,6 +4,9 @@ #include #include +#include +#include +#include /* Per-queue free-page registers (8814A only — 0x0230..0x0240 don't decode * the same way on 8812/8821). Cross-checked against hal/rtl8814a_spec.h @@ -329,12 +332,41 @@ void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor, SetMonitorChannel(channel); _logger->info("Listening air..."); - while (!should_stop) { - auto packets = _device.infinite_read(); - for (auto &p : packets) { - _packetProcessor(p); - } + /* Keep several bulk-IN transfers in flight at once (mirrors the kernel's ~4 + * always-posted RX URBs — confirmed via usbmon: rtw88 re-submits each URB + * ~20us after completion and cycles 4 of them). With a single synchronous + * transfer there is a window between transfers where NO URB is posted; the + * 8814's RX-DMA ring pauses in that gap and, under sparse traffic, + * intermittently wedges after a short burst — the "RX stalls at ~10 frames" + * bug (8812/8821 tolerate it, the 8814 does not). infinite_read() is + * reentrant (local buffer + local FrameParser; libusb is thread-safe), so a + * small worker pool restores the always-posted behaviour. Frame order across + * workers is not preserved, which is fine for monitor-mode RX. Set + * DEVOURER_RX_URBS=1 to restore the old single-transfer behaviour. */ + int rx_workers = 4; + if (const char *e = std::getenv("DEVOURER_RX_URBS")) { + rx_workers = std::atoi(e); + if (rx_workers < 1) rx_workers = 1; + } + std::mutex proc_mu; + std::vector workers; + for (int i = 0; i < rx_workers; ++i) { + workers.emplace_back([this, &proc_mu]() { + while (!should_stop) { + auto packets = _device.infinite_read(); + if (packets.empty()) + continue; + std::lock_guard lk(proc_mu); + for (auto &p : packets) { + if (should_stop) + break; + _packetProcessor(p); + } + } + }); } + for (auto &t : workers) + t.join(); #if 0 _device.UsbDevice.SetBulkDataHandler(BulkDataHandler);