From 0c7caf79b5948c56b9fd6852b3e47bd1ca443d89 Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Wed, 17 Jun 2026 17:51:23 +0800 Subject: [PATCH] feat(esp_hw_support): implement software CPU retention synchronization for SMP sleep modes --- .../include/esp_private/sleep_cpu.h | 23 +++- .../lowpower/port/esp32h4/sleep_cpu.c | 18 +--- .../lowpower/port/esp32p4/sleep_cpu.c | 18 +--- .../lowpower/port/esp32s31/sleep_cpu.c | 18 +--- components/esp_hw_support/sleep_modes.c | 101 +++++++++++------- 5 files changed, 92 insertions(+), 86 deletions(-) diff --git a/components/esp_hw_support/include/esp_private/sleep_cpu.h b/components/esp_hw_support/include/esp_private/sleep_cpu.h index 76bb557910a..905d24ea7d3 100644 --- a/components/esp_hw_support/include/esp_private/sleep_cpu.h +++ b/components/esp_hw_support/include/esp_private/sleep_cpu.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -69,14 +69,27 @@ esp_err_t esp_sleep_cpu_retention(uint32_t (*goto_sleep)(uint32_t, uint32_t, uin #if !CONFIG_FREERTOS_UNICORE && CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU /** - * Do sleep prepare for other smp cores + * Start the software CPU retention synchronization. + * + * This waits until the other CPU retention state to IDLE before + * the caller starts the software CPU retention. */ -void sleep_smp_cpu_sleep_prepare(void); +void sleep_cpu_retention_start(void); /** - * Do wakeup prepare for other smp cores + * Run the software CPU retention flow on the stalled other CPU. + * + * @param arg Unused */ -void sleep_smp_cpu_wakeup_prepare(void); +void sleep_cpu_retention_execute(void* arg); + +/** + * Finish the software CPU retention synchronization. + * + * This is called by the initiating CPU after wakeup or sleep rejection to + * synchronize with the other CPU retention state and return its own state to idle. + */ +void sleep_cpu_retention_finish(void); /** * Notify the other core that this sleep does not require retention. diff --git a/components/esp_hw_support/lowpower/port/esp32h4/sleep_cpu.c b/components/esp_hw_support/lowpower/port/esp32h4/sleep_cpu.c index 546101cfae1..ba09ce46e19 100644 --- a/components/esp_hw_support/lowpower/port/esp32h4/sleep_cpu.c +++ b/components/esp_hw_support/lowpower/port/esp32h4/sleep_cpu.c @@ -477,17 +477,12 @@ esp_err_t sleep_cpu_configure(bool light_sleep_enable) #if !CONFIG_FREERTOS_UNICORE #if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU -static IRAM_ATTR void smp_core_do_retention(void) +IRAM_ATTR void sleep_cpu_retention_execute(void* arg) { + (void) arg; esp_cpu_branch_prediction_disable(); uint8_t core_id = esp_cpu_get_core_id(); - if (core_id == 0) { - WRITE_PERI_REG(INTPRI_CPU_INTR_FROM_CPU_2_REG, 0); - } else { - WRITE_PERI_REG(INTPRI_CPU_INTR_FROM_CPU_3_REG, 0); - } - // Wait another core start to do retention ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-infinite-loop") bool smp_skip_retention = false; @@ -560,19 +555,16 @@ IRAM_ATTR void esp_sleep_cpu_skip_retention(void) { } #endif -void sleep_smp_cpu_sleep_prepare(void) +void sleep_cpu_retention_start(void) { #if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU while (atomic_load(&s_smp_retention_state[!esp_cpu_get_core_id()]) != SMP_IDLE) { ; } - esp_ipc_isr_call((esp_ipc_isr_func_t)smp_core_do_retention, NULL); -#else - esp_ipc_isr_stall_other_cpu(); #endif } -void sleep_smp_cpu_wakeup_prepare(void) +void sleep_cpu_retention_finish(void) { #if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU uint8_t core_id = esp_cpu_get_core_id(); @@ -584,8 +576,6 @@ void sleep_smp_cpu_wakeup_prepare(void) ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-infinite-loop") } atomic_store(&s_smp_retention_state[core_id], SMP_IDLE); -#else - esp_ipc_isr_release_other_cpu(); #endif } #endif //!CONFIG_FREERTOS_UNICORE diff --git a/components/esp_hw_support/lowpower/port/esp32p4/sleep_cpu.c b/components/esp_hw_support/lowpower/port/esp32p4/sleep_cpu.c index 0104d6e55bd..eefa0849aa6 100644 --- a/components/esp_hw_support/lowpower/port/esp32p4/sleep_cpu.c +++ b/components/esp_hw_support/lowpower/port/esp32p4/sleep_cpu.c @@ -395,17 +395,12 @@ esp_err_t sleep_cpu_configure(bool light_sleep_enable) #if !CONFIG_FREERTOS_UNICORE #if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU -static SPM_IRAM_ATTR void smp_core_do_retention(void) +SPM_IRAM_ATTR void sleep_cpu_retention_execute(void* arg) { + (void) arg; esp_cpu_branch_prediction_disable(); uint8_t core_id = esp_cpu_get_core_id(); - if (core_id == 0) { - WRITE_PERI_REG(HP_SYSTEM_CPU_INT_FROM_CPU_2_REG, 0); - } else { - WRITE_PERI_REG(HP_SYSTEM_CPU_INT_FROM_CPU_3_REG, 0); - } - // Wait another core start to do retention ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-infinite-loop") bool smp_skip_retention = false; @@ -475,19 +470,16 @@ SPM_IRAM_ATTR void esp_sleep_cpu_skip_retention(void) { } #endif -void sleep_smp_cpu_sleep_prepare(void) +void sleep_cpu_retention_start(void) { #if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU while (atomic_load(&s_smp_retention_state[!esp_cpu_get_core_id()]) != SMP_IDLE) { ; } - esp_ipc_isr_call((esp_ipc_isr_func_t)smp_core_do_retention, NULL); -#else - esp_ipc_isr_stall_other_cpu(); #endif } -void sleep_smp_cpu_wakeup_prepare(void) +void sleep_cpu_retention_finish(void) { #if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU uint8_t core_id = esp_cpu_get_core_id(); @@ -499,8 +491,6 @@ void sleep_smp_cpu_wakeup_prepare(void) ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-infinite-loop") } atomic_store(&s_smp_retention_state[core_id], SMP_IDLE); -#else - esp_ipc_isr_release_other_cpu(); #endif } #endif //!CONFIG_FREERTOS_UNICORE diff --git a/components/esp_hw_support/lowpower/port/esp32s31/sleep_cpu.c b/components/esp_hw_support/lowpower/port/esp32s31/sleep_cpu.c index 138f6066a1f..be12e505cf2 100644 --- a/components/esp_hw_support/lowpower/port/esp32s31/sleep_cpu.c +++ b/components/esp_hw_support/lowpower/port/esp32s31/sleep_cpu.c @@ -418,17 +418,12 @@ esp_err_t sleep_cpu_configure(bool light_sleep_enable) #if !CONFIG_FREERTOS_UNICORE #if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU -static IRAM_ATTR void smp_core_do_retention(void) +IRAM_ATTR void sleep_cpu_retention_execute(void* arg) { + (void) arg; esp_cpu_branch_prediction_disable(); uint8_t core_id = esp_cpu_get_core_id(); - if (core_id == 0) { - WRITE_PERI_REG(HP_SYSTEM_CPU_INT_FROM_CPU_2_REG, 0); - } else { - WRITE_PERI_REG(HP_SYSTEM_CPU_INT_FROM_CPU_3_REG, 0); - } - // Wait another core start to do retention ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-infinite-loop") bool smp_skip_retention = false; @@ -496,19 +491,16 @@ IRAM_ATTR void esp_sleep_cpu_skip_retention(void) { } #endif -void sleep_smp_cpu_sleep_prepare(void) +void sleep_cpu_retention_start(void) { #if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU while (atomic_load(&s_smp_retention_state[!esp_cpu_get_core_id()]) != SMP_IDLE) { ; } - esp_ipc_isr_call((esp_ipc_isr_func_t)smp_core_do_retention, NULL); -#else - esp_ipc_isr_stall_other_cpu(); #endif } -void sleep_smp_cpu_wakeup_prepare(void) +void sleep_cpu_retention_finish(void) { #if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU uint8_t core_id = esp_cpu_get_core_id(); @@ -520,8 +512,6 @@ void sleep_smp_cpu_wakeup_prepare(void) ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-infinite-loop") } atomic_store(&s_smp_retention_state[core_id], SMP_IDLE); -#else - esp_ipc_isr_release_other_cpu(); #endif } #endif //!CONFIG_FREERTOS_UNICORE diff --git a/components/esp_hw_support/sleep_modes.c b/components/esp_hw_support/sleep_modes.c index 583d24b047a..9fb5ff4809a 100644 --- a/components/esp_hw_support/sleep_modes.c +++ b/components/esp_hw_support/sleep_modes.c @@ -351,10 +351,6 @@ static sleep_config_t s_config = { expected when determining wakeup cause. */ static bool s_light_sleep_wakeup = false; -/* Updating RTC_MEMORY_CRC_REG register via set_rtc_memory_crc() - is not thread-safe, so we need to disable interrupts before going to deep sleep. */ -static portMUX_TYPE __attribute__((unused)) spinlock_rtc_deep_sleep = portMUX_INITIALIZER_UNLOCKED; - ESP_LOG_ATTR_TAG(TAG, "sleep"); /* APP core of esp32 can't access to RTC FAST MEMORY, do not define it with RTC_IRAM_ATTR, @@ -525,28 +521,28 @@ esp_err_t esp_deep_sleep_try(uint64_t time_in_us) static esp_err_t s_sleep_hook_register(esp_deep_sleep_cb_t new_cb, esp_deep_sleep_cb_t s_cb_array[MAX_DSLP_HOOKS]) { - esp_os_enter_critical(&spinlock_rtc_deep_sleep); + esp_os_enter_critical(&s_config.lock); for (int n = 0; n < MAX_DSLP_HOOKS; n++) { if (s_cb_array[n]==NULL || s_cb_array[n]==new_cb) { s_cb_array[n]=new_cb; - esp_os_exit_critical(&spinlock_rtc_deep_sleep); + esp_os_exit_critical(&s_config.lock); return ESP_OK; } } - esp_os_exit_critical(&spinlock_rtc_deep_sleep); + esp_os_exit_critical(&s_config.lock); ESP_LOGE(TAG, "Registered deepsleep callbacks exceeds MAX_DSLP_HOOKS"); return ESP_ERR_NO_MEM; } static void s_sleep_hook_deregister(esp_deep_sleep_cb_t old_cb, esp_deep_sleep_cb_t s_cb_array[MAX_DSLP_HOOKS]) { - esp_os_enter_critical(&spinlock_rtc_deep_sleep); + esp_os_enter_critical(&s_config.lock); for (int n = 0; n < MAX_DSLP_HOOKS; n++) { if(s_cb_array[n] == old_cb) { s_cb_array[n] = NULL; } } - esp_os_exit_critical(&spinlock_rtc_deep_sleep); + esp_os_exit_critical(&s_config.lock); } esp_err_t esp_deep_sleep_register_hook(esp_deep_sleep_cb_t new_dslp_cb) @@ -1251,7 +1247,7 @@ static esp_err_t FORCE_IRAM_ATTR deep_sleep_start(bool allow_sleep_rejection) /* Disable interrupts and stall another core in case another task writes * to RTC memory while we calculate RTC memory CRC. */ - esp_os_enter_critical(&spinlock_rtc_deep_sleep); + esp_os_enter_critical(&s_config.lock); esp_ipc_isr_stall_other_cpu(); esp_ipc_isr_stall_pause(); /* Another core is stalled and interrupts are disabled, so we can safely claim the thread-safe @@ -1356,7 +1352,7 @@ static esp_err_t FORCE_IRAM_ATTR deep_sleep_start(bool allow_sleep_rejection) xPortThreadSafeDisclaim(); esp_ipc_isr_stall_resume(); esp_ipc_isr_release_other_cpu(); - esp_os_exit_critical(&spinlock_rtc_deep_sleep); + esp_os_exit_critical(&s_config.lock); return err; } @@ -1428,6 +1424,51 @@ static SLEEP_FN_ATTR esp_err_t esp_light_sleep_inner(uint32_t sleep_flags, uint3 return reject; } +#if !CONFIG_FREERTOS_UNICORE +static SLEEP_FN_ATTR esp_err_t sleep_smp_cpu_sleep_prepare(void) +{ +#if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU && SOC_PM_CPU_RETENTION_BY_SW + sleep_cpu_retention_start(); +#endif +#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX + // The other core will be stalled by high-priority interrupt and spins on variables in internal RAM, + // which naturally avoids cache livelock, so the 20ms livelock workaround timeout is not needed. + esp_int_wdt_livelock_workaround(false); +#endif + esp_err_t ipc_isr_err = esp_ipc_isr_stall_other_cpu_safe(); + if (ipc_isr_err == ESP_OK) { +#if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU && SOC_PM_CPU_RETENTION_BY_SW + // Run CPU retention in the context of the other safely stalled CPU. + esp_ipc_isr_call(sleep_cpu_retention_execute, NULL); +#endif + esp_ipc_isr_stall_pause(); + } else { +#if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU && SOC_PM_CPU_RETENTION_BY_SW + sleep_cpu_retention_finish(); +#endif +#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX + // Configure WDT to use livelock workaround timeout after releasing other CPU + esp_int_wdt_livelock_workaround(true); +#endif + return ESP_ERR_SLEEP_REJECT; + } + return ESP_OK; +} + +static SLEEP_FN_ATTR void sleep_smp_cpu_wakeup_prepare(void) +{ + esp_ipc_isr_stall_resume(); +#if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU && SOC_PM_CPU_RETENTION_BY_SW + sleep_cpu_retention_finish(); +#endif + esp_ipc_isr_release_other_cpu(); +#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX + // Configure WDT to use livelock workaround timeout after releasing other CPU + esp_int_wdt_livelock_workaround(true); +#endif +} +#endif // !CONFIG_FREERTOS_UNICORE + /** * vddsdio is used for power supply of spi flash * @@ -1448,14 +1489,7 @@ esp_err_t esp_light_sleep_start(void) { s_config.ccount_ticks_record = esp_cpu_get_cycle_count(); esp_sleep_execute_event_callbacks(SLEEP_EVENT_SW_GOTO_SLEEP, (void *)0); -#if CONFIG_ESP_TASK_WDT_USE_ESP_TIMER - esp_err_t timerret = ESP_OK; - /* If a task watchdog timer is running, we have to stop it. */ - timerret = esp_task_wdt_stop(); -#endif // CONFIG_ESP_TASK_WDT_USE_ESP_TIMER - - esp_timer_private_lock(); esp_os_enter_critical(&s_config.lock); s_config.rtc_ticks_at_sleep_start = rtc_time_get(); uint32_t ccount_at_sleep_start = esp_cpu_get_cycle_count(); @@ -1470,23 +1504,22 @@ esp_err_t esp_light_sleep_start(void) #endif #if !CONFIG_FREERTOS_UNICORE -#if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU && SOC_PM_CPU_RETENTION_BY_SW - sleep_smp_cpu_sleep_prepare(); -#else -#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX - // The other core will be stalled by high-priority interrupt and spins on variables in internal RAM, - // which naturally avoids cache livelock, so the 20ms livelock workaround timeout is not needed. - esp_int_wdt_livelock_workaround(false); -#endif - esp_ipc_isr_stall_other_cpu(); -#endif - esp_ipc_isr_stall_pause(); + if (sleep_smp_cpu_sleep_prepare() != ESP_OK) { + esp_os_exit_critical(&s_config.lock); + return ESP_ERR_SLEEP_REJECT; + } #endif + /* Another core is stalled and interrupts are disabled, so we can safely claim the thread-safe critical section to avoid deadlocks and fastup the sleep process. */ xPortThreadSafeClaim(); +#if CONFIG_ESP_TASK_WDT_USE_ESP_TIMER + /* If a task watchdog timer is running, we have to stop it. */ + esp_err_t timerret = esp_task_wdt_stop(); +#endif // CONFIG_ESP_TASK_WDT_USE_ESP_TIMER + #if CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION && CONFIG_PM_SLP_IRAM_OPT /* Cache Suspend 0: if CONFIG_PM_SLP_IRAM_OPT is enabled, suspend cache here so that the access to flash * during the sleep process can be explicitly exposed. @@ -1704,18 +1737,8 @@ esp_err_t esp_light_sleep_start(void) /* Restore port critical before unstalling other CPU */ xPortThreadSafeDisclaim(); - esp_timer_private_unlock(); #if !CONFIG_FREERTOS_UNICORE - esp_ipc_isr_stall_resume(); -#if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU && SOC_PM_CPU_RETENTION_BY_SW sleep_smp_cpu_wakeup_prepare(); -#else - esp_ipc_isr_release_other_cpu(); -#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX - // Configure WDT to use livelock workaround timeout after releasing other CPU - esp_int_wdt_livelock_workaround(true); -#endif -#endif #endif #if SOC_RTC_WDT_SUPPORTED