Zephyr Power Management Subsystem详细介绍
Posted 17岁boy想当攻城狮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Zephyr Power Management Subsystem详细介绍相关的知识,希望对你有一定的参考价值。
目录
- 简介
- 前景补充
- 系统的组成
- Tickless Idle分析
- KCONFIG配置
- 相关API
- 编写一个支持PM电源管理的驱动
简介
Power Management Subsystem(电源管理子系统)在Zephyr下是负责管理电源模式的系统,它制定了一套规范与模型由SOC开发者实现在这个系统上属于自己的电源模式,并且还提供了几种电源管理策略,开发者基于这些策略为SOC开发出对应的电源模式
电源的管理统一由Power Management Subsystem控制,在Power Management Subsystem里定义了部分Weak函数,当发生电源操作时电源管理子系统会去调用这些函数,如果开发者没有实现那么什么都不会做,如果实现了则调用开发者的实现,这样针对每个不同的SOC体系都不需要修改内核代码可以轻松完成对不同SOC的适配
前景补充
Power Management Subsystem对一些电源管理的方法起了一些术语名称,下面是对这些术语名称的解释
SOC Interface
SOC Interface(SOC接口)这是对具备SOC知识并为硬件功能提供接口的组件的通用术语,它将特定于SOC的实现抽象为应用程序和操作系统,简单一点说就是将SOC接口抽象为HAL层(驱动层)
IDLE Thread
IDLE Thread(空闲线程)是Zephyr下空闲线程,它是Zephyr RTOS里优先级最低的线程,当没有线程工作时会进入到这个线程,并将这个线程的CPU工作时间设置为最近一次要被唤醒线程的Ticks步数,倘若Ticks步数是指定电源模式则进入指定电源模式
* Tickless Idle
Tickless Idle(无刻点闲置)可以理解为基于Idle线程的低功耗实现,它的主要作用就是当当前系统不在工作时(没有线程工作或所有线程都进行了Sleep)会被切换到Idle线程,并根据当前的电源策略来进行对应的低功耗处理
Power Gating
Power Gating(功率选通)是实现低功耗的一种方法,比如部分集成电路里集成了开关电路,通过将一些能够被自由切断与连接的电路进行切断,屏蔽电流流通节省功耗,当唤醒时在将电路接通
Power State
Power State(电源状态)是在SOC处理电源时的状态变化描述,在SOC级别实现的处理器和设备电源时的一个状态,通过Power State可以得知当前处于怎样的一个电源状态
Device Runtime Power Management
Device Runtime Power Management(设备运行时电源管理),它的作用是在运行时去将一些未使用或空闲的设备进行关闭,同时它会自动根据使用情况开启与关闭,达到运行时系统节能的状态
系统的组成
Power Management Subsystem将电源管理分成三个部分:System Power Management、Device Power Management Infrastructure、Device Runtime Power Management
System Power Management
System Power Management(系统电源管理)体现Idle线程进行调度上面,当开启CONFIG_PM时,如果内核此时没有调度的话,电源管理系统可以根据选定的电源管理策略和内核分配的空闲时间,将空闲系统置于支持的电源状态之一,它主要体现在系统层面。
应用程序负责设置唤醒事件,唤醒事件通常是由一个SoC外围模块(如SysTick、RTC、Timer或GPIO)触发的中断。根据输入的电源模式,只有一些SoC外围模块可能处于激活状态,并可用作唤醒源
同时当要切换到IDLE线程时会调用钩子函数:suspend(暂停)/resume(恢复),即当进入电源模式之前调用一次suspend函数,出来之后调用resume函数
下图是System Power Management调用过程:
同时System Power Management提供两个子模块:
* Power State
Power State(电源状态)当SOC电源模式进行切换时Zephyr内部会用pm_state来标识当前SOC电源模式(由SOC开发者实现),表明当前系统在进行怎样的电源变化,同时开发者也要实现这些操作,pm_state可以取如下值:
枚举 | 意义 |
---|---|
PM_STATE_ACTIVE | 运行时活动状态 系统已完全通电并处于活动状态 |
PM_STATE_RUNTIME_IDLE | 运行时空闲状态 运行时空闲是一种系统睡眠状态,在这种状态下,所有内核都会进入可能最深的空闲状态,并等待中断,对设备没有任何要求,使其处于当前状态 |
PM_STATE_SUSPEND_TO_IDLE | 挂起到空闲状态 系统会经历一个正常的平台挂起,将所有内核置于尽可能深的空闲状态,并可能将外围设备置于低功耗状态。不会丢失任何操作状态(即cpu核心不会丢失执行上下文),因此系统可以很容易地返回到中断的位置 |
PM_STATE_STANDBY | 待机状态 除了将外围设备置于低功耗状态之外,所有非引导CPU都将关闭电源。相对于挂起到空闲,它应该可以节省更多的能量,但恢复延迟通常会大于该状态。但在uniprocesser系统上,它应该是与挂起到空闲状态相同的状态 |
PM_STATE_SUSPEND_TO_RAM | 挂起到ram状态 此状态通过尽可能多地关闭系统电源提供了显著的节能,此时内存应置于自刷新模式以保留其内容。设备和CPU的状态保存在内存中,可能需要ROM中的一些引导代码才能从中恢复系统 |
PM_STATE_SUSPEND_TO_DISK | 挂起到磁盘状态 此状态通过关闭尽可能多的系统(包括内存)来显著节约能源。内存的内容被写入磁盘或其他非易失性存储器,恢复时,借助引导代码将其读回内存,将系统恢复到挂起到磁盘的同一执行点 |
PM_STATE_SOFT_OFF | 软关闭状态 此状态消耗的电量最小,并且需要较大的延迟才能返回到运行时活动状态。系统的内容(CPU和内存)将不会被保留,因此系统将重新启动,就像从初始通电和内核引导开始一样 |
* Power Management Policies
Power Management Policies(电源管理策略)是处理电源状态的几种策略方针,它支持如下几个策略:
1. Residency
Residency(派驻策略)策略实现方式是为每个电源状态设置一个休眠时间,发生Sleep、Deep Sleep或其它引起休眠的操作时,System Power Management会调用Residency的休眠策略,Tickless Idle会取出最近一次要被唤醒线程的剩余Timeout,然后把这个Timeout与电源状态的休眠时间做一个比较,如果大于等于这个休眠时间则调用Weak协议函数然后进入指定休眠状态
2. Application
Application(应用策略)策略实现方式是由应用程序来实现如何完成休眠工作
3. Dummy
Dummy(假设策略)策略一般用于测试,它会轮流切换不同的电源状态,如有四种电源状态,如果使用Dummy策略会在这四种电源里轮流切换,确保每一种电源状态能够被切换到
Device Power Management Infrastructure
Device Power Management Infrastructure(设备电源管理基础架构)是基础设备模型实现框架,SOC开发者需要根据这个规范实现对应的PM设备模型,它主要体现在控制设备层面上,同时它支持支持两种设备电源管理方法:
Distributed method
Distributed method(分布式)是由应用层直接与PM设备进行交互,根据当前应用程序的工作状态来修改当前SOC的状态,即便当前有线程正在工作也可以让SOC进入指定的低功耗状态,通俗易懂的说就是应用层可以直接访问PM Driver的API接口,直接主动进入指定低功耗状态,但是需要开发者自己去设置当前SOC电源,并且开发者需要根据不同的管理策略做对应的电源处理
Central method
Central method(中心式)这种方式由System Power Management统一处理,它处理的方式就是只有在当前没有任何线程处于工作状态,从沉睡队列里取出一个即将要Timeout的线程(即将要唤醒的线程),如果这个线程沉睡的Ticks步数大于等于指定沉睡模式时会去自己设置SOC电源状态然后进入指定电源模式,并且在进入深度模式时会自动去检查是否满足进入条件,如进入之前需要确保没有一个工作线程,同时最近一次唤醒的线程需大于等于指定沉睡模式的Ticks步数,当然要决定进入深度睡眠的话还需要保证当前设备不能被应用层的一些硬件中断事务打断,如应用层设置了i2c中断或usart中断,如果产生中断这个线程会被唤醒,所以System Power Management需要将这部分中断进行屏蔽,同时System Power Management也会自动设置电源状态
下面是它的设备模型规范介绍,开发者需要实现的几种状态与操作:
Device Power Management States
Device Power Management States(设备电源管理状态)在电源管理子系统里负责标识当前SOC设备的状态,在Device Power Management States里定义了四种设备状态。这些状态是根据在这些状态中丢失的设备上下文的程度、为省电而执行的操作类型以及状态转换对设备行为的影响来分类的,设备上下文包括设备寄存器、时钟、内存等这些相关联的东西
Device Power Management States将电源定义为如下几种状态(模式),这些状态变化时与系统层的System Power Management Power State状态是挂钩的:
状态值 | 状态 |
---|---|
PM_DEVICE_STATE_ACTIVE | 激活状态 设备正常运行,保留所有设备上下文 |
PM_DEVICE_STATE_LOW_POWER | 低功率状态(睡眠状态 硬件保留设备上下文,驱动程序无需还原设备上下文 |
PM_DEVICE_STATE_SUSPEND | 挂起状态(深度睡眠状态) 硬件会丢失大多数设备上下文,设备驱动程序必须保存、还原或重新初始化硬件丢失的任何上下文 |
PM_DEVICE_STATE_SUSPENDING | 转换状态(不属于电源模式) 设备正在从PM_DEVICE_STATE_ACTIVE状态到PM_DEVICE_STATE_SUSPEND状态转换 |
PM_DEVICE_STATE_RESUMING | 转换状态(不属于电源模式) 设备正在从PM_DEVICE_STATE_SUSPEND状态到PM_DEVICE_STATE_ACTIVE状态转换 |
PM_DEVICE_STATE_OFF | 关闭状态 设备电源已完全断开,进入此状态时,设备上下文将丢失,重新开机时需要重新初始化设备 |
Device Power Management Operations
Device Power Management Operations(设备电源管理操作)在电源管理子系统里负责对驱动程序提供功能控制接口,以指示要执行的电源管理操作,同时设置当前电源系统的状态,例如某个外设设备支持电源管理,那么这个外设驱动需要遵循PM设备模型框架,同时定义电源状态与对应的操作,具体状态可以参考System Power Management Power State和Device Power Management States两个条目
Device Power Management Operations提供了两种电源操作命令:
命令 | 作用 |
---|---|
PM_DEVICE_STATE_SET | 设置要操作的电源状态 |
PM_DEVICE_STATE_GET | 获取当前电源状态 |
每个PM驱动需要实现如下操作:
- 实现SOC支持的电源模式
- 实现SOC支持的电源模式转换,从一个电源状态到另外一个电源状态的切换
- 处理在进行电源转换时所需要的操作
以下是PM驱动程序在电源状态转换期间可能执行的一些操作示例:
- 保存/还原电源状态
- 开启/关闭时钟
- 开启/关闭电源
- 屏蔽/取消屏蔽中断
除此之外还提供了一种比较特殊的机制:Busy Status Indication,下面是它的介绍:
Busy Status Indication
Busy Status Indication(忙状态指示)提供了一种叫做硬件事务的中间保护的机制,这种机制作用是为了解决在进行低功耗模式时有其它设备正在进行读写导致的数据不一致的情况,如Flash设备正在进行写操作,此时进入了低功耗模式将Flash的时钟关闭后会导致数据并没有写完,导致复位后读取时数据不一致的情况
如果使用这种方案的话可以将指定设备设置为“硬件事务的中间保护”意味着这个设备正在进行工作,那么PM电源管理子系统就会对这个设备进行保护,只有在这个设备完成之后才进入低功耗模式,否则不会进入,这样就保证了数据的正确性
Device Runtime Power Management
Device Runtime Power Management(设备运行时电源管理)是PM电源管理子系统提供的一个用于在系统处于活动时来降低功耗的一个服务,它的原理就是在当系统处于正常工作时去将一些处于空闲状态或者没有使用的设备将它关闭,它主要体现在系统层面与设备层面
在Zephyr的Driver Management里面每一个设备都有一个引用计数器,比如我们为某个设备写了一个Driver同时在Dts里面将它开启出来,这个Driver会添加到全局的设备列表里,同时每个设备对应一个引用计数器,当我们使用device_get_binding或其它获取Driver API时都会使引用计数器加1,并且使用一些工作API函数时都会让设备的状态发生变化,Device Runtime Power Management就是通过来遍历这些引用计数器和设备状态来判断这个设备此时是空闲还是未使用,如果是空闲或未使用的话就关闭这个设备的时钟来降低功耗,需要注意如果开启与关闭的前提是这个外设驱动支持PM电源操作,这样Device Runtime Power Management才能去调用它的PM电源API与关闭时钟或者电源,具体可以参考Device Power Management Operations条目
Tickless Idle分析
前言
每个Soc上的Tickless Idle实现不同但是对于Tickless Idle的实现都大同相近,此段落的内容以STM32 ARM Cortex-M系列为例
概念
几乎每个RTOS系统上都有一个Idle线程,这个线程的优先级为最低,当没有线程在工作时或当所有线程进入Sleep时Sched(调度器)会切换到Idle线程,同时会设置一个Timer定时器,这个定时器的Ticks步数就是最近一次要被唤醒线程的Timeout,如果没有线程则是K_TICKS_FOREVER,然后将线程切换到Idle线程里,然后根据Ticks步数进入指定电源模式,若不属于任何电源模式则进入一个暂停模式(需要SOC开发者实现),一般很多处理器都有自己的暂停指令,如Intel处理器中的是hlt指令,它可以让CPU进入暂停状态,什么都不做只有被非屏蔽的中断打断时才会继续工作
但是进入空转并不能实现低功耗,因为只是让CPU暂时不进行工作了,许多外设电路都在工作的,为此引入了Tickless Idle,其实就是对Idle线程进行修改,就是在进入Idle线程时去根据最近一次要被唤醒的Ticks步数与电源状态沉睡时间做比较(Residency策略)去进入指定电源状态,不同的电源状态可以关闭不同的外设,不在单纯当CPU不工作,而是让外设也不工作,如在Stm32上的睡眠模式,它会将SOC上1.8v供电的所有电路全部关闭
实现原理
以Residency策略为例
首先定义你的电源状态沉睡时间,假设定义了三种电源状态,每种状态对应不同的沉睡模式
随后在系统开启时会建立四个线程,其中三个为工作线程即用户线程,剩下一个为Idle线程
当A线程调用Sleep时会切换到B,B调用Sleep时会切换到C,当C调用Sleep时会进入到Idle,Idle在取出最近一次要被唤醒的线程Timeout来判定是否要进入电源模式
代码分析
Idle代码存在于:kernel/Idle.c
中,将代码展开如下:
/*
* Copyright (c) 2016 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <kernel.h>
#include <toolchain.h>
#include <linker/sections.h>
#include <drivers/timer/system_timer.h>
#include <wait_q.h>
#include <pm/pm.h>
#include <stdbool.h>
#include <logging/log.h>
#include <ksched.h>
#include <kswap.h>
LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL);
/**
* @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();
d
#endif
void z_pm_save_idle_exit(int32_t ticks)
#ifdef CONFIG_PM
/* Some CPU low power states require notification at the ISR
* to allow any operations that needs to be done before kernel
* switches task or processes nested interrupts.
* This can be simply ignored if not required.
*/
pm_system_resume();
#endif /* CONFIG_PM */
sys_clock_idle_exit();
void idle(void *unused1, void *unused2, void *unused3)
ARG_UNUSED(unused1);
ARG_UNUSED(unused2);
ARG_UNUSED(unused3);
__ASSERT_NO_MSG(_current->base.prio >= 0);
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();
#if !defined(CONFIG_PREEMPT_ENABLED)
# if !defined(CONFIG_USE_SWITCH) || defined(CONFIG_SPARC)
/* A legacy mess: the idle thread is by definition
* preemptible as far as the modern scheduler is
* concerned, but older platforms use
* CONFIG_PREEMPT_ENABLED=n as an optimization hint
* that interrupt exit always returns to the
* interrupted context. So in that setup we need to
* explicitly yield in the idle thread otherwise
* nothing else will run once it starts.
*/
if (_kernel.ready_q.cache != _current)
z_swap_unlocked();
# endif
#endif
它的入口函数是idle(void *unused1, void *unused2, void *unused3)
,这里对它进行一个分析,这里只对重点代码进行分析,首先可以看到第一段while
里的代码
首先它会判断当前是否处于SMP模式(多处理器模式),并且如果是SMP的情况下需要保证每个处理器不能处于IPI模式(处理器间中断(即一个CPU向另外一个CPU发起中断请求,多处理器中断),如果处于多处理器模式且处于处理器间中断的情况下则调用k_busy_wait
函数进入忙等待状态,这个函数不会让当前线程让出CPU时间,不会出现线程切换,等待其它处理器完成处理,然后调用k_yield
函数让出CPU时间,但是有个前提就是当前有线程比当前的线程优先级要高才能让出CPU工作时间
if (IS_ENABLED(CONFIG_SMP) &&
!IS_ENABLED(CONFIG_SCHED_IPI_SUPPORTED))
k_busy_wait(100);
k_yield();
continue;
这段代码是先屏蔽CPU的中断,然后检查有没有CONFIG_PM
宏,也就意味着我们需要在Kconfig
里将CONFIG_PM
开启才能使用PM功能,如果有则调用pm_save_idle
,注意这个函数可以理解为Tickless Idle
的实现,如果没有则调用k_cpu_idle
,也就是最原始的空闲代码,让CPU进入暂停,同时中断解除也是在k_cpu_idle
里实现的
(void) arch_irq_lock();
if (IS_ENABLED(CONFIG_PM))
pm_save_idle();
else
k_cpu_idle();
然后我们假设我们已经开启了CONFIG_PM
宏开启了System Power Management功能,那么就会进入到pm_save_idle
函数中去
可以看到pm_save_idle
函数里第一行就是预编译命令,如果没有定义CONFIG_PM则不会生成对应的代码,所以如果想要开启System Power Management功能就必须定义CONFIG_PM
#ifdef CONFIG_PM
然后它调用了z_get_next_timeout_expiry
函数,这个函数就是取出最近一次要被唤醒线程的Ticks
剩余步数,注意从这里其实可以看到它是一个int32_t
类型的,所以最大值不会超过int32
MAX
int32_t ticks = z_get_next_timeout_expiry();
_kernel.idle = ticks;
然后它会去调用pm_system_suspend
函数去判断ticks
是否处于电源状态沉睡时间,如果处于则进入电源状态,如果不处于则返回PM_STATE_ACTIVE
,然后调用k_cpu_idle
进入暂停
if (pm_system_suspend(ticks) == PM_STATE_ACTIVE)
k_cpu_idle();
我们可以拆开pm_system_suspend
函数看一下:
enum pm_state pm_system_suspend(int32_t ticks)
SYS_PORT_TRACING_FUNC_ENTER(pm, system_suspend, ticks);
z_power_state = pm_policy_next_state(ticks);
if (z_power_state.state == PM_STATE_ACTIVE)
LOG_DBG("No PM operations done.");
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks, z_power_state.state);
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())
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend,
ticks, _handle_device_abort(z_power_state));
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())
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend,
ticks, _handle_device_abort(z_power_state));
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();
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks, z_power_state.state);
return z_power_state.state;
可以看到第一行调用了pm_policy_next_state
函数,注意这个函数会根据你使用的策略而编译对应不同的文件,如使用的是Residency策略,它对应的是pm_residency.c
里的实现,所以在这里就体现到Power Management Policies
z_power_state = pm_policy_next_state(ticks);
这里我将pm_residency.c
里的代码拿出来看下它的具体实现:
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)以上是关于Zephyr Power Management Subsystem详细介绍的主要内容,如果未能解决你的问题,请参考以下文章
Zephyr Power Management Subsystem详细介绍
ARM Cortex-A73 Power management
使用PD(Power Designer)设计数据库,并且生成可执行的SQL文件创建数据库(本文以SQL Server Management Studio软件执行为例)
TFS 任务在远程计算机上运行 Power shell 错误:System.Management.Automation.RuntimeException:无法安装“VisualStudioRemote