第2章 Linux USB子系统框架

做USB gadget驱动开发,第一件事就是要搞明白Linux里USB的软件体系是怎么搭起来的。

说白了,这就像你要装修一栋楼,得先知道承重墙在哪、水电管线怎么走的。否则你糊里糊涂就开始敲墙,最后可能整栋楼都塌了。

Linux USB子系统分三层:USB核心、主机控制器驱动、设备驱动。这三层各司其职,又互相配合。我习惯把核心层看作“交通枢纽”,主机控制器是“高速公路”,设备驱动则是“收费站”。你想想看,没有枢纽,车流就乱了;没有路,车没法跑;没有收费站,车辆就没法有序上下高速。

USB核心层 - 粘合剂与交通枢纽

USB核心层是Linux内核管理所有USB设备的中枢。它负责三大块:

  • 设备枚举:当USB设备插上时,核心层负责读取设备的描述符,分配地址,加载合适的驱动程序。
  • 数据传输管理:它定义了URB(USB Request Block)这个数据结构,所有USB数据传输都通过URB完成。
  • 驱动注册与匹配:为USB设备驱动和USB主机控制器驱动提供统一的注册接口。

我个人认为,理解USB核心层的关键,就是搞懂它如何把硬件细节藏起来,给上层提供一个干净的接口。

核心数据结构: usb_device 代表一个物理设备,usb_interface 代表设备的一个功能接口,usb_driver 代表一个驱动程序。这三个结构体就是你写驱动时打交道最多的对象。

主机控制器层 - 硬件层面的总指挥

主机控制器驱动,就是直接和USB硬件寄存器打交道的代码。

Linux支持多种主机控制器规范:

协议标准速度常见硬件
OHCIUSB 1.1 全速/低速早期嵌入式芯片,如S3C2440
UHCIUSB 1.1 全速/低速Intel早期主板
EHCIUSB 2.0 高速大多数PC和SoC
XHCIUSB 3.0/3.1 超高速现代PC和高端ARM芯片

我记得早期内核里,这四种控制器驱动是完全独立的代码。后来社区引入了HCD(Host Controller Driver)抽象层,把共性抽出来。嗯,这里要注意:

在Gadget驱动开发中,我们一般不用关注主机控制器细节。但当你做Gadget+Host双角色切换时,一定要确认硬件是否支持OTG,以及对应的驱动是否加载正确。我曾经在一个项目里遇到设备插入后枚举失败,排查了半天,最后发现是EHCI驱动没编进内核。

设备驱动层 - 让硬件干活的人

这一层就是我们自己写驱动的地方。

Linux USB设备驱动分为两类:

  • USB Gadget驱动:让设备模拟成U盘、网卡、串口等。这是本课程的重点。
  • USB Host侧驱动:驱动连接到主机的USB设备,比如键盘、鼠标、存储设备。

对于Gadget驱动,核心接口是 struct usb_gadget_driver。你只需要填充回调函数,然后注册到内核。

static struct usb_gadget_driver my_gadget_driver = {
    .function =  my_gadget_bind,
    .unbind =    my_gadget_unbind,
    .setup =     my_gadget_setup,
    .disconnect = my_gadget_disconnect,
    .driver = {
        .name = "my_gadget",
        .owner = THIS_MODULE,
    },
};

说白了,核心层帮你把URB的收发、端点管理等脏活累活都干了。你只需要关注怎么响应主机的请求。

个人习惯:写Gadget驱动时,先仔细看usb_gadget_driver结构体的注释。里面每个回调在什么时机调用,文档里写得很清楚。我刚开始做时,就是没注意.setup回调的参数含义,导致控制传输解析一直出错。

三层架构如何协同工作

数据流是这样的:

  1. 主机发送一个USB请求(比如读U盘的一个扇区)。
  2. 主机控制器层把这个请求组装成USB事务,通过总线发送出去。
  3. 在Gadget端,硬件收到数据后触发中断。
  4. USB核心层(Gadget侧)解析中断,调用你注册的.setup回调。
  5. 你在回调里处理请求,构造响应数据,通过usb_ep_queue把数据发回去。

每个层次只负责自己那一块。核心层不关心你用的是EHCI还是XHCI,驱动层不关心数据是怎么在总线上传输的。

为了让你更直观地理解这三层的关系,我画了一张框架图:

Linux USB 子系统三层架构 用户空间 (用户态) 内核空间 (内核态) USB设备驱动层 (Gadget驱动 / Host侧驱动) USB核心层 (设备枚举 / URB管理 / 驱动匹配) 主机控制器层 (OHCI / UHCI / EHCI / XHCI) 硬件层 (USB控制器) 数据流从上到下,控制流从下到上。每一层只与相邻层通信。

这张图我画得比较简洁。你仔细看,用户空间和内核空间之间有明显的界限。设备驱动调用核心层的API,核心层再调用主机控制器层的接口。反过来,数据从硬件上来时,也是逐层回调。

一个小技巧:调试Gadget驱动时,可以在USB核心层的drivers/usb/gadget/目录下加打印。我遇到疑难杂症时,经常在udc-core.c的usb_gadget_udc_start函数里加dev_dbg,看驱动注册和启动的完整流程。

一个常见的误解

很多人以为写Gadget驱动就是直接操作硬件寄存器。其实不是。

你写的驱动跑在设备驱动层,调用的是USB核心提供的API,比如usb_ep_enable、usb_ep_queue。真正的硬件操作,是由主机控制器层(在Gadget端通常叫UDC驱动)完成的。

举个例子,你要发送一个数据包:

  1. 你的驱动调用usb_ep_queue,把URB交给核心层。
  2. 核心层把请求转发给UDC驱动。
  3. UDC驱动把数据写入硬件FIFO,启动DMA传输。

所以,你写的驱动实际上是在和核心层对话,而不是直接和硬件寄存器打交道。这个思路捋顺了,后面学起来会轻松很多。