设备驱动程序

请参阅 struct device_driver 的内核文档。

分配

设备驱动程序是静态分配的结构。虽然系统中可能有多个驱动程序支持的设备,但 struct device_driver 代表的是整个驱动程序(而不是特定的设备实例)。

初始化

驱动程序必须至少初始化名称和总线字段。它还应该初始化 devclass 字段(当它到达时),以便在内部获得正确的链接。它还应该尽可能多地初始化回调,尽管每个回调都是可选的。

声明

如上所述,struct device_driver 对象是静态分配的。下面是 eepro100 驱动程序的示例声明。此声明仅为假设;它依赖于驱动程序完全转换为新模型

static struct device_driver eepro100_driver = {
       .name          = "eepro100",
       .bus           = &pci_bus_type,

       .probe         = eepro100_probe,
       .remove                = eepro100_remove,
       .suspend               = eepro100_suspend,
       .resume                = eepro100_resume,
};

大多数驱动程序无法完全转换为新模型,因为它们所属的总线具有总线特定的结构,其中包含无法概括的总线特定字段。

最常见的例子是设备 ID 结构。驱动程序通常定义其支持的设备 ID 数组。这些结构的格式和比较设备 ID 的语义完全是总线特定的。将它们定义为总线特定的实体会牺牲类型安全性,因此我们保留了总线特定的结构。

总线特定的驱动程序应在总线特定驱动程序的定义中包含通用的 struct device_driver。像这样

struct pci_driver {
       const struct pci_device_id *id_table;
       struct device_driver     driver;
};

包含总线特定字段的定义看起来像(再次使用 eepro100 驱动程序)

static struct pci_driver eepro100_driver = {
       .id_table       = eepro100_pci_tbl,
       .driver               = {
              .name           = "eepro100",
              .bus            = &pci_bus_type,
              .probe          = eepro100_probe,
              .remove         = eepro100_remove,
              .suspend        = eepro100_suspend,
              .resume         = eepro100_resume,
       },
};

有些人可能会觉得嵌入式结构初始化的语法很笨拙甚至有点丑陋。到目前为止,这是我们找到的实现我们想要的目标的最佳方法...

注册

int driver_register(struct device_driver *drv);

驱动程序在启动时注册该结构。对于没有总线特定字段的驱动程序(即没有总线特定驱动程序结构),它们将使用 driver_register 并传递指向其 struct device_driver 对象的指针。

但是,大多数驱动程序将具有总线特定的结构,并且需要使用诸如 pci_driver_register 之类的东西向总线注册。

重要的是驱动程序应尽早注册其驱动程序结构。向核心注册会初始化 struct device_driver 对象中的几个字段,包括引用计数和锁。这些字段被假定为始终有效,并且可以被设备模型核心或总线驱动程序使用。

过渡总线驱动程序

通过定义包装函数,可以更容易地过渡到新模型。驱动程序可以完全忽略通用结构,而让总线包装器填写字段。对于回调,总线可以定义通用回调,将调用转发到驱动程序的总线特定回调。

此解决方案仅为临时解决方案。为了在驱动程序中获取类信息,无论如何都必须修改驱动程序。由于将驱动程序转换为新模型应减少一些基础设施的复杂性和代码大小,因此建议在添加类信息时转换它们。

访问

一旦对象被注册,它就可以访问该对象的公共字段,例如锁和设备列表

int driver_for_each_dev(struct device_driver *drv, void *data,
                        int (*callback)(struct device *dev, void *data));

devices 字段是已绑定到该驱动程序的所有设备的列表。LDM 核心提供了一个辅助函数来操作驱动程序控制的所有设备。此辅助函数会在每次节点访问时锁定驱动程序,并在访问每个设备时进行适当的引用计数。

sysfs

当注册驱动程序时,会在其总线的目录中创建一个 sysfs 目录。在此目录中,驱动程序可以导出用户空间接口,以全局方式控制驱动程序的操作;例如,切换驱动程序中的调试输出。

此目录的未来功能将是一个 “devices” 目录。此目录将包含指向其支持的设备目录的符号链接。

回调

int     (*probe)        (struct device *dev);

probe() 条目在任务上下文中被调用,总线的 rwsem 被锁定,并且驱动程序部分绑定到设备。驱动程序通常使用 container_of() 将 “dev” 转换为总线特定类型,无论是在 probe() 还是其他例程中。该类型通常提供设备资源数据,例如 pci_dev.resource[] 或 platform_device.resources,这些数据除了 dev->platform_data 外,还用于初始化驱动程序。

此回调拥有将驱动程序绑定到给定设备的驱动程序特定逻辑。这包括验证设备是否存在,它是否是驱动程序可以处理的版本,是否可以分配和初始化驱动程序数据结构,以及是否可以初始化任何硬件。驱动程序通常使用 dev_set_drvdata() 存储指向其状态的指针。当驱动程序已成功将其自身绑定到该设备时,probe() 返回零,并且驱动程序模型代码将完成其将驱动程序绑定到该设备的部分。

驱动程序的 probe() 可能会返回负的 errno 值,以指示该驱动程序未绑定到此设备,在这种情况下,它应该已释放其分配的所有资源。

可选地,如果驱动程序依赖于尚未可用的资源(例如,由尚未初始化的驱动程序提供),则 probe() 可能会返回 -EPROBE_DEFER。驱动程序核心会将设备放入延迟探测列表中,并稍后尝试再次调用它。如果驱动程序必须延迟,则应尽早返回 -EPROBE_DEFER,以减少花费在设置工作上的时间,这些设置工作将在稍后需要解除和重新执行。

警告

如果 probe() 已创建子设备,则不得返回 -EPROBE_DEFER,即使这些子设备在清理路径中再次被删除也是如此。如果在注册子设备后返回 -EPROBE_DEFER,则可能会导致对同一驱动程序进行 .probe() 调用的无限循环。

void    (*sync_state)   (struct device *dev);

sync_state 仅对一个设备调用一次。当设备的所有使用者设备都成功探测后,就会调用它。通过查看将该设备连接到其使用者设备的设备链接来获得该设备的消费者列表。

在 late_initcall_sync() 期间首次尝试调用 sync_state(),以便为固件和驱动程序提供时间将设备彼此链接。在首次尝试调用 sync_state() 期间,如果当时该设备的所有消费者都已成功探测,则会立即调用 sync_state()。如果在首次尝试期间该设备没有消费者,则也将其视为 “该设备的所有消费者都已探测”,并且会立即调用 sync_state()。

如果在首次尝试调用设备的 sync_state() 期间,仍然有尚未成功探测的消费者,则会推迟 sync_state() 调用,并且只有当一个或多个设备的消费者成功探测后才会在将来重新尝试。如果在重新尝试期间,驱动程序核心发现该设备有一个或多个尚未探测的消费者,则会再次推迟 sync_state() 调用。

sync_state() 的典型用例是让内核干净地从引导加载程序接管设备管理。例如,如果引导加载程序将设备保持开启状态并处于特定的硬件配置,则设备的驱动程序可能需要将设备保持在引导配置中,直到该设备的所有消费者都已探测。一旦该设备的所有消费者都已探测,设备的驱动程序就可以同步设备的硬件状态,以匹配所有消费者请求的聚合软件状态。因此得名 sync_state()。

虽然可以从 sync_state() 中受益的资源的明显示例包括诸如稳压器之类的资源,但 sync_state() 对于诸如 IOMMU 之类的复杂资源也很有用。例如,具有多个消费者(其地址被 IOMMU 重新映射的设备)的 IOMMU 可能需要将其映射固定在(或添加到)引导配置,直到其所有消费者都已探测。

虽然 sync_state() 的典型用例是让内核干净地从引导加载程序接管设备管理,但 sync_state() 的使用并不限于此。只要在设备的所有消费者都探测后执行操作有意义,就可以使用它

int     (*remove)       (struct device *dev);

remove 被调用以从设备取消绑定驱动程序。如果设备从系统中物理移除、如果正在卸载驱动程序模块、在重新启动序列期间或其他情况下,可能会调用此方法。

由驱动程序决定设备是否存在。它应该释放专门为设备分配的任何资源;即设备 driver_data 字段中的任何内容。

如果设备仍然存在,它应该使设备静止并将其置于受支持的低功耗状态。

int     (*suspend)      (struct device *dev, pm_message_t state);

调用 suspend 是为了将设备置于低功耗状态。

int     (*resume)       (struct device *dev);

Resume 用于将设备从低功耗状态唤醒。

属性

struct driver_attribute {
        struct attribute        attr;
        ssize_t (*show)(struct device_driver *driver, char *buf);
        ssize_t (*store)(struct device_driver *, const char *buf, size_t count);
};

设备驱动程序可以通过它们的 sysfs 目录导出属性。驱动程序可以使用 DRIVER_ATTR_RW 和 DRIVER_ATTR_RO 宏来声明属性,这两个宏的工作方式与 DEVICE_ATTR_RW 和 DEVICE_ATTR_RO 宏完全相同。

示例

DRIVER_ATTR_RW(debug);

这等同于声明

struct driver_attribute driver_attr_debug;

然后可以使用它来使用以下方式在驱动程序的目录中添加和删除属性

int driver_create_file(struct device_driver *, const struct driver_attribute *);
void driver_remove_file(struct device_driver *, const struct driver_attribute *);