Linux USB URBs: Submission, Completion, Cancellation, and Teardown
Follow USB Request Blocks through endpoint submission, asynchronous completion, cancellation, disconnect, and safe buffer lifetime in Linux drivers.
USB drivers do not usually move data by making one synchronous call and waiting for an entire transfer to finish. They construct USB Request Blocks (URBs), submit work to the host controller, and later receive completion callbacks. The callback can run after a timeout, cancellation, disconnect, or driver teardown has begun. Correctness depends on ownership and lifetime rules as much as on the endpoint’s packet format.
An URB represents a transfer request to an endpoint, not a guarantee that the device accepted an application-level command. Completion status reports the USB transaction result; a successful transfer does not prove that firmware applied a configuration or persisted data. Device protocols need their own acknowledgements and retry rules.
Identify the device, interface, and endpoint
USB is enumerated as devices with configurations, interfaces, alternate settings, and endpoints. A driver normally binds to an interface and claims or selects the resources described by that interface. Before debugging a transfer, identify the vendor/product ID, interface number, endpoint address and direction, transfer type, maximum packet size, and any alternate setting needed by the protocol.
Read-only inventory examples:
lsusb
lsusb -t
journalctl -k -b --no-pager
readlink -f /sys/class/tty/ttyUSB0/device
The tty path is only an example for a serial adapter. Do not assume every USB device creates a tty or that the first interface is the data interface. Use the descriptor output and bound driver information. If several identical devices are attached, record serial numbers or physical ports privately and redact before publishing diagnostics.
The USB core and host-controller driver translate URBs into transactions. Endpoint type affects transfer scheduling and semantics: control transfers have SETUP and STATUS stages and may include a DATA stage, bulk transfers provide reliable data delivery without a reserved periodic rate, interrupt transfers are polled on a periodic schedule, and isochronous transfers prioritize timing while tolerating packet errors. Do not infer device-level reliability from the endpoint’s transfer type alone.
Submission transfers ownership until completion
Drivers initialize an URB with its device, endpoint pipe, transfer buffer, requested length, completion callback, and context. After successful submission, the USB core owns the request until completion or cancellation. A driver must not free or reuse the URB or buffer while the request can still access it. Completion reports actual length and status, which can differ from the requested length.
The ownership transition is the core invariant. Before submission, the driver may prepare the buffer. While submitted, the core and host controller can use it. In the completion callback, the driver regains ownership and decides whether to process data, resubmit, record an error, or stop. A callback that immediately resubmits creates a continuous pipeline; teardown must then prevent further submissions and drain every outstanding URB.
URB allocation and transfer-buffer allocation have API-specific rules, including DMA mapping and memory constraints. Use the USB API helpers rather than inventing a DMA address or passing a temporary stack buffer. The driver documentation distinguishes transfer-buffer ownership and coherent allocations. For user-space USB clients, libusb exposes different lifetime contracts; kernel-driver assumptions do not automatically apply.
Completion status and partial data
A completion callback receives both a status and an actual byte count. The status can indicate normal success, unlink or shutdown, timeout, stall, protocol or transaction error, or host-controller failure. Some transfers can complete with partial data. Drivers must interpret each status in the context of the protocol and transfer type; treating every nonzero result as a retry can create an endless loop, while discarding partial data can lose a valid message prefix.
Retries need explicit bounds and device semantics. A bulk endpoint can be retried for a transient transport error, but a control command that toggles state may not be safe to resend blindly. Isochronous packet descriptors can carry per-packet status and length, so the URB-level status alone may not describe every sample. A protocol parser should validate frame boundaries and sequence numbers before publishing data to the rest of the driver.
Instrument submission time, endpoint, requested length, completion time, actual length, status, and retry count. Avoid logging every packet at normal severity on a busy device; that can overwhelm the journal and perturb timing. Add rate-limited diagnostics or tracepoints when the driver provides them, and capture a short USB monitor trace only when needed.
Synchronous helpers do not remove protocol state
The USB API offers synchronous helpers for control and bulk messages. They simplify one-request workflows but still require a valid context where sleeping is allowed, bounded timeout handling, and correct device lifetime. Do not call sleeping helpers from atomic context or an interrupt handler. A timeout does not prove the device did nothing: the device may have received the command but its response was delayed or lost.
For a synchronous request, separate USB transport result from protocol result. Check the return code, actual length, and response fields. If the command changes device state, use a protocol sequence number, query, or idempotent operation where available rather than resending an ambiguous command. Avoid using an unbounded timeout to mask host-controller stalls.
For asynchronous bulk input, a common design keeps a small pool of URBs in flight. Buffer count and size balance latency, throughput, and memory use. A pool that is too small can leave the bus idle while the driver processes data; one that is too large can increase memory pressure and the amount of stale data to drain during disconnect. Measure transfer completion gaps and consumer backlog under the production workload.
Cancellation and disconnect are races to drain
Unlinking an URB requests cancellation; it does not synchronously prove that the completion callback has already finished. Teardown should first prevent new submissions, then unlink active requests, wait for completion callbacks as required by the API, and only afterward free shared state and buffers. A disconnect callback can race with completion, workqueues, timers, and user-facing operations.
Use a stopping flag protected by the driver’s synchronization scheme. The completion callback should observe the flag and avoid resubmission after shutdown begins. Every submitted URB needs one well-defined owner and one drain path. If a driver uses workqueues to process data, flush or cancel that work before freeing the object the worker references. Avoid deadlocks by not waiting for completion while holding a lock that the callback needs.
Test cable removal during idle, under sustained input, during a pending control request, and during device reset. Confirm no use-after-free, callback resubmission, leaked URB, or stuck interface remains. For recovery, distinguish a USB-level reset from a device-protocol reset; one may preserve interface state while the other requires alternate-setting and endpoint reinitialization.
Diagnose the layer that failed
Start with journalctl -k, lsusb -t, and the interface’s sysfs driver link. A device that never enumerates is a different problem from a bound driver with repeated endpoint errors. A device that enumerates but fails after runtime suspend needs power-management and resume analysis, not just endpoint retry tuning.
usbmon can expose bus-level requests for supported host-controller paths. Treat captures as sensitive: they can contain application payloads, identifiers, and credentials. Limit capture duration, protect files, and redact before sharing. The trace shows USB traffic and timing, not the device’s internal state or the meaning of application commands.
Record kernel version, host controller, device revision, endpoint descriptors, driver, transfer type, timeout, power state, and exact error sequence. Compare a known-good port and cable only under a controlled test. Avoid disabling autosuspend globally as a first response; if a per-device quirk is required, document the evidence, scope, and rollback.
Acceptance requires correct buffer ownership, bounded retries, complete teardown, and deterministic recovery under disconnect and cancellation. Validate that data frames are parsed from actual lengths, duplicate command submissions cannot silently corrupt state, and the driver stops producing work after removal. A transfer that succeeds on a warm boot is not sufficient if resume and disconnect paths remain unsafe.
URBs are asynchronous contracts over endpoint transactions. Once the driver treats submission, completion, cancellation, and teardown as explicit state transitions, USB failures become observable and recoverable rather than intermittent memory-lifetime bugs.
Related:
- How udev and the Device Model Actually Discover and Name Hardware
- Linux Device Runtime PM: Usage Counts, Autosuspend, and Callback Order
Sources: