feat(esp_pm): add tickless idle support in WAITI mode to reduce power consumption

This commit is contained in:
wuzhenghui
2026-07-22 12:55:53 +08:00
parent 306d54b6d5
commit 0aa75e183b
12 changed files with 407 additions and 21 deletions
+7
View File
@@ -1170,9 +1170,12 @@ static esp_err_t SLEEP_FN_ATTR esp_sleep_start(uint32_t sleep_flags, esp_sleep_m
#endif
// Configure timer wakeup
bool timer_wakeup_armed = false;
if (!should_skip_sleep && (s_config.wakeup_triggers & RTC_TIMER_TRIG_EN)) {
if (timer_wakeup_prepare(sleep_duration) != ESP_OK) {
should_skip_sleep = allow_sleep_rejection ? true : false;
} else {
timer_wakeup_armed = true;
}
}
@@ -1185,6 +1188,10 @@ static esp_err_t SLEEP_FN_ATTR esp_sleep_start(uint32_t sleep_flags, esp_sleep_m
} else {
result = esp_sleep_start_safe(sleep_flags, reject_triggers, deep_sleep, &config);
}
if (timer_wakeup_armed) {
/* Disarm leftover comparator after non-timer wakeups so it cannot fire into other LP/RTC timer users. */
rtc_timer_hal_clear_wakeup_time(0);
}
#if CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION
if (sleep_flags & RTC_SLEEP_PD_VDDSDIO) {
/* Cache Suspend 2: If previous sleep powerdowned the flash, suspend cache here so that the
+1 -1
View File
@@ -1,5 +1,5 @@
idf_build_get_property(target IDF_TARGET)
set(priv_requires esp_system esp_driver_gpio esp_timer esp_hal_uart)
set(priv_requires esp_system esp_driver_gpio esp_timer esp_hal_uart esp_hal_rtc_timer esp_hal_systimer hal freertos)
if(${target} STREQUAL "linux")
return() # This component is not supported by the POSIX/Linux simulator
+14
View File
@@ -78,6 +78,20 @@ menu "Power Management"
This feature is intended to be used when lower power consumption is needed
while there is enough place in IRAM to place source code.
config PM_TICKLESS_IDLE_WAITI
bool "Enable tickless idle without entering light sleep"
depends on PM_ENABLE
depends on FREERTOS_USE_TICKLESS_IDLE
depends on FREERTOS_SYSTICK_USES_SYSTIMER
depends on SOC_RTC_TIMER_SUPPORTED
default n
help
When enabled, tickless idle also applies when the system does not enter
light sleep. This includes DFS-only configurations (light sleep disabled),
and cases where light sleep is enabled but not taken (for example when
blocked by ESP_PM_NO_LIGHT_SLEEP or other PM locks). This can reduce idle
power consumption in those situations.
config PM_SLP_DISABLE_GPIO
bool "Disable all GPIO when chip at sleep"
depends on FREERTOS_USE_TICKLESS_IDLE
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -84,8 +84,10 @@ void esp_pm_impl_switch_mode(pm_mode_t mode, pm_mode_switch_t lock_or_unlock, pm
void esp_pm_impl_init(void);
/**
* @brief Hook function for the idle task
* Must be called from the IDLE task on each CPU before entering waiti state.
* @brief Release the per-core RTOS PM lock so DFS can drop frequency while idle.
*
* Safe to call more than once per idle entry: subsequent calls are no-ops until
* an ISR / leave_idle() re-acquires the lock.
*/
void esp_pm_impl_idle_hook(void);
@@ -110,6 +112,17 @@ void esp_pm_impl_dump_stats(FILE* out);
*/
void esp_pm_impl_waiti(void);
#if CONFIG_PM_TICKLESS_IDLE_WAITI
/**
* @brief Execute a planned tickless WAITI from the idle task, if any.
*
* Handles RTOS PM lock release around WFI. Call once from esp_vApplicationIdleHook();
*
* @return true if tickless WAITI ran (idle hook should return early)
*/
bool esp_pm_impl_tickless_waiti(void);
#endif
/**
* @brief Callback function type for peripherals to skip light sleep.
*
+3
View File
@@ -4,6 +4,9 @@ entries:
if PM_RTOS_IDLE_OPT = y:
pm_impl:esp_pm_impl_idle_hook (noflash)
pm_impl:esp_pm_impl_waiti (noflash)
if PM_TICKLESS_IDLE_WAITI = y:
pm_impl:esp_pm_impl_tickless_waiti (noflash)
pm_impl:tickless_waiti_enter (noflash)
if PM_SLP_IRAM_OPT = y:
pm_impl:esp_pm_impl_get_cpu_freq (noflash)
+315 -9
View File
@@ -30,7 +30,6 @@
#include "hal/uart_ll.h"
#include "hal/uart_types.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -54,6 +53,36 @@
#include "esp_memory_utils.h"
#include "esp_rom_sys.h"
#if CONFIG_PM_TICKLESS_IDLE_WAITI
#include "hal/rtc_timer_hal.h"
#include "hal/systimer_ll.h"
#include "esp_private/systimer.h"
#include "esp_private/freertos_idf_additions_priv.h"
#include "esp_intr_alloc.h"
#if CONFIG_ESP_TASK_WDT_EN
#include "esp_private/esp_task_wdt.h"
#endif
#if CONFIG_ESP_INT_WDT
#include "esp_private/esp_int_wdt.h"
#endif
#if SOC_LP_PERIPH_SHARE_INTERRUPT
#include "esp_private/rtc_ctrl.h"
#endif
#if SOC_LP_PERIPH_SHARE_INTERRUPT
#define TICKLESS_WAITI_LP_TIMER_INTR_SOURCE ETS_LP_TIMER_INTR_SOURCE
#else
#if CONFIG_IDF_TARGET_ESP32P4 || CONFIG_IDF_TARGET_ESP32S31
#define TICKLESS_WAITI_LP_TIMER_INTR_SOURCE ETS_LP_TIMER_REG_0_INTR_SOURCE
#else
#define TICKLESS_WAITI_LP_TIMER_INTR_SOURCE ETS_LP_RTC_TIMER_INTR_SOURCE
/* LP_RTC_TIMER is shared by the LP timer alarm and brownout detector (see power_supply_periph.c). */
#define TICKLESS_WAITI_LP_TIMER_INTR_STATUS ((uint32_t)&LP_TIMER.int_st)
#define TICKLESS_WAITI_LP_TIMER_INTR_MASK BIT(31) /* LP_TIMER_SOC_WAKEUP_INT_ST */
#endif
#endif /* !SOC_LP_PERIPH_SHARE_INTERRUPT */
#endif /* CONFIG_PM_TICKLESS_IDLE_WAITI */
#define MHZ (1000000)
#ifdef CONFIG_FREERTOS_SYSTICK_USES_CCOUNT
@@ -71,10 +100,10 @@
#define CCOMPARE_PREPARE_CYCLES_IN_FREQ_UPDATE 60
#endif // CONFIG_FREERTOS_SYSTICK_USES_CCOUNT
/* When light sleep is used, wake this number of microseconds earlier than
/* When tickless idle is used, wake this number of microseconds earlier than
* the next tick.
*/
#define LIGHT_SLEEP_EARLY_WAKEUP_US 100
#define TICKLESS_IDLE_EARLY_WAKEUP_US 100
#if CONFIG_IDF_TARGET_ESP32
/* Minimal divider at which REF_CLK_FREQ can be obtained */
@@ -825,6 +854,254 @@ static inline void IRAM_ATTR other_core_should_skip_light_sleep(int core_id)
#endif
}
/* ---------------------------------------- Tickless WAITI Implementation ------------------------------------------
* Use the LP/RTC timer to bound the maximum idle duration, then enter WAITI with interrupts
* enabled. The OS tick systimer keeps running during WAITI; the LP timer alarm (or any other
* interrupt) takes the CPU out of WFI.
*
* Multi-core: the LP/RTC timer has a single shared alarm comparator (timer_id=0), programmed
* with the earliest deadline among the parked cores. The LP timer interrupt is only routed to
* core 0, so its ISR calls portYIELD_CORE() on the other parked core. Whenever any core leaves
* WAITI it yields any remaining parked sibling.
*
* Two-phase park: portSUPPRESS_TICKS_AND_SLEEP() (vApplicationSleep) runs under xKernelLock with
* interrupts masked, so WFI cannot be issued there. Phase 1 arms the LP alarm, suppresses the OS
* tick, and records the wake deadline in s_waiti_plan[]; phase 2 runs in esp_vApplicationIdleHook()
* on the next idle-loop iteration to enter WFI, disarm, restore the tick, and advance xTickCount
* via xTaskCatchUpTicks() on core 0.
* esp_pm_impl_waiti() handles plain WFI for shorter idle windows.
* --------------------------------------------------------------------------------------------------------------- */
#if CONFIG_PM_TICKLESS_IDLE_WAITI
/* Protected by s_switch_lock */
typedef struct {
bool valid;
int64_t deadline_us; // absolute esp_timer time at which to wake.
} tickless_waiti_plan_t;
static tickless_waiti_plan_t s_waiti_plan[CONFIG_FREERTOS_NUMBER_OF_CORES];
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
static volatile uint32_t s_waiti_armed_mask; // cores that currently hold an armed LP/RTC deadline.
static volatile uint32_t s_waiti_parked_mask; // cores currently blocked in the WAITI WFI.
static uint64_t s_waiti_target_rtc[CONFIG_FREERTOS_NUMBER_OF_CORES]; // per-core wakeup deadline in RTC slow-clock ticks.
static uint64_t s_waiti_programmed_target; // deadline currently programmed into the shared comparator.
#endif
static void IRAM_ATTR tickless_waiti_isr(void *arg)
{
(void)arg;
#if SOC_LP_PERIPH_SHARE_INTERRUPT
SET_PERI_REG_MASK(RTC_CNTL_INT_CLR_REG, RTC_CNTL_MAIN_TIMER_INT_CLR_M);
#else
rtc_timer_ll_clear_alarm_intr_status(&LP_TIMER, 0);
#endif
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
/* The shared LP/RTC timer interrupt is only routed to core 0; wake the core1 if parked. */
int core_id = esp_cpu_get_core_id();
if (s_waiti_parked_mask & BIT(1 - core_id)) {
portYIELD_CORE(1 - core_id);
}
#endif
}
/* Suppress this core's periodic OS-tick interrupt. Only the interrupt is masked; the periodic
* comparator and the systimer counter keep free-running, so the exact number of ticks elapsed while
* masked is preserved in the counter. */
FORCE_INLINE_ATTR void tickless_waiti_suppress_sys_tick_intr(int core_id)
{
systimer_ll_enable_alarm_int(&SYSTIMER, SYSTIMER_ALARM_OS_TICK_CORE0 + core_id, false);
}
FORCE_INLINE_ATTR void tickless_waiti_restore_sys_tick_intr(int core_id)
{
systimer_ll_enable_alarm_int(&SYSTIMER, SYSTIMER_ALARM_OS_TICK_CORE0 + core_id, true);
}
FORCE_INLINE_ATTR void tickless_waiti_arm_alarm(int core_id, int64_t sleep_time_us)
{
uint32_t cal_val = esp_clk_slowclk_cal_get();
uint64_t rtc_ticks = rtc_time_us_to_slowclk((uint64_t)sleep_time_us, cal_val);
uint64_t now_rtc = rtc_timer_hal_get_cycle_count(0);
/* rtc_time_us_to_slowclk() truncates; a zero delta makes target == now_rtc and the
* alarm will never fire. Clamp to one slow-clock tick. */
uint64_t target = now_rtc + MAX(rtc_ticks, 1);
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
/* The comparator is shared, so program it with the earliest deadline among all armed cores.
* Only reprogram when this core lowers the deadline or the alarm was idle, to avoid
* clobbering an earlier deadline that another core still depends on. */
s_waiti_target_rtc[core_id] = target;
bool was_empty = (s_waiti_armed_mask == 0);
s_waiti_armed_mask |= BIT(core_id);
uint64_t min_target = target;
if ((s_waiti_armed_mask & BIT(1 - core_id)) && s_waiti_target_rtc[1 - core_id] < target) {
min_target = s_waiti_target_rtc[1 - core_id];
}
if (was_empty || (min_target < s_waiti_programmed_target)) {
rtc_timer_hal_set_wakeup_time(0, min_target);
s_waiti_programmed_target = min_target;
}
#else
(void)core_id;
rtc_timer_hal_set_wakeup_time(0, target);
#endif
}
FORCE_INLINE_ATTR void tickless_waiti_disarm_alarm(int core_id)
{
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
s_waiti_armed_mask &= ~BIT(core_id);
if (s_waiti_armed_mask != 0) {
/* The leaving core may have owned the programmed min; reprogram to the earliest
* deadline still armed so the sibling is not woken early or spuriously. */
uint64_t min_target = s_waiti_target_rtc[1 - core_id];
if (min_target != s_waiti_programmed_target) {
rtc_timer_hal_set_wakeup_time(0, min_target);
s_waiti_programmed_target = min_target;
}
return;
}
#else
(void)core_id;
#endif
#if SOC_LP_PERIPH_SHARE_INTERRUPT
CLEAR_PERI_REG_MASK(RTC_CNTL_SLP_TIMER1_REG, RTC_CNTL_MAIN_TIMER_ALARM_EN_M);
SET_PERI_REG_MASK(RTC_CNTL_INT_CLR_REG, RTC_CNTL_MAIN_TIMER_INT_CLR_M);
#else
rtc_timer_ll_set_target_enable(&LP_TIMER, 0, false);
rtc_timer_ll_clear_alarm_intr_status(&LP_TIMER, 0);
#endif
}
static bool tickless_waiti_enter(void)
{
int core_id = xPortGetCoreID();
if (!s_skipped_light_sleep[core_id] || !s_waiti_plan[core_id].valid) {
return false;
}
portENTER_CRITICAL(&s_switch_lock);
s_waiti_plan[core_id].valid = false;
int64_t remaining_us = s_waiti_plan[core_id].deadline_us - esp_timer_get_time();
if ((remaining_us < configEXPECTED_IDLE_TIME_BEFORE_SLEEP * portTICK_PERIOD_MS * 1000LL)
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
/* core 0 owns the global tick; it must not suppress it while core 1 is still active. */
|| (core_id == 0 && !(s_waiti_parked_mask & BIT(1)))
#endif
) {
/* Abort without Claim/CatchUp: systimer counter kept running while the alarm int was
* masked, so the next SysTickIsrHandler will recover missed ticks via its diff path. */
tickless_waiti_disarm_alarm(core_id);
tickless_waiti_restore_sys_tick_intr(core_id);
portEXIT_CRITICAL(&s_switch_lock);
return false;
}
bool stop_global_tick = (core_id == 0);
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
s_waiti_parked_mask |= BIT(core_id);
#if CONFIG_ESP_INT_WDT && CONFIG_ESP_INT_WDT_CHECK_CPU1
if (core_id == 1) {
/* The interrupt watchdog feed runs from core 0's tick hook and, with CPU1 liveness checking,
* requires core 1 to keep ticking. Pause that check while core 1 is intentionally idle. */
esp_int_wdt_pause_cpu1_checking(true);
}
#endif
#endif
portEXIT_CRITICAL(&s_switch_lock);
if (stop_global_tick) {
/* Tick hook no longer runs while the global tick is suppressed, so the watchdogs it feeds
* would time out. Pause them across the WAITI window. */
#if CONFIG_ESP_TASK_WDT_EN
esp_task_wdt_stop();
#endif
#if CONFIG_ESP_INT_WDT
esp_int_wdt_pause();
#endif
}
/* Lock-free context with interrupts enabled: the LP/RTC alarm (or any other interrupt) takes
* the CPU out of WFI, and an ISR may portYIELD_CORE() this core. */
esp_cpu_wait_for_intr();
if (stop_global_tick) {
#if CONFIG_ESP_INT_WDT
esp_int_wdt_resume();
#endif
#if CONFIG_ESP_TASK_WDT_EN
esp_task_wdt_restart();
#endif
}
portENTER_CRITICAL(&s_switch_lock);
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
s_waiti_parked_mask &= ~BIT(core_id);
#if CONFIG_ESP_INT_WDT && CONFIG_ESP_INT_WDT_CHECK_CPU1
if (core_id == 1) {
esp_int_wdt_pause_cpu1_checking(false);
}
#endif
// We may have been woken by an unrelated interrupt rather than the LP timer; make sure
// the sibling leaves WAITI and restores the tick.
if (s_waiti_parked_mask & BIT(1 - core_id)) {
portYIELD_CORE(1 - core_id);
}
#endif
tickless_waiti_disarm_alarm(core_id);
uint32_t elapsed_ticks = xPortSysTickClaimElapsedTicks(core_id);
tickless_waiti_restore_sys_tick_intr(core_id);
portEXIT_CRITICAL(&s_switch_lock);
if (core_id == 0 && elapsed_ticks > 0) {
(void) xTaskCatchUpTicks((TickType_t) elapsed_ticks);
}
/* Wake path re-acquires the RTOS lock via leave_idle(); drop it again so
* vApplicationSleep() in this idle iteration can enter light sleep / prepare WAITI. */
esp_pm_impl_idle_hook();
return true;
}
bool esp_pm_impl_tickless_waiti(void)
{
/* Release RTOS PM lock before any WFI so DFS can drop to min_freq. */
esp_pm_impl_idle_hook();
return tickless_waiti_enter();
}
static void tickless_waiti_init(void)
{
#if SOC_LP_PERIPH_SHARE_INTERRUPT
rtc_isr_register(tickless_waiti_isr, NULL, RTC_CNTL_MAIN_TIMER_INT_ENA_M, 0);
SET_PERI_REG_MASK(RTC_CNTL_INT_ENA_REG, RTC_CNTL_MAIN_TIMER_INT_ENA_M);
#else
#if CONFIG_IDF_TARGET_ESP32P4 || CONFIG_IDF_TARGET_ESP32S31
ESP_ERROR_CHECK(esp_intr_alloc(TICKLESS_WAITI_LP_TIMER_INTR_SOURCE, ESP_INTR_FLAG_IRAM, tickless_waiti_isr, NULL, NULL));
#else
ESP_ERROR_CHECK(esp_intr_alloc_intrstatus(TICKLESS_WAITI_LP_TIMER_INTR_SOURCE,
ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED,
TICKLESS_WAITI_LP_TIMER_INTR_STATUS,
TICKLESS_WAITI_LP_TIMER_INTR_MASK,
tickless_waiti_isr, NULL, NULL));
#endif
rtc_timer_ll_alarm_intr_enable(&LP_TIMER, 0, true);
#endif
}
#endif /* CONFIG_PM_TICKLESS_IDLE_WAITI */
// Compute how long the CPU may stay idle: the smaller of the FreeRTOS expected idle window
// and the time until the next esp_timer alarm.
FORCE_INLINE_ATTR int64_t pm_get_min_idle_us(TickType_t xExpectedIdleTime)
{
int64_t now = esp_timer_get_time();
int64_t next_esp_timer_alarm = esp_timer_get_next_alarm_for_wake_up();
int64_t time_until_next_alarm = next_esp_timer_alarm - now;
int64_t wakeup_delay_us = portTICK_PERIOD_MS * 1000LL * xExpectedIdleTime;
return MIN(wakeup_delay_us, time_until_next_alarm);
}
// Adjust RTOS tick count based on the amount of time spent in sleep.
FORCE_INLINE_ATTR void pm_step_tick(int64_t slept_us, TickType_t xExpectedIdleTime)
{
@@ -866,11 +1143,7 @@ void vApplicationSleep( TickType_t xExpectedIdleTime )
int core_id = xPortGetCoreID();
if (!should_skip_light_sleep(core_id)) {
/* Calculate how much we can sleep */
int64_t next_esp_timer_alarm = esp_timer_get_next_alarm_for_wake_up();
int64_t now = esp_timer_get_time();
int64_t time_until_next_alarm = next_esp_timer_alarm - now;
int64_t wakeup_delay_us = portTICK_PERIOD_MS * 1000LL * xExpectedIdleTime;
int64_t sleep_time_us = MIN(wakeup_delay_us, time_until_next_alarm);
int64_t sleep_time_us = pm_get_min_idle_us(xExpectedIdleTime);
int64_t slept_us = 0;
#if CONFIG_PM_LIGHT_SLEEP_CALLBACKS
uint32_t cycle = esp_cpu_get_cycle_count();
@@ -878,7 +1151,7 @@ void vApplicationSleep( TickType_t xExpectedIdleTime )
sleep_time_us -= (esp_cpu_get_cycle_count() - cycle) / (esp_clk_cpu_freq() / 1000000ULL);
#endif
if (sleep_time_us >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP * portTICK_PERIOD_MS * 1000LL) {
esp_sleep_enable_timer_wakeup(sleep_time_us - LIGHT_SLEEP_EARLY_WAKEUP_US);
esp_sleep_enable_timer_wakeup(sleep_time_us - TICKLESS_IDLE_EARLY_WAKEUP_US);
/* Enter sleep */
ESP_PM_TRACE_ENTER(SLEEP, core_id);
int64_t sleep_start = esp_timer_get_time();
@@ -904,6 +1177,35 @@ void vApplicationSleep( TickType_t xExpectedIdleTime )
esp_pm_execute_exit_sleep_callbacks(slept_us);
#endif
}
#if CONFIG_PM_TICKLESS_IDLE_WAITI
else {
/* Light sleep was skipped. This covers both the case where light sleep is globally
* disabled (DFS-only configuration, s_light_sleep_en == false) and the case where it is
* enabled but currently forbidden (a power management lock keeps s_mode away from
* PM_MODE_LIGHT_SLEEP, a mode switch is in progress, or a peripheral vetoed sleep). In all
* of these we can still suppress the OS tick and enter WAITI to save power.
*
* WFI must run with interrupts enabled, which is not the case here (xKernelLock is held).
* esp_pm_impl_tickless_waiti() enters WFI on the next idle-loop iteration.
* On failure to tickless-park, esp_pm_impl_waiti() handles plain WFI. */
int64_t sleep_time_us = pm_get_min_idle_us(xExpectedIdleTime);
if ((sleep_time_us >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP * portTICK_PERIOD_MS * 1000LL)
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
/* core 0 owns the global tick; it must not suppress it while core 1 is still active. */
&& (core_id != 0 || (s_waiti_parked_mask & BIT(1)))
#endif
) {
s_waiti_plan[core_id].deadline_us = esp_timer_get_time() + sleep_time_us;
s_waiti_plan[core_id].valid = true;
/* Wake slightly early so that slow-clock inaccuracy biases towards an early
* (rather than late) wakeup; xTaskCatchUpTicks() on core 0 recover the exact
* tick count after WFI. */
tickless_waiti_arm_alarm(core_id, sleep_time_us - TICKLESS_IDLE_EARLY_WAKEUP_US);
tickless_waiti_suppress_sys_tick_intr(core_id);
}
}
#endif /* CONFIG_PM_TICKLESS_IDLE_WAITI */
portEXIT_CRITICAL(&s_switch_lock);
}
#endif //CONFIG_FREERTOS_USE_TICKLESS_IDLE
@@ -1020,6 +1322,10 @@ void esp_pm_impl_init(void)
s_cpu_freq_by_mode[i] = default_config;
}
#if CONFIG_PM_TICKLESS_IDLE_WAITI
tickless_waiti_init();
#endif
#ifdef CONFIG_PM_DFS_INIT_AUTO
int xtal_freq_mhz = esp_clk_xtal_freq() / MHZ;
esp_pm_config_t cfg = {
+9 -1
View File
@@ -40,8 +40,16 @@ void esp_vApplicationTickHook(void)
void esp_vApplicationIdleHook(void)
{
bool can_go_idle = true;
int core = xPortGetCoreID();
#if CONFIG_PM_TICKLESS_IDLE_WAITI
/* Two-phase WAITI: plan is armed in vApplicationSleep(); execute it here (kernel lock free). */
if (esp_pm_impl_tickless_waiti()) {
return;
}
#endif
bool can_go_idle = true;
for (int n = 0; n < MAX_HOOKS; n++) {
if (idle_cb[core][n] != NULL && !idle_cb[core][n]()) {
can_go_idle = false;
+4
View File
@@ -331,6 +331,10 @@ menu "FreeRTOS"
when no tasks need to run. To skip unnecessary wake-up initialize a timer with the
"skip_unhandled_events" option as true.
While the OS tick interrupt is suppressed during tickless idle, tick hooks are not invoked
for those suppressed ticks. This applies to esp_register_freertos_tick_hook() callbacks and
to configUSE_TICK_HOOK.
If disabled, automatic light sleep support will be disabled.
config FREERTOS_IDLE_TIME_BEFORE_SLEEP
@@ -201,6 +201,24 @@
#endif /* ( !CONFIG_FREERTOS_SMP && ( configNUM_CORES > 1 ) ) */
#if CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER
/*
* Reconcile the given core's OS-tick accounting against the free-running counter while its
* OS-tick interrupt is still masked.
*
* Updates s_handled_systicks using the same accounting as SysTickIsrHandler(), so
* re-enabling the alarm afterwards will not reprocess suppressed ticks. The caller should
* restore the OS-tick interrupt and then advance the RTOS tick by the returned amount.
*
* @param cpu_id Core whose suppressed OS-tick accounting should be reconciled.
*
* @return Number of whole OS-tick periods that elapsed while masked.
*/
uint32_t xPortSysTickClaimElapsedTicks( int cpu_id );
#endif /* CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER */
/*------------------------------------------------------------------------------
* TASK UTILITIES (PRIVATE)
*----------------------------------------------------------------------------*/
+16 -3
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -50,6 +50,9 @@ void SysTickIsrHandler(void *arg);
static uint32_t s_handled_systicks[configNUM_CORES] = { 0 };
/* Systimer HAL layer object */
static systimer_hal_context_t systimer_hal;
/**
* @brief Set up the systimer peripheral to generate the tick interrupt
*
@@ -65,8 +68,6 @@ void vSystimerSetup(void)
#else
const unsigned level = ESP_INTR_FLAG_LEVEL1;
#endif
/* Systimer HAL layer object */
static systimer_hal_context_t systimer_hal;
/* set system timer interrupt vector */
ESP_ERROR_CHECK(esp_intr_alloc(ETS_SYSTIMER_TARGET0_INTR_SOURCE + cpuid, ESP_INTR_FLAG_IRAM | level, SysTickIsrHandler, &systimer_hal, NULL));
@@ -155,6 +156,18 @@ void SysTickIsrHandler(void *arg)
ESP_PM_TRACE_EXIT(TICK, cpuid);
#endif
}
uint32_t xPortSysTickClaimElapsedTicks(int cpu_id)
{
uint32_t alarm_id = SYSTIMER_ALARM_OS_TICK_CORE0 + (uint32_t)cpu_id;
uint32_t total = systimer_hal_get_counter_value(&systimer_hal, SYSTIMER_COUNTER_OS_TICK) / systimer_ll_get_alarm_period(systimer_hal.dev, alarm_id);
uint32_t elapsed = total - s_handled_systicks[cpu_id];
if (elapsed == 0) {
return 0;
}
s_handled_systicks[cpu_id] = total;
return elapsed;
}
#endif /* CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER */
/* ------------------------------------------------ Common Port Tick ---------------------------------------------------
@@ -17,8 +17,8 @@ extern "C"
typedef enum {
ETS_LP_RTC_INTR_SOURCE = 0,
ETS_LP_WDT_INTR_SOURCE,
ETS_LP_TIMER_REG0_INTR_SOURCE,
ETS_LP_TIMER_REG1_INTR_SOURCE,
ETS_LP_TIMER_REG_0_INTR_SOURCE,
ETS_LP_TIMER_REG_1_INTR_SOURCE,
ETS_MB_HP_INTR_SOURCE,
ETS_MB_LP_INTR_SOURCE,
ETS_PMU_0_INTR_SOURCE,
+2 -2
View File
@@ -9,8 +9,8 @@
const char *const esp_isr_names[] = {
[ETS_LP_RTC_INTR_SOURCE] = "LP_RTC",
[ETS_LP_WDT_INTR_SOURCE] = "LP_WDT",
[ETS_LP_TIMER_REG0_INTR_SOURCE] = "LP_TIMER_REG0",
[ETS_LP_TIMER_REG1_INTR_SOURCE] = "LP_TIMER_REG1",
[ETS_LP_TIMER_REG_0_INTR_SOURCE] = "LP_TIMER_REG0",
[ETS_LP_TIMER_REG_1_INTR_SOURCE] = "LP_TIMER_REG1",
[ETS_MB_HP_INTR_SOURCE] = "MB_HP",
[ETS_MB_LP_INTR_SOURCE] = "MB_LP",
[ETS_PMU_0_INTR_SOURCE] = "PMU_0",