Linux PM core - Runtime PM

mac2024-03-11  26

了解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 nothingRPM_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
最新回复(0)