设备驱动程序 【ChatGPT】

发布时间 2023-12-10 13:21:19作者: 摩斯电码

设备驱动程序

请参阅结构体device_driver的kerneldoc。

分配

设备驱动程序是静态分配的结构。尽管系统中可能有多个驱动程序支持的设备,但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目录。在此目录中,驱动程序可以导出一个接口到用户空间,以全局控制驱动程序的操作;例如,在驱动程序中切换调试输出。

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

回调

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

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

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

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

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

警告
如果在创建子设备后返回-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 *);