平台设备和驱动程序

有关平台总线的驱动程序模型接口,请参阅 <linux/platform_device.h>:platform_device 和 platform_driver。此伪总线用于连接基础设施最少的总线上的设备,例如用于在许多片上系统处理器上集成外围设备的设备,或一些“旧式”PC互连;而不是像 PCI 或 USB 这样的大型正式指定的互连。

平台设备

平台设备通常以自主实体形式出现在系统中。这包括旧式的基于端口的设备和外围总线的主桥,以及集成到片上系统平台中的大多数控制器。它们通常的共同点是直接从 CPU 总线寻址。极少数情况下,platform_device 会通过某种其他总线的一段连接;但其寄存器仍然可以直接寻址。

平台设备被赋予一个名称(用于驱动程序绑定)和一个资源列表,例如地址和 IRQ。

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 *);
      void (*remove)(struct platform_device *);
      void (*shutdown)(struct platform_device *);
      int (*suspend)(struct platform_device *, pm_message_t state);
      int (*resume)(struct platform_device *);
      struct device_driver driver;
      const struct platform_device_id *id_table;
      bool prevent_deferred_probe;
      bool driver_managed_dma;
};

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

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

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 作为所有者参数传递

#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”表示只有一个。

它们是连接在一起的,因此名称/id “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() 命令行解析之上,并且可以在非常早的时候执行。

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

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() 将与特定类别关联的已注册早期平台设备与已注册的早期平台驱动程序匹配。匹配的设备将被探测(probe())。此步骤可以在早期启动期间的任何时间执行。对于串行端口情况,越早越好。

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

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

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