2. Linux USB 驱动框架:usb_driver 结构体、probe 与 disconnect 回调
在 Linux USB 子系统中,usb_driver 是连接 USB 设备与内核驱动模型的核心桥梁。它定义了驱动与设备匹配的规则、设备插入时的初始化逻辑(probe)以及设备拔出时的清理逻辑(disconnect)。理解这一框架是编写稳定 USB 驱动的第一步。
2.1 usb_driver 结构体核心成员
结构体 struct usb_driver 定义在 <linux/usb.h> 中。下表列出了驱动开发中最常使用的成员:
| 成员 | 类型 | 说明 |
|---|---|---|
| name | const char * |
驱动名称,在 /sys/bus/usb/drivers/ 中显示 |
| probe | int (*probe)(struct usb_interface *, const struct usb_device_id *) |
设备匹配成功时调用,完成初始化 |
| disconnect | void (*disconnect)(struct usb_interface *) |
设备拔出或驱动卸载时调用,释放资源 |
| id_table | const struct usb_device_id * |
驱动支持的设备 ID 列表,用于匹配 |
| suspend / resume | 电源管理回调 | 系统挂起/恢复时的处理(可选) |
| supports_autosuspend | int |
是否支持自动挂起,节能场景常用 |
关键点:probe 和 disconnect 是每个 USB 驱动必须实现的两个回调。即使驱动功能简单,也必须提供空函数体,否则内核无法正确管理设备生命周期。
2.2 probe 回调:设备插入时的初始化流程
当 USB 核心检测到设备插入,并且设备的 Vendor ID / Product ID(或 class/subclass/protocol)与驱动的 id_table 匹配时,内核会调用驱动的 probe 函数。典型的 probe 流程包含以下步骤:
- 获取接口结构:通过参数
struct usb_interface *访问设备端点信息。 - 分配私有数据结构:使用
kzalloc分配驱动私有上下文。 - 初始化 URB 和缓冲区:为后续的数据传输做准备。
- 注册字符设备或输入设备:将设备暴露给用户空间。
- 设置接口私有数据:调用
usb_set_intfdata()关联私有数据。
以下是一个精简的 probe 实现示例:
static int my_usb_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(interface);
struct my_usb_dev *dev;
int ret;
// 1. 分配私有数据
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
// 2. 保存设备句柄
dev->udev = usb_get_dev(udev);
dev->interface = interface;
// 3. 初始化端点信息(以批量端点为例)
usb_find_bulk_in_endpoint(interface->cur_altsetting, &dev->bulk_in);
usb_find_bulk_out_endpoint(interface->cur_altsetting, &dev->bulk_out);
// 4. 注册字符设备(省略具体实现)
ret = register_chrdev(dev->major, "my_usb", &my_fops);
if (ret < 0) {
usb_put_dev(dev->udev);
kfree(dev);
return ret;
}
// 5. 关联私有数据到接口
usb_set_intfdata(interface, dev);
dev_info(&interface->dev, "My USB device probed\n");
return 0;
}
常见错误:probe 中忘记调用
usb_set_intfdata(),会导致 disconnect 中无法获取私有数据,进而引发内存泄漏或空指针访问。
2.3 disconnect 回调:设备拔出时的清理流程
disconnect 是 probe 的逆操作。当设备物理拔出、驱动卸载或系统进入低功耗模式时,内核调用此函数。核心职责是:
- 停止所有正在进行的 URB 传输(
usb_kill_urb())。 - 注销字符设备或输入设备。
- 释放私有数据内存(
kfree)。 - 减少设备引用计数(
usb_put_dev())。
static void my_usb_disconnect(struct usb_interface *interface)
{
struct my_usb_dev *dev = usb_get_intfdata(interface);
if (!dev)
return;
// 1. 注销字符设备
unregister_chrdev(dev->major, "my_usb");
// 2. 杀死所有待处理的 URB
usb_kill_urb(dev->bulk_in_urb);
usb_kill_urb(dev->bulk_out_urb);
// 3. 释放 URB 和缓冲区
usb_free_urb(dev->bulk_in_urb);
usb_free_urb(dev->bulk_out_urb);
kfree(dev->bulk_in_buffer);
kfree(dev->bulk_out_buffer);
// 4. 释放设备引用
usb_put_dev(dev->udev);
// 5. 释放私有数据
kfree(dev);
// 6. 清空接口私有数据指针
usb_set_intfdata(interface, NULL);
dev_info(&interface->dev, "My USB device disconnected\n");
}
调试建议:在 disconnect 入口处添加
dev_info() 打印,可以快速确认设备拔出事件是否被正确捕获。如果 disconnect 未被调用,说明驱动注册或引用计数存在问题。
2.4 usb_driver 注册与注销
驱动模块的入口和出口函数负责将 usb_driver 注册到 USB 核心:
static struct usb_driver my_usb_driver = {
.name = "my_usb_driver",
.probe = my_usb_probe,
.disconnect = my_usb_disconnect,
.id_table = my_usb_id_table,
};
static int __init my_usb_init(void)
{
return usb_register(&my_usb_driver);
}
static void __exit my_usb_exit(void)
{
usb_deregister(&my_usb_driver);
}
module_init(my_usb_init);
module_exit(my_usb_exit);
MODULE_LICENSE("GPL");
2.5 驱动框架与设备生命周期 SVG 图
下图展示了从设备插入到 probe 调用,再到设备拔出触发 disconnect 的完整生命周期:
2.6 常见 probe 失败场景与排查
probe 返回负值会导致设备绑定失败。下表列出了最常见的失败原因及对应的错误码:
| 错误码 | 宏定义 | 典型原因 |
|---|---|---|
| -ENOMEM | 12 | 内存分配失败(kzalloc / usb_alloc_urb) |
| -ENODEV | 19 | 设备已断开或接口无效 |
| -EIO | 5 | 读取设备描述符失败或端点配置错误 |
| -EPROTONOSUPPORT | 93 | 不支持的 USB 协议或接口子类 |
| -EBUSY | 16 | 设备或接口已被其他驱动占用 |
调试技巧:在 probe 中每个可能失败的点之后添加
dev_err() 打印,配合 dmesg 可以快速定位失败位置。例如:dev_err(&interface->dev, "Failed to allocate URB: %d\n", ret);
2.7 本章小结
- usb_driver 是 USB 驱动的核心结构体,必须实现 probe 和 disconnect 回调。
- probe 负责设备初始化、资源分配和用户空间接口注册。
- disconnect 负责停止 I/O、释放资源和清理私有数据。
- id_table 决定了驱动能绑定哪些设备,匹配失败则 probe 不会被调用。
- probe 失败时需自行清理已分配资源,内核不会自动回滚。