简介
当内核没有要调度
的内容时,它会进入空闲状态
。如果通过CONFIG_PM
选项启用,则电源管理子系统可以根据所选的电源管理策略
和内核分配的空闲时间持续时间
,将空闲系统
置于受支持的电源状态之一。设置唤醒事件
是应用程序
的责任。唤醒事件
通常是由其中一个SoC外设模块
如SysTick
、RTC
、计数器
或GPIO
触发的中断
。
进入休眠的原理实现
在zephyr/kernel/idle.c
下:
void idle(void *unused1, void *unused2, void *unused3)
{
ARG_UNUSED(unused1);
ARG_UNUSED(unused2);
ARG_UNUSED(unused3);
while (true) {
/* SMP systems without a working IPI can't
* actual enter an idle state, because they
* can't be notified of scheduler changes
* (i.e. threads they should run). They just
* spin in a yield loop. This is intended as
* a fallback configuration for new platform
* bringup.
*/
if (IS_ENABLED(CONFIG_SMP) &&
!IS_ENABLED(CONFIG_SCHED_IPI_SUPPORTED)) {
k_busy_wait(100);
k_yield();
continue;
}
/* Note weird API: k_cpu_idle() is called with local
* CPU interrupts masked, and returns with them
* unmasked. It does not take a spinlock or other
* higher level construct.
*/
(void) arch_irq_lock();
// 判定是否启动了电源管理
if (IS_ENABLED(CONFIG_PM)) {
pm_save_idle();
} else {
k_cpu_idle();
}
/* It is possible to (pathologically) configure the
* idle thread to have a non-preemptible priority.
* You might think this is an API bug, but we actually
* have a test that exercises this. Handle the edge
* case when that happens.
*/
if (K_IDLE_PRIO < 0) {
k_yield();
}
}
}
/**
* @brief Indicate that kernel is idling in tickless mode
*
* Sets the kernel data structure idle field to either a positive value or
* K_FOREVER.
*/
static void pm_save_idle(void)
{
#ifdef CONFIG_PM
// 获取下次系统调度时间
int32_t ticks = z_get_next_timeout_expiry();
_kernel.idle = ticks;
/*
* Call the suspend hook function of the soc interface to allow
* entry into a low power state. The function returns
* PM_STATE_ACTIVE if low power state was not entered, in which
* case, kernel does normal idle processing.
*
* This function is entered with interrupts disabled. If a low power
* state was entered, then the hook function should enable inerrupts
* before exiting. This is because the kernel does not do its own idle
* processing in those cases i.e. skips k_cpu_idle(). The kernel's
* idle processing re-enables interrupts which is essential for
* the kernel's scheduling logic.
*/
// 启动电源管理
if (pm_system_suspend(ticks) == PM_STATE_ACTIVE) {
k_cpu_idle();
}
#endif
}
enum pm_state pm_system_suspend(int32_t ticks)
{
// 根据电源管理策略计算出电源管理状态
z_power_state = pm_policy_next_state(ticks);
if (z_power_state.state == PM_STATE_ACTIVE) {
LOG_DBG("No PM operations done.");
return z_power_state.state;
}
post_ops_done = 0;
if (ticks != K_TICKS_FOREVER) {
/*
* Just a sanity check in case the policy manager does not
* handle this error condition properly.
*/
__ASSERT(z_power_state.min_residency_us >=
z_power_state.exit_latency_us,
"min_residency_us < exit_latency_us");
/*
* We need to set the timer to interrupt a little bit early to
* accommodate the time required by the CPU to fully wake up.
*/
// 设置下次系统调度的超时定时器
z_set_timeout_expiry(ticks -
k_us_to_ticks_ceil32(z_power_state.exit_latency_us), true);
}
// 这部分我们之后会在设备电源里讲解
#if CONFIG_PM_DEVICE
bool should_resume_devices = true;
switch (z_power_state.state) {
case PM_STATE_RUNTIME_IDLE:
__fallthrough;
case PM_STATE_SUSPEND_TO_IDLE:
__fallthrough;
case PM_STATE_STANDBY:
/* low power peripherals. */
if (pm_low_power_devices()) {
return _handle_device_abort(z_power_state);
} break;
case PM_STATE_SUSPEND_TO_RAM:
__fallthrough;
case PM_STATE_SUSPEND_TO_DISK:
if (pm_suspend_devices()) {
return _handle_device_abort(z_power_state);
}
break;
default:
should_resume_devices = false;
break;
}
#endif
/*
* This function runs with interruptions locked but it is
* expected the SoC to unlock them in
* pm_power_state_exit_post_ops() when returning to active
* state. We don't want to be scheduled out yet, first we need
* to send a notification about leaving the idle state. So,
* we lock the scheduler here and unlock just after we have
* sent the notification in pm_system_resume().
*/
k_sched_lock();
//启动电源管理的调试定时器
pm_debug_start_timer();
/* Enter power state */
// 通知要进入系统休眠
pm_state_notify(true);
// 进入系统休眠,这是一个和体系架构相关的函数
pm_power_state_set(z_power_state);
// 停止电源管理的调试定时器
pm_debug_stop_timer();
/* Wake up sequence starts here */
// 这部分在之后设备电源管理讲解
#if CONFIG_PM_DEVICE
if (should_resume_devices) {
/* Turn on peripherals and restore device states as necessary */
pm_resume_devices();
}
#endif
// 输出系统电源管理的调试信息
pm_log_debug_info(z_power_state.state);
//退出系统休眠,这是一个和体系架构相关的函数
pm_system_resume();
k_sched_unlock();
return z_power_state.state;
}
系统电源管理的执行步骤:
- 系统进入
idle
任务 - 获取下次系统调度的时间
- 通过电源管理策略根据时间获取电源管理状态
- 根据电源管理状态进行休眠
电源管理策略解析
//从设备树中获取电源管理策略的结构体
static const struct pm_state_info pm_min_residency[] =
PM_STATE_INFO_DT_ITEMS_LIST(DT_NODELABEL(cpu0));
struct pm_state_info pm_policy_next_state(int32_t ticks)
{
int i;
for (i = ARRAY_SIZE(pm_min_residency) - 1; i >= 0; i--) {
uint32_t min_residency, exit_latency;
// 判定改电源管理状态是否被约束
if (!pm_constraint_get(pm_min_residency[i].state)) {
continue;
}
min_residency = k_us_to_ticks_ceil32(
pm_min_residency[i].min_residency_us);
exit_latency = k_us_to_ticks_ceil32(
pm_min_residency[i].exit_latency_us);
__ASSERT(min_residency > exit_latency,
"min_residency_us < exit_latency_us");
// 通过时间来确定要进行的电源管理状态
if ((ticks == K_TICKS_FOREVER) ||
(ticks >= (min_residency + exit_latency))) {
LOG_DBG("Selected power state %d "
"(ticks: %d, min_residency: %u)",
pm_min_residency[i].state, ticks,
pm_min_residency[i].min_residency_us);
return pm_min_residency[i];
}
}
LOG_DBG("No suitable power state found!");
return (struct pm_state_info){PM_STATE_ACTIVE, 0, 0};
}
电源管理策略定义
想要定义电源管理策略,目前从源码上分析,我们只能通过设备树
来定义。
电源管理策略设备树绑定文件
description: Properties for power management state
compatible: "zephyr,power-state"
properties:
power-state-name:
type: string
required: true
description: indicates a power state
enum:
- "active"
- "runtime-idle"
- "suspend-to-idle"
- "standby"
- "suspend-to-ram"
- "suspend-to-disk"
- "soft-off"
substate-id:
type: int
required: false
description: Platform specific identification.
min-residency-us:
type: int
required: false
description: |
Minimum residency duration in microseconds. It is the minimum time for a
given idle state to be worthwhile energywise. It includes the time to enter
in this state.
exit-latency-us:
type: int
required: false
description: |
Worst case latency in microseconds required to exit the idle state.
- power-state-name: 定义电源状态
- substate-id: 改状态的唯一id
- min-residency-us: 进入改电源状态的最小时间/us。
- exit-latency-us: 退出改电源状态的最大时间/us。
电源管理策略示例
/ {
power-states {
stop0: state0 {
compatible = "zephyr,power-state";
power-state-name = "suspend-to-idle";
substate-id = <1>;
min-residency-us = <100>;
};
stop1: state1 {
compatible = "zephyr,power-state";
power-state-name = "suspend-to-idle";
substate-id = <2>;
min-residency-us = <500>;
};
stop2: state2 {
compatible = "zephyr,power-state";
power-state-name = "suspend-to-idle";
substate-id = <3>;
min-residency-us = <900>;
};
};
}
电源管理示例
Kconfig定义:
CONFIG_GPIO=y
//启动电源管理
CONFIG_PM=y
设备树:
/{
power-states {
stop0: state0 {
compatible = "zephyr,power-state";
power-state-name = "suspend-to-idle";
substate-id = <1>;
min-residency-us = <500>;
};
stop1: state1 {
compatible = "zephyr,power-state";
power-state-name = "suspend-to-idle";
substate-id = <2>;
min-residency-us = <700>;
};
stop2: state2 {
compatible = "zephyr,power-state";
power-state-name = "suspend-to-idle";
substate-id = <3>;
min-residency-us = <1000>;
};
};
leds {
compatible = "gpio-leds";
green_led_2: led_2 {
gpios = <&gpioa 5 GPIO_ACTIVE_HIGH>;
label = "User LD2";
};
};
aliases {
led0 = &green_led_2;
sw0 = &user_button;
};
}
代码:
/*
* Copyright (c) 2021 Linaro Limited
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <sys/printk.h>
#define SLEEP_TIME_MS 2000
static const struct gpio_dt_spec led =
GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
void main(void)
{
bool led_is_on = true;
__ASSERT_NO_MSG(device_is_ready(led.port));
printk("Device ready\n");
while (true) {
gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
gpio_pin_set(led.port, led.pin, (int)led_is_on);
if (led_is_on == false) {
/* Release resource to release device clock */
gpio_pin_configure(led.port, led.pin, GPIO_DISCONNECTED);
}
k_msleep(SLEEP_TIME_MS);
if (led_is_on == true) {
/* Release resource to release device clock */
gpio_pin_configure(led.port, led.pin, GPIO_DISCONNECTED);
}
led_is_on = !led_is_on;
}
}