Linux PM core - Runtime PM

  • 了解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
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值