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 流程包含以下步骤:

  1. 获取接口结构:通过参数 struct usb_interface * 访问设备端点信息。
  2. 分配私有数据结构:使用 kzalloc 分配驱动私有上下文。
  3. 初始化 URB 和缓冲区:为后续的数据传输做准备。
  4. 注册字符设备或输入设备:将设备暴露给用户空间。
  5. 设置接口私有数据:调用 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 的完整生命周期:

USB 驱动 probe/disconnect 生命周期 USB 设备生命周期与驱动回调流程 USB 设备插入 USB 核心枚举 匹配 id_table probe() 调用 分配资源 / 注册设备 设备正常工作 USB 设备拔出 disconnect() 调用 释放资源 / 注销设备 驱动清理完成 说明: probe 和 disconnect 总是成对出现。如果 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 失败时需自行清理已分配资源,内核不会自动回滚。