4. USB枚举过程详解:复位、地址分配、读取描述符、配置设备
USB枚举是主机(Host)发现并初始化一个新接入设备的过程。对于USB Gadget而言,设备端(Device)必须严格按照USB 2.0规范规定的状态机响应主机的枚举请求。本章将深入剖析枚举的四个核心阶段:总线复位、地址分配、描述符读取和设备配置,并结合Linux Gadget驱动源码分析其实现机制。
4.1 枚举状态机总览
USB 2.0规范定义了设备枚举的六个主要状态:上电(Powered)、默认(Default)、地址(Address)、配置(Configured)、挂起(Suspended)。其中,复位和地址分配发生在Default与Address状态之间。下图展示了枚举的完整流程:
4.2 总线复位(Bus Reset)
当主机检测到设备插入或用户请求复位时,主机向总线发送复位信号(SE0状态持续至少10ms)。设备端检测到复位后,必须执行以下操作:
- 恢复默认地址:将设备地址重置为0(默认地址)。
- 复位端点状态:所有端点(除端点0外)进入未配置状态。
- 清空缓冲区:丢弃所有未处理的数据。
- 进入默认状态:准备响应端点0上的控制传输。
udc_start()中注册复位处理函数,通常调用usb_gadget_udc_reset()来重置Gadget状态。
复位完成后,主机以12Mbps(全速)或480Mbps(高速)的速率与设备通信,并开始发送SOF(Start of Frame)包。
4.3 地址分配(Address Assignment)
主机通过控制传输的SET_ADDRESS请求为设备分配唯一地址。该过程分为两步:
- 主机发送SET_ADDRESS请求:使用默认地址0,请求包中包含新地址(1~127)。
- 设备确认并切换地址:设备在状态阶段完成后立即切换到新地址,后续所有通信使用新地址。
| 字段 | 值 | 说明 |
|---|---|---|
| bmRequestType | 0x00 | 主机到设备,标准请求,接收方为设备 |
| bRequest | 0x05 | SET_ADDRESS |
| wValue | 新地址 (1~127) | 主机分配的唯一地址 |
| wIndex | 0x0000 | 未使用 |
| wLength | 0x0000 | 无数据阶段 |
在Linux Gadget驱动中,地址分配由UDC硬件自动完成或由驱动软件处理。典型的处理流程如下:
/* Gadget驱动中SET_ADDRESS处理示例 */
static int composite_setup(struct usb_gadget *gadget,
const struct usb_ctrlrequest *ctrl)
{
int ret = -EOPNOTSUPP;
if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
switch (ctrl->bRequest) {
case USB_REQ_SET_ADDRESS:
/* 保存新地址,在状态阶段后生效 */
gadget->dev_addr = ctrl->wValue;
ret = 0;
break;
/* 其他请求处理... */
}
}
return ret;
}
4.4 读取描述符(Descriptor Retrieval)
地址分配完成后,主机开始读取设备的描述符以了解设备的能力和配置。主机通常按以下顺序读取:
- 设备描述符(Device Descriptor):前18字节,包含USB版本、设备类、最大包大小等信息。
- 配置描述符(Configuration Descriptor):包含配置信息、接口数量、供电方式等。
- 接口描述符(Interface Descriptor):描述每个接口的功能和端点数量。
- 端点描述符(Endpoint Descriptor):描述每个端点的地址、方向、传输类型和最大包大小。
- 字符串描述符(String Descriptor):可选的制造商、产品、序列号信息。
下图展示了描述符的层次结构:
主机通过GET_DESCRIPTOR请求读取描述符。设备描述符的前8字节包含总长度,主机据此决定后续读取量。关键字段解析:
| 描述符类型 | bDescriptorType值 | 最小长度 | 关键字段 |
|---|---|---|---|
| 设备描述符 | 0x01 | 18字节 | bcdUSB, idVendor, idProduct, bMaxPacketSize0 |
| 配置描述符 | 0x02 | 9字节 | wTotalLength, bNumInterfaces, bConfigurationValue |
| 接口描述符 | 0x04 | 9字节 | bInterfaceNumber, bAlternateSetting, bNumEndpoints |
| 端点描述符 | 0x05 | 7字节 | bEndpointAddress, bmAttributes, wMaxPacketSize |
| 字符串描述符 | 0x03 | 可变 | UNICODE字符串 |
usb_gadget_driver结构体中的bind()回调注册。驱动使用usb_assign_descriptors()函数将描述符链表绑定到Gadget实例。主机请求时,UDC驱动自动从链表中查找并返回对应描述符。
4.5 配置设备(Configuration)
读取完所有描述符后,主机发送SET_CONFIGURATION请求来激活设备的某个配置。设备收到后必须:
- 验证配置值:检查wValue是否与某个配置描述符的bConfigurationValue匹配。
- 初始化端点:根据配置描述符中的接口和端点信息,初始化所有非默认端点。
- 分配资源:为每个端点分配FIFO缓冲区、DMA通道等硬件资源。
- 进入配置状态:设备现在可以开始正常的数据传输。
SET_CONFIGURATION请求格式:
| 字段 | 值 | 说明 |
|---|---|---|
| bmRequestType | 0x00 | 主机到设备,标准请求,接收方为设备 |
| bRequest | 0x09 | SET_CONFIGURATION |
| wValue | 配置值 (1~255) | 要激活的配置编号,0表示取消配置 |
| wIndex | 0x0000 | 未使用 |
| wLength | 0x0000 | 无数据阶段 |
usb_gadget_config_number()管理多配置。
配置完成后,主机可以发送SET_INTERFACE请求选择备用接口设置(Alternate Setting),用于切换接口的端点配置。例如,音频设备可以通过备用设置切换采样率。
4.6 枚举失败常见原因与调试
嵌入式开发中枚举失败是最常见的问题。下表总结了典型失败原因及排查方法:
| 失败现象 | 可能原因 | 排查方法 |
|---|---|---|
| 主机无响应 | 硬件连接错误、D+上拉电阻缺失 | 用示波器检查D+/D-信号,确认上拉电阻(1.5kΩ)连接正确 |
| 复位后无通信 | 端点0最大包大小不匹配 | 检查设备描述符中bMaxPacketSize0是否与硬件能力一致 |
| 地址分配失败 | 状态阶段处理错误 | 确认设备在状态阶段完成后才切换地址,使用USB分析仪抓包 |
| 描述符读取超时 | 描述符数据错误或长度不匹配 | 使用lsusb -v查看主机侧接收到的描述符,对比预期值 |
| 配置失败 | 端点资源不足或描述符冲突 | 检查端点地址是否重复,确认FIFO大小是否满足wMaxPacketSize |
usbmon或wireshark抓取USB总线数据,可以完整看到枚举过程中的每个Setup包、数据阶段和状态阶段。对于Gadget端,开启内核的CONFIG_USB_GADGET_DEBUG和CONFIG_USB_GADGET_VERBOSE可以打印详细的枚举日志。