4. 端点配置数据结构:struct usb_host_endpoint 与 struct usb_endpoint_descriptor 解析
在 USB 驱动开发中,端点是主机与设备之间数据传输的逻辑通道。Linux 内核使用 struct usb_host_endpoint 和 struct usb_endpoint_descriptor 两个核心数据结构来描述和管理端点配置。理解这两个结构体的字段含义、层级关系以及它们在 URB 传输中的作用,是调试端点配置错误和 URB 传输失败的基础。
4.1 数据结构层级关系
struct usb_host_endpoint 是内核为每个端点维护的“宿主”结构,它包裹了从设备固件中解析出的标准描述符 struct usb_endpoint_descriptor,并附加了驱动运行时所需的元数据(如 URB 队列、定时器、流 ID 表等)。两者的关系如下图所示:
usb_endpoint_descriptor),主机端驱动将其包装为“宿主端点”(usb_host_endpoint)以便管理传输状态。
4.2 struct usb_endpoint_descriptor 字段详解
该结构体定义在 include/uapi/linux/usb/ch9.h 中,是 USB 规范中标准端点描述符的 C 语言映射。每个字段对应 USB 2.0/3.0 规范中端点描述符的字节偏移。
| 字段名 | 类型 | 字节偏移 | 说明 | 调试要点 |
|---|---|---|---|---|
bLength |
__u8 | 0 | 描述符长度,固定为 7(USB 2.0)或 9(USB 3.0 含额外字段) | 若值异常,说明设备固件描述符损坏 |
bDescriptorType |
__u8 | 1 | 描述符类型,端点描述符固定为 USB_DT_ENDPOINT (0x05) |
类型不匹配时内核会拒绝解析 |
bEndpointAddress |
__u8 | 2 | 高 4 位:端点号 (0~15);低 1 位:方向 (0=OUT, 1=IN) | 方向错误导致 URB 提交失败 (-EINVAL) |
bmAttributes |
__u8 | 3 | 低 2 位:传输类型 (0=控制, 1=等时, 2=批量, 3=中断) | 类型与 URB 管道标志必须一致 |
wMaxPacketSize |
__le16 | 4-5 | 端点支持的最大数据包字节数;USB 3.0 高 11 位表示突发数 | 超过此值提交 URB 会被截断或拒绝 |
bInterval |
__u8 | 6 | 轮询间隔:中断/等时端点使用,单位取决于设备速度 | 间隔过小导致 CPU 占用过高 |
bRefresh |
__u8 | 7 | 音频设备专用,刷新率 | 非音频设备通常为 0 |
bSynchAddress |
__u8 | 8 | 同步端点地址,仅 USB 3.0 等时端点使用 | USB 2.0 设备此字段不存在 |
lsusb -v 可以查看设备所有端点的描述符原始值。驱动中通过 ep->desc.wMaxPacketSize 读取最大包长时,注意使用 __le16_to_cpu() 进行字节序转换。
4.3 struct usb_host_endpoint 核心字段
该结构体定义在 include/linux/usb.h 中,是驱动与 USB 核心层交互的主要对象。驱动通过 struct usb_device 的 ep_in[] 和 ep_out[] 数组访问已配置的端点。
| 字段名 | 类型 | 说明 | 驱动常用操作 |
|---|---|---|---|
desc |
struct usb_endpoint_descriptor | 嵌入的标准端点描述符,驱动通过 ep->desc 直接访问 |
读取 ep->desc.bEndpointAddress 判断方向 |
urb_list |
struct list_head | 挂载到该端点的 URB 等待队列头 | 调试时遍历队列检查未完成 URB |
hcpriv |
void * | 主机控制器驱动私有数据,指向 HCD 内部端点状态 | 通常不直接操作,HCD 内部使用 |
ep_dev |
struct usb_ep_device * | 端点设备文件,用于 /sys/bus/usb/devices/... 导出 |
调试时通过 sysfs 查看端点状态 |
extra |
struct list_head | 额外的描述符链表(如音频类特定描述符) | 解析类特定描述符时遍历此链表 |
enabled |
unsigned | 端点是否已使能,0=禁用,1=使能 | 提交 URB 前检查此标志 |
streams |
unsigned | USB 3.0 流能力,表示支持的流 ID 数量 | 批量流端点专用,非流端点此值为 0 |
4.4 端点地址与方向解码
bEndpointAddress 字段是调试中最常解析的字段。其位布局如下:
- Bit 7:方向位。0 = OUT(主机到设备),1 = IN(设备到主机)。
- Bits 6-4:保留,必须为 0。
- Bits 3-0:端点号,范围 0~15。端点 0 是默认控制端点,不在此描述。
usb_rcvbulkpipe() 与 usb_sndbulkpipe() 使用错误,会导致 URB 提交失败并返回 -EINVAL。务必使用 usb_rcvxxxpipe(dev, ep->desc.bEndpointAddress & 0x0F) 匹配方向。
4.5 传输类型与 bmAttributes 解码
bmAttributes 的低 2 位决定端点传输类型,驱动必须使用对应的 URB 类型提交:
| bmAttributes 低 2 位 | 传输类型 | 对应 URB 类型 | 典型应用 |
|---|---|---|---|
| 00 | 控制 (Control) | usb_submit_urb() 配合控制管道 |
枚举、标准请求 |
| 01 | 等时 (Isochronous) | URB_ISO_ASAP 标志 |
音频、视频流 |
| 10 | 批量 (Bulk) | usb_bulk_msg() 或批量 URB |
存储、网络、打印机 |
| 11 | 中断 (Interrupt) | usb_interrupt_msg() 或中断 URB |
键盘、鼠标、HID 设备 |
4.6 驱动中获取端点描述符的典型代码
以下代码片段展示了在 USB 驱动探测函数中如何遍历接口的端点并读取配置信息:
static int my_driver_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct usb_host_interface *iface_desc = intf->cur_altsetting;
struct usb_endpoint_descriptor *ep_desc;
int i;
for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
ep_desc = &iface_desc->endpoint[i].desc;
/* 检查端点方向 */
if (usb_endpoint_dir_in(ep_desc)) {
dev_info(&intf->dev, "端点 %02x: IN\n",
ep_desc->bEndpointAddress);
} else {
dev_info(&intf->dev, "端点 %02x: OUT\n",
ep_desc->bEndpointAddress);
}
/* 检查传输类型 */
if (usb_endpoint_xfer_bulk(ep_desc)) {
dev_info(&intf->dev, " 类型: 批量, 最大包长: %d\n",
__le16_to_cpu(ep_desc->wMaxPacketSize));
} else if (usb_endpoint_xfer_int(ep_desc)) {
dev_info(&intf->dev, " 类型: 中断, 间隔: %d ms\n",
ep_desc->bInterval);
}
/* 保存端点用于后续 URB 提交 */
if (usb_endpoint_dir_in(ep_desc) &&
usb_endpoint_xfer_bulk(ep_desc)) {
udev->ep_in[usb_endpoint_num(ep_desc)] =
&iface_desc->endpoint[i];
}
}
return 0;
}
ep_desc->wMaxPacketSize 的原始值,必须通过 __le16_to_cpu() 转换。在 USB 3.0 中,wMaxPacketSize 的高 11 位表示突发数,低 11 位才是包大小,解析时需使用 usb_endpoint_maxp(ep_desc) 辅助宏。
4.7 常见端点配置错误与调试方法
- 端点号冲突: 同一接口下两个端点使用相同端点号但方向不同是合法的(如批量 IN 和批量 OUT 共用端点号 1),但相同方向相同端点号重复定义会导致内核警告。
- wMaxPacketSize 超出限制: USB 2.0 批量端点最大包长 512 字节,高速中断端点最大 1024 字节,等时端点最大 1024 字节。超出限制时 URB 提交会返回
-ENOMEM。 - bInterval 值异常: 低速/全速中断端点 bInterval 单位为毫秒,高速/超速中断端点单位为 125µs 的倍数。错误设置会导致中断 URB 无法按时完成。
- 描述符长度不匹配: USB 3.0 端点描述符长度为 9 字节(含 bRefresh 和 bSynchAddress),USB 2.0 为 7 字节。驱动应使用
ep_desc->bLength判断版本。
cat /sys/kernel/debug/usb/devices 可以查看内核中所有 USB 设备的端点配置树。使用 usbmon 可以捕获 URB 提交时的端点地址和状态码。
4.8 小结
struct usb_host_endpoint 和 struct usb_endpoint_descriptor 是 USB 驱动开发中必须掌握的基础数据结构。驱动通过 ep->desc 访问描述符字段,通过 ep->urb_list 管理 URB 队列。调试端点配置错误时,应优先检查 bEndpointAddress 的方向位、bmAttributes 的传输类型以及 wMaxPacketSize 是否与 URB 管道匹配。使用内核提供的辅助宏(如 usb_endpoint_dir_in()、usb_endpoint_xfer_bulk())可以避免手动位操作错误。