电源管理入门:驱动Runtime PM管理

电子说

1.2w人已加入

描述

Runtime PM管理也就是设备驱动里面的电源管理,即设备驱动结构体里面的struct dev_pm_ops,只控制设备自己的电源。这样可以在设备不需要工作的时候可以进入到低功耗状态,更好的管理设备自己的电源,所谓:“各扫门前雪”。

为什么需要Runtime PM?

不同于系统的电源管理,设备自己的电源管理更加的细化。这就像一个层级关系,系统整体的是一个大的电源状态管理,但是对于众多的集成外国设备也不能一刀切,就是不能要干活都干活要休息都休息,要细化管理不能懒政,就对每个设备自己也来一套电源状态管理,直接把机制从系统哪里复制过来一份一个阉割版的就够用,采用分而治之的思想,只要系统要统一指挥的时候听话就可以,其他时候可以自己决策执行就是runtime PM管理。这里的设备有可能是外设,比如sensor、lcdc等。这里的设备也有可能是SOC内部的某些IP,比如codec、dsp、usb等。

1. 框架介绍

1.1 为什么需要Runtime PM Framework?

系统基本的电源管理,例如关机休眠等,需要调用device的电源Runtime API就是ops回调函数,而且需要按一个顺序的queue去实施,而且系统跟设备状态发生冲突的时候也需要去处理,综上就需要一个Framework去统一做这些事情

设备驱动需要根据系统的一些参数来决定自己的电源状态,例如CPU是否idle等,就需要系统框架的支持

当设备处于低功耗模式时,wakeup signal常常需要platform或者bus的支持。

1.2 系统框架图

runtime

数据结构:

runtime

image.png

关机举例:

runtime

休眠举例:

runtime

2. Drivers

Device drivers(包括bus、class、power domain)实现了runtime pm相关的runtime_idle/runtime_suspend/runtime_resume三个回调:

runtime_suspend用于实现设备的低功耗操作

runtime_resume用于实现设备的低功耗恢复相关的操作

runtime_idle属于runtime_suspend的一个过渡,用于缓冲频繁的suspend与resume,它会判断设备是否具备suspend的条件,如果具备在合适的时机,就会suspend设备。

runtime_suspend与runtime_resume回调函数里会调用clock framework/reset framework/regulator framework提供的时钟开关、复位、电源开关的接口。这里以SPI驱动为例进行说明:

 

 

subsys_initcall(pl022_init);

static int __init pl022_init(void)
{
        return amba_driver_register(&pl022_driver);
}

static struct amba_driver pl022_driver = {
        .drv = {
                .name        = "ssp-pl022",
                .pm        = &pl022_dev_pm_ops,
        },
        .id_table        = pl022_ids,
        .probe                = pl022_probe,
        .remove                = pl022_remove,
};

static const struct dev_pm_ops pl022_dev_pm_ops = {
        SET_SYSTEM_SLEEP_PM_OPS(pl022_suspend, pl022_resume)
        SET_RUNTIME_PM_OPS(pl022_runtime_suspend, pl022_runtime_resume, NULL)
};
                         
#define SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) 
        .runtime_suspend = suspend_fn, 
        .runtime_resume = resume_fn, 
        .runtime_idle = idle_fn,

 

 

pm结构体dev_pm_ops 中的有3个以runtime开头的成员函数:runtime_suspend()、runtime_resume()和runtime_idle(),它们辅助设备完成运行时的电源管理

 

 

struct dev_pm_ops {
    ...
    int (*runtime_suspend)(struct device *dev);
    int (*runtime_resume)(struct device *dev);
    int (*runtime_idle)(struct device *dev);
    ...
};

 

 

运行时的PM与前文描述的系统级挂起到RAM时候的PM不太一样,它是针对单个设备,指系统在非睡眠状态的情况下,某个设备在空闲时可以进入运行时挂起状态,而在不是空闲时执行运行时恢复使得设备进入正常工作状态,如此,这个设备在运行时会省电。

runtime

每个设备处理好自己的电源管理,在不需要工作时进入低功耗状态。也就是"各人自扫门前雪"。Linux提供了一系列API,以便于设备可以声明自己的运行时PM状态:

 

函数名字 功能
pm_runtime_suspend 引发设备的挂起,执行相关的runtime_suspend()函数。
pm_schedule_suspend “调度”设备的挂起,延迟delay毫秒后将挂起工作挂入pm_wq等待队列,结果等价于delay毫秒后执行相关的runtime_suspend()函数。
pm_runtime_resume 引发设备的恢复,执行相关的runtime_resume()函数。
pm_request_resume 发起一个设备恢复的请求,该请求也是挂入pm_wq等待队列。
pm_runtime_idle 引发设备的空闲,执行相关的runtime_idle()函数。
pm_request_idle 发起一个设备空闲的请求,该请求也是挂入pm_wq等待队列。
pm_runtime_enable 使能设备的运行时PM支持。
pm_runtime_disable 禁止设备的运行时PM支持。
pm_runtime_getpm_runtime_get_sync 增加设备的引用计数(usage_count),这类似于clk_get(),会间接引发设备的runtime_resume()。
pm_runtime_putpm_runtime_put_sync 减小设备的引用计数,这类似于clk_put(),会间接引发设备的runtime_idle()。

 

3. Runtime PM core

Runtime pm core主要提供了三类函数接口:

提供enable/disable接口给设备驱动,用于该设备驱动决定是否打开或关闭RPM,

提供get、put类接口给设备驱动,用于决定什么时候进入或者恢复设备低功耗,

在设备驱动调用了get、put接口后RPM会调用各设备驱动实现的runtime_suspend/runtime_resume接口

对于决定设备是否进入低功耗的get/put接口的调用时机,一般会在操作设备相关寄存器前调用get接口,在操作完相关寄存器后调用put接口。或者在设备驱动的open、release、start、stop等接口里调用,用户层的services通过ioctrl或者驱动提供的文件节点调用驱动的这些接口。

我们可以这样简单地理解Linux运行时PM的机制,每个设备(总线的控制器自身也属于一个设备)都 有引用计数usage_count和活跃子设备(Active Children,子设备的意思就是该级总线上挂的设备)计数child_count,当两个计数都为0的时候,就进入空闲状态,调用pm_request_idle(dev)。

当设备进入空闲状态,与pm_request_idle(dev)对应的PM核并不一定直接调用设备驱动的runtime_suspend(),它实际上在多数情况下是调用与该设备对应的bus_type的runtime_idle()。

在具体的设备驱动中,一般的用法则是在设备驱动probe()时运行pm_runtime_enable()使能运行时PM支持,在运行过程中动态地执行“pm_runtime_get_xxx()->做工作->pm_runtime_put_xxx()”的序列。如代码清单19.19中的drivers/watchdog/omap_wdt.c OMAP的看门狗驱动。

在omap_wdt_start()中启动了pm_runtime_get_sync(),

而在omap_wdt_stop()中调用了pm_runtime_put_sync()。

 

 

static const struct watchdog_ops omap_wdt_ops = {
        .owner                = THIS_MODULE,
        .start                = omap_wdt_start,
        .stop                = omap_wdt_stop,
        .ping                = omap_wdt_ping,
        .set_timeout        = omap_wdt_set_timeout,
        .get_timeleft        = omap_wdt_get_timeleft,
};

static int omap_wdt_start(struct watchdog_device *wdog)
{

    pm_runtime_get_sync(wdev->dev);//告诉内核要开始用看门狗这个设备了,如果看门狗设备已经进入省电模式(之前引用计数为0且执行了运行时挂起),会导致该设备的运行时恢复
    
static int omap_wdt_stop(struct watchdog_device *wdog)
{
    pm_runtime_put_sync(wdev->dev);//告诉内核不用这个设备了,如果引用计数变为0且活跃子设备为0,则导致该看门狗设备的运行时挂起。

 

 

在一些设备上不使用的时候不能立即挂起,,因为挂起状态的进入和恢复需要一些时间,如果设备不在挂起之间保留一定的时间,频繁进出挂起反而会带来新的开销。因此,我们可根据情况决定只有设备在空闲了一段时间后才进入挂起(一般来说,一个一段时间没有被使用的设备,还会有一段时间不会被使用),基于此,一些设备驱动也常常使用自动挂动模式进行编程。

runtime

在执行操作的时候声明pm_runtime_get(),操作完成后执行pm_runtime_mark_last_busy()和pm_runtime_put_autosuspend(),一旦自动挂动的延时到期且设备的使用计数为0,则引发相关runtime_suspend()入口函数的调用。

设备驱动PM成员的runtime_suspend()一般完成保存上下文、切到省电模式的工作,而runtime_resume()一般完成对硬件上电、恢复上下文的工作

4. power domain framework

一个power domain上可能包含多个IP,每个IP可能对应一个或多个设备。这些设备会在dts中描述与power domain的绑定关系。系统初始化的时候,会将这个power domain放到一个链表中,然后根据设备中dts描述的与power domain的关系,将设备挂在power domain节点下的链表中。

当某个设备驱动通过put接口调用,将usage_count从1减少到0,这时会先调用power domain注册的runtime_suspend接口,在这个接口中,会先调用该设备驱动的runtime_suspend,然后遍历该power domain下所有的设备是否都允许suspend(各设备驱动的usage_count是否为0),若允许就会直接调用关闭power domian的接口,否则直接返回。当某个设备驱动通过get接口调用,将usage_count从0增加到1,这时会先调用power domain注册的runtime_resume接口,在这个接口中,会先将power domain上电,然后再调用设备驱动对应的runtime_resume回调函数,让设备退出低功耗。

5.  runtime pm的sysfs

对于支持rpm的设备,在相应的设备节点下有多个rpm相关属性的文件节点,分别为control,runtime_susupend_time,runtime_active_time,autosuspend_delay_ms,runtime_status。接口在文件: /kernel/drivers/base/power/sysfs.c中描述。

/sys/devices/.../power/control

on - 调用pm_runtime_forbid接口,增加设备的引用计数,然后resume设备。

auto - 调用pm_runtime_allow接口,减少设备的引用计数,如果设备的引用计数为0,则idle设备。

runtime

/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的延迟时间。

 

后记:

在编写驱动的时候,如果涉及电源管理的功耗需求,就需要实现struct dev_pm_ops,为驱动程序增加一个电源管理的功能,会更加的灵活,也是我们去优化系统功耗的一个重要方向。因为大多数程序估计是供应商提供的,但是我们自己的加的硬件的程序估计是我们自己去写的,并且做业务挺耗电,给自己写的驱动加个电源管理就挺好。

审核编辑:汤梓红
 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分