3. USB描述符解析:设备描述符、配置描述符、接口描述符、端点描述符

USB描述符(Descriptor)是USB设备向主机报告自身能力与属性的结构化数据块。主机通过标准设备请求(如GET_DESCRIPTOR)逐层获取这些信息,从而构建完整的设备拓扑与功能视图。在驱动开发中,正确解析描述符是配置URB、选择端点、确定传输类型的先决条件。

核心概念:USB描述符采用层级嵌套结构——设备描述符指向配置描述符,配置描述符包含接口描述符,接口描述符又包含端点描述符。驱动通常只关注与数据传输直接相关的接口和端点描述符。

3.1 描述符层级结构总览

标准USB描述符遵循固定的层级关系,主机通过设备地址和描述符类型字段逐级请求。下图展示了完整的描述符树形结构:

USB描述符层级结构图 设备描述符 配置描述符 接口描述符 端点描述符 0 端点描述符 1 一个配置可包含多个接口,一个接口可包含多个端点(端点0除外) 驱动通常只解析与功能相关的接口描述符和端点描述符
驱动开发要点:在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图

下图展示了主机从获取设备描述符到配置端点的完整解析流程,以及驱动在其中的介入点:

USB描述符解析流程与驱动介入点 USB描述符解析流程与驱动介入点 1. 获取设备描述符 2. 获取配置描述符 3. 解析接口描述符 4. 遍历端点描述符 是否为批量端点? 是 否 5. 配置批量传输URB 设置端点地址、最大包大小、回调函数 6. 提交URB到主机控制器 其他传输类型 驱动介入点:在步骤3-5之间,驱动通过usb_interface结构体获取接口描述符,然后遍历altsetting->endpoint数组解析端点描述符 批量传输驱动重点关注bEndpointAddress的方向位和wMaxPacketSize的大小字段

3.8 描述符解析常见问题与避坑

  • 描述符长度校验:解析前必须检查bLength字段是否与预期一致,防止设备返回畸形描述符导致内核崩溃。
  • 字节序问题:wMaxPacketSize和wTotalLength为小端序(Little-Endian),在x86平台上需使用le16_to_cpu()转换。
  • 备用设置切换:切换备用设置后,端点描述符可能发生变化(如wMaxPacketSize改变),驱动必须重新解析。
  • 端点0的特殊性:端点0没有对应的端点描述符,其最大包大小在设备描述符的bMaxPacketSize0字段中定义。
  • 复合设备解析:复合设备的每个接口可能属于不同的类,驱动需根据bInterfaceClass匹配对应的驱动程序。
本章小结:USB描述符是设备与主机之间的“通信契约”。设备描述符提供全局信息,配置描述符定义供电与接口数量,接口描述符标识功能单元,端点描述符则直接决定URB的传输参数。在批量传输驱动开发中,正确解析端点描述符的bEndpointAddress(方向)和wMaxPacketSize(包大小)是配置URB的第一步,也是确保数据传输正确性的基础。