Back to Home

Writing USB drivers with libusb: a guide for developers

Article explains creating USB drivers via libusb without working with kernel code. Considers an example with Android in fastboot mode, VID/PID analysis, requests via Control Endpoint and descriptor decoding. Material oriented towards middle/senior developers.

USB drivers in 5 minutes: from theory to working code
Advertisement 728x90

# USB Drivers Without the Pain: From Theory to Code with libusb

Writing drivers for USB devices often intimidates developers with the complexity of low-level code. However, with the right toolkit like libusb, the process becomes as straightforward as network programming with sockets. In this article, we'll walk through creating a driver for a USB device, using an Android device in bootloader mode as an example.

USB Basics: What a Developer Needs to Know

The USB specification defines a standard mechanism for identifying devices via VID (Vendor ID) and PID (Product ID). These identifiers are assigned to the manufacturer (VID by USB-IF) and the specific product (PID by the manufacturer itself). When a device connects, the host requests descriptors—binary structures in the firmware containing info on the device class, capabilities, and required driver. Key point: most tasks can be handled in user space via standard drivers like Winusb.sys (Windows) or usbfs (Linux), without kernel code.

Important terminology:

Google AdInline article slot
  • Endpoint: Logical channel for data transfer (Control, Bulk, Interrupt, Isochronous).
  • Device Descriptor: Root structure with VID/PID and basic parameters.
  • Device Class: Standardized functionality (HID, Mass Storage), but many devices use Vendor Specific Class.

Preparing the Device: Selection and Setup

We'll use an Android device in Bootloader (fastboot) mode as an example. Reasons for this choice:

  • Accessibility: Most smartphones support switching to fastboot.
  • Simple protocol: Fastboot documentation is open and minimalistic.
  • No preinstalled drivers: The OS doesn't hijack the interaction.

Switching to Bootloader mode usually requires holding a button combo during power-on (e.g., volume up + power). To identify the device on Linux, use lsusb:

$ lsusb
Bus 008 Device 014: ID 18d1:4ee0 Google Inc. Nexus/Pixel Device (fastboot)

Here, 18d1 is Google's VID, 4ee0 is the PID for fastboot. On Windows, USB Device Tree Viewer provides similar info. Key point: If the OS loads a driver (e.g., shows with a ⚠️ in Device Manager), you'll need to replace it with Winusb.sys using Zadig.

Google AdInline article slot

Enumerating the Device: From Manual Analysis to Programmatic Implementation

The enumeration process starts automatically on connection. The OS analyzes VID/PID and device class to select a driver. For user space, we implement this with libusb. Here's code for registering a connection handler:

#include <print>
#include <libusb-1.0/libusb.h>

auto hotplug_callback(
    libusb_context *ctx,
    libusb_device *device,
    libusb_hotplug_event event,
    void *user_data
) -> int {
    std::println("Device plugged in!");
    return 0;
}

auto main() -> int {
    libusb_context *context = nullptr;
    libusb_init(&context);

    libusb_hotplug_callback_handle handle;
    libusb_hotplug_register_callback(
        context,
        LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
        LIBUSB_HOTPLUG_ENUMERATE,
        0x18d1, 0x4ee0,
        LIBUSB_HOTPLUG_MATCH_ANY,
        hotplug_callback, nullptr,
        &handle
    );

    while (true) {
        if (libusb_handle_events(context) < 0) break;
    }

    libusb_hotplug_deregister_callback(context, handle);
    libusb_exit(context);
}

This code initializes libusb, registers a callback for devices with VID=0x18d1 and PID=0x4ee0, and waits for connection. On success, it prints "Device plugged in!". On Windows, you may need to detach the kernel driver:

libusb_detach_kernel_driver(handle, 0);

Communicating with the Device via Control Endpoint

For basic interaction, use the Control Endpoint (ID 0x00)—the standard channel for service requests. Here's implementing a GET_STATUS request:

Google AdInline article slot
libusb_device_handle *handle = nullptr;
libusb_open(device, &handle);

std::vector<std::uint8_t> data(0xFF);
const auto result = libusb_control_transfer(
    handle,
    LIBUSB_ENDPOINT_IN | LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_STANDARD,
    LIBUSB_REQUEST_GET_STATUS,
    0x00, 0x00,
    data.data(), data.size(),
    1000
);

if (result >= 0) print_bytes(std::span(data).subspan(0, result));
libusb_close(handle);

A successful response (e.g., 01 00) decodes per the spec: first byte—power source (1 = battery-powered), second—remote wakeup support (0 = not supported). The Control Endpoint enables other standard requests too (GET_DESCRIPTOR, SET_CONFIGURATION), crucial for fetching descriptors.

Requesting Descriptors: The Key to Understanding the Device

Descriptors are the foundation of USB device interaction. Request the device descriptor via GET_DESCRIPTOR:

const auto result = libusb_control_transfer(
    handle,
    LIBUSB_ENDPOINT_IN | LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_STANDARD,
    LIBUSB_REQUEST_GET_DESCRIPTOR,
    (LIBUSB_DT_DEVICE << 8), // Tip deskriptora
    0x00,
    data.data(), data.size(),
    1000
);

The received data matches the libusb_device_descriptor structure with fields like:

  • bcdUSB: USB specification version
  • bMaxPacketSize0: Max packet size for Control Endpoint
  • idVendor/idProduct: VID and PID
  • bNumConfigurations: Number of configurations

Analyzing descriptors reveals supported interfaces and endpoints. For a configuration descriptor, change the request type to LIBUSB_DT_CONFIG and specify the config index in wIndex.

Key Points

  • User Space Operation: Use libusb instead of kernel code—easier to debug and safer.
  • VID/PID as Primary Identifiers: Driver binding relies on them; device class is often uninformative (Vendor Specific Class).
  • Control Endpoint as Basic Tool: Use it to fetch descriptors and control the device before setting up other endpoints.
  • Descriptors as Source of Truth: Their structure is defined in the USB spec; decoding provides a full picture of device capabilities.

— Editorial Team

Advertisement 728x90

Read Next