Runtime PM管理也就是设备驱动里面的电源管理即设备驱动结构体里面的**struct dev_pm_ops只控制设备自己的电源。这样可以在设备不需要工作的时候可以进入到低功耗状态更好的管理设备自己的电源**所谓“各扫门前雪”。为什么需要Runtime PM不同于系统的电源管理设备自己的电源管理更加的细化。这就像一个层级关系系统整体的是一个大的电源状态管理但是对于众多的集成外国设备也不能一刀切就是不能要干活都干活要休息都休息要细化管理不能懒政就对每个设备自己也来一套电源状态管理直接把机制从系统哪里复制过来一份一个阉割版的就够用采用分而治之的思想只要系统要统一指挥的时候听话就可以其他时候可以自己决策执行就是runtime PM管理。这里的设备有可能是外设比如sensor、lcdc等。这里的设备也有可能是SOC内部的某些IP比如codec、dsp、usb等。1. 框架介绍1.1 为什么需要Runtime PM Framework?系统基本的电源管理例如关机休眠等需要调用device的电源Runtime API就是ops回调函数而且需要按一个顺序的queue去实施而且系统跟设备状态发生冲突的时候也需要去处理综上就需要一个Framework去统一做这些事情设备驱动需要根据系统的一些参数来决定自己的电源状态例如CPU是否idle等就需要系统框架的支持当设备处于低功耗模式时wakeup signal常常需要platform或者bus的支持。1.2 系统框架图数据结构image.png关机举例休眠举例2. DriversDevice drivers包括bus、class、power domain实现了runtime pm相关的runtime_idle/runtime_suspend/runtime_resume三个回调runtime_suspend用于实现设备的低功耗操作runtime_resume用于实现设备的低功耗恢复相关的操作runtime_idle属于runtime_suspend的一个过渡用于缓冲频繁的suspend与resume它会判断设备是否具备suspend的条件如果具备在合适的时机就会suspend设备。runtime_suspend与runtime_resume回调函数里会调用clock framework/reset framework/regulator framework提供的时钟开关、复位、电源开关的接口。这里以SPI驱动为例进行说明subsys_initcall(pl022_init);static int __init pl022_init(void){returnamba_driver_register(pl022_driver);}static struct amba_driver pl022_driver{.drv{.namessp-pl022, .pmpl022_dev_pm_ops,}, .id_tablepl022_ids, .probepl022_probe, .removepl022_remove,};static const struct dev_pm_ops pl022_dev_pm_ops{SET_SYSTEM_SLEEP_PM_OPS(pl022_suspend, pl022_resume)SET_RUNTIME_PM_OPS(pl022_runtime_suspend, pl022_runtime_resume, NULL)};#define SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) \.runtime_suspendsuspend_fn,\.runtime_resumeresume_fn,\.runtime_idleidle_fn,pm结构体dev_pm_ops 中的有3个以runtime开头的成员函数runtime_suspend、runtime_resume和runtime_idle它们辅助设备完成运行时的电源管理struct dev_pm_ops{... int(*runtime_suspend)(struct device *dev);int(*runtime_resume)(struct device *dev);int(*runtime_idle)(struct device *dev);...};运行时的PM与前文描述的系统级挂起到RAM时候的PM不太一样它是针对单个设备指系统在非睡眠状态的情况下某个设备在空闲时可以进入运行时挂起状态而在不是空闲时执行运行时恢复使得设备进入正常工作状态如此这个设备在运行时会省电。每个设备处理好自己的电源管理在不需要工作时进入低功耗状态。也就是各人自扫门前雪。Linux提供了一系列API以便于设备可以声明自己的运行时PM状态函数名字功能pm_runtime_suspend引发设备的挂起执行相关的runtime_suspend函数。pm_schedule_suspend“调度”设备的挂起延迟delay毫秒后将挂起工作挂入pm_wq等待队列结果等价于delay毫秒后执行相关的runtime_suspend函数。pm_runtime_resume引发设备的恢复执行相关的runtime_resume函数。pm_request_resume发起一个设备恢复的请求该请求也是挂入pm_wq等待队列。pm_runtime_idle引发设备的空闲执行相关的runtime_idle函数。pm_request_idle发起一个设备空闲的请求该请求也是挂入pm_wq等待队列。pm_runtime_enable使能设备的运行时PM支持。pm_runtime_disable禁止设备的运行时PM支持。pm_runtime_getpm_runtime_get_sync增加设备的引用计数usage_count这类似于clk_get会间接引发设备的runtime_resume。pm_runtime_putpm_runtime_put_sync减小设备的引用计数这类似于clk_put会间接引发设备的runtime_idle。3. Runtime PM coreRuntime pm core主要提供了三类函数接口提供enable/disable接口给设备驱动用于该设备驱动决定是否打开或关闭RPM提供get、put类接口给设备驱动用于决定什么时候进入或者恢复设备低功耗在设备驱动调用了get、put接口后RPM会调用各设备驱动实现的runtime_suspend/runtime_resume接口。对于决定设备是否进入低功耗的get/put接口的调用时机一般会在操作设备相关寄存器前调用get接口在操作完相关寄存器后调用put接口。或者在设备驱动的open、release、start、stop等接口里调用用户层的services通过ioctrl或者驱动提供的文件节点调用驱动的这些接口。我们可以这样简单地理解Linux运行时PM的机制每个设备总线的控制器自身也属于一个设备都 有引用计数usage_count和活跃子设备Active Children子设备的意思就是该级总线上挂的设备计数child_count当两个计数都为0的时候就进入空闲状态调用pm_request_idledev。当设备进入空闲状态与pm_request_idledev对应的PM核并不一定直接调用设备驱动的runtime_suspend它实际上在多数情况下是调用与该设备对应的bus_type的runtime_idle。在具体的设备驱动中一般的用法则是在设备驱动probe时运行pm_runtime_enable使能运行时PM支持在运行过程中动态地执行“pm_runtime_get_xxx-做工作-pm_runtime_put_xxx”的序列。如代码清单19.19中的drivers/watchdog/omap_wdt.c OMAP的看门狗驱动。在omap_wdt_start中启动了pm_runtime_get_sync而在omap_wdt_stop中调用了pm_runtime_put_sync。static const struct watchdog_ops omap_wdt_ops{.ownerTHIS_MODULE, .startomap_wdt_start, .stopomap_wdt_stop, .pingomap_wdt_ping, .set_timeoutomap_wdt_set_timeout, .get_timeleftomap_wdt_get_timeleft,};static int omap_wdt_start(struct watchdog_device *wdog){pm_runtime_get_sync(wdev-dev);//告诉内核要开始用看门狗这个设备了如果看门狗设备已经进入省电模式之前引用计数为0且执行了运行时挂起会导致该设备的运行时恢复 static int omap_wdt_stop(struct watchdog_device *wdog){pm_runtime_put_sync(wdev-dev);//告诉内核不用这个设备了如果引用计数变为0且活跃子设备为0则导致该看门狗设备的运行时挂起。在一些设备上不使用的时候不能立即挂起因为挂起状态的进入和恢复需要一些时间如果设备不在挂起之间保留一定的时间频繁进出挂起反而会带来新的开销。因此我们可根据情况决定只有设备在空闲了一段时间后才进入挂起一般来说一个一段时间没有被使用的设备还会有一段时间不会被使用基于此一些设备驱动也常常使用自动挂动模式进行编程。在执行操作的时候声明pm_runtime_get操作完成后执行pm_runtime_mark_last_busy和pm_runtime_put_autosuspend一旦自动挂动的延时到期且设备的使用计数为0则引发相关runtime_suspend入口函数的调用。设备驱动PM成员的runtime_suspend一般完成保存上下文、切到省电模式的工作而runtime_resume一般完成对硬件上电、恢复上下文的工作4. power domain framework一个power domain上可能包含多个IP每个IP可能对应一个或多个设备。这些设备会在dts中描述与power domain的绑定关系。系统初始化的时候会将这个power domain放到一个链表中然后根据设备中dts描述的与power domain的关系将设备挂在power domain节点下的链表中。当某个设备驱动通过put接口调用将usage_count从1减少到0这时会先调用power domain注册的runtime_suspend接口在这个接口中会先调用该设备驱动的runtime_suspend然后遍历该power domain下所有的设备是否都允许suspend各设备驱动的usage_count是否为0,若允许就会直接调用关闭power domian的接口否则直接返回。当某个设备驱动通过get接口调用将usage_count从0增加到1这时会先调用power domain注册的runtime_resume接口在这个接口中会先将power domain上电然后再调用设备驱动对应的runtime_resume回调函数让设备退出低功耗。5. runtime pm的sysfs对于支持rpm的设备在相应的设备节点下有多个rpm相关属性的文件节点分别为controlruntime_susupend_time,runtime_active_timeautosuspend_delay_msruntime_status。接口在文件: /kernel/drivers/base/power/sysfs.c中描述。/sys/devices/…/power/controlon - 调用pm_runtime_forbid接口增加设备的引用计数然后resume设备。auto - 调用pm_runtime_allow接口减少设备的引用计数如果设备的引用计数为0则idle设备。/sys/devices/…/power/runtime_statusactive - 设备的状态是正常工作状态。suspend- 设备的状态是低功耗模式。suspending-设备的状态正在从active-suspend转化。resuming-设备的状态正在从suspend-active转化。error-设备runtime出现错误此时runtime_error的标志置位。unsupported-设备的runtime 没有使能此时disable_depth标志置位。/sys/devices/…/power/runtime_suspend_time设备在suspend状态的时间/sys/devices/…/power/runtime_active_time设备在active状态的时间/sys/devices/…/power/autosuspend_delay_ms设备在idle状态多久之后suspend设置延迟suspend的延迟时间。6参考https://zhuanlan.zhihu.com/p/576243151https://codeantenna.com/a/bZCPl5SFSwhttps://github.com/wizardst/Linux_drivers_frameworks_doc/blob/master/Linux%E7%94%B5%E6%BA%90%E7%AE%A1%E7%90%86Run-time%20PM%20%E8%AF%A6%E8%A7%A3后记在编写驱动的时候如果涉及电源管理的功耗需求就需要实现struct dev_pm_ops为驱动程序增加一个电源管理的功能会更加的灵活也是我们去优化系统功耗的一个重要方向。因为大多数程序估计是供应商提供的但是我们自己的加的硬件的程序估计是我们自己去写的并且做业务挺耗电给自己写的驱动加个电源管理就挺好。干啥都能干干啥啥不是专业入门劝退堪称程序员杂家”。欢迎各位有自己公众号的留言申请转载多谢后续会继续更新纯干货分析欢迎分享给朋友欢迎点赞、收藏、在看、划线和评论交流公众号“那路谈OS与SoC嵌入式软件”欢迎关注个人文章汇总https://thatway1989.github.io