3、Linux USB子系统概述:USB core、HCD、UDC、Gadget框架架构

说到Linux USB子系统,我第一次接触时也被它的分层结构吓了一跳。你想想看,一个USB协议栈,从上到下分了四五层,每层还各自有回调函数、数据结构、注册接口。刚入门的人很容易迷失在 urb、usb_device、usb_gadget 这些结构体里。

其实没那么复杂。说白了,这套框架就是把USB主机侧和设备侧的管理逻辑分开,中间用一个抽象的USB core来统一调度。我这些年做USB驱动,踩过不少坑,慢慢才摸清楚每层该干什么、不该干什么。今天我就带你把这几层捋一遍。

3.1 整体架构:USB Core 的定位

先看一张我手绘的框架图,帮你快速建立全局印象。

Linux USB 子系统整体架构 应用程序 USB Core HCD-Host Controller Driver (主机控制器驱动) UDC (设备控制器驱动) USB 硬件控制器 (Host/Device) Gadget API (设备功能描述) 核心要点 USB Core 是桥梁,向下管理 HCD/UDC,向上提供统一接口 Gadget API 在 UDC 之上,用于实现设备端功能(如串口、存储)

从图上你能看到,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 几个核心的数据流

理解数据在子系统中的流转,比背结构体定义重要得多。常见的几个路径如下:

  1. 主机侧传输:应用→USB Core→HCD→硬件→HCD→USB Core→应用。
  2. 设备侧传输:主机→硬件→UDC→Gadget 驱动→Gadget API→用户空间(通过 FunctionFS 等)。
  3. 控制传输:设备枚举时的标准请求(如 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/。这个目录结构本身就能反映框架的分层思想。