3. USB描述符解析:设备描述符、配置描述符、接口描述符、端点描述符
USB描述符(Descriptor)是USB设备向主机报告自身能力与属性的结构化数据块。主机通过标准设备请求(如GET_DESCRIPTOR)逐层获取这些信息,从而构建完整的设备拓扑与功能视图。在驱动开发中,正确解析描述符是配置URB、选择端点、确定传输类型的先决条件。
核心概念:USB描述符采用层级嵌套结构——设备描述符指向配置描述符,配置描述符包含接口描述符,接口描述符又包含端点描述符。驱动通常只关注与数据传输直接相关的接口和端点描述符。
3.1 描述符层级结构总览
标准USB描述符遵循固定的层级关系,主机通过设备地址和描述符类型字段逐级请求。下图展示了完整的描述符树形结构:
驱动开发要点:在Linux USB子系统中,
usb_device结构体通过config字段链接配置描述符,配置描述符通过intf_cache链接接口描述符,接口描述符通过altsetting数组包含端点描述符。驱动遍历这些结构即可获取所有传输参数。
3.2 设备描述符(Device Descriptor)
设备描述符是USB设备的“身份证”,位于描述符层级的最顶层。主机在枚举阶段通过默认控制端点(端点0)获取该描述符,长度为18字节。其关键字段如下:
| 偏移(字节) | 字段名 | 大小(字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为18 |
| 1 | bDescriptorType | 1 | 描述符类型,设备描述符为0x01 |
| 2-3 | bcdUSB | 2 | USB规范版本号(BCD码),如0x0200表示USB 2.0 |
| 4 | bDeviceClass | 1 | 设备类代码(0x00表示在接口描述符中定义) |
| 5 | bDeviceSubClass | 1 | 设备子类代码 |
| 6 | bDeviceProtocol | 1 | 设备协议代码 |
| 7 | bMaxPacketSize0 | 1 | 端点0的最大包大小(USB 2.0为64字节) |
| 8-9 | idVendor | 2 | 厂商ID(VID),由USB-IF分配 |
| 10-11 | idProduct | 2 | 产品ID(PID),由厂商自定义 |
| 12-13 | bcdDevice | 2 | 设备版本号(BCD码) |
| 14 | iManufacturer | 1 | 厂商字符串描述符索引 |
| 15 | iProduct | 1 | 产品字符串描述符索引 |
| 16 | iSerialNumber | 1 | 序列号字符串描述符索引 |
| 17 | bNumConfigurations | 1 | 支持的配置描述符数量 |
常见误区:设备描述符中的
bDeviceClass字段若为0x00,表示设备类在接口描述符中定义。此时不能仅凭设备描述符判断设备类型,必须继续解析接口描述符。例如,复合设备(Composite Device)通常使用此方式。
3.3 配置描述符(Configuration Descriptor)
配置描述符描述设备的供电模式、最大电流消耗以及包含的接口数量。一个设备可以有多个配置(如低功耗模式和高性能模式),但同一时刻只能激活一个配置。配置描述符长度为9字节:
| 偏移(字节) | 字段名 | 大小(字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为9 |
| 1 | bDescriptorType | 1 | 描述符类型,配置描述符为0x02 |
| 2-3 | wTotalLength | 2 | 配置描述符及其所有子描述符的总长度(字节) |
| 4 | bNumInterfaces | 1 | 该配置包含的接口数量 |
| 5 | bConfigurationValue | 1 | 用于SetConfiguration请求的配置值 |
| 6 | iConfiguration | 1 | 配置字符串描述符索引 |
| 7 | bmAttributes | 1 | 配置特性(位7:总线供电,位6:自供电,位5:远程唤醒) |
| 8 | bMaxPower | 1 | 最大电流消耗(单位:2mA,如0x32表示100mA) |
驱动实践:在Linux中,
usb_set_configuration()函数根据bConfigurationValue激活配置。驱动通常使用usb_get_descriptor()获取配置描述符,然后通过usb_parse_configuration()解析整个配置树。
3.4 接口描述符(Interface Descriptor)
接口描述符是驱动开发中最常关注的描述符,它定义了设备的一个功能单元。一个配置可以包含多个接口(如音频设备包含音频控制和音频流两个接口),每个接口可以有多个备用设置(Alternate Setting)。接口描述符长度为9字节:
| 偏移(字节) | 字段名 | 大小(字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为9 |
| 1 | bDescriptorType | 1 | 描述符类型,接口描述符为0x04 |
| 2 | bInterfaceNumber | 1 | 接口编号(从0开始) |
| 3 | bAlternateSetting | 1 | 备用设置编号(用于切换接口参数) |
| 4 | bNumEndpoints | 1 | 该接口使用的端点数量(不包括端点0) |
| 5 | bInterfaceClass | 1 | 接口类代码(如0x08表示大容量存储类) |
| 6 | bInterfaceSubClass | 1 | 接口子类代码 |
| 7 | bInterfaceProtocol | 1 | 接口协议代码 |
| 8 | iInterface | 1 | 接口字符串描述符索引 |
备用设置机制:接口描述符的
bAlternateSetting字段允许设备在运行时切换端点配置。例如,USB视频设备在低分辨率时使用较少的端点带宽,高分辨率时切换到更多端点。驱动通过usb_set_interface()选择备用设置。
3.5 端点描述符(Endpoint Descriptor)
端点描述符是驱动配置URB的直接依据,它定义了数据传输的方向、传输类型、最大包大小和轮询间隔。每个非0端点都有一个对应的端点描述符,长度为7字节:
| 偏移(字节) | 字段名 | 大小(字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为7 |
| 1 | bDescriptorType | 1 | 描述符类型,端点描述符为0x05 |
| 2 | bEndpointAddress | 1 | 端点地址(位3-0:端点号,位7:方向,0=OUT,1=IN) |
| 3 | bmAttributes | 1 | 传输类型(位1-0:00=控制,01=等时,10=批量,11=中断) |
| 4-5 | wMaxPacketSize | 2 | 最大包大小(位10-0:大小,位12-11:附加事务数) |
| 6 | bInterval | 1 | 轮询间隔(单位:帧/微帧,中断端点有效) |
端点地址编码规则:
- 位7(方向位):0表示OUT(主机到设备),1表示IN(设备到主机)
- 位3-0(端点号):取值范围0x00~0x0F(端点0为控制端点,双向)
- 示例:
0x81表示端点1 IN,0x02表示端点2 OUT
传输类型编码(bmAttributes位1-0):
- 00:控制传输(Control)——仅端点0使用
- 01:等时传输(Isochronous)——实时数据,无重传
- 10:批量传输(Bulk)——大块数据,保证交付
- 11:中断传输(Interrupt)——小数据量,定期轮询
批量传输端点配置要点:批量端点的
wMaxPacketSize在USB 2.0高速模式下固定为512字节,全速模式下为64字节。bInterval字段对批量端点无意义(通常为0),但驱动在提交URB时仍需正确设置urb->interval字段。
3.6 描述符解析实战:从原始字节到驱动结构
以下代码演示如何在Linux驱动中解析端点描述符,提取批量传输端点的关键参数:
#include <linux/usb.h>
/* 解析端点描述符,配置批量传输URB */
static int configure_bulk_ep(struct usb_device *udev,
struct usb_endpoint_descriptor *ep_desc,
struct urb *urb)
{
int ret;
unsigned int ep_addr;
unsigned int ep_type;
unsigned int max_pkt;
/* 步骤1:读取端点地址 */
ep_addr = ep_desc->bEndpointAddress;
/* 步骤2:读取传输类型 */
ep_type = ep_desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
/* 步骤3:检查是否为批量端点 */
if (ep_type != USB_ENDPOINT_XFER_BULK) {
dev_err(&udev->dev, "端点不是批量传输类型\n");
return -EINVAL;
}
/* 步骤4:读取最大包大小 */
max_pkt = le16_to_cpu(ep_desc->wMaxPacketSize);
/* 步骤5:配置URB */
usb_fill_bulk_urb(urb, udev,
usb_rcvbulkpipe(udev, ep_addr) | /* IN端点 */
usb_sndbulkpipe(udev, ep_addr), /* OUT端点 */
NULL, 0, bulk_complete, NULL);
urb->transfer_buffer_length = max_pkt; /* 设置传输长度 */
dev_info(&udev->dev,
"批量端点配置成功:地址=0x%02x, 最大包=%u字节\n",
ep_addr, max_pkt);
return 0;
}
解析流程总结:驱动解析描述符的典型路径为:
usb_device->config->intf_cache->altsetting->endpoint。通过usb_endpoint_dir_in()和usb_endpoint_xfer_bulk()等内联函数,可以快速判断端点方向和传输类型。
3.7 描述符解析流程SVG图
下图展示了主机从获取设备描述符到配置端点的完整解析流程,以及驱动在其中的介入点:
3.8 描述符解析常见问题与避坑
- 描述符长度校验:解析前必须检查
bLength字段是否与预期一致,防止设备返回畸形描述符导致内核崩溃。 - 字节序问题:
wMaxPacketSize和wTotalLength为小端序(Little-Endian),在x86平台上需使用le16_to_cpu()转换。 - 备用设置切换:切换备用设置后,端点描述符可能发生变化(如
wMaxPacketSize改变),驱动必须重新解析。 - 端点0的特殊性:端点0没有对应的端点描述符,其最大包大小在设备描述符的
bMaxPacketSize0字段中定义。 - 复合设备解析:复合设备的每个接口可能属于不同的类,驱动需根据
bInterfaceClass匹配对应的驱动程序。
本章小结:USB描述符是设备与主机之间的“通信契约”。设备描述符提供全局信息,配置描述符定义供电与接口数量,接口描述符标识功能单元,端点描述符则直接决定URB的传输参数。在批量传输驱动开发中,正确解析端点描述符的
bEndpointAddress(方向)和wMaxPacketSize(包大小)是配置URB的第一步,也是确保数据传输正确性的基础。