3. 端点描述符与配置:USB 批量传输的核心参数

在 QNX 环境下进行 USB 批量传输性能调优,首先必须透彻理解 USB 端点描述符(Endpoint Descriptor)的结构。端点描述符是 USB 设备固件向主机(Host)声明其传输能力的关键数据结构,它定义了端点的传输类型、方向、最大包长以及服务间隔。对于批量端点而言,bEndpointAddress、wMaxPacketSize 和 bInterval 三个字段直接决定了数据吞吐量、延迟特性和系统资源占用。

核心观点: 批量端点的性能瓶颈往往不是 USB 总线带宽,而是端点描述符参数与 QNX 内核 USB 栈(如 io-usb-otg、devu-uhci)之间的配置失配。错误的 wMaxPacketSize 或 bInterval 会导致频繁的短包中断、DMA 碎片化以及调度延迟。

3.1 端点描述符结构总览

USB 标准规定,每个非默认端点(端点 0 除外)都必须包含一个端点描述符。该描述符在设备配置(Configuration Descriptor)中紧随接口描述符(Interface Descriptor)之后。其标准结构如下表所示:

偏移 (字节) 字段名 长度 (字节) 说明
0 bLength 1 描述符长度,固定为 7 字节
1 bDescriptorType 1 描述符类型,端点描述符值为 0x05
2 bEndpointAddress 1 端点地址及方向(位 7 控制方向)
3 bmAttributes 1 传输类型(位 1:0 定义类型)
4 wMaxPacketSize 2 端点支持的最大数据包大小(字节)
6 bInterval 1 轮询间隔(单位:帧/微帧)

对于批量传输端点,bmAttributes 字段的低两位必须为 10b(批量传输)。QNX 的 USB 栈在枚举阶段会解析这些字段,并据此分配 URBs(USB Request Blocks)和 DMA 缓冲区。

USB 端点描述符结构(批量端点) bLength 0x07 bDescriptorType 0x05 bEndpointAddress 方向+端点号 bmAttributes 0x02 (批量) wMaxPacketSize 最大包长 bInterval 轮询间隔 方向位 (bit 7) 批量传输吞吐量关键 HS 下必须为 0 批量端点描述符中,bEndpointAddress、wMaxPacketSize、bInterval 是性能调优的三大核心字段

3.2 bEndpointAddress:方向与端点号解码

bEndpointAddress 是一个单字节字段,它同时编码了端点号(Endpoint Number)和传输方向(Direction)。其位定义如下:

  • 位 7(方向位): 0 表示 OUT 端点(主机到设备),1 表示 IN 端点(设备到主机)。
  • 位 6:4: 保留位,必须为 0。
  • 位 3:0(端点号): 取值范围 0x00~0x0F,共 16 个端点号。

例如,bEndpointAddress = 0x81 表示端点号 1 的 IN 端点;0x02 表示端点号 2 的 OUT 端点。在 QNX 驱动开发中,usb_device_entry 结构体中的 endpoint 字段直接存储此值,用于 URB 路由。

调优提示: 在 QNX 的 io-usb-otg 驱动中,批量 IN 端点(设备到主机)的 bEndpointAddress 方向位必须与硬件 FIFO 方向一致。如果固件错误地将 IN 端点声明为 OUT,QNX 主机侧将无法收到任何数据,且不会产生错误中断——数据会静默丢失。

3.3 wMaxPacketSize:批量传输的吞吐量天花板

wMaxPacketSize 是 2 字节字段,定义了端点在一个微帧(microframe)内能够发送或接收的最大数据字节数。对于批量端点,该字段的取值与 USB 速度直接相关:

USB 速度 wMaxPacketSize 有效值 理论最大吞吐量(每微帧) QNX 典型配置
Low-Speed (1.5 Mbps) 不支持批量传输 — —
Full-Speed (12 Mbps) 8, 16, 32, 64 64 字节 64 字节(推荐)
High-Speed (480 Mbps) 512 512 字节 512 字节(必须)
SuperSpeed (5 Gbps) 1024 1024 字节 1024 字节

在 High-Speed 模式下,wMaxPacketSize 必须为 512 字节。如果固件设置小于 512,QNX 主机栈会拒绝该配置,导致设备枚举失败。Full-Speed 模式下,建议使用 64 字节以获得最佳总线利用率。

wMaxPacketSize 对批量传输吞吐量的影响 FS: 64 字节 每帧 1 个事务 吞吐量: 0.768 MB/s HS: 512 字节 每微帧 1 个事务 吞吐量: 53.248 MB/s SS: 1024 字节 每微帧 1 个事务 吞吐量: 106.496 MB/s 批量传输吞吐量 ≈ wMaxPacketSize × 每微帧事务数 × 8000 微帧/秒 HS 下 wMaxPacketSize 必须为 512,否则枚举失败

3.4 bInterval:批量端点的轮询间隔

bInterval 字段在批量端点中的含义与等时或中断端点不同。对于批量传输,该字段在 USB 2.0 规范中定义为“最大 NAK 速率限制”,即端点可以连续返回 NAK 的最大微帧数。但在实际 QNX 驱动实现中,bInterval 的行为如下:

  • Full-Speed 批量端点: bInterval 表示主机轮询该端点的时间间隔,单位为帧(1 帧 = 1ms)。有效值范围 1~255。例如 bInterval = 1 表示每帧轮询一次。
  • High-Speed 批量端点: bInterval 必须为 0。HS 批量传输由主机控制器硬件自动调度,不需要软件轮询间隔。
  • SuperSpeed 批量端点: bInterval 同样必须为 0。
常见错误: 在 QNX 下调试 HS 批量设备时,如果固件将 bInterval 设置为非零值(如 1),io-usb-otg 驱动可能会进入错误处理路径,导致批量传输被降级为轮询模式,吞吐量骤降至 FS 水平。务必检查设备固件中的描述符定义。

3.5 批量端点配置的 QNX 实践检查清单

在 QNX 环境下验证端点描述符配置时,建议按以下步骤逐一核对:

  1. 使用 usb -v 命令 查看设备枚举日志,确认 bEndpointAddress 方向位与预期一致。
  2. 检查 wMaxPacketSize 是否匹配 USB 速度等级(HS 必须为 512)。
  3. 确认 bInterval 在 HS/SS 下为 0,FS 下根据应用延迟需求设置(通常为 1)。
  4. 验证 bmAttributes 低两位是否为 0x02(批量传输)。
  5. 在 QNX 驱动中打印 usb_endpoint_descriptor 结构体,确认内核解析后的值与固件声明一致。
批量端点配置检查流程(QNX 实践) usb -v 查看枚举 bEndpointAddress 检查 wMaxPacketSize HS:512 / FS:64 确认 bInterval HS:0 / FS:1 验证 bmAttributes 0x02 全部通过 → 批量传输配置正确 可进行下一步吞吐量基准测试 任一失败 → 修正固件描述符 重新枚举并验证

3.6 批量端点描述符的 QNX 内核解析示例

在 QNX 驱动代码中,端点描述符通常通过 usb_endpoint_descriptor 结构体访问。以下是一个典型的解析片段:

#include <usb/usb.h>

void parse_bulk_endpoint(struct usb_endpoint_descriptor *ep) {
    uint8_t  bEndpointAddress = ep->bEndpointAddress;
    uint16_t wMaxPacketSize   = ep->wMaxPacketSize;
    uint8_t  bInterval        = ep->bInterval;
    uint8_t  bmAttributes     = ep->bmAttributes;

    // 提取方向
    uint8_t direction = (bEndpointAddress & 0x80) >> 7;
    uint8_t ep_num    = bEndpointAddress & 0x0F;

    // 检查是否为批量端点
    if ((bmAttributes & 0x03) != USB_ENDPOINT_XFER_BULK) {
        printf("错误:非批量端点\n");
        return;
    }

    // 验证 wMaxPacketSize
    if (wMaxPacketSize != 512 && wMaxPacketSize != 64) {
        printf("警告:wMaxPacketSize 非标准值 %d\n", wMaxPacketSize);
    }

    // 验证 bInterval
    if (bInterval != 0) {
        printf("注意:HS 批量端点 bInterval 应为 0,当前为 %d\n", bInterval);
    }

    printf("批量端点: EP 0x%02x, 方向 %s, 最大包长 %d, 间隔 %d\n",
           ep_num,
           direction ? "IN" : "OUT",
           wMaxPacketSize,
           bInterval);
}
调试技巧: 在 QNX 的 io-usb-otg 驱动中,可以在 usb_device_add 函数入口处添加类似的打印,实时观察设备枚举时的端点描述符内容。这比事后抓取 USB 分析仪数据更快捷。

3.7 本章小结