Also known as: RTL-SDR broken pipe, EPIPE at tuner init, USB pipe stall recovery
RTL-SDR USB stall recovery is the collected fix-lore for one failure family: the dongle enumerates fine, then the very first burst of tuner-init I²C traffic (or an early baseband write) fails with an OS-specific USB error. The R820T-family tuner behind the RTL2832U bridge is sensitive to timing on its control-pipe writes, and every operating system reports the resulting stall with a different — and differently misleading — error string.
Symptom table by OS
| Symptom | Looks like | Actually | Fix/Check |
|---|---|---|---|
Linux: r82xx init: burst write: rtl2832u: I2CWrite addr=0x34: broken pipe (EPIPE) |
Dead dongle / bad cable | The tuner’s I²C bridge was not ready for a multi-byte write; the chip needs settle time after the I²C repeater is enabled | Fixed in GopherTrunk with a 5 ms chip-settle after SetI2CRepeater(true) (#248); update, then retest with gophertrunk sdr list --probe |
Windows 10: ERROR_GEN_FAILURE on baseband init step 0 |
Wedged clone firmware needing more resets | A race inside one attempt: GopherTrunk calls WinUSB directly and its second byte-identical write lands while clone firmware is still digesting the first — libusb’s per-transfer overhead was incidentally hiding the race | Fixed with a 10 ms settle between warmup and baseband init (#395); no retry loop can help, because the failure is between two transfers within a single attempt |
Windows 11: access denied (ERROR_ACCESS_DENIED) opening the SDR |
Driver/permissions problem (Zadig) | The config listed the same serial twice (e.g. once as role: control, once as role: voice); Windows refused the second CreateFile on the already-open device |
Remove the duplicate — now rejected at config load (#333) |
| macOS: “configured SDR not present on the bus” though the device is detected | Cable / lsusb problem | IOKit kern_return 0xe000404f (kIOUSBPipeStalled) — a transient control-pipe stall on the R820T’s first I²C burst, common on cold boot; the failed probe made detection conclude no tuner exists |
Fixed by mapping the IOKit code to the existing stall-retry path (#1038); a control-pipe stall clears on the next SETUP, so retrying the identical request is the correct recovery |
| Decode goes silent mid-run, ~0% CPU, no errors | Pipeline stall / deadlock | The dongle dropped off the bus and re-enumerated (marginal cable, hub power, EMI, thermal) | Check dmesg for USB disconnect, device number N timestamp-matching the last decoded line (#345) |
“Works in SDR++” does not exonerate the dongle path
The single most repeated misdirection in these threads: the same dongle works in
SDR++, SDRTrunk, or rtl_test, so the reporter (reasonably) concludes the hardware is
fine and GopherTrunk is broken — or the maintainer concludes the dongle is an
incompatible clone. Both readings miss that those tools sit on libusb, which clears
endpoint stalls automatically and adds per-transfer latency that hides timing races.
GopherTrunk’s pure-Go USB stack is several layers thinner, so it
both outruns slow clone firmware (#395)
and sees raw stalls libusb would have absorbed
(#1038). A dongle that works
elsewhere tells you the hardware is capable; it says nothing about whether the thin
path needs a settle delay or a stall retry.
The same class of subtlety cut the other way in #248: a prior “optimization” removed a redundant-looking I²C repeater off-toggle, and a register cache then swallowed the subsequent on-write — but on NESDR v5 silicon the chip needed the fresh wire write, not just the register value, to arm the I²C bridge.
Recovery semantics worth knowing
- A control-pipe (EP0) stall is self-clearing. The host controller clears it on the
next SETUP packet, so the correct recovery is to retry the identical request —
GopherTrunk retries per-chunk with chunk-size halving.
libusb_clear_haltis the wrong hammer for EP0 (verified in #248: the endpoint was not actually halted). - WinUSB “reset” is not a device reset.
WinUsb_ResetPipe(0)only clears a stalled endpoint. Recovering firmware wedged by a previously crashed process needs a full device-handle rebind: clear-halt, drop the WinUSB handles, re-CreateFile+WinUsb_Initialize. Some clone dongles need two resets with backoff (#333). - A retry loop is only as good as its error matching. In #345 the decoder-restart loop matched one sentinel error; the post-disconnect error was a different string, so the loop bailed and the daemon ran on half-dead. If a component silently stops retrying, check what error text it actually saw.
Diagnostic tools
gophertrunk sdr list --probe— one line of probe output cracked #1038 by printing the raw IOKit kern_return the normal path swallowed.- Paired USB traces: run GopherTrunk with
RTLSDR_DEBUG_USB=1and a known-good tool (rtl_test) withLIBUSB_DEBUG=4in the same session, then diff the wire traffic. Six rounds of exactly this cracked #248 — and reading the error wrapper prefix (USB warmup:vsr82xx init: burst write:) shows which layer fired and whether a fix moved the failure. dmesg(Linux) for disconnect/re-enumeration lines when the symptom is silence rather than an error.
See the diagnostic playbook for where these steps sit in the wider escalation ladder.
Provenance
- #248 — NESDR SMArt v5 EPIPE at tuner init; six rounds of paired traces; the 5 ms chip-settle fix.
- #395 — Windows 10
ERROR_GEN_FAILURE: GopherTrunk outran clone firmware between two byte-identical writes; libusb’s overhead was hiding the race. - #333 — Windows access denied from a duplicated serial in config; WinUSB reset-vs-rebind semantics; double reset for clones.
- #1038 — macOS IOKit
0xe000404fpipe stall masquerading as “device detected but not present.” - #345 — the “pipeline stall” that was a USB disconnect, proven by
dmesgcorrelation; the too-narrow retry sentinel.