1. 前言
终于可以写Runtime PM(后面简称RPM)了,说实话,蜗蜗有点小激动。因为从个人的角度讲,我很推崇使用RPM进行日常的动态电源管理,而不是suspend机制。
软件工程的基本思想就是模块化:高内聚和低耦合。通俗地讲呢,就是“各人自扫门前雪”,尽量扫好自己的(高内聚),尽量不和别人交互(低耦合)。而RPM正体现了这一思想:每个设备(包括CPU)都处理好自身的电源管理工作,尽量以最低的能耗完成交代的任务,尽量在不需要工作的时候进入低功耗状态,尽量不和其它模块有过多耦合。每个设备都是最节省的话,整个系统一定是最节省的,最终达到无所谓睡、无所谓醒的天人合一状态。
讲到这里想到自己的一则趣事:大学时,蜗蜗是寝室长,但不爱打扫卫生,于是就提出一个口号,“不污染,不治理;谁污染,谁治理”。结果呢,大家猜就是了,呵呵。言归正传,开始吧。
2. Runtime PM的软件框架
听多了RPM的传说,有种莫名的恐惧,觉的会很复杂。但看代码,也就是“drivers/base/power/runtime.c”中1400行而已。
从设计思路上讲,它确实简单。下面是一个大概的软件框架:
device driver(或者driver所在的bus、class等)需要提供3个回调函数,runtime_suspend、runtime_resume和runtime_idle,分别用于suspend device、resume device和idle device。它们一般由RPM core在合适的时机调用,以便降低device的power consumption。
而调用的时机,最终是由device driver决定的。driver会在适当的操作点,调用RPM core提供的put和get系列的helper function,汇报device的当前状态。RPM core会为每个device维护一个引用计数,get时增加计数值,put时减少计数值,当计数为0时,表明device不再被使用,可以立即或一段时间后suspend,以节省功耗。
好吧,说总是简单,那做呢?很不幸,到目前为止,linux kernel的runtime PM还是很复杂。这里的复杂,不是从实现的角度,而是从对外的角度。在“include\linux\pm_runtime.h”中,RPM提供了将近50个接口。软件模块化的设计理念中,最重要的一个原则就是提供简洁的接口。很显然,RPM没有做到!
无论RPM面对的问题有多么复杂,无论理由有多么充分,它也应坚守“简洁性”这一原则。否则,结果只有一个----无人敢用。这就是当前Linux kernel电源管理中“Opportunistic suspend”和RPM两种机制并存的原因。但是,就算现状不理想,也不能否认RPM的先进性,在当前以及未来很长的一段时间内,它会是kernel电源管理更新比较活跃的部分,因为可以做的还很多。
鉴于这个现状,本文以及后续RPM有关的文章,会选取最新的kernel(当前为linux-3.17),以便及时同步相关的更新。
3. Runtime PM的运行机制
3.1 核心机制
RPM的核心机制是这样的:
1)为每个设备维护一个引用计数(device->power.usage_count),用于指示该设备的使用状态。
2)需要使用设备时,device driver调用pm_runtime_get(或pm_runtime_get_sync)接口,增加引用计数;不再使用设备时,device driver调用pm_runtime_put(或pm_runtime_put_sync)接口,减少引用计数。
3)每一次put,RPM core都会判断引用计数的值。如果为零,表示该设备不再使用(idle)了,则使用异步(ASYNC)或同步(SYNC)的方式,调用设备的.runtime_idle回调函数。
4).runtime_idle的存在,是为了在idle和suspend之间加一个缓冲,避免频繁的suspend/resume操作。因此它的职责是:判断设备是否具备suspend的条件,如果具备,在合适的时机,suspend设备。
5)pm_runtime_autosuspend、pm_request_autosuspend等接口,会起一个timer,并在timer到期后,使用异步(ASYNC)或同步(SYNC)的方式,调用设备的.runtime_suspend回调函数,suspend设备,同时记录设备的PM状态。
6)每一次get,RPM core都会判断设备的PM状态,如果不是active,则会使用异步(ASYNC)或同步(SYNC)的方式,调用设备的.runtime_resume回调函数,resume设备。
注1:Runtime PM中的“suspend”,不一定要求设备必须进入低功耗状态,而是要求设备在suspend后,不再处理数据,不再和CPUs、RAM进行任何的交互,直到设备的.runtime_resume被调用。因为此时设备的parent(如bus controller)、CPU是、RAM等,都有可能因为suspend而不再工作,如果设备再有任何动作,都会造成不可预期的异常。下面是“Documentation\power\runtime_pm.txt”中的解释,供大家参考:
注2:回忆一下wakeup events和wakeup lock,Runtime PM和它们在本质上是一样的,都是实时的向PM core报告“我不工作了,可以睡了”、“我要工作了,不能睡(或醒来吧)”。不同的是:wakeup events和RPM的报告者是内核空间drivers,而wakeup lock是用户空间进程;wakeup events和wakelock涉及的睡眠对象是整个系统,包括CPU和所有的devices,而RPM是一个一个独立的device(CPU除外,它由cpu idle模块处理,可看作RPM的特例)。
3.2 get和put的时机
这个话题的本质是:device idle的判断标准是什么?
再回忆一下“autosleep”中有关“Opportunistic suspend”的讨论,对“Opportunistic suspend”而言,suspend时机的判断是相当困难的,因为整机的运行环境比较复杂。而每一个具体设备的idle,就容易多了,这就是Runtime PM的优势。回到这个话题上,对device而言,什么是idle?
device是通过用户程序为用户提供服务的,而服务的方式分为两种:接受指令,做事情(被动);上报事件(主动,一般通过中断的方式)。因此,设备active的时间段,包括【接受指令,完成指令】和【事件到达,由driver记录下来】两个。除此之外的时间,包括driver从用户程序获得指令(以及相关的数据)、driver将事件(以及相关的数据)交给应用程序,都是idle时间。
那idle时间是否应立即suspend以节省功耗?不一定,要具体场景具体对待:例如网络传输,如果网络连接正常,那么在可预期的、很短的时间内,设备又会active(传输网络数据),如果频繁suspend,会降低性能,且不会省电;比如用户按键,具有突发性,因而可以考虑suspend;等等。
由于get和put正是设备idle状态的切换点,因此get和put的时机就容易把握了:
1)主动访问设备时,如写寄存器、发起数据传输等等,get,增加引用计数,告诉RPM core设备active;访问结束后,put,减小引用计数,告诉RPM core设备可能idle。
2)设备有事件通知时,get(可能在中断处理中);driver处理完事件后,put。
注3:以上只是理论场景,实际可以放宽,以减小设计的复杂度。
3.3 异步(ASYNC)和同步(SYNC)
设备驱动代码可在进程和中断两种上下文执行,因此put和get等接口,要么是由用户进程调用,要么是由中断处理函数调用。由于这些接口可能会执行device的.runtime_xxx回调函数,而这些接口的执行时间是不确定的,有些可能还会睡眠等待。这对用户进程或者中断处理函数来说,是不能接受的。
因此,RPM core提供的默认接口(pm_runtime_get/pm_runtime_put等),采用异步调用的方式(由ASYNC flag表示),启动一个work queue,在单独的线程中,调用.runtime_xxx回调函数,这可以保证设备驱动之外的其它模块正常运行。
另外,如果设备驱动清楚地知道自己要做什么,也可以使用同步接口(pm_runtime_get_sync/pm_runtime_put_sync等),它们会直接调用.runtime_xxx回调函数,不过,后果自负!
3.4 Runtime PM过程中的同步问题
由于.runtime_xxx回调函数可能采用异步的形式调用,以及Generic PM suspend和RPM并存的现状,要求RPM要小心处理同步问题,包括:
3.5 级联设备之间的Runtime PM
struct device结构中,有一个parent指针,指向该设备的父设备(没有的话为空)。父设备通常是Bus、host controller,设备的正常工作,依赖父设备。体现在RPM中,就是如下的行为:
1)parent设备下任何一个设备处于active状态,parent必须active。
2)parent设备下任何一个设备idle了,要通知parent,parent以此记录处于active状态的child设备个数。
3)parent设备下所有子设备都idle了,parent才可以idle。
以上行为RPM core会自动处理,不需要驱动工程师太过操心。
3.6 device的runtime status及其初始状态
在Runtime Power Management的过程中,device可处于四种状态:RPM_ACTIVE、RPM_RESUMING、RPM_SUSPENDED和RPM_SUSPENDING。
device注册时,设备模型代码会调用pm_runtime_init接口,将设备的runtime status初始化为RPM_SUSPENDED,而kernel并不知道某个设备初始化时的真正状态,因此设备驱动需要根据实际情况,调用RPM的helper函数,将自身的status设置正确。
4. runtime PM的API汇整
RPM提供的API位于“include/linux/pm_runtime.h”中,在这里先浏览一下,目的有二:一是对前面描述的RPM运行机制有一个感性的认识;二是为后面分析RPM的运行机制做准备。
注5:我会把和驱动编写相关度较高的API加粗,其它的能不用就不用、能不看就不看!
extern int __pm_runtime_idle(struct device *dev, int rpmflags);
extern int __pm_runtime_suspend(struct device *dev, int rpmflags);
extern int __pm_runtime_resume(struct device *dev, int rpmflags);
extern int pm_schedule_suspend(struct device *dev, unsigned int delay);
extern void pm_runtime_enable(struct device dev);
extern void pm_runtime_disable(struct device dev);
extern void pm_runtime_allow(struct device dev);
extern void pm_runtime_forbid(struct device dev);
RPM core通过sysfs(drivers/base/power/sysfs.c),为每个设备提供一个“/sys/devices/.../power/control”文件,通过该文件可让用户空间程序直接访问device的RPM功能。这两个函数用来控制是否开启该功能(默认开启)。
extern int pm_runtime_barrier(struct device *dev);
extern int pm_generic_runtime_idle(struct device dev);
extern int pm_generic_runtime_suspend(struct device dev);
extern int pm_generic_runtime_resume(struct device *dev);
extern void pm_runtime_no_callbacks(struct device *dev);
extern void pm_runtime_irq_safe(struct device *dev);
static inline int pm_runtime_idle(struct device dev)
static inline int pm_runtime_suspend(struct device dev)
static inline int pm_runtime_resume(struct device *dev)
static inline int pm_request_idle(struct device dev)
static inline int pm_request_resume(struct device dev)
static inline int pm_runtime_get(struct device dev)
static inline int pm_runtime_put(struct device dev)
static inline int pm_runtime_get_sync(struct device dev)
static inline int pm_runtime_put_sync(struct device dev)
static inline int pm_runtime_put_sync_suspend(struct device *dev)
static inline int pm_runtime_autosuspend(struct device dev)
static inline int pm_request_autosuspend(struct device dev)
static inline int pm_runtime_put_autosuspend(struct device dev)
static inline int pm_runtime_put_sync_autosuspend(struct device dev)
static inline void pm_runtime_use_autosuspend(struct device dev)
static inline void pm_runtime_dont_use_autosuspend(struct device dev)
extern void pm_runtime_set_autosuspend_delay(struct device dev, int delay);
extern unsigned long pm_runtime_autosuspend_expiration(struct device dev);
总结一下:总觉得这些API所提供的功能有些重叠,重叠的有点啰嗦。可能设计者为了提供更多的便利,可过渡的便利和自由,反而是一种束缚和烦恼!
5. runtime PM的使用步骤
觉得上面已经讲了,就不再重复了。