简介

当内核没有要调度的内容时,它会进入空闲状态。如果通过CONFIG_PM选项启用,则电源管理子系统可以根据所选的电源管理策略内核分配的空闲时间持续时间,将空闲系统置于受支持的电源状态之一。
设置唤醒事件应用程序的责任。唤醒事件通常是由其中一个SoC外设模块SysTickRTC计数器GPIO触发的中断
22.系统电源管理 - 图1

进入休眠的原理实现

zephyr/kernel/idle.c下:

  1. void idle(void *unused1, void *unused2, void *unused3)
  2. {
  3. ARG_UNUSED(unused1);
  4. ARG_UNUSED(unused2);
  5. ARG_UNUSED(unused3);
  6. while (true) {
  7. /* SMP systems without a working IPI can't
  8. * actual enter an idle state, because they
  9. * can't be notified of scheduler changes
  10. * (i.e. threads they should run). They just
  11. * spin in a yield loop. This is intended as
  12. * a fallback configuration for new platform
  13. * bringup.
  14. */
  15. if (IS_ENABLED(CONFIG_SMP) &&
  16. !IS_ENABLED(CONFIG_SCHED_IPI_SUPPORTED)) {
  17. k_busy_wait(100);
  18. k_yield();
  19. continue;
  20. }
  21. /* Note weird API: k_cpu_idle() is called with local
  22. * CPU interrupts masked, and returns with them
  23. * unmasked. It does not take a spinlock or other
  24. * higher level construct.
  25. */
  26. (void) arch_irq_lock();
  27. // 判定是否启动了电源管理
  28. if (IS_ENABLED(CONFIG_PM)) {
  29. pm_save_idle();
  30. } else {
  31. k_cpu_idle();
  32. }
  33. /* It is possible to (pathologically) configure the
  34. * idle thread to have a non-preemptible priority.
  35. * You might think this is an API bug, but we actually
  36. * have a test that exercises this. Handle the edge
  37. * case when that happens.
  38. */
  39. if (K_IDLE_PRIO < 0) {
  40. k_yield();
  41. }
  42. }
  43. }
  44. /**
  45. * @brief Indicate that kernel is idling in tickless mode
  46. *
  47. * Sets the kernel data structure idle field to either a positive value or
  48. * K_FOREVER.
  49. */
  50. static void pm_save_idle(void)
  51. {
  52. #ifdef CONFIG_PM
  53. // 获取下次系统调度时间
  54. int32_t ticks = z_get_next_timeout_expiry();
  55. _kernel.idle = ticks;
  56. /*
  57. * Call the suspend hook function of the soc interface to allow
  58. * entry into a low power state. The function returns
  59. * PM_STATE_ACTIVE if low power state was not entered, in which
  60. * case, kernel does normal idle processing.
  61. *
  62. * This function is entered with interrupts disabled. If a low power
  63. * state was entered, then the hook function should enable inerrupts
  64. * before exiting. This is because the kernel does not do its own idle
  65. * processing in those cases i.e. skips k_cpu_idle(). The kernel's
  66. * idle processing re-enables interrupts which is essential for
  67. * the kernel's scheduling logic.
  68. */
  69. // 启动电源管理
  70. if (pm_system_suspend(ticks) == PM_STATE_ACTIVE) {
  71. k_cpu_idle();
  72. }
  73. #endif
  74. }
  75. enum pm_state pm_system_suspend(int32_t ticks)
  76. {
  77. // 根据电源管理策略计算出电源管理状态
  78. z_power_state = pm_policy_next_state(ticks);
  79. if (z_power_state.state == PM_STATE_ACTIVE) {
  80. LOG_DBG("No PM operations done.");
  81. return z_power_state.state;
  82. }
  83. post_ops_done = 0;
  84. if (ticks != K_TICKS_FOREVER) {
  85. /*
  86. * Just a sanity check in case the policy manager does not
  87. * handle this error condition properly.
  88. */
  89. __ASSERT(z_power_state.min_residency_us >=
  90. z_power_state.exit_latency_us,
  91. "min_residency_us < exit_latency_us");
  92. /*
  93. * We need to set the timer to interrupt a little bit early to
  94. * accommodate the time required by the CPU to fully wake up.
  95. */
  96. // 设置下次系统调度的超时定时器
  97. z_set_timeout_expiry(ticks -
  98. k_us_to_ticks_ceil32(z_power_state.exit_latency_us), true);
  99. }
  100. // 这部分我们之后会在设备电源里讲解
  101. #if CONFIG_PM_DEVICE
  102. bool should_resume_devices = true;
  103. switch (z_power_state.state) {
  104. case PM_STATE_RUNTIME_IDLE:
  105. __fallthrough;
  106. case PM_STATE_SUSPEND_TO_IDLE:
  107. __fallthrough;
  108. case PM_STATE_STANDBY:
  109. /* low power peripherals. */
  110. if (pm_low_power_devices()) {
  111. return _handle_device_abort(z_power_state);
  112. } break;
  113. case PM_STATE_SUSPEND_TO_RAM:
  114. __fallthrough;
  115. case PM_STATE_SUSPEND_TO_DISK:
  116. if (pm_suspend_devices()) {
  117. return _handle_device_abort(z_power_state);
  118. }
  119. break;
  120. default:
  121. should_resume_devices = false;
  122. break;
  123. }
  124. #endif
  125. /*
  126. * This function runs with interruptions locked but it is
  127. * expected the SoC to unlock them in
  128. * pm_power_state_exit_post_ops() when returning to active
  129. * state. We don't want to be scheduled out yet, first we need
  130. * to send a notification about leaving the idle state. So,
  131. * we lock the scheduler here and unlock just after we have
  132. * sent the notification in pm_system_resume().
  133. */
  134. k_sched_lock();
  135. //启动电源管理的调试定时器
  136. pm_debug_start_timer();
  137. /* Enter power state */
  138. // 通知要进入系统休眠
  139. pm_state_notify(true);
  140. // 进入系统休眠,这是一个和体系架构相关的函数
  141. pm_power_state_set(z_power_state);
  142. // 停止电源管理的调试定时器
  143. pm_debug_stop_timer();
  144. /* Wake up sequence starts here */
  145. // 这部分在之后设备电源管理讲解
  146. #if CONFIG_PM_DEVICE
  147. if (should_resume_devices) {
  148. /* Turn on peripherals and restore device states as necessary */
  149. pm_resume_devices();
  150. }
  151. #endif
  152. // 输出系统电源管理的调试信息
  153. pm_log_debug_info(z_power_state.state);
  154. //退出系统休眠,这是一个和体系架构相关的函数
  155. pm_system_resume();
  156. k_sched_unlock();
  157. return z_power_state.state;
  158. }

系统电源管理的执行步骤:

  • 系统进入idle任务
  • 获取下次系统调度的时间
  • 通过电源管理策略根据时间获取电源管理状态
  • 根据电源管理状态进行休眠

电源管理策略解析

  1. //从设备树中获取电源管理策略的结构体
  2. static const struct pm_state_info pm_min_residency[] =
  3. PM_STATE_INFO_DT_ITEMS_LIST(DT_NODELABEL(cpu0));
  4. struct pm_state_info pm_policy_next_state(int32_t ticks)
  5. {
  6. int i;
  7. for (i = ARRAY_SIZE(pm_min_residency) - 1; i >= 0; i--) {
  8. uint32_t min_residency, exit_latency;
  9. // 判定改电源管理状态是否被约束
  10. if (!pm_constraint_get(pm_min_residency[i].state)) {
  11. continue;
  12. }
  13. min_residency = k_us_to_ticks_ceil32(
  14. pm_min_residency[i].min_residency_us);
  15. exit_latency = k_us_to_ticks_ceil32(
  16. pm_min_residency[i].exit_latency_us);
  17. __ASSERT(min_residency > exit_latency,
  18. "min_residency_us < exit_latency_us");
  19. // 通过时间来确定要进行的电源管理状态
  20. if ((ticks == K_TICKS_FOREVER) ||
  21. (ticks >= (min_residency + exit_latency))) {
  22. LOG_DBG("Selected power state %d "
  23. "(ticks: %d, min_residency: %u)",
  24. pm_min_residency[i].state, ticks,
  25. pm_min_residency[i].min_residency_us);
  26. return pm_min_residency[i];
  27. }
  28. }
  29. LOG_DBG("No suitable power state found!");
  30. return (struct pm_state_info){PM_STATE_ACTIVE, 0, 0};
  31. }

电源管理策略定义

想要定义电源管理策略,目前从源码上分析,我们只能通过设备树来定义。

电源管理策略设备树绑定文件

  1. description: Properties for power management state
  2. compatible: "zephyr,power-state"
  3. properties:
  4. power-state-name:
  5. type: string
  6. required: true
  7. description: indicates a power state
  8. enum:
  9. - "active"
  10. - "runtime-idle"
  11. - "suspend-to-idle"
  12. - "standby"
  13. - "suspend-to-ram"
  14. - "suspend-to-disk"
  15. - "soft-off"
  16. substate-id:
  17. type: int
  18. required: false
  19. description: Platform specific identification.
  20. min-residency-us:
  21. type: int
  22. required: false
  23. description: |
  24. Minimum residency duration in microseconds. It is the minimum time for a
  25. given idle state to be worthwhile energywise. It includes the time to enter
  26. in this state.
  27. exit-latency-us:
  28. type: int
  29. required: false
  30. description: |
  31. 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。

电源管理策略示例

  1. / {
  2. power-states {
  3. stop0: state0 {
  4. compatible = "zephyr,power-state";
  5. power-state-name = "suspend-to-idle";
  6. substate-id = <1>;
  7. min-residency-us = <100>;
  8. };
  9. stop1: state1 {
  10. compatible = "zephyr,power-state";
  11. power-state-name = "suspend-to-idle";
  12. substate-id = <2>;
  13. min-residency-us = <500>;
  14. };
  15. stop2: state2 {
  16. compatible = "zephyr,power-state";
  17. power-state-name = "suspend-to-idle";
  18. substate-id = <3>;
  19. min-residency-us = <900>;
  20. };
  21. };
  22. }

电源管理示例

Kconfig定义:

  1. CONFIG_GPIO=y
  2. //启动电源管理
  3. CONFIG_PM=y

设备树:

  1. /{
  2. power-states {
  3. stop0: state0 {
  4. compatible = "zephyr,power-state";
  5. power-state-name = "suspend-to-idle";
  6. substate-id = <1>;
  7. min-residency-us = <500>;
  8. };
  9. stop1: state1 {
  10. compatible = "zephyr,power-state";
  11. power-state-name = "suspend-to-idle";
  12. substate-id = <2>;
  13. min-residency-us = <700>;
  14. };
  15. stop2: state2 {
  16. compatible = "zephyr,power-state";
  17. power-state-name = "suspend-to-idle";
  18. substate-id = <3>;
  19. min-residency-us = <1000>;
  20. };
  21. };
  22. leds {
  23. compatible = "gpio-leds";
  24. green_led_2: led_2 {
  25. gpios = <&gpioa 5 GPIO_ACTIVE_HIGH>;
  26. label = "User LD2";
  27. };
  28. };
  29. aliases {
  30. led0 = &green_led_2;
  31. sw0 = &user_button;
  32. };
  33. }

代码:

  1. /*
  2. * Copyright (c) 2021 Linaro Limited
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. #include <zephyr.h>
  7. #include <device.h>
  8. #include <devicetree.h>
  9. #include <drivers/gpio.h>
  10. #include <sys/printk.h>
  11. #define SLEEP_TIME_MS 2000
  12. static const struct gpio_dt_spec led =
  13. GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
  14. void main(void)
  15. {
  16. bool led_is_on = true;
  17. __ASSERT_NO_MSG(device_is_ready(led.port));
  18. printk("Device ready\n");
  19. while (true) {
  20. gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
  21. gpio_pin_set(led.port, led.pin, (int)led_is_on);
  22. if (led_is_on == false) {
  23. /* Release resource to release device clock */
  24. gpio_pin_configure(led.port, led.pin, GPIO_DISCONNECTED);
  25. }
  26. k_msleep(SLEEP_TIME_MS);
  27. if (led_is_on == true) {
  28. /* Release resource to release device clock */
  29. gpio_pin_configure(led.port, led.pin, GPIO_DISCONNECTED);
  30. }
  31. led_is_on = !led_is_on;
  32. }
  33. }