4. USB枚举过程
USB 枚举是主机(Host)识别并配置一个新接入 USB 设备的完整过程。它是 USB 子系统中最核心的协议流程,也是驱动开发者必须透彻理解的第一道关卡。枚举过程由主机端的 USB 核心(USB Core)自动完成,设备端只需被动响应标准请求。本章将按时间顺序拆解枚举的 7 个关键阶段,并深入每个阶段背后的数据交互与状态机变迁。
4.1 枚举全流程总览
下图展示了从设备插入到驱动加载完成的完整状态机与数据流。每个阶段都对应一个特定的 USB 设备状态。
4.2 阶段一:设备插入检测
当 USB 设备物理插入端口时,主机控制器(Host Controller)通过检测 D+ 或 D- 数据线上的电平变化来感知设备连接。对于全速/高速设备,D+ 被拉高;对于低速设备,D- 被拉高。
- 硬件检测: 主机控制器轮询端口状态寄存器,发现端口状态变化位(Port Change)置位。
- 内核通知: 主机控制器驱动调用
hub_irq()或hub_events(),触发 Hub 驱动处理端口事件。 - 去抖动(Debounce): 等待 100ms 以消除物理接触抖动,确保连接稳定。
hub_events() → hub_port_connect_change() → usb_alloc_dev() → usb_new_device()
4.3 阶段二:总线复位
主机确认设备连接后,立即向该端口发送复位信号(Reset)。复位信号将 D+ 和 D- 同时拉低至少 10ms,使设备回到初始状态。
| 复位参数 | 规范要求 | 内核实现 |
|---|---|---|
| 复位持续时间 | ≥ 10ms | 通常 10ms ~ 20ms |
| 复位后设备状态 | Default 状态,地址为 0 | 设备等待主机发送 SETUP 包 |
| 速度协商 | 高速设备通过 Chirp 握手 | 主机控制器检测设备速度 |
复位完成后,设备处于 Default 状态,使用默认地址 0 与主机通信。此时设备仅能响应控制传输,且只能接收来自主机的标准请求。
4.4 阶段三:分配地址
主机通过控制传输发送 SET_ADDRESS 请求,为设备分配一个唯一的 7 位地址(1~127)。这是枚举过程中第一个必须成功的关键步骤。
// 主机发送的 SET_ADDRESS 请求格式(控制传输 Setup 包)
// bmRequestType: 0x00 (主机到设备,标准请求,接收端为设备)
// bRequest: 0x05 (SET_ADDRESS)
// wValue: 新地址值 (例如 0x0002)
// wIndex: 0x0000
// wLength: 0x0000
// 内核中分配地址的核心函数 (drivers/usb/core/hub.c)
static int hub_set_address(struct usb_device *udev, int devnum)
{
int retval;
// 构造 SET_ADDRESS 请求
retval = usb_control_msg(udev, usb_snddefctrl(udev),
USB_REQ_SET_ADDRESS, 0,
devnum, 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
if (retval == 0) {
// 地址分配成功后,设备切换到新地址
udev->devnum = devnum;
usb_set_device_state(udev, USB_STATE_ADDRESS);
}
return retval;
}
4.5 阶段四:读取描述符
地址分配完成后,主机开始通过新地址读取设备的描述符信息。描述符是 USB 设备的“身份证”,包含了设备类型、厂商信息、配置能力、接口功能、端点特性等全部关键数据。
4.5.1 描述符读取顺序
- 设备描述符(Device Descriptor): 固定 18 字节,包含 VID、PID、bcdUSB、bMaxPacketSize0 等。
- 配置描述符(Configuration Descriptor): 包含配置的总长度、接口数量、属性等。主机先读取前 9 字节获取总长度,再读取完整描述符。
- 接口描述符(Interface Descriptor): 描述一个功能接口的类、子类、协议、备用设置等。
- 端点描述符(Endpoint Descriptor): 描述每个端点的地址、方向、传输类型、最大包大小。
// 设备描述符结构 (include/uapi/linux/usb/ch9.h)
struct usb_device_descriptor {
__u8 bLength; // 描述符长度 (18)
__u8 bDescriptorType; // 描述符类型 (0x01)
__le16 bcdUSB; // USB 规范版本号 (BCD)
__u8 bDeviceClass; // 设备类
__u8 bDeviceSubClass; // 设备子类
__u8 bDeviceProtocol; // 设备协议
__u8 bMaxPacketSize0; // 端点 0 最大包大小
__le16 idVendor; // 厂商 ID (VID)
__le16 idProduct; // 产品 ID (PID)
__le16 bcdDevice; // 设备版本号
__u8 iManufacturer; // 厂商字符串索引
__u8 iProduct; // 产品字符串索引
__u8 iSerialNumber; // 序列号字符串索引
__u8 bNumConfigurations; // 配置描述符数量
} __attribute__ ((packed));
4.5.2 描述符读取流程
4.6 阶段五:配置设备
主机解析完所有描述符后,根据设备的能力和系统策略选择一个合适的配置(Configuration),发送 SET_CONFIGURATION 请求激活该配置。
- 请求格式:
SET_CONFIGURATION(bRequest = 0x09),wValue 为配置编号(bConfigurationValue)。 - 设备响应: 设备进入 Configured 状态,所有被选中的接口和端点进入可用状态。
- 多配置选择: 主机通常选择第一个配置(bConfigurationValue = 1),除非驱动有特殊要求。
usb_set_configuration() 函数负责发送 SET_CONFIGURATION 请求,并解析配置描述符中的接口和端点信息,创建 usb_interface 和 usb_host_endpoint 数据结构。
4.7 阶段六:加载驱动
设备配置完成后,USB 核心根据设备的 接口信息 寻找匹配的驱动程序。匹配依据包括:
| 匹配方式 | 匹配依据 | 优先级 | 示例 |
|---|---|---|---|
| VID/PID 精确匹配 | idVendor + idProduct | 最高 | 特定厂商的特定设备 |
| 类/子类/协议匹配 | bInterfaceClass + bInterfaceSubClass + bInterfaceProtocol | 中 | HID 类、Mass Storage 类 |
| 通用驱动匹配 | usb_generic_driver | 最低 | 无专用驱动时的兜底 |
// USB 设备 ID 表示例 (驱动声明支持的设备)
static const struct usb_device_id my_usb_table[] = {
// VID/PID 精确匹配
{ USB_DEVICE(0x1234, 0x5678) },
// 类匹配 (HID 类)
{ USB_INTERFACE_INFO(USB_CLASS_HID, 0, 0) },
// 结束标记
{ }
};
MODULE_DEVICE_TABLE(usb, my_usb_table);
驱动匹配成功后,内核调用驱动的 probe() 函数,驱动在此函数中完成初始化、分配资源、注册设备节点等操作。
4.8 阶段七:设备就绪
驱动加载并初始化完成后,设备进入 就绪状态。此时:
- 用户空间可以通过
/dev/bus/usb/或驱动创建的字符设备节点访问设备。 - 设备可以正常进行数据传输(中断传输、批量传输、等时传输)。
- USB 核心将设备标记为
USB_STATE_CONFIGURED,并通知用户空间(udev)生成设备事件。
lsusb -v 可以查看枚举完成后设备的所有描述符信息。使用 usbmon 或 usbhid-dump 可以捕获枚举过程中的 USB 数据包,是分析枚举失败问题的利器。
4.9 枚举失败常见原因与排查
| 失败阶段 | 典型现象 | 可能原因 | 排查方法 |
|---|---|---|---|
| 设备插入检测 | 内核无任何日志 | 硬件连接问题、Hub 驱动未加载 | 检查 dmesg,确认 Hub 驱动正常 |
| 总线复位 | 复位超时 | 设备未正确响应复位信号 | 用示波器测量 D+/D- 电平 |
| 地址分配 | SET_ADDRESS 失败 | 设备端点 0 异常、时序问题 | 检查 usbmon 捕获的 SETUP 包 |
| 读取描述符 | 描述符校验失败 | bMaxPacketSize0 错误、描述符格式错误 | 对比 USB 规范检查描述符内容 |
| 配置设备 | SET_CONFIGURATION 失败 | 设备不支持所选配置 | 检查 bNumConfigurations 和配置值 |
| 加载驱动 | 无驱动匹配 | VID/PID 或类信息未注册 | 检查 modprobe 和 lsusb -t |
dmesg -w 实时监控内核日志是排查枚举问题的最直接手段。