Contents
计算机系统的运行是依靠电能运行的,而能量的获取是有成本的,因此如果能在保证系统运转的基础上,尽量节省对能量的消耗,就会大大提升该系统的生存竞争力。这方面,大自然已经做的很好了,如植物的落叶,如动物的冬眠,等等。而在计算机的世界里(这里以运行Linux OS的嵌入式系统为例),称作电源管理(Power Management)。
电源管理(Power Management)在Linux Kernel中,是一个比较庞大的子系统,涉及到供电(Power Supply)、充电(Charger)、时钟(Clock)、频率(Frequency)、电压(Voltage)、睡眠/唤醒(Suspend/Resume)等方方面面。
对于嵌入式设备而言,功耗管控能力是设备性能表现的重要依据。而嵌入式开发中电源功耗管控主要由驱动控制。
RPM框架
RPM(Runtime power management)在Linux内核驱动中,是一套运行时电源管理框架,为系统中所有的device提供了一种运行时电源管理的机制。这也是新内核(linux 2.6以后)展现的新特性:模块化处理,实现高内聚和低耦合。通俗地讲呢,就是“各人自扫门前雪”,尽量扫好自己的(高内聚),尽量不和别人交互(低耦合)。而RPM正体现了这一思想:每个设备(包括CPU)都处理好自身的电源管理工作,尽量以最低的能耗完成交代的任务,尽量在不需要工作的时候进入低功耗状态,尽量不和其它模块有过多耦合。每个设备都是最节省的话,整个系统一定是最节省的。
RPM的软件设计框架如下图所示:
为什么需要Runtime PM Framework?
- 改变设备的电源状态需要整个平台的支持。
- 当设备处于低功耗模式时,wakeup signal常常需要platform或者bus的支持。
- 设备驱动不知道什么时候去suspend设备的,也就是驱动是没法判断设备是否处于idle状态,通常需要依赖subsystem,比如bus。
- pm相关的操作都需要顺序执行,这时候就免不了使用workqueue。
- Runtime PM需要和system-wide suspend需要保持兼容。比如system-wide suspend的时候设备已经处于Runtime PM的SUSPENDED状态了,这时候应该怎么处理此设备?
以上这些工作都需要Runtime PM framework的支持,这也都是Runtime PM framework应该操心的事情。
Runtime PM数据结构
为了实现设备的runtime pm,需要subsystem(PM domains、device types、classes and bus types)和driver提供下面三个回调函数:
//内核驱动中使用的电源管理数据结构
struct dev_pm_ops {
....
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};
三个回调函数分别用于suspend device,resume device和idle device。通常Runtime PM framework会在合适的时机调用三个函数。
Runtime PM运行机制
核心机制
RPM的核心机制是这样的:
- 为每个设备维护一个引用计数(device->power.usage_count),用于指示该设备的使用状态。
- 当需要使用设备时,device driver调用pm_runtime_get(或pm_runtime_get_sync)接口,增加引用计数;不再使用设备时,device driver调用pm_runtime_put(或pm_runtime_put_sync),减少引用计数。每一次put,RPM core都会判断引用计数的值。如果为零,表示该设备不在使用(Idle)了,则使用异步(ASYNC)或同步(SYNC),调用设备的.runtime_idle回调函数。
- .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设备。
- pm_runtime_autosuspend、pm_request_autosuspend等接口,会起一个timer,并在timer到期后,使用异步(ASYNC)或同步(SYNC)的方式,调用设备的.runtime_suspend回调函数,suspend设备,同时记录设备的PM状态。
- 每一次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而不再工作,如果设备再有任何动作,都会造成不可预期的异常。
注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的特例)。
get和put的时机
什么时候进行get和put操作的实质就是device idle判断的标准是什么?
device是通过用户程序为用户提供服务,而服务的方式又分成两种:接受指令,做事情(被动);上报事件(主动,一般是通过中断的方式)。因此,设备active的时间段,包括【接收指令、完成指令】和 【事件到达,由driver记录下来】另种事件处理类型。除此之外的时间,包括driver从用户处层序获得指令(以及相关数据)、driver将事件(以及相关的数据)交给应用程序 ,都是idle时间。
那么对于设备而言,idle时间是否应该立即suspend以节省功耗?不一定,要具体场景具体对待:例如如果网络连接正常,那么在可预期的、很短的时间内,设备又会active(传输网络数据),如果频繁suspend,会降低性能,且不会省电;比如用户按键,具有突发性,因而可以考虑suspend;等等。
由于get和put正是设备idle状态的切换点,因此get和put的使用时机理论上就可以控制如下:
- 主动访问设备时,如写寄存器、发起数据传输等等;使用get操作,增加引用计数,告诉RPM core设备active;访问结束后,使用put操作,减小引用计数,告诉RPM core设备可能idle。
- 设备有事件通知时,使用get操作(可能在中断处理中);driver处理完事件后,使用put操作。
异步(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过程中的同步问题
由于.runtime_xxx回调函数可能采用异步的形式调用,以及Generic PM suspend和RPM并存的现状,因此要求RPM需要小心处理同步问题,在RPM core中有相当一部分的代码用处处理同步问题,主要内容包括:
多个.runtime_suspend请求之间的同步
多个.runtime_resume请求之间的同步
多个.runtime_idle请求之间的同步
.runtime_suspend请求和system_resume请求之间的同步;
.runtime_suspend请求和system suspend之间的同步;
.runtime_resume请求和system resume之间的同步;
等等
级联设备之间的Runtime PM
struct device结构中,有一个parent指针,指向该设备的父设备(如果没有则为空)。父设备通常是Bus、host controller,设备的正常工作,依赖父设备。体现在RPM中,就是如下的行为:
- parent设备下任何一个设备处于active状态,parent必须active。
- parent设备下任何一个设备idle了,要通知parent,parent以此记录处于active状态的child设备个数。
- parent设备下所有子设备都idle了,parent才可以idle。
以上行为RPM core会自动处理,不需要在设备驱动中进行操作。
device的runtime status及其初始状态
在Runtime Power Management的过程中,device可处于四种状态:RPM_ACTIVE、RPM_RESUMING、RPM_SUSPENDED和RPM_SUSPENDING。
- RPM_ACTIVE,设备处于正常工作的状态,表示设备的.runtime_resume回调函数执行成功;
- PRM_SUSPENDED,设备处于suspend状态,表示设备.runtime_suspend回调函数执行成功;
- RPM_RESUMING,设备的.runtime_resume正在被执行;
- RPM_SUSPENDING,设备的.runtime_suspend正在被执行;
- 注:.runtime_idle只是suspend前的过渡,因此runtime_status和idle无关。
device注册时,设备模型代码会调用pm_runtime_init接口,将设备的runtime status初始化为RPM_SUSPENDED,而kernel并不知道某个设备初始化时的真正状态,因此设备驱动需要根据实际情况,调用RPM的helper函数,将自身的status设置正确。
RPM使用流程
从驱动开发的角度来看如何使用RPM。
1. 当驱动对设备进行初始化时,调用pm_runtime_enable(),使能设备的runtime pm功能。
/**
* pm_runtime_enable - Enable runtime PM of a device.
* @dev: Device to handle.
*/
void pm_runtime_enable(struct device *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->power.lock, flags);
if (dev->power.disable_depth > 0) {
dev->power.disable_depth--; //disable_depth 初始值为1
...;
} else {
dev_warn(dev, "Unbalanced %s!\n", __func__);
}
...;
spin_unlock_irqrestore(&dev->power.lock, flags);
}
/*
核心操作是将dev->power.disable_depth的值减一,该值是在系统启动初期,
对每一个device进行初始化时,会调用pm_runtime_init函数,然后把该参数设置为1.
在pm_runtime_init 函数中,还将device 的初始RPM状态设置为RPM_SUSPENDED,
代表设备当前处于休眠的状态,还不可以访问。
*/
2.当driver认为其设备需要进行工作(打开)时,调用pm_rumtime_get()/ pm_rumtime_get_sync (),向RPM core上报设备状态为开,若条件满足,RPM core会执行到driver的runtime_resume回调函数,配置设备为工作的状态。对应的API如下所示:
static inline int pm_runtime_get(struct device *dev);
static inline int pm_runtime_get_sync(struct device *dev);
可以从名字看出,以上两个API的区别,在于pm_rumtime_get()用于异步操作,而pm_rumtime_get_sync ()用于同步操作。如果你期望在执行get操作后立马访问设备,那么这里推荐你使用pm_rumtime_get_sync ()接口,因为pm_rumtime_get()接口会把一些核心的操作放到一个工作队列(workqueue)中延后执行。
3.当driver认为其设备不需要进行工作(关闭)时,则调用pm_rumtime_put()/ pm_rumtime_put_sync (),向RPM core上报设备状态为关,若条件满足,RPM core会执行到driver的runtime_suspend回调函数,配置设备为关闭的状态。对应的API如下所示:
static inline int pm_runtime_put(struct device *dev);
static inline int pm_runtime_put_sync(struct device *dev);
使用总结:driver 在使用 RPM 功能前,需要通过pm_rumtime_enable(),使能设备的runtime pm功能;当驱动认为设备可以睡眠时,就调用pm_rumtime_put()/ pm_rumtime_put_sync ();当驱动认为设备需要工作时,就调用pm_rumtime_get()/ pm_rumtime_get_sync ()。
核心API
RPM提供的API位于“include/linux/pm_runtime.h”中。
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完成。
*/
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_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()
*/
extern void pm_runtime_get/pm_runtime_put(异步请求增加/减少引用计数)
extern void pm_runtime_get_sync/pm_runtime_put_sync(同步请求增加/减少引用计数)
extern void pm_runtime_set_active/pm_runtime_set_suspended(设置设备的runtime运行状态)
extern void pm_schedule_suspend(在指定时间之后suspend)
/*
以上接口都比较简单,最终会调用
__pm_runtime_resume/__pm_runtime_suspend/__pm_runtime_idle接口中。
*/
以__pm_runtime_resume为例进行分析