USB 热插拔

Linux 热插拔

在像 USB (和 Cardbus PCI) 这样的热插拔总线中,最终用户在通电状态下将设备插入总线。 在大多数情况下,用户希望这些设备可以立即使用。 这意味着系统必须做很多事情,包括

  • 找到可以处理该设备的驱动程序。 这可能涉及加载内核模块;较新的驱动程序可以使用 module-init-tools 将其设备(和类)支持发布到用户实用程序。

  • 将驱动程序绑定到该设备。 总线框架使用设备驱动程序的 probe() 例程来完成此操作。

  • 告诉其他子系统配置新设备。 可能需要启用打印队列、启动网络、安装磁盘分区等等。 在某些情况下,这些将是特定于驱动程序的操作。

这涉及内核模式和用户模式操作的混合。 使设备可以立即使用意味着任何用户模式操作都不能等待管理员来完成它们:内核必须被动地触发它们(触发一些监控守护程序来调用帮助程序),或者主动地(直接调用这样的用户模式帮助程序)。

那些触发的操作必须支持系统的管理策略; 这些程序在这里被称为“策略代理”。 通常,它们涉及分派到更熟悉的管理工具的 shell 脚本。

因为其中一些操作依赖于驱动程序的信息(元数据),而这些信息目前仅在动态链接驱动程序时可用,所以当您配置高度模块化的系统时,您会获得最佳的热插拔效果。

内核热插拔助手 (/sbin/hotplug)

有一个内核参数:/proc/sys/kernel/hotplug,它通常保存路径名 /sbin/hotplug。 该参数命名了一个内核可以在不同时间调用的程序。

/sbin/hotplug 程序可以由任何子系统调用,作为其对配置更改的反应的一部分,来自该子系统中的线程。 只需要一个参数:被通知某些内核事件的子系统的名称。 该名称用作进一步事件分派的第一个键;任何其他参数和环境变量参数都由进行调用的子系统指定。

热插拔软件和其他资源可在以下网址获得

邮件列表信息也可在该站点获得。

USB 策略代理

当 USB 设备从系统中添加或删除时,USB 子系统当前会调用 /sbin/hotplug。 调用由内核 hub 工作队列 [hub_wq] 完成,或者作为 root hub 初始化的一部分(由 init、modprobe、kapmd 等完成)。 它的单个命令行参数是字符串“usb”,并且它传递以下环境变量

ACTION

add, remove

PRODUCT

USB 供应商、产品和版本代码(十六进制)

TYPE

设备类代码(十进制)

INTERFACE

接口 0 类代码(十进制)

如果配置了 “usbdevfs”,则还会传递 DEVICE 和 DEVFS。 DEVICE 是设备的路径名,对于具有多个和/或备用接口而使驱动程序选择复杂化的设备非常有用。 按照设计,USB 热插拔独立于 usbdevfs:您可以执行 USB 设备设置的大部分基本部分,而无需使用该文件系统,也无需运行用户模式守护程序来检测系统配置中的更改。

当前可用的策略代理实现可以加载模块的驱动程序,并且可以调用特定于驱动程序的设置脚本。 最新的实现利用了 USB module-init-tools 支持。 后来的代理可能会卸载驱动程序。

USB Modutils 支持

当前版本的 module-init-tools 将创建一个 modules.usbmap 文件,该文件包含来自每个驱动程序的 MODULE_DEVICE_TABLE 的条目。 各种用户模式策略代理可以使用此类文件来确保在启动时或稍后加载所有正确的驱动程序模块。

有关此类表条目的完整信息,请参阅 linux/usb.h; 或查看现有驱动程序。 每个表条目描述了一个或多个标准,用于将驱动程序与设备或设备类别进行匹配。 特定标准由 “match_flags” 中设置的位以及字段值标识。 您可以直接构造标准,也可以使用如下宏,并使用 driver_info 来存储更多信息

USB_DEVICE (vendorId, productId)
    ... matching devices with specified vendor and product ids
USB_DEVICE_VER (vendorId, productId, lo, hi)
    ... like USB_DEVICE with lo <= productversion <= hi
USB_INTERFACE_INFO (class, subclass, protocol)
    ... matching specified interface class info
USB_DEVICE_INFO (class, subclass, protocol)
    ... matching specified device class info

一个简短的示例,对于支持多个特定 USB 设备及其怪癖的驱动程序,可能具有如下的 MODULE_DEVICE_TABLE

static const struct usb_device_id mydriver_id_table[] = {
    { USB_DEVICE (0x9999, 0xaaaa), driver_info: QUIRK_X },
    { USB_DEVICE (0xbbbb, 0x8888), driver_info: QUIRK_Y|QUIRK_Z },
    ...
    { } /* end with an all-zeroes entry */
};
MODULE_DEVICE_TABLE(usb, mydriver_id_table);

大多数 USB 设备驱动程序应将这些表传递给 USB 子系统以及模块管理子系统。 但并非全部:某些驱动程序框架使用分层在 USB 上的接口连接,因此它们不需要这样的 struct usb_driver

直接连接到 USB 子系统的驱动程序应声明如下

static struct usb_driver mydriver = {
    .name           = "mydriver",
    .id_table       = mydriver_id_table,
    .probe          = my_probe,
    .disconnect     = my_disconnect,

    /*
    if using the usb chardev framework:
        .minor              = MY_USB_MINOR_START,
        .fops               = my_file_ops,
    if exposing any operations through usbdevfs:
        .ioctl              = my_ioctl,
    */
};

当 USB 子系统知道驱动程序的设备 ID 表时,它会在选择要 probe() 的驱动程序时使用。 执行新设备处理的线程会根据设备的接口和设备描述符检查驱动程序的设备 ID 条目 MODULE_DEVICE_TABLE。 只有在匹配时才会调用 probe(),并且 probe() 的第三个参数将是匹配的条目。

如果您没有为您的驱动程序提供 id_table,那么可能会为每个新设备 probe 您的驱动程序;probe() 的第三个参数将为 NULL