3. 端点类型详解:控制端点(Endpoint 0)、批量端点、中断端点、等时端点特性对比

USB 端点是主机与设备之间数据传输的逻辑通道,每种端点类型对应不同的传输需求、带宽保证和错误处理机制。理解这四种端点的特性是驱动调试中定位传输错误的基础。

3.1 端点类型全景对比

下表从传输方向、数据包大小、带宽保证、错误重试、典型用途五个维度对四种端点进行横向对比:

特性维度 控制端点 (Endpoint 0) 批量端点 中断端点 等时端点
传输方向 双向(控制读/写) 单向(IN 或 OUT) 单向(IN 或 OUT) 单向(IN 或 OUT)
最大数据包大小 FS: 8B / HS: 64B / SS: 512B FS: 64B / HS: 512B / SS: 1024B FS: 64B / HS: 1024B / SS: 1024B FS: 1023B / HS: 1024B / SS: 1024B
带宽保证 10% 保留带宽(固定) 无保证(尽力而为) 有保证(轮询间隔内) 有保证(预留带宽)
错误重试 硬件自动重试(最多 3 次) 硬件自动重试(直到成功或超时) 硬件自动重试(直到成功或超时) 无重试(数据直接丢弃)
典型用途 枚举、描述符获取、命令控制 U 盘、打印机、大容量存储 键盘、鼠标、游戏手柄 摄像头、音频流、实时数据
调试要点: 在 URB 错误处理中,控制端点和批量端点的硬件重试机制会掩盖瞬时错误,而等时端点的“无重试”特性则要求驱动层必须主动处理数据丢失。

3.2 控制端点 (Endpoint 0) —— 设备的“控制面板”

控制端点是每个 USB 设备默认存在的双向端点,地址固定为 0x00。它使用控制传输协议,由 Setup 阶段、Data 阶段(可选)和 Status 阶段组成。

  • Setup 阶段: 主机发送 8 字节的 Setup 包,包含请求类型、请求码、值、索引和长度。
  • Data 阶段: 根据 Setup 包的方向,进行数据读写。数据包大小受端点 0 的 wMaxPacketSize 限制。
  • Status 阶段: 用于确认传输完成,方向与 Data 阶段相反。
驱动调试关键: 控制传输的 URB 必须正确设置 urb->setup_packet 和 urb->transfer_buffer。常见错误是 Setup 包长度与 Data 阶段不匹配,导致 Status 阶段失败。
// 控制端点 URB 初始化示例(Linux 内核)
struct urb *urb;
unsigned char *setup_packet;

urb = usb_alloc_urb(0, GFP_KERNEL);
setup_packet = kmalloc(8, GFP_KERNEL);

// 填充 Setup 包:读取设备描述符
setup_packet[0] = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE; // bmRequestType
setup_packet[1] = USB_REQ_GET_DESCRIPTOR;                            // bRequest
setup_packet[2] = USB_DT_DEVICE & 0xFF;                              // wValue low
setup_packet[3] = (USB_DT_DEVICE >> 8) & 0xFF;                      // wValue high
setup_packet[4] = 0x00;                                              // wIndex low
setup_packet[5] = 0x00;                                              // wIndex high
setup_packet[6] = 18;                                                // wLength low
setup_packet[7] = 0x00;                                              // wLength high

usb_fill_control_urb(urb, dev, usb_rcvctrlpipe(dev, 0),
                     setup_packet, buffer, 18,
                     control_callback, context);

3.3 批量端点 —— 大数据的“搬运工”

批量端点用于传输大量数据,但没有带宽保证。USB 主机控制器会在每个微帧中尽可能多地安排批量传输,但优先级低于中断和等时传输。

  • 错误处理: 硬件自动重试失败的批量传输,直到成功或超时。驱动层通常通过 URB 的 status 字段判断最终结果。
  • 流控制: 批量端点支持 NAK 握手,当设备忙时返回 NAK,主机控制器会在后续微帧重试。
  • 调试陷阱: 批量端点的重试机制可能导致 URB 完成回调延迟,驱动不应在回调中做耗时操作。
常见错误: 批量端点 URB 的 transfer_buffer_length 必须与端点描述符的 wMaxPacketSize 对齐。如果长度不是 wMaxPacketSize 的整数倍,最后一个短包可能导致设备端解析错误。

3.4 中断端点 —— 低延迟的“通知通道”

中断端点提供有保证的轮询间隔,适合传输少量但需要及时响应的数据。主机控制器会在每个微帧中轮询中断端点,轮询间隔由端点描述符的 bInterval 字段指定。

  • 轮询间隔: 全速设备最小 1ms,高速设备最小 125µs(微帧)。
  • 数据包大小: 全速最大 64 字节,高速最大 1024 字节。
  • 错误重试: 与批量端点相同,硬件自动重试直到成功或超时。
// 中断端点 URB 初始化示例
struct urb *urb;
int pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

urb = usb_alloc_urb(0, GFP_KERNEL);
usb_fill_int_urb(urb, dev, pipe, buffer, size,
                 interrupt_callback, context, interval);

// interval 参数来自端点描述符的 bInterval
// 高速设备:interval = 2^(bInterval-1) * 125µs
// 全速设备:interval = bInterval * 1ms
调试技巧: 中断端点的 URB 通常使用 URB_ISO_ASAP 标志(虽然名字带 ISO,但中断端点也可用),让主机控制器尽快安排传输。如果驱动需要固定轮询周期,应使用 usb_submit_urb 的返回值和完成回调来重新提交。

3.5 等时端点 —— 实时数据的“快车道”

等时端点是唯一不保证数据完整性的传输类型。它预留固定带宽,保证传输延迟,但数据出错时不重试。适用于音频、视频等对实时性要求高、对丢包容忍度高的场景。

  • 带宽预留: 在 USB 帧/微帧中预留固定数量的字节,其他传输类型不能占用。
  • 无重试: 数据错误或 CRC 校验失败时,数据直接丢弃,不触发重试。
  • URB 结构: 等时 URB 包含多个 iso_frame_desc 结构,每个描述一个帧/微帧的数据。
// 等时端点 URB 初始化示例(多帧传输)
struct urb *urb;
int number_of_packets = 8; // 每个 URB 包含 8 个帧

urb = usb_alloc_urb(number_of_packets, GFP_KERNEL);
urb->dev = dev;
urb->pipe = usb_rcvisocpipe(dev, endpoint->bEndpointAddress);
urb->transfer_buffer = buffer;
urb->transfer_buffer_length = buffer_size;
urb->complete = isoc_callback;
urb->context = context;
urb->start_frame = -1; // 由主机控制器分配
urb->number_of_packets = number_of_packets;
urb->interval = 1; // 每个微帧传输一次

for (i = 0; i < number_of_packets; i++) {
    urb->iso_frame_desc[i].offset = i * packet_size;
    urb->iso_frame_desc[i].length = packet_size;
}
调试陷阱: 等时 URB 的 iso_frame_desc[i].actual_length 在完成回调中可能小于 length,表示该帧数据丢失。驱动必须检查 status 和 actual_length,不能假设所有帧都成功。

3.6 端点类型选择与传输错误关联

下图展示了四种端点类型在 USB 传输层次中的位置,以及常见错误与端点特性的关联:

USB 端点类型与传输错误关联图 USB 主机控制器 (HCD) 控制端点 Endpoint 0 双向 · 重试 3 次 批量端点 Bulk IN/OUT 尽力而为 · 自动重试 中断端点 Interrupt IN/OUT 轮询保证 · 自动重试 等时端点 Isochronous 带宽预留 · 无重试 常见传输错误与端点类型关联 控制超时 Setup 包错误 NAK 风暴 数据包对齐错误 轮询间隔不匹配 数据丢失 帧数据丢失 带宽预留不足 驱动调试建议 检查 Setup 包长度 监控 NAK 计数 验证 bInterval 检查 actual_length

3.7 端点描述符解析与调试

在驱动初始化阶段,必须正确解析端点描述符。以下是从设备配置描述符中提取端点信息的典型流程:

  1. 获取配置描述符: 使用控制传输读取完整的配置描述符(包括所有接口和端点描述符)。
  2. 遍历接口描述符: 找到目标接口的 bInterfaceNumber 和 bAlternateSetting。
  3. 解析端点描述符: 遍历接口内的端点描述符,提取 bEndpointAddress、bmAttributes、wMaxPacketSize、bInterval。
  4. 验证端点类型: 根据 bmAttributes 的低 2 位判断类型(00=控制,01=等时,10=批量,11=中断)。
// 端点描述符解析示例
struct usb_endpoint_descriptor *ep = &iface_desc->endpoint[i].desc;

int ep_addr = ep->bEndpointAddress;
int ep_type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
int max_pkt = le16_to_cpu(ep->wMaxPacketSize) & 0x7FF;
int interval = ep->bInterval;

switch (ep_type) {
case USB_ENDPOINT_XFER_CONTROL:
    pr_info("Endpoint 0x%02x: Control, max packet %d\n", ep_addr, max_pkt);
    break;
case USB_ENDPOINT_XFER_BULK:
    pr_info("Endpoint 0x%02x: Bulk, max packet %d\n", ep_addr, max_pkt);
    break;
case USB_ENDPOINT_XFER_INT:
    pr_info("Endpoint 0x%02x: Interrupt, max packet %d, interval %d\n",
            ep_addr, max_pkt, interval);
    break;
case USB_ENDPOINT_XFER_ISOC:
    pr_info("Endpoint 0x%02x: Isochronous, max packet %d, interval %d\n",
            ep_addr, max_pkt, interval);
    break;
}
调试工具: 使用 lsusb -v 查看设备端点描述符,对比驱动解析结果。如果驱动报告的端点类型与 lsusb 输出不一致,通常是 bmAttributes 解析错误或接口选择错误。