[TOC]

drivers/base/platform.c 平台设备模型(Platform Device Model) 描述SoC内部设备

历史与背景

这项技术是为了解决什么特定问题而诞生的?

drivers/base/platform.c 文件实现了Linux内核中的“平台设备模型”(Platform Device Model)。这项技术是为了解决嵌入式系统(特别是基于ARM等SoC的设备)中如何描述和管理那些不通过标准总线(如PCI, USB, I2C, SPI)枚举,而是直接由系统集成商(SoC厂商)通过内存映射地址或其他固定方式连接到CPU总线上的设备的问题。

在没有平台设备模型之前,这些SoC内部的设备(如GPIO控制器、UART控制器、时钟控制器、中断控制器、DMA控制器等)的驱动程序通常是零散地被编写的,并且管理方式非常底层和不统一。开发者需要手动解析设备树(Device Tree)或其他平台数据,获取设备的基地址、长度、中断号等信息,然后手动映射内存、申请中断,并将这些信息传递给驱动。这种方式存在以下问题:

  • 代码冗余:每种类型的设备驱动都需要重复实现获取和解析设备信息的代码。
  • 缺乏统一性:驱动程序没有一个通用的接口来描述自己支持的设备,也无法通过设备模型进行统一管理。
  • 不易于管理:对这些设备的生命周期管理(如probe, remove, suspend, resume)缺乏统一的框架。

平台设备模型通过引入 struct platform_devicestruct platform_driver,以及一个总线类型(”platform” bus),提供了一个标准化的接口来描述这些设备,并将它们纳入到Linux统一的设备模型和驱动核心框架中进行管理。

它的发展经历了哪些重要的里程碑或版本迭代?

平台设备模型的发展与Linux在嵌入式领域的应用和设备树(Device Tree)的普及密切相关。

  • 早期(Linux 2.5/2.6):随着Linux内核向嵌入式领域扩展,需要一种方式来描述SoC上的设备。最初可能是一些基于ACPI或主板特定描述文件的方法,但对于ARM等平台,更通用的方式是基于设备树。
  • 设备树(Device Tree)的普及:在ARM架构中,设备树(DT)成为描述硬件配置的标准方式。DT通过.dts(Device Tree Source)文件描述了硬件拓扑、设备属性、资源(内存基地址、中断号、GPIO等)。
  • 平台设备模型与DT的结合:平台设备模型成为解析DT中描述的“平台设备”信息,并将其转换为内核可以识别和管理的 struct platform_device 的关键机制。DT核心会解析DT节点,为每个符合条件的节点创建一个 struct platform_device 实例。
  • 统一驱动模型集成:平台设备模型本身是Linux统一设备模型的一部分。它通过为 struct platform_driver 定义标准的 .probe, .remove, .suspend, .resume 等回调函数,并将其注册到 platform_bus_type,使得平台设备能够与驱动核心的其他部分无缝集成,享受通用驱动管理带来的好处。
  • ACPI支持:在x86等使用ACPI的平台上,ACPI表也能被解析,从中提取平台设备信息,创建 struct platform_device

目前该技术的社区活跃度和主流应用情况如何?

平台设备模型是Linux内核中一个非常成熟、稳定且极其活跃的核心子系统。它几乎是所有SoC(如ARM Cortex-A系列、RISC-V等)内部设备驱动(如CPU内部的UART、SPI控制器、I2C控制器、GPIO控制器、定时器、中断控制器、显示控制器、内存控制器等)的基础。任何为嵌入式系统编写的驱动,只要其设备信息是通过设备树或ACPI描述的,几乎都会使用平台设备模型来注册和管理。

核心原理与设计

它的核心工作原理是什么?

平台设备模型的核心是将硬件设备的描述(通常来自设备树或ACPI)转换为内核可管理的“平台设备”和“平台驱动”配对。

  1. 设备树(或ACPI)解析

    • 当内核启动并初始化设备树(或ACPI)时,DT/ACPI解析器会遍历硬件描述。
    • 对于那些被描述为“平台设备”的节点(通常通过 compatible 属性来识别,如 "vendor,soc-uart"),解析器会创建一个 struct platform_device 实例。
    • 这个 struct platform_device 会包含该设备的所有信息,如:
      • 设备名(通常来自DT节点名或compatible属性)。
      • 资源列表(Memory regions, I/O ports, IRQs, GPIOs等)。
      • 平台数据(void *platform_data,存储特定于该设备的、驱动需要的数据)。
      • I/O资源描述。
  2. 平台设备注册

    • DT/ACPI解析完成后,会将所有创建的 struct platform_device 实例注册到内核的平台总线(platform_bus_type)上。
    • 注册过程会触发驱动核心(drivers/base/driver.c)尝试将该设备与已注册的、支持它的 platform_driver 进行匹配。
  3. 平台驱动注册

    • 驱动开发者定义一个 struct platform_driver 结构体,其中包含:
      • 驱动名。
      • 匹配函数(.match)或 compatible 字符串数组(在DT环境中,平台总线会根据DT节点的compatible属性自动匹配)。
      • 标准的驱动回调函数:.probe (初始化), .remove (卸载), .suspend (休眠), .resume (唤醒) 等。
    • 驱动开发者通过 platform_driver_register() 将其驱动注册到内核。
  4. 设备-驱动绑定

    • 当一个 platform_device 被注册到平台总线,并且系统中有与之匹配的 platform_driver 存在时,驱动核心会调用该驱动的 .probe() 函数。
    • .probe() 函数中,驱动可以安全地访问设备的所有资源(如通过 platform_get_resource() 获取内存地址、中断号等),并进行初始化。
  5. 生命周期管理

    • 驱动的 .remove() 函数会在设备被移除(如DT节点被移除,或执行 driver_unregister() 的逆操作)时被调用。
    • .suspend().resume() 回调函数会在系统执行休眠和唤醒时被内核电源管理子系统调用。

它的主要优势体现在哪些方面?

  • 标准化:为SoC内部设备提供了一个统一的描述和管理框架,无论其具体硬件功能是什么。
  • 解耦:将硬件描述(DT/ACPI)与驱动逻辑完全分离。驱动程序不需要关心硬件是如何被具体描述的,只需要知道如何解析平台设备提供的资源。
  • 自动化匹配:通过 compatible 字符串等机制,实现驱动与设备的自动匹配,无需手动干预。
  • 集成到通用设备模型:平台设备和驱动可以无缝地接入Linux的设备模型,享受设备模型提供的所有通用服务,如sysfs接口、电源管理、设备资源管理(devres)等。
  • 简化驱动开发:驱动开发者只需要实现特定于硬件的逻辑,而资源的获取、设备的匹配、生命周期管理等通用工作由平台层和驱动核心完成。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

drivers/base/platform.c 作为平台设备模型的核心,其设计非常健壮。其“劣势”更多体现在它的设计哲学限制上:

  • 仅适用于特定类型的设备:平台设备模型主要用于描述那些不通过标准总线枚举的、直接连接到CPU总线上的设备。它不适用于通过PCIe、USB、I2C、SPI等标准总线发现的设备(这些设备有各自的总线驱动模型)。
  • 依赖于固件描述:平台设备信息(资源、匹配信息)通常需要从设备树或ACPI等硬件描述语言中解析而来。如果硬件描述不完整或不正确,那么平台设备及其驱动的初始化就会失败。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

平台设备模型是处理SoC内部集成硬件设备驱动的标准和唯一解决方案。

  • 场景一:ARM SoC的UART控制器
    一个ARM Cortex-A SoC包含几个内置的UART控制器。在设备树源文件(.dts)中,会为每个UART控制器节点定义其内存基地址、中断号、时钟源以及 compatible 属性(如 "vendor,soc-uart-v1")。内核启动时,DT解析器会为每个UART创建一个 struct platform_device。然后,drivers/uart/uart_ 8250_native.c 或其他UART驱动的 platform_driver 会根据 compatible 字符串进行匹配,调用其 .probe() 函数来初始化UART硬件。
  • 场景二:GPIO控制器驱动
    SoC集成的GPIO控制器,其操作寄存器和中断通常是直接映射到CPU地址空间的。设备树中会描述GPIO控制器的资源(内存映射的寄存器地址、中断号)以及它所能控制的GPIO引脚的配置信息。内核的DT驱动会为GPIO控制器创建一个 struct platform_device,然后drivers/gpio/gpio- 6button.c或相关GPIO库驱动的 platform_driver 会匹配并注册,以便内核的其他部分可以通过GPIO子系统来访问这些引脚。
  • 场景三:CPU内部定时器和中断控制器
    系统时钟、高精度定时器(HPET)、高级可编程中断控制器(APIC)等核心硬件,通常也通过平台设备模型来描述和驱动。

是否有不推荐使用该技术的场景?为什么?

  • 总线枚举设备:对于通过PCIe总线发现的网卡、USB控制器,或通过USB总线发现的USB设备,它们有各自更适合的驱动模型(PCI驱动模型、USB驱动模型)。虽然理论上也可以将它们抽象成平台设备,但这会失去总线特有的信息(如PCI Vendor ID/Device ID),并且增加了不必要的复杂性。
  • 纯软件虚拟设备:对于完全由软件模拟产生的虚拟设备(例如,在drivers/base/faux.c中介绍的Faux Bus所管理的设备),使用平台设备模型是“滥用”,因为它提供了远超所需的匹配和资源管理功能。

对比分析

请将其 与 其他相似技术 进行详细对比。

平台设备模型的主要对比对象是其他Linux内核中的总线驱动模型,特别是PCI和USB,以及与硬件描述相关的设备树。

特性 Platform Device Model (platform.c) PCI Device Model (drivers/pci/*) USB Device Model (drivers/usb/*) Device Tree (DT)
设计用途 描述SoC内部、非标准总线枚举的设备,通常由DT/ACPI描述。 描述通过PCI总线连接的标准外设。 描述通过USB总线连接的标准外设。 描述硬件的拓扑结构、资源分配和设备属性,作为所有总线模型的输入。
硬件发现 间接:通过DT/ACPI解析,非总线枚举。 直接:PCI总线进行枚举,识别Vendor/Device ID。 直接:USB总线进行枚举,识别Vendor/Product ID和Class。 提供描述:DT文件定义了硬件,内核根据DT创建设备。
驱动匹配 间接:DT/ACPI的compatible字符串、设备名。 直接:PCI Vendor/Device ID、Subsystem ID、Class Code。 直接:USB Vendor/Product ID、Class/SubClass/Protocol。 首要信息:DT的compatible字符串是设备识别的关键。
核心实体 struct platform_device, struct platform_driver struct pci_dev, struct pci_driver struct usb_device, struct usb_driver struct device_node, struct property
资源获取 platform_get_resource(), platform_get_irq(), devm_platform_get_resource() pci_request_regions(), pci_ioremap_bar(), pci_enable_msi() usb_get_intf(), usb_alloc_coherent() 由DT节点中的reg, interrupts, gpios, clocks等属性描述,并由相应总线驱动解析。
生命周期管理 集成到统一设备模型,由driver.c协调Probe/Remove/Suspend/Resume。 集成到统一设备模型,有专门的PCI电源管理。 集成到统一设备模型,有专门的USB电源管理。 提供硬件描述,但不直接管理驱动生命周期,由总线驱动负责。

platform_bus: 代表/sys/devices/platform的根设备

这个结构体定义了一个虚拟的根设备。它本身不对应任何具体的硬件,其唯一的作用是在sysfs文件系统中创建一个名为/sys/devices/platform的目录,作为所有平台设备的父容器,将它们组织在一起。

1
2
3
4
5
6
7
8
9
10
11
/*
* 定义一个名为 platform_bus 的 'struct device' 变量.
* 它代表一个设备实例.
*/
struct device platform_bus = {
/*
* .init_name 是设备在初始化时使用的名称. 内核驱动核心会使用这个名字
* 来创建设备节点. 这行代码将导致在sysfs中创建 /sys/devices/platform 目录.
*/
.init_name = "platform",
};

platform_dev_group: 定义平台设备在sysfs中的属性文件组

这些结构体和宏定义了当一个平台设备被注册后,在其sysfs目录中应该出现哪些属性文件。is_visible回调函数还可以根据具体设备的情况动态决定某个文件是否应该显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* 定义一个静态常量属性组 platform_dev_group. 'static' 关键字使其仅在当前文件内可见.
* 'const' 表示其内容在编译后不可更改.
*/
static const struct attribute_group platform_dev_group = {
/*
* .attrs 指向一个 'struct attribute' 数组(此处为 platform_dev_attrs, 未在代码片段中显示).
* 该数组定义了多个属性文件, 例如 "driver_override", "modalias" 等.
*/
.attrs = platform_dev_attrs,
/*
* .is_visible 指向一个回调函数 (platform_dev_attrs_visible, 未显示).
* 内核在创建每个属性文件之前会调用此函数, 以判断该文件对于当前设备是否应该可见.
*/
.is_visible = platform_dev_attrs_visible,
};

/*
* __ATTRIBUTE_GROUPS 是一个辅助宏, 它创建一个名为 platform_dev_groups 的
* 'struct attribute_group' 指针数组, 并将 platform_dev_group 的地址作为其唯一的成员.
* 这样做是为了让 bus_type 结构体能够方便地引用一个或多个属性组.
*/
__ATTRIBUTE_GROUPS(platform_dev);

platform_dev_pm_ops: 定义平台总线的电源管理回调函数

这个结构体定义了平台设备默认的电源管理操作。当系统进入休眠或设备进入运行时挂起状态时,内核会调用这些函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* 定义一个静态常量 'struct dev_pm_ops' 变量, 用于描述电源管理操作.
*/
static const struct dev_pm_ops platform_dev_pm_ops = {
/*
* SET_RUNTIME_PM_OPS 是一个宏, 用于设置运行时电源管理的回调函数.
* 当一个设备长时间未使用时, 内核可以将其置于低功耗状态.
* pm_generic_runtime_suspend: 内核提供的通用运行时挂起函数.
* pm_generic_runtime_resume: 内核提供的通用运行时恢复函数.
* NULL: 表示没有特定的运行时空闲回调.
*/
SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend, pm_generic_runtime_resume, NULL)
/*
* USE_PLATFORM_PM_SLEEP_OPS 是一个宏, 它会包含系统级休眠(suspend-to-RAM/disk)相关的
* 多个回调函数, 如 .suspend, .resume, .freeze, .thaw 等.
* 这些函数最终会调用具体设备驱动中实现的电源管理方法.
*/
USE_PLATFORM_PM_SLEEP_OPS
};

这组函数共同定义了平台总线(Platform Bus)的核心行为,它们作为platform_bus_type结构体的回调函数,构成了Linux内核驱动模型中设备与驱动交互的完整生命周期。对于STM32H750这样的嵌入式系统,平台总线是管理其所有片上外设(如SPI、I2C、UART、DMA等)的基础。这些回调函数确保了当一个在设备树中定义的设备被注册时,内核能够正确地为其找到并加载驱动、配置所需资源(时钟、DMA、电源)、并在需要时安全地移除它。

platform_match: 匹配平台设备与平台驱动

此函数是平台总线的核心匹配逻辑。每当一个新设备或新驱动被注册到平台总线时,内核就会调用此函数来判断它们是否“匹配”。只有匹配成功,后续的probe(探测)过程才会被触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
* platform_match - 将平台设备与平台驱动绑定.
* @dev: 需要匹配的设备.
* @drv: 需要匹配的驱动.
*
* 平台设备ID被假定为按以下方式编码:
* "<名称><实例号>", 其中 <名称> 是设备类型的简短描述,
* 如 "pci" 或 "floppy", 而 <实例号> 是设备的枚举实例号, 如 '0' 或 '42'.
* 驱动ID则仅仅是 "<名称>".
* 因此, 从 platform_device 结构体中提取 <名称>,
* 并将其与驱动的名称进行比较. 返回它们是否匹配.
* (注意: 此注释描述的是最古老的基于名称的匹配方式, 现代内核的匹配逻辑更复杂).
*/
static int platform_match(struct device *dev, const struct device_driver *drv)
{
/*
* to_platform_device 是一个宏, 用于从通用的 struct device 指针安全地转换为
* struct platform_device 指针.
*/
struct platform_device *pdev = to_platform_device(dev);
/*
* to_platform_driver 是一个宏, 用于从通用的 struct device_driver 指针安全地转换为
* struct platform_driver 指针.
*/
struct platform_driver *pdrv = to_platform_driver(drv);

/*
* 优先级1: 检查 'driver_override'. 这是一个可以通过sysfs设置的特殊属性,
* 用于强制一个设备绑定到指定的驱动, 主要用于调试.
*/
if (pdev->driver_override)
/*
* 如果设置了 driver_override, 则只比较它的值和驱动的名称.
* strcmp 返回0表示字符串相等, !0 为真, 表示匹配成功.
*/
return !strcmp(pdev->driver_override, drv->name);

/*
* 优先级2: 尝试基于设备树(Open Firmware)风格的匹配.
* 这对于 STM32H750 来说是最主要和最常用的匹配方式.
*/
if (of_driver_match_device(dev, drv))
/*
* of_driver_match_device 会比较设备树节点中的 "compatible" 属性
* 和驱动的 of_match_table 列表. 如果找到匹配项, 返回1.
*/
return 1;

/*
* 优先级3: 尝试基于 ACPI 风格的匹配.
* ACPI 主要用于x86架构的PC系统, 在STM32嵌入式系统上通常不使用.
*/
if (acpi_driver_match_device(dev, drv))
return 1;

/*
* 优先级4: 尝试根据驱动提供的 ID 表进行匹配.
*/
if (pdrv->id_table)
/*
* platform_match_id 会遍历驱动的 id_table, 将表中的每一项名称
* 与设备的名称进行比较. 如果找到匹配项, 返回非NULL指针, 表示匹配成功.
*/
return platform_match_id(pdrv->id_table, pdev) != NULL;

/*
* 优先级5: 回退到最基本的驱动名称与设备名称匹配.
* 这是最初始的匹配方法.
*/
return (strcmp(pdev->name, drv->name) == 0);
}

platform_uevent: 为平台设备生成uevent事件

当平台设备被注册或移除时,此函数被调用以生成一个uevent。用户空间的udev守护进程会监听这些事件,并根据事件内容(主要是MODALIAS)自动加载或卸载对应的驱动程序内核模块(.ko文件)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
* @dev: 产生事件的设备.
* @env: 一个用于构建uevent环境变量的缓冲区.
*/
static int platform_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const struct platform_device *pdev = to_platform_device(dev);
int rc;

/*
* 优先尝试生成基于设备树风格的 MODALIAS.
* 对于STM32设备, 这会生成一个类似 "of:N<节点名>C<compatible属性>" 的别名.
* 这是现代系统中最精确的模块自动加载方式.
*/
rc = of_device_uevent_modalias(dev, env);
/*
* 如果 of_device_uevent_modalias 成功或返回了除 -ENODEV (非设备树设备) 之外的错误,
* 则直接返回它的结果.
*/
if (rc != -ENODEV)
return rc;

/*
* 尝试生成基于ACPI风格的 MODALIAS. 在STM32上此函数同样会返回 -ENODEV.
*/
rc = acpi_device_uevent_modalias(dev, env);
if (rc != -ENODEV)
return rc;

/*
* 如果以上方法都失败, 则生成一个基于名称的传统 MODALIAS.
* PLATFORM_MODULE_PREFIX 通常是 "platform:".
* 例如, 对于名为 "stm32-spi" 的设备, 会生成 "MODALIAS=platform:stm32-spi".
*/
add_uevent_var(env, "MODALIAS=%s%s", PLATFORM_MODULE_PREFIX,
pdev->name);
return 0;
}

platform_probe: 探测并初始化平台设备

platform_match成功后,内核驱动核心会调用此函数。它作为总线层和具体驱动之间的桥梁,负责执行一些通用的准备工作(如配置时钟和电源域),然后调用具体驱动程序提供的probe函数,以完成硬件的最终初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*
* @dev: 将要被探测的设备 (此时 dev->driver 已经指向匹配的驱动).
*/
static int platform_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
int ret;

/*
* 一个通过 platform_driver_probe() 注册的驱动不能被再次绑定,
* 因为它的 probe 函数通常位于 __init 代码段中, 在启动后就被释放了.
* 对于这类驱动, .probe 会在 __platform_driver_probe() 中被设置为 platform_probe_fail.
* 为了匹配传统行为, 我们甚至不为它们准备时钟和电源域.
*/
if (unlikely(drv->probe == platform_probe_fail))
return -ENXIO;

/*
* 从设备的设备树节点中读取时钟信息并进行默认设置(例如, 使能时钟).
* 对于STM32上的外设, 这是至关重要的一步, 否则外设将无法工作.
*/
ret = of_clk_set_defaults(_dev->of_node, false);
if (ret < 0)
return ret;

/*
* 将设备附加到其电源管理域(Power Management Domain).
* 这使得可以通过电源域统一管理一组设备的电源状态.
*/
ret = dev_pm_domain_attach(_dev, true);
if (ret)
goto out; // 如果附加失败, 直接跳转到出口.

/*
* 检查驱动是否提供了 probe 函数.
*/
if (drv->probe) {
/*
* 调用驱动自己的 probe 函数, 传入平台设备指针.
* 这是驱动对硬件进行初始化的地方(如请求内存, 映射寄存器, 请求中断等).
*/
ret = drv->probe(dev);
if (ret)
/*
* 如果驱动的probe失败了, 必须撤销之前的电源域附加操作.
*/
dev_pm_domain_detach(_dev, true);
}

out:
/*
* 处理"延迟探测"(deferred probe)的情况. 如果驱动的probe返回-EPROBE_DEFER,
* 表示该设备依赖的某个其他资源尚未就绪, 需要稍后重试.
* 但如果驱动设置了 prevent_deferred_probe 标志, 则表明它不支持延迟探测.
*/
if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
dev_warn(_dev, "probe deferral not supported\n"); // 打印一个警告.
ret = -ENXIO; // 将错误码转换为一个更明确的"无此设备或地址"错误.
}

return ret;
}

platform_remove, platform_shutdown: 移除和关闭平台设备

这两个函数分别在设备被移除(例如,驱动模块被卸载)或系统关机时被调用。它们负责调用具体驱动中的对应函数,以执行反初始化和清理工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static void platform_remove(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);

/* 如果驱动提供了 remove 函数, 则调用它来释放资源. */
if (drv->remove)
drv->remove(dev);
/* 将设备从其电源域分离. */
dev_pm_domain_detach(_dev, true);
}

static void platform_shutdown(struct device *_dev)
{
struct platform_device *dev = to_platform_device(_dev);
struct platform_driver *drv;

/* 如果设备没有绑定驱动, 则无事可做. */
if (!_dev->driver)
return;

drv = to_platform_driver(_dev->driver);
/* 如果驱动提供了 shutdown 函数, 则在系统关机时调用它来将硬件置于静默状态. */
if (drv->shutdown)
drv->shutdown(dev);
}

platform_dma_configure, platform_dma_cleanup: 配置和清理DMA

这两个函数是为设备配置DMA(直接内存访问)的关键钩子。对于STM32H750上的高性能外设,DMA是必不可少的。platform_dma_configure在设备探测期间被调用,负责解析设备树中的DMA相关属性,并配置好DMA通道和IOMMU(如果存在)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static int platform_dma_configure(struct device *dev)
{
struct device_driver *drv = READ_ONCE(dev->driver);
struct fwnode_handle *fwnode = dev_fwnode(dev);
enum dev_dma_attr attr;
int ret = 0;

/* 检查固件节点是否是设备树节点. */
if (is_of_node(fwnode)) {
/*
* 调用 of_dma_configure, 它会解析设备树节点中的 "dmas" 和 "dma-names" 属性,
* 并为该设备配置好DMA.
*/
ret = of_dma_configure(dev, to_of_node(fwnode), true);
} else if (is_acpi_device_node(fwnode)) { // 对于ACPI系统
attr = acpi_get_dma_attr(to_acpi_device_node(fwnode));
ret = acpi_dma_configure(dev, attr);
}

/* 在IOMMU层调用时, dev->driver可能无效 */
if (ret || !drv || to_platform_driver(drv)->driver_managed_dma)
return ret;

/* 对于没有被IOMMU特殊管理的设备, 尝试使用默认的IOMMU保护域. */
ret = iommu_device_use_default_domain(dev);
if (ret)
/* 如果失败, 撤销架构相关的DMA操作设置. */
arch_teardown_dma_ops(dev);

return ret;
}

static void platform_dma_cleanup(struct device *dev)
{
struct platform_driver *drv = to_platform_driver(dev->driver);

/* 如果DMA不是由驱动自己管理的, 则将设备从默认的IOMMU域中解除. */
if (!drv->driver_managed_dma)
iommu_device_unuse_default_domain(dev);
}

platform_bus_type: 定义平台总线的核心行为和属性

这是平台总线模型中最重要的结构体。它定义了总线的所有行为,如如何匹配设备和驱动,如何在匹配成功后执行探测(probe),以及总线在sysfs中的呈现方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
* 定义一个常量 'struct bus_type' 变量 platform_bus_type.
* 它描述了一个总线类型的所有特征和操作.
*/
const struct bus_type platform_bus_type = {
/*
* .name 是总线的名称. 这将导致在sysfs中创建 /sys/bus/platform 目录.
*/
.name = "platform",
/*
* .dev_groups 指向上面定义的属性组数组. 这使得在每个平台设备的sysfs目录下
* 都会创建 platform_dev_group 中定义的属性文件.
*/
.dev_groups = platform_dev_groups,
/*
* .match 指向一个回调函数(platform_match), 这是总线匹配的核心. 当有新的平台设备或
* 平台驱动注册时, 内核会调用此函数来判断它们是否匹配.
*/
.match = platform_match,
/*
* .uevent 指向一个回调函数(platform_uevent), 用于生成uevent事件. 用户空间的udev等
* 程序会监听这些事件, 以便在设备出现时自动加载对应的驱动模块.
*/
.uevent = platform_uevent,
/*
* .probe 指向一个回调函数(platform_probe), 当.match匹配成功后, 内核会调用它.
* 这个函数会进一步调用具体驱动程序中实现的 probe 函数, 以完成设备的初始化.
*/
.probe = platform_probe,
/*
* .remove 指向一个回调函数(platform_remove), 当设备被移除或驱动被卸载时调用.
* 它会进一步调用驱动的 remove 函数.
*/
.remove = platform_remove,
/*
* .shutdown 指向一个回调函数(platform_shutdown), 在系统关机时为每个设备调用.
*/
.shutdown = platform_shutdown,
/*
* .dma_configure 指向一个回调函数, 用于配置设备的DMA能力.
* 在像STM32这样的SoC上, 这是配置外设DMA的关键步骤.
*/
.dma_configure = platform_dma_configure,
/*
* .dma_cleanup 指向一个回调函数, 在设备释放时清理DMA配置.
*/
.dma_cleanup = platform_dma_cleanup,
/*
* .pm 指向上面定义的电源管理操作结构体, 将这些操作与总线关联起来.
*/
.pm = &platform_dev_pm_ops,
};
/*
* 将 platform_bus_type 的符号导出, 以便其他GPL许可证的内核模块可以引用它.
*/
EXPORT_SYMBOL_GPL(platform_bus_type);
/*
* 将 platform_bus 的符号导出, 以便其他模块可以将其作为父设备.
*/
EXPORT_SYMBOL_GPL(platform_bus);

platform_bus_init: 注册平台总线和根设备

这个函数在内核启动过程中被调用,它执行了将平台总线注册到内核驱动核心的实际操作。__init属性告诉编译器将此函数放在特殊的初始化段中,在启动完成后,这段代码所占用的内存会被释放,这对于内存有限的嵌入式系统来说非常重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void __weak __init early_platform_cleanup(void) { }

/*
* 函数 platform_bus_init
* '__init' 标记表示这是一个内核初始化函数.
* @return: 成功时返回0, 失败时返回负的错误码.
*/
int __init platform_bus_init(void)
{
/*
* 定义一个整型变量 error, 用于存储函数调用的返回值.
*/
int error;

/*
* 调用 early_platform_cleanup(). 这个函数用于清理在系统非常早期的启动阶段
* (在完整总线模型建立前) 可能已经注册的一些平台设备.
*/
early_platform_cleanup();

/*
* 调用 device_register() 注册上面定义的 platform_bus 设备.
* 这一步会在sysfs中创建 /sys/devices/platform 目录.
*/
error = device_register(&platform_bus);
/*
* 检查注册是否成功.
*/
if (error) {
/*
* 如果失败, 调用 put_device() 减少设备的引用计数, 以便正确地清理它.
*/
put_device(&platform_bus);
/*
* 返回错误码.
*/
return error;
}
/*
* 调用 bus_register() 注册上面定义的 platform_bus_type 总线类型.
* 这一步会在sysfs中创建 /sys/bus/platform 目录, 并使总线的匹配、探测等
* 机制生效.
*/
error = bus_register(&platform_bus_type);
/*
* 检查注册是否成功.
*/
if (error)
/*
* 如果注册总线失败, 则必须注销之前成功注册的设备, 以保持系统状态的一致性.
*/
device_unregister(&platform_bus);

/*
* 返回最终的执行结果.
*/
return error;
}

platform_driver_register 向内核注册一个平台驱动程序

此代码定义了 Linux 内核中的平台总线类型 (platform_bus_type) 并提供了平台驱动注册函数 (platform_driver_register)。其核心作用是为非自发现(non-discoverable)的、基于内存地址映射的硬件设备, 提供一套标准的驱动注册与设备绑定机制。

原理与作用: 平台总线——管理非自发现设备

  1. 什么是总线 (Bus)? 在Linux驱动模型中, “总线”是一种软件概念, 用于连接设备和驱动。对于像USB或PCI这样的物理总线, 设备可以自发现 (self-discovering)。当你插入一个USB设备时, 它会主动向主机报告自己的ID, 内核根据这个ID去查找并加载匹配的驱动。

  2. 什么是平台总线 (Platform Bus)? 在像STM32H750这样的嵌入式系统中, 大部分外设 (如UART、I2C、SPI、定时器、GPIO控制器) 都不是自发现的。它们“固定地”存在于芯片的特定物理地址上。内核无法主动去扫描并识别它们。这些设备就被称为平台设备 (platform devices)。平台总线就是一个虚拟的、不存在于物理硬件上的总线, 专门用来管理这些平台设备和它们的驱动。

  3. 平台总线 (platform_bus_type) 的定义:
    platform_bus_type 是一个 const struct bus_type 类型的全局实例。它作为一个软件实体, 定义了平台总线的所有行为规则, 其关键成员为函数指针:

    • .name: 字符串 “platform”, 作为该总线在 sysfs 文件系统中的标识 (/sys/bus/platform)。
    • .match: 指向 platform_match 函数。当一个新设备或新驱动注册到平台总线时, 内核的驱动核心 (driver core) 会调用此函数, 以判断该设备与驱动之间是否存在对应关系。匹配逻辑通常是比较设备 (来自设备树) 的 compatible 属性与驱动支持的 compatible 字符串列表。
    • .probe: 指向 platform_probe 函数。当 .match 函数返回成功后, 驱动核心会调用此函数, platform_probe 再进一步调用驱动程序自身实现的 .probe 函数, 以执行硬件的初始化。
    • 其他函数指针定义了总线在移除设备、关机、电源管理和DMA配置等不同阶段的行为。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
* 使用一个宏来避免为了获取THIS_MODULE而产生的头文件包含链.
*/
// 定义宏 platform_driver_register, 该宏接收一个参数 drv.
#define platform_driver_register(drv) \
/*
* 此宏展开后, 调用 __platform_driver_register 函数.
* 它将 drv 参数直接传递, 并自动将 THIS_MODULE 宏作为第二个参数传入.
* THIS_MODULE 是一个编译时宏, 会展开为指向当前内核模块的 'struct module' 实例的指针.
*/
__platform_driver_register(drv, THIS_MODULE)

/*
* 定义一个常量 'struct bus_type' 类型的实例, 变量名为 platform_bus_type.
* 此结构体定义了平台总线的全部操作函数和属性.
*/
const struct bus_type platform_bus_type = {
.name = "platform", // 总线名称.
.dev_groups = platform_dev_groups, // 总线设备的默认sysfs属性文件组.
.match = platform_match, // 指向用于设备与驱动匹配的函数.
.uevent = platform_uevent, // 指向用于生成uevent事件的函数.
.probe = platform_probe, // 指向用于执行设备探测(绑定)的函数.
.remove = platform_remove, // 指向用于设备解绑的函数.
.shutdown = platform_shutdown, // 指向用于系统关闭时回调的函数.
.dma_configure = platform_dma_configure, // 指向用于配置设备DMA的函数.
.dma_cleanup = platform_dma_cleanup, // 指向用于清理设备DMA配置的函数.
.pm = &platform_dev_pm_ops, // 指向定义了电源管理操作的结构体.
};
/*
* 将 platform_bus_type 符号导出.
* 这使得其他遵循GPL许可证的内核模块可以引用此全局变量.
*/
EXPORT_SYMBOL_GPL(platform_bus_type);
/**
* __platform_driver_register - 为平台级设备注册一个驱动程序
* @drv: platform_driver 结构体指针.
* @owner: 所属内核模块的 'struct module' 指针.
*/
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
/*
* 将 drv 内嵌的 'struct device_driver' 的 owner 成员, 赋值为 owner 参数.
*/
drv->driver.owner = owner;
/*
* 将 drv 内嵌的 'struct device_driver' 的 bus 成员, 赋值为全局 'platform_bus_type' 的地址.
*/
drv->driver.bus = &platform_bus_type;

/*
* 调用通用的驱动注册函数 driver_register, 将配置好的驱动注册到内核驱动核心.
* 该函数返回0表示成功, 返回负值错误码表示失败.
*/
return driver_register(&drv->driver);
}
/*
* 将 __platform_driver_register 函数的符号导出, 以便在内核其他部分被调用.
*/
EXPORT_SYMBOL_GPL(__platform_driver_register);

platform_get_irq_optionalplatform_get_irq: 获取平台设备的中断号

这两个函数是Linux内核平台设备驱动模型中用于从硬件描述(主要是设备树)中获取中断请求(IRQ)号的标准API。它们是驱动程序将其自身与硬件中断连接起来的桥梁。

platform_get_irq_optional是核心的、”安静”的工作函数, 而platform_get_irq则是它的一层”健谈”的封装。

platform_get_irq_optional: 以”可选”方式获取中断号

此函数的核心原理是提供一个统一的、跨多种固件接口(Device Tree, ACPI, 旧式平台数据)的、健壮的中断号发现机制。它会按照预设的优先级顺序尝试多种方法来解析中断信息。对于STM32H750这样的、完全依赖设备树(Device Tree)的嵌入式系统, 其主要执行路径如下:

  1. 设备树优先: 函数首先检查设备是否有一个设备树节点 (is_of_node)。如果是(STM32平台总是如此), 它会立即调用of_irq_get。这个函数会直接解析设备树节点中的interruptsinterrupts-extended属性, 并将其翻译成一个Linux内核可以使用的IRQ号。
  2. 处理依赖: of_irq_get是一个强大的函数, 如果它发现中断控制器驱动尚未就绪, 它不会立即失败, 而是会返回-EPROBE_DEFERplatform_get_irq_optional会忠实地将这个返回值传递上去, 从而触发内核的延迟探测机制, 保证了正确的驱动加载顺序。
  3. 旧式平台数据回退: 如果从设备树获取失败(例如, 设备树文件不规范), 它会回退到platform_get_resource。这是一种较旧的机制, 用于从驱动程序硬编码或由板级文件提供的”平台资源”数组中查找类型为IORESOURCE_IRQ的资源。
  4. 设置触发类型: 如果通过platform_get_resource找到了中断, 它还会检查资源标志位, 并使用irqd_set_trigger_type来配置中断的触发方式(如上升沿、下降沿、高电平或低电平触发)。
  5. 最终检查: 在返回之前, 它会进行一个重要的安全检查: WARN(!ret, "0 is an invalid IRQ number\n")。在Linux中, IRQ 0通常被认为是无效或保留的。如果函数最终计算出的IRQ号为0, 它会打印一个内核警告并返回-EINVAL, 防止驱动程序使用一个无效的中断。

“Optional”(可选)的含义在于, 如果所有方法都找不到中断, 它只会”安静地”返回一个错误码(-ENXIO), 而不会在内核日志中打印错误信息。这对于那些中断是可选功能(例如, 一个可以通过轮询或中断模式工作的驱动)的设备非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* platform_get_irq_optional - 为设备获取一个可选的IRQ
* @dev: 平台设备
* @num: IRQ编号的索引
*
* 为平台设备获取一个IRQ. 设备驱动应检查返回值的错误,
* 以免将负整数值传递给 request_irq() API.
* 这个函数与 platform_get_irq() 相同, 区别在于当无法获取IRQ时, 它不会打印错误消息.
*
* 示例::
*
* int irq = platform_get_irq_optional(pdev, 0);
* if (irq < 0)
* return irq;
*
* 返回: 成功时返回非零的IRQ号, 失败时返回负的错误码.
*/
int platform_get_irq_optional(struct platform_device *dev, unsigned int num)
{
int ret;
/*
* 为SPARC架构提供的特殊实现. 在STM32(ARM架构)上, 这部分代码不会被编译.
*/
#ifdef CONFIG_SPARC
if (!dev || num >= dev->archdata.num_irqs)
goto out_not_found;
ret = dev->archdata.irqs[num];
goto out;
#else
/*
* 获取设备的固件节点句柄. 这是一个通用句柄, 可以代表设备树节点或ACPI节点.
*/
struct fwnode_handle *fwnode = dev_fwnode(&dev->dev);
struct resource *r;

/*
* 检查这是否是一个设备树(Open Firmware)节点. 对于STM32平台, 这总是成立的.
*/
if (is_of_node(fwnode)) {
/*
* 调用 of_irq_get 从设备树中解析第 'num' 个中断.
* 这是在现代嵌入式Linux上获取中断的主要方式.
*/
ret = of_irq_get(to_of_node(fwnode), num);
/*
* 如果成功(ret > 0)或需要延迟探测(ret == -EPROBE_DEFER),
* 则我们已经得到了结果, 直接跳转到函数末尾的 'out' 标签.
*/
if (ret > 0 || ret == -EPROBE_DEFER)
goto out;
}

/*
* 如果从设备树获取失败, 回退到旧的平台资源机制.
* 查找类型为 IORESOURCE_IRQ 的第 'num' 个资源.
*/
r = platform_get_resource(dev, IORESOURCE_IRQ, num);

/*
* ACPI相关的代码块, 在非ACPI的STM32系统上通常不会执行.
*/
if (is_acpi_device_node(fwnode)) {
if (r && r->flags & IORESOURCE_DISABLED) {
ret = acpi_irq_get(ACPI_HANDLE_FWNODE(fwnode), num, r);
if (ret)
goto out;
}
}

/*
* 如果找到的资源 'r' 带有触发类型标志 (IORESOURCE_BITS).
*/
if (r && r->flags & IORESOURCE_BITS) {
struct irq_data *irqd;

/* 获取中断号对应的 irq_data 结构体 */
irqd = irq_get_irq_data(r->start);
if (!irqd)
goto out_not_found;
/*
* 根据资源标志设置中断的硬件触发类型 (例如, 上升/下降沿).
* 注释说明 IORESOURCE_BITS 的标志位与 IRQF_TRIGGER* 的标志位是一一对应的.
*/
irqd_set_trigger_type(irqd, r->flags & IORESOURCE_BITS);
}

/*
* 如果通过平台资源找到了中断 (r 不为 NULL).
*/
if (r) {
/*
* 中断号就存储在资源的 start 成员中.
*/
ret = r->start;
goto out;
}

/*
* 另一个ACPI相关的回退路径, 允许从 GpioInt 资源获取中断.
* 在STM32系统上不适用.
*/
if (num == 0 && is_acpi_device_node(fwnode)) {
ret = acpi_dev_gpio_irq_get(to_acpi_device_node(fwnode), num);
if (ret >= 0 || ret == -EPROBE_DEFER)
goto out;
}

#endif
/* 所有的查找方法都失败后, 会跳转到这里. */
out_not_found:
/*
* 设置返回值为 -ENXIO (无此设备或地址), 这是找不到资源的典型错误码.
*/
ret = -ENXIO;
/* 所有成功或需要特殊处理的路径都会跳转到这里. */
out:
/*
* 这是一个非常重要的安全检查. IRQ 0 在历史上是无效的或保留的.
* 如果 ret 最终为0, WARN() 会在内核日志中打印一条警告, 并且函数会返回 -EINVAL(无效参数),
* 防止驱动程序使用这个可能无效的IRQ号.
*/
if (WARN(!ret, "0 is an invalid IRQ number\n"))
return -EINVAL;
/*
* 返回最终找到的IRQ号或错误码.
*/
return ret;
}
EXPORT_SYMBOL_GPL(platform_get_irq_optional);

platform_get_irq: 以”必需”方式获取中断号

此函数是一个简单但非常实用的封装。它的原理是platform_get_irq_optional的基础上, 增加了一个自动的错误处理和日志记录层

当一个驱动程序需要的中断是其正常运行所必需的时, 应该使用这个函数。如果platform_get_irq_optional返回错误, platform_get_irq会使用dev_err_probe函数:

  • 在内核日志中打印一条标准的、格式化的错误信息, 其中包含了设备名和请求失败的中断索引号。这对于调试非常有帮助。
  • dev_err_probe能智能地处理-EPROBE_DEFER错误码。在这种情况下, 它会返回-EPROBE_DEFER本身而不会打印错误消息, 因为这是一个正常的、可恢复的”依赖未就绪”状态, 而不是一个真正的错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* platform_get_irq - 为设备获取一个IRQ
* @dev: 平台设备
* @num: IRQ编号的索引
*
* 为平台设备获取一个IRQ, 并且在查找IRQ失败时打印一条错误消息.
* 设备驱动应检查返回值的错误, 以免将负整数值传递给 request_irq() API.
*
* 示例::
*
* int irq = platform_get_irq(pdev, 0);
* if (irq < 0)
* return irq;
*
* 返回: 成功时返回非零的IRQ号, 失败时返回负的错误码.
*/
int platform_get_irq(struct platform_device *dev, unsigned int num)
{
int ret;

/*
* 调用核心的"可选"版本来执行实际的查找工作.
*/
ret = platform_get_irq_optional(dev, num);
/*
* 检查返回值是否为负数 (即, 是否为错误码).
*/
if (ret < 0)
/*
* 如果是错误, 调用 dev_err_probe.
* 这个函数会打印一条带有设备信息的错误日志, 并返回原始的错误码.
* 它能正确处理 -EPROBE_DEFER, 在这种情况下它不会打印错误.
*/
return dev_err_probe(&dev->dev, ret,
"IRQ index %u not found\n", num);

/*
* 如果成功, 直接返回获取到的IRQ号.
*/
return ret;
}
EXPORT_SYMBOL_GPL(platform_get_irq);

平台设备资源获取与映射辅助函数

此代码片段提供了一组在Linux平台设备驱动模型中广泛使用的、至关重要的辅助函数。它们的核心作用是为平台驱动程序(platform driver)提供一个标准的、便捷的、且高度安全的方式, 来获取其对应的硬件设备(platform device)所拥有的资源(特别是内存映射的I/O寄存器地址), 并将其映射到内核的虚拟地址空间中。

这套API的原理是将资源查找和ioremap操作紧密地封装在一起, 并利用devm框架实现全自动的生命周期管理, 从而极大地简化了驱动程序的编写并提高了其健壮性。

  • platform_get_resource: 这是最基础的查找函数。平台设备(struct platform_device)在被内核实例化时, 会从设备树(Device Tree)中读取其reg属性, 并将这些信息(物理起始地址, 大小)预先填充到一个名为resource的数组中。此函数的作用就是让驱动程序能够通过类型和索引号, 在这个预填充的数组中找到一个特定的资源。
  • devm_platform_..._ioremap_resource...: 这一系列函数是更高层的封装。它们首先在内部调用platform_get_resourceplatform_get_resource_byname来找到目标资源, 然后立即将该资源传递给devm_ioremap_resource函数(我们之前分析过)。devm_ioremap_resource会负责请求内存区域、执行ioremap映射、并将所有这些操作注册到devm框架中。这意味着驱动程序的开发者在probe函数中只需调用这一个函数, 就可以完成资源获取和映射, 并且完全无需在错误处理路径或驱动的remove函数中手动编写iounmaprelease_mem_region的代码。内核设备模型会在驱动卸载时自动完成所有清理工作。

platform_get_resource: 获取一个平台设备资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* platform_get_resource - 为一个设备获取一个资源
* @dev: 平台设备
* @type: 资源类型 (例如 IORESOURCE_MEM, IORESOURCE_IRQ)
* @num: 资源索引号 (从0开始)
*
* 返回: 一个指向资源的指针, 如果失败则返回 NULL.
*/
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
{
u32 i;

/* 遍历设备所拥有的所有资源. dev->num_resources 是资源的数量. */
for (i = 0; i < dev->num_resources; i++) {
/* 获取指向第 i 个资源结构体的指针. */
struct resource *r = &dev->resource[i];

/*
* 检查当前资源的类型是否与请求的类型匹配,
* 并且索引号 num 是否是我们想要的.
* num-- == 0 是一个巧妙的技巧:
* - 如果 num 是 0 (请求第0个), 条件 (0-- == 0) 为真.
* - 如果 num 是 1 (请求第1个), 第一次循环时 (1-- == 0) 为假, num 变为 0.
* 第二次匹配到同类型资源时, (0-- == 0) 就为真了.
*/
if (type == resource_type(r) && num-- == 0)
return r; /* 找到匹配, 返回指向该资源的指针. */
}
return NULL; /* 遍历完都未找到, 返回NULL. */
}
/* 将此函数导出, 供内核其他部分使用. */
EXPORT_SYMBOL_GPL(platform_get_resource);

devm框架下的封装函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#ifdef CONFIG_HAS_IOMEM // 仅在内核配置了IOMEM支持时编译以下代码

/**
* devm_platform_get_and_ioremap_resource - 为平台设备调用devm_ioremap_resource并获取资源
* @pdev: 平台设备, 用于资源查找和资源管理
* @index: 资源索引号
* @res: 可选的输出参数, 用于存储获取到的资源指针
*
* 返回: 一个指向重映射后内存的指针, 失败则返回ERR_PTR()编码的错误码.
*/
void __iomem *
devm_platform_get_and_ioremap_resource(struct platform_device *pdev,
unsigned int index, struct resource **res)
{
struct resource *r;

/* 步骤1: 调用基础函数, 获取指定索引的内存资源. */
r = platform_get_resource(pdev, IORESOURCE_MEM, index);

/* 如果调用者提供了res指针, 就将找到的资源指针存入其中. */
if (res)
*res = r;
/*
* 步骤2: 调用核心的devm映射函数.
* 这个函数会负责请求、映射和注册自动清理.
*/
return devm_ioremap_resource(&pdev->dev, r);
}
EXPORT_SYMBOL_GPL(devm_platform_get_and_ioremap_resource);

/**
* devm_platform_ioremap_resource - 为平台设备调用devm_ioremap_resource
* @pdev: 平台设备, 用于资源查找和资源管理
* @index: 资源索引号
*
* 返回: 一个指向重映射后内存的指针, 失败则返回ERR_PTR()编码的错误码.
*/
void __iomem *devm_platform_ioremap_resource(struct platform_device *pdev,
unsigned int index)
{
/*
* 这是一个更简洁的封装, 它直接调用上面的函数, 但不关心获取到的resource结构体本身.
* 这是最常用的API之一.
*/
return devm_platform_get_and_ioremap_resource(pdev, index, NULL);
}
EXPORT_SYMBOL_GPL(devm_platform_ioremap_resource);

/**
* devm_platform_ioremap_resource_byname - 通过名称为平台设备调用devm_ioremap_resource
* @pdev: 平台设备, 用于资源查找和资源管理
* @name: 资源的名称 (在设备树的 "reg-names" 属性中定义)
*
* 返回: 一个指向重映射后内存的指针, 失败则返回ERR_PTR()编码的错误码.
*/
void __iomem *
devm_platform_ioremap_resource_byname(struct platform_device *pdev,
const char *name)
{
struct resource *res;

/* 步骤1: 调用 platform_get_resource_byname (未显示, 但功能类似) 按名称查找资源. */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
/* 步骤2: 调用核心的devm映射函数. */
return devm_ioremap_resource(&pdev->dev, res);
}
EXPORT_SYMBOL_GPL(devm_platform_ioremap_resource_byname);

#endif /* CONFIG_HAS_IOMEM */