4. 中断端点特性:中断传输的时序保证、轮询间隔(bInterval)、最大包大小与适用场景(HID设备)

中断传输(Interrupt Transfer)是USB规范中为有限延迟、周期性、小批量数据传输而设计的传输类型。与批量传输尽力而为的语义不同,中断传输提供了明确的时序保证,确保主机在约定的时间窗口内轮询设备并完成数据交换。本章深入剖析中断端点的核心参数:轮询间隔(bInterval)、最大包大小,以及它们在HID(Human Interface Device)类设备中的典型应用。

核心认知: 中断传输的“中断”并非硬件中断,而是主机保证在固定时间间隔内主动轮询端点。设备无法主动发起数据传输,只能等待主机的IN令牌包。

4.1 中断传输的时序保证机制

USB主机控制器(Host Controller)在每帧(Frame)或微帧(Microframe)的调度中,为每个中断端点预留了固定的服务时间槽。这种调度机制保证了中断传输的最大延迟(Maximum Latency)和最小轮询频率。

  • 全速/低速总线: 帧周期为1ms,主机在每个帧的SOF(Start of Frame)之后,按照中断端点的bInterval值,在特定帧中发起事务。
  • 高速总线: 微帧周期为125µs,主机在每个微帧中检查调度表,决定是否轮询该中断端点。
  • 时序保证的本质: 主机控制器硬件保证在bInterval指定的时间窗口内,至少有一次针对该端点的IN或OUT事务。如果总线带宽充足,延迟可以小于bInterval;但不会超过bInterval。
关键指标: 中断传输的延迟上界 = bInterval × 帧/微帧周期。例如,全速设备bInterval=10,则最大延迟为10ms;高速设备bInterval=8(对应8×125µs=1ms),则最大延迟为1ms。
中断传输时序保证:帧级轮询调度 帧 N 帧 N+1 帧 N+2 帧 N+3 帧 N+4 帧 N+5 bInterval = 3 每3帧轮询一次 最大延迟 = 3 × 1ms = 3ms 全速模式下,主机保证3ms内完成轮询 主机控制器在每帧的SOF之后,根据中断端点描述符的bInterval值, 在调度表中插入轮询事务。硬件保证时序,不受软件负载影响。

4.2 轮询间隔(bInterval)详解

bInterval是中断端点描述符中的关键字段,位于端点描述符的偏移量7处。其编码方式与总线速度密切相关,直接决定了中断传输的轮询频率。

总线速度 bInterval 编码方式 轮询周期计算公式 有效范围
低速 (Low Speed) 直接表示帧数 周期 = bInterval × 1ms 10 ~ 255 (10ms ~ 255ms)
全速 (Full Speed) 直接表示帧数 周期 = bInterval × 1ms 1 ~ 255 (1ms ~ 255ms)
高速 (High Speed) 指数编码 (2^(bInterval-1)) 周期 = 2^(bInterval-1) × 125µs 1 ~ 16 (125µs ~ 4.096s)
超高速 (SuperSpeed) 直接表示微帧数 周期 = bInterval × 125µs 1 ~ 16 (125µs ~ 2ms)
常见配置错误: 低速中断端点必须将bInterval设置为10或更大(≥10ms),这是USB规范对低速设备轮询频率的硬性限制。如果设置为小于10的值,主机控制器可能拒绝配置或导致不可预测的调度行为。

4.2.1 bInterval 对URB提交的影响

在Linux内核中,中断URB的提交周期必须与bInterval匹配。驱动程序通过usb_fill_int_urb()函数初始化URB时,内核会根据端点描述符自动计算合适的URB间隔。

/* Linux内核源码: drivers/usb/core/urb.c */
void usb_fill_int_urb(struct urb *urb, struct usb_device *dev,
                      unsigned int pipe, void *transfer_buffer,
                      int buffer_length, usb_complete_t complete_fn,
                      void *context, int interval)
{
    urb->dev = dev;
    urb->pipe = pipe;
    urb->transfer_buffer = transfer_buffer;
    urb->transfer_buffer_length = buffer_length;
    urb->complete = complete_fn;
    urb->context = context;

    if (unlikely(interval < 1))
        interval = 1;
    urb->interval = interval;

    /* 内核会根据端点bInterval对interval进行修正 */
    usb_urb_interval_check(urb);
}

驱动程序在提交URB时,urb->interval字段必须与端点描述符的bInterval一致。如果驱动程序设置的interval小于bInterval,主机控制器调度器会以bInterval为准,忽略驱动程序的设置。

4.3 最大包大小(MaxPacketSize)约束

中断端点的最大包大小受总线速度和端点类型的限制。USB规范为中断端点定义了明确的包大小上限,超过该上限的配置将被主机拒绝。

总线速度 中断端点最大包大小 备注
低速 (Low Speed) 8 字节 仅支持控制传输和中断传输
全速 (Full Speed) 64 字节 与批量端点最大包大小相同
高速 (High Speed) 1024 字节 允许在微帧内进行突发传输(最多3笔事务)
超高速 (SuperSpeed) 1024 字节 支持突发传输,最大突发长度由端点伴侣描述符定义
设计原则: 中断传输适用于小批量、周期性数据。如果单次数据量超过最大包大小,驱动程序必须将数据拆分为多个URB,或考虑使用批量传输/等时传输。HID设备通常每次报告(Report)大小在8~64字节之间,完全适配中断端点的包大小限制。

4.4 适用场景:HID设备深度分析

HID(Human Interface Device)类设备是中断传输最典型的应用场景。键盘、鼠标、游戏手柄、触摸屏、条形码扫描仪等设备都通过中断端点与主机通信。

4.4.1 HID设备的中断端点配置

一个标准的HID设备通常包含一个中断IN端点,用于向主机发送输入报告(Input Report)。部分HID设备还包含一个中断OUT端点,用于接收主机的输出报告(Output Report),例如键盘的LED状态指示。

  • 中断IN端点: 设备向主机报告按键、鼠标移动、触摸事件等。bInterval通常设置为8~32(全速下对应8ms~32ms),兼顾响应速度和总线带宽。
  • 中断OUT端点: 主机向设备发送配置指令或状态反馈,例如键盘CapsLock灯控制。bInterval通常与IN端点一致或略大。
  • 报告描述符(Report Descriptor): HID设备通过报告描述符定义数据的格式和含义,中断端点传输的原始数据由HID驱动解析为具体的输入事件。
HID设备中断端点通信架构 USB主机 HID核心驱动 (hid-core) USB中断URB调度器 主机控制器 (HCD) USB 总线 HID设备 中断IN端点 (0x81) 中断OUT端点 (0x02) HID报告描述符 中断传输数据流 主机按bInterval轮询 → 设备返回HID报告 → 主机解析为输入事件 典型HID设备中断端点参数 • 鼠标:bInterval=8 (8ms),MaxPacketSize=8字节 • 键盘:bInterval=16 (16ms),MaxPacketSize=8字节 • 游戏手柄:bInterval=4 (4ms),MaxPacketSize=64字节 • 触摸屏:bInterval=2 (2ms),MaxPacketSize=64字节

4.4.2 HID设备中断URB处理流程

在Linux内核中,HID设备的中断URB处理遵循以下典型流程:

  1. 设备枚举阶段: USB核心读取HID设备的接口描述符和端点描述符,获取中断IN端点的bInterval和MaxPacketSize。
  2. HID驱动绑定: hid-core驱动为中断IN端点分配并初始化一个中断URB,设置回调函数为hid_irq_in()。
  3. URB提交: 驱动调用usb_submit_urb()提交URB,主机控制器开始按bInterval周期轮询设备。
  4. 数据到达: 设备有输入事件时,在主机轮询时返回数据;无事件时返回NAK。主机在收到数据后调用回调函数。
  5. 报告解析: 回调函数将原始数据传递给HID核心,HID核心根据报告描述符解析出具体的输入事件(按键、移动等)。
  6. URB重提交: 回调函数处理完成后,驱动重新提交URB,维持连续轮询。
/* Linux内核源码: drivers/hid/usbhid/hid-core.c */
static void hid_irq_in(struct urb *urb)
{
    struct hid_device *hid = urb->context;
    int ret;

    switch (urb->status) {
    case 0: /* 成功 */
        /* 将原始报告传递给HID核心层解析 */
        hid_input_report(hid, HID_INPUT_REPORT,
                         urb->transfer_buffer,
                         urb->actual_length, 1);
        break;
    case -ECONNRESET: /* 设备断开 */
    case -ENOENT:
    case -ESHUTDOWN:
        return; /* 不再重提交URB */
    default: /* 其他错误 */
        break;
    }

    /* 重新提交URB,维持连续轮询 */
    ret = usb_submit_urb(urb, GFP_ATOMIC);
    if (ret && ret != -EPERM && ret != -ENODEV)
        dev_err(&hid->dev, "can't resubmit intr, %s-%d\n",
                hid->name, urb->interval);
}
性能优化: HID驱动在URB回调函数中重新提交URB,而不是在提交时设置URB_NO_INTERRUPT flag后自动重提交。这种设计允许驱动在每次数据到达后执行必要的解析和过滤,再决定是否继续轮询。对于低延迟要求的游戏外设,可以适当减小bInterval值,但需注意总线带宽占用。

4.5 中断传输与其他传输类型的对比

特性 中断传输 批量传输 等时传输 控制传输
时序保证 有(bInterval保证最大延迟) 无(尽力而为) 有(固定带宽预留) 无(尽力而为)
错误重传 支持(硬件重试) 支持(硬件重试) 不支持(无重传) 支持(硬件重试)
数据大小 小(≤1024字节/事务) 大(≤1024字节/事务) 中(≤1024字节/事务) 小(≤64字节/全速)
典型延迟 1ms ~ 几十ms 不确定(可能数百ms) 125µs ~ 1ms 不确定
适用场景 HID、少量周期性数据 存储、大块数据 音频、视频流 枚举、配置、命令
带宽预算: 中断传输虽然有时序保证,但占用的是总线周期性传输带宽。全速总线上,所有中断端点和等时端点的总带宽不能超过90%(即每帧900字节)。高速总线上,中断端点每微帧最多占用1024字节。设计系统时,必须计算所有中断端点的带宽总和,避免超出总线容量导致配置失败。

4.6 中断端点配置实战:bInterval选择指南

在实际的嵌入式系统或USB设备固件开发中,选择合适的bInterval值需要在响应速度和总线带宽之间取得平衡。

  • 高响应需求(游戏鼠标、触控笔): 选择bInterval=1~4(全速下1ms~4ms,高速下125µs~500µs)。注意高速设备bInterval=1时,每微帧轮询一次,对主机控制器和USB总线带宽消耗较大。
  • 中等响应需求(办公鼠标、键盘): 选择bInterval=8~16(全速下8ms~16ms)。这是大多数HID设备的默认配置,兼顾用户体验和总线效率。
  • 低频率数据(温度传感器、环境监测): 选择bInterval=32~255(全速下32ms~255ms)。对于变化缓慢的物理量,过高的轮询频率只会浪费总线带宽和CPU资源。
  • 电池供电设备: 适当增大bInterval(如50ms~100ms),减少主机轮询频率,降低设备端唤醒和响应功耗。
内核调试技巧: 使用 lsusb -v 查看设备端点描述符中的bInterval值。使用 usbmon 或 ftrace 跟踪中断URB的提交和完成时间,验证实际轮询间隔是否与bInterval一致。如果发现延迟异常,检查主机控制器的调度策略和总线带宽占用情况。
bInterval 选择决策流程 确定设备响应需求 是否需要 < 5ms 延迟? 是 高速设备 bInterval = 1~4 否 全速/低速设备 bInterval = 8~32 验证总线带宽 & 功耗约束