4. 端点(Endpoint)概念详解:方向、类型与描述符
端点是 USB 通信中最基本的逻辑单元,也是主机与设备之间数据传输的终点。每个 USB 设备可以包含多个端点,每个端点都有唯一的方向、类型和传输特性。理解端点的完整概念是掌握 URB 编程与驱动开发的基础。
核心认知: 端点可以类比为“USB 设备内部的数据端口”。主机通过 URB 向端点发送或接收数据,端点则负责将数据传递给设备内部的特定功能单元(如 HID 报告、音频流、存储块等)。
4.1 端点方向:IN 与 OUT
端点的方向是相对于主机而言的,这是 USB 协议中最重要的约定之一。方向由端点描述符中的 bEndpointAddress 字段的最高位(bit 7)标识:
- USB_DIR_IN(0x80): 数据从设备传输到主机。主机通过 URB 读取设备数据。
- USB_DIR_OUT(0x00): 数据从主机传输到设备。主机通过 URB 向设备写入数据。
驱动开发注意: 在 Linux 内核中,方向宏定义位于
<linux/usb/ch9.h>。编写 URB 时,必须根据端点方向正确设置 urb->pipe,否则会导致通信失败或内核警告。
4.2 端点类型:四种传输模式
USB 规范定义了四种端点类型,每种类型对应不同的传输特性、带宽保证和错误处理机制。端点类型由端点描述符中的 bmAttributes 字段的低 2 位决定。
| 类型 | bmAttributes 值 | 传输方向 | 带宽保证 | 错误重试 | 典型应用 |
|---|---|---|---|---|---|
| 控制 (Control) | 0x00 | 双向(IN + OUT) | 有(固定 10%) | 硬件自动重试 | 设备枚举、配置、命令 |
| 批量 (Bulk) | 0x02 | 单向(IN 或 OUT) | 无(空闲时传输) | 硬件自动重试 | U盘、打印机、大文件 |
| 中断 (Interrupt) | 0x03 | 单向(IN 或 OUT) | 有(轮询保证) | 硬件自动重试 | 键盘、鼠标、HID 设备 |
| 等时 (Isochronous) | 0x01 | 单向(IN 或 OUT) | 有(固定带宽) | 无重试 | 音频、视频、实时流 |
关键区别: 等时端点是唯一不保证数据正确送达的类型。它没有 ACK/NAK 握手,也不进行硬件重试。驱动开发者必须自行处理数据丢失或损坏的情况,通常通过冗余校验或应用层重传机制。
4.3 端点描述符结构
端点描述符是 USB 描述符体系中的核心组成部分,它向主机报告端点的所有属性。在 Linux 内核中,端点描述符由结构体 struct usb_endpoint_descriptor 表示,定义在 <linux/usb/ch9.h> 中。
4.3.1 标准端点描述符字段
| 字段 | 偏移 | 大小 | 说明 |
|---|---|---|---|
bLength |
0 | 1 字节 | 描述符长度,固定为 7 字节 |
bDescriptorType |
1 | 1 字节 | 描述符类型,端点描述符固定为 0x05 |
bEndpointAddress |
2 | 1 字节 | 端点地址:bit 7 方向(0=OUT, 1=IN),bit 3-0 端点号 |
bmAttributes |
3 | 1 字节 | 端点属性:bit 1-0 传输类型(0=控制, 1=等时, 2=批量, 3=中断) |
wMaxPacketSize |
4 | 2 字节 | 端点支持的最大数据包大小(字节) |
bInterval |
6 | 1 字节 | 轮询间隔(中断/等时端点有效,单位:微帧或毫秒) |
内核代码示例: 驱动中获取端点描述符的典型方式是通过
struct usb_host_endpoint 结构体,它包装了 struct usb_endpoint_descriptor。以下代码展示如何解析端点地址和类型:
/* 从 usb_host_endpoint 中提取端点信息 */
struct usb_host_endpoint *ep = &interface->ep_desc[i];
struct usb_endpoint_descriptor *desc = &ep->desc;
/* 解析端点地址 */
unsigned int ep_num = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
unsigned int ep_dir = desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
/* 解析端点类型 */
unsigned int ep_type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
/* 获取最大包大小 */
unsigned int max_pkt = le16_to_cpu(desc->wMaxPacketSize);
/* 打印端点信息 */
printk(KERN_INFO "Endpoint: addr=0x%02x, num=%u, dir=%s, type=%s, max_pkt=%u\n",
desc->bEndpointAddress,
ep_num,
ep_dir ? "IN" : "OUT",
(ep_type == USB_ENDPOINT_XFER_CONTROL) ? "Control" :
(ep_type == USB_ENDPOINT_XFER_BULK) ? "Bulk" :
(ep_type == USB_ENDPOINT_XFER_INT) ? "Interrupt" : "Isochronous",
max_pkt);
4.4 端点 0 的特殊性
每个 USB 设备必须包含一个默认的控制端点——端点 0。它是双向的,同时支持 IN 和 OUT 传输,用于设备枚举、标准请求(如 Get Descriptor、Set Configuration)以及类特定命令。
- 唯一性: 端点 0 是设备上唯一一个在未配置状态下就可用的端点。
- 双向性: 与普通端点不同,端点 0 不需要区分 IN/OUT 地址,它使用同一个端点号处理双向通信。
- 驱动接口: Linux 内核提供
usb_control_msg()函数专门用于端点 0 的控制传输,驱动开发者无需手动构造 URB。
调试技巧: 使用
lsusb -v 命令可以查看设备所有端点的描述符信息。重点关注 bEndpointAddress、bmAttributes 和 wMaxPacketSize 字段,它们直接决定了驱动中 URB 的构造方式。
4.5 端点与 URB 的映射关系
在驱动开发中,每个 URB 必须绑定到一个具体的端点。URB 的 pipe 字段编码了端点号、方向和类型,内核通过 pipe 值找到对应的硬件端点进行数据传输。
| URB 字段 | 对应端点描述符字段 | 说明 |
|---|---|---|
urb->pipe |
bEndpointAddress + bmAttributes |
使用 usb_rcvbulkpipe()、usb_sndintpipe() 等宏构造 |
urb->transfer_buffer_length |
wMaxPacketSize |
单次传输不能超过最大包大小(批量/中断可多包) |
urb->interval |
bInterval |
中断/等时端点的轮询间隔,必须与描述符一致 |
urb->transfer_flags |
(间接) | 等时端点需设置 URB_ISO_ASAP 标志 |