[toc]

在这里插入图片描述

drivers/bus/simple-pm-bus.c 简单电源管理总线(Simple PM Bus) 通用的、轻量级的设备电源管理协调器

历史与背景

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

simple-pm-bus 的诞生是为了解决在复杂的片上系统(SoC)中一个常见的、但缺乏标准化解决方案的问题:如何以一种通用、有序的方式管理一组相互关联的、但又不属于任何标准总线(如I2C, SPI)的设备的电源状态(主要是休眠与唤醒)

在许多SoC设计中,存在大量简单的、直接挂在内存映射I/O(MMIO)上的设备。这些设备可能在逻辑上属于一个功能单元(例如,一个视频处理流水线上的多个IP核),它们需要按照特定的顺序进入休眠(suspend)或从中恢复(resume)。

simple-pm-bus出现之前,处理这种情况通常依赖于:

  • 平台代码中的硬编码:在板级支持文件(board file)中硬编码休眠/唤醒的调用顺序,这使得代码难以移植和维护。
  • 驱动间的隐式依赖:驱动程序之间通过msleep等脆弱的方式来猜测和等待其他设备的状态。

simple-pm-bus提供了一个轻量级的、基于设备树的虚拟总线,专门用于解决这个问题。它允许开发者将一组设备“聚合”在一起,并保证在系统级的休眠/唤醒流程中,这些设备会按照它们在设备树中声明的顺序被同步地有序地休眠和唤醒。

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

simple-pm-bus 是由内核开发者 Linus Walleij 引入的,作为对内核电源管理(PM)框架的一个补充。它的发展是增量式的:

  1. 核心概念实现:最初的版本实现了其核心功能——创建一个虚拟总线,并在总线的回调函数中,按照设备注册的顺序来调用每个子设备的PM回调。
  2. genpd的集成:一个重要的演进是它与通用电源域(Generic Power Domains, genpd)框架的集成。这使得simple-pm-bus不仅能协调设备的软件休眠状态,还能协同管理它们所依赖的硬件电源域的开关。
  3. 异步支持的考量与放弃:社区曾讨论过是否为simple-pm-bus添加异步的休眠/唤醒能力。但最终的结论是,这个总线的核心价值就在于其简单性和保证的顺序性,而异步处理会破坏这一点,且对于真正需要异步的复杂场景,已有其他更好的解决方案(如设备链接device_link)。

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

simple-pm-bus 是一个相对“小众”但非常实用的内核组件。它不像I2C或USB总线那样被普遍知晓,但在嵌入式和SoC领域,它是一个解决特定问题的标准工具。

  • 应用情况:在ARM、ARM64等平台的设备树(DTS)文件中,经常可以看到simple-pm-bus的身影,用于组织那些没有标准总线接口的MMIO设备。
  • 社区状态:该代码非常稳定和成熟,改动很少。

核心原理与设计

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

simple-pm-bus 的核心是一个**“代理”或“直通(passthrough)”总线**。它自身不实现任何复杂的硬件协议,而是利用VFS和设备模型的框架来协调和转发电源管理事件。

  1. 设备树声明:一切始于设备树。开发者会创建一个节点,其compatible属性为"simple-pm-bus"。所有需要被该总线管理的设备,都作为这个节点的子节点来声明。子节点在DTS文件中的物理顺序,就决定了它们被处理的顺序
  2. 总线驱动注册simple-pm-bus.c中实现了一个平台驱动程序。当内核解析设备树并找到一个simple-pm-bus节点时,这个驱动的probe函数会被调用。
  3. 虚拟总线创建probe函数的主要任务是创建一个struct bus_type的实例,这是一个代表simple-pm-bus的虚拟总线。
  4. PM回调的实现:这个虚拟总线的关键在于它自己定义了suspendresume回调函数。
  5. 有序的事件转发
    • 当系统休眠时:内核电源管理核心会调用simple-pm-bussuspend回调。这个回调函数会按照与设备注册时相反的顺序(后注册的先休眠),遍历所有挂在该总线上的子设备,并依次调用它们各自驱动程序的suspend回调。
    • 当系统唤醒时:过程相反。simple-pm-busresume回调会按照设备注册时的顺序(先注册的先唤醒),依次调用子设备的resume回调。

通过这种方式,simple-pm-bus利用设备模型固有的父子关系和注册顺序,以极低的成本实现了一个有序的PM事件协调器。

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

  • 保证执行顺序:这是其核心价值。它为一组设备的休眠/唤醒提供了严格的、可预测的顺序保证。
  • 简单性:实现非常简单,代码量小,易于理解。它不引入任何新的复杂概念。
  • 基于设备树:将设备间的PM依赖关系以一种声明式的方式固化在设备树中,使得硬件描述与驱动代码解耦,提高了可移植性。
  • 同步执行:其同步执行模型对于那些必须等待前一个设备完全休眠/唤醒后才能进行下一步操作的场景,是非常简单和可靠的。

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

  • 严格的同步模型:其最大的优势也是其最大的局限性。它不支持并行/异步的休眠和唤醒,因此可能会拖慢整个系统的休眠/唤醒速度。
  • 功能单一:它只处理电源管理。它不是一个通用的总线,不提供设备间的通信、中断处理或DMA等功能。
  • 不处理运行时PMsimple-pm-bus主要关注系统级的休眠/唤醒(suspend/resume)。虽然它可以与运行时PM(Runtime PM)共存,但它本身不提供复杂的运行时PM协调逻辑。

使用场景

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

当满足以下所有条件时,simple-pm-bus是首选解决方案:

  • 你有一组没有标准硬件总线的设备(通常是MMIO设备)。
  • 这些设备在系统进入或退出全局休眠状态时,必须按照一个严格的顺序进行休眠或唤醒。
  • 这个顺序是静态的,可以在设备树中预先定义。
  • 不需要并行处理来加速休眠/唤醒过程。

示例
一个视频编码SoC包含三个IP核:一个图像缩放器(Scaler),一个颜色空间转换器(CSC),和一个H.264编码器(Encoder)。它们形成一个流水线。在系统休眠时,必须先关闭Encoder,再关闭CSC,最后关闭Scaler。唤醒时顺序相反。
在设备树中,可以这样描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
video_pipeline: video-pipeline {
compatible = "simple-pm-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;

scaler@1000 {
compatible = "vendor,scaler";
reg = <0x1000 0x100>;
};

csc@2000 {
compatible = "vendor,csc";
reg = <0x2000 0x100>;
};

encoder@3000 {
compatible = "vendor,encoder";
reg = <0x3000 0x100>;
};
};

在这种配置下,simple-pm-bus会确保唤醒时scaler -> csc -> encoderresume顺序,休眠时encoder -> csc -> scalersuspend顺序。

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

  • 需要高性能并行处理:如果一组设备的休眠/唤醒可以并行进行以加速系统状态转换,那么simple-pm-bus的强制串行模型会成为瓶颈。在这种情况下,应该让这些设备独立存在,并依赖内核的通用异步机制。
  • 设备间有复杂的运行时依赖:如果设备A的运行时PM状态依赖于设备B的状态(例如,A只有在B处于Active状态时才能被唤醒),那么应该使用更强大的**设备链接(Device Links)**机制,它专门用于管理设备间的运行时依赖。
  • 设备属于标准总线:如果设备挂在I2C, SPI, USB等标准总线之上,那么应该使用这些总线自身的电源管理规范,而不是simple-pm-bus

对比分析

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

simple-pm-bus vs. 设备链接 (Device Links)

特性 simple-pm-bus 设备链接 (Device Links)
功能概述 一个虚拟总线,用于在系统休眠/唤醒期间,保证一组设备按顺序被回调。 一个点对点的依赖关系描述。它声明一个设备(consumer)依赖于另一个设备(supplier)。
主要解决的问题 静态的、有序的系统级PM回调。 动态的、运行时的PM依赖。确保supplier在consumer需要它之前被唤醒,在consumer不再需要它之后才休眠。
工作时机 系统休眠/唤醒 (suspend/resume)。 运行时PM (runtime_suspend/runtime_resume) 和 系统休眠/唤醒
依赖模型 多对一(总线)。多个设备属于同一个总线,共享一个顺序。 一对一(链接)。可以构建复杂的依赖图(DAG)。
配置方式 在设备树中通过父子节点关系隐式定义顺序 通过设备树中的power-domainsclocks等属性自动创建,或通过device_link_add() API 显式创建
适用场景 硬件流水线等需要在系统休眠时严格按顺序开关的场景。 驱动A需要确保驱动B的设备(如时钟、电源)已经开启后才能继续工作的场景。

Simple Power-Managed Bus Driver

本代码片段展示了一个名为 simple-pm-bus平台设备驱动(platform driver)。其核心功能是为那些本身没有复杂硬件、主要作为其他设备“容器”的简单总线设备(如 simple-bus),提供一个通用的、基于时钟管理的运行时电源管理(Runtime PM)框架。当总线下的所有子设备都进入空闲状态时,此驱动会自动禁用(gate)总线的所有时钟以节省功耗;当任何一个子设备需要被访问时,它又会自动重新使能这些时钟。

实现原理分析

此驱动是 Linux 内核中一个典型的“胶水层”和“基础设施”驱动,它为一类设备提供了通用的行为,其原理基于设备树、平台驱动模型和运行时电源管理框架。

  1. 设备匹配与驱动绑定:

    • simple_pm_bus_of_match 数组定义了此驱动会尝试绑定的设备。它通过 compatible 字符串来匹配设备树中的节点。
    • 核心目标: .compatible = "simple-pm-bus"。任何在设备树中明确标记为 simple-pm-bus 的节点,都是此驱动的主要服务对象。
    • 透明总线处理: 它也匹配 simple-bus, simple-mfd 等。但对于这些设备,它设置了 .data = ONLY_BUS。在 probe 函数中,有一个复杂的逻辑 if (match && match->data),其效果是:如果一个设备匹配的是这些“透明总线”,并且这个驱动是其最精确的匹配项(of_property_match_string(...) == 0),那么 probe 函数就什么也不做,直接返回成功。这是一种“占位”行为,确保了这些总线设备有一个驱动与之绑定(满足设备模型的完整性),但将实际的电源管理等任务留给它们自己的驱动(如果存在的话)。
  2. probe 函数的核心逻辑:

    • 当一个真正的 simple-pm-bus 设备被探测到时:
      a. 获取时钟: devm_clk_bulk_get_all 是一个关键函数。它会自动解析设备树中与该总线节点关联的所有 clocks 属性,并获取这些时钟的句柄。
      b. 使能 Runtime PM: pm_runtime_enable(&pdev->dev) 激活了内核的运行时电源管理框架。此后,内核会开始自动跟踪该总线设备及其所有子设备的“使用计数”(usage count)。
      c. 填充子设备: of_platform_populate(np, NULL, lookup, &pdev->dev) 是另一个核心步骤。它会解析当前总线节点的所有子节点,并为每个子节点动态地创建和注册一个新的平台设备。这使得这些子设备能够被它们自己的驱动程序所探测和绑定。
  3. 运行时电源管理 (simple_pm_bus_pm_ops):

    • 此驱动通过 .pm 字段,将其电源管理操作集 simple_pm_bus_pm_ops 注册到内核。
    • runtime_suspend: 当内核的 Runtime PM 框架检测到该总线及其所有子设备都已空闲(使用计数降为 0),并且过了自动挂起的延迟时间后,它会自动调用 simple_pm_bus_runtime_suspend。此函数的核心是 clk_bulk_disable_unprepare,它会关闭之前获取的所有时钟。
    • runtime_resume: 当任何一个子设备(或总线自身)需要被访问(例如,用户空间 open 一个子设备文件,导致其驱动的 probe 被调用),内核会首先自动调用父总线的 simple_pm_bus_runtime_resume。此函数的核心是 clk_bulk_prepare_enable,它会重新使能所有时钟,确保总线和子设备能够正常工作。

代码分析

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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// ... (包含的头文件) ...

// 驱动的私有数据结构,用于存储获取到的时钟信息。
struct simple_pm_bus {
struct clk_bulk_data *clks;
int num_clks;
};

/**
* @brief simple_pm_bus_probe - 驱动的 probe 函数,当匹配的设备被发现时调用。
* @param pdev: 指向平台设备的指针。
* @return int: 成功返回0,失败返回错误码。
*/
static int simple_pm_bus_probe(struct platform_device *pdev)
{
// ... (变量定义) ...

// 特殊情况处理:如果使用了 driver_override,则不执行任何操作,直接返回。
if (pdev->driver_override)
return 0;

match = of_match_device(dev->driver->of_match_table, dev);
// 特殊情况处理:如果匹配的是 "simple-bus" 等透明总线,并且是最佳匹配,则不执行任何操作。
if (match && match->data) {
if (of_property_match_string(np, "compatible", match->compatible) == 0)
return 0;
else
return -ENODEV;
}

// 为驱动的私有数据分配内存 (使用 devm_kzalloc,可自动释放)。
bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
if (!bus)
return -ENOMEM;

// 获取设备树中定义的所有时钟。
bus->num_clks = devm_clk_bulk_get_all(&pdev->dev, &bus->clks);
if (bus->num_clks < 0)
return dev_err_probe(&pdev->dev, bus->num_clks, "failed to get clocks\n");

// 将私有数据与设备关联起来。
dev_set_drvdata(&pdev->dev, bus);

dev_dbg(&pdev->dev, "%s\n", __func__);

// 启用此设备的运行时电源管理。
pm_runtime_enable(&pdev->dev);

// 如果设备来自设备树,则解析其子节点并为它们创建平台设备。
if (np)
of_platform_populate(np, NULL, lookup, &pdev->dev);

return 0;
}

/**
* @brief simple_pm_bus_remove - 驱动的 remove 函数,当设备被移除时调用。
* @param pdev: 指向平台设备的指针。
*/
static void simple_pm_bus_remove(struct platform_device *pdev)
{
// 对于透明总线或 driver_override 的情况,不执行任何操作。
if (pdev->driver_override || data)
return;

dev_dbg(&pdev->dev, "%s\n", __func__);

// 禁用此设备的运行时电源管理。
pm_runtime_disable(&pdev->dev);
}

/**
* @brief simple_pm_bus_runtime_suspend - 运行时挂起回调。
* @param dev: 指向设备的指针。
* @return int: 始终返回0。
*/
static int simple_pm_bus_runtime_suspend(struct device *dev)
{
struct simple_pm_bus *bus = dev_get_drvdata(dev);

// 关闭(unprepare & disable)所有关联的时钟。
clk_bulk_disable_unprepare(bus->num_clks, bus->clks);

return 0;
}

/**
* @brief simple_pm_bus_runtime_resume - 运行时恢复回调。
* @param dev: 指向设备的指针。
* @return int: 成功返回0,失败返回错误码。
*/
static int simple_pm_bus_runtime_resume(struct device *dev)
{
struct simple_pm_bus *bus = dev_get_drvdata(dev);
int ret;

// 使能(prepare & enable)所有关联的时钟。
ret = clk_bulk_prepare_enable(bus->num_clks, bus->clks);
if (ret) {
dev_err(dev, "failed to enable clocks: %d\n", ret);
return ret;
}

return 0;
}

// ... (系统级 suspend/resume 的包装函数) ...

// 定义驱动的电源管理操作集。
static const struct dev_pm_ops simple_pm_bus_pm_ops = {
RUNTIME_PM_OPS(simple_pm_bus_runtime_suspend, simple_pm_bus_runtime_resume, NULL)
NOIRQ_SYSTEM_SLEEP_PM_OPS(simple_pm_bus_suspend, simple_pm_bus_resume)
};

// 定义一个标记,用于标识仅作为总线的匹配项。
#define ONLY_BUS ((void *) 1)

// 定义驱动的 OF (设备树) 匹配表。
static const struct of_device_id simple_pm_bus_of_match[] = {
{ .compatible = "simple-pm-bus", },
{ .compatible = "simple-bus", .data = ONLY_BUS },
{ .compatible = "simple-mfd", .data = ONLY_BUS },
{ .compatible = "isa", .data = ONLY_BUS },
{ .compatible = "arm,amba-bus", .data = ONLY_BUS },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, simple_pm_bus_of_match);

// 定义平台驱动结构体。
static struct platform_driver simple_pm_bus_driver = {
.probe = simple_pm_bus_probe,
.remove = simple_pm_bus_remove,
.driver = {
.name = "simple-pm-bus",
.of_match_table = simple_pm_bus_of_match,
.pm = pm_ptr(&simple_pm_bus_pm_ops), // 关联电源管理操作
},
};

// 使用模块宏来注册平台驱动。
module_platform_driver(simple_pm_bus_driver);

// 模块元数据
MODULE_DESCRIPTION("Simple Power-Managed Bus Driver");
MODULE_AUTHOR("Geert Uytterhoeven <geert+renesas@glider.be>");