2. Linux USB子系统架构
在嵌入式 Linux 系统中,USB 子系统是一个高度模块化的分层架构。理解 USB Core、HCD(Host Controller Driver)、Gadget 驱动栈以及 UDC(USB Device Controller)驱动之间的关系,是编写 USB Gadget 功能驱动(如虚拟网卡、虚拟串口)的基础。本章将拆解这一架构,并分析数据在其中的流动路径。
2.1 整体架构分层
Linux USB 子系统从物理连接上分为主机侧(Host)和设备侧(Device/Gadget)。在嵌入式开发中,一个 SoC 通常同时具备 Host 和 Device 两种角色,但 Gadget 驱动栈专用于设备侧。下图展示了核心分层关系:
2.2 核心组件职责
下表总结了各层组件的核心职责与典型实现:
| 组件 | 所属角色 | 核心职责 | 典型实现/文件 |
|---|---|---|---|
| USB Core | 主机侧 | 设备枚举、配置解析、URB 管理、驱动匹配 | drivers/usb/core/ |
| HCD | 主机侧 | 与 USB 主机控制器硬件交互,处理传输描述符、中断、DMA | ehci-hcd, xhci-hcd |
| Gadget Core | 设备侧 | 提供 Gadget API、管理配置/接口/端点、处理 ep0 请求 | drivers/usb/gadget/udc/core.c |
| UDC Driver | 设备侧 | 与 USB 设备控制器硬件交互,实现端点操作、中断处理、DMA | dwc2-udc, chipidea-udc |
| Function Driver | 设备侧 | 实现具体 USB 功能(如 ECM、ACM、Mass Storage) | g_ether, g_serial, g_mass_storage |
关键概念:Gadget Core 与 UDC Driver 的分界点在于“硬件无关性”。Gadget Core 提供统一的 API 给 Function Driver,而 UDC Driver 负责与具体 SoC 的 USB 设备控制器寄存器打交道。这种分层使得 Function Driver 可以跨平台复用。
2.3 数据流路径分析
以虚拟网卡(Ethernet Gadget)为例,数据从主机侧发送到设备侧,再被设备侧网络栈接收的完整路径如下:
- 主机侧应用发送网络数据包到 USB 主机控制器。
- HCD 将数据封装为 URB,通过 DMA 写入 USB 总线。
- UDC Driver 从硬件 FIFO 读取数据,触发中断。
- Gadget Core 将数据递交给对应的 Function Driver(如
u_ether)。 - Function Driver 将 USB 数据包转换为网络数据包,送入设备侧网络协议栈。
- 设备侧应用(如
iperf、ssh)从网络接口读取数据。
2.4 Gadget 驱动栈内部结构
Gadget 驱动栈是设备侧的核心,其内部进一步细分为三个层次:
- UDC Core (Gadget Core):提供
usb_gadget、usb_ep、usb_request等抽象结构体,管理端点 0 的控制传输。 - UDC Driver:实现
usb_gadget_ops回调函数(如ep_enable、ep_queue、pullup),直接操作硬件寄存器。 - Composite Framework / Function Driver:组合多个 USB 功能(如同时提供网卡和串口),或单一功能驱动。
调试要点:当 Gadget 功能异常时,首先检查
/sys/kernel/debug/usb/gadget/ 下的调试信息,确认 UDC 是否绑定成功、端点是否已使能。使用 lsusb -t 在主机侧查看设备描述符是否被正确识别。
2.5 关键数据结构关系
在 Gadget 驱动栈中,以下四个数据结构构成了核心对象模型:
| 数据结构 | 所属层 | 作用 | 关键字段 |
|---|---|---|---|
struct usb_gadget |
Gadget Core | 代表一个 USB 设备控制器实例 | ops, ep0, speed, max_speed |
struct usb_ep |
Gadget Core | 代表一个端点,用于数据传输 | ops, maxpacket, name |
struct usb_request |
Gadget Core | 代表一次数据传输请求 | buf, length, complete, status |
struct usb_gadget_driver |
Function Driver | 代表一个 Gadget 功能驱动 | bind, unbind, setup, disconnect |
常见陷阱:在 UDC Driver 的
ep_queue 回调中,必须正确处理 DMA 缓冲区的对齐要求。许多 SoC 的 USB 控制器要求请求缓冲区物理地址按 4 字节或 32 字节对齐,否则会导致传输失败或数据损坏。在编写 Function Driver 时,应使用 usb_ep_alloc_request() 分配请求,不要直接 kmalloc。
2.6 主机侧与设备侧的对称性
理解 USB 子系统的一个关键视角是主机侧与设备侧的对称性:
- URB (USB Request Block) 是主机侧的数据传输单元,由 USB Core 管理。
- usb_request 是设备侧的数据传输单元,由 Gadget Core 管理。
- 两者在功能上对等:都包含缓冲区、长度、完成回调、状态标志。
- HCD 将 URB 转换为硬件传输描述符;UDC Driver 将 usb_request 转换为硬件传输描述符。
这种对称性使得开发者可以从主机侧调试经验快速迁移到设备侧开发。例如,主机侧通过 usb_submit_urb() 发起传输,设备侧通过 usb_ep_queue() 发起传输。
2.7 本章小结
Linux USB 子系统通过清晰的分层设计,将主机侧与设备侧的复杂性隔离。对于 Gadget 驱动开发者而言,重点掌握以下三层关系:
- Gadget Core 提供硬件无关的 API,是 Function Driver 与 UDC Driver 之间的桥梁。
- UDC Driver 是硬件相关层,负责与 SoC 的 USB 设备控制器交互。
- Function Driver 实现具体的 USB 功能,如虚拟网卡、虚拟串口。