3. USB协议基础(二):USB传输类型、帧与微帧机制、带宽管理

USB协议定义了四种不同的传输类型,分别对应不同的应用场景:控制传输、批量传输、中断传输和等时传输。理解这四种传输类型的特性、事务格式以及适用场景,是开发USB设备驱动和主机控制器驱动的核心基础。同时,USB的帧/微帧机制和带宽管理策略,决定了主机如何高效、公平地分配总线资源。

3.1 四种USB传输类型详解

USB是一种基于轮询(Polling)的总线协议,所有传输都由主机(Host)发起。设备只能被动响应主机的请求。四种传输类型在可靠性、延迟、数据量等方面各有侧重。

传输类型 可靠性 延迟 数据量 重试机制 典型应用
控制传输 最高(保证送达) 中等 小(通常≤64字节) 硬件重试,错误重传 枚举、配置、命令
批量传输 高(CRC校验+重试) 不确定(尽力而为) 大(8/16/32/64字节) 硬件重试,错误重传 U盘、打印机数据
中断传输 高(CRC校验+重试) 有界(保证轮询间隔) 小(≤1024字节) 硬件重试,错误重传 鼠标、键盘、HID
等时传输 无(无重试) 固定(保证带宽) 大(≤1023字节) 无重试,允许丢包 音频、视频、摄像头

3.1.1 控制传输(Control Transfer)

控制传输是USB设备枚举和配置的基础,也是唯一必须实现的传输类型。它采用请求-应答模式,由主机发起一个设置事务(Setup Transaction),然后可选地跟随数据事务(Data Transaction)和状态事务(Status Transaction)。

  • 阶段组成:控制传输分为三个阶段:Setup阶段、Data阶段(可选)、Status阶段。
  • 端点:默认使用端点0(双向端点)。
  • 数据包大小:低速设备为8字节,全速设备为8/16/32/64字节,高速设备为64字节。
  • 可靠性:硬件层面保证重试,直到成功或主机放弃。
驱动开发提示:在Linux内核中,控制传输通常通过 usb_control_msg() 函数发起。该函数封装了Setup包、数据缓冲区和超时时间。驱动开发者需要熟悉USB标准请求(如GET_DESCRIPTOR、SET_CONFIGURATION)以及类特定请求。
// Linux内核中发起控制传输的典型调用
int ret;
struct usb_device *udev = interface_to_usbdev(intf);

ret = usb_control_msg(udev,
                      usb_rcvctrlpipe(udev, 0), // 接收方向,端点0
                      USB_REQ_GET_DESCRIPTOR,    // bRequest
                      USB_DIR_IN,                // bmRequestType
                      (USB_DT_DEVICE << 8),      // wValue
                      0,                         // wIndex
                      buf,                       // 数据缓冲区
                      sizeof(struct usb_device_descriptor), // wLength
                      USB_CTRL_GET_TIMEOUT);     // 超时时间
if (ret < 0) {
    dev_err(&intf->dev, "Failed to get device descriptor: %d\n", ret);
}

3.1.2 批量传输(Bulk Transfer)

批量传输用于传输大量数据,且对实时性要求不高。它利用总线空闲时间进行传输,当总线带宽紧张时,批量传输会被延迟。

  • 特点:保证数据完整性(CRC校验+硬件重试),但不保证延迟。
  • 数据包大小:全速最大64字节,高速最大512字节,超高速最大1024字节。
  • 事务格式:由IN/OUT事务组成,每个事务包含令牌包、数据包和握手包。
  • 应用:U盘(BOT协议)、打印机、大容量存储设备。
带宽管理注意:在USB 2.0协议中,批量传输没有预留带宽。如果总线上有等时或中断传输占用了大量带宽,批量传输的吞吐量会显著下降。在高速模式下,批量传输可以独占最多80%的帧时间。

3.1.3 中断传输(Interrupt Transfer)

中断传输并非硬件中断,而是主机保证在固定的时间间隔内轮询设备,以获取数据。它适用于需要定期查询且数据量较小的场景。

  • 轮询间隔:设备在描述符中声明所需的轮询间隔(bInterval)。全速设备间隔为1~255ms,高速设备间隔为1~16(单位125µs微帧)。
  • 数据包大小:全速最大64字节,高速最大1024字节。
  • 可靠性:与批量传输相同,具有CRC校验和硬件重试。
  • 应用:鼠标、键盘、游戏手柄、HID设备。

3.1.4 等时传输(Isochronous Transfer)

等时传输是唯一不保证数据可靠性的传输类型。它牺牲了重试机制,以换取固定的带宽和可预测的延迟。

  • 特点:无握手阶段,无重试,允许数据丢失(CRC错误时直接丢弃)。
  • 带宽保证:在枚举阶段,主机控制器会为等时端点预留带宽。如果带宽不足,枚举会失败。
  • 数据包大小:全速最大1023字节,高速最大1024字节。
  • 应用:USB音频设备(扬声器、麦克风)、USB视频设备(摄像头)、实时数据采集。
关键对比:等时传输与中断传输最大的区别在于“可靠性 vs 实时性”。中断传输保证数据送达,但延迟受限于轮询间隔;等时传输保证带宽和低延迟,但可能丢包。音频设备必须使用等时传输,因为重试会导致音频卡顿。

3.2 帧与微帧机制

USB总线的时间被划分为固定长度的帧(Frame)或微帧(Microframe)。主机控制器(HC)在每个帧/微帧开始时发送SOF(Start of Frame)包,用于同步和计时。

USB速度 帧长度 微帧数量 SOF间隔
低速/全速(USB 1.x) 1 ms 1 1 ms
高速(USB 2.0) 1 ms 8 125 µs
超高速(USB 3.x) 125 µs 1(总线间隔) 125 µs

帧/微帧是主机控制器进行带宽分配和调度的基本时间单位。在每个帧/微帧的开始,主机控制器会执行一个调度列表,决定哪些端点可以在这个时间片内进行事务。

微帧的意义:USB 2.0引入微帧机制,将1ms帧细分为8个125µs的微帧。这使得高速设备可以获得更精细的带宽分配和更低的延迟。例如,一个高速中断端点可以将轮询间隔设置为1个微帧(125µs),从而实现接近实时响应的效果。
USB 2.0 帧与微帧结构 帧 N(1 ms) 微帧0 微帧1 微帧2 微帧3 微帧4 微帧5 微帧6 微帧7 微帧内部调度示例(125 µs) SOF 等时IN 中断IN 批量OUT 批量IN EOF 时间轴(125 µs)

3.3 带宽管理

USB主机控制器(如EHCI、xHCI)负责在每个帧/微帧内调度所有端点的事务。带宽管理是确保所有周期性传输(等时和中断)满足其延迟和带宽要求的关键。

3.3.1 带宽分配原则

  • 周期性传输优先:在每个微帧开始时,主机控制器首先处理等时传输和中断传输。这些传输具有预留带宽。
  • 非周期性传输填充:在完成周期性传输后,剩余的帧时间用于批量传输和控制传输。
  • 带宽上限:USB 2.0规定,所有周期性传输(等时+中断)最多占用一个微帧的90%(即112.5 µs)。剩余的10%用于批量传输、控制传输和协议开销(SOF、EOF等)。
  • 超高速增强:USB 3.x进一步优化了带宽利用率,支持更大的数据包和更高效的调度。

3.3.2 带宽计算示例

假设一个全速USB音频设备,使用等时传输,每个微帧传输64字节数据。计算其带宽占用:

  1. 事务时间:全速模式下,传输64字节数据需要约 13 × 64 + 开销 ≈ 832 µs?错误。全速模式下,一个64字节的等时IN事务大约需要 13 + (64 × 8) / 12 ≈ 55 µs(包含协议开销)。
  2. 带宽占用:55 µs / 1000 µs = 5.5%。
  3. 多个端点:如果总线上有多个等时端点,总占用不能超过90%。
驱动开发常见错误:在枚举等时端点时,如果请求的带宽超过剩余可用带宽,主机控制器会返回错误(-ENOSPC)。驱动开发者必须处理这种错误,并考虑降低数据率或使用更小的数据包。

3.3.3 主机控制器调度策略

不同主机控制器(UHCI/OHCI/EHCI/xHCI)的调度实现不同,但核心思想一致:维护一个帧列表(Frame List)或调度树(Schedule Tree)。

EHCI主机控制器调度结构(简化) 帧列表 帧N-1 帧N 帧N+1 周期性调度 等时端点列表 中断端点列表 Split Transaction 非周期性调度 批量端点列表 控制端点列表 异步列表 执行引擎 调度流程:帧列表 → 周期性调度(等时/中断优先)→ 非周期性调度(批量/控制) EHCI使用iTD(等时传输描述符)和qTD(队列传输描述符)管理端点 等时 中断 批量 控制 开销

3.3.4 带宽管理在驱动开发中的影响

  • 端点描述符的bInterval字段:驱动开发者在配置端点时,必须正确设置轮询间隔。对于等时端点,这个值决定了每个微帧/帧传输的数据量,直接影响带宽占用。
  • URB(USB Request Block):在Linux内核中,驱动通过提交URB来发起传输。对于等时URB,驱动需要指定每个微帧传输的数据包数量(number_of_packets)和每个包的大小(iso_frame_desc)。
  • 带宽预留失败处理:当主机控制器无法为等时端点预留所需带宽时,usb_submit_urb() 会返回 -ENOSPC。驱动应该优雅地处理此错误,例如降低采样率或通知用户。
// Linux内核中等时URB的提交示例
struct urb *urb;
int ret;

urb = usb_alloc_urb(ISO_PACKETS, GFP_KERNEL);
if (!urb)
    return -ENOMEM;

urb->dev = udev;
urb->pipe = usb_rcvisocpipe(udev, endpoint->bEndpointAddress);
urb->transfer_buffer = buffer;
urb->transfer_buffer_length = ISO_PACKETS * ISO_PACKET_SIZE;
urb->complete = isoc_complete;
urb->number_of_packets = ISO_PACKETS;
urb->interval = 1; // 每个微帧传输一次

for (i = 0; i < ISO_PACKETS; i++) {
    urb->iso_frame_desc[i].offset = i * ISO_PACKET_SIZE;
    urb->iso_frame_desc[i].length = ISO_PACKET_SIZE;
}

ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
    dev_err(&intf->dev, "Failed to submit isoc URB: %d\n", ret);
    usb_free_urb(urb);
    return ret;
}

3.4 本章小结

  • USB定义了四种传输类型:控制、批量、中断、等时,分别对应不同的可靠性、延迟和带宽需求。
  • 帧(1ms)和微帧(125µs)是USB总线的时间基础,主机控制器在每个时间单元内调度事务。
  • 带宽管理遵循“周期性传输优先”原则,等时和中断传输预留带宽,批量传输利用空闲时间。
  • 驱动开发者必须理解端点描述符中的带宽参数(如wMaxPacketSize、bInterval),并正确处理带宽不足的错误。