3、Linux USB子系统概述:USB core、HCD、UDC、Gadget框架架构
说到Linux USB子系统,我第一次接触时也被它的分层结构吓了一跳。你想想看,一个USB协议栈,从上到下分了四五层,每层还各自有回调函数、数据结构、注册接口。刚入门的人很容易迷失在 urb、usb_device、usb_gadget 这些结构体里。
其实没那么复杂。说白了,这套框架就是把USB主机侧和设备侧的管理逻辑分开,中间用一个抽象的USB core来统一调度。我这些年做USB驱动,踩过不少坑,慢慢才摸清楚每层该干什么、不该干什么。今天我就带你把这几层捋一遍。
3.1 整体架构:USB Core 的定位
先看一张我手绘的框架图,帮你快速建立全局印象。
从图上你能看到,USB Core 处于中间层。它不直接操作硬件,而是通过注册回调函数来串联主机侧和设备侧。我在项目中见过有人觉得 USB Core 是个“黑盒”,其实它就是一套管理 URBs(USB Request Blocks)和枚举流程的框架代码。
3.2 主机侧:HCD 和 USB Core 的配合
HCD 的全称是 Host Controller Driver。它直接面对硬件寄存器。
- 职责:初始化主机控制器,处理中断,提交和完成 URBs。
- 常见类型:EHCI(USB 2.0)、XHCI(USB 3.0)、OHCI/UHCI(USB 1.1)。
- 注册方式:通过
usb_create_hcd()创建实例,调用usb_add_hcd()注册到 USB Core。
个人经验:我调试过一块 XHCI 控制器的驱动,卡在 URBs 提交后没有完成回调。查了两天才发现是 HCD 的 urb_enqueue 实现里忘记调用 usb_hcd_link_urb_to_ep()。你看,这些细节藏在 USB Core 的 API 契约里,不看文档根本想不到。
USB Core 对 HCD 的依赖很重。每个 USB 总线实例都对应一个 HCD。Core 负责将 URBs 分发给 HCD,HCD 负责把 URBs 转换成硬件事务。
小技巧:拿到一块新板子,不确定 HCD 是否工作正常,可以先看 /sys/kernel/debug/usb/devices。如果能看到设备树,说明 HCD 枚举成功了。
3.3 设备侧:UDC 和 Gadget 框架
设备侧和主机侧是对称的。UDC(USB Device Controller)驱动负责管理底层的设备控制器硬件。Gadget 框架则运行在 UDC 之上,提供了更高层次的抽象。
| 层级 | 职责 | 配置复杂度 |
|---|---|---|
| UDC 驱动 | 操作控制器寄存器,管理端点,处理中断 | 硬件相关,移植难度中 |
| Gadget API | 提供 usb_gadget、usb_ep 等抽象 |
与硬件无关,可重用 |
| Gadget 驱动 | 实现具体功能(如 serial、mass storage) | 功能逻辑,复用性高 |
你可能会问:为什么不直接用 UDC 驱动暴露的接口?我记得自己刚开始尝试写一个简单的串口 gadget,如果直接操作 UDC 的端点函数,代码量很大不说,还很脆弱。后来改用 Gadget API,很多东西都封装好了。
3.4 关键的抽象:usb_gadget 和 usb_ep
Gadget API 的核心是两个结构体:
struct usb_gadget:代表一个 USB 设备控制器。它包含速度、端点列表、操作函数集等。struct usb_ep:代表一个端点。支持usb_ep_queue()、usb_ep_enable()等操作。
写 gadget 驱动时,你不需要关心 UDC 底层寄存器怎么配。你只需要调用 usb_ep_queue() 来发送或接收数据。我最初写 gadget 驱动时,花了很大力气去读 UDC 的数据手册,后来才发现,其实 Gadget API 已经把这层屏蔽掉了。
避坑指南:我曾经在一个 OTG 项目里,把 gadget 驱动的 bind 回调写得太复杂,在里面做了太多初始化。结果系统启动时 USB 枚举超时,设备不能被识别。正确的做法是:bind 里只注册描述符和配置,真正的资源分配留给 setup 回调或 enable 阶段。
3.5 几个核心的数据流
理解数据在子系统中的流转,比背结构体定义重要得多。常见的几个路径如下:
- 主机侧传输:应用→USB Core→HCD→硬件→HCD→USB Core→应用。
- 设备侧传输:主机→硬件→UDC→Gadget 驱动→Gadget API→用户空间(通过 FunctionFS 等)。
- 控制传输:设备枚举时的标准请求(如 Get Descriptor)由 UDC 驱动直接响应,Gadget 框架通过
setup回调来处理。
在实际调试中,我会在关键路径上加上 dev_dbg() 和 trace_printk()。比如,URB 的提交和完成函数、Gadget 的 setup 回调,都是很好的插桩点。
3.6 小结一下:怎么入手?
如果你是第一次接触 USB 子系统,我建议按这个顺序学习:
- 先看 USB Core 的
hub.c和usb.c,了解枚举流程。 - 再看一个简单的 HCD 实现,比如
dummy_hcd.c,这是虚拟的,方便调试。 - 最后写一个最小的 gadget 驱动,比如 gadget zero,它提供了测试端点。
我个人的习惯:每次看一个新子系统,都先从它的 Kconfig 和 Makefile 开始。这样你就能知道哪些文件是核心,哪些是可选的。USB 子系统的核心文件集中在 drivers/usb/core/ 下,HCD 在 drivers/usb/host/,Gadget 在 drivers/usb/gadget/。这个目录结构本身就能反映框架的分层思想。