Device core API¶
Build a virtual USB device from descriptors and requests: create a
USBDevice, author its configuration through
DescriptorGroups (raw bytes or lazy blocks,
with Endpoint pipes declared via
In / Out), register per-interface
control / SET_INTERFACE handlers and per-endpoint data callbacks, then
plug() it onto a transport. The core has no class concept - the
object-oriented authoring layer lives in the class layer;
Components → USBDevice explains the split.
Most applications author devices with the class layer or the ready-made classes; the core API is for hand-built devices - see Components → USBDevice for what authoring one directly looks like.
device
¶
The device core - build a virtual USB device from descriptors and requests.
The core knows only descriptors and requests: append descriptor content through
:class:DescriptorGroup (raw bytes or lazy blocks), route :class:Endpoint
pipes, and register per-interface control / SET_INTERFACE handlers plus
per-endpoint data callbacks. Standard requests, Microsoft OS/BOS/WebUSB descriptors
and the URB engine are answered here. It has no class concept - the
object-oriented authoring layer (Interface / Function) lives in
:mod:usbip.function and is built entirely on this module's public API.
USBDevice
¶
USBDevice(vid, pid, *, product=None, manufacturer=None, serial=None, bcdDevice=256, bcdUSB=512, device_class=0, device_subclass=0, device_protocol=0)
Source code in usbip/device.py
num_interfaces
property
¶
Number of claimed interfaces == the next free bInterfaceNumber.
interfaces
property
¶
The claim owners, one per bInterfaceNumber (class-built devices: the
:class:Interface objects; raw-authored devices: whatever tag - possibly
None - was passed to claim_interface).
functions
property
¶
The group owners in wire order (class-built devices: the
:class:Function objects).
add_string
¶
add_group
¶
Open a new :class:DescriptorGroup - one function's worth of
configuration-descriptor content and the Microsoft OS scoping unit. owner is an
opaque tag the layer above may attach (never interpreted here).
Source code in usbip/device.py
route_endpoint
¶
Route an endpoint into the device, relocating its address if taken.
The declared address is a preference: it is honoured when the number is
free, so a lone function keeps the addresses it asks for, but when another
endpoint already holds it the endpoint is moved to the lowest free number
in the same direction. Without this a composite would emit a descriptor in
which two interfaces claim one address, and the host would route both to
whichever registered last. Mirrors ep_alloc_number() in
the C library's src/usbip_device.c. owner tags the endpoint for diagnostics.
Source code in usbip/device.py
on_control
¶
Register handler(setup, data=b"") -> bytes | None for class/vendor
control requests addressed to interface ifnum (raise :class:Stall to
reject). ifnum=None registers the device-level fallback: requests with
a non-interface recipient, or for an interface nobody registered.
Source code in usbip/device.py
has_control_fallback
¶
on_set_alt
¶
Register handler(alt) for SET_INTERFACE on ifnum. Unregistered
interfaces acknowledge SET_INTERFACE with no side effect.
add_reset_hook
¶
add_speed_hook
¶
set_device_triple
¶
Set the device-descriptor class triple - unless the device is composite, where 0xEF/0x02/0x01 is pinned (a function's own triple must not silently unmake the composite) and the request is ignored with a diagnostic.
Source code in usbip/device.py
set_composite
¶
Declare the device composite: several independent class functions on one device. Call BEFORE adding any class.
Sets the device-descriptor triple to 0xEF/0x02/0x01 (Miscellaneous / Common Class / Interface Association) and asks every multi-interface class to emit an Interface Association Descriptor grouping its own interfaces.
Linux does not need this - cdc_acm and friends group their interfaces from the class-specific descriptors (CDC's Union descriptor). Windows does: usbccgp splits a composite into one child devnode per function, and without an IAD it splits per interface, handing CDC's data interface to a different devnode from its communications interface, so no COM port forms.
A single-function device must NOT set this. The triple is pinned from
here on: a class's own device_triple (e.g. Bluetooth's
0xE0/0x01/0x01) is ignored with a diagnostic, since overwriting
0xEF/0x02/0x01 would silently unmake the composite. Mirrors the C
usbip_device_set_composite().
Source code in usbip/device.py
enable_msos
¶
Advertise Microsoft OS 1.0 + 2.0 descriptors with the given Compatible ID so
Windows auto-binds a function driver without an .inf: "WINUSB" (so libusb
apps like dfu-util work without Zadig) or "MTP" (Media Transfer Protocol).
guid adds a DeviceInterfaceGUID - WinUSB wants one, MTP leaves it None.
Bumps bcdUSB to 0x0210 so the host fetches the BOS (Microsoft OS 2.0).
function scopes the advertisement to one :class:DescriptorGroup (a
:class:Function or :class:Interface is accepted and resolved to its
group). Leave it None to describe the whole device, which is right for a
single-function device but not for a composite: there Windows gives each
function its own devnode, and a device-wide Compatible ID would bind one
driver over all of them. Prefer :meth:Function.enable_msos. Re-enabling
the same scope replaces it.
Source code in usbip/device.py
enable_winusb
¶
Advertise WinUSB (Compatible ID "WINUSB" + a DeviceInterfaceGUID).
set_msos_vendor_codes
¶
Override the vendor request codes for the Microsoft OS requests (default 0x20 for Microsoft OS 1.0, 0x21 for Microsoft OS 2.0). The device answers those codes itself before any interface sees them, so change them if a function on this device uses vendor request 0x20/0x21 with wIndex 0x0004, 0x0005 or 0x0007.
Source code in usbip/device.py
enable_webusb
¶
Advertise WebUSB: add a WebUSB platform-capability descriptor to the BOS
and answer the bVendorCode/GET_URL (wIndex 0x02) vendor request with url,
so a WebUSB-capable browser surfaces the device and can open it. Pick
vendor_code distinct from WinUSB's 0x20/0x21 if both are enabled. Pair with
enable_winusb() so the device also binds WinUSB on Windows. Bumps bcdUSB to
0x0210 so the host fetches the BOS. iLandingPage is derived from the URL
(present when non-empty), matching the C usbip_device_enable_webusb().
Source code in usbip/device.py
set_speed
¶
Report a link speed to the importer (default SPEED_FULL). USB/IP is URB-level, so "high speed" is just the reported speed plus speed-correct endpoint sizing. Call BEFORE add() so speed-aware classes (cdc_acm, msc, mtp, uac, uvc) size their endpoints; for robustness any already-added interface is re-sized here too.
Source code in usbip/device.py
add
¶
Add a :class:Function (or a bare :class:Interface, wrapped in an anonymous
single-interface Function) - the one way to add anything, bundled class or your
own. Composition itself lives in the class layer: any object implementing
_attach(dev) can be added; the device only provides the primitives
(add_group, claim_interface, route_endpoint, on_control, ...).
Returns the class's data-plane handle - _attach's return value, which for a
:class:Function is its :attr:~usbip.function.Function.primary (CDCACM ->
the Data port) and for a bare interface is the interface itself. An _attach
that returns nothing yields the object passed in.
Source code in usbip/device.py
is_iso
¶
set_iso_pacing
¶
Pace isochronous completions to real time. USB/IP has no SOF clock, so by default iso transfers complete instantly and the host's audio/video engine free-runs (a UAC speaker plays many times too fast). When enabled, each iso completion is scheduled for the wall-clock time its packet schedule would really take (a per-endpoint deadline) and delivered by a background pacer thread, so the serve thread never blocks - full-duplex (speaker + mic at once) stays real time, like real hardware.
Source code in usbip/device.py
pace_cancel
¶
Drop a queued iso completion whose URB was unlinked (so the host's stream releases cleanly instead of getting a late completion).
Source code in usbip/device.py
cancel_urb
¶
Handle a host CMD_UNLINK for seqnum: drop any parked IN URB (real
kernels pipeline interrupt-IN URBs and unlink the spares; completing an
unlinked seqnum makes vhci 'cannot find urb' and tear the device down) and
any queued iso completion.
Source code in usbip/device.py
device_descriptor
¶
Source code in usbip/device.py
config_bytes
¶
The full configuration descriptor, exactly as served to the host: the groups' blocks rendered in order (callables re-render every time, so late-bound content is always current).
Source code in usbip/device.py
interface_triples
¶
(bInterfaceClass, bInterfaceSubClass, bInterfaceProtocol) per interface, in bInterfaceNumber order, read from the rendered configuration (alternate setting 0) - used for the USB/IP device-list reply.
Source code in usbip/device.py
bos_bytes
¶
The BOS descriptor (WebUSB / Microsoft OS 2.0 capabilities), as served to the host.
Source code in usbip/device.py
vendor_descriptor
¶
Answer a core-handled vendor request - Microsoft OS (WinUSB) or WebUSB GET_URL - or None if it isn't one of ours (so an interface can handle it).
Source code in usbip/device.py
reset_io
¶
Flush every endpoint (buffered IN data + parked URBs) and let each interface reset its own buffers. The USB/IP server calls this when a host attaches, so a re-attach behaves like a fresh plug (a real device sees a bus reset). Without it, events that piled up after a previous host left get delivered to the new host and corrupt its init.
Source code in usbip/device.py
handle_urb
¶
Process one URB. Returns the urb to send its response now, or None if
the URB was PARKED (an IN endpoint with no data yet) - it will be
completed asynchronously via respond when the device writes data, so
the server's read loop never blocks. respond is supplied by the USB/IP
server; when absent we fall back to a short blocking wait.
Source code in usbip/device.py
759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 | |
plug
¶
Serve this device and return immediately (a context manager that unplugs on exit).
Plugging another device onto the same transport exports it alongside this
one rather than failing to bind: the listener is shared, and each device
is named by its own busid - 1-1, 1-2, ... in plug order unless
:meth:set_busid named it. The importer picks one (usbip attach -b
1-2), and every imported device gets its own connection. Distinct from a
composite device (:meth:set_composite), which is one device with
several functions.
Source code in usbip/device.py
set_busid
¶
Name this device on the wire instead of the 1-<n> assigned by
:meth:plug. Only useful when a process exports several devices and the
importer wants stable names regardless of plug order. Call before plug().
Source code in usbip/device.py
DescriptorGroup
¶
One function's worth of configuration-descriptor content: an ordered run
of descriptor blocks plus the interface numbers claimed through it. Created
with :meth:USBDevice.add_group; the groups render in creation order to form
the configuration descriptor.
A block is bytes, or a zero-argument callable returning bytes -
callables are rendered on every GET_DESCRIPTOR, so content that depends on
late-bound state (relocated endpoint addresses, speed-sized wMaxPacketSize,
alternate settings) is always current.
The group is also the scoping key for the Microsoft OS descriptors
(:meth:enable_msos): on a composite each group is advertised as its own
Windows function. A device that never creates a group is treated as one
implicit device-wide group.
Source code in usbip/device.py
add
¶
claim_interface
¶
Claim the next free bInterfaceNumber for this group and return it.
add_endpoint
¶
Create an :class:Endpoint from an :func:In/:func:Out spec, route it
(relocating the address if taken), and append its endpoint descriptor as a
lazy block - the descriptor always reflects the final address and size.
Source code in usbip/device.py
enable_msos
¶
Advertise Microsoft OS descriptors scoped to this group's interfaces (see
:meth:USBDevice.enable_msos).
Endpoint
¶
A byte pipe. Device writes IN data and reads OUT data; the device's URB handler is the other end. Backed by a queue so reads/writes decouple.
Source code in usbip/device.py
write
¶
Queue IN data and complete as many parked URBs as it covers, in FIFO order.
Draining a WHILE loop here (not a single pop) matters once a client keeps more than one IN URB outstanding: completing only one URB per write left the remainder in _q with further URBs still parked - nothing ever re-matched them, so the stream stalled and the NEXT write (e.g. an MSC CSW) was handed to the wrong URB, completing them out of order. Each parked URB takes up to its length from the FRONT chunk only (_pop_locked), preserving write/short-packet boundaries.
Source code in usbip/device.py
read
¶
take_or_park
¶
Return queued IN data if any; otherwise register complete(data) to
run when data arrives (NAK-until-data) and return None. seqnum lets the
server cancel this parked URB if the host later unlinks it.
Source code in usbip/device.py
cancel
¶
Drop a parked IN URB the host has UNLINKed, so it is never completed (completing an unlinked seqnum desyncs the kernel's vhci). Returns True if one was removed.
Source code in usbip/device.py
stall
¶
Halt the endpoint: every URB STALLs until the host clears the halt.
Any IN URB the host already has outstanding is STALLed immediately - leaving it parked is the hang this exists to avoid, since the host would otherwise sit on the read until its own timeout fires. Queued data survives the halt and is delivered once it is cleared: mass storage halts bulk-IN to abandon a failed data phase, then queues the CSW.
Source code in usbip/device.py
clear_halt
¶
reset
¶
Drop buffered data and any parked URBs - called when a host (re)attaches so leftovers from a previous session can't corrupt the new one.