平台设备和驱动程序 【ChatGPT】

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

平台设备和驱动程序

请参阅<linux/platform_device.h>以获取与平台总线的驱动程序模型接口相关的信息:platform_device和platform_driver。这个伪总线用于连接具有最小基础设施的总线上的设备,例如用于集成许多系统级芯片处理器上的外围设备或一些“传统”的PC互连,而不是像PCI或USB这样的大型正式规范。

平台设备

平台设备通常作为系统中的独立实体出现。这包括传统的基于端口的设备和连接到外围总线的主机桥以及大多数集成到系统级芯片平台的控制器。它们通常具有的共同特点是可以直接从CPU总线进行寻址。很少情况下,平台设备将通过某种其他类型的总线段连接,但其寄存器仍然可以直接寻址。

平台设备被赋予一个名称,用于驱动程序绑定,并且有一个资源列表,例如地址和中断请求(IRQs):

struct platform_device {
      const char      *name;
      u32             id;
      struct device   dev;
      u32             num_resources;
      struct resource *resource;
};

平台驱动程序

平台驱动程序遵循标准的驱动程序模型约定,其中发现/枚举由驱动程序之外的代码处理,驱动程序提供probe()和remove()方法。它们使用标准约定支持电源管理和关机通知:

struct platform_driver {
      int (*probe)(struct platform_device *);
      int (*remove)(struct platform_device *);
      void (*shutdown)(struct platform_device *);
      int (*suspend)(struct platform_device *, pm_message_t state);
      int (*suspend_late)(struct platform_device *, pm_message_t state);
      int (*resume_early)(struct platform_device *);
      int (*resume)(struct platform_device *);
      struct device_driver driver;
};

请注意,probe()通常应验证指定的设备硬件是否实际存在;有时平台设置代码无法确定。探测可以使用设备资源,包括时钟和设备平台数据。

平台驱动程序以正常方式注册自己:

int platform_driver_register(struct platform_driver *drv);

或者,在已知设备不可热插拔的常见情况下,probe()例程可以存在于init部分,以减少驱动程序的运行时内存占用:

int platform_driver_probe(struct platform_driver *drv,
                  int (*probe)(struct platform_device *))

内核模块可以由多个平台驱动程序组成。平台核心提供了注册和注销驱动程序数组的辅助函数:

int __platform_register_drivers(struct platform_driver * const *drivers,
                              unsigned int count, struct module *owner);
void platform_unregister_drivers(struct platform_driver * const *drivers,
                                 unsigned int count);

如果其中一个驱动程序注册失败,之前注册的所有驱动程序将按相反的顺序注销。请注意,有一个方便的宏,将THIS_MODULE作为owner参数传递:

#define platform_register_drivers(drivers, count)

设备枚举

通常,特定于平台(通常也是特定于板级)的设置代码将注册平台设备:

int platform_device_register(struct platform_device *pdev);

int platform_add_devices(struct platform_device **pdevs, int ndev);

一般规则是仅注册实际存在的设备,但在某些情况下可能会注册额外的设备。例如,内核可能配置为与外部网络适配器一起工作,该适配器可能未在所有板上填充,或者与某些板不连接到任何外围设备的集成控制器一起工作。

在某些情况下,引导固件将导出描述在给定板上填充的设备的表。如果没有这样的表,通常系统设置代码设置正确设备的唯一方法是为特定目标板构建内核。这种特定于板级的内核在嵌入式和定制系统开发中很常见。

在许多情况下,与平台设备相关的内存和中断请求(IRQ)资源不足以让设备的驱动程序工作。板级设置代码通常使用设备的platform_data字段提供附加信息。

嵌入式系统通常需要一个或多个平台设备的时钟,这些时钟通常在需要时保持关闭(以节省功耗)。系统设置还将这些时钟与设备关联,以便调用clk_get(&pdev->dev, clock_name)在需要时返回它们。

传统驱动程序:设备探测

某些驱动程序没有完全转换为驱动程序模型,因为它们承担了非驱动程序角色:驱动程序注册其平台设备,而不是留给系统基础设施处理。这样的驱动程序无法进行热插拔或冷插拔,因为这些机制要求设备创建位于与驱动程序不同的系统组件中。

这样做的唯一“好”理由是处理旧的系统设计,这些设计(如原始IBM PC)依赖于容易出错的“探测硬件”模型进行硬件配置。较新的系统在很大程度上放弃了该模型,而是采用总线级别的动态配置支持(PCI、USB)或由引导固件提供的设备表(例如,在x86上的PNPACPI)。关于可能在哪里的选项太多,即使操作系统的有根据的猜测也经常是错误的,从而引发问题。

不鼓励使用这种驱动程序风格。如果要更新此类驱动程序,请尝试将设备枚举移动到更合适的位置,超出驱动程序之外。这通常是清理工作,因为这样的驱动程序通常已经具有“正常”模式,例如使用由PNP或平台设备设置创建的设备节点。

尽管如此,还是有一些API支持这种传统驱动程序。除非使用这些调用与此类无法热插拔的驱动程序一起,否则请避免使用这些调用:

struct platform_device *platform_device_alloc(
                const char *name, int id);

您可以使用platform_device_alloc()动态分配设备,然后使用资源和platform_device_register()进行初始化。通常更好的解决方案是:

struct platform_device *platform_device_register_simple(
                const char *name, int id,
                struct resource *res, unsigned int nres);

您可以使用platform_device_register_simple()作为一步调用来分配和注册设备。

设备命名和驱动程序绑定

platform_device.dev.bus_id 是设备的规范名称。它由两个组件构成:

  • platform_device.name ... 也用于驱动程序匹配。
  • platform_device.id ... 设备实例编号,或者为"-1"表示只有一个。

这两者连接在一起,因此名称/编号 "serial"/0 表示 bus_id 为 "serial.0",而 "serial/3" 表示 bus_id 为 "serial.3";两者都将使用名为 "serial" 的 platform_driver。而 "my_rtc"/-1 将是 bus_id 为 "my_rtc"(没有实例 id),并使用名为 "my_rtc" 的 platform_driver。

驱动程序绑定是由驱动程序核心自动执行的,在设备和驱动程序之间找到匹配后调用驱动程序的 probe()。如果 probe() 成功,驱动程序和设备将像往常一样绑定。有三种不同的方式来找到这样的匹配:

  • 每当注册一个设备时,都会检查该总线的驱动程序是否匹配。平台设备应该在系统引导期间非常早就注册。
  • 当使用 platform_driver_register() 注册驱动程序时,将检查该总线上所有未绑定的设备是否匹配。驱动程序通常在引导期间较晚注册,或者通过模块加载。
  • 使用 platform_driver_probe() 注册驱动程序的方式与使用 platform_driver_register() 类似,只是如果另一个设备注册,该驱动程序将不会后续被探测。(这是可以的,因为此接口仅用于非可热插拔设备。)

早期平台设备和驱动程序

早期平台接口在系统引导期间为平台设备驱动程序提供平台数据。该代码建立在 early_param() 命令行解析之上,并且可以在非常早期执行。

示例:"earlyprintk" 类早期串行控制台的 6 个步骤

1. 注册早期平台设备数据

架构代码使用函数 early_platform_add_devices() 注册平台设备数据。对于早期串行控制台,这应该是串口的硬件配置。在此时注册的设备将稍后与早期平台驱动程序进行匹配。

2. 解析内核命令行

架构代码调用 parse_early_param() 来解析内核命令行。这将执行所有匹配的 early_param() 回调。用户指定的早期平台设备将在此时注册。对于早期串行控制台,用户可以在内核命令行中指定端口为 "earlyprintk=serial.0",其中 "earlyprintk" 是类字符串,"serial" 是平台驱动程序的名称,0 是平台设备 id。如果 id 为 -1,则可以省略点和 id。

3. 安装属于某个类的早期平台驱动程序

架构代码可以选择使用函数 early_platform_driver_register_all() 强制注册所有属于某个类的早期平台驱动程序。步骤 2 中的用户指定设备优先于这些驱动程序。由于早期串行驱动程序代码应该在用户在内核命令行中指定端口之前被禁用,因此此步骤被省略。

4. 注册早期平台驱动程序

使用 early_platform_init() 编译进来的平台驱动程序会在步骤 2 或 3 中自动注册。早期串行驱动程序示例应该使用 early_platform_init("earlyprintk", &platform_driver)。

5. 探测属于某个类的早期平台驱动程序

架构代码调用 early_platform_driver_probe() 来匹配已注册的属于某个类的早期平台设备与已注册的早期平台驱动程序。匹配的设备将被探测。此步骤可以在早期引导的任何时刻执行。对于串口的情况,尽早执行可能是个好主意。

6. 在早期平台驱动程序的 probe() 中

驱动程序代码在早期引导期间需要特别小心,特别是在涉及内存分配和中断注册时。probe() 函数中的代码可以使用 is_early_platform_device() 来检查它是在早期平台设备时调用,还是在常规平台设备时调用。早期串行驱动程序在此时执行 register_console()。

更多信息,请参阅 <linux/platform_device.h>。