From 15e4028e2d99b7bda88acdc582055a2368b45163 Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Sat, 9 May 2026 09:58:26 +0800 Subject: [PATCH 1/3] fix(esp_hw_support): fix psram data corrupt after lightsleep if halfsleep is enabled --- components/esp_hw_support/sleep_modes.c | 29 +++++++++++++------------ 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/components/esp_hw_support/sleep_modes.c b/components/esp_hw_support/sleep_modes.c index 5781683bfeb..47c1d7bf39e 100644 --- a/components/esp_hw_support/sleep_modes.c +++ b/components/esp_hw_support/sleep_modes.c @@ -992,14 +992,15 @@ static esp_err_t FORCE_IRAM_ATTR esp_sleep_start_safe(uint32_t sleep_flags, uint spi_flash_enable_deep_power_down_mode(false); } #endif - /* Cache Resume 1: Resume cache for continue running*/ - sleep_cache_resume(); -#if CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED && CONFIG_SPIRAM_XIP_FROM_PSRAM - // Code outside of esp_sleep_start_safe may be linked to FLASH, and if CONFIG_SPIRAM_XIP_FROM_PSRAM - // is enabled, code in Flash will be copied to PSRAM for execution. We need to wait here until - // PSRAM exits half-sleep before returning. +#if CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED && (CONFIG_SPIRAM_XIP_FROM_PSRAM || !CONFIG_PM_SLP_IRAM_OPT) + // Code outside of esp_sleep_start_safe may be linked to FLASH if CONFIG_PM_SLP_IRAM_OPT is false, + // Code that accesses flash memory may cause cached PSRAM data to be replaced back into PSRAM before + // it has fully resumed. And if CONFIG_SPIRAM_XIP_FROM_PSRAM is enabled, code in Flash will be copied + // to PSRAM for execution. We need to wait here until PSRAM exits half-sleep before returning. esp_psram_impl_resume_from_halfsleep_mode(s_config.rtc_clk_cal_period); #endif + /* Cache Resume 1: Resume cache for continue running*/ + sleep_cache_resume(); } return result; } @@ -1694,6 +1695,14 @@ esp_err_t esp_light_sleep_start(void) esp_clk_private_unlock(); esp_timer_private_unlock(); +#if CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED && !CONFIG_SPIRAM_XIP_FROM_PSRAM && CONFIG_PM_SLP_IRAM_OPT + // If CONFIG_SPIRAM_XIP_FROM_PSRAM is not enabled and CONFIG_PM_SLP_IRAM_OPT is enable, + // the sleep-wake process prior to this point does not access the PSRAM, so we can postpone waiting + // for the PSRAM to resume until here, in order to reuse the time overhead + // of the wake-up process as much as possible. + esp_psram_impl_resume_from_halfsleep_mode(s_config.rtc_clk_cal_period); +#endif + #if CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION && CONFIG_PM_SLP_IRAM_OPT /* Cache Resume 0: sleep process done, resume cache for continue running */ if (!(s_config.wakeup_triggers & ignore_check_wakeup_triggers)) { @@ -1701,14 +1710,6 @@ esp_err_t esp_light_sleep_start(void) } #endif -#if CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED && !CONFIG_SPIRAM_XIP_FROM_PSRAM - // If CONFIG_SPIRAM_XIP_FROM_PSRAM is not enabled, the sleep-wake process - // prior to this point does not access the PSRAM, so we can postpone waiting - // for the PSRAM to resume until here, in order to reuse the time overhead - // of the wake-up process as much as possible. - esp_psram_impl_resume_from_halfsleep_mode(s_config.rtc_clk_cal_period); -#endif - #if !CONFIG_FREERTOS_UNICORE esp_ipc_isr_stall_resume(); #if CONFIG_PM_ESP_SLEEP_POWER_DOWN_CPU && SOC_PM_CPU_RETENTION_BY_SW From 858ecf6f70f7b19a0b25ab9b88dc07c8e1a7ae38 Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Thu, 7 May 2026 21:00:39 +0800 Subject: [PATCH 2/3] feat(esp_hw_support): check psram data is not corrupted after lightsleep --- .../test_apps/psram/main/test_psram.c | 32 +++++++++++++++---- .../esp_system_unity_tests/main/test_sleep.c | 18 +++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/components/esp_psram/test_apps/psram/main/test_psram.c b/components/esp_psram/test_apps/psram/main/test_psram.c index da91ced5a9a..a40a9ab94e5 100644 --- a/components/esp_psram/test_apps/psram/main/test_psram.c +++ b/components/esp_psram/test_apps/psram/main/test_psram.c @@ -13,7 +13,9 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "unity.h" +#include "esp_cpu.h" #include "esp_heap_caps.h" +#include "esp_ipc_isr.h" #include "esp_private/esp_psram_io.h" #include "esp_psram.h" #include "esp_private/esp_psram_extram.h" @@ -27,14 +29,14 @@ __attribute__((unused)) const static char *TAG = "PSRAM"; static void s_test_psram_heap_allocable(void) { size_t largest_size = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM); - ESP_LOGI(TAG, "largest size is %zu", largest_size); + ESP_EARLY_LOGI(TAG, "largest size is 0x%08x", largest_size); uint32_t *ext_buffer = (uint32_t *)heap_caps_calloc(largest_size, 1, MALLOC_CAP_SPIRAM); TEST_ASSERT(ext_buffer); intptr_t start = (intptr_t)ext_buffer; intptr_t end = (intptr_t)ext_buffer + largest_size; - ESP_LOGI(TAG, "test ext buffer start addr is 0x%"PRIxPTR", end addr is 0x%"PRIxPTR, start, end); + ESP_EARLY_LOGI(TAG, "test ext buffer start addr is %p, end addr is %p", start, end); TEST_ASSERT(esp_psram_check_ptr_addr((void *)start) && esp_psram_check_ptr_addr((void *)end)); for (int i = 0; i < largest_size / sizeof(uint32_t); i++) { @@ -61,16 +63,32 @@ TEST_CASE("stress test psram heap allocable", "[psram][manual][ignore]") } #if !CONFIG_SPIRAM_XIP_FROM_PSRAM + +IRAM_ATTR static void s_psram_halfsleep_test(uint32_t rtc_slow_clk_period) +{ + // Stall another core and disable branch predictor to prevent it from accessing PSRAM + esp_ipc_isr_stall_other_cpu(); +#if SOC_BRANCH_PREDICTOR_SUPPORTED + esp_cpu_branch_prediction_disable(); +#endif + esp_psram_impl_enter_halfsleep_mode(); + esp_rom_delay_us(1000); + esp_psram_impl_exit_halfsleep_mode(); + esp_psram_impl_resume_from_halfsleep_mode(rtc_slow_clk_period); +#if SOC_BRANCH_PREDICTOR_SUPPORTED + esp_cpu_branch_prediction_enable(); +#endif + esp_ipc_isr_release_other_cpu(); + + s_test_psram_heap_allocable(); +} + TEST_CASE("test psram halfsleep mode (if applicable)", "[psram]") { uint32_t rtc_slow_clk_period = rtc_clk_cal(CLK_CAL_RTC_SLOW, CONFIG_RTC_CLK_CAL_CYCLES); s_test_psram_heap_allocable(); for (int i = 0; i < 3; i++) { - esp_psram_impl_enter_halfsleep_mode(); - esp_rom_delay_us(1000); - esp_psram_impl_exit_halfsleep_mode(); - esp_psram_impl_resume_from_halfsleep_mode(rtc_slow_clk_period); - s_test_psram_heap_allocable(); + s_psram_halfsleep_test(rtc_slow_clk_period); } } #endif diff --git a/components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep.c b/components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep.c index 2f6f74e1cef..1232191e25f 100644 --- a/components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep.c +++ b/components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep.c @@ -320,6 +320,24 @@ static void test_psram_accessible_after_lightsleep(void) TEST_ESP_OK(sleep_cpu_configure(true)); #endif + // Verify PSRAM was not corrupted after light sleep + uint8_t *psram_data = (uint8_t *)heap_caps_malloc(1024 * 1024, MALLOC_CAP_SPIRAM); + uint8_t test_pattern = 0x5a; + for (int i = 0; i < 5; i++) { + memset(psram_data, test_pattern, 1024 * 1024); + esp_sleep_enable_timer_wakeup(100 * 1000); + esp_light_sleep_start(); + for (int j = 0; j < (1024 * 1024); j++) { + TEST_ASSERT_EQUAL(test_pattern, psram_data[j]); + } + test_pattern = ~test_pattern; +#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP + TEST_ASSERT_EQUAL(PMU_SLEEP_PD_TOP, sleep_ctx.sleep_flags & PMU_SLEEP_PD_TOP); +#endif + } + free(psram_data); + + // Verify that all addresses in PSRAM are accessible after light sleep esp_sleep_enable_timer_wakeup(100 * 1000); esp_light_sleep_start(); TEST_ASSERT_EQUAL(0, sleep_ctx.sleep_request_result); From ecfdeef9d7e309fa94b7f1df9a9de792da9f38eb Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Mon, 11 May 2026 11:29:11 +0800 Subject: [PATCH 3/3] feat(esp_psram): wakeup PSRAM by CE# force control instead of dummy write --- .../esp32p4/include/hal/psram_ctrlr_ll.h | 32 +++++++++++++++++++ .../esp32s31/include/hal/psram_ctrlr_ll.h | 22 +++++++++++++ .../esp_psram/device/esp_psram_impl_ap_hex.c | 6 +++- .../esp_psram/device/esp_psram_impl_ap_oct.c | 11 ++----- 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/components/esp_hal_mspi/esp32p4/include/hal/psram_ctrlr_ll.h b/components/esp_hal_mspi/esp32p4/include/hal/psram_ctrlr_ll.h index af138f4a06a..d59c3faffef 100644 --- a/components/esp_hal_mspi/esp32p4/include/hal/psram_ctrlr_ll.h +++ b/components/esp_hal_mspi/esp32p4/include/hal/psram_ctrlr_ll.h @@ -26,6 +26,7 @@ #include "soc/clk_tree_defs.h" #include "soc/hp_system_struct.h" #include "rom/opi_flash.h" +#include "esp_rom_sys.h" #ifdef __cplusplus extern "C" { @@ -60,6 +61,14 @@ extern "C" { #define PSRAM_CTRLR_LL_INTR_EVENT_SUPPORTED 1 +/** + * ESP32-P4 MSPI revision < 3.0 lacks SPIMEM CS force-control (cs_keep_active / + * cs0_dis / cs1_dis) needed to assert CE# and wake PSRAM from half-sleep. + * The device driver must issue a dummy MSPI write instead. + * Revision >= 3.0 can use psram_ctrlr_ll_half_sleep_wakeup(). + */ +#define PSRAM_CTRLR_LL_MSPI_WAKEUP_WORKAROUND (HAL_CONFIG(CHIP_SUPPORT_MIN_REV) < 300) + /** * @brief Set PSRAM write cmd * @@ -946,6 +955,29 @@ static inline void psram_ctrlr_ll_enable_core_err_resp(void) HP_SYSTEM.core_err_resp_dis.val = 0x0; } +#if !PSRAM_CTRLR_LL_MSPI_WAKEUP_WORKAROUND +/** + * @brief Wake PSRAM from half-sleep via MSPI CS controls (P4 MSPI rev >= 3.0). + * + * MSPI2: mem_cs_oe_ctrl drives CS. MSPI3: cs_keep_active + cs0/cs1_dis for CE#. + * Restore MSPI2/MSPI3 cs settings when done. + */ +__attribute__((always_inline)) +static inline void psram_ctrlr_ll_half_sleep_wakeup(void) +{ + bool old_oe_ctrl = SPIMEM2.mem_misc.mem_cs_oe_ctrl; + SPIMEM2.mem_misc.mem_cs_oe_ctrl = 1; + SPIMEM3.misc.cs_keep_active = 1; + SPIMEM3.misc.cs0_dis = 1; + SPIMEM3.misc.cs1_dis = 0; + esp_rom_delay_us(3); + SPIMEM3.misc.cs1_dis = 1; + SPIMEM3.misc.cs0_dis = 0; + SPIMEM3.misc.cs_keep_active = 0; + SPIMEM2.mem_misc.mem_cs_oe_ctrl = old_oe_ctrl; +} +#endif + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_mspi/esp32s31/include/hal/psram_ctrlr_ll.h b/components/esp_hal_mspi/esp32s31/include/hal/psram_ctrlr_ll.h index c963e228bbd..f18fc2755cb 100644 --- a/components/esp_hal_mspi/esp32s31/include/hal/psram_ctrlr_ll.h +++ b/components/esp_hal_mspi/esp32s31/include/hal/psram_ctrlr_ll.h @@ -27,6 +27,7 @@ #include "soc/clk_tree_defs.h" #include "soc/hp_system_struct.h" #include "rom/opi_flash.h" +#include "esp_rom_sys.h" #ifdef __cplusplus extern "C" { @@ -958,6 +959,27 @@ static inline void psram_ctrlr_ll_enable_core_err_resp(void) HP_SYSTEM.core_err_resp_dis.val = 0x0; } +/** + * @brief Wake PSRAM from half-sleep via MSPI CS controls. + * + * MSPI2: mem_cs_oe_ctrl drives CS. MSPI3: cs_keep_active + cs0/cs1_dis for CE#. + * Restore MSPI2/MSPI3 cs settings when done. + */ +__attribute__((always_inline)) +static inline void psram_ctrlr_ll_half_sleep_wakeup(void) +{ + bool old_oe_ctrl = SPIMEM2.mem_misc.mem_cs_oe_ctrl; + SPIMEM2.mem_misc.mem_cs_oe_ctrl = 1; + SPIMEM3.misc.cs_keep_active = 1; + SPIMEM3.misc.cs0_dis = 1; + SPIMEM3.misc.cs1_dis = 0; + esp_rom_delay_us(3); + SPIMEM3.misc.cs1_dis = 1; + SPIMEM3.misc.cs0_dis = 0; + SPIMEM3.misc.cs_keep_active = 0; + SPIMEM2.mem_misc.mem_cs_oe_ctrl = old_oe_ctrl; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_psram/device/esp_psram_impl_ap_hex.c b/components/esp_psram/device/esp_psram_impl_ap_hex.c index daed4151e26..ccc4cfbd921 100644 --- a/components/esp_psram/device/esp_psram_impl_ap_hex.c +++ b/components/esp_psram/device/esp_psram_impl_ap_hex.c @@ -568,7 +568,7 @@ PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_exit_halfsleep_mode(void) // Record the tick exiting halfsleep mode s_halfsleep_ctx.halfsleep_wakeup_tick = rtc_time_get(); - // Do a SPI dummy write transmission to invalid address to wake up from halfsleep mode +#if PSRAM_CTRLR_LL_MSPI_WAKEUP_WORKAROUND uint8_t null = 0; psram_ctrlr_ll_common_transaction(PSRAM_CTRLR_LL_MSPI_ID_3, AP_HEX_PSRAM_REG_WRITE, AP_HEX_PSRAM_WR_CMD_BITLEN, @@ -577,6 +577,10 @@ PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_exit_halfsleep_mode(void) &null, 0, NULL, 0, false); +#else + // Set CE# to active to wakeup PSRAM halfsleep + psram_ctrlr_ll_half_sleep_wakeup(); +#endif } PSRAM_HALFSLEEP_RESUME_CODE_ATTR void esp_psram_impl_resume_from_halfsleep_mode(uint32_t slowclk_period) diff --git a/components/esp_psram/device/esp_psram_impl_ap_oct.c b/components/esp_psram/device/esp_psram_impl_ap_oct.c index 8d70ece8676..485faf63096 100644 --- a/components/esp_psram/device/esp_psram_impl_ap_oct.c +++ b/components/esp_psram/device/esp_psram_impl_ap_oct.c @@ -565,15 +565,8 @@ void esp_psram_impl_exit_halfsleep_mode(void) // Record the tick exiting halfsleep mode s_halfsleep_ctx.halfsleep_wakeup_tick = rtc_time_get(); - // Do a SPI dummy write transmission to invalid address to wake up from halfsleep mode - uint8_t null = 0; - psram_ctrlr_ll_common_transaction(PSRAM_CTRLR_LL_MSPI_ID_3, - AP_OCT_PSRAM_REG_WRITE, AP_OCT_PSRAM_WR_CMD_BITLEN, - 0xFF, AP_OCT_PSRAM_ADDR_BITLEN, - 0, - &null, 0, - NULL, 0, - false); + // Set CE# to active to wakeup PSRAM halfsleep + psram_ctrlr_ll_half_sleep_wakeup(); } void esp_psram_impl_resume_from_halfsleep_mode(uint32_t slowclk_period)