USB exclusive access: claim-before-reset + advisory per-adapter lock#169
Merged
Conversation
Two devourer processes on one adapter would wedge the chip: the second
launch's libusb_reset_device (issued before its claim) re-enumerated the
adapter out from under the first process and crashed it, and the demo
asserted on the doomed claim instead of exiting cleanly.
Fix the open ordering and add a cross-platform advisory lock:
- src/UsbOpen.{h,cpp}: claim_interface_then_reset() — the recommended open
sequence. Takes an advisory per-adapter lock FIRST, then detaches,
configures, claims, and only then (as sole owner) resets. A second process
is refused before it touches the device. The kernel interface claim
(LIBUSB_ERROR_BUSY on Linux usbfs) is a redundant second gate; on WinUSB,
where the claim does not arbitrate, the lock is the guard.
- src/UsbDeviceLock.{h,cpp}: devourer::UsbDeviceLock — advisory exclusive
lock keyed to the adapter's bus+port path. POSIX flock / Windows named
mutex, auto-released on process exit (including SIGKILL / crash), so there
are never stale locks. Held for the device lifetime via a shared_ptr in
RtlUsbAdapter; WiFiDriver::CreateRtlDevice uses the lock the caller passes
(from claim_interface_then_reset) or acquires its own as a best-effort
second gate for direct library consumers.
- All six demos now call claim_interface_then_reset instead of the old inline
detach/reset/claim/assert(rc==0) block, and hand the lock to CreateRtlDevice.
Validated on Linux (RTL8812AU) and macOS (RTL8822CU): a second process on
the same adapter is refused ("USB adapter in use ... refusing to open") and
exits cleanly BEFORE any reset, so the first process keeps running;
single-process RX and TX bring-up are unaffected. Build clean + ctest 2/2
on both.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
josephnef
added a commit
that referenced
this pull request
Jul 4, 2026
Extends the `DEVOURER_CW_TONE` MP single-tone (bare RF local-oscillator carrier, from #165) to the **Jaguar3** family — RTL8822CU (rtl8822c) and RTL8812EU/8822EU (rtl8822e) — the last USB generation without it. SDR-validated on a USRP B210. Builds on #167 (Jaguar1+Jaguar2) and current master (#169). ## Why Jaguar3 needs a different recipe The halbb/halrf generation needs two things the older chips don't (ported from the vendor `phydm_mp_set_single_tone_jgr3` + `phydm_set_pmac_txon_jgr3`): 1. **RF 0x00 is write-only through the HSSI 3-wire port** (`0x1808`/`0x4108`). The direct BB→RF window (`0x3c00`) is a *read-only shadow* for RF 0x00, so a window write is silently dropped. (RF 0x58 / LO-enable is a normal reg and still uses the window.) 2. **The BB must be held in continuous TX** (`0x1c3c`/`0x1a00`/`0x1ca4` + PMAC/TX-OFDM `0x1d08`/`0x1e70`), or the RF state machine re-drives RF 0x00 back to RX within milliseconds and no carrier comes out. `RtlJaguar3Device::StartCwTone`/`StopCwTone` implement the full recipe (path A, CCA off, serialized on the coex `_reg_mu`); the coex runtime thread isn't started for a tone. ## SDR results | Device | 2.4 GHz | 5 GHz | |---|---|---| | **RTL8822CU** (2T2R) | +38 dBFS, carrier-to-spur **62 dB** | c2s **51–58 dB** | | **RTL8812EU** (BL-M8812EU2, 1T1R, rfe 21) | radiates (weak — **no 2.4 GHz PA**) | radiates, weaker (bare-LO output is low on this module), center carrier, gain-responsive | ## 8812EU 5 GHz bring-up robustness (two fixes) - **DACK/IQK USB-read crash:** the status-poll loops issue tens of thousands of USB control-IN reads; a single glitch made `rtw_read` throw `std::ios_base::failure` and aborted bring-up. Wrapped `dac_calibrate()` + `phy_iq_calibrate()` in a bounded retry (`HalJaguar3::retry_cal`), mirroring `StartRxLoop`'s existing retry. - **Post-bring-up read NAK:** on the 8812EU at 5 GHz the FW power-mode/coex H2C steps and the `GPIO_MUXCFG` RFE-pin write leave the chip NAKing control-IN reads, so `StartCwTone`'s BB reads failed. For a CW tone, arm *early* (after the channel/RFE config, before those steps) and return — a bare tone needs no per-rate power/coex/FW-coex. The 5 GHz PA still needs its RFE-enable pins driven, so snapshot `0x40`/`0x64`, arm, then **blind-write** the PA pins (the read-breaking write happens last). The plain (non-CW) TX path is unchanged. ## Scope / notes - `tests/cw_tone_sdr.sh` gains `8822cu`/`8822eu` cells. - 8822CU regression-checked after the restructure: still +38 dBFS. - The 8812EU intermittently drops control-IN under *rapid repeated* ch36 bring-ups (a device-USB trait); single clean launches work. Closes the Jaguar3 follow-up noted in #165. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Launching two devourer processes on the same adapter wedges the chip. The open sequence was
detach → libusb_reset_device → claim → assert(rc==0), so a second launch's reset ran before its claim failed — re-enumerating the adapter out from under the first process and crashing it — and theassertturned the doomed claim into aSIGABRTinstead of a clean exit.Fix
Use the OS's own exclusivity correctly, and add a cross-platform advisory lock as the primary cross-platform gate.
src/UsbOpen.{h,cpp}—claim_interface_then_reset(), the recommended open sequence: take an advisory per-adapter lock first, then detach, configure, claim, and only then — as sole owner — reset. A second process is refused before it touches the device. The kernel interface claim (LIBUSB_ERROR_BUSYon Linux usbfs) is a redundant second gate; on WinUSB, wherelibusb_claim_interfacedoes not arbitrate, the advisory lock is the guard.src/UsbDeviceLock.{h,cpp}—devourer::UsbDeviceLock: advisory exclusive lock keyed to the adapter's bus + port path. POSIXflock/ Windows named mutex, auto-released on process exit includingSIGKILL/crash, so there are never stale locks (the failure mode a PID file has). Held for the device lifetime via ashared_ptrinRtlUsbAdapter;WiFiDriver::CreateRtlDeviceuses the lock the caller passes, or acquires its own as a best-effort second gate for direct library consumers.claim_interface_then_resetinstead of the inlinedetach/reset/claim/assertblock and hand the lock toCreateRtlDevice.Validation
Second process on the same adapter, first process already running:
USB adapter in use … refusing to open, exits before reset → 1st survivesSIGABRT(assert)Single-process RX and TX bring-up unaffected. Build clean +
ctest2/2 on both platforms. The Windows/mingw paths (named-mutex lock) are exercised by this PR's CI matrix.🤖 Generated with Claude Code