4. 中断端点特性详解:中断传输的时序保证、轮询间隔(bInterval)、最大包大小限制、中断端点在Gadget中的角色
中断端点(Interrupt Endpoint)是USB通信中用于传输低延迟、有限大小数据的端点类型。与批量端点(Bulk)尽力而为的传输模式不同,中断端点为USB主机提供了明确的轮询保证,确保设备能在可预测的时间窗口内被主机访问。本章将深入剖析中断端点的核心特性,包括时序保证机制、bInterval轮询间隔、最大包大小限制,以及中断端点在Gadget驱动中的具体角色与实现要点。
4.1 中断传输的时序保证机制
中断传输的时序保证是USB协议为实时性需求提供的核心能力。主机控制器(Host Controller)在每帧(Frame)或微帧(Microframe)中为中断端点预留了固定的轮询时隙,确保设备的数据能在规定延迟内被主机读取。
- 全速/低速模式:每帧(1ms)轮询一次,bInterval表示帧数。
- 高速模式:每微帧(125µs)轮询一次,bInterval表示微帧数。
- 超高速模式:每微帧(125µs)轮询,bInterval表示微帧数,但支持更大的包大小。
中断传输的时序保证并非“实时中断”,而是带宽预留机制。主机在枚举阶段根据端点描述符中的bInterval值,在USB帧调度表中为该端点分配固定的轮询时间片。如果设备没有数据发送,主机仍会发起IN令牌包,设备返回NAK握手;如果设备有数据,则返回DATA包。
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 | 实时控制、高速传感器 |
在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),可连续发送多个包 |
对于高速高带宽中断端点,需要额外配置 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)。
- 音频类设备:音频控制端点使用中断端点传输音量变化、静音状态等控制信息。
- 自定义类设备:传感器数据采集、工业控制信号等需要周期性上报的场景。
4.5 Gadget驱动中中断端点的实现要点
在Linux Gadget驱动中,中断端点的实现涉及端点注册、请求队列管理和完成回调处理。以下是关键实现步骤和注意事项:
- 端点注册:在
bind()回调中通过usb_ep_autoconfig()或手动指定端点地址注册中断端点。 - 请求分配:使用
usb_ep_alloc_request()分配URB请求,并设置缓冲区。 - 请求提交:调用
usb_ep_queue()将请求提交到中断端点,驱动需确保在完成回调中重新提交以维持周期性上报。 - 完成回调:在
complete()回调中处理主机读取后的数据,并根据需要准备下一次上报数据。 - 错误处理:处理端点停止(
-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、控制信号、周期性状态 | 大块数据、文件传输、流媒体 |