3. USB核心数据结构 - usb_device
在Linux USB主机驱动体系中,usb_device是描述一个USB设备实例的核心数据结构。它贯穿设备枚举、配置、通信和销毁的整个生命周期,是驱动开发者必须掌握的第一把钥匙。本章将深入剖析其结构体定义、关键字段含义以及生命周期管理。
3.1 结构体定义与关键字段解析
usb_device定义在include/linux/usb.h中,是USB子系统对物理或逻辑USB设备的抽象。下面列出其核心字段,并按功能分组说明。
| 字段名 | 类型 | 说明 |
|---|---|---|
devnum |
int |
设备地址(1~127),由主机在枚举阶段分配,用于后续所有事务的目标寻址。 |
speed |
enum usb_device_speed |
设备速率,取值包括USB_SPEED_LOW、USB_SPEED_FULL、USB_SPEED_HIGH、USB_SPEED_SUPER等。 |
bus |
struct usb_bus * |
指向所属USB总线(主机控制器)的指针,用于访问HCD层接口。 |
config |
struct usb_host_config * |
指向当前激活配置的描述符集合,包含接口、端点等信息。 |
ep0 |
struct usb_host_endpoint |
默认端点0的控制传输通道,用于枚举和标准请求。 |
state |
enum usb_device_state |
设备状态机,如USB_STATE_ATTACHED、USB_STATE_CONFIGURED、USB_STATE_SUSPENDED。 |
descriptor |
struct usb_device_descriptor |
设备描述符原始数据,包含idVendor、idProduct、bcdUSB等。 |
actconfig |
struct usb_host_config * |
与config相同,指向当前激活配置,用于快速访问。 |
rawdescriptors |
unsigned char * |
保存原始配置描述符数据的缓冲区,用于驱动解析。 |
children |
struct list_head |
子设备链表头,用于USB Hub挂载的下游设备管理。 |
parent |
struct usb_device * |
指向父设备(Hub或Root Hub)的指针,根设备为NULL。 |
udev->speed,而非通过描述符计算。该字段在枚举阶段由USB核心根据端口状态设置,比解析bcdUSB更可靠。
3.2 关键字段深度解读
3.2.1 devnum:设备地址的分配与复用
devnum是主机为每个设备分配的唯一地址,范围1~127。地址0保留给尚未分配地址的设备(默认状态)。枚举开始时,设备使用地址0与主机通信;主机通过SET_ADDRESS请求分配一个空闲地址后,设备切换到该地址进行后续传输。设备断开后,地址被回收,可重新分配给新插入的设备。
3.2.2 speed:速率与传输模式
速率字段直接影响端点调度和传输类型的选择。不同速率对应的典型应用场景如下:
- Low Speed (1.5 Mbps):人机交互设备(鼠标、键盘),仅支持控制传输和中断传输。
- Full Speed (12 Mbps):音频设备、基本HID设备,支持控制、中断、批量传输。
- High Speed (480 Mbps):存储设备、视频设备,支持所有四种传输类型,微帧调度。
- Super Speed (5 Gbps):USB 3.0设备,新增批量流传输(Streams)和双总线架构。
3.2.3 config与actconfig:配置管理
一个USB设备可以包含多个配置(Configuration),但同一时刻只能有一个配置处于激活状态。config和actconfig均指向当前激活的usb_host_config结构体。该结构体内部包含接口数组(struct usb_interface)和端点描述符,是驱动绑定和资源分配的依据。
3.2.4 ep0:默认控制端点
ep0是每个USB设备必须实现的端点,用于设备枚举和标准设备请求(如GET_DESCRIPTOR、SET_CONFIGURATION)。usb_host_endpoint结构体包含端点描述符、调度信息和URB队列。驱动开发者通常不直接操作ep0,而是通过usb_control_msg()等API间接使用。
usb_device并非直接包含所有端点信息,而是通过config → usb_interface → usb_host_endpoint的层级访问。这种设计体现了USB描述符的树形结构,也符合“配置-接口-端点”的层次模型。
3.3 生命周期:从插入到移除
usb_device的生命周期与USB设备的物理插拔和枚举过程紧密绑定。整个过程可以分为四个阶段:创建与枚举、配置与激活、正常运行、销毁与释放。
3.3.1 阶段一:创建与枚举
- Hub检测到端口状态变化:Hub驱动程序通过中断传输获知设备插入,调用
hub_port_connect_change()。 - 分配usb_device:USB核心调用
usb_alloc_dev()分配usb_device结构体,初始化devnum = 0、state = USB_STATE_ATTACHED。 - 复位与获取描述符:主机对端口复位,设备进入默认状态(
USB_STATE_POWERED→USB_STATE_DEFAULT)。主机通过端点0发送GET_DESCRIPTOR请求获取设备描述符。 - 分配地址:主机发送
SET_ADDRESS请求,分配非零地址,devnum更新,状态进入USB_STATE_ADDRESS。
3.3.2 阶段二:配置与激活
- 读取配置描述符:主机读取所有配置描述符、接口描述符和端点描述符,填充
rawdescriptors和config数组。 - 选择配置:USB核心根据驱动匹配结果或默认配置,调用
usb_set_configuration(),发送SET_CONFIGURATION请求。 - 状态迁移:设备状态变为
USB_STATE_CONFIGURED,config和actconfig指向激活配置,接口驱动开始绑定。
3.3.3 阶段三:正常运行
- 设备处于
USB_STATE_CONFIGURED状态,驱动通过URB与端点进行数据传输。 - 支持挂起/唤醒:
state可切换至USB_STATE_SUSPENDED,通过远程唤醒恢复。 - Hub子设备管理:如果设备是Hub,
children链表管理下游设备。
3.3.4 阶段四:销毁与释放
- 断开检测:Hub检测到端口状态变为断开,调用
hub_port_connect_change()处理断开事件。 - 通知驱动:USB核心调用
usb_disconnect(),遍历接口驱动,调用驱动的disconnect回调。 - 释放资源:释放配置描述符缓冲区、URB队列、端点资源,
devnum归还地址池。 - 释放usb_device:调用
usb_free_dev()释放结构体内存,state最终变为USB_STATE_NOTATTACHED。
disconnect回调中,驱动不应再访问usb_device的config或ep0字段,因为这些资源可能在回调返回后被核心释放。建议在disconnect开始时保存必要信息或取消所有待处理URB。
3.4 生命周期中的关键操作与代码示例
以下代码片段展示了在枚举过程中,USB核心如何初始化usb_device的关键字段:
// 简化自 drivers/usb/core/hub.c 中的 hub_port_init()
struct usb_device *usb_alloc_dev(struct usb_device *parent,
struct usb_bus *bus, unsigned port1)
{
struct usb_device *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return NULL;
dev->devnum = 0; // 初始地址为0
dev->speed = USB_SPEED_UNKNOWN;
dev->bus = bus;
dev->parent = parent;
dev->state = USB_STATE_ATTACHED;
INIT_LIST_HEAD(&dev->children);
// ep0 初始化
INIT_LIST_HEAD(&dev->ep0.urb_list);
dev->ep0.desc.bEndpointAddress = 0;
dev->ep0.desc.bmAttributes = USB_ENDPOINT_XFER_CONTROL;
dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
return dev;
}
// 枚举完成后设置地址
void usb_set_device_state(struct usb_device *dev,
enum usb_device_state new_state)
{
dev->state = new_state;
// 状态变更时可能触发电源管理或通知
}
// 配置激活后
int usb_set_configuration(struct usb_device *dev, int configuration)
{
// ... 发送 SET_CONFIGURATION 请求
dev->actconfig = dev->config;
dev->state = USB_STATE_CONFIGURED;
return 0;
}
usb_alloc_dev()只分配结构体,不分配描述符缓冲区;描述符在枚举过程中通过usb_get_descriptor()动态分配。devnum的分配由usb_alloc_devnum()管理,使用位图记录已分配地址,确保唯一性。- 设备断开时,
usb_disconnect()会递归断开所有子设备(Hub场景),确保children链表被正确清理。 - 驱动不应在
disconnect之后持有usb_device指针,除非通过usb_get_dev()/usb_put_dev()引用计数保护。