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流程图展示完整的注册路径:
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展示其内部路径:
4.3.1 unbind回调的职责
在虚拟网卡驱动中,unbind回调必须完成以下清理工作:
- 停止所有正在进行的USB请求(usb_ep_dequeue)。
- 释放已分配的网络设备(free_netdev)。
- 注销网络设备(unregister_netdev)。
- 释放端点对应的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回调,确保驱动加载与卸载的健壮性。