Runtime PM
背景介绍:
每个设备(包括CPU)都处理好自身的电源管理工作,尽量以最低的能耗完成交代的任务,尽量在不需要工作的时候进入低功耗状态,尽量不和其它模块有过多耦合。每个设备都是最节省的话,整个系统一定是最节省的。
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设备
Device States
enum rpm_status {
RPM_ACTIVE = 0,/* 设备处于正常工作状态,处于全速全进状态。runtime_resume的回调执行完毕。 */
RPM_RESUMING,/* 设备状态正在从suspend到active转换。 runtime_resume的回调正在执行 */
RPM_SUSPENDED, /* 设备处于低功耗状态,不能处于IO操作。runtime_suspend的回调执行完毕 */
RPM_SUSPENDING,/* 设备状态正从active到suspend状态转换。runtime_suspend回调正在执行 */
};
没有调用runtime_idle回调。那runtime_idle回调什么时候调用呢? idle状态是suspend状态前的一个过渡而已,通常会在runtime_suspend调用之前调用一段时间runtime_idle回调。
Runtime PM请求类型
enum rpm_request {
RPM_REQ_NONE = 0,
RPM_REQ_IDLE,/* 执行runtime_idle() */
RPM_REQ_SUSPEND,/* 执行runtime_suspend () */
RPM_REQ_AUTOSUSPEND, /* 延迟autosuspend_delay后执行runtime_suspend() */
RPM_REQ_RESUME,/* 执行runtime_resume() */
};
Subsystem and Driver Callbacks
为了实现设备的runtime pm,需要subsystem(PM domains, device types, classes and bus types)和driver提供下面三个回调函数:
struct dev_pm_domain {
struct dev_pm_ops ops;
void (*detach)(struct device *dev, bool power_off);
int (*activate)(struct device *dev);
void (*sync)(struct device *dev);
void (*dismiss)(struct device *dev);
};
struct dev_pm_ops {
*********************
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};
Runtime PM数据段
在每个device结构中都存在dev_pm_info的结构,此结构中通过CONFIG_PM_RUNTIME配置字段代码了Runtime PM的信息。
struct dev_pm_info {
....
struct timer_list suspend_timer; /*休眠时候用到的定时器*/
unsigned long timer_expires; /*定时器的超时函数*/
struct work_struct work; /*用于workqueue中的工作项*/
wait_queue_head_t wait_queue; /*等待队列,用于异步pm操作时候使*/
atomic_t usage_count; /*设备的引用计数,通过该字段判断是否有设备使用*/
atomic_t child_count; /*用于禁止Runtime helper function。等于0代表使能,1代表禁止*/
unsigned int disable_depth:3;
unsigned int idle_notification:1;
unsigned int request_pending:1;
unsigned int deferred_resume:1;
unsigned int run_wake:1;
unsigned int runtime_auto:1;
unsigned int no_callbacks:1;
unsigned int irq_safe:1;
unsigned int use_autosuspend:1;
unsigned int timer_autosuspends:1;
unsigned int memalloc_noio:1;
enum rpm_request request;
enum rpm_status runtime_status;
int runtime_error;
int autosuspend_delay;
unsigned long last_busy;
unsigned long active_jiffies;
unsigned long suspended_jiffies;
unsigned long accounting_timestamp;
};
.suspend_timer: 休眠时候用到的定时器。
.timer_expires: 定时器的超时函数。
.work: 用于workqueue中的工作项。
.wait_queue: 等待队列,用于异步pm操作时候使用。
.usage_count: 设备的引用计数,通过该字段判断是否有设备使用。
.child_count: 此设备的"active"子设备的个数。
.disable_depth: 用于禁止Runtime helper function。等于0代表使能,1代表禁止。
.idle_notification: 如果该值被设备,则调用runtime_idle回调函数。
.request_pending: 如果设备,代表工作队列有work请求。
.deferred_resume: 当设备正在执行-> runtime_suspend()的时候,如果->runtime_resume()将要运行,而等待挂起操作完成并不实际,就会设置该值;这里的意思是“一旦你挂起完成,我就开始恢复”。
.run_wake: 如果设备能产生runtime wake-up events事件,就设置该值。
.runtime_auto: 如果设置,则表示用户空间已允许设备驱动程序通过/sys/devices/.../power/control接口在运行时对该设备进行电源管理。
.no_callbacks: 表明该设备不是有Runtime PM callbacks。
.irq_safe: 表示->runtime_suspend()和->runtime_resume()回调函数将在持有自旋锁并禁止中断的情况下被调用。
.use_autosuspend: 表示该设备驱动支持延迟自动休眠功能。
.timer_autosuspends: 表明PM核心应该在定时器到期时尝试进行自动休眠(autosuspend),而不是一个常规的挂起(normal suspend)。
.request: runtime pm请求类型。
.runtime_status: runtime pm设备状态。
.runtime_error: 如果该值设备,表明有错误。
.autosuspend_delay: 延迟时间,用于自动休眠
Runtime PM的软件框架
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,以节省功耗。
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:以上只是理论场景,实际可以放宽,以减小设计的复杂度。
异步(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回调函数,不过,后果自负!
runtime PM的API汇整
RPM提供的API位于“include/linux/pm_runtime.h”中,
目的有二:
- 一是对前面描述的RPM运行机制有一个感性的认识;
- 二是为后面分析RPM的运行机制做准备。
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);
很多RPM请求都是异步的,这些请求会挂到一个名称为“pm_wq”的工作队列上,这个函数的目的,就是清空这个队列,另外如果有resume请求,同步等待resume完成。主要调用在devices shutdown接口,devices suspend也会调用,还有个别驱动中会使用到,频率较低。
好复杂,希望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的超时值
graph TB
A(device_initialize)-->B(device_pm_init)
B-->C(pm_runtime_init)
void pm_runtime_init(struct device *dev)
{
dev->power.runtime_status = RPM_SUSPENDED;
dev->power.idle_notification = false;
dev->power.disable_depth = 1;
atomic_set(&dev->power.usage_count, 0);
dev->power.runtime_error = 0;
atomic_set(&dev->power.child_count, 0);
pm_suspend_ignore_children(dev, false);
dev->power.runtime_auto = true;
dev->power.request_pending = false;
dev->power.request = RPM_REQ_NONE;
dev->power.deferred_resume = false;
dev->power.accounting_timestamp = jiffies;
INIT_WORK(&dev->power.work, pm_runtime_work);
dev->power.timer_expires = 0;
hrtimer_init(&dev->power.suspend_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); /*初始化 power suspend timer*/
dev->power.suspend_timer.function = pm_suspend_timer_fn; //*初始化 设置timer的回调函数*//
init_waitqueue_head(&dev->power.wait_queue);/*初始 wait queue的list*/
}