- 了解Runtime PM
1.Runtime PM 软件框架
device Driver(或者driver所在的bus、class等)需要提供3个回调函数,如下所示:
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。它们一般由RPM core在合适的时机调用,以便降低device的power consumption。
而调用的时机,最终是由device driver决定的。driver会在适当的操作点,调用RPM core提供的put和get系列的helper function,汇报device的当前状态。RPM core会为每个device维护一个引用计数,get时增加计数值,put时减少计数值,当计数为0时,表明device不再被使用,可以立即或一段时间后suspend,以节省功耗。
1.1.Device States
Runtime PM Framework使用rpm_status枚举类型表示device的状态
enum rpm_status {
RPM_ACTIVE = 0,
RPM_RESUMING,
RPM_SUSPENDED,
RPM_SUSPENDING,
};
- RPM_ACTIVE: 设备处于正常工作状态,处于全速全进状态。runtime_resum 的回调执行完毕。
- RPM_RESUMING: 设备状态正在从suspend到active转换。 runtime_resume的回调正在执行。
- RPM_SUSPENDED: 设备处于低功耗状态,不能处于IO操作。runtime_suspend的回调执行完毕。
- RPM_SUSPENDING: 设备状态正从active到suspend状态转换。runtime_suspend回调正在执行。
1.2.Runtime PM请求类型
因为使设备进入suspend或者resume状态,有同步和异步的方式。通常在异步的时候会用到workqueue,这时候就会用到设备的请求类型。
enum rpm_request {
RPM_REQ_NONE = 0,
RPM_REQ_IDLE,
RPM_REQ_SUSPEND,
RPM_REQ_AUTOSUSPEND,
RPM_REQ_RESUME,
};
- RPM_REQ_NONE: Do nothing
- RPM_REQ_IDLE: 运行设备的runtime_idle回调。
- RPM_REQ_SUSPEND: 运行设备的runtime_suspend回调。
- RPM_REQ_AUTOSUSPEN: 在一段时间之后运行设备的runtime_suspend回调。
- RPM_REQ_RESUME: 运行设备的runtime_resume回调。
1.3.Runtime PM数据段
在每个device结构中都存在dev_pm_info的结构,此结构中通过CONFIG_PM_RUNTIME配置字段代码了Runtime PM的信息。
具体说明参考:Documentation/power/runtime_pm.txt
struct dev_pm_info {
....
struct timer_list suspend_timer;
unsigned long timer_expires;
struct work_struct work;
wait_queue_head_t wait_queue;
atomic_t usage_count;
atomic_t child_count;
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: 延迟时间,用于自动休眠。
在device register的时候,进行dev_pm_info初始化。具体如下所示:
platform_device_register
-> device_initialize
-> device_pm_init
->device_pm_init_common
->device_pm_sleep_init
-> pm_runtime_init
-> INIT_WORK(&dev->power.work, pm_runtime_work);
初始化 dev_pm_info结构体:
1564 void pm_runtime_init(struct device *dev)
1565 {
1566 dev->power.runtime_status = RPM_SUSPENDED;
1567 dev->power.idle_notification = false;
1568
1569 dev->power.disable_depth = 1;
1570 atomic_set(&dev->power.usage_count, 0);
1571
1572 dev->power.runtime_error = 0;
1573
1574 atomic_set(&dev->power.child_count, 0);
1575 pm_suspend_ignore_children(dev, false);
1576 dev->power.runtime_auto = true;
1577
1578 dev->power.request_pending = false;
1579 dev->power.request = RPM_REQ_NONE;
1580 dev->power.deferred_resume = false;
1581 INIT_WORK(&dev->power.work, pm_runtime_work);
1582 ...
1583 }
-
dev->power.runtime_status = RPM_SUSPENDED;
- the runtime PM status of the device; this field’s initial value is RPM_SUSPENDED, which means that each device is initially regarded by the PM core as ‘suspended’, regardless of its real hardware status;
-
dev->power.disable_depth = 1;
- used for disabling the helper functions (they work normally if this is equal to zero); the initial value of it is 1 (i.e. runtime PM is initially disabled for all devices);
-
atomic_set(&dev->power.usage_count, 0);
- the usage counter of the device,set value is 0;
-
dev->power.runtime_error = 0;
- if set, there was a fatal error , so the helper functions will not work until this flag is cleared; this is the error code returned by the failing callback;
2.Runtime PM 运行机制
- 每个设备都维护一个usage_count变量,用于记录该设备的使用情况。当大于0的时候说明该设备在使用,当等于0的时候说明该设备没在使用。
- 需要使用该设备的时候,设备驱动调用pm_runtime_get/pm_runtime_get_sync接口,增加变量usage_count的值;不再使用该设备的时候,调用pm_runtime_put/pm_runtime_put_sync接口,减小usage_count变量的值。
- 每次调用get接口的时候,Runtime PM framework会判断该设备的状态。如果该不是active状态,则使用异步(ASYNC)或者同步(SYNC)方式调用runtime_resume回调函数,唤醒设备。
- 每次调用put接口的时候,Runtime PM framewokr会判断设备的引用计数,如果为零,则使用异步(ASYNC)或者同步(SYNC)方式调用runtime_idle回调函数。
- 为了防止频繁suspend,在suspend前面引入了idle状态。当设备处于idle状态之后,会在合适的时间调用suspend回调函数。通常都会通过runtime pm helper function启动一个timer,设置超时时间,在超时之后调用runtime_suspend回调函数。
3.常用APIs
3.1.基本APIs
-
void pm_runtime_init(struct device * dev);
初始化dev_pm_info结构中的设备运行时PM字段。 -
void pm_runtime_remove(struct device *dev);
确保设备的运行时PM在该设备从设备层次删除后将被禁用。 -
int pm_runtime_idle(struct device *dev);
执行子系统级的设备空闲回调,返回0成功,或失败的错误代码,其中的-EINPROGRESS 表示->runtime_idle()已经在执行。 -
int pm_runtime_suspend(struct device *dev);
对设备执行子系统级的挂起回调;返回0表示成功;如果设备的运行时PM状态已经是“挂起”则返回1;或失败时返回错误代码,其中,-EAGAIN或-EBUSY意味着企图在未来再次挂起设备是安全的。 -
int pm_runtime_autosuspend(struct device*dev);
与pm_runtime_suspend()相同,除了考虑了自动休眠延迟时间;如果pm_runtime_autosuspend_expiration()说该延迟尚未到期,那么就会调度适当时间的自动休眠功能,并返回0。 -
int pm_runtime_resume(struct device *dev);
对设备执行子系统级的恢复回调;返回0表示成功;如果设备的运行时PM状态已经是“活跃的(active)”就返回1;或失败时错误代码,其中-EAGAIN意味着在未来试图恢复设备可能是安全的;但应附加对‘power.runtime_error’进行检查。 -
int pm_request_idle(struct device *dev);
对设备提交一个执行子系统级的空闲回调的请求(请求由一个pm_wq的工作项代表);返回0表示成功,或如果请求没有排队成功就返回错误代码。 -
int pm_request_autosuspend(struct device*dev);
调度子系统级的挂起回调函数,使其在设备的自动休眠延迟(autosuspend delay)过期时执行;如果延迟已过期,则工作项立即被排队。 -
int pm_schedule_suspend(struct device *dev,unsigned int delay);
调度在未来执行设备的子系统级的挂起回调,其中“delay”是在pm_wq上排队挂起回调工作项之前等待的时间,以毫秒为单位(如果“delay”是零,工作项马上进行排队);返回0表示成功;如果该设备的运行时PM状态已经是“挂起”时返回1;或在当请求没有被调度(或 “delay”为0时被排队)时返回错误代码;如果->runtime_suspend()的执行已经被调度但尚未到期,则“delay”的新值将被用来作为等待的时间。 -
int pm_request_resume(struct device *dev);
对设备提交一个执行子系统级恢复回调的请求(该请求由一个pm_wq中的工作项代表);成功返回0;如果设备的运行时PM状态已经是”活跃的(active)“则返回1;或当请求没有被排上队时返回错误代码。 -
void pm_runtime_get_noresume(struct device*dev);
递增设备的使用计数。 -
int pm_runtime_get(struct device *dev);
递增设备的使用计数,运行__pm_request_resume(dev),并返回其结果。 -
int pm_runtime_get_sync(struct device *dev);
递增设备的使用计数,运行__pm_runtime_resume(dev),并返回其结果。 -
void pm_runtime_put_noidle(struct device*dev);
递减设备的使用计数。 -
int pm_runtime_put(struct device *dev);
设备的使用计数减1,如果结果是0,则运行pm_request_idle(dev)并返回其结果。 -
int pm_runtime_put_autosuspend(struct device*dev);
设备的使用计数减1,如果结果是0,则运行pm_request_autosuspend(dev)并返回其结果。 -
int pm_runtime_put_sync(struct device *dev);
设备的使用计数减1,如果结果是0,则运行pm_runtime_idle(dev)并返回其结果。 -
int pm_runtime_put_sync_suspend(struct device*dev);
设备的使用计数减1,如果结果是0,则运行pm_runtime_suspend(dev)并返回其结果。 -
int pm_runtime_put_sync_autosuspend(structdevice *dev);
设备的使用计数减1,如果结果是0,则运行pm_runtime_autosuspend(dev)并返回其结果。 -
void pm_runtime_enable(struct device *dev);
使能运行时PM的辅助函数,使其能运行第2节中所描述的设备的总线类型的运行时PM回调。 -
int pm_runtime_disable(struct device *dev);
防止运行时PM辅助函数运行设备的子系统级的运行时PM回调,确保设备的所有等待中的运行时PM操作已完成或取消;如果有一个恢复请求正在等待,且为了满足该请求而执行设备的子系统级的恢复回调是必要的,则返回1;否则返回0。 -
void pm_suspend_ignore_children(struct device*dev, bool enable);
设置/取消设备的power.ignore_children标志。 -
int pm_runtime_set_active(struct device*dev);
清除设备的“power.runtime_error”标志,设置设备的运行时PM状态为”活跃的(active)“,并更新其父设备的”活跃子设备“计数(唯一有效的使用此函数的条件是,如果“power.runtime_error”被设置,或者“power.disable_depth”大于零);如果设备的父设备是不活跃的,且其“power.ignore_children”标志没有设置,该函数就会失败并返回错误代码。 -
void pm_runtime_set_suspended(struct device *dev);
清除设备的“power.runtime_error”标志,设置设备的运行时PM状态为“挂起”,并恰当更新其父设备的“活跃的子设备”计数(此函数唯一有效的使用条件是,如果“power.runtime_error”被设置,或“power.disable_depth”大于零)。 -
bool pm_runtime_suspended(struct device*dev);
如果该设备的运行时PM状态为“挂起”且其“power.disable_depth”字段等于0,返回true;否则返回false。 -
void pm_runtime_allow(struct device *dev);
设置设备的power.runtime_auto标志,并递减其使用计数(用于/sys/devices/…/power/control接口,实际上允许使设备在运行时被电源管理)。 -
void pm_runtime_forbid(struct device *dev);
取消设置设备的power.runtime_auto标志,并递增其使用计数(用于/sys/devices/…/power/control接口,实际上禁止设备在运行时被电源管理)。 -
void pm_runtime_no_callbacks(struct device*dev);
设置设备的power.no_callbacks标志,并从/sys/devices/…/power中删除运行时PM属性(或防止设备在注册时添加他们)。 -
void pm_runtime_irq_safe(struct device *dev);
设置设备的power.irq_safe标志,造成运行时PM挂起和恢复回调在禁止中断的情况下被调用(但不包括空闲回调)。 -
void pm_runtime_mark_last_busy(struct device*dev);
设置power.last_busy字段为当前时间。 -
void pm_runtime_use_autosuspend(struct device*dev);
设置power.use_autosuspend标志,使能自动休眠延迟。 -
void pm_runtime_dont_use_autosuspend(structdevice *dev);
清除power.use_autosuspend标志,禁用自动休眠延迟。 -
void pm_runtime_set_autosuspend_delay(structdevice *dev, int delay);
设置power.autosuspend_delay的值为“delay”(以毫秒为单位),如果“delay”是负的,则防止运行时挂起。 -
unsigned long pm_runtime_autosuspend_expiration(struct device *dev);
基于power.last_busy和power.autosuspend_delay计算当前自动休眠延迟的到期时间;如果延迟时间是1000毫秒或更大,则到期时间四舍五入精确到秒(rounded up);如果延迟时间已经过期或power.use_autosuspend没有设置,则返回0;否则返回以jiffies计的过期时间。
3.2. 基础APIs
这三个函数是RPM的idle、put/suspend、get/resume等操作的基础,根据rpmflag,有着不同的操作逻辑。后续很多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);
3.3. RPM enable/disable
- extern void pm_runtime_enable(struct device *dev);
- extern void pm_runtime_disable(struct device *dev);
最初,所有设备的RPM被禁用,这意味着3.1.中描述的大部分的运行时PM辅助函数将返回-EAGAIN,直到为设备调用pm_runtime_enable()之后。
设备RPM功能的enable/disable,可嵌套调用,会使用一个变量(dev->power.disable_depth)记录disable的深度。只要disable_depth大于零,就意味着RPM功能不可使用,很多的API调用(如suspend/reesume/put/get等)会返回失败。
RPM初始化时,会将所有设备的disable_depth置为1,也就是disable状态,driver初始化完毕后,要根据设备的时机状态,调用这两个函数,将RPM状态设置正确。
3.4.Autosuspend
- 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的超时值:
- 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);
3.5.增加/减少设备的使用计数
- static inline int pm_runtime_get(struct device *dev)
- static inline int pm_runtime_put(struct device *dev)
增加/减少设备的使用计数,并判断是否为0,如果为零,尝试调用设备的idle callback,如果不为零,尝试调用设备的resume callback。
和上面类似,只不过为同步调用:
- 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)
3.6.设置device RPM state
- int pm_runtime_set_active(struct device *dev);
- void pm_runtime_set_suspended(struct device *dev);
4.Runtime Sys接口
提供五个属性,分别为control, runtime_susupend_time, runtime_active_time, autosuspend_delay_ms,runtime_status。
-
/sys/devices/…/power/control
- on - 调用pm_runtime_forbid接口,增加设备的引用计数,然后resume设备。
- auto - 调用pm_runtime_allow接口,减少设备的引用计数,如果设备的引用计数为0,则idle设备。
-
/sys/devices/…/power/runtime_status
- active - 设备的状态是正常工作状态。
- suspend- 设备的状态是低功耗模式。
- suspending-设备的状态正在从active->suspend转化。
- resuming-设备的状态正在从suspend->active转化。
- error-设备runtime出现错误,此时runtime_error的标志置位。
- unsupported-设备的runtime 没有使能,此时disable_depth标志置位。
-
/sys/devices/…/power/runtime_suspend_time
- 设备在suspend状态的时间
-
/sys/devices/…/power/runtime_active_time
- 设备在active状态的时间
-
/sys/devices/…/power/autosuspend_delay_ms
- 设备在idle状态多久之后suspend,设置延迟suspend的延迟时间。
5.驱动开发常用API
- pm_runtime_enable(使能设备的runtime pm)
- pm_runtime_get/pm_runtime_put(异步请求增加/减少引用计数)
- pm_runtime_get_sync/pm_runtime_put_sync(同步请求增加/减少引用计数)
- pm_runtime_set_active/pm_runtime_set_suspended(设置设备的runtime运行状态)
- pm_schedule_suspend(在指定时间之后suspend)
对于runtime PM,默认状态下设备的状态是suspend,如果硬件上它是运行状态,需要调用pm_runtime_set_active()来修改它的状态,然后调用 pm_runtime_enable()来使能runtime PM。一般是在probe()的结尾处使用,而pm_runtime_disable()一般在驱动remove中调用。
为了不想让设备频繁地开、关,可以使用autosuspend功能,驱动中执行update_autosuspend()来启用autosuspend功能。
6.Example
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/pm_runtime.h>
static int runtime_pm_probe(struct platform_device *pdev){
printk(KERN_EMERG "runtime_pm: runtime_pm_probe!\n");
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
return 0;
}
static int runtime_pm_remove(struct platform_device *pdev){
printk(KERN_EMERG "runtime_pm: runtime_pm_remove!\n");
pm_runtime_disable(&pdev->dev);
return 0;
}
static int runtime_pm_suspend(struct device *dev){
printk(KERN_EMERG "runtime_pm: runtime_pm_suspend!\n");
return 0;
}
static int runtime_pm_resume(struct device *dev){
printk(KERN_EMERG "runtime_pm: runtime_pm_resume!\n");
return 0;
}
static int runtime_pm_idle(struct device *dev){
printk(KERN_EMERG "runtime_pm: runtime_pm_idle\n");
return 0;
}
static const struct dev_pm_ops runtime_pm_ops = {
SET_RUNTIME_PM_OPS(runtime_pm_suspend, runtime_pm_resume, runtime_pm_idle)
};
static void runtime_pm_release(struct device * dev){}
static struct platform_device runtime_device = {
.name = "runtime_device",
.id = -1,
.dev = {
.release = runtime_pm_release,
},
};
static struct platform_driver runtime_driver = {
.probe = runtime_pm_probe,
.remove = runtime_pm_remove,
.driver = {
.owner = THIS_MODULE,
.name = "runtime_device",
.pm = &runtime_pm_ops,
},
};
static int runtime_pm_init(void){
printk(KERN_EMERG "runtime_pm: runtime_pm_init\n");
platform_device_register(&runtime_device);
platform_driver_register(&runtime_driver);
return 0;
}
static void runtime_pm_exit(void){
printk(KERN_EMERG "runtime_pm: runtime_pm_exit\n");
platform_driver_unregister(&runtime_driver);
platform_device_unregister(&runtime_device);
}
module_init(runtime_pm_init);
module_exit(runtime_pm_exit);
MODULE_LICENSE("GPL");
Debug:
如下是测试结果:
1. 查看当前设备的runtime状态
cat /sys/devices/platform/runtime_device/power/runtime_status
suspend
2. 查看设备的runtime_suspend时间
cat /sys/devices/platform/runtime_device/power/runtime_suspended_time
341028
3. 使设备处于active状态
echo on > /sys/devices/platform/runtime_device/power/control
4. 使设备进入suspend状态
echo auto > /sys/devices/platform/runtime_device/power/control
5. 查看转换状态的打印
test:/ # dmesg | grep "runtime"
[ 451.432602] c7 runtime_pm: runtime_pm_resume!
[ 509.842328] c5 runtime_pm: runtime_pm_idle
[ 509.846430] c5 runtime_pm: runtime_pm_suspend!
参考:
- Documentation/power/runtime_pm.txt