电源管理入门-16驱动Runtime PM管理

Runtime PM管理也就是设备驱动里面的电源管理,只针对设备自己有效,这样可以在设备不需要工作的时候可以进入到低功耗状态,更好的管理设备自己的电源。

为什么需要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 系统框架图

数据结构:

关机举例:

休眠举例:

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驱动为例进行说明:

ini 复制代码
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(),它们辅助设备完成运行时的电源管理

c 复制代码
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不太一样,它是针对单个设备,指系统在非睡眠状态的情况下,某个设备在空闲时可以进入运行时挂起状态,而在不是空闲时执行运行时恢复使得设备进入正常工作状态,如此,这个设备在运行时会省电。

每个设备处理好自己的电源管理,在不需要工作时进入低功耗状态。也就是"各人自扫门前雪"。 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()。
ini 复制代码
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,则导致该看门狗设备的运行时挂起。 

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

在执行操作的时候声明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设备。

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

6参考:

后记:

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

干啥都能干,干啥啥不是,

专业入门劝退,堪称程序员杂家"。

欢迎各位有自己公众号的留言:申请转载,多谢!

后续会继续更新,纯干货分析,欢迎分享给朋友,欢迎点赞、收藏、在看、划线和评论交流以!

相关推荐
玉红77725 分钟前
R语言的数据类型
开发语言·后端·golang
lvbu_2024war012 小时前
MATLAB语言的网络编程
开发语言·后端·golang
问道飞鱼2 小时前
【Springboot知识】Springboot进阶-实现CAS完整流程
java·spring boot·后端·cas
Q_19284999062 小时前
基于Spring Boot的电影网站系统
java·spring boot·后端
豌豆花下猫2 小时前
Python 潮流周刊#83:uv 的使用技巧(摘要)
后端·python·ai
凡人的AI工具箱2 小时前
每天40分玩转Django:Django部署概述
开发语言·数据库·后端·python·django
SomeB1oody3 小时前
【Rust自学】7.2. 路径(Path)Pt.1:相对路径、绝对路径与pub关键字
开发语言·后端·rust
SomeB1oody3 小时前
【Rust自学】7.3. 路径(Path)Pt.2:访问父级模块、pub关键字在结构体和枚举类型上的使用
开发语言·后端·rust
Bony-3 小时前
Go语言反射从入门到进阶
开发语言·后端·golang
凡人的AI工具箱4 小时前
每天40分玩转Django:Django Email
数据库·人工智能·后端·python·django·sqlite