返回首页

使用 libusb 编写 USB 驱动程序:开发者指南

文章解释了如何通过 libusb 创建 USB 驱动程序,而无需处理内核代码。考虑了 Android 在 fastboot 模式下的示例、VID/PID 分析、通过 Control Endpoint 的请求以及描述符解码。材料面向中高级开发者。

5 分钟掌握 USB 驱动程序:从理论到工作代码
Advertisement 728x90

USB 驱动开发无痛指南:libusb 从理论到代码

为 USB 设备编写驱动程序常常让开发者望而却步,因为底层代码过于复杂。然而,使用像 libusb 这样的合适工具包,这个过程就变得像用 socket 进行网络编程一样简单直接。本文将逐步讲解如何为 USB 设备创建一个驱动,以 Android 设备处于 Bootloader(fastboot)模式为例。

USB 基础知识:开发者需要了解的内容

USB 规范定义了通过 VID(厂商 ID)和 PID(产品 ID)识别设备标准机制。这些标识符分配给制造商(VID 由 USB-IF 分配)和特定产品(PID 由制造商自身分配)。设备连接时,主机会请求描述符——固件中的二进制结构,包含设备类、功能以及所需驱动的信息。关键点:大多数任务可以在用户空间通过标准驱动如 Winusb.sys(Windows)或 usbfs(Linux)处理,无需内核代码。

重要术语:

Google AdInline article slot
  • 端点:数据传输的逻辑通道(控制、批量、中断、等时)。
  • 设备描述符:包含 VID/PID 和基本参数的根结构。
  • 设备类:标准化功能(HID、大容量存储),但许多设备使用供应商特定类。

准备设备:选择与设置

我们以 Android 设备处于 Bootloader(fastboot)模式为例。选择此例子的原因:

  • 易获取性:大多数智能手机支持切换到 fastboot。
  • 简单协议:fastboot 文档公开且简洁。
  • 无预装驱动:操作系统不会接管交互。

切换到 Bootloader 模式通常需要在开机时按住按键组合(例如,音量上键 + 电源键)。在 Linux 上识别设备,使用 lsusb

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

此处,18d1 是 Google 的 VID,4ee0 是 fastboot 的 PID。在 Windows 上,USB Device Tree Viewer 提供类似信息。关键点:如果操作系统加载了驱动(例如,在设备管理器中显示 ⚠️),你需要使用 Zadig 将其替换为 Winusb.sys。

Google AdInline article slot

枚举设备:从手动分析到程序化实现

枚举过程在连接时自动启动。操作系统分析 VID/PID 和设备类来选择驱动。对于用户空间,我们使用 libusb 实现。下面是注册连接处理程序的代码:

#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);
}

此代码初始化 libusb,为 VID=0x18d1 和 PID=0x4ee0 的设备注册回调,并等待连接。成功时打印 "Device plugged in!"。在 Windows 上,可能需要分离内核驱动:

libusb_detach_kernel_driver(handle, 0);

通过控制端点与设备通信

对于基本交互,使用控制端点(ID 0x00)——服务请求的标准通道。下面是实现 GET_STATUS 请求:

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);

成功响应(例如 01 00)根据规范解码:第一个字节——电源来源(1 = 电池供电),第二个——远程唤醒支持(0 = 不支持)。控制端点还支持其他标准请求(GET_DESCRIPTOR、SET_CONFIGURATION),对于获取描述符至关重要。

请求描述符:理解设备的关键

描述符是 USB 设备交互的基础。通过 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
);

接收到的数据匹配 libusb_device_descriptor 结构,其字段包括:

  • bcdUSB:USB 规范版本
  • bMaxPacketSize0:控制端点的最大数据包大小
  • idVendor/idProduct:VID 和 PID
  • bNumConfigurations:配置数量

分析描述符可揭示支持的接口和端点。对于配置描述符,将请求类型改为 LIBUSB_DT_CONFIG,并在 wIndex 中指定配置索引。

关键要点

  • 用户空间操作:使用 libusb 而非内核代码——更容易调试且更安全。
  • VID/PID 作为主要标识符:驱动绑定依赖于它们;设备类往往缺乏信息(供应商特定类)。
  • 控制端点作为基本工具:在使用其他端点前,用它获取描述符并控制设备。
  • 描述符作为真相来源:其结构定义于 USB 规范;解码提供设备能力的完整视图。

— Editorial Team

Advertisement 728x90

继续阅读