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 握手。
端点地址 bEndpointAddress 位布局 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0 方向位 端点编号 (0~15) 示例解析 bEndpointAddress = 0x81 → Bit7=1 (IN), 端点编号=1 → 端点1 IN bEndpointAddress = 0x02 → Bit7=0 (OUT), 端点编号=2 → 端点2 OUT

4.3 传输类型:控制、批量、中断、等时

bmAttributes 字段的低 2 位定义了端点的传输类型。四种类型在延迟、带宽、错误重试等方面有本质区别:

bmAttributes[1:0] 传输类型 典型应用 错误重试 带宽保证
00 控制 (Control) 设备枚举、命令配置 硬件自动重试 10% 保留
01 等时 (Isochronous) 音频、视频流 无重试 预分配
10 批量 (Bulk) 存储、网络、数据采集 硬件自动重试 无保证
11 中断 (Interrupt) 键盘、鼠标、HID 输入 硬件自动重试 按 bInterval 轮询
驱动选择依据:批量传输适合大数据量、非实时场景;中断传输适合低延迟、小数据量输入;等时传输适合实时流但容忍丢包;控制传输仅用于端点 0 的配置管理。
四种传输类型特性对比 控制传输 bmAttributes=00 双向 端点0专用 有重试 等时传输 bmAttributes=01 无重试 实时流 预分配带宽 批量传输 bmAttributes=10 有重试 大数据量 无带宽保证 中断传输 bmAttributes=11 有重试 低延迟 轮询驱动 驱动开发关键点 • 控制传输:仅用于端点0,驱动通过 usb_control_msg() 提交 • 批量传输:最常用,通过 usb_bulk_msg() 或 URB 提交,适合存储/网络 • 中断传输:HID 设备标配,通过 usb_interrupt_msg() 提交,bInterval 决定轮询频率 • 等时传输:无重试机制,驱动需自行处理丢包和同步

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 字段中设置该值,主机控制器将按此间隔调度传输。设置过小会浪费总线带宽,设置过大会导致数据延迟。
bInterval 轮询间隔计算示例(高速设备) 0 1 2 3 4 5 6 7 bInterval = 3 时的轮询行为 • 高速设备:轮询间隔 = 2^(bInterval-1) × 125μs = 2^2 × 125μs = 500μs • 每 4 个微帧(500μs)主机发起一次中断传输 • 驱动在 URB 中设置 urb->interval = 3,与描述符一致

4.6 驱动中解析端点描述符的完整流程

在 Linux 内核驱动中,解析端点描述符通常发生在探测函数(probe)中,流程如下:

  1. 从 struct usb_interface 获取 struct usb_host_interface。
  2. 遍历 desc.bNumEndpoints 个端点。
  3. 对每个端点,读取 bEndpointAddress 判断方向。
  4. 读取 bmAttributes 判断传输类型。
  5. 读取 wMaxPacketSize 确定缓冲区大小。
  6. 读取 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 调度异常或带宽浪费