3. USB描述符体系:设备描述符、配置描述符、接口描述符、端点描述符、音频特定类描述符的结构与关系
USB描述符是USB设备向主机报告自身能力与配置的核心数据结构。对于UAC(USB Audio Class)设备而言,描述符体系不仅包含标准的USB描述符,还包含音频特定的类描述符,它们共同构成了主机枚举和驱动加载的基础。理解这一层级结构是开发USB声卡固件的首要任务。
3.1 标准USB描述符层级结构
标准USB描述符采用严格的树形层级结构,从设备级到功能级逐层细化。每个USB设备只有一个设备描述符,但可以包含多个配置描述符,每个配置下又包含多个接口和端点描述符。
3.2 各标准描述符的结构与字段
每个标准描述符都有固定的字节布局和字段定义。以下是UAC开发中最关键的几个描述符结构。
3.2.1 设备描述符 (Device Descriptor)
设备描述符是主机读取的第一个描述符,长度为18字节。对于UAC设备,关键字段是 bDeviceClass、bDeviceSubClass 和 bDeviceProtocol,它们通常设置为0xFF(厂商自定义)或0x00(在接口级别定义),因为UAC功能在接口描述符中声明。
struct usb_device_descriptor {
uint8_t bLength; // 描述符长度,固定为18
uint8_t bDescriptorType; // 描述符类型,设备描述符为0x01
uint16_t bcdUSB; // USB规范版本号,如0x0200表示USB 2.0
uint8_t bDeviceClass; // 类代码,UAC设备通常设为0x00
uint8_t bDeviceSubClass; // 子类代码
uint8_t bDeviceProtocol; // 协议代码
uint8_t bMaxPacketSize0; // 端点0最大包大小
uint16_t idVendor; // 厂商ID
uint16_t idProduct; // 产品ID
uint16_t bcdDevice; // 设备版本号
uint8_t iManufacturer; // 厂商字符串描述符索引
uint8_t iProduct; // 产品字符串描述符索引
uint8_t iSerialNumber; // 序列号字符串描述符索引
uint8_t bNumConfigurations; // 支持的配置数量
} __attribute__((packed));
3.2.2 配置描述符 (Configuration Descriptor)
配置描述符长度为9字节,描述了设备的电源管理属性和接口总数。UAC设备通常只使用一个配置,但该配置下包含多个接口。
| 字段 | 偏移 | 大小 | 说明 | UAC典型值 |
|---|---|---|---|---|
| bLength | 0 | 1 | 描述符长度 | 0x09 |
| bDescriptorType | 1 | 1 | 配置描述符类型 | 0x02 |
| wTotalLength | 2 | 2 | 整个配置的总字节数(含所有子描述符) | 根据实际描述符链计算 |
| bNumInterfaces | 4 | 1 | 该配置下的接口数量 | ≥2(AC接口 + AS接口) |
| bConfigurationValue | 5 | 1 | 用于SetConfiguration()的值 | 0x01 |
| iConfiguration | 6 | 1 | 配置字符串描述符索引 | 0x00(无字符串) |
| bmAttributes | 7 | 1 | 电源属性:D7=总线供电,D6=自供电,D5=远程唤醒 | 0x80(总线供电) |
| bMaxPower | 8 | 1 | 最大电流,单位2mA | 0x32(100mA) |
3.2.3 接口描述符 (Interface Descriptor)
接口描述符长度为9字节,是UAC设备的核心描述符。UAC1.0设备通常包含两个接口:音频控制接口(AC Interface,bInterfaceClass=0x01)和音频流接口(AS Interface,bInterfaceClass=0x01)。
bInterfaceSubClass 为0x01,音频流接口的 bInterfaceSubClass 为0x02。主机通过这两个子类值区分控制路径和数据路径。
3.2.4 端点描述符 (Endpoint Descriptor)
端点描述符长度为7字节(标准)或9字节(含附加属性)。对于UAC音频流接口,端点描述符必须使用9字节的 音频数据端点描述符,其中包含同步类型和刷新率等信息。
struct usb_endpoint_descriptor {
uint8_t bLength; // 长度:标准7字节,音频9字节
uint8_t bDescriptorType; // 端点描述符类型0x05
uint8_t bEndpointAddress; // 端点地址:bit7=方向(1:IN,0:OUT),bit3-0=端点号
uint8_t bmAttributes; // 传输类型:bit1-0=00控制,01等时,02批量,03中断
uint16_t wMaxPacketSize; // 最大包大小
uint8_t bInterval; // 轮询间隔(帧/微帧)
// 以下为音频特定扩展字段(仅UAC等时端点使用)
uint8_t bRefresh; // 刷新率
uint8_t bSynchAddress; // 同步端点地址
} __attribute__((packed));
3.3 音频特定类描述符 (Audio Class-Specific Descriptors)
UAC协议在标准描述符的基础上,定义了音频特定的类描述符。这些描述符位于标准接口描述符之后,用于描述音频功能的具体细节,如采样率、位深、声道数、音量控制范围等。
3.3.1 音频控制接口的类描述符
音频控制接口(AC Interface)的类描述符链通常包含以下结构:
- AC接口头描述符 (Header Descriptor): 描述UAC版本号、总字节数、包含的音频流接口数量。
- 输入端子描述符 (Input Terminal Descriptor): 描述音频输入源的类型(如麦克风、Line In)。
- 输出端子描述符 (Output Terminal Descriptor): 描述音频输出目标的类型(如扬声器、耳机)。
- 特征单元描述符 (Feature Unit Descriptor): 描述音量控制、静音控制等音频处理功能。
- 选择单元描述符 (Selector Unit Descriptor): 描述输入源选择功能(可选)。
- 处理单元描述符 (Processing Unit Descriptor): 描述音频处理效果(可选)。
3.3.2 音频流接口的类描述符
音频流接口(AS Interface)的类描述符链相对简单,主要包含:
- AS接口头描述符 (General Descriptor): 描述音频流格式类型(如PCM、AC-3)。
- 格式类型描述符 (Format Type Descriptor): 描述采样率、位深、声道数等具体格式参数。
3.4 描述符间的依赖关系与解析顺序
主机在枚举USB声卡时,按照严格的顺序解析描述符。理解这一顺序对于固件中描述符的组织至关重要。
| 步骤 | 主机操作 | 读取的描述符 | 关键信息获取 |
|---|---|---|---|
| 1 | 获取设备描述符 | 设备描述符 (18字节) | USB版本、厂商/产品ID、配置数量 |
| 2 | 选择配置 | 配置描述符 (9字节) | 接口数量、总描述符长度、电源属性 |
| 3 | 读取完整配置 | 配置描述符 + 所有子描述符 | 根据wTotalLength读取完整描述符链 |
| 4 | 解析接口 | 接口描述符 | 识别AC接口(bSubClass=0x01)和AS接口(bSubClass=0x02) |
| 5 | 解析类描述符 | 音频类特定描述符 | 端子类型、特征单元、格式类型、采样率等 |
| 6 | 解析端点 | 端点描述符 | 端点地址、传输类型、最大包大小、同步参数 |
3.5 描述符组织的最佳实践
bLength 字段必须与实际长度一致。如果某个描述符长度错误,主机将无法正确解析后续描述符,导致枚举失败。建议在固件中使用 sizeof() 宏自动计算长度,避免手动填写错误。
在嵌入式Linux USB声卡开发中,描述符的组织应遵循以下原则:
- 紧凑排列: 所有描述符在内存中连续排列,没有空隙。配置描述符的
wTotalLength必须精确等于所有描述符字节数之和。 - 接口交替设置: UAC1.0要求音频流接口支持至少两个接口交替设置(Alternate Setting)。Alternate Setting 0 表示无数据流(零带宽),Alternate Setting 1 表示激活的音频流(带端点描述符)。
- 字符串描述符可选: 字符串描述符用于提供人类可读的厂商、产品信息,但不是必须的。如果不需要,将对应的索引字段设为0x00。
- 类描述符顺序固定: 音频类描述符必须按照UAC规范定义的顺序排列,例如AC接口中端子描述符必须在特征单元描述符之前。
3.6 描述符调试技巧
在开发过程中,可以使用以下方法验证描述符的正确性:
# 在Linux主机上使用lsusb查看描述符树
lsusb -v -d 1234:5678
# 使用usbmon捕获USB枚举过程
sudo modprobe usbmon
sudo cat /sys/kernel/debug/usb/usbmon/0u | grep "1234:5678"
# 在嵌入式设备上使用USB分析仪或打印描述符数据
# 内核日志中会显示描述符解析错误信息
dmesg | grep -i usb