3. UDC驱动注册与注销流程:usb_add_gadget_udc()与usb_del_gadget_udc()的调用链与内核实现分析

UDC(USB Device Controller)驱动是USB Gadget框架中连接硬件控制器的核心层。UDC驱动的注册与注销是Gadget设备枚举的前提,本章深入分析usb_add_gadget_udc()与usb_del_gadget_udc()两个核心API的调用链与内核实现机制。

3.1 UDC驱动注册入口:usb_add_gadget_udc()

该函数是UDC驱动向内核注册的标准入口,通常在UDC控制器的probe()函数中调用。其核心职责是初始化usb_gadget结构体、创建sysfs属性文件、注册到Gadget核心层,并最终触发Uevent通知用户空间。

关键数据结构:struct usb_gadget 代表一个UDC控制器实例,包含端点列表、速度能力、驱动绑定状态等核心字段。

3.1.1 函数原型与参数

int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget);
参数 类型 描述
parent struct device * 父设备指针,通常是平台设备或PCI设备
gadget struct usb_gadget * 已初始化的UDC控制器实例

3.1.2 内部调用链分析

usb_add_gadget_udc()的执行流程可分为四个阶段,每个阶段都有明确的错误处理回滚机制:

  1. 设备初始化阶段:调用device_initialize()初始化gadget->dev,设置设备名称为"gadget"。
  2. sysfs属性创建阶段:调用device_create_file()创建udc、speed、max_speed等属性文件。
  3. 设备注册阶段:调用device_add()将gadget设备添加到设备模型。
  4. 绑定检查阶段:调用usb_gadget_probe_driver()检查是否有等待绑定的Gadget驱动。
实现细节:在device_add()之后,内核会自动触发uevent,用户空间的udev或mdev可以据此加载对应的Gadget驱动。
usb_add_gadget_udc() 调用链流程图 usb_add_gadget_udc() ① device_initialize() ② device_create_file() ③ device_add() ④ usb_gadget_probe_driver() 错误回滚机制 • 阶段①失败:直接返回 • 阶段②失败:put_device() • 阶段③失败:删除sysfs文件 • 阶段④失败:device_del() 返回 0 (成功) / 负值 (失败) 每个阶段失败都会执行对应的清理操作,保证资源不泄漏

3.2 UDC驱动注销入口:usb_del_gadget_udc()

该函数是UDC驱动注销的标准接口,通常在UDC控制器的remove()函数中调用。其核心职责是解绑当前Gadget驱动、删除sysfs属性文件、注销设备,并释放相关资源。

3.2.1 函数原型与参数

void usb_del_gadget_udc(struct usb_gadget *gadget);
参数 类型 描述
gadget struct usb_gadget * 要注销的UDC控制器实例

3.2.2 内部调用链分析

usb_del_gadget_udc()的执行流程是注册过程的逆操作,同样包含四个关键步骤:

  1. 驱动解绑阶段:调用usb_gadget_udc_stop()停止当前绑定的Gadget驱动,释放端点资源。
  2. 设备删除阶段:调用device_del()从设备模型中移除gadget设备。
  3. sysfs属性删除阶段:调用device_remove_file()删除之前创建的属性文件。
  4. 设备释放阶段:调用put_device()减少设备引用计数,触发资源释放。
注意事项:注销操作必须在进程上下文执行,且不能持有任何自旋锁。如果当前有Gadget驱动正在处理USB请求,usb_gadget_udc_stop()会等待所有未完成的请求完成或超时。
usb_del_gadget_udc() 调用链流程图 usb_del_gadget_udc() ① usb_gadget_udc_stop() ② device_del() ③ device_remove_file() ④ put_device() 注销完成 关键行为 • 停止所有端点请求 • 等待未完成请求完成 • 从设备树中移除节点 • 删除 /sys/bus/platform/ 下属性 • 释放 gadget 结构体内存

3.3 注册与注销的对称性分析

UDC驱动的注册与注销在操作序列上严格对称,这种设计保证了内核资源管理的可预测性。下表对比了两个流程的核心差异:

操作阶段 注册 (usb_add_gadget_udc) 注销 (usb_del_gadget_udc)
设备初始化 device_initialize() put_device() (逆操作)
sysfs属性 device_create_file() device_remove_file()
设备模型 device_add() device_del()
驱动绑定 usb_gadget_probe_driver() usb_gadget_udc_stop()

3.4 实际驱动中的调用示例

以DWC3控制器驱动为例,展示UDC注册与注销在实际驱动中的典型用法:

static int dwc3_gadget_probe(struct dwc3 *dwc)
{
    struct usb_gadget *gadget = &dwc->gadget;
    int ret;

    /* 初始化gadget结构体 */
    gadget->ops = &dwc3_gadget_ops;
    gadget->max_speed = USB_SPEED_SUPER;
    gadget->name = "dwc3-gadget";

    /* 注册UDC */
    ret = usb_add_gadget_udc(dwc->dev, gadget);
    if (ret) {
        dev_err(dwc->dev, "failed to add UDC: %d\n", ret);
        return ret;
    }

    return 0;
}

static int dwc3_gadget_remove(struct dwc3 *dwc)
{
    struct usb_gadget *gadget = &dwc->gadget;

    /* 注销UDC */
    usb_del_gadget_udc(gadget);

    return 0;
}
移植要点:在移植UDC驱动时,务必确保probe()函数中usb_add_gadget_udc()的调用时机正确——必须在所有硬件资源(时钟、中断、DMA池)初始化完成之后,但在使能中断之前调用。这样可以避免在设备未完全就绪时被Gadget驱动绑定。

3.5 内核实现中的关键数据结构关系

UDC注册与注销操作的核心是维护usb_gadget与usb_udc两个结构体之间的关系。下图展示了注册完成后内核中的数据结构拓扑:

UDC注册后内核数据结构关系 struct usb_gadget • dev : struct device • ops : struct usb_gadget_ops * • ep_list : list_head struct usb_udc • gadget : struct usb_gadget * • driver : struct usb_gadget_driver * • dev : struct device gadget->udc struct usb_gadget_driver • bind() / unbind() / setup() • max_speed / driver udc->driver usb_add_gadget_udc() 内部会创建 usb_udc 实例,并将 gadget->udc 指向它 注销时 usb_del_gadget_udc() 会断开所有指针引用并释放 usb_udc 内存

3.6 常见问题与调试方法

常见问题1:注册失败 -ENODEV
原因:父设备指针parent为NULL或父设备未初始化。
解决:确保在调用usb_add_gadget_udc()之前,父设备已经通过platform_device_register()或pci_register_driver()正确注册。
常见问题2:注销时内核崩溃
原因:注销时仍有Gadget驱动正在处理USB请求,或者gadget结构体已经被释放。
解决:在调用usb_del_gadget_udc()之前,确保所有端点请求已经完成,并且没有其他线程正在访问gadget结构体。
调试方法:可以通过/sys/kernel/debug/usb/gadget目录下的调试文件查看当前UDC的注册状态。如果UDC注册成功,会在/sys/bus/platform/devices/下看到对应的gadget设备节点。

通过以上分析可以看出,usb_add_gadget_udc()与usb_del_gadget_udc()是UDC驱动生命周期管理的核心接口,理解其内部调用链对于正确移植UDC驱动、排查枚举问题至关重要。