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 硬件驱动层。这种设计实现了功能逻辑与硬件控制的完全分离。

USB Gadget API 层次结构图 USB Gadget API 层次结构 Gadget Function 层(功能驱动) UVC Camera | ACM Serial | Mass Storage | Ethernet/RNDIS Gadget API 层(UDC 抽象层) usb_gadget_driver / usb_gadget / usb_ep / usb_request UDC 硬件驱动层(控制器驱动) DWC2 | DWC3 | Chipidea | Renesas | FSL Function 驱动通过 Gadget API 调用 UDC 驱动,实现端点分配、数据传输和状态管理 分层设计使得 Function 驱动无需关心底层硬件细节,提升代码可移植性

各层的主要职责与核心数据结构如下表所示:

层次 核心数据结构 主要职责 典型文件
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 驱动模型结构图 UDC 驱动模型核心数据结构关系 struct usb_gadget • ops: usb_gadget_ops * • ep0: usb_ep * • speed: enum usb_device_speed • is_otg: bool struct usb_gadget_ops • pullup() • udc_start() • udc_stop() • get_frame() struct usb_ep • num: u8 • maxpacket: u16 • ops: usb_ep_ops * struct usb_request • buf: void * • length: unsigned • complete: callback ops ep_list req_list UDC 驱动通过实现 usb_gadget_ops 回调,将硬件操作注册到 Gadget 核心层 Function 驱动通过 usb_ep 和 usb_request 进行数据传输,不直接操作硬件寄存器

UDC 驱动的注册流程通常包含以下步骤:

  1. 平台设备初始化:在设备树或平台代码中定义 USB 控制器节点,驱动加载时获取硬件资源(基地址、中断、时钟)。
  2. 分配并初始化 usb_gadget:调用 usb_ep_gadget_init() 或手动填充结构体,设置端点 0 和操作函数。
  3. 实现 usb_gadget_ops:编写 pullup、udc_start、udc_stop 等回调,操作控制器寄存器。
  4. 注册 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
Gadget Function 驱动注册流程图 Gadget Function 驱动注册与运行流程 ① 模块初始化 usb_function_register() → 注册到 Gadget 核心 ② 配置绑定 (bind) 分配端点 EP → 注册控制请求 → 初始化上下文 ③ 设置配置 (set_alt) AltSetting=0 → 停止流 | AltSetting=1 → 启动流 ④ 数据传输 usb_ep_queue() → 中断处理 → 完成回调 UVC 驱动在 bind 阶段注册 PROBE/COMMIT 控制请求,在 set_alt 阶段启动视频流传输

在 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 功能是否被正确枚举。