System PM
CONFIG_PM
开关。用来控制整个SoC的状态。
Device tree
zephyr,power-state.yaml
介绍了zephyr power state的device tree binding。
一个设备树的例子:
power-states {
idle: idle {
compatible = "zephyr,power-state";
power-state-name = "runtime-idle";
};
stop: stop {
compatible = "zephyr,power-state";
power-state-name = "suspend-to-idle"; // pm状态,见yaml
substate-id = <0>; // 如果有两个相同的power-state-name,需要用substate-id来区分
min-residency-us = <1000>; // 最少在该状态停留的时间
exit-latency-us = <2>; // 离开该状态最worse的时间
};
pstop1: pstop1 {
compatible = "zephyr,power-state";
power-state-name = "suspend-to-idle";
substate-id = <1>;
min-residency-us = <1000>;
exit-latency-us = <2>;
};
};
PM policy
有两种PM policy:
- Residency based
- Application defined
第一种policy根据在设备中定义的min-residency-us
和exit-latency-us
, 如果进入idle状态,距离下一个要调度的事件时间time_to_next_scheduled_event
>min-residency-us
+exit-latency-us
,那么就进入该pm state。并在几种都满足的pm state的情况下,选择最省电的。
第二种policy是app自定义一个pm_notifier
结构体,定义state_entry
和state_exit
回调函数,利用注册接口,pm_notifier_register
注册,之后在系统进入idle状态后,会调用回调函数state_entry
。
static struct pm_notifier notifier = {
.state_entry = notify_pm_state_entry,
.state_exit = notify_pm_state_exit,
};
void pm_notifier_register(struct pm_notifier *notifier)
Source code
state.h
列出了所有支持的pm状态,越靠下的越省电,当然唤醒的流程和时间也更长:
enum pm_state {
PM_STATE_ACTIVE,
// cpu cores都进入idle状态,不改变devices状态。
PM_STATE_RUNTIME_IDLE,
// cpu cores都进入idle状态,可以让外设进入低功耗状态。
PM_STATE_SUSPEND_TO_IDLE,
// 单核和suspend to idle一样,多核会把non-boot cpus都关掉。
PM_STATE_STANDBY,
// 内存进入自刷新模式,devices和cpus状态都保存到memory,resume从rom code起来。
PM_STATE_SUSPEND_TO_RAM,
// 内存也关掉,内容都保存到flash/disk,启动再读出来。
PM_STATE_SUSPEND_TO_DISK,
// 全部关掉,cpu和memory内容也不保存。
PM_STATE_SOFT_OFF,
PM_STATE_COUNT,
};
在调用idle()
函数后,会调用pm_system_suspend
bool pm_system_suspend(int32_t ticks)
{
uint8_t id = _current_cpu->id;
k_spinlock_key_t key;
key = k_spin_lock(&pm_forced_state_lock);
if (z_cpus_pm_forced_state[id].state != PM_STATE_ACTIVE) {
z_cpus_pm_state[id] = z_cpus_pm_forced_state[id];
z_cpus_pm_forced_state[id].state = PM_STATE_ACTIVE;
} else {
const struct pm_state_info *info;
info = pm_policy_next_state(id, ticks);
if (info != NULL) {
z_cpus_pm_state[id] = *info;
}
}
k_spin_unlock(&pm_forced_state_lock, key);
if (z_cpus_pm_state[id].state == PM_STATE_ACTIVE) {
LOG_DBG("No PM operations done.");
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks,
z_cpus_pm_state[id].state);
return false;
}
// 如果打开了PM_DEVICE,并且不是只由runtime pm管理,会把所有devices都suspend
#if defined(CONFIG_PM_DEVICE) && !defined(CONFIG_PM_DEVICE_RUNTIME_EXCLUSIVE)
if (atomic_sub(&_cpus_active, 1) == 1) {
if (z_cpus_pm_state[id].state != PM_STATE_RUNTIME_IDLE) {
pm_suspend_devices();
}
}
#endif
if (ticks != K_TICKS_FOREVER) {
/*
* We need to set the timer to interrupt a little bit early to
* accommodate the time required by the CPU to fully wake up.
*/
sys_clock_set_timeout(ticks -
k_us_to_ticks_ceil32(
z_cpus_pm_state[id].exit_latency_us),
true);
}
k_sched_lock();
pm_stats_start();
/* Enter power state */
// 调用app注册的回调函数。
pm_state_notify(true);
atomic_set_bit(z_post_ops_required, id);
// 这个函数是SoC自定义的进入不同状态的操作函数。
// 一般会进入suspend/sleep状态,进入WFI等待前面设置的timeout中断。
pm_state_set(z_cpus_pm_state[id].state, z_cpus_pm_state[id].substate_id);
pm_stats_stop();
/* Wake up sequence starts here */
#if defined(CONFIG_PM_DEVICE) && !defined(CONFIG_PM_DEVICE_RUNTIME_EXCLUSIVE)
if (atomic_add(&_cpus_active, 1) == 0) {
pm_resume_devices();
}
#endif
pm_stats_update(z_cpus_pm_state[id].state);
pm_system_resume();
k_sched_unlock();
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks,
z_cpus_pm_state[id].state);
return true;
}
Device PM
CONFIG_DEVICE_PM
分为两种
- Device runtime PM
- System managed device PM
Device Runtime PM
如果启用了runtime pm,那么在System PM比如suspend to ram/disk…就不用suspend/resume devices了。
System-Managed Device PM
通常在System PM进入某种状态的过程中设置devices状态,在pm_system_suspend()中完成。
在下面场景可以选择System-Managed Device PM:
- 没有device suspend/resume的时候需要blocking的操作。这时候比runtime PM操作更简单。
- 某些device不用单独suspend/resume,完全由host控制,随cpu一起睡下去。
更推荐使用Device Runtime PM。
Driver Consumer
static int dummy_driver_pm_action(const struct device *dev,
enum pm_device_action action)
{
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
/* suspend the device */
break;
case PM_DEVICE_ACTION_RESUME:
/* resume the device */
break;
case PM_DEVICE_ACTION_TURN_ON:
/*
* powered on the device, used when the power
* domain this device belongs is resumed.
*/
...
break;
case PM_DEVICE_ACTION_TURN_OFF:
/*
* power off the device, used when the power
* domain this device belongs is suspended.
*/
break;
default:
return -ENOTSUP;
}
return 0;
}
// 定义inst0对应的pm_device结构体
PM_DEVICE_DT_INST_DEFINE(0, dummy_driver_pm_action);
// 第三个参数把pm_device挂入device结构体
DEVICE_DT_INST_DEFINE(0, &dummy_init,
PM_DEVICE_DT_INST_GET(0), NULL, NULL, POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL);
Source code
device.h
列出了支持的device pm状态:
enum pm_device_state {
PM_DEVICE_STATE_ACTIVE,
PM_DEVICE_STATE_SUSPENDED,
/** Device is being suspended. */
PM_DEVICE_STATE_SUSPENDING,
/** Device is turned off (power removed).*/
PM_DEVICE_STATE_OFF
};
分析下PM_DEVICE_DT_INST_DEFINE
宏:
PM_DEVICE_DT_INST_DEFINE(0, dummy_driver_pm_action);
#define PM_DEVICE_DT_INST_DEFINE(idx, pm_action_cb, ...) \
Z_PM_DEVICE_DEFINE(DT_DRV_INST(idx), \
Z_DEVICE_DT_DEV_ID(DT_DRV_INST(idx)), \
pm_action_cb, \
COND_CODE_1(IS_EMPTY(__VA_ARGS__), (0), (__VA_ARGS__)))
#define Z_PM_DEVICE_DEFINE(node_id, dev_id, pm_action_cb, isr_safe) \
Z_PM_DEVICE_DEFINE_SLOT(dev_id); \
static struct COND_CODE_1(isr_safe, (pm_device_isr), (pm_device)) \
Z_PM_DEVICE_NAME(dev_id) = \
Z_PM_DEVICE_INIT(Z_PM_DEVICE_NAME(dev_id), node_id, \
pm_action_cb, isr_safe)
#define Z_PM_DEVICE_INIT(obj, node_id, pm_action_cb, isr_safe) \
{ \
.base = Z_PM_DEVICE_BASE_INIT(obj, node_id, pm_action_cb, \
isr_safe ? BIT(PM_DEVICE_FLAG_ISR_SAFE) : 0), \
COND_CODE_1(isr_safe, (), (Z_PM_DEVICE_RUNTIME_INIT(obj))) \
}
#define Z_PM_DEVICE_BASE_INIT(obj, node_id, pm_action_cb, _flags) \
{ \
.action_cb = pm_action_cb, \
.state = PM_DEVICE_STATE_ACTIVE, \
.flags = ATOMIC_INIT(Z_PM_DEVICE_FLAGS(node_id) | (_flags)), \
Z_PM_DEVICE_POWER_DOMAIN_INIT(node_id) \
}
如果在PM_DEVICE_DT_INST_DEFINE
没有传入第三个参数,则第11行创建了pm_device
结构体,并在13行Z_PM_DEVICE_INIT
中初始化:
static struct pm_device __pm_device_dev_id =
{
.base.action_cb = action_cb,
.base.state = PM_DEVICE_STATE_ACTIVE, // 初始状态为active
.base.flag = PM_DEVICE_FLAG_WS_CAPABLE | PM_DEVICE_FLAG_RUNTIME_AUTO | PM_DEVICE_FLAG_PD // 从设备树中获取wakeup_source, zephyr_pm_device_runtime_auto, power_domain属性,有则把对应flag置起,没有不置起。
.base.domain =
.lock = Z_SEM_INITIALIZER(obj.lock, 1, 1),
.event = Z_EVENT_INITIALIZER(obj.event),
}
Shell command
CONFIG_PM_DEVICE_SHELL
提供了一些pm shell操作。
Device Rumtime PM
CONFIG_PM_DEVICE_RUNTIME
开关。只有当app或其他subsys调用到driver api的时候,才会resume设备(pm_device_runtime_get()接口),一般处于suspend状态。
在driver init函数中需要调用pm_device_runtime_enable()
来enable device runtime pm。
如果设备默认状态是physical suspended的,需要在pm_device_runtime_enable()
之前调用pm_device_init_suspended
修改pm状态为suspend,因为软件状态初始化的时候是active。防止在pm_device_runtime_enable()
又进行一次suspend。
有一种不用在driver init函数中调用pm_device_runtime_enable()的方法,在device tree中对应的device加入zephyr,pm-device-runtime-auto;属性,这样会在kernel init中自动调用pm_device_runtime_enable()
// driver init function
static int mydev_init(const struct device *dev)
{
/* OPTIONAL: mark device as suspended if it is physically suspended */
pm_device_init_suspended(dev);
/* enable device runtime power management */
pm_device_runtime_enable(dev);
}
static inline void pm_device_init_suspended(const struct device *dev)
{
struct pm_device_base *pm = dev->pm_base;
pm->state = PM_DEVICE_STATE_SUSPENDED;
}
int pm_device_runtime_enable(const struct device *dev)
{
int ret = 0;
struct pm_device *pm = dev->pm;
// 默认这个flag是不会置起来的,在这个函数末尾置起
if (atomic_test_bit(&pm->base.flags, PM_DEVICE_FLAG_RUNTIME_ENABLED)) {
goto end;
}
/* lazy init of PM fields */
// pm_device初始化的时候并没有设置pm->dev,所以这里应该会跑进去,初始化system workqueue delayable work
if (pm->dev == NULL) {
pm->dev = dev;
k_work_init_delayable(&pm->work, runtime_suspend_work);
}
// 默认状态是PM_DEVICE_STATE_ACTIVE,所以上来会调用一次driver回调函数进suspend。
if (pm->base.state == PM_DEVICE_STATE_ACTIVE) {
ret = pm->base.action_cb(pm->dev, PM_DEVICE_ACTION_SUSPEND);
if (ret < 0) {
goto unlock;
}
// 设置状态为suspend.
pm->base.state = PM_DEVICE_STATE_SUSPENDED;
}
//初始化为没有人在使用该device
pm->base.usage = 0U;
//至此初始化完成,把runtime_enable置起
atomic_set_bit(&pm->base.flags, PM_DEVICE_FLAG_RUNTIME_ENABLED);
end:
return ret;
}
经过上面的初始化后,设备进入suspend状态,当app或subsys其他上层调用到该device后,在device driver中的操作函数中可以调用 pm_device_runtime_get()
,会把usage++, 还会调用到resume的回调函数,把设备唤醒。
可以在driver的suspend函数中关clk, 在resume函数中开clk,这样实现节省功耗
还有一种异步的操作。
// Todo: