4. USB枚举流程概述:设备插入检测、复位、地址分配、描述符请求、配置选择的全流程时序图
USB 枚举是主机(Host)与设备(Device)建立通信前必须完成的初始化握手过程。枚举的本质是主机通过默认地址(Address 0)向设备请求一系列标准描述符,并根据描述符信息为设备分配唯一地址、选择工作配置。本章将按时间顺序拆解枚举的 6 个核心阶段,并给出完整的时序关系。
4.1 枚举全流程阶段划分
一次完整的 USB 枚举包含以下 6 个阶段,每个阶段都依赖前一个阶段的结果:
| 阶段 | 触发条件 | 主机动作 | 设备响应 | 关键协议要素 |
|---|---|---|---|---|
| 1. 设备插入检测 | D+/D- 电平变化 | 检测到 J/K 状态,启动轮询 | 上拉电阻使能,信号线状态切换 | Hub 中断 + 端口状态变化 |
| 2. 复位(Reset) | 检测到设备存在 | 驱动 D+/D- 为 SE0 持续 ≥10ms | 复位后进入默认状态,地址为 0 | SE0 信号、复位恢复时间 |
| 3. 地址分配(Set Address) | 复位完成 | 发送 SET_ADDRESS 请求 | 存储新地址,返回 0 长度状态包 | 控制传输、状态阶段握手 |
| 4. 设备描述符请求 | 地址生效 | 发送 GET_DESCRIPTOR (Device) | 返回 18 字节设备描述符 | bMaxPacketSize0 关键字段 |
| 5. 配置描述符请求 | 获取设备描述符后 | 发送 GET_DESCRIPTOR (Configuration) | 返回配置描述符 + 接口/端点描述符 | 描述符集合长度、端点数量 |
| 6. 配置选择(Set Configuration) | 解析描述符完成 | 发送 SET_CONFIGURATION 请求 | 按配置编号激活内部功能 | bConfigurationValue 匹配 |
4.2 设备插入检测与速度识别
USB 总线上的设备插入检测由 Hub 或 Root Hub 的端口状态机完成。当设备插入时,设备端的上拉电阻会将 D+ 或 D- 线拉高,产生可被主机检测到的电平变化。
- 低速设备(1.5 Mbps): 上拉电阻接在 D- 线。
- 全速设备(12 Mbps): 上拉电阻接在 D+ 线。
- 高速设备(480 Mbps): 先以全速模式插入,再通过 Chirp 握手协商切换到高速。
主机侧的 Hub 驱动通过轮询端口状态寄存器(如 PORTSC 寄存器中的 Port Connect Status Change 位)来感知插入事件。
hub_events() 工作队列处理端口状态变化。当检测到 USB_PORT_STAT_CONNECTION 置位时,内核会启动 hub_port_connect_change() 函数,进入枚举流程。
4.3 复位(Reset)与默认状态
主机检测到设备插入后,必须对端口执行复位操作。复位信号通过将 D+ 和 D- 同时拉低(SE0)并保持至少 10ms 来实现。复位完成后:
- 设备地址被重置为 0(默认地址)。
- 设备进入 Default 状态,准备接收控制传输。
- 主机通过读取端口状态寄存器确认复位完成(
Port Reset Change位被置位)。
msleep(10) 或等待端口状态位确认,而不是硬编码固定延时。
4.4 地址分配(Set Address)
复位完成后,主机通过默认地址 0 向设备发送 SET_ADDRESS 标准请求。这是枚举过程中第一个控制传输,使用 控制写传输(bmRequestType = 0x00,bRequest = 0x05)。
地址分配的控制传输分为三个阶段:
- 设置阶段(Setup): 主机发送 8 字节设置包,包含新地址值(1~127)。
- 数据阶段(Data): 无数据(wLength = 0)。
- 状态阶段(Status): 设备返回 0 长度 IN 包确认。
设备在收到 SET_ADDRESS 后,必须在状态阶段完成后立即切换到新地址。后续所有通信都使用新地址。
/* Linux 内核中 SET_ADDRESS 的实现路径 */
/* 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_sndctrlpipe(udev, 0),
USB_REQ_SET_ADDRESS, 0, devnum, 0,
NULL, 0, USB_CTRL_SET_TIMEOUT);
if (retval == 0) {
/* 等待设备完成地址切换 */
msleep(2);
udev->devnum = devnum;
}
return retval;
}
4.5 描述符请求:获取设备能力
地址分配完成后,主机开始通过 GET_DESCRIPTOR 请求获取设备的描述符信息。描述符是 USB 设备的“身份证”,以层级结构描述设备的能力。
4.5.1 设备描述符(Device Descriptor)
主机首先请求设备描述符,长度为 18 字节。关键字段包括:
- bcdUSB: USB 规范版本号(如 0x0200 表示 USB 2.0)。
- bDeviceClass / bDeviceSubClass / bDeviceProtocol: 设备类、子类、协议代码。
- bMaxPacketSize0: 端点 0 的最大包大小(8/16/32/64 字节)。
- idVendor / idProduct: 厂商 ID 和产品 ID。
4.5.2 配置描述符集合(Configuration Descriptor Set)
获取设备描述符后,主机请求配置描述符。配置描述符本身长度为 9 字节,但它后面会跟随:
- 一个或多个 接口描述符(Interface Descriptor)。
- 每个接口描述符后面跟随零个或多个 端点描述符(Endpoint Descriptor)。
主机通过 wTotalLength 字段知道整个描述符集合的总长度,然后一次性读取完整集合。
| 描述符类型 | 长度(字节) | 关键字段 | 作用 |
|---|---|---|---|
| 配置描述符 | 9 | bConfigurationValue, bNumInterfaces, wTotalLength | 描述配置的总体属性 |
| 接口描述符 | 9 | bInterfaceNumber, bAlternateSetting, bNumEndpoints | 描述功能接口 |
| 端点描述符 | 7 | bEndpointAddress, bmAttributes, wMaxPacketSize | 描述数据传输端点 |
4.6 配置选择(Set Configuration)
解析完配置描述符集合后,主机根据设备类驱动或用户空间的配置策略,选择一个合适的配置值,发送 SET_CONFIGURATION 请求(bRequest = 0x09)。
- 请求格式: bmRequestType = 0x00,wValue = 选中的 bConfigurationValue。
- 设备响应: 设备根据配置值激活对应的接口和端点,进入 Configured 状态。
- 枚举完成标志: 配置选择成功后,设备可以开始正常的数据传输(中断传输、批量传输、等时传输)。
4.7 枚举全流程时序图
下面的 SVG 时序图展示了从设备插入到配置完成的完整枚举流程,包含主机与设备之间的交互步骤和时间顺序。
4.8 枚举过程中的关键状态机
USB 设备在枚举过程中经历以下状态转换,每个状态对应特定的主机操作:
- Attached: 设备物理连接到端口。
- Powered: 端口供电(VBUS 有效)。
- Default: 复位完成,地址为 0,可响应控制传输。
- Address: 收到 SET_ADDRESS 并确认,使用新地址。
- Configured: 收到 SET_CONFIGURATION,功能接口激活。
4.9 枚举流程的驱动实现要点
在 Linux 内核中,枚举流程主要由 drivers/usb/core/hub.c 和 drivers/usb/core/generic.c 实现。以下是驱动开发者需要关注的关键函数调用链:
/* Linux 内核枚举主路径(简化) */
hub_port_connect_change() // 检测到端口变化
└─ hub_port_init() // 复位 + 地址分配
├─ hub_set_address() // 发送 SET_ADDRESS
└─ usb_get_device_descriptor() // 获取设备描述符
└─ usb_new_device() // 创建 USB 设备对象
└─ usb_enumerate_device() // 获取配置描述符
└─ usb_get_configuration() // 读取配置描述符集合
└─ usb_choose_configuration() // 选择最佳配置
└─ usb_set_configuration() // 发送 SET_CONFIGURATION
usbmon 或 lsusb -v 抓取枚举阶段的控制传输数据。对比标准枚举流程,可以快速定位是哪个阶段出现问题。例如,如果 lsusb -v 显示 bMaxPacketSize0 = 64 但设备实际只支持 8 字节,则枚举会在第一次控制传输后失败。
4.10 本章小结
USB 枚举是一个严格按序执行的 6 阶段过程:
- 检测 → 复位 → 地址分配 建立通信基础。
- 设备描述符 → 配置描述符 获取设备能力。
- 配置选择 激活设备功能。