电源管理

XWOS的电源管理

概述

XWOS有两套电源管理框架,分别用于MP系统和UP系统。

XWOS的电源管理框架只提供了基本流程,最终SOC如何休眠由BSP中的回调函数实现: 用户可以自行决定是否保持SDRAM的刷新、是否保持某些GPIO的输出、或则是否进入最低功耗的待机等等。

MP系统的电源管理

电源管理领域

MP系统中,CPU不唯一,XWOS中使用 电源管理领域(PMDM) 来统一管理所有CPU的休眠与唤醒。

流程

MP系统电源管理流程
Photo: xwos.tech / CC-BY-SA-4.0

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

正在运行 ( XWMP_PMDM_STAGE_RUNNING )

所有CPU正常运行,可以使用下面CAPI进入休眠的流程:

xwer_t xwos_pm_suspend(void);

此CAPI是操作系统抽象层 OSAL 中的CAPI,实际调用的是:

xwer_t xwmp_pmdm_suspend(void);
冻结调度器 —— 解冻调度器 ( XWMP_PMDM_STAGE_FREEZING - XWMP_PMDM_STAGE_THAWING )

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

冻结线程 —— 解冻线程 ( XWMP_SKD_WKLKCNT_FREEZING - XWMP_SKD_WKLKCNT_THAWING )

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

全部线程已经冻结 ( XWMP_SKD_WKLKCNT_ALLFRZ )

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

调度器已经暂停 ( XWMP_SKD_WKLKCNT_SUSPENDED )

CPU从上一步骤的调度器服务中断中退出后,会切换回线程上下文,此时因所有线程已经冻结, CPU只可能运行在 空闲任务 中。 CPU会在空闲任务中向PMDM报告调度器 已经暂停 的状态。此过程不能在调度器服务中断中进行, 因为调度器服务中断是最高优先级中断,不能被唤醒事件中断打断,有可能会导致无法唤醒的问题。

正在暂停 —— 正在恢复 ( XWMP_PMDM_STAGE_SUSPENDING - XWMP_PMDM_STAGE_RESUMING )

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

  • suspend() 回调函数用于在休眠之前让用户关闭设备、配置SDRAM刷新模式、配置GPIO等;
  • resume() 回调函数用于在唤醒之前执行与 suspend() 回调函数相反的操作;
  • 执行这两个函数时,PMDM会自动锁定 resume-suspend锁 ,并关闭当前CPU的总中断开关。 resume-suspend锁 是自旋锁,如果CPU-1正在执行 suspend() 回调函数,此时出现了唤醒事件,会分下面两种情况进行处理:
    • 唤醒事件中断也绑定在CPU-1上,唤醒事件中断会挂起,直到 suspend() 回调函数返回, PMDM解锁 resume-suspend锁 并打开CPU总中断开关,才被处理,然后执行 resume() 回调函数;
    • 唤醒事件中断绑定在CPU-2上,CPU-2会立即进入唤醒事件中断,但一直会 自旋 等待CPU-1释放 resume-suspend锁 , 直到获得 resume-suspend锁 后,才会执行 resume() 回调函数;
  • 示例WeActMiniStm32H750的 resume()suspend() 回调函数:
/* XWOS/xwbd/WeActMiniStm32H750/bm/xwac/xwds/pm.c */
void stm32cube_pm_resume(void)
{
        ...
        /* 恢复stm32cube中的所有设备 */
        xwds_pm_resume(&stm32cube_ds);
}

void stm32cube_pm_suspend(void)
{
        ...
        /* 暂停stm32cube:
           + 暂停所有设备
           + 配置GPIO */
        xwds_pm_suspend(&stm32cube_ds);

        /* 设置休眠方式为STOP模式:
           STOP模式下寄存器与内部RAM数据不丢失,
           因此休眠方式为SuspendToRAM,唤醒后运行状态可恢复。*/
        LL_PWR_SetRegulModeDS(LL_PWR_REGU_DSMODE_LOW_POWER);
        LL_PWR_EnableFlashPowerDown();
        LL_PWR_CPU_SetD1PowerMode(LL_PWR_CPU_MODE_D1STOP);
        LL_PWR_CPU_SetD2PowerMode(LL_PWR_CPU_MODE_D2STOP);
        LL_PWR_CPU_SetD3PowerMode(LL_PWR_CPU_MODE_D3STOP);
        LL_LPM_EnableDeepSleep();

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

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

  • sleep() :休眠SOC
  • wakeup() :唤醒SOC

当最后一个CPU的 空闲任务 执行完上一阶段的 suspend() 回调函数后, PMDM将电源管理步骤切换到此阶段,并执行 sleep() 回调函数。 SOC系统在 sleep() 回调函数内部进入低功耗状态, 此时时钟停止,代码停止运行, sleep() 回调函数不会返回。

XWOS将 sleep() 回调函数设计在空闲任务中执行,是因为中断优先级的问题有可能导致系统无法唤醒。 例如基于ARM-m的单片机,如果在一个高优先级的中断中执行了休眠指令( WFI ),低优先级的唤醒中断无法把系统唤醒。

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

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

示例,WeActMiniStm32H750的 wakeup()sleep() 回调函数:

/* XWOS/xwbd/WeActMiniStm32H750/bm/xwac/xwds/pm.c */
void stm32cube_pm_wakeup(void)
{
        LL_LPM_EnableSleep(); /* 清除DEEPSLEEP位 */
        SystemClock_Config(); /* 从STOP模式恢复后,需要重新配置时钟 */
}

void stm32cube_pm_sleep(void)
{
        /* 位置1 */
        cm_wfi();
        /* 位置2 */
}

分为两种情况讨论:

  • stm32cube_pm_sleep() 中的 WFI 指令执行后出现唤醒事件: 唤醒流程执行完毕后,代码会回到 stm32cube_pm_sleep()位置2
  • stm32cube_pm_sleep() 中的 WFI 指令执行之前出现唤醒事件: 唤醒事件中断会打断 stm32cube_pm_sleep() 函数, 然后执行 stm32cube_pm_wakeup() 函数, 函数 LL_LPM_EnableSleep() 会清除ARMv7-m的DEEPSLEEP位,接下来的唤醒流程也会将滴答定时器重新打开, 当再次回到 stm32cube_pm_sleep()位置1 时, WFI 指令只会使CPU进入ARMv7-m的SLEEP模式, 只是短暂地暂停了一下CPU时钟,即将到来的滴答定时器中断可使系统恢复正常。

UP内核的电源管理

流程

UP系统电源管理流程
Photo: xwos.tech / CC-BY-SA-4.0

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

正在运行 ( XWUP_PM_STAGE_RUNNING )

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

xwer_t xwup_skd_suspend(void);
正在冻结线程 —— 正在解冻线程 ( XWUP_PM_STAGE_FREEZING - XWUP_PM_STAGE_THAWING )

一旦开始休眠流程,调度器会中断所有线程的 等待睡眠 态,并通知所有线程冻结。这个过程耗时比较长, 在执行过程中如果遇到唤醒事件,调度器可在唤醒事件中断中切换为解冻流程:解冻已冻结的线程,并将调度器状态逐步恢复至 正在运行

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

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

正在暂停 —— 正在恢复 ( XWUP_PM_STAGE_SUSPENDING - XWUP_PM_STAGE_RESUMING )

当全部线程冻结后,调度器只能调度 空闲任务 ,调度器会在 空闲任务 中执行用户的 suspend() 回调函数。 此时,如果出现唤醒事件,系统会在唤醒事件中断中将状态从 正在暂停 切换为 正在恢复 ,并执行用户的 resume() 回调函数。

  • suspend() 回调函数用于在休眠之前让用户关闭设备、配置SDRAM刷新模式、配置GPIO等;
  • resume() 回调函数用于在唤醒之前执行与 suspend() 回调函数相反的操作;
  • 执行这两个函数时系统会关闭CPU总中断开关。如果正在执行 suspend() 回调函数时出现了唤醒事件, 唤醒事件中断会挂起,直到 suspend() 回调函数,系统打开CPU总中断开关时才被处理,然后执行 resume() 回调函数;
已经暂停 ( XWUP_PM_STAGE_SUSPENDED )

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

  • sleep() :休眠SOC
  • wakeup() :唤醒SOC

空闲任务 执行完上一阶段的 suspend() 回调函数后,电源管理步骤切换到此阶段,并执行 sleep() 回调函数。 SOC系统在 sleep() 回调函数内部进入休眠状态,此时时钟停止,代码停止运行, sleep() 回调函数不会返回。

XWOS将 sleep() 回调函数设计在空闲任务中执行,是因为中断优先级的问题有可能导致系统无法唤醒。 例如基于ARM-m的单片机,如果在一个高优先级的中断中执行了休眠指令( WFI ),低优先级的唤醒中断无法把系统唤醒。

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

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

CAPI参考