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描述符的树形结构,也符合“配置-接口-端点”的层次模型。
usb_device 核心字段关系图 struct usb_device devnum (int) speed (enum) state (enum) bus * config / actconfig ep0 descriptor parent / children rawdescriptors 字段分组说明 绿色:设备标识与状态 | 紫色:总线与配置层级 | 橙色:描述符与拓扑关系

3.3 生命周期:从插入到移除

usb_device的生命周期与USB设备的物理插拔和枚举过程紧密绑定。整个过程可以分为四个阶段:创建与枚举、配置与激活、正常运行、销毁与释放。

3.3.1 阶段一:创建与枚举

  1. Hub检测到端口状态变化:Hub驱动程序通过中断传输获知设备插入,调用hub_port_connect_change()。
  2. 分配usb_device:USB核心调用usb_alloc_dev()分配usb_device结构体,初始化devnum = 0、state = USB_STATE_ATTACHED。
  3. 复位与获取描述符:主机对端口复位,设备进入默认状态(USB_STATE_POWERED → USB_STATE_DEFAULT)。主机通过端点0发送GET_DESCRIPTOR请求获取设备描述符。
  4. 分配地址:主机发送SET_ADDRESS请求,分配非零地址,devnum更新,状态进入USB_STATE_ADDRESS。

3.3.2 阶段二:配置与激活

  1. 读取配置描述符:主机读取所有配置描述符、接口描述符和端点描述符,填充rawdescriptors和config数组。
  2. 选择配置:USB核心根据驱动匹配结果或默认配置,调用usb_set_configuration(),发送SET_CONFIGURATION请求。
  3. 状态迁移:设备状态变为USB_STATE_CONFIGURED,config和actconfig指向激活配置,接口驱动开始绑定。

3.3.3 阶段三:正常运行

  • 设备处于USB_STATE_CONFIGURED状态,驱动通过URB与端点进行数据传输。
  • 支持挂起/唤醒:state可切换至USB_STATE_SUSPENDED,通过远程唤醒恢复。
  • Hub子设备管理:如果设备是Hub,children链表管理下游设备。

3.3.4 阶段四:销毁与释放

  1. 断开检测:Hub检测到端口状态变为断开,调用hub_port_connect_change()处理断开事件。
  2. 通知驱动:USB核心调用usb_disconnect(),遍历接口驱动,调用驱动的disconnect回调。
  3. 释放资源:释放配置描述符缓冲区、URB队列、端点资源,devnum归还地址池。
  4. 释放usb_device:调用usb_free_dev()释放结构体内存,state最终变为USB_STATE_NOTATTACHED。
注意事项:在disconnect回调中,驱动不应再访问usb_device的config或ep0字段,因为这些资源可能在回调返回后被核心释放。建议在disconnect开始时保存必要信息或取消所有待处理URB。
usb_device 生命周期状态机 ATTACHED POWERED DEFAULT ADDRESS CONFIGURED SUSPENDED NOTATTACHED Hub供电 复位 SET_ADDRESS SET_CONFIGURATION 挂起/唤醒 断开/释放 异常断开 生命周期关键事件 插入 → 枚举 → 配置 → 运行 → 断开 → 释放

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()引用计数保护。

3.5 小结