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()的执行流程可分为四个阶段,每个阶段都有明确的错误处理回滚机制:
- 设备初始化阶段:调用
device_initialize()初始化gadget->dev,设置设备名称为"gadget"。 - sysfs属性创建阶段:调用
device_create_file()创建udc、speed、max_speed等属性文件。 - 设备注册阶段:调用
device_add()将gadget设备添加到设备模型。 - 绑定检查阶段:调用
usb_gadget_probe_driver()检查是否有等待绑定的Gadget驱动。
实现细节:在
device_add()之后,内核会自动触发uevent,用户空间的udev或mdev可以据此加载对应的Gadget驱动。
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()的执行流程是注册过程的逆操作,同样包含四个关键步骤:
- 驱动解绑阶段:调用
usb_gadget_udc_stop()停止当前绑定的Gadget驱动,释放端点资源。 - 设备删除阶段:调用
device_del()从设备模型中移除gadget设备。 - sysfs属性删除阶段:调用
device_remove_file()删除之前创建的属性文件。 - 设备释放阶段:调用
put_device()减少设备引用计数,触发资源释放。
注意事项:注销操作必须在进程上下文执行,且不能持有任何自旋锁。如果当前有Gadget驱动正在处理USB请求,
usb_gadget_udc_stop()会等待所有未完成的请求完成或超时。
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两个结构体之间的关系。下图展示了注册完成后内核中的数据结构拓扑:
3.6 常见问题与调试方法
常见问题1:注册失败 -ENODEV
原因:父设备指针
解决:确保在调用
原因:父设备指针
parent为NULL或父设备未初始化。解决:确保在调用
usb_add_gadget_udc()之前,父设备已经通过platform_device_register()或pci_register_driver()正确注册。
常见问题2:注销时内核崩溃
原因:注销时仍有Gadget驱动正在处理USB请求,或者
解决:在调用
原因:注销时仍有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驱动、排查枚举问题至关重要。