3. USB Gadget 核心框架分析
USB Gadget 框架是 Linux 内核中用于实现设备端(Device Side)USB 功能的软件架构。它允许嵌入式设备(如 ARM 开发板)模拟各种 USB 外设,例如摄像头(UVC)、串口(ACM)、存储设备(Mass Storage)或网络适配器(Ethernet/RNDIS)。本章将深入剖析 Gadget 核心框架的三个关键层次:Gadget API 层次结构、UDC 驱动模型以及 Function 驱动的注册流程。
核心要点:理解 Gadget 框架的分层设计是进行驱动移植的基础。上层 Function 驱动(如 UVC)通过标准 API 与底层 UDC 硬件解耦,使得同一份摄像头驱动代码可以无缝运行在不同厂商的 USB 控制器上。
3.1 Gadget API 层次结构
Linux USB Gadget 框架采用经典的分层架构,从上到下依次为:Gadget Function 层、Gadget API 层(也称为 UDC 抽象层)和 UDC 硬件驱动层。这种设计实现了功能逻辑与硬件控制的完全分离。
各层的主要职责与核心数据结构如下表所示:
| 层次 | 核心数据结构 | 主要职责 | 典型文件 |
|---|---|---|---|
| Function 层 | usb_function |
实现具体的 USB 功能逻辑,如 UVC 的帧传输、ACM 的串口数据收发 | drivers/usb/gadget/function/uvc.c |
| API 层 | usb_gadget_driver, usb_gadget, usb_ep, usb_request |
提供统一的编程接口,管理端点、请求和配置 | drivers/usb/gadget/udc/core.c |
| UDC 层 | usb_gadget_ops |
直接操作 USB 控制器寄存器,处理硬件中断和 DMA 传输 | drivers/usb/dwc2/gadget.c |
移植提示:在 ARM 平台移植摄像头驱动时,通常只需要关注 Function 层(UVC 驱动)和 API 层的适配。UDC 层一般由 SoC 厂商或内核主线提供,除非使用非标准 USB 控制器,否则无需修改。
3.2 UDC(USB Device Controller)驱动模型
UDC 驱动模型是 Gadget 框架的基石,它负责将具体的 USB 硬件控制器抽象为内核可管理的设备对象。每个 UDC 驱动都需要实现 usb_gadget_ops 结构体中的回调函数,并注册到 Gadget 核心层。
UDC 驱动的核心数据结构关系如下:
usb_gadget:代表一个 USB 设备控制器实例,包含设备状态、端点列表和操作函数指针。usb_gadget_ops:定义 UDC 硬件操作接口,包括pullup(上拉 D+ 线)、udc_start(启动控制器)、udc_stop(停止控制器)等。usb_ep:表示一个 USB 端点,包含端点号、方向、最大包大小和传输类型。usb_request:描述一次 USB 数据传输请求,包含数据缓冲区、长度、完成回调等。
UDC 驱动的注册流程通常包含以下步骤:
- 平台设备初始化:在设备树或平台代码中定义 USB 控制器节点,驱动加载时获取硬件资源(基地址、中断、时钟)。
- 分配并初始化
usb_gadget:调用usb_ep_gadget_init()或手动填充结构体,设置端点 0 和操作函数。 - 实现
usb_gadget_ops:编写pullup、udc_start、udc_stop等回调,操作控制器寄存器。 - 注册 UDC 设备:调用
usb_add_gadget_udc()将usb_gadget注册到 Gadget 核心层,使其对上层 Function 驱动可见。
常见问题:在 ARM 平台移植时,UDC 驱动最常见的问题是 DMA 一致性映射和中断处理。确保
dma_set_coherent_mask() 正确设置,并检查中断号是否与设备树匹配。
3.3 Gadget Function 驱动注册流程
Gadget Function 驱动是 USB 功能的具体实现者。以 UVC 摄像头驱动为例,其注册流程涉及从内核模块初始化到 USB 枚举完成的完整链路。理解这一流程对于调试和移植至关重要。
Function 驱动的注册流程可以分为以下关键阶段:
| 阶段 | 调用函数 | 关键操作 | 涉及文件 |
|---|---|---|---|
| 1. 模块初始化 | module_init() |
调用 usb_function_register() 注册 Function 驱动到 Gadget 核心 |
f_uvc.c |
| 2. 配置绑定 | bind() |
分配端点、注册中断、初始化 UVC 控制请求和流传输上下文 | uvc_configfs.c |
| 3. 设置配置 | set_alt() |
根据 USB 主机选择的配置(AltSetting)启动或停止视频流 | uvc_video.c |
| 4. 数据传输 | uvc_function_ep0_complete() |
处理控制请求(如 PROBE/COMMIT),通过 usb_ep_queue() 提交数据请求 |
uvc_queue.c |
在 UVC 摄像头驱动的具体实现中,注册流程的关键代码片段如下:
// f_uvc.c - UVC Function 驱动注册示例
static struct usb_function_instance *uvc_alloc_inst(void)
{
struct uvc_device *uvc;
uvc = kzalloc(sizeof(*uvc), GFP_KERNEL);
if (!uvc)
return ERR_PTR(-ENOMEM);
// 初始化 UVC 设备上下文
INIT_LIST_HEAD(&uvc->video.reqs);
spin_lock_init(&uvc->video.qlock);
return &uvc->func_inst;
}
static int uvc_bind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct uvc_device *uvc = to_uvc(f);
int ret;
// 分配 USB 端点(通常使用端点 3 进行批量传输)
uvc->video.ep = usb_ep_autoconfig(cdev->gadget, &uvc_stream_ss_in_desc);
if (!uvc->video.ep)
return -ENODEV;
// 注册 EP0 控制请求处理函数
uvc->control.req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL);
uvc->control.req->complete = uvc_function_ep0_complete;
// 将 Function 添加到配置中
usb_add_function(c, f);
return 0;
}
// 模块入口
static int __init uvc_init(void)
{
return usb_function_register(&uvc_func_inst);
}
module_init(uvc_init);
调试建议:在 ARM 平台移植时,可以在
bind() 和 set_alt() 函数中添加 dev_dbg() 打印,观察端点分配是否成功以及 AltSetting 切换是否正确。使用 lsusb -v 在主机端查看设备描述符,确认 UVC 功能是否被正确枚举。