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 章节