3. UDC驱动核心:UDC(USB Device Controller)驱动的作用、注册与回调机制

UDC(USB Device Controller)驱动是USB Gadget框架中直接与硬件交互的底层软件层。它负责管理USB控制器硬件的寄存器、端点(Endpoint)资源、DMA传输以及中断处理。在Gadget驱动栈中,UDC驱动处于最底层,向上通过标准接口与Gadget Function驱动通信,向下直接操作硬件。

核心认知:UDC驱动是连接硬件与协议栈的桥梁。面试中,面试官通常关注你是否理解UDC驱动的注册流程、回调函数链以及端点生命周期管理。

3.1 UDC驱动的核心职责

UDC驱动需要完成以下关键任务:

  • 硬件初始化与去初始化:上电时配置控制器时钟、电源、中断线;关机时释放资源。
  • 端点管理:创建、配置、使能/禁用端点(包括控制端点0和功能端点)。
  • DMA与数据搬运:管理USB请求(URB/Request)的提交、取消和完成回调。
  • 中断处理:处理USB总线事件(复位、挂起、恢复、连接/断开)和端点传输完成中断。
  • 状态报告:向Gadget层报告当前连接状态、速度(HS/FS/LS)和帧号。
职责领域 具体操作 对应内核接口/结构体
硬件初始化 ioremap、时钟使能、中断注册 platform_driver_register()
端点管理 分配端点、设置最大包大小、FIFO配置 usb_ep_ops 中的 enable/disable
请求处理 提交/取消USB请求,处理完成回调 usb_request、usb_ep_queue()
中断处理 解析中断状态寄存器,分发事件 request_irq() + 自定义handler
状态通知 调用 gadget->ops->connect/disconnect usb_gadget_ops

3.2 UDC驱动的注册流程

UDC驱动通常以内核平台驱动(Platform Driver)或PCI驱动形式存在。注册过程分为两个阶段:驱动注册和Gadget注册。

  1. 编写平台驱动结构体:填充 struct platform_driver 的 probe/remove 回调。
  2. 在probe中:
    • 映射硬件寄存器地址(devm_ioremap_resource)。
    • 初始化控制器内部状态(时钟、复位、FIFO)。
    • 分配并初始化 struct usb_gadget 实例。
    • 设置 gadget->ops(指向UDC驱动的操作函数表)。
    • 调用 usb_add_gadget(gadget) 将Gadget设备注册到内核。
  3. 在remove中:
    • 调用 usb_del_gadget(gadget) 注销Gadget设备。
    • 释放中断、取消DMA、释放寄存器映射。
// 典型的UDC驱动probe函数骨架
static int my_udc_probe(struct platform_device *pdev)
{
    struct my_udc *udc;
    struct usb_gadget *gadget;
    int ret;

    udc = devm_kzalloc(&pdev->dev, sizeof(*udc), GFP_KERNEL);
    if (!udc)
        return -ENOMEM;

    udc->regs = devm_ioremap_resource(&pdev->dev,
                      platform_get_resource(pdev, IORESOURCE_MEM, 0));
    if (IS_ERR(udc->regs))
        return PTR_ERR(udc->regs);

    // 初始化硬件
    my_udc_hw_init(udc);

    // 初始化Gadget结构体
    gadget = &udc->gadget;
    gadget->ops = &my_udc_ops;          // 绑定操作函数表
    gadget->ep0 = &udc->ep0;            // 端点0
    gadget->name = "my-udc";
    gadget->dev.parent = &pdev->dev;

    // 注册Gadget设备
    ret = usb_add_gadget(gadget);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, udc);
    dev_info(&pdev->dev, "UDC probe success\n");
    return 0;
}
面试要点:注意 usb_add_gadget() 与 usb_add_gadget_udc() 的区别。前者只注册Gadget设备,后者会额外创建UDC类设备并自动匹配Gadget驱动。新内核推荐使用 usb_add_gadget_udc() 简化流程。

3.3 回调机制:usb_gadget_ops 详解

UDC驱动通过 struct usb_gadget_ops 向Gadget层暴露硬件操作能力。这是面试中最高频的考点之一。

struct usb_gadget_ops {
    int     (*get_frame)(struct usb_gadget *);
    int     (*wakeup)(struct usb_gadget *);
    int     (*set_selfpowered)(struct usb_gadget *, int);
    int     (*vbus_session)(struct usb_gadget *, int);
    int     (*vbus_draw)(struct usb_gadget *, unsigned mA);
    int     (*pullup)(struct usb_gadget *, int);
    int     (*ioctl)(struct usb_gadget *, unsigned, unsigned long);
    void    (*get_config_params)(struct usb_gadget *, struct usb_config_params *);
    int     (*udc_start)(struct usb_gadget *, struct usb_gadget_driver *);
    int     (*udc_stop)(struct usb_gadget *);
};
回调函数 触发时机 典型实现
udc_start Gadget驱动绑定到UDC时调用 使能中断、连接DMA、拉高D+(pullup)
udc_stop Gadget驱动解绑时调用 停止所有端点、禁用中断、拉低D+
pullup 软件控制D+/D-上拉电阻 写寄存器控制DP/DM上拉开关
get_frame 主机请求获取当前帧号 读取SOF帧号寄存器
vbus_session VBUS检测状态变化 通知PM核心,唤醒/休眠控制器
vbus_draw 配置最大VBUS电流 设置充电/限流参数
常见陷阱:许多面试者混淆 udc_start 和 pullup 的职责。记住:udc_start 是软件层面的绑定和初始化,pullup 是物理层信号控制。主机检测到设备插入依赖 pullup(1),而不是 udc_start。

3.4 端点操作与请求生命周期

UDC驱动还需要实现 struct usb_ep_ops,这是操作具体端点的接口。每个端点对应一个 struct usb_ep 实例。

struct usb_ep_ops {
    int (*enable)(struct usb_ep *ep, const struct usb_endpoint_descriptor *desc);
    int (*disable)(struct usb_ep *ep);
    struct usb_request *(*alloc_request)(struct usb_ep *ep, gfp_t gfp_flags);
    void (*free_request)(struct usb_ep *ep, struct usb_request *req);
    int (*queue)(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags);
    int (*dequeue)(struct usb_ep *ep, struct usb_request *req);
    int (*set_halt)(struct usb_ep *ep, int value);
    int (*set_wedge)(struct usb_ep *ep);
    int (*fifo_status)(struct usb_ep *ep);
    void (*fifo_flush)(struct usb_ep *ep);
};

USB请求(struct usb_request)的生命周期如下:

  1. 分配:Function驱动调用 usb_ep_alloc_request() 分配请求对象。
  2. 填充:设置缓冲区指针、长度、完成回调 complete。
  3. 提交:调用 usb_ep_queue() 将请求提交到UDC硬件队列。
  4. 传输:UDC硬件执行DMA或PIO传输。
  5. 完成:传输结束后,UDC中断处理函数调用请求的 complete 回调。
  6. 释放:Function驱动调用 usb_ep_free_request() 回收请求。
UDC驱动架构与回调流程 Gadget Function驱动 usb_gadget_ops usb_ep_ops 中断处理 USB Device Controller 硬件 回调流程说明: 1. Function驱动调用 usb_ep_queue() → 触发 usb_ep_ops.queue 2. UDC硬件完成传输 → 触发中断 → 中断处理函数调用 req->complete 3. 主机复位/断开 → UDC检测到事件 → 调用 gadget->ops->udc_stop 4. 软件拉高D+ → 调用 gadget->ops->pullup(1) → 主机枚举开始

3.5 面试高频问题与避坑指南

  • Q: udc_start 和 pullup 的调用顺序?
    A: 先调用 udc_start 完成软件初始化,再调用 pullup(1) 通知主机。顺序不可颠倒。
  • Q: 如何处理端点STALL条件?
    A: 调用 usb_ep_set_halt() 设置端点Halt状态。注意控制端点处理协议错误时需要自动清除Halt。
  • Q: UDC驱动如何支持多个配置?
    A: 在 udc_start 中不预设配置,由Gadget层根据主机请求动态切换。UDC只需提供足够的端点资源。
  • Q: 中断上下文中能否调用 usb_ep_queue?
    A: 可以,但必须使用 GFP_ATOMIC 标志。这是UDC驱动中常见的优化手段。
实战建议:阅读内核源码中 drivers/usb/gadget/udc/ 目录下的真实驱动(如 dwc2、dwc3、chipidea),重点关注 udc_start 和端点 queue 的实现。面试时能引用具体驱动代码会大幅加分。