4. Gadget驱动注册与注销:usb_gadget_probe_driver()、usb_gadget_unregister_driver()的使用流程

在USB Gadget框架中,驱动注册与注销是连接Gadget功能驱动与UDC(USB Device Controller)核心层的桥梁。本章深入剖析usb_gadget_probe_driver()与usb_gadget_unregister_driver()两个核心API的内部机制、调用流程及实战注意事项,帮助开发者理解Gadget驱动从加载到卸载的完整生命周期。

4.1 核心API概述

Gadget驱动注册与注销的API定义在drivers/usb/gadget/udc/core.c中,是UDC核心层对外暴露的关键接口。下表对比了两个API的功能与使用场景:

API函数 功能描述 调用时机 返回值
usb_gadget_probe_driver() 将Gadget功能驱动注册到UDC核心,并尝试与可用的UDC设备绑定 模块加载或驱动初始化时 0成功,负值错误码
usb_gadget_unregister_driver() 从UDC核心注销Gadget功能驱动,并断开与已绑定UDC的连接 模块卸载或驱动退出时 void
关键提示:这两个API是成对出现的。驱动开发中必须确保在模块退出时调用usb_gadget_unregister_driver(),否则会导致UDC核心层残留无效指针,引发内核崩溃。

4.2 usb_gadget_probe_driver() 注册流程

该函数的执行流程分为三个阶段:驱动校验、UDC匹配、绑定回调。下面通过SVG流程图展示完整的注册路径:

usb_gadget_probe_driver() 注册流程图 调用 probe_driver 阶段1:驱动校验 检查 driver->bind / driver->unbind / driver->max_speed 阶段2:UDC匹配 遍历 udc_list,查找未绑定且匹配的UDC设备 匹配条件:udc->driver == NULL && 驱动支持该UDC速度 匹配成功? 返回 -ENODEV 阶段3:绑定回调 调用 driver->bind(udc) 完成功能初始化 注册完成,返回0

4.2.1 驱动校验细节

在阶段1中,UDC核心会检查以下关键字段:

  • bind 回调:必须非空,否则返回 -EINVAL。
  • unbind 回调:必须非空,用于后续注销时清理资源。
  • max_speed:不能超过 USB_SPEED_SUPER_PLUS,且必须大于 USB_SPEED_UNKNOWN。
  • function 链表:可选检查,但建议在 bind 中初始化。

4.2.2 UDC匹配与绑定

匹配过程的核心逻辑如下:

// 核心匹配代码路径(简化)
list_for_each_entry(udc, &udc_list, list) {
    if (udc->driver)
        continue;  // 已被其他驱动绑定
    if (driver->max_speed < udc->gadget->max_speed)
        continue;  // 驱动不支持该UDC的最高速度
    // 匹配成功,执行绑定
    ret = driver->bind(&udc->gadget);
    if (!ret) {
        udc->driver = driver;
        break;
    }
}
实战要点:在虚拟网卡驱动中,bind回调通常负责分配网络设备结构体、初始化端点、注册网络设备。务必在bind中检查UDC支持的端点数量与类型,避免硬件不兼容。

4.3 usb_gadget_unregister_driver() 注销流程

注销流程相对简洁,但必须保证资源完全释放。下面通过SVG展示其内部路径:

usb_gadget_unregister_driver() 注销流程图 调用 unregister_driver 已绑定UDC? 直接返回 调用 driver->unbind(udc) 释放端点、清除配置、停止传输 udc->driver = NULL; 解除绑定 注销完成

4.3.1 unbind回调的职责

在虚拟网卡驱动中,unbind回调必须完成以下清理工作:

  1. 停止所有正在进行的USB请求(usb_ep_dequeue)。
  2. 释放已分配的网络设备(free_netdev)。
  3. 注销网络设备(unregister_netdev)。
  4. 释放端点对应的request缓冲区。
// 虚拟网卡unbind回调示例
static int geth_unbind(struct usb_gadget *gadget) {
    struct geth_dev *dev = get_gadget_data(gadget);

    // 停止所有端点传输
    usb_ep_dequeue(dev->in_ep, dev->in_req);
    usb_ep_dequeue(dev->out_ep, dev->out_req);

    // 注销网络设备
    unregister_netdev(dev->net);
    free_netdev(dev->net);

    // 释放request
    usb_ep_free_request(dev->in_ep, dev->in_req);
    usb_ep_free_request(dev->out_ep, dev->out_req);

    set_gadget_data(gadget, NULL);
    return 0;
}

4.4 驱动注册与注销的完整代码模板

以下是一个虚拟网卡驱动的标准注册/注销模板,展示了两个API的典型用法:

// 模块初始化函数
static int __init geth_init(void) {
    int ret;

    // 注册Gadget驱动
    ret = usb_gadget_probe_driver(&geth_driver);
    if (ret) {
        pr_err("geth: failed to probe driver, err=%d\n", ret);
        return ret;
    }

    pr_info("geth: USB Ethernet Gadget driver registered\n");
    return 0;
}

// 模块退出函数
static void __exit geth_exit(void) {
    // 注销Gadget驱动
    usb_gadget_unregister_driver(&geth_driver);
    pr_info("geth: USB Ethernet Gadget driver unregistered\n");
}

module_init(geth_init);
module_exit(geth_exit);

// Gadget驱动结构体定义
static struct usb_gadget_driver geth_driver = {
    .function   = (char *) "Gadget Ethernet",
    .max_speed  = USB_SPEED_HIGH,
    .bind       = geth_bind,
    .unbind     = geth_unbind,
    .setup      = geth_setup,
    .disconnect = geth_disconnect,
    .suspend    = geth_suspend,
    .resume     = geth_resume,
    .driver     = {
        .name   = "g_ether",
        .owner  = THIS_MODULE,
    },
};
常见错误:在bind回调中分配资源后,如果后续步骤失败,必须手动释放已分配资源并返回错误码。否则会导致资源泄漏,且UDC核心不会自动清理。建议在bind中使用goto错误处理模式。

4.5 注册与注销的同步机制

UDC核心使用互斥锁udc_lock保护驱动注册与注销过程,确保并发安全。下表总结了锁的使用规则:

操作 持有锁 锁类型 注意事项
usb_gadget_probe_driver() udc_lock 互斥锁 bind回调中不能调用probe_driver,否则死锁
usb_gadget_unregister_driver() udc_lock 互斥锁 unbind回调中不能调用unregister_driver
bind/unbind回调内部 已持有udc_lock 不可重入 回调中禁止睡眠操作(如kmalloc(GFP_KERNEL))
性能建议:由于bind回调在持有udc_lock的情况下执行,应避免在bind中执行耗时操作。如果必须进行固件加载或硬件初始化,建议使用工作队列异步处理。

4.6 多UDC场景下的注册行为

当系统存在多个UDC控制器时,usb_gadget_probe_driver()会尝试与所有未绑定的UDC匹配。匹配顺序由UDC在链表中的位置决定,通常与UDC注册顺序一致。开发者可以通过以下方式控制绑定行为:

  • 使用UDC名称匹配:在驱动结构体中设置udc_name字段,指定要绑定的UDC名称。
  • 动态绑定:通过sysfs接口/sys/bus/platform/drivers/...手动绑定。
  • 延迟绑定:在bind回调中检查UDC的name属性,选择性绑定。
// 指定UDC名称的驱动结构体
static struct usb_gadget_driver geth_driver = {
    .udc_name   = "musb-hdrc.0",  // 只绑定到特定的UDC
    .function   = (char *) "Gadget Ethernet",
    .max_speed  = USB_SPEED_HIGH,
    .bind       = geth_bind,
    // ... 其他字段
};

通过本章的学习,开发者应能熟练掌握Gadget驱动的注册与注销流程,理解UDC核心层的匹配机制,并在实际项目中正确实现bind与unbind回调,确保驱动加载与卸载的健壮性。