2. USB核心数据结构:usb_device、usb_interface、usb_host_endpoint 结构体详解
在USB主机侧驱动开发中,理解内核如何描述USB设备是编写正确URB(USB Request Block)的前提。Linux USB核心使用三个层级分明的结构体来抽象物理设备:usb_device(设备级)、usb_interface(接口级)和usb_host_endpoint(端点级)。本章将逐一拆解这三个结构体的核心字段、层级关系以及它们在批量传输配置中的实际作用。
usb_interface 和 usb_host_endpoint,因为批量传输URB的提交直接依赖于端点描述符的解析。
2.1 结构体层级关系总览
在深入每个结构体之前,先通过一张层级关系图建立整体认知。USB设备在枚举完成后,内核会构建如下数据结构链:
2.2 usb_device:设备级抽象
usb_device 是USB子系统中最顶层的结构体,代表一个物理USB设备。它在设备插入时由USB核心分配,并在设备断开时释放。驱动开发中通常通过 interface_to_usbdev() 宏从接口指针获取设备指针。
| 字段名 | 类型 | 说明 |
|---|---|---|
devnum |
int | 设备地址(1~127),由USB核心在枚举时分配 |
speed |
enum usb_device_speed | 设备速度:USB_SPEED_LOW/FULL/HIGH/SUPER |
descriptor |
struct usb_device_descriptor | 设备描述符,包含VID、PID、bcdUSB等关键信息 |
config |
struct usb_host_config * | 当前激活的配置描述符指针 |
actconfig |
struct usb_host_config * | 与config相同,指向当前激活配置 |
bus |
struct usb_bus * | 所属USB总线(主机控制器) |
ep0 |
struct usb_host_endpoint | 默认控制端点(端点0),用于控制传输 |
state |
enum usb_device_state | 设备状态:ATTACHED/POWERED/DEFAULT/ADDRESS/CONFIGURED/SUSPENDED |
usb_device 的 speed 字段决定了批量传输的最大包长度(FS: 64字节,HS: 512字节,SS: 1024字节)。ep0 虽然不用于批量传输,但驱动常通过它发送类特定请求来初始化批量端点。
2.3 usb_interface:驱动绑定的核心单元
usb_interface 是USB驱动开发中最常接触的结构体。USB设备的一个接口对应一个逻辑功能(如键盘、鼠标、音频流),驱动通过 usb_driver 的 probe() 回调获得接口指针。批量传输端点通常属于某个接口的备用设置(alternate setting)。
| 字段名 | 类型 | 说明 |
|---|---|---|
altsetting |
struct usb_host_interface * | 接口的所有备用设置数组,altsetting[0]为默认设置 |
cur_altsetting |
int | 当前激活的备用设置索引 |
num_altsetting |
int | 备用设置的总数量 |
dev |
struct device | 内核通用设备结构,用于驱动模型绑定 |
usb_dev |
struct usb_device * | 指向所属的usb_device,通过interface_to_usbdev()获取 |
minor |
int | 接口的次设备号(用于USB设备节点) |
获取当前接口的端点信息,通常通过 interface->cur_altsetting->endpoint 数组访问。下面的代码片段展示了如何在probe函数中遍历批量端点:
static int my_usb_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *endpoint;
int i;
// 获取当前激活的备用设置
iface_desc = interface->cur_altsetting;
// 遍历该接口下的所有端点
for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
endpoint = &iface_desc->endpoint[i].desc;
// 检查是否为批量传输端点
if (usb_endpoint_is_bulk_in(endpoint)) {
dev_info(&interface->dev, "Found bulk IN endpoint\n");
// 保存端点地址和最大包长度
bulk_in_ep = usb_endpoint_num(endpoint);
bulk_in_maxp = usb_endpoint_maxp(endpoint);
}
if (usb_endpoint_is_bulk_out(endpoint)) {
dev_info(&interface->dev, "Found bulk OUT endpoint\n");
bulk_out_ep = usb_endpoint_num(endpoint);
bulk_out_maxp = usb_endpoint_maxp(endpoint);
}
}
return 0;
}
interface->altsetting[0] 来获取端点信息。如果设备当前激活的是备用设置1(如UVC摄像头的高带宽模式),altsetting[0] 可能不包含任何批量端点。始终使用 interface->cur_altsetting 来获取当前激活的接口描述符。
2.4 usb_host_endpoint:URB提交的目标
usb_host_endpoint 是USB端点在内核中的完整表示,包含了描述符信息、URB队列和调度状态。批量传输URB最终被提交到某个端点,由主机控制器调度执行。
| 字段名 | 类型 | 说明 |
|---|---|---|
desc |
struct usb_endpoint_descriptor | 端点描述符,包含bEndpointAddress、bmAttributes、wMaxPacketSize |
urb_list |
struct list_head | 挂载到该端点的URB链表,由USB核心管理 |
hcpriv |
void * | 主机控制器私有数据,驱动不应直接访问 |
ep_dev |
struct device | 端点对应的设备节点(sysfs接口) |
extra |
unsigned char * | 端点描述符的额外数据(如类特定扩展描述符) |
extralen |
int | 额外数据的长度 |
端点描述符 usb_endpoint_descriptor 是硬件层面的描述,其关键字段解析如下:
| 字段 | 位域/值 | 说明 |
|---|---|---|
bEndpointAddress |
Bit 0-3: 端点号 Bit 7: 方向(0=OUT, 1=IN) |
例如 0x81 表示端点1 IN;0x02 表示端点2 OUT |
bmAttributes |
Bit 0-1: 传输类型 00=控制, 01=等时, 10=批量, 11=中断 |
批量传输必须为 0x02 |
wMaxPacketSize |
Bit 0-10: 最大包大小 Bit 11-12: 额外事务数(HS等时) |
批量传输中FS=64, HS=512, SS=1024 |
bInterval |
1-255 | 批量传输通常忽略此字段,中断/等时传输使用 |
内核提供了便捷的宏来解析端点描述符,避免手动位操作:
// 判断端点类型和方向
usb_endpoint_is_bulk_in(epd) // 是否为批量IN端点
usb_endpoint_is_bulk_out(epd) // 是否为批量OUT端点
usb_endpoint_is_int_in(epd) // 是否为中断IN端点
usb_endpoint_xfer_bulk(epd) // 是否为批量传输(不分方向)
// 获取端点号和最大包长度
usb_endpoint_num(epd) // 返回端点号(0-15)
usb_endpoint_maxp(epd) // 返回wMaxPacketSize值
usb_endpoint_type(epd) // 返回传输类型枚举值
2.5 批量传输端点配置实战流程
结合上述三个结构体,配置批量传输端点的完整流程如下:
- 获取接口指针: 在驱动的
probe()回调中,参数struct usb_interface *interface即为当前接口。 - 获取当前备用设置: 通过
interface->cur_altsetting获取当前激活的接口描述符。 - 遍历端点数组: 从
cur_altsetting->endpoint数组(大小为desc.bNumEndpoints)中逐个检查。 - 识别批量端点: 使用
usb_endpoint_is_bulk_in/out()宏判断端点类型。 - 保存端点信息: 记录端点地址(用于URB的
pipe构造)和最大包长度(用于缓冲区对齐)。 - 构造URB并提交: 使用
usb_fill_bulk_urb()填充URB,指定目标端点pipe。
usb_rcvbulkpipe(udev, ep_num) 或 usb_sndbulkpipe(udev, ep_num),其中 udev 通过 interface_to_usbdev(interface) 获取。
下面的SVG图展示了从设备枚举到URB提交的完整数据流:
2.6 结构体关系总结
下表总结了三个核心结构体在批量传输配置中的角色分工:
| 结构体 | 批量传输中的角色 | 驱动开发者操作 |
|---|---|---|
usb_device |
提供设备速度、地址、默认控制端点 | 通过 interface_to_usbdev() 获取,用于构造pipe |
usb_interface |
提供当前备用设置和端点数组 | 在probe中遍历 cur_altsetting->endpoint[] |
usb_host_endpoint |
包含端点描述符和URB队列 | 解析 desc 字段获取地址和最大包长 |
bEndpointAddress 和 wMaxPacketSize 缓存到驱动私有结构体中。这样在后续URB提交时无需再次遍历端点数组,提升性能并减少代码耦合。