2. Linux USB子系统架构

Linux USB子系统采用分层的模块化架构设计,从上到下依次为:设备驱动层、核心层(usbcore)、HCD层(Host Controller Driver),以及底层的USB主机控制器硬件。这种分层设计使得驱动开发人员只需关注自己所在的层级,通过标准化的API与相邻层交互,极大地提高了代码复用性和可移植性。

架构核心要点: Linux USB子系统的设计哲学是“分层抽象,接口统一”。usbcore作为枢纽,向上提供统一的设备驱动接口,向下定义HCD层的标准回调函数集。URB(USB Request Block)是贯穿各层的数据传输载体,而USBFIO(USB Framework I/O)则提供了更上层的I/O抽象。
Linux USB 子系统分层架构 设备驱动层 (Device Driver Layer) USB 鼠标、键盘、存储、网卡、摄像头等具体设备驱动 核心层 (usbcore) USB 总线管理、设备枚举、配置/接口/端点解析 URB 路由与分发、设备热插拔、电源管理 HCD 层 (Host Controller Driver) ehci-hcd / ohci-hcd / uhci-hcd / xhci-hcd USB 主机控制器硬件 EHCI / OHCI / UHCI / xHCI 控制器 URB 贯 穿 各 层 传 输 USBFIO I/O 抽象 层

2.1 核心层 (usbcore)

usbcore是USB子系统的中枢神经,负责所有与USB协议栈管理相关的核心任务。它不直接操作硬件,而是通过定义好的接口调用HCD层的回调函数来完成底层通信。usbcore的主要职责包括:

  • 总线管理: 维护USB总线拓扑结构,处理设备的热插拔事件(插入/拔出)。
  • 设备枚举: 为新插入的设备分配地址,读取设备描述符、配置描述符、接口描述符和端点描述符。
  • 资源分配: 管理USB带宽、传输类型(控制/批量/中断/等时)和端点地址。
  • 驱动匹配: 根据设备的VID/PID或类代码,将设备与对应的设备驱动进行绑定。
  • URB管理: 接收来自设备驱动的URB请求,将其路由到正确的HCD进行处理。
RTOS移植启示: 在RTOS中,通常没有独立的usbcore层。USB协议栈的核心功能(如枚举、描述符解析)往往被集成到HCD驱动中,或者作为一个轻量级的库存在。RTOS开发者需要自行实现设备驱动与HCD之间的调度和同步机制。

2.2 HCD层 (Host Controller Driver)

HCD层是软件与硬件之间的桥梁,直接操作USB主机控制器的寄存器。Linux内核针对不同的控制器规范实现了不同的HCD驱动:

控制器规范 Linux HCD驱动 速度支持 典型应用
UHCI (Universal Host Controller Interface) uhci-hcd USB 1.1 (低速/全速) Intel/VIA 芯片组,软件复杂度高
OHCI (Open Host Controller Interface) ohci-hcd USB 1.1 (低速/全速) 非Intel平台,硬件复杂度高
EHCI (Enhanced Host Controller Interface) ehci-hcd USB 2.0 (高速) USB 2.0 标准控制器
xHCI (eXtensible Host Controller Interface) xhci-hcd USB 3.0/3.1/3.2 (超高速/超高速+) 现代PC和嵌入式平台

HCD层需要实现usbcore定义的一组标准回调函数,主要包括:

  • urb_enqueue():将一个URB提交到硬件处理队列。
  • urb_dequeue():取消一个正在处理或等待的URB。
  • endpoint_disable():禁用某个端点。
  • get_frame_number():获取当前微帧/帧号。
  • hub_status_data():读取根集线器的状态变化。
HCD 层 URB 处理流程 usbcore 提交 URB urb_enqueue() 操作硬件描述符链表 DMA 传输完成中断 URB 完成回调 通知 usbcore 关键点:HCD 层负责将 URB 转换为硬件可执行的描述符链表,并处理 DMA 完成中断。 RTOS 中通常需要手动管理 DMA 缓冲区的 Cache 一致性。

2.3 设备驱动层

设备驱动层位于USB子系统的最上层,直接与用户空间或内核其他子系统交互。Linux内核为常见的USB设备类提供了通用的驱动框架,开发者只需实现少量回调即可完成驱动开发:

  • USB Mass Storage类: 使用usb-storage驱动,通过SCSI命令集访问U盘、移动硬盘。
  • HID类: 使用usbhid驱动,支持鼠标、键盘、游戏手柄等输入设备。
  • CDC类: 使用cdc_acm、cdc_ether等驱动,支持USB串口、USB网卡。
  • Video类: 使用uvcvideo驱动,支持USB摄像头。
  • Audio类: 使用snd-usb-audio驱动,支持USB声卡。

设备驱动的核心工作是填充struct usb_driver结构体,并实现probe()和disconnect()回调:

static struct usb_driver my_usb_driver = {
    .name       = "my_usb_device",
    .probe      = my_usb_probe,
    .disconnect = my_usb_disconnect,
    .id_table   = my_usb_id_table,
};

static int my_usb_probe(struct usb_interface *intf,
                        const struct usb_device_id *id)
{
    struct usb_device *dev = interface_to_usbdev(intf);
    struct usb_host_interface *iface_desc = intf->cur_altsetting;

    // 解析端点信息
    for (int i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
        struct usb_endpoint_descriptor *ep = &iface_desc->endpoint[i].desc;
        // 分配URB并提交
    }
    return 0;
}
RTOS移植差异: Linux的设备驱动层依赖于struct usb_driver框架和probe/disconnect热插拔模型。在RTOS中,通常没有自动的设备枚举和驱动匹配机制,开发者需要手动调用枚举函数,并在检测到设备插入后手动创建设备任务和端点管道。

2.4 USBFIO 机制

USBFIO (USB Framework I/O) 是Linux USB子系统提供的一个上层I/O抽象层,它封装了URB的创建、提交和回收过程,为设备驱动提供了更简洁的读写接口。USBFIO的核心思想是“发起I/O请求,等待完成”,屏蔽了URB的异步细节。

USBFIO提供的主要API包括:

API 函数 功能描述 同步/异步
usb_bulk_msg() 发送/接收批量传输数据 同步(阻塞等待)
usb_control_msg() 发送控制传输请求 同步(阻塞等待)
usb_interrupt_msg() 发送/接收中断传输数据 同步(阻塞等待)
usb_alloc_urb() 分配一个URB结构体 —
usb_submit_urb() 提交URB到usbcore 异步
usb_kill_urb() 强制终止一个URB —
USBFIO vs 原生URB: USBFIO的同步接口(如usb_bulk_msg)内部会创建一个URB、提交它,然后调用wait_for_completion()等待完成。这种方式简化了驱动开发,但牺牲了灵活性。对于需要高吞吐量或低延迟的场景(如USB摄像头、高速数据采集),驱动应直接使用URB异步接口。

2.5 URB 机制

URB (USB Request Block) 是Linux USB子系统中数据传输的核心载体,类似于网络子系统中的sk_buff。每个URB描述了一次完整的USB数据传输请求,包含目标设备、端点、传输方向、数据缓冲区、回调函数等关键信息。

URB的生命周期包含以下关键阶段:

  1. 分配: 驱动调用usb_alloc_urb()分配URB结构体。
  2. 填充: 设置URB的目标端点、传输类型、数据缓冲区指针、回调函数等。
  3. 提交: 调用usb_submit_urb()将URB提交给usbcore,usbcore将其路由到对应的HCD。
  4. 调度: HCD将URB转换为硬件描述符,放入主机控制器的处理链表。
  5. 执行: 主机控制器通过DMA或PIO方式完成数据传输。
  6. 完成: 硬件产生中断,HCD在中断处理中调用URB的回调函数,通知驱动传输完成。
  7. 回收: 驱动在回调中处理数据后,调用usb_free_urb()释放URB。
URB 生命周期状态机 空闲 (IDLE) usb_alloc_urb 已分配 (ALLOC) usb_submit_urb 已提交 (SUBMIT) HCD调度 处理中 (IN-PROG) DMA完成中断 完成 (COMPLETE) 回调函数 回调处理 (CALLBACK) usb_free_urb 释放URB 关键点:URB 的整个生命周期是异步的。驱动提交 URB 后立即返回,当硬件完成传输后通过中断触发回调。 RTOS 中通常使用信号量或消息队列来同步 URB 完成事件,而不是依赖 Linux 的 completion 机制。

URB结构体的核心字段如下:

struct urb {
    struct list_head urb_list;          // 链表节点
    struct usb_device *dev;             // 目标USB设备
    unsigned int pipe;                  // 管道信息(端点+方向+类型)
    int status;                         // 传输状态
    unsigned int transfer_flags;        // 传输标志
    void *transfer_buffer;              // 数据缓冲区
    dma_addr_t transfer_dma;            // DMA缓冲区地址
    int transfer_buffer_length;         // 缓冲区长度
    int actual_length;                  // 实际传输长度
    unsigned char *setup_packet;        // 控制传输的setup包
    usb_complete_t complete;            // 完成回调函数
    void *context;                      // 回调上下文
    int interval;                       // 中断/等时传输的轮询间隔
    int number_of_packets;              // 等时传输的包数量
    struct usb_iso_packet_descriptor iso_frame_desc[];
};
RTOS移植对比: Linux的URB机制依赖于内核的内存分配器(slab)、链表操作、等待队列和软中断。在RTOS中移植时,需要将complete回调替换为RTOS的消息队列或事件标志组,将urb_list替换为RTOS的双向链表实现,并将DMA缓冲区的管理适配到RTOS的内存管理框架中。

2.6 各层协作流程总结

当用户空间程序通过read()系统调用读取USB鼠标数据时,整个USB子系统的协作流程如下:

  1. 用户空间调用read() → 进入设备驱动层的usbhid驱动。
  2. 设备驱动层调用usb_submit_urb()提交一个中断传输URB → 进入usbcore。
  3. usbcore根据URB中的设备/端点信息,找到对应的HCD驱动 → 调用urb_enqueue()。
  4. HCD层将URB转换为硬件中断传输描述符(如EHCI的qTD),写入主机控制器的周期性链表 → 启动调度。
  5. 硬件在下一个微帧中执行中断传输,将鼠标数据通过DMA写入内存 → 产生中断。
  6. HCD层中断处理函数读取中断状态,找到完成的URB → 调用URB的complete回调。
  7. 设备驱动层回调函数处理数据(解析HID报告) → 唤醒等待的read()进程。
  8. 用户空间获得鼠标数据。
RTOS移植核心差异总结:
  • 分层简化: RTOS通常将usbcore和HCD合并,减少层间调用开销。
  • 同步机制: Linux使用wait_for_completion()和wake_up(),RTOS使用信号量/事件组。
  • 内存管理: Linux使用slab分配器,RTOS使用静态内存池或堆分配。
  • 中断处理: Linux将中断分为上半部和下半部(tasklet/workqueue),RTOS通常在ISR中直接发送消息给任务。
  • 驱动模型: Linux有自动的probe/disconnect热插拔机制,RTOS需要手动轮询或依赖GPIO中断检测设备插入。