电源管理

描述

玄武OS的电源管理框架有两套,分别用于SMP系统和UP系统。因为多核系统CPU数量较多, 流程更复杂。玄武OS的电源管理框架提供了基本流程,最终SOC如何休眠由用户在回调函数中 进行配置,用户可以自行决定是否保持SDRAM的刷新、是否保持某些GPIO的输出、 或则是否进入最低功耗的待机等等。

SMP系统的电源管理

电源管理领域

SMP系统中,CPU不只一个,玄武OS中使用 电源管理领域 来统一管理所有CPU的休眠与唤醒。 电源管理领域 在BSP中进行初始化。例如STM32F4x(soc的代码位于 xwcd/soc/arm/v7m/gcc/m4/stm32f4x)在<04.启动流程.md>的 soc_init() 阶段初始化 电源管理领域

/* xwcd/soc/arm/v7m/gcc/m4/stm32f4x/soc_xwpmdm.c */
__xwbsp_data struct xwos_pmdm soc_xwpm_domain = {
        .xwsd_num = CPUCFG_CPU_NUM,
};

/* xwcd/soc/arm/v7m/gcc/m4/stm32f4x/soc_init.c */
__xwbsp_init_code
void soc_init(void)
{
        ...
#if defined(XuanWuOS_CFG_CORE__smp)
        /* PM domain */
        xwos_pmdm_init(&soc_xwpm_domain);
        ...
#endif
}

电源管理领域 一般不需要用户自定义,可通过下面两种方式获取:

  • 使用头文件:xwcd/soc/arm/v7m/gcc/m4/stm32f4x/soc_xwpmdm.h
  • 使用API:
struct xwos_pmdm * xwos_pmdm_get_lc(void);

流程

img

图中,左列为休眠流程,右列为唤醒流程,箭头代表了可以进行阶段切换的方向。 电源管理领域将电源管理分为四个阶段(图中绿色的框):

正在运行 ( XWOS_PMDM_STAGE_RUNNING )

所有CPU正常运行,可以使用下面API使得整个电源管理领域进入休眠的流程:

xwer_t xwos_pmdm_suspend(struct xwos_pmdm * pmdm);
冻结线程 —— 解冻线程 ( XWOS_PMDM_STAGE_FREEZING - XWOS_PMDM_STAGE_THAWING )

一旦开始休眠流程,电源管理领域会通知所有CPU冻结线程。当调度器中线程 全部冻结后,调度器会向电源管理领域报告 已暂停 的状态。调度器冻结 线程的过程是一个比较复杂的过程,这个阶段又分为很多子阶段(图中蓝色的框):

# 调度器冻结线程 —— 调度器解冻线程 ( XWOS_SCHEDULER_WKLKCNT_FREEZING - XWOS_SCHEDULER_WKLKCNT_THAWING )

调度器会中断所有线程的 等待睡眠 态,并为所有线程设置 可冻结 标志,然后依次调度每个线程,直到它们运行到冻结点进行冻结,这个过程 耗时比较长,在执行过程中如果遇到唤醒事件,调度器会在唤醒事件中断中 切换为解冻流程:解冻已经冻结的线程,并取消未冻结的线程的 可冻结 标志。

# 调度器中全部线程已经冻结 ( XWOS_SCHEDULER_WKLKCNT_ALLFRZ )

最后一个线程完成冻结时,CPU处于调度器服务中断中,调度器会将滴答定时器关闭。 如果此刻出现唤醒事件,待CPU从调度器服务中断中退出后,会立即进入唤醒事件中断, 休眠流程切换为唤醒流程,并重新打开滴答定时器。 按照调度器中断的描述,唤醒事件的中断优先级是低于 调度器服务中断的,因此唤醒事件的中断一定会在调度器服务中断退出 后才开始执行,滴答定时器一定会先关闭,然后再打开,顺序不会错乱。

# 调度器已暂停 ( XWOS_SCHEDULER_WKLKCNT_SUSPENDED )

CPU从上一步骤的调度器服务中断中退出后,会切换回线程上下文,此时因所有 线程已经冻结,CPU只可能运行在空闲任务中,CPU会在空闲任务中 向电源管理领域报告调度器已暂停的状态。此过程不能在调度器服务中断中进行, 因为电源管理领域切换阶段的相关操作以及回调函数是由最后一个报告的CPU来完成的。 如果在调度器服务中断中进行,由于调度器服务中断是最高优先级中断, 不能被唤醒事件中断打断,有可能会导致无法唤醒的问题。

正在暂停 —— 正在恢复 ( XWOS_PMDM_STAGE_SUSPENDING - XWOS_PMDM_STAGE_RESUMING )

当最后一个CPU报告了已暂停的状态后,会在最后一个CPU的空闲任务 中执行用户的 suspend 回调函数。此时,如果出现唤醒事件,电源领域的阶段会在 唤醒事件中断中从 正在暂停 切换为 正在恢复 ,并执行用户的 resume 回调函数。

  • suspend 回调函数用于在休眠之前让用户关闭设备、配置SDRAM的刷新模式、 配置GPIO等;
  • resume 回调函数用于在唤醒线程之前执行与 suspend 回调函数相反的操作;
  • 执行这两个函数时会锁定电源管理领域的 resume-suspend 锁,并关闭当前 CPU的总中断开关。 resume-suspend 锁是自旋锁,如果CPU-1正在执行 suspend 回调函数,此时出现了唤醒事件,会分下面两种情况进行处理:
    • 唤醒事件中断也是绑定在CPU-1上,由于CPU-1的总中断开关已关闭,唤醒事件 中断会挂起直到 suspend 回调函数执行完毕并释放 resume-suspend 锁并 打开CPU总中断开关后才会进入,然后执行 resume 回调函数;
    • 唤醒事件中断绑定在CPU-2上,CPU-2会立即进入唤醒事件中断,但一直会 “自旋‘ 等待 CPU-1释放 resume-suspend 锁后才能获得 resume-suspend 锁, 然后执行 resume 回调函数;
  • 示例:STM32的 resume-suspend 回调函数

/* xwbd/fk429m/bm/stm32cube/xwds/pm.c */
void stm32cube_pm_resume(void)
{
        __maybe_unused xwer_t rc;
        xwirq_t irq;

        rc = xwos_irq_get_id(&irq); /* [DEBUG]方便获取此时的中断号 */

        /* 恢复默认值 */
        LL_PWR_SetPowerMode(LL_PWR_MODE_STOP_MAINREGU);

        /* 恢复stm32cube中的所有设备 */
        xwds_pm_resume(&stm32cube_ds);
}

void stm32cube_pm_suspend(void)
{
        __maybe_unused xwer_t rc;
        xwirq_t irq;

        rc = xwos_irq_get_id(&irq);  /* [DEBUG]方便获取此时的中断号 */

        /* 暂停stm32cube:
           + 暂停所有设备
           + 设置GPIO
           + 配置SDRAM的自刷新模式 */
        xwds_pm_suspend(&stm32cube_ds);

        /* 设置休眠方式为STOP模式并开启低功耗调压器,
           STM32的STOP模式下寄存器与内部RAM数据不丢失 */
        LL_PWR_SetPowerMode(LL_PWR_MODE_STOP_LPREGU);

        /* 设置ARMv7-m的DEEPSLEEP位 */
        LL_LPM_EnableDeepSleep();
}
已经暂停 ( XWOS_PMDM_STAGE_SUSPENDED )

此阶段为休眠流程的最后一个阶段,也是唤醒流程的第一个阶段。 此阶段提供给用户的回调函数:

  • sleep 回调函数,让系统进入休眠状态
  • wakeup 回调函数,唤醒系统

suspend 回调函数执行完毕后进入,也在最后一个CPU的 空闲任务中进入此阶段。因为如果在中断中执行, 有可能因为唤醒事件中断的优先级低于当前中断的优先级而 导致无法唤醒。(例如STM32,如果在一个高优先级的中断中执行了 wfi 指令, 低优先级的唤醒中断无法把系统唤醒。)

当唤醒事件出现, wakeup 回调函数在唤醒事件中断中执行。

sleep 回调函数与 wakeup 回调函数之间没有锁的保护, 因此 sleep 回调函数要设计成能被 wakeup 回调函数打断。

示例:STM32的 wakeup-sleep 回调函数

/* xwbd/fk429m/bm/stm32cube/xwds/pm.c */
void stm32cube_pm_wakeup(void)
{
        LL_LPM_EnableSleep(); /* 清除DEEPSLEEP位 */
        SystemClock_Config(); /* 重新配置时钟 */
}

void stm32cube_pm_sleep(void)
{
        wfi();
}
**stm32cube\_pm\_sleep()** 中只有一条 **WFI** 指令,当在
[空闲任务](07.调度器.md###空闲任务)中执行 **stm32cube\_pm\_sleep()** 时,如果发生唤醒事件,
会在唤醒事件中断中执行 **stm32cube\_pm\_wakeup()** , **LL\_LPM\_EnableSleep()**
会清除ARMv7-m的DEEPSLEEP位,接下来的唤醒流程也会将滴答定时器重新打开,当
再次回到 **stm32cube\_pm\_sleep()** 时, **WFI** 指令只会使CPU进入ARMv7-m的SLEEP
模式,这种模式只是CPU时钟暂停,任意中断都可恢复CPU时钟,等待滴答定时器的
下一次中断后,系统就可恢复正常。

配置

/* <cfg/xwos.h> */
/* (空) */

API参考

  • 头文件:xwos/smp/pm.h
  • 注释:见头文件

UP内核的电源管理

流程

img

图中,左列为休眠流程,右列为唤醒流程,箭头代表了可以进行状态切换的方向。 UP系统电源管理分为五个阶段:

正在运行 ( XWOS_SCHEDULER_WKLKCNT_RUNNING )

调度器正常运行,可以使用下面API进入休眠的流程:

xwer_t xwos_scheduler_suspend(void);
正在冻结线程 —— 正在解冻线程 ( XWOS_SCHEDULER_WKLKCNT_FREEZING - XWOS_SCHEDULER_WKLKCNT_THAWING )

一旦开始休眠流程,调度器会通知所有线程冻结。调度器会先将电源管理的阶段调整 为小于 正在运行(XWOS_SCHEDULER_WKLKCNT_RUNNING) ,然后中断所有线程的 等待睡眠 态,然后依次调度每个线程,直到它们运行到冻结点进行冻结。 这个过程耗时比较长,在执行过程中如果遇到唤醒事件,调度器可在唤醒事件中断中 切换为解冻流程:解冻已经冻结的线程,并将调度器状态逐步恢复至 正在运行

UP系统与SMP系统的区别

  • UP系统中只有一个CPU,因此线程是否可休眠可以以调度器的阶段作为判断的依据, 不必像SMP系统一样为每个线程设置 可休眠 状态。
调度器中全部线程已经冻结 ( XWOS_SCHEDULER_WKLKCNT_ALLFRZ )

最后一个线程完成冻结时,CPU处于调度器服务中断中,调度器会将滴答定时器关闭。 如果此刻出现唤醒事件,待CPU从调度器服务中断中退出后,会立即进入唤醒事件中断, 休眠流程切换为唤醒流程,并重新打开滴答定时器。 按照调度器中断的描述,唤醒事件的中断优先级 是低于调度器服务中断的,因此唤醒事件的中断一定会在调度器服务中断退出 后才开始执行,滴答定时器一定会先关闭,然后再打开,顺序不会错乱。

正在暂停 —— 正在恢复 ( XWOS_SCHEDULER_WKLKCNT_SUSPENDING - XWOS_SCHEDULER_WKLKCNT_RESUMING )

当全部线程都冻结后,调度器只能调度<07.调度器.md>,调度器会在其 中执行用户的 suspend 回调函数。此时,如果出现唤醒事件,电源管理会在唤醒 事件中断中将阶段 正在暂停 切换为 正在恢复 ,并在唤醒事件中断中执行用户 的 resume 回调函数。

  • suspend 回调函数用于在休眠之前让用户关闭设备、配置SDRAM的刷新模式、 配置GPIO等;
  • resume 回调函数用于在唤醒线程之前执行与 suspend 回调函数相反的操作;
  • 执行这两个函数时会关闭CPU总中断开关。如果正在执行 suspend 回调函数时 出现了唤醒事件,唤醒事件中断会挂起直到 suspend 回调函数执行完毕后并 打开CPU总中断开关后才会进入,然后执行 resume 回调函数;
  • 示例:STM32的 resume-suspend 回调函数
已经暂停 ( XWOS_SCHEDULER_WKLKCNT_SUSPENDED )

此阶段为休眠流程的最后一个阶段,也是唤醒流程的第一个阶段。 此阶段提供给用户的回调函数:

  • sleep 回调函数,让系统进入休眠状态
  • wakeup 回调函数,唤醒系统

suspend 回调函数执行完毕后,也是在空闲任务中进入此阶段的。 因为如果在中断中进入此阶段,有可能因为唤醒事件中断的优先级低于当前中断的优先级 而导致无法唤醒。(例如STM32,如果在一个高优先级的中断中执行了 wfi 指令, 低优先级的唤醒中断无法把系统唤醒。)

当唤醒事件出现, wakeup 回调函数在唤醒事件中断中执行。

sleep 回调函数与 wakeup 回调函数之间没有锁的保护, 因此 sleep 回调函数要设计成能被 wakeup 回调函数打断。

示例:STM32的 wakeup-sleep 回调函数

配置

/* <cfg/xwos.h> */

#define XWUPCFG_SD_PM                 1 // 是否启用调度器电源管理,取值:1|0

API参考

  • 头文件:xwos/up/pm.h
  • 注释:见头文件