Linux电源管理 -- RPM机制

    计算机系统的运行是依靠电能运行的,而能量的获取是有成本的,因此如果能在保证系统运转的基础上,尽量节省对能量的消耗,就会大大提升该系统的生存竞争力。这方面,大自然已经做的很好了,如植物的落叶,如动物的冬眠,等等。而在计算机的世界里(这里以运行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的软件设计框架如下图所示:
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为例进行分析
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值