[Linux][Power]RuntimePM功能

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*/
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值