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 传输层次中的位置,以及常见错误与端点特性的关联:
3.7 端点描述符解析与调试
在驱动初始化阶段,必须正确解析端点描述符。以下是从设备配置描述符中提取端点信息的典型流程:
- 获取配置描述符: 使用控制传输读取完整的配置描述符(包括所有接口和端点描述符)。
- 遍历接口描述符: 找到目标接口的
bInterfaceNumber和bAlternateSetting。 - 解析端点描述符: 遍历接口内的端点描述符,提取
bEndpointAddress、bmAttributes、wMaxPacketSize、bInterval。 - 验证端点类型: 根据
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 解析错误或接口选择错误。