2. USB协议基础:USB传输类型与描述符体系

USB协议采用主机-设备主从架构,所有通信均由主机(Host)发起。理解USB传输类型与描述符体系,是掌握Linux USB子系统驱动初始化流程的前提。本章将系统梳理四种传输类型的特性与适用场景,并深入解析设备、配置、接口、端点四级描述符的结构与枚举顺序。

2.1 USB传输类型

USB规范定义了四种传输类型,分别对应不同的带宽、延迟和可靠性需求。下表从关键维度进行对比:

传输类型 方向 带宽保证 错误重试 典型应用 端点类型
控制传输 双向 10% 保留 是 枚举、配置命令 控制端点 (0)
批量传输 单向 无 是(硬件重试) U盘、打印机数据 批量端点
中断传输 单向 有(轮询间隔) 是 鼠标、键盘 中断端点
同步传输 单向 有(固定带宽) 否 音频、视频流 同步端点
关键点:控制传输是USB枚举的基石。设备连接后,主机首先通过默认控制管道(端点0)获取描述符、分配地址、配置设备。所有驱动初始化都始于控制传输的成功完成。

2.1.1 控制传输

控制传输由建立阶段、数据阶段(可选)、状态阶段组成。每个USB设备必须实现端点0,用于接收标准设备请求(如GET_DESCRIPTOR、SET_ADDRESS、SET_CONFIGURATION)。

  • 建立阶段:主机发送8字节Setup包,包含请求类型、请求码、值、索引、长度。
  • 数据阶段:按Setup包指定的方向传输数据,长度由wLength字段决定。
  • 状态阶段:设备返回握手包(ACK/NAK/STALL),确认操作结果。
驱动视角:在Linux USB核心中,usb_control_msg() 封装了完整的控制传输流程。驱动初始化阶段频繁调用此函数读取描述符、配置设备。

2.1.2 批量传输

批量传输利用未预留的带宽,在总线空闲时发送数据。硬件自动执行错误检测与重试,保证数据完整性。适用于对实时性要求不高、但数据量大的场景。

  • 批量OUT:主机→设备,用于下发数据(如写入U盘)。
  • 批量IN:设备→主机,用于读取数据(如读取U盘内容)。

2.1.3 中断传输

中断传输并非真正的中断,而是主机按固定轮询间隔(bInterval字段指定)查询设备是否有数据需要传输。适用于人机交互设备(HID)。

  • 轮询间隔:全速设备最小1ms,高速设备最小125μs(微帧)。
  • 数据保证:硬件重试失败的数据包,确保不丢失。

2.1.4 同步传输

同步传输为实时数据流预留固定带宽,不进行错误重试。适用于音频、视频等允许少量丢包但对延迟敏感的场景。

  • 无重试:数据出错直接丢弃,不阻塞后续传输。
  • 带宽预留:在帧/微帧中固定分配时间片。
常见误区:同步传输不保证数据正确性,驱动开发者必须自行处理数据校验或容忍丢包。在Linux中,同步端点通常使用URB的 iso_frame_desc 数组管理每个包的传输状态。
USB四种传输类型对比 USB四种传输类型对比 控制传输 端点0,双向 枚举/配置命令 有重试,10%带宽保留 批量传输 单向,大数据量 U盘/打印机 无带宽保证,硬件重试 中断传输 单向,轮询查询 鼠标/键盘 有轮询间隔,有重试 同步传输 单向,实时流 音频/视频 固定带宽,无重试 USB主机 说明 四种传输类型由端点描述符中的bmAttributes字段指定,驱动通过URB类型选择对应传输管道。

2.2 USB描述符体系

USB描述符是设备向主机报告自身能力的结构化数据。描述符采用层级树状组织:设备描述符 → 配置描述符 → 接口描述符 → 端点描述符。主机通过标准设备请求逐层读取,完成设备识别与驱动匹配。

2.2.1 描述符层级结构

  1. 设备描述符(Device Descriptor):位于顶层,描述设备的通用信息,如USB规范版本、VID/PID、设备类、最大包大小(端点0)。
  2. 配置描述符(Configuration Descriptor):每个设备可有一个或多个配置,描述供电方式、最大电流、接口数量。主机通过SET_CONFIGURATION选择激活的配置。
  3. 接口描述符(Interface Descriptor):每个配置包含一个或多个接口,描述功能类别(如HID、Mass Storage、Audio)和备选设置(Alternate Setting)。
  4. 端点描述符(Endpoint Descriptor):每个接口包含零个或多个端点,描述传输方向、传输类型、最大包大小、轮询间隔。
驱动匹配关键:Linux USB核心通过设备描述符中的 idVendor、idProduct、bDeviceClass 以及接口描述符中的 bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol 来匹配驱动。驱动开发者通常在 usb_device_id 表中声明这些匹配字段。
USB描述符层级结构 USB描述符层级结构 设备描述符 VID/PID/设备类/最大包大小 配置描述符 供电方式/电流/接口数量 接口描述符 0 功能类/子类/协议 接口描述符 1 功能类/子类/协议 接口描述符 2 功能类/子类/协议 端点0(控制) 最大包大小 端点1(批量IN) 最大包大小/轮询间隔 端点2(中断OUT) 最大包大小/轮询间隔 端点3(同步IN) 最大包大小/预留带宽 枚举顺序:设备 → 配置 → 接口 → 端点,主机通过GET_DESCRIPTOR请求逐级读取。

2.2.2 关键描述符字段解析

以下列出驱动开发中最常涉及的描述符字段:

描述符类型 字段 偏移 说明
设备描述符 idVendor 8 厂商ID(USB-IF分配)
idProduct 10 产品ID(厂商自定义)
bDeviceClass 4 设备类代码(0x00表示由接口描述符定义)
bMaxPacketSize0 7 端点0最大包大小(8/16/32/64)
配置描述符 bNumInterfaces 4 该配置下的接口数量
bConfigurationValue 5 SET_CONFIGURATION使用的配置值
bMaxPower 7 最大电流(单位2mA)
接口描述符 bInterfaceNumber 2 接口索引号
bInterfaceClass 5 接口类代码(如0x03 HID、0x08 Mass Storage)
bInterfaceProtocol 7 接口协议代码(如0x01键盘、0x02鼠标)
端点描述符 bEndpointAddress 2 端点地址(bit7方向:0 OUT/1 IN,bit3-0端点号)
bmAttributes 3 传输类型(0控制/1同步/2批量/3中断)
wMaxPacketSize 4 最大包大小(同步传输含附加位)
驱动开发提示:在Linux内核中,描述符结构体定义在 include/uapi/linux/usb/ch9.h。驱动通过 usb_get_descriptor() 或直接解析URB返回数据来获取描述符信息。枚举过程中,核心层自动完成描述符解析,驱动只需关注接口描述符和端点描述符即可。

2.2.3 枚举过程中的描述符读取顺序

USB设备插入后,主机按以下顺序读取描述符:

  1. 获取设备描述符(前8字节):主机发送GET_DESCRIPTOR请求,读取设备描述符的前8字节,获取端点0的最大包大小(bMaxPacketSize0)。
  2. 复位设备:主机发送SET_ADDRESS分配唯一地址,设备进入地址状态。
  3. 获取完整设备描述符:使用新地址重新读取完整设备描述符(18字节)。
  4. 获取配置描述符:读取配置描述符(9字节),获取总长度(wTotalLength),再读取完整配置描述符(包含所有接口和端点)。
  5. 解析接口和端点:主机遍历配置描述符中的接口描述符和端点描述符,构建设备拓扑。
  6. 选择配置:主机发送SET_CONFIGURATION激活配置,设备进入配置状态,驱动开始初始化。
常见问题:如果设备描述符中的bMaxPacketSize0与实际端点0能力不匹配,主机可能无法正确读取后续描述符,导致枚举失败。驱动开发者应确保固件中该字段准确无误。
USB枚举描述符读取流程 USB枚举描述符读取流程 1. 获取设备描述符前8字节 获取bMaxPacketSize0 2. SET_ADDRESS分配地址 设备进入地址状态 3. 获取完整设备描述符 18字节,含VID/PID 4. 获取配置描述符 先读9字节,再读完整 5. 解析接口和端点 遍历描述符构建拓扑 6. SET_CONFIGURATION 设备进入配置状态 枚举完成标志 SET_CONFIGURATION成功后,USB核心调用驱动的probe()函数,驱动开始基于描述符信息初始化设备。 驱动probe()初始化开始 读取接口/端点描述符,注册URB

2.3 传输类型与描述符在驱动初始化中的关联

驱动初始化阶段,传输类型与描述符信息紧密耦合:

  • 控制传输:驱动通过端点0发送类特定请求(如HID的SET_IDLE、Mass Storage的Bulk-Only Mass Storage Reset),这些请求定义在接口描述符的bInterfaceClass和bInterfaceProtocol中。
  • 批量/中断/同步端点注册:驱动解析端点描述符后,为每个非控制端点创建URB并指定传输类型。URB的type字段必须与端点描述符的bmAttributes一致。
  • 最大包大小对齐:驱动分配DMA缓冲区时,必须考虑端点描述符中的wMaxPacketSize,确保URB数据长度不超过该值(批量传输除外,可分割为多个事务)。
代码示例:驱动从端点描述符获取信息并初始化URB
/* 遍历接口的端点列表 */
for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
    struct usb_endpoint_descriptor *ep = &iface_desc->ep[i].desc;

    if (usb_endpoint_is_bulk_in(ep)) {
        /* 批量IN端点:用于读取数据 */
        dev->bulk_in_pipe = usb_rcvbulkpipe(dev->udev,
                              ep->bEndpointAddress);
        dev->bulk_in_size = usb_endpoint_maxp(ep);
        dev->bulk_in_buf = kmalloc(dev->bulk_in_size, GFP_KERNEL);
    } else if (usb_endpoint_is_int_out(ep)) {
        /* 中断OUT端点:用于发送命令 */
        dev->int_out_pipe = usb_sndintpipe(dev->udev,
                               ep->bEndpointAddress);
        dev->int_out_interval = ep->bInterval;
    }
}

通过本章的学习,读者应掌握USB四种传输类型的特性与选择依据,理解描述符层级结构及其在枚举过程中的读取顺序,并能够将描述符信息与驱动初始化中的URB配置对应