设备驱动¶
请参阅 struct device_driver
的 kerneldoc。
分配¶
设备驱动程序是静态分配的结构。尽管一个系统中可能有多个设备由一个驱动程序支持,但 struct device_driver
代表的是整个驱动程序(而不是特定的设备实例)。
初始化¶
驱动程序必须至少初始化 name 和 bus 字段。 它还应该初始化 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 仅针对设备调用一次。 当设备的所有消费者设备都已成功探测时,将调用它。 设备的消费者列表是通过查看将该设备连接到其消费者设备的设备链接来获得的。
首次尝试调用 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() 对于像 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 *);