2. USB设备枚举过程:设备插入、复位、获取描述符、地址分配、配置选择

USB设备枚举是主机(Host)识别并配置一个新接入设备的过程。该过程完全由主机控制器驱动,设备作为被动响应方。理解枚举的每个阶段,是编写正确URB(USB Request Block)和配置端点描述符的基础。

核心要点:枚举过程是主机与设备之间一系列标准控制传输(Control Transfer)的交互序列。设备在获得唯一地址并完成配置前,只能通过默认地址(0)与默认端点(端点0)通信。

2.1 枚举全流程概览

一次完整的USB设备枚举包含以下7个关键步骤。每一步都可能失败,驱动开发者需要理解失败原因及对应的错误码。

USB设备枚举完整流程 ① 设备插入 ② 总线复位 ③ 获取设备描述符(前8字节) ④ 设置地址 ⑤ 获取完整设备/配置/字符串描述符 ⑥ 设置配置 ⑦ 枚举完成,设备就绪

2.2 各阶段详细分析

2.2.1 设备插入与电气检测

当USB设备插入端口时,主机控制器通过检测D+或D-数据线上的上拉电阻来感知设备的存在。对于全速/高速设备,D+线被拉高;对于低速设备,D-线被拉高。此阶段不涉及任何软件交互,完全由硬件完成。

2.2.2 总线复位

主机控制器检测到设备插入后,向该端口发送复位信号(SE0状态持续至少10ms)。复位后,设备地址被重置为0,设备处于默认状态,准备接收控制传输。

驱动开发者提示:在Linux内核中,复位操作由 hub_port_reset() 函数完成。复位后,设备必须能够在10ms内响应来自主机(地址0)的请求。

2.2.3 获取设备描述符(前8字节)

主机通过默认地址0向设备的端点0发送 GET_DESCRIPTOR 请求,请求设备描述符的前8个字节。这8个字节包含了设备描述符的总长度、设备类型、USB规范版本号等信息。主机根据这些信息决定后续的传输策略。

偏移量 字段 大小 说明
0 bLength 1字节 描述符长度(固定为18)
1 bDescriptorType 1字节 描述符类型(设备描述符为1)
2-3 bcdUSB 2字节 USB规范版本号(BCD码)
4 bDeviceClass 1字节 设备类代码
5 bDeviceSubClass 1字节 设备子类代码
6 bDeviceProtocol 1字节 设备协议代码
7 bMaxPacketSize0 1字节 端点0的最大包大小

2.2.4 设置地址

主机分配一个唯一的地址(1-127)给设备,通过 SET_ADDRESS 请求发送给设备。设备收到请求后,必须在状态阶段完成后才能使用新地址。这是枚举过程中唯一一个在状态阶段后才生效的控制请求。

常见陷阱:在 SET_ADDRESS 请求的状态阶段完成之前,设备必须继续响应地址0的请求。状态阶段完成后,设备立即切换到新地址。驱动开发者在实现设备固件时,必须正确处理这个时间窗口。

2.2.5 获取完整描述符

主机使用新分配的地址,再次发送 GET_DESCRIPTOR 请求,获取完整的设备描述符(18字节)。随后,主机获取配置描述符、接口描述符、端点描述符,以及可选的字符串描述符。这些描述符构成了设备的完整功能画像。

描述符的层级关系如下:

USB描述符层级结构 设备描述符 配置描述符 接口描述符 端点描述符 0 端点描述符 1 一个配置可包含多个接口,一个接口可包含多个端点。端点0始终存在,不包含在端点描述符中。

2.2.6 设置配置

主机根据获取到的配置描述符,选择一个合适的配置(通常为第一个配置),通过 SET_CONFIGURATION 请求激活该配置。设备收到请求后,必须按照配置描述符中的端点描述符初始化所有非默认端点。

配置选择的关键决策因素:

  • 电源需求:主机根据设备的 bMaxPower 字段判断当前总线供电能力是否满足。
  • 驱动匹配:主机根据接口描述符中的 bInterfaceClass、bInterfaceSubClass 和 bInterfaceProtocol 加载对应的驱动程序。
  • 带宽计算:对于等时传输和中断传输,主机需要计算总线带宽是否足够。

2.2.7 枚举完成

配置设置成功后,设备进入配置状态(Configured),所有端点准备就绪,可以开始正常的数据传输。此时,设备驱动程序可以开始提交URB进行数据读写。

2.3 枚举过程中的URB交互

枚举过程的每一步本质上都是主机通过URB与设备进行的控制传输交互。以下是一个典型的 GET_DESCRIPTOR 请求对应的URB设置过程:

/* 构造获取设备描述符的URB */
struct urb *urb;
struct usb_ctrlrequest *dr;

urb = usb_alloc_urb(0, GFP_KERNEL);
dr = kmalloc(sizeof(*dr), GFP_KERNEL);

/* 填充控制请求头 */
dr->bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
dr->bRequest = USB_REQ_GET_DESCRIPTOR;
dr->wValue = (USB_DT_DEVICE << 8);  /* 高字节为描述符类型,低字节为索引 */
dr->wIndex = 0;
dr->wLength = 18;  /* 请求完整设备描述符 */

/* 填充URB */
usb_fill_control_urb(urb, udev, usb_rcvctrlpipe(udev, 0),
                     (unsigned char *)dr, buf, 18,
                     descriptor_callback, context);

/* 提交URB */
usb_submit_urb(urb, GFP_KERNEL);

2.4 枚举失败常见原因

失败阶段 典型错误码 可能原因
复位后无响应 -ETIMEDOUT 设备上电初始化时间过长,未能在10ms内响应
获取描述符失败 -EPROTO 设备返回的描述符格式错误或校验失败
设置地址失败 -EIO 设备未正确处理SET_ADDRESS请求的状态阶段
配置选择失败 -ENODEV 设备不支持主机选择的配置,或端点参数超出主机能力
实战建议:在调试USB设备枚举问题时,建议使用 usbmon 工具抓取USB总线上的原始URB数据包。通过分析控制传输的请求和响应,可以快速定位枚举失败的阶段和原因。

2.5 端点描述符与URB提交的关联

枚举过程中获取的端点描述符,直接决定了后续URB提交时的参数配置。每个端点描述符包含以下关键字段:

  • bEndpointAddress: 端点地址(低4位为端点号,最高位为方向:0=OUT,1=IN)
  • bmAttributes: 传输类型(0=控制,1=等时,2=批量,3=中断)
  • wMaxPacketSize: 最大包大小(决定URB缓冲区大小和传输效率)
  • bInterval: 轮询间隔(仅对中断和等时端点有效)

驱动开发者在提交URB时,必须根据端点描述符中的参数正确设置URB的 pipe、transfer_buffer_length 和 interval 字段。错误的参数配置会导致URB提交失败或数据传输异常。