转自:http://www.wowotech.net/pm_subsystem/rpm_overview.htmlhtml
终于能够写Runtime PM(后面简称RPM)了,说实话,蜗蜗有点小激动。由于从我的的角度讲,我很推崇使用RPM进行平常的动态电源管理,而不是suspend机制。linux
软件工程的基本思想就是模块化:高内聚和低耦合。通俗地讲呢,就是“各人自扫门前雪”,尽可能扫好本身的(高内聚),尽可能不和别人交互(低耦合)。而RPM正体现了这一思想:每一个设备(包括CPU)都处理好自身的电源管理工做,尽可能以最低的能耗完成交代的任务,尽可能在不须要工做的时候进入低功耗状态,尽可能不和其它模块有过多耦合。每一个设备都是最节省的话,整个系统必定是最节省的,最终达到无所谓睡、无所谓醒的天人合一状态。网络
讲到这里想到本身的一则趣事:大学时,蜗蜗是寝室长,但不爱打扫卫生,因而就提出一个口号,“不污染,不治理;谁污染,谁治理”。结果呢,你们猜就是了,呵呵。言归正传,开始吧。app
听多了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电源管理更新比较活跃的部分,由于能够作的还不少。spa
鉴于这个现状,本文以及后续RPM有关的文章,会选取最新的kernel(当前为linux-3.17),以便及时同步相关的更新。
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设备。
能够不提供,RPM core会使用异步(ASYNC)或同步(SYNC)的方式,调用设备的.runtime_suspend回调函数,suspend设备,同时记录设备的PM状态;
能够调用RPM core提供helper函数(pm_runtime_autosuspend_expiration、pm_runtime_autosuspend、pm_request_autosuspend),要求在指定的时间后,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”中的解释,供你们参考:
* Once the subsystem-level suspend callback (or the driver suspend callback,
if invoked directly) has completed successfully for the given device, the PM
core regards the device as suspended, which need not mean that it has been
put into a low power state. It is supposed to mean, however, that the
device will not process data and will not communicate with the CPU(s) and
RAM until the appropriate resume callback is executed for it. The runtime
PM status of a device after successful execution of the suspend callback is
'suspended'.
注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的特例)。
这个话题的本质是: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:以上只是理论场景,实际能够放宽,以减少设计的复杂度。
设备驱动代码可在进程和中断两种上下文执行,所以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回调函数,不过,后果自负!
因为.runtime_xxx回调函数可能采用异步的形式调用,以及Generic PM suspend和RPM并存的现状,要求RPM要当心处理同步问题,包括:
多个.runtime_suspend请求之间的同步;
多个.runtime_resume请求之间的同步;
多个.runtime_idle请求之间的同步;
.runtime_suspend请求和.runtime_resume请求之间的同步;
.runtime_suspend请求和system suspend之间的同步;
.runtime_resume请求和system resume之间的同步;
等等。
由此可知,RPM core将会有至关一部分代码,用来处理同步问题。
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会自动处理,不须要驱动工程师太过操心。
在Runtime Power Management的过程当中,device可处于四种状态:RPM_ACTIVE、RPM_RESUMING、RPM_SUSPENDED和RPM_SUSPENDING。
RPM_ACTIVE,设备处于正常工做的状态,表示设备的.runtime_resume回调函数执行成功;
RPM_SUSPENDED,设备处于suspend状态,表示设备.runtime_suspend回调函数执行成功;
RPM_RESUMING,设备的.runtime_resume正在被执行;
RPM_SUSPENDING,设备的.runtime_suspend正在被执行。
注4:前面说过,.runtime_idle只是suspend前的过渡,所以runtime status和idle无关。
device注册时,设备模型代码会调用pm_runtime_init接口,将设备的runtime status初始化为RPM_SUSPENDED,而kernel并不知道某个设备初始化时的真正状态,所以设备驱动须要根据实际状况,调用RPM的helper函数,将自身的status设置正确。
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);
这三个函数是RPM的idle、put/suspend、get/resume等操做的基础,根据rpmflag,有着不一样的操做逻辑。后续不少API,都是基于它们三个。通常不会在设备驱动中直接使用。
extern int pm_schedule_suspend(struct device *dev, unsigned int delay);
在指定的时间后(delay,单位是ms),suspend设备。该接口为异步调用,不会更改设备的引用计数,可在driver的.rpm_idle中调用,免去driver本身再启一个timer的烦恼。
extern void pm_runtime_enable(struct device *dev);
extern void pm_runtime_disable(struct device *dev);
设备RPM功能的enable/disable,可嵌套调用,会使用一个变量(dev->power.disable_depth)记录disable的深度。只要disable_depth大于零,就意味着RPM功能不可以使用,不少的API调用(如suspend/reesume/put/get等)会返回失败。
RPM初始化时,会将全部设备的disable_depth置为1,也就是disable状态,driver初始化完毕后,要根据设备的时机状态,调用这两个函数,将RPM状态设置正确。
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);
这名字起的!!!
由3.3的描述可知,不少RPM请求都是异步的,这些请求会挂到一个名称为“pm_wq”的工做队列上,这个函数的目的,就是清空这个队列,另外若是有resume请求,同步等待resume完成。好复杂,但愿driver永远不要用到它!!
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);
几个通用的函数,通常给subsystem的RPM driver使用,直接调用devie driver的相应的callback函数。
extern void pm_runtime_no_callbacks(struct device *dev);
告诉RPM core本身没有回调函数,不用再调用了(或者调用都是成功的),真啰嗦。
extern void pm_runtime_irq_safe(struct device *dev);
告诉RPM core,以下函数能够在中断上下文调用:
pm_runtime_idle()
pm_runtime_suspend()
pm_runtime_autosuspend()
pm_runtime_resume()
pm_runtime_get_sync()
pm_runtime_put_sync()
pm_runtime_put_sync_suspend()
pm_runtime_put_sync_autosuspend()
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)
直接使用同步的方式,尝试idle/suspend/resume设备,若是条件许可,就会执行相应的callback函数。driver尽可能不要使用它们。
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)
增长/减小设备的使用计数,并判断是否为0,若是为零,尝试调用设备的idle callback,若是不为零,尝试调用设备的resume callback。
这两个接口是RPM的正统接口啊,多多使用!
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)
和上面相似,只不过为同步调用。另外提供了一个可直接调用suspend的put接口,何须的!
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)
autosuspend相关接口。所谓的autosuspend,就是在suspend的基础上,增长一个timer,仍是以为有点啰嗦。不说了。
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);
控制是否使用autosuspend功能,以及设置/获取autosuspend的超时值。
总结一下:总以为这些API所提供的功能有些重叠,重叠的有点啰嗦。可能设计者为了提供更多的便利,可过渡的便利和自由,反而是一种束缚和烦恼!
以为上面已经讲了,就再也不重复了。