Skip to content

Host driver

The host side drives a USB device instead of being one. It talks USB/IP itself, so it needs no kernel driver, no vhci and no root - which makes it ideal for tests: serve a device in one process and drive it from another, on any OS.

In USB/IP terms this side is the client (which end is the server): something must already be serving before any of the calls below can succeed - a program on the device API, or a real usbipd exporting genuine hardware.

This imports a device, reads its descriptor over a control transfer, and round-trips a payload through its bulk endpoints. Run vendor_device.py first, then this against it.

examples/doc/host_drive.py
#!/usr/bin/env python3
"""
The documentation's host example: import a device from a USB/IP server and drive
it with usbip.host (libusb-shaped), with no kernel driver, no vhci and no root.
Mirrors the C library's doc/examples/host_drive.c.

In USB terms this program is the HOST; in USB/IP terms it is the CLIENT - it
CONNECTS to a server that is already serving a device (the device end listens).

Start the device first, then drive it:
  python3 examples/vendor_device.py &       # serves 1209:0004 on :3240
  python3 host_drive.py                            # ...or: host_drive.py --host 10.0.0.5

The same code drives a REAL device exported by a real usbipd - point it at that
server, name the bus id it exported, and drop the --vid/--pid check.
"""

import argparse
import sys

import usbip.host
from usbip import USBIP, Stall
from usbip.core import DeviceDescriptor

VENDOR_ID = 0x1209  # what vendor_device.py serves
PRODUCT_ID = 0x0004
EP_BULK_OUT = 0x01  # host -> device
EP_BULK_IN = 0x81  # device -> host


def main():
    ap = argparse.ArgumentParser(description="drive a USB/IP device from Python")
    ap.add_argument("--host", default=None, help="USB/IP server (default: local)")
    ap.add_argument("--port", type=int, default=3240)
    ap.add_argument("--busid", default="1-1", help="bus id to import (real usbipd exports others)")
    ap.add_argument("--vid", type=lambda s: int(s, 0), default=VENDOR_ID)
    ap.add_argument("--pid", type=lambda s: int(s, 0), default=PRODUCT_ID)
    args = ap.parse_args()

    # 1. import the device. Naming a transport is the only USB/IP-aware step;
    #    without one it connects to 127.0.0.1:3240. open() imports the bus id,
    #    checks vid/pid, reads the device descriptor and sets configuration 1.
    transport = USBIP(args.host, args.port) if args.host else None
    try:
        handle = usbip.host.open(args.vid, args.pid, busid=args.busid, transport=transport)
    except OSError as exc:
        print(
            f"cannot reach the USB/IP server: {exc} (is vendor_device.py running?)", file=sys.stderr
        )
        return 1
    except Exception as exc:  # NotFound, protocol errors
        print(f"import failed: {exc}", file=sys.stderr)
        return 1

    with handle:
        # 2. a control transfer: the standard GET_DESCRIPTOR(device)
        raw = handle.control(0x80, 0x06, 0x0100, 0, 18)
        desc = DeviceDescriptor.parse(raw)
        print(
            f"opened {desc.idVendor:04x}:{desc.idProduct:04x} "
            f"(device descriptor: {len(raw)} bytes, USB {desc.bcdUSB >> 8:x}.{desc.bcdUSB & 0xFF:02x})"
        )

        # 3. bulk I/O: send a payload, read the device's echo back
        message = b"hello device"
        try:
            handle.bulk_out(EP_BULK_OUT, message)
            echo = handle.bulk_in(EP_BULK_IN, 64)
        except Stall:
            print("the device STALLed the pipe", file=sys.stderr)
            return 1
        print(f"sent {len(message)} bytes, received {len(echo)} back: {echo!r}")

    matched = echo == message
    verdict = "OK: loopback round-trip matched" if matched else "MISMATCH"
    print(verdict)
    return 0 if matched else 1


if __name__ == "__main__":
    sys.exit(main())

Start the device in one terminal and the driver in another:

python3 examples/vendor_device.py       # the device being driven
python3 examples/doc/host_drive.py      # in a second terminal

usbip.open() / usbip_host_open_vid_pid() return a handle with the familiar control / bulk / interrupt / iso transfers.

import usbip

h = usbip.open(0x1209, 0x0001)                      # local by default
desc = h.control(0x80, 0x06, 0x0100, 0, 18)         # GET_DESCRIPTOR(device)
h.bulk_out(0x01, b"ping\n")                         # OUT
echo = h.bulk_in(0x81, 64)                          # IN
h.close()
#include <stdio.h>
#include "usbip-host.h"

int main(void) {
    usbip_host_context *ctx;
    usbip_host_init(&ctx);                               /* local: 127.0.0.1:3240 */
    usbip_host_handle *h = usbip_host_open_vid_pid(ctx, 0x1209, 0x0001);
    if (!h) { 
        fprintf(stderr, "device not found\n");
        return 1;
    }

    usb_device_descriptor d;
    usbip_host_control_transfer(h, 0x80, 0x06, 0x0100, 0, (uint8_t *)&d, 18, 1000);

    int n; uint8_t echo[64];
    usbip_host_bulk_transfer(h, 0x01, (uint8_t *)"ping\n", 5, &n, 1000);   /* OUT */
    usbip_host_bulk_transfer(h, 0x81, echo, sizeof(echo), &n, 1000);        /* IN  */
    printf("%04x:%04x, %d echo bytes\n", d.idVendor, d.idProduct, n);

    usbip_host_close(h);
    usbip_host_exit(ctx);
    return 0;
}

A STALLed transfer raises Stall (USB_ERROR_PIPE in C); recover the pipe with handle.clear_halt(addr).

The same code drives genuine hardware that a Linux box exports with the kernel's own usbipd - the API neither knows nor cares that the device is physical, and your driver still runs on any OS. Exporting real hardware is the one step that is Linux-only, since usbipd/usbip bind have no equivalent elsewhere; run it on the machine holding the device:

sudo modprobe usbip-host
sudo usbipd -D                       # the USB/IP server daemon
usbip list -l                        # find the bus id, e.g. 3-2
sudo usbip bind -b 3-2               # export it

Then import it by that bus id, without a vid/pid check:

import usbip

h = usbip.attach("10.0.0.5", "3-2")          # remote host, the exported bus id
print(h.control(0x80, 0x06, 0x0100, 0, 18))  # its real device descriptor

Unlike the kernel's usbip list -r, the Python host has no device-list call: it imports the bus id you name (1-1 is what this library always serves).

For a real device class, subclass Driver (Python) or register a usbip_host_driver (C). The bundled drivers - HID, CDC-ACM, MSC, MTP, Bluetooth - open with Driver.open(); see Host & drivers for the pattern.

If the program that should drive the device already exists - dfu-util, lsusb, a libusbK or WinUSB application - it needs no porting and none of the API above: the C wrapper libraries let a stock binary drive a virtual device unmodified. For pyusb specifically - a Python caller pointed at the libusb wrapper - see pyusb.

Mix languages

A C device can be driven by the Python host and vice-versa - they share the wire protocol. The test suite does exactly this for cross-language verification.

Full programs: examples/doc/host_drive.py; in C, examples/host/cdc_host.c, uvc_host.c, host_probe.c. API: Host API · Host drivers.