4. 端点描述符详解:端点地址、传输类型、方向、最大包大小与轮询间隔
端点描述符(Endpoint Descriptor)是 USB 设备枚举阶段由设备返回给主机的核心配置信息之一。它定义了主机与设备之间每一个数据通道的物理属性和传输契约。在驱动开发中,URB 的构造、提交以及数据流控制都直接依赖端点描述符中的字段。本节将逐一拆解端点描述符的五个关键参数:端点地址、传输类型、方向、最大包大小和轮询间隔。
核心要点:端点描述符是 URB 提交的“硬件契约”,驱动必须严格按描述符字段配置 URB,否则 USB 主机控制器将拒绝传输或产生 STALL 握手。
4.1 端点描述符结构总览
USB 规范定义的标准端点描述符长度为 7 字节,结构如下表所示:
| 偏移 (字节) | 字段 | 大小 (位) | 说明 |
|---|---|---|---|
| 0 | bLength | 8 | 描述符长度,固定为 7 |
| 1 | bDescriptorType | 8 | 描述符类型,端点描述符为 0x05 |
| 2 | bEndpointAddress | 8 | 端点地址(含方向) |
| 3 | bmAttributes | 8 | 传输类型(控制/批量/中断/等时) |
| 4 | wMaxPacketSize | 16 | 最大包大小(字节) |
| 6 | bInterval | 8 | 轮询间隔(毫秒或微帧) |
驱动视角:在 Linux 内核中,端点描述符由
struct usb_endpoint_descriptor 表示,定义在 <linux/usb/ch9.h>。驱动通过 usb_rcvbulkpipe()、usb_sndintpipe() 等宏来解析端点地址和方向。
4.2 端点地址与方向(IN/OUT)
bEndpointAddress 字节同时编码了端点编号和传输方向。其位布局如下:
- Bit 7:方向位。0 = OUT(主机→设备),1 = IN(设备→主机)。
- Bit 6-0:端点编号,范围 0~15(控制端点 0 固定为双向)。
例如:0x81 表示端点 1 IN,0x02 表示端点 2 OUT。驱动在构造 URB 时,必须使用正确的管道宏来匹配方向:
/* 构造 IN 端点管道 */
unsigned int pipe_in = usb_rcvbulkpipe(udev, 1); /* 端点 1 IN */
/* 构造 OUT 端点管道 */
unsigned int pipe_out = usb_sndbulkpipe(udev, 2); /* 端点 2 OUT */
常见错误:方向不匹配是 URB 提交失败的主要原因之一。如果设备端点描述符为 IN,驱动却使用 OUT 管道提交 URB,主机控制器将返回 -EPIPE 错误,设备端会返回 STALL 握手。
4.3 传输类型:控制、批量、中断、等时
bmAttributes 字段的低 2 位定义了端点的传输类型。四种类型在延迟、带宽、错误重试等方面有本质区别:
| bmAttributes[1:0] | 传输类型 | 典型应用 | 错误重试 | 带宽保证 |
|---|---|---|---|---|
| 00 | 控制 (Control) | 设备枚举、命令配置 | 硬件自动重试 | 10% 保留 |
| 01 | 等时 (Isochronous) | 音频、视频流 | 无重试 | 预分配 |
| 10 | 批量 (Bulk) | 存储、网络、数据采集 | 硬件自动重试 | 无保证 |
| 11 | 中断 (Interrupt) | 键盘、鼠标、HID 输入 | 硬件自动重试 | 按 bInterval 轮询 |
驱动选择依据:批量传输适合大数据量、非实时场景;中断传输适合低延迟、小数据量输入;等时传输适合实时流但容忍丢包;控制传输仅用于端点 0 的配置管理。
4.4 最大包大小(wMaxPacketSize)
wMaxPacketSize 是 16 位字段,定义了端点单次传输的最大数据载荷字节数。该值直接影响 URB 的缓冲区大小和传输效率。
- 全速/低速设备:最大包大小通常为 8、16、32 或 64 字节。
- 高速设备:批量端点最大包大小固定为 512 字节,中断端点可达 1024 字节,等时端点可达 1024 字节。
- 超速设备:批量端点最大包大小为 1024 字节。
驱动实践:在分配 URB 缓冲区时,应使用
usb_endpoint_maxp(&ep_desc) 宏获取 wMaxPacketSize,确保缓冲区大小不小于该值。对于批量传输,驱动通常将 URB 缓冲区设为最大包大小的整数倍。
/* 从端点描述符获取最大包大小 */
struct usb_endpoint_descriptor *ep = &iface_desc->endpoint[0].desc;
int max_packet = usb_endpoint_maxp(ep);
/* 分配 URB 缓冲区,按最大包大小对齐 */
urb->transfer_buffer = kmalloc(max_packet * 8, GFP_KERNEL);
urb->transfer_buffer_length = max_packet * 8;
4.5 轮询间隔(bInterval)
bInterval 字段仅对中断端点和等时端点有意义,它定义了主机轮询端点的时间间隔。不同速度模式下 bInterval 的编码方式不同:
| 设备速度 | bInterval 单位 | 有效范围 | 实际轮询间隔 |
|---|---|---|---|
| 低速/全速 | 毫秒 (ms) | 1~255 | bInterval × 1ms |
| 高速 | 微帧 (125μs) | 1~16 | 2^(bInterval-1) × 125μs |
| 超速 | 微帧 (125μs) | 1~16 | 2^(bInterval-1) × 125μs |
驱动影响:bInterval 决定了中断 URB 的提交频率。驱动在 URB 的 interval 字段中设置该值,主机控制器将按此间隔调度传输。设置过小会浪费总线带宽,设置过大会导致数据延迟。
4.6 驱动中解析端点描述符的完整流程
在 Linux 内核驱动中,解析端点描述符通常发生在探测函数(probe)中,流程如下:
- 从
struct usb_interface获取struct usb_host_interface。 - 遍历
desc.bNumEndpoints个端点。 - 对每个端点,读取
bEndpointAddress判断方向。 - 读取
bmAttributes判断传输类型。 - 读取
wMaxPacketSize确定缓冲区大小。 - 读取
bInterval设置 URB 轮询间隔。
/* 端点描述符解析示例 */
static int my_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_host_interface *iface_desc = intf->cur_altsetting;
struct usb_endpoint_descriptor *ep;
int i;
for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
ep = &iface_desc->endpoint[i].desc;
/* 解析端点地址和方向 */
int ep_num = usb_endpoint_num(ep);
int is_in = usb_endpoint_dir_in(ep);
/* 解析传输类型 */
int type = usb_endpoint_type(ep);
/* 解析最大包大小 */
int max_pkt = usb_endpoint_maxp(ep);
/* 解析轮询间隔 */
int interval = ep->bInterval;
dev_info(&intf->dev,
"EP%d %s: type=%d max_pkt=%d interval=%d\n",
ep_num, is_in ? "IN" : "OUT",
type, max_pkt, interval);
}
return 0;
}
调试提示:使用
lsusb -v 可以查看设备所有端点描述符的原始值。驱动开发时,建议先通过该命令确认端点参数,再与驱动代码中的解析结果比对,确保一致性。
4.7 端点描述符与 URB 提交的关联
URB 提交时必须严格匹配端点描述符的五个参数,否则 USB 核心层会拒绝提交。下表总结了 URB 字段与端点描述符的对应关系:
| 端点描述符字段 | URB 对应字段/宏 | 不匹配后果 |
|---|---|---|
| bEndpointAddress | usb_rcv*pipe() / usb_snd*pipe() | -EPIPE,设备 STALL |
| bmAttributes | urb->type (PIPE_* 宏) | URB 提交失败 -EINVAL |
| wMaxPacketSize | urb->transfer_buffer_length | 数据截断或溢出 |
| bInterval | urb->interval | 调度异常或带宽浪费 |