2. USB设备枚举过程:设备插入、复位、获取描述符、地址分配、配置选择
USB设备枚举是主机(Host)识别并配置一个新接入设备的过程。该过程完全由主机控制器驱动,设备作为被动响应方。理解枚举的每个阶段,是编写正确URB(USB Request Block)和配置端点描述符的基础。
2.1 枚举全流程概览
一次完整的USB设备枚举包含以下7个关键步骤。每一步都可能失败,驱动开发者需要理解失败原因及对应的错误码。
2.2 各阶段详细分析
2.2.1 设备插入与电气检测
当USB设备插入端口时,主机控制器通过检测D+或D-数据线上的上拉电阻来感知设备的存在。对于全速/高速设备,D+线被拉高;对于低速设备,D-线被拉高。此阶段不涉及任何软件交互,完全由硬件完成。
2.2.2 总线复位
主机控制器检测到设备插入后,向该端口发送复位信号(SE0状态持续至少10ms)。复位后,设备地址被重置为0,设备处于默认状态,准备接收控制传输。
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字节)。随后,主机获取配置描述符、接口描述符、端点描述符,以及可选的字符串描述符。这些描述符构成了设备的完整功能画像。
描述符的层级关系如下:
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 | 设备不支持主机选择的配置,或端点参数超出主机能力 |
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提交失败或数据传输异常。