2. USB Gadget 驱动基础:Gadget API 架构、UDC 层、Gadget Function 层、ConfigFS 与 Legacy 接口
在深入实时延迟优化之前,必须理解 USB Gadget 驱动的核心架构。Linux USB Gadget 子系统采用分层的软件架构,将硬件控制、功能实现与配置管理解耦。本章将逐一剖析 Gadget API 架构、UDC 层、Function 层以及两种配置接口(ConfigFS 与 Legacy)。
2.1 Gadget API 架构总览
USB Gadget 子系统位于 Linux 内核的 drivers/usb/gadget/ 目录下,其设计目标是在嵌入式设备上模拟 USB 外设(如串口、网卡、存储设备)。整个架构分为三层:UDC 层(硬件抽象)、Gadget 层(核心 API)和 Function 层(协议实现)。
关键概念:Gadget API 层是上下两层之间的桥梁。Function 层通过 Gadget API 分配端点、提交 USB 请求(URB),而 UDC 层负责将请求转换为硬件事务。实时延迟优化的主要战场就在 Gadget API 层的请求调度和 UDC 层的中断处理路径上。
2.2 UDC(USB Device Controller)层
UDC 层是硬件抽象层,直接与 SoC 或外挂 USB 控制器硬件交互。每个 UDC 驱动必须实现 usb_gadget_ops 结构体中的回调函数。
| 回调函数 | 功能描述 | 实时关键性 |
|---|---|---|
get_frame |
获取当前微帧号(125μs 精度) | 高:用于等时传输调度 |
ep_enable |
配置端点类型、方向、最大包大小 | 中:配置阶段调用 |
ep_queue |
将 USB 请求提交到硬件队列 | 极高:数据路径核心函数 |
pullup |
控制 D+ 上拉电阻,通知主机设备连接 | 低:仅状态切换 |
start/stop |
启动/停止 UDC 控制器 | 低:初始化/销毁 |
UDC 层的主要职责包括:
- 端点管理:维护端点 0(控制端点)和功能端点(批量/中断/等时)的硬件描述符。
- DMA 映射:将 USB 请求缓冲区映射到 DMA 地址,减少 CPU 拷贝。
- 中断处理:处理 USB 控制器中断(如传输完成、复位、挂起),并回调 Gadget API 层。
- 电源管理:处理挂起/恢复事件。
实时优化关注点:UDC 层的中断处理函数运行在中断上下文,其执行时间直接影响系统最大延迟。常见的优化手段包括:使用 threaded IRQ 将非关键处理推入进程上下文、减少临界区长度、使用 DMA 而非 PIO 模式。
2.3 Gadget Function 层
Function 层实现具体的 USB 协议逻辑。每个 Function 对应一个 USB 接口描述符和一组端点。常见的 Function 包括:
- Ethernet (ECM/RNDIS/EEM):通过 USB 模拟网络接口,常用于 RNDIS 共享网络。
- Serial (ACM):模拟串口,通过 USB CDC ACM 协议实现。
- Mass Storage (MSC):模拟 U 盘,使用 SCSI 命令集。
- Audio (UAC1/UAC2):模拟音频设备,对实时性要求极高。
- HID:模拟键盘、鼠标等人机交互设备。
Function 通过 usb_function 结构体注册到 Gadget API:
struct usb_function {
const char *name;
struct usb_gadget_strings **strings;
struct usb_descriptor_header **descriptors;
struct usb_descriptor_header **hs_descriptors;
struct usb_descriptor_header **ss_descriptors;
int (*bind)(struct usb_configuration *, struct usb_function *);
void (*unbind)(struct usb_configuration *, struct usb_function *);
int (*set_alt)(struct usb_function *, unsigned interface, unsigned alt);
int (*get_alt)(struct usb_function *, unsigned interface);
void (*disable)(struct usb_function *);
int (*setup)(struct usb_function *, const struct usb_ctrlrequest *);
bool (*req_match)(struct usb_function *, const struct usb_ctrlrequest *, bool config);
void (*suspend)(struct usb_function *);
void (*resume)(struct usb_function *);
};
2.4 ConfigFS 接口
ConfigFS 是当前推荐的 USB Gadget 配置接口,基于内核的 configfs 文件系统。它允许用户在用户空间通过文件系统操作动态组合和配置 Gadget 功能,无需重新编译内核或编写内核模块。
ConfigFS 配置流程:
- 挂载 configfs:
mount -t configfs none /sys/kernel/config - 创建 Gadget:
mkdir /sys/kernel/config/usb_gadget/g1 - 设置 VID/PID:
echo 0x1d6b > /sys/kernel/config/usb_gadget/g1/idVendor - 创建配置:
mkdir /sys/kernel/config/usb_gadget/g1/configs/c.1 - 创建功能:
mkdir /sys/kernel/config/usb_gadget/g1/functions/ecm.usb0 - 绑定功能到配置:
ln -s /sys/kernel/config/usb_gadget/g1/functions/ecm.usb0 /sys/kernel/config/usb_gadget/g1/configs/c.1 - 绑定 UDC:
echo musb-hdrc.0 > /sys/kernel/config/usb_gadget/g1/UDC
实时优势:ConfigFS 允许在运行时动态切换功能,无需重启系统。在实时系统中,可以在不中断关键任务的前提下,通过脚本快速切换 Gadget 模式(例如从串口切换到网卡)。
2.5 Legacy 接口(GadgetFS)
Legacy 接口(也称为 GadgetFS)是早期的 USB Gadget 配置方式,通过 /dev/gadget 设备节点与用户空间交互。它使用 gadgetfs 文件系统,用户空间程序直接读写端点文件。
| 特性 | ConfigFS | Legacy (GadgetFS) |
|---|---|---|
| 配置方式 | 文件系统操作(mkdir/ln/echo) | 用户空间程序通过 ioctl/读写端点文件 |
| 动态性 | 支持运行时动态配置 | 需要用户空间守护进程 |
| 内核模块依赖 | 无需自定义内核模块 | 需要 g_*.ko 模块或自定义模块 |
| 实时性能 | 中等(上下文切换在用户空间) | 较低(多次用户态/内核态切换) |
| 维护状态 | 推荐(内核主线维护) | 遗留(仅维护兼容性) |
实时系统建议:对于实时 Linux 系统,优先使用 ConfigFS 接口。Legacy GadgetFS 在每次端点读写时都会产生用户态/内核态切换,增加不可预测的延迟抖动。ConfigFS 允许将 Function 完全运行在内核空间,减少上下文切换次数。
2.6 两种接口的架构对比
2.7 实时视角下的接口选择
从实时系统角度,选择配置接口时需考虑以下因素:
- 延迟确定性:ConfigFS 的 Function 运行在内核空间,不受用户空间调度抖动影响。
- 上下文切换开销:Legacy 接口每次端点读写至少产生 2 次上下文切换(用户→内核→用户),增加 10-50μs 延迟。
- 优先级继承:内核空间的 Function 可以配合 RT mutex 和优先级继承机制,避免优先级反转。
- 中断响应:ConfigFS 允许 Function 直接在中断回调中处理数据,而 Legacy 需要唤醒用户空间线程。
最佳实践:在实时 Linux 系统中,始终使用 ConfigFS 接口配置 USB Gadget。如果必须使用 Legacy 接口(例如维护旧代码),建议将用户空间守护进程设置为 SCHED_FIFO 实时优先级,并使用
mlockall() 锁定内存页,减少缺页异常导致的延迟。