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设备。
能够不供给,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的特例)。
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要当心处理同步问题,包含:
多个.runtime_suspend恳求之间的同步;
多个.runtime_resume恳求之间的同步;
多个.runtime_idle恳求之间的同步;
.runtime_suspend恳求和.runtime_resume恳求之间的同步;
.runtime_suspend恳求和system suspend之间的同步;
.runtime_resume恳求和system resume之间的同步;
等等。
由此可知,RPM core将会有适当一部分代码,用来处理同步问题。
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。
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设置正确。
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);
这三个函数是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所供给的功用有些堆叠,堆叠的有点烦琐。或许规划者为了供给更多的便当,可过渡的便当和自在,反而是一种捆绑和烦恼!
5. runtime PM的运用过程
觉得上面现已讲了,就不再重复了。