当前位置: 首页 > news >正文

Linux电源管理(四),设备的Runtime Power Management(RPM)

更多linux系统电源管理相关的内容请看:Linux电源管理、功耗管理 和 发热管理 (CPUFreq、CPUIdle、RPM、thermal、睡眠 和 唤醒)-CSDN博客

1 简介

RPM是Runtime Power Management的简称,即运行时电源管理,可以理解为一套非用即关机制,不依赖于系统的睡眠/唤醒机制,在系统处于运行状态时,也可以对特定模块做上下电处理。通常RPM的suspend和resume回调函数会调用clk和regulator提供的函数,以便对时钟和电源做动态的开关管理从而达到降低功耗的目的。

                《SoC底层软件低功耗系统设计与实现》第8章 RPM框架设计与实现

针对单个设备,指系统在⾮睡眠状态的情况下,某个设备在空闲时可以进⼊运⾏时挂起状态,⽽在不是空闲时执⾏运⾏时恢复使得设备进⼊正常⼯作状态,如此,这个设备在运⾏时会省电。

                《Linux设备驱动开发详解:基于最新的Linux4.0内核》 19.10 运⾏时的PM

本文主要基于linux-5.4.18版本的内核代码进行分析。

2 数据结构

2.1  struct device和struct device_driver

//include/linux/device.h
struct device {......struct dev_pm_info  power;......
};
//include/linux/device.h
struct device_driver {......const struct dev_pm_ops *pm;......
};

2.2  struct dev_pm_info

//include/linux/pm.h
struct dev_pm_info {......unsigned int        async_suspend:1;  //"/sys/devices/xxx/xxx/power/async"......atomic_t        usage_count;atomic_t        child_count;......unsigned int        runtime_auto:1;......enum rpm_status     runtime_status;......
};

 struct dev_pm_info结构体的成员很多,这里简要介绍一些,其他成员会在后面章节介绍。

<1> child_count和usage_count

每个设备(总线的控制器自身也属于一个设备)都有引用计数usage_count和活跃子设备(子设备的意思就是该级总线上挂的设备)计数child_count,当两个计数都为0的时候,就进入空闲状态,调用pm_request_idle()。

增加设备的引用计数

int pm_runtime_get(struct device *dev);
int pm_runtime_get_sync(struct device *dev);

减少引用计数 

int pm_runtime_put(struct device *dev);
int pm_runtime_get_sync(struct device *dev);

查看child_count和usage_count的值

/sys/devices/xxx/xxx/power/runtime_active_kids
/sys/devices/xxx/xxx/power/runtime_usage

<2> runtime_status

请看下面的第3章节

2.3  struct dev_pm_ops

//include/linux/pm.h
struct dev_pm_ops {......int (*runtime_suspend)(struct device *dev);int (*runtime_resume)(struct device *dev);int (*runtime_idle)(struct device *dev);
};

 

3 设备的RPM状态

3.1 表示状态的数据结构

//include/linux/pm.h
struct dev_pm_info {......enum rpm_status     runtime_status;......
};
enum rpm_status {RPM_ACTIVE = 0,RPM_RESUMING,RPM_SUSPENDED,RPM_SUSPENDING,
};

3.2 RPM状态的更新 

__update_runtime_status()函数会更新设备的RPM状态

static void __update_runtime_status(struct device *dev, enum rpm_status status)
{update_pm_runtime_accounting(dev);dev->power.runtime_status = status;
}
rpm_resume();-> __update_runtime_status(dev, RPM_RESUMING);-> rpm_callback();               //调用具体设备的runtime_resume()-> __update_runtime_status(dev, RPM_ACTIVE);
rpm_suspend();-> __update_runtime_status(dev, RPM_SUSPENDING);-> rpm_callback();             //调用具体设备的runtime_suspend()-> __update_runtime_status(dev, RPM_SUSPENDED);

 

3.3 状态切换关系 

                《SoC底层软件低功耗系统设计与实现》 8.1.10 RPM的函数⼯作时序;1.状态机切换

3.4 查看设备的RPM状态

/sys/devices/xxx/xxx/power/runtime_status

# cat  /sys/bus/i2c/devices/i2c-1/device/power/runtime_status 
suspended

4 RPM在设备驱动中的用法

在具体的设备驱动中,一般的用法则是在设备驱动probe()时运行pm_runtime_enable()使能运行时PM支持,在运行过程中动态地执行“pm_runtime_get_xxx()->做工作->pm_runtime_put_xxx()”的序列。

                《Linux设备驱动开发详解:基于最新的Linux4.0内核》 19.10 运⾏时的PM

以eeprom芯片at24的驱动为例: 

//drivers/misc/eeprom/at24.c
at24_probe();-> pm_runtime_set_active(dev);-> pm_runtime_enable(dev);at24_read();-> pm_runtime_get_sync();-> at24_regmap_read();-> pm_runtime_put();at24_write();-> pm_runtime_get_sync();-> at24_regmap_write();-> pm_runtime_put();

5 Autosuspend, or automatically-delayed suspends

5.1 简介

改变设备的电源状态并不是无代价的,它需要时间和精力。一个设备只有被认为在一段时间之内应该待在某个低功耗状态时才会被改变为该目标状态。一个常见的做法是一段时间未使用的设备很可能在最近的未来也保持未使用,根据此建议,直到设备在一段时间内处于inactive状态时驱动程序才允许设备在运行时挂起。即使该做法不是最佳的,但是其在防止设备在低功耗状态和上电状态之间频繁切换时仍然起到了一定的作用。

autosuspend并不意味着设备被自动挂起,而是意味着runtime suspend将会自动被延时执行,直到设置的期望的inactivity时间耗尽(由定时器实现)。

inactivity是基于power.last_busy这个域来决策的。在处理完I/O事件后驱动程序应该调用pm_runtime_mark_last_busy()来更新这个域,通常是在调用pm_runtime_put_autosuspend()的前一步执行。inactivity的周期时长与具体的策略有关。可以通过调用pm_runtime_set_autosuspend_delay()来设置一个初始值,但是设备注册完成后,这个时长就需要用户空间来控制,可以通过修改/sys/devices/…/power/autosuspend_delay_ms来实现。

为了使用autosuspend,子系统或者驱动必须要调用pm_runtime_use_autosuspend()

                《SoC底层软件低功耗系统设计与实现》 8.1.5 autosuspend与automatically-delayed suspends分析
                Documentation/power/runtime_pm.rst

5.2 数据结构

//include/linux/pm.h
struct dev_pm_info {......struct hrtimer      suspend_timer;unsigned long       timer_expires;unsigned int        use_autosuspend:1;unsigned int        timer_autosuspends:1;int         autosuspend_delay;......
};

5.3 高精度定时器(suspend_timer) 

5.3.1 简介

autosuspend功能需要延时一段时间后再执行设备的runtime suspend,内核代码中通过高精度定时器(suspend_timer)来完成这个延时工作。

5.3.2 定时器初始化 和 启动

初始化

pm_runtime_init();-> hrtimer_init(&dev->power.suspend_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);-> dev->power.suspend_timer.function = pm_suspend_timer_fn;

启动定时器

方法一、调用pm_schedule_suspend();

pm_schedule_suspend();-> hrtimer_start(&dev->power.suspend_timer, expires, HRTIMER_MODE_ABS);

方法二(autosuspend)、在rpm_suspend()中启动

static int rpm_suspend(struct device *dev, int rpmflags)__releases(&dev->power.lock) __acquires(&dev->power.lock)
{....../* If the autosuspend_delay time hasn't expired yet, reschedule. */if ((rpmflags & RPM_AUTO)&& dev->power.runtime_status != RPM_SUSPENDING) {u64 expires = pm_runtime_autosuspend_expiration(dev);if (expires != 0) {/* Pending requests need to be canceled. */dev->power.request = RPM_REQ_NONE;/** Optimization: If the timer is already running and is* set to expire at or before the autosuspend delay,* avoid the overhead of resetting it.  Just let it* expire; pm_suspend_timer_fn() will take care of the* rest.*/if (!(dev->power.timer_expires &&dev->power.timer_expires <= expires)) {/** We add a slack of 25% to gather wakeups* without sacrificing the granularity.*/u64 slack = (u64)READ_ONCE(dev->power.autosuspend_delay) *(NSEC_PER_MSEC >> 2);dev->power.timer_expires = expires;hrtimer_start_range_ns(&dev->power.suspend_timer,        //启动高精度定时器suspend_timerns_to_ktime(expires),slack,HRTIMER_MODE_ABS);}dev->power.timer_autosuspends = 1;goto out;}}......
};

5.3.3 设置延时的时间(autosuspend_delay)

驱动初始化时可以调用pm_runtime_set_autosuspend_delay()来设置autosuspend_delay的初始值。系统启动后,可以通过/sys/devices/…/power/autosuspend_delay_ms文件修改autosuspend_delay的值

void pm_runtime_set_autosuspend_delay(struct device *dev, int delay)
{int old_delay, old_use;spin_lock_irq(&dev->power.lock);old_delay = dev->power.autosuspend_delay;old_use = dev->power.use_autosuspend;dev->power.autosuspend_delay = delay;                //设置autosuspend_delay的值update_autosuspend(dev, old_delay, old_use);spin_unlock_irq(&dev->power.lock);
}
EXPORT_SYMBOL_GPL(pm_runtime_set_autosuspend_delay);

修改/sys/devices/…/power/autosuspend_delay_ms文件的值时,对应的代码流程如下:

autosuspend_delay_ms_store();-> pm_runtime_set_autosuspend_delay();

5.4 驱动实例

以I2C总线控制器驱动为例 

初始化

dw_i2c_plat_probe();-> pm_runtime_set_autosuspend_delay(&pdev->dev, 1000);-> pm_runtime_use_autosuspend(&pdev->dev);-> pm_runtime_set_active(&pdev->dev);-> pm_runtime_enable(&pdev->dev);

发送数据 

i2c_transfer();-> __i2c_transfer();-> adap->algo->master_xfer();-> i2c_dw_xfer();-> pm_runtime_get_sync();-> i2c_dw_xfer_init(dev);           /* Start the transfers */-> wait_for_completion_timeout();   /* Wait for tx to complete */-> pm_runtime_mark_last_busy();-> pm_runtime_put_autosuspend();

 

6 用户层直接控制RPM状态

6.1 数据结构

//include/linux/pm.h
struct dev_pm_info {......unsigned int        runtime_auto:1;......
};

unsigned int runtime_auto

if set, indicates that the user space has allowed the device driver to power manage the device at run time via the /sys/devices/.../power/control `interface;` it may only be modified with the help of the pm_runtime_allow() and pm_runtime_forbid() helper functions
                Documentation/power/runtime_pm.rst

Each device in the driver model has a flag to control whether it is subject to runtime power management.  This flag, :c:member:`runtime_auto`, is initialized by the bus type (or generally subsystem) code using :c:func:`pm_runtime_allow()` or :c:func:`pm_runtime_forbid()`; the default is to allow runtime power management.

The setting can be adjusted by user space by writing either "on" or "auto" to the device's :file:`power/control` sysfs file.  Writing "auto" calls :c:func:`pm_runtime_allow()`, setting the flag and allowing the device to be runtime power-managed by its driver.  Writing "on" calls :c:func:`pm_runtime_forbid()`, clearing the flag, returning the device to full power if it was in a low-power state, and preventing the device from being runtime power-managed.  User space can check the current value of the :c:member:`runtime_auto` flag by reading that file.

The device's :c:member:`runtime_auto` flag has no effect on the handling of system-wide power transitions.  In particular, the device can (and in the majority of cases should and will) be put into a low-power state during a
system-wide transition to a sleep state even though its :c:member:`runtime_auto` flag is clear.
                Documentation/driver-api/pm/devices.rst

6.2 用户层控制接口

/sys/devices/xxx/xxx/power/control

# cat  /sys/bus/i2c/devices/i2c-1/device/power/control 
auto
# echo on >  /sys/bus/i2c/devices/i2c-1/device/power/control

7 RPM接口函数

7.1 同步接口函数(立即执行)

                《SoC底层软件低功耗系统设计与实现》 8.1.8 主要函数介绍 

7.2 异步接口函数(延后执行)

                《SoC底层软件低功耗系统设计与实现》 8.1.8 主要函数介绍 

8 调试

8.1 /sys/kernel/debug/tracing/events/rpm/

8.2 /sys/devices/xxx/xxx/power/

8.2.1 runtime_enabled

在设备驱动probe()时运行pm_runtime_enable()使能运行时PM支持,可以通过runtime_enabled文件查看设备的RPM是否enable,例如:

# cat /sys/bus/i2c/devices/i2c-1/power/runtime_enabled 
enabled

8.2.2 runtime_active_time和runtime_suspended_time

统计设备处于active和suspend状态的时间,单位是毫秒。

# cat  /sys/bus/i2c/devices/i2c-1/device/power/runtime_suspended_time 
51992008
# cat  /sys/bus/i2c/devices/i2c-1/device/power/runtime_active_time 
1638828

8.2.3 runtime_active_kids和runtime_usage

请看2.2章节

8.2.4 autosuspend_delay_ms

请看5.3.3章节

8.2.5 runtime_status

请看3.4章节

8.2.6 control

请看6.2 章节

相关文章:

  • 网络知识:路由器静态路由与动态路由介绍
  • YCDISM2025-更新
  • 接口测试和单元测试详解
  • connection.cursor() 与 models.objects.filter
  • [web]攻防世界 easyphp
  • [U-Net]DA-TRANSUNET
  • 前端数据库缓存
  • onnx注册cpu版flashattention
  • springboot基于hadoop的酷狗音乐爬虫大数据分析可视化系统(源码+lw+部署文档+讲解),源码可白嫖!
  • 自动化测试概念及常用函数篇 [软件测试 基础]
  • GIT 使用小记
  • C++ 类与对象(上):从基础定义到内存布局的深度解析
  • 直播预告 |【仓颉社区】第32期WORKSHOP
  • 02_java的运行机制以及JDKJREJVM基本介绍
  • 视频汇聚平台EasyCVR赋能高清网络摄像机:打造高性价比视频监控系统
  • Python基础语法:查看数据的类型type(),数据类型转换,可变和不可变类型
  • 如何使用无线远程控制模块来实现rs-485无线控制?
  • 4.24工作总结
  • 安全生产知识竞赛活动方案流程规则
  • Linux内核之struct pt_regs结构
  • 30天内三访中国,宝马董事长:没有一家公司可以在全球价值链外独立运行
  • 政治局会议:根据形势变化及时推出增量储备政策,加强超常规逆周期调节
  • 公安部知识产权犯罪侦查局:侦破盗录传播春节档院线电影刑案25起
  • 生于1982年,孙晋出任共青团广西壮族自治区委员会书记
  • 建行原副行长章更生被开除党籍:靠贷吃贷,大搞权钱交易
  • 外媒:罗马教皇方济各去世