核心内容摘要
拆解男女一起拆轮滑鞋:一场关于成长、误解与和解的冒险之旅
title: simple-pm-buscategories:linuxdriversbustags:linuxdriversbusabbrlink: b441a160date:
15:36:50https://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/OMMIO上的设备。
这些设备可能在逻辑上属于一个功能单元例如一个视频处理流水线上的多个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等功能。
不处理运行时PMsimple-pm-bus主要关注系统级的休眠/唤醒suspend/resume。
虽然它可以与运行时PMRuntime 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; scaler1000 { compatible vendor,scaler; reg 0x1000 0x100; }; csc2000 { compatible vendor,csc; reg 0x2000 0x100; }; encoder3000 { 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;matchof_match_device(dev-driver-of_match_table,dev);// 特殊情况处理如果匹配的是 simple-bus 等透明总线并且是最佳匹配则不执行任何操作。
if(matchmatch-data){if(of_property_match_string(np,compatible,match-compatible)
return0;elsereturn-ENODEV;}// 为驱动的私有数据分配内存 (使用 devm_kzalloc可自动释放)。
busdevm_kzalloc(pdev-dev,sizeof(*bus),GFP_KERNEL);if(!bus)return-ENOMEM;// 获取设备树中定义的所有时钟。
bus-num_clksdevm_clk_bulk_get_all(pdev-dev,bus-clks);if(bus-num_clks
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*busdev_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*busdev_get_drvdata(dev);intret;// 使能(prepare enable)所有关联的时钟。
retclk_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*)
// 定义驱动的 OF (设备树) 匹配表。
staticconststructof_device_idsimple_pm_bus_of_match[];MODULE_DEVICE_TABLE(of,simple_pm_bus_of_match);// 定义平台驱动结构体。
staticstructplatform_driversimple_pm_bus_driver{.probesimple_pm_bus_probe,.removesimple_pm_bus_remove,.driver{.namesimple-pm-bus,.of_match_tablesimple_pm_bus_of_match,.pmpm_ptr(simple_pm_bus_pm_ops),// 关联电源管理操作},};// 使用模块宏来注册平台驱动。
module_platform_driver(simple_pm_bus_driver);// 模块元数据MODULE_DESCRIPTION(Simple Power-Managed Bus Driver);MODULE_AUTHOR(Geert Uytterhoeven geertrenesasglider.be);