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)以便管理传输状态。
struct usb_host_endpoint 与 struct usb_endpoint_descriptor 层级关系 struct usb_host_endpoint struct usb_endpoint_descriptor bLength (描述符长度) bDescriptorType (描述符类型) bEndpointAddress (端点地址 + 方向) bmAttributes (传输类型) wMaxPacketSize (最大包长) bInterval (轮询间隔) bRefresh (刷新率, 仅音频) bSynchAddress (同步端点地址) usb_host_endpoint 附加字段 urb_list (URB 等待队列) hcpriv (HCD 私有数据) ep_dev (端点设备文件) extra (额外描述符列表) enabled (端点使能标志) streams (USB 3.0 流 ID 表) toggle (数据同步位) desc (指向 usb_endpoint_descriptor) desc 说明:usb_host_endpoint 通过 desc 指针引用 usb_endpoint_descriptor,驱动通常通过 ep->desc 访问描述符字段

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 是默认控制端点,不在此描述。
常见错误: 驱动在构造 URB 时,如果 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 提交时的端点地址和状态码。
端点配置错误排查流程 URB 提交失败 检查 bEndpointAddress 方向位 检查 bmAttributes 传输类型 检查 wMaxPacketSize 与 bInterval 修正后重新提交 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())可以避免手动位操作错误。