4. 中断端点特性详解:中断传输的时序保证、轮询间隔(bInterval)、最大包大小限制、中断端点在Gadget中的角色

中断端点(Interrupt Endpoint)是USB通信中用于传输低延迟、有限大小数据的端点类型。与批量端点(Bulk)尽力而为的传输模式不同,中断端点为USB主机提供了明确的轮询保证,确保设备能在可预测的时间窗口内被主机访问。本章将深入剖析中断端点的核心特性,包括时序保证机制、bInterval轮询间隔、最大包大小限制,以及中断端点在Gadget驱动中的具体角色与实现要点。

核心认知:中断端点并非硬件中断,而是主机保证在固定时间间隔内轮询的端点。在Gadget驱动中,中断端点常用于HID设备(键盘、鼠标)、游戏手柄、以及需要周期性状态上报的复合设备。

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

中断传输的时序保证是USB协议为实时性需求提供的核心能力。主机控制器(Host Controller)在每帧(Frame)或微帧(Microframe)中为中断端点预留了固定的轮询时隙,确保设备的数据能在规定延迟内被主机读取。

  • 全速/低速模式:每帧(1ms)轮询一次,bInterval表示帧数。
  • 高速模式:每微帧(125µs)轮询一次,bInterval表示微帧数。
  • 超高速模式:每微帧(125µs)轮询,bInterval表示微帧数,但支持更大的包大小。

中断传输的时序保证并非“实时中断”,而是带宽预留机制。主机在枚举阶段根据端点描述符中的bInterval值,在USB帧调度表中为该端点分配固定的轮询时间片。如果设备没有数据发送,主机仍会发起IN令牌包,设备返回NAK握手;如果设备有数据,则返回DATA包。

调试要点:在Gadget驱动调试中,如果中断端点出现数据丢失或延迟抖动,首先检查bInterval配置是否正确,其次确认主机端驱动是否支持该轮询间隔。使用USB分析仪可以捕获主机是否按预期频率发送IN令牌。
中断传输时序保证:主机轮询调度 帧 N 帧 N+1 帧 N+2 帧 N+3 帧 N+4 帧 N+5 轮询 轮询 轮询 说明: • 主机每帧(1ms)检查中断端点调度表。 • bInterval=3 表示每3帧轮询一次该端点,保证最大延迟 ≤ 3ms。 • 设备无数据时返回NAK,主机继续调度其他端点,不浪费带宽。

4.2 轮询间隔(bInterval)详解

bInterval是中断端点描述符中的关键字段,定义了主机轮询该端点的最大时间间隔。其编码方式与USB速度模式密切相关,理解bInterval的编码规则是正确配置中断端点的前提。

USB速度 bInterval 单位 bInterval 取值范围 实际轮询间隔 典型应用
低速 (1.5 Mbps) 帧 (1ms) 10 ~ 255 10ms ~ 255ms 鼠标、键盘
全速 (12 Mbps) 帧 (1ms) 1 ~ 255 1ms ~ 255ms HID设备、游戏手柄
高速 (480 Mbps) 微帧 (125µs) 1 ~ 16 (实际为 2^(bInterval-1)) 125µs ~ 4.096ms 高速HID、音频控制
超高速 (5 Gbps) 微帧 (125µs) 1 ~ 16 125µs ~ 4.096ms 实时控制、高速传感器
常见配置错误:在全速模式下,bInterval=0 是无效值,主机将拒绝该端点描述符。在高速模式下,bInterval 使用指数编码(2^(bInterval-1)),例如 bInterval=3 表示每 4 个微帧轮询一次(500µs),而非每 3 个微帧。务必查阅 USB 2.0 规范第 9.6.6 节。

在Gadget驱动中,bInterval通过端点描述符结构体中的 bInterval 字段设置。以下是一个全速中断端点的描述符定义示例:

/* 全速中断端点描述符 */
static struct usb_endpoint_descriptor fs_hid_interrupt_ep = {
    .bLength          = USB_DT_ENDPOINT_SIZE,
    .bDescriptorType  = USB_DT_ENDPOINT,
    .bEndpointAddress = USB_DIR_IN | 1,  /* EP1 IN */
    .bmAttributes     = USB_ENDPOINT_XFER_INT,
    .wMaxPacketSize   = cpu_to_le16(64),
    .bInterval        = 10,              /* 每10ms轮询一次 */
};

对于高速模式,bInterval的编码需要特别注意。高速中断端点的实际轮询间隔为 2^(bInterval-1) * 125µs。例如:

  • bInterval = 1 → 2^0 * 125µs = 125µs(最快轮询)
  • bInterval = 4 → 2^3 * 125µs = 1ms
  • bInterval = 16 → 2^15 * 125µs = 4.096ms(最慢轮询)

4.3 最大包大小限制

中断端点的最大包大小(wMaxPacketSize)受到USB速度和传输方向的严格限制。超出限制的配置将导致枚举失败或数据传输异常。

USB速度 中断端点最大包大小 备注
低速 ≤ 8 字节 仅支持控制传输和中断传输
全速 ≤ 64 字节 中断端点最大包大小与批量端点相同
高速 ≤ 1024 字节(高带宽模式可达 3072 字节) 高带宽模式需要额外的端点 companion 描述符
超高速 ≤ 1024 字节 支持突发传输(Burst),可连续发送多个包
Gadget驱动实践:在配置中断端点时,wMaxPacketSize 必须与端点硬件 FIFO 大小匹配。例如,在 dwc2 控制器上,如果 FIFO 深度为 64 字节,则 wMaxPacketSize 不能超过 64。超出硬件限制会导致数据截断或传输错误。

对于高速高带宽中断端点,需要额外配置 usb_endpoint_descriptor 的 wMaxPacketSize 高位部分,并添加 usb_ss_ep_comp_descriptor 描述符。以下是一个高速高带宽中断端点的配置示例:

/* 高速高带宽中断端点描述符 */
static struct usb_endpoint_descriptor hs_hid_interrupt_ep = {
    .bLength          = USB_DT_ENDPOINT_SIZE,
    .bDescriptorType  = USB_DT_ENDPOINT,
    .bEndpointAddress = USB_DIR_IN | 2,
    .bmAttributes     = USB_ENDPOINT_XFER_INT |
                        (2 << 4),  /* 高带宽模式:每微帧3个事务 */
    .wMaxPacketSize   = cpu_to_le16(1024),  /* 基本包大小 */
    .bInterval        = 1,                  /* 每微帧轮询 */
};

/* 高速端点 companion 描述符(USB 3.0 可选) */
static struct usb_ss_ep_comp_descriptor hs_ep_comp = {
    .bLength          = USB_DT_SS_EP_COMP_SIZE,
    .bDescriptorType  = USB_DT_SS_ENDPOINT_COMP,
    .bMaxBurst        = 0,      /* 突发传输次数 */
    .bmAttributes     = 0,
    .wBytesPerInterval = cpu_to_le16(3072),  /* 每微帧总字节数 */
};

4.4 中断端点在Gadget中的角色

在USB Gadget驱动框架中,中断端点承担着周期性状态上报和低延迟事件通知的核心职责。与批量端点不同,中断端点不保证数据吞吐量,但保证最大延迟。以下是中断端点在Gadget中的典型应用场景:

  • HID类设备:键盘、鼠标、游戏手柄通过中断端点上报按键状态、坐标移动等实时输入数据。
  • 通信设备类(CDC):调制解调器、虚拟串口使用中断端点传输线路状态、控制信号(如DCD、RI)。
  • 音频类设备:音频控制端点使用中断端点传输音量变化、静音状态等控制信息。
  • 自定义类设备:传感器数据采集、工业控制信号等需要周期性上报的场景。
中断端点在Gadget驱动中的角色架构 USB 主机 (Host) 轮询 Gadget 设备 Gadget 功能驱动 中断端点 (EP IN) UDC 控制器驱动 应用场景 HID 键盘/鼠标 CDC 控制信号 音频控制状态 传感器数据上报 Gadget中断端点数据流: 功能驱动 → usb_ep_queue() → UDC控制器 → USB总线 → 主机轮询读取 主机保证在 bInterval 时间内完成一次轮询,设备数据延迟可控。

4.5 Gadget驱动中中断端点的实现要点

在Linux Gadget驱动中,中断端点的实现涉及端点注册、请求队列管理和完成回调处理。以下是关键实现步骤和注意事项:

  1. 端点注册:在 bind() 回调中通过 usb_ep_autoconfig() 或手动指定端点地址注册中断端点。
  2. 请求分配:使用 usb_ep_alloc_request() 分配URB请求,并设置缓冲区。
  3. 请求提交:调用 usb_ep_queue() 将请求提交到中断端点,驱动需确保在完成回调中重新提交以维持周期性上报。
  4. 完成回调:在 complete() 回调中处理主机读取后的数据,并根据需要准备下一次上报数据。
  5. 错误处理:处理端点停止(-ESHUTDOWN)、请求取消(-ECONNRESET)等异常情况。

以下是一个典型的中断端点请求提交循环示例:

/* 中断端点请求完成回调 */
static void hid_ep_complete(struct usb_ep *ep, struct usb_request *req)
{
    struct hid_device *hid = req->context;
    int status = req->status;

    switch (status) {
    case 0: /* 成功传输 */
        /* 处理主机读取的数据(对于IN端点,数据已发送) */
        hid->report_id++;
        break;
    case -ESHUTDOWN: /* 端点被禁用 */
    case -ECONNRESET: /* 连接重置 */
        return; /* 停止重新提交 */
    default:
        pr_err("HID EP error: %d\n", status);
        break;
    }

    /* 准备下一次上报数据 */
    hid_prepare_report(hid, req->buf);
    req->length = hid->report_size;
    req->zero = 0;

    /* 重新提交请求,维持周期性上报 */
    status = usb_ep_queue(ep, req, GFP_ATOMIC);
    if (status)
        pr_err("Re-queue HID EP failed: %d\n", status);
}

/* 启动中断端点上报 */
static int hid_start_ep(struct hid_device *hid)
{
    struct usb_request *req;
    int ret;

    req = usb_ep_alloc_request(hid->ep, GFP_KERNEL);
    if (!req)
        return -ENOMEM;

    req->buf = kmalloc(hid->report_size, GFP_KERNEL);
    if (!req->buf) {
        usb_ep_free_request(hid->ep, req);
        return -ENOMEM;
    }

    req->length = hid->report_size;
    req->complete = hid_ep_complete;
    req->context = hid;
    req->zero = 0;

    /* 首次提交 */
    hid_prepare_report(hid, req->buf);
    ret = usb_ep_queue(hid->ep, req, GFP_KERNEL);
    if (ret) {
        kfree(req->buf);
        usb_ep_free_request(hid->ep, req);
    }

    return ret;
}
调试技巧:在中断端点调试中,可以通过 /sys/kernel/debug/usb/gadget/<gadget-name>/ep<num> 查看端点的状态、已传输字节数和错误计数。使用 usbmon 或 ftrace 可以捕获中断端点的URB提交和完成事件,帮助定位轮询间隔异常或数据丢失问题。

4.6 中断端点与批量端点的对比

在Gadget驱动设计中,选择中断端点还是批量端点取决于应用需求。下表总结了两种端点的关键差异:

特性 中断端点 批量端点
时序保证 有(bInterval保证最大延迟) 无(尽力而为)
带宽占用 固定预留(即使无数据也占用调度时隙) 动态分配(空闲时让出带宽)
最大包大小(全速) 64 字节 64 字节
最大包大小(高速) 1024 字节(高带宽模式 3072) 512 字节
典型延迟 1ms ~ 255ms(可配置) 不确定(取决于总线负载)
错误重试 硬件自动重试(直到成功或超时) 硬件自动重试(直到成功或超时)
典型应用 HID、控制信号、周期性状态 大块数据、文件传输、流媒体
选择建议:如果设备需要保证数据在固定时间窗口内被主机读取(如键盘按键响应),必须使用中断端点。如果设备传输大量数据且对延迟不敏感(如固件升级),应使用批量端点以节省USB总线带宽。