title: simple-pm-bus
categories:
- linux
- drivers
- bus
tags: - linux
- drivers
- bus
abbrlink: b441a160
date: 2025-10-16 15:36:50
https://github.com/wdfk-prog/linux-study
文章目录
- drivers/bus/simple-pm-bus.c 简单电源管理总线(Simple PM Bus) 通用的、轻量级的设备电源管理协调器
- 历史与背景
- 这项技术是为了解决什么特定问题而诞生的?
- 它的发展经历了哪些重要的里程碑或版本迭代?
- 目前该技术的社区活跃度和主流应用情况如何?
- 核心原理与设计
- 它的核心工作原理是什么?
- 它的主要优势体现在哪些方面?
- 它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 使用场景
- 在哪些具体的业务或技术场景下,它是首选解决方案?
- 是否有不推荐使用该技术的场景?为什么?
- 对比分析
- 请将其 与 其他相似技术 进行详细对比。
- Simple Power-Managed Bus Driver
- 实现原理分析
- 代码分析
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)框架的一个补充。它的发展是增量式的:
- 核心概念实现:最初的版本实现了其核心功能——创建一个虚拟总线,并在总线的回调函数中,按照设备注册的顺序来调用每个子设备的PM回调。
- 与
genpd的集成:一个重要的演进是它与通用电源域(Generic Power Domains,genpd)框架的集成。这使得simple-pm-bus不仅能协调设备的软件休眠状态,还能协同管理它们所依赖的硬件电源域的开关。 - 异步支持的考量与放弃:社区曾讨论过是否为
simple-pm-bus添加异步的休眠/唤醒能力。但最终的结论是,这个总线的核心价值就在于其简单性和保证的顺序性,而异步处理会破坏这一点,且对于真正需要异步的复杂场景,已有其他更好的解决方案(如设备链接device_link)。
目前该技术的社区活跃度和主流应用情况如何?
simple-pm-bus是一个相对“小众”但非常实用的内核组件。它不像I2C或USB总线那样被普遍知晓,但在嵌入式和SoC领域,它是一个解决特定问题的标准工具。
- 应用情况:在ARM、ARM64等平台的设备树(DTS)文件中,经常可以看到
simple-pm-bus的身影,用于组织那些没有标准总线接口的MMIO设备。 - 社区状态:该代码非常稳定和成熟,改动很少。
核心原理与设计
它的核心工作原理是什么?
simple-pm-bus的核心是一个**“代理”或“直通(passthrough)”总线**。它自身不实现任何复杂的硬件协议,而是利用VFS和设备模型的框架来协调和转发电源管理事件。
- 设备树声明:一切始于设备树。开发者会创建一个节点,其
compatible属性为"simple-pm-bus"。所有需要被该总线管理的设备,都作为这个节点的子节点来声明。子节点在DTS文件中的物理顺序,就决定了它们被处理的顺序。 - 总线驱动注册:
simple-pm-bus.c中实现了一个平台驱动程序。当内核解析设备树并找到一个simple-pm-bus节点时,这个驱动的probe函数会被调用。 - 虚拟总线创建:
probe函数的主要任务是创建一个struct bus_type的实例,这是一个代表simple-pm-bus的虚拟总线。 - PM回调的实现:这个虚拟总线的关键在于它自己定义了
suspend和resume回调函数。 - 有序的事件转发:
- 当系统休眠时:内核电源管理核心会调用
simple-pm-bus的suspend回调。这个回调函数会按照与设备注册时相反的顺序(后注册的先休眠),遍历所有挂在该总线上的子设备,并依次调用它们各自驱动程序的suspend回调。 - 当系统唤醒时:过程相反。
simple-pm-bus的resume回调会按照设备注册时的顺序(先注册的先唤醒),依次调用子设备的resume回调。
- 当系统休眠时:内核电源管理核心会调用
通过这种方式,simple-pm-bus利用设备模型固有的父子关系和注册顺序,以极低的成本实现了一个有序的PM事件协调器。
它的主要优势体现在哪些方面?
- 保证执行顺序:这是其核心价值。它为一组设备的休眠/唤醒提供了严格的、可预测的顺序保证。
- 简单性:实现非常简单,代码量小,易于理解。它不引入任何新的复杂概念。
- 基于设备树:将设备间的PM依赖关系以一种声明式的方式固化在设备树中,使得硬件描述与驱动代码解耦,提高了可移植性。
- 同步执行:其同步执行模型对于那些必须等待前一个设备完全休眠/唤醒后才能进行下一步操作的场景,是非常简单和可靠的。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 严格的同步模型:其最大的优势也是其最大的局限性。它不支持并行/异步的休眠和唤醒,因此可能会拖慢整个系统的休眠/唤醒速度。
- 功能单一:它只处理电源管理。它不是一个通用的总线,不提供设备间的通信、中断处理或DMA等功能。
- 不处理运行时PM:
simple-pm-bus主要关注系统级的休眠/唤醒(suspend/resume)。虽然它可以与运行时PM(Runtime PM)共存,但它本身不提供复杂的运行时PM协调逻辑。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
当满足以下所有条件时,simple-pm-bus是首选解决方案:
- 你有一组没有标准硬件总线的设备(通常是MMIO设备)。
- 这些设备在系统进入或退出全局休眠状态时,必须按照一个严格的顺序进行休眠或唤醒。
- 这个顺序是静态的,可以在设备树中预先定义。
- 不需要并行处理来加速休眠/唤醒过程。
示例:
一个视频编码SoC包含三个IP核:一个图像缩放器(Scaler),一个颜色空间转换器(CSC),和一个H.264编码器(Encoder)。它们形成一个流水线。在系统休眠时,必须先关闭Encoder,再关闭CSC,最后关闭Scaler。唤醒时顺序相反。
在设备树中,可以这样描述:
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->encoder的resume顺序,休眠时encoder->csc->scaler的suspend顺序。
是否有不推荐使用该技术的场景?为什么?
- 需要高性能并行处理:如果一组设备的休眠/唤醒可以并行进行以加速系统状态转换,那么
simple-pm-bus的强制串行模型会成为瓶颈。在这种情况下,应该让这些设备独立存在,并依赖内核的通用异步机制。 - 设备间有复杂的运行时依赖:如果设备A的运行时PM状态依赖于设备B的状态(例如,A只有在B处于Active状态时才能被唤醒),那么应该使用更强大的**设备链接(Device Links)**机制,它专门用于管理设备间的运行时依赖。
- 设备属于标准总线:如果设备挂在I2C, SPI, USB等标准总线之上,那么应该使用这些总线自身的电源管理规范,而不是
simple-pm-bus。
对比分析
请将其 与 其他相似技术 进行详细对比。
simple-pm-busvs. 设备链接 (Device Links)
| 特性 | simple-pm-bus | 设备链接 (Device Links) |
|---|---|---|
| 功能概述 | 一个虚拟总线,用于在系统休眠/唤醒期间,保证一组设备按顺序被回调。 | 一个点对点的依赖关系描述。它声明一个设备(consumer)依赖于另一个设备(supplier)。 |
| 主要解决的问题 | 静态的、有序的系统级PM回调。 | 动态的、运行时的PM依赖。确保supplier在consumer需要它之前被唤醒,在consumer不再需要它之后才休眠。 |
| 工作时机 | 系统休眠/唤醒(suspend/resume)。 | 运行时PM(runtime_suspend/runtime_resume) 和系统休眠/唤醒。 |
| 依赖模型 | 多对一(总线)。多个设备属于同一个总线,共享一个顺序。 | 一对一(链接)。可以构建复杂的依赖图(DAG)。 |
| 配置方式 | 在设备树中通过父子节点关系隐式定义顺序。 | 通过设备树中的power-domains或clocks等属性自动创建,或通过device_link_add()API显式创建。 |
| 适用场景 | 硬件流水线等需要在系统休眠时严格按顺序开关的场景。 | 驱动A需要确保驱动B的设备(如时钟、电源)已经开启后才能继续工作的场景。 |
Simple Power-Managed Bus Driver
本代码片段展示了一个名为simple-pm-bus的平台设备驱动(platform driver)。其核心功能是为那些本身没有复杂硬件、主要作为其他设备“容器”的简单总线设备(如simple-bus),提供一个通用的、基于时钟管理的运行时电源管理(Runtime PM)框架。当总线下的所有子设备都进入空闲状态时,此驱动会自动禁用(gate)总线的所有时钟以节省功耗;当任何一个子设备需要被访问时,它又会自动重新使能这些时钟。
实现原理分析
此驱动是 Linux 内核中一个典型的“胶水层”和“基础设施”驱动,它为一类设备提供了通用的行为,其原理基于设备树、平台驱动模型和运行时电源管理框架。
设备匹配与驱动绑定:
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函数就什么也不做,直接返回成功。这是一种“占位”行为,确保了这些总线设备有一个驱动与之绑定(满足设备模型的完整性),但将实际的电源管理等任务留给它们自己的驱动(如果存在的话)。
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)是另一个核心步骤。它会解析当前总线节点的所有子节点,并为每个子节点动态地创建和注册一个新的平台设备。这使得这些子设备能够被它们自己的驱动程序所探测和绑定。
- 当一个真正的
运行时电源管理 (
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,它会重新使能所有时钟,确保总线和子设备能够正常工作。
- 此驱动通过
代码分析
// ... (包含的头文件) ...// 驱动的私有数据结构,用于存储获取到的时钟信息。structsimple_pm_bus{structclk_bulk_data*clks;intnum_clks;};/** * @brief simple_pm_bus_probe - 驱动的 probe 函数,当匹配的设备被发现时调用。 * @param pdev: 指向平台设备的指针。 * @return int: 成功返回0,失败返回错误码。 */staticintsimple_pm_bus_probe(structplatform_device*pdev){// ... (变量定义) ...// 特殊情况处理:如果使用了 driver_override,则不执行任何操作,直接返回。if(pdev->driver_override)return0;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)return0;elsereturn-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)returndev_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);return0;}/** * @brief simple_pm_bus_remove - 驱动的 remove 函数,当设备被移除时调用。 * @param pdev: 指向平台设备的指针。 */staticvoidsimple_pm_bus_remove(structplatform_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。 */staticintsimple_pm_bus_runtime_suspend(structdevice*dev){structsimple_pm_bus*bus=dev_get_drvdata(dev);// 关闭(unprepare & disable)所有关联的时钟。clk_bulk_disable_unprepare(bus->num_clks,bus->clks);return0;}/** * @brief simple_pm_bus_runtime_resume - 运行时恢复回调。 * @param dev: 指向设备的指针。 * @return int: 成功返回0,失败返回错误码。 */staticintsimple_pm_bus_runtime_resume(structdevice*dev){structsimple_pm_bus*bus=dev_get_drvdata(dev);intret;// 使能(prepare & enable)所有关联的时钟。ret=clk_bulk_prepare_enable(bus->num_clks,bus->clks);if(ret){dev_err(dev,"failed to enable clocks: %d\n",ret);returnret;}return0;}// ... (系统级 suspend/resume 的包装函数) ...// 定义驱动的电源管理操作集。staticconststructdev_pm_opssimple_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)};// 定义一个标记,用于标识仅作为总线的匹配项。#defineONLY_BUS((void*)1)// 定义驱动的 OF (设备树) 匹配表。staticconststructof_device_idsimple_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);// 定义平台驱动结构体。staticstructplatform_driversimple_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>");