From ecab1fec1957f5967c8d3cbb3ae90901989687fe Mon Sep 17 00:00:00 2001 From: Xiao Xufeng Date: Fri, 8 May 2026 01:58:01 +0800 Subject: [PATCH] fix(mspi): fixed possible boot failure in some builds when psram is enabled A typical scenario is: when XIP on PSRAM enabled, compiler optimization level is Os. Under certain binary layout, boot hangs and backtrace points to `esp_sleep_config_gpio_isolate`. The root cause is that, during PSRAM initialization, it calls esp_gpio_reserve, which happens to place before the reported function. However, after call, there is no barrier before the clock adjustment in `mspi_timing_enter_low_speed_mode`. The clock gets changed when the cache is still fetching data, resulting in the corrupted data in the end of the cache line. This commits add spi_flash_disable_cache as a barrier to make sure the cache transactions is finished before the clock switch. --- .../include/esp_private/mspi_timing_tuning.h | 15 +++++++++ .../esp_hw_support/mspi_timing_tuning.c | 32 +++++++++++++++---- .../esp_psram/esp32s3/esp_psram_impl_octal.c | 6 ++-- .../esp_psram/esp32s3/esp_psram_impl_quad.c | 6 ++-- .../include/esp_private/cache_utils.h | 16 ++++++++++ 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/components/esp_hw_support/include/esp_private/mspi_timing_tuning.h b/components/esp_hw_support/include/esp_private/mspi_timing_tuning.h index c1e999cc181..9c9bcf8b3b4 100644 --- a/components/esp_hw_support/include/esp_private/mspi_timing_tuning.h +++ b/components/esp_hw_support/include/esp_private/mspi_timing_tuning.h @@ -30,6 +30,21 @@ void mspi_timing_enter_low_speed_mode(bool control_spi1); */ void mspi_timing_enter_high_speed_mode(bool control_spi1); +/** + * @brief Switch MSPI to low speed while suspending external memory cache to avoid in-flight cache line fills across the + * clock change. + * + * @note Early init only. Not safe for general runtime use: does not coordinate with other cores or freeze cache. + */ +void mspi_timing_enter_low_speed_early(void); + +/** + * @brief Switch MSPI to high speed while suspending external memory cache. + * + * @note Same usage constraints as @ref mspi_timing_enter_low_speed_early. + */ +void mspi_timing_enter_high_speed_early(void); + /** * @brief Switch MSPI into low speed mode / high speed mode. * @note This API is cache safe, it will freeze both D$ and I$ and restore them after MSPI is switched diff --git a/components/esp_hw_support/mspi_timing_tuning.c b/components/esp_hw_support/mspi_timing_tuning.c index 117d1e91d04..7822c8fa6f9 100644 --- a/components/esp_hw_support/mspi_timing_tuning.c +++ b/components/esp_hw_support/mspi_timing_tuning.c @@ -16,6 +16,7 @@ #include "hal/spi_flash_hal.h" #include "hal/cache_hal.h" #include "hal/cache_ll.h" +#include "esp_private/cache_utils.h" #include "esp_private/mspi_timing_tuning.h" #include "mspi_timing_config.h" #include "mspi_timing_by_mspi_delay.h" @@ -320,9 +321,9 @@ void mspi_timing_flash_tuning(void) { /** * set MSPI related regs to 20mhz configuration, to get reference data from FLASH - * see detailed comments in this function (`mspi_timing_enter_low_speed_mode`) + * see detailed comments in this function (`mspi_timing_enter_low_speed_early`) */ - mspi_timing_enter_low_speed_mode(true); + mspi_timing_enter_low_speed_early(); #if SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY mspi_tuning_cfg_drv_t drv = { @@ -349,7 +350,7 @@ void mspi_timing_flash_tuning(void) s_do_tuning(reference_data, &timing_configs, true); - mspi_timing_enter_high_speed_mode(true); + mspi_timing_enter_high_speed_early(); } #else void mspi_timing_flash_tuning(void) @@ -367,9 +368,9 @@ void mspi_timing_psram_tuning(void) { /** * set MSPI related regs to 20mhz configuration, to write reference data to PSRAM - * see detailed comments in this function (`mspi_timing_enter_low_speed_mode`) + * see detailed comments in this function (`mspi_timing_enter_low_speed_early`) */ - mspi_timing_enter_low_speed_mode(true); + mspi_timing_enter_low_speed_early(); #if SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY // write data into psram, used to do timing tuning test. @@ -399,7 +400,7 @@ void mspi_timing_psram_tuning(void) //Get required config, and set them to PSRAM related registers s_do_tuning(reference_data, &timing_configs, false); - mspi_timing_enter_high_speed_mode(true); + mspi_timing_enter_high_speed_early(); } #else @@ -499,6 +500,25 @@ void mspi_timing_change_speed_mode_cache_safe(bool switch_down) #endif //#if SOC_CACHE_FREEZE_SUPPORTED } +/*------------------------------------------------------------------------------ + * Early-init MSPI speed switch (see mspi_timing_tuning.h) + *----------------------------------------------------------------------------*/ +void mspi_timing_enter_low_speed_early(void) +{ + uint32_t cache_state = 0; + spi_flash_disable_cache(0, &cache_state); + mspi_timing_enter_low_speed_mode(true); + spi_flash_restore_cache(0, cache_state); +} + +void mspi_timing_enter_high_speed_early(void) +{ + uint32_t cache_state = 0; + spi_flash_disable_cache(0, &cache_state); + mspi_timing_enter_high_speed_mode(true); + spi_flash_restore_cache(0, cache_state); +} + /*------------------------------------------------------------------------------ * APIs to inform SPI1 Flash driver of necessary timing configurations *----------------------------------------------------------------------------*/ diff --git a/components/esp_psram/esp32s3/esp_psram_impl_octal.c b/components/esp_psram/esp32s3/esp_psram_impl_octal.c index 7749ef39ab4..5c2ec7dcc86 100644 --- a/components/esp_psram/esp32s3/esp_psram_impl_octal.c +++ b/components/esp_psram/esp32s3/esp_psram_impl_octal.c @@ -341,8 +341,8 @@ esp_err_t esp_psram_impl_enable(void) s_set_psram_cs_timing(); s_configure_psram_ecc(); - //enter MSPI slow mode to init PSRAM device registers - mspi_timing_enter_low_speed_mode(true); + //enter MSPI slow mode to init PSRAM device registers (early init: see mspi_timing_enter_low_speed_early) + mspi_timing_enter_low_speed_early(); //set to variable dummy mode SET_PERI_REG_MASK(SPI_MEM_DDR_REG(1), SPI_MEM_SPI_FMEM_VAR_DUMMY); @@ -377,7 +377,7 @@ esp_err_t esp_psram_impl_enable(void) //Do PSRAM timing tuning, we use SPI1 to do the tuning, and set the SPI0 PSRAM timing related registers accordingly mspi_timing_psram_tuning(); //Back to the high speed mode. Flash/PSRAM clocks are set to the clock that user selected. SPI0/1 registers are all set correctly - mspi_timing_enter_high_speed_mode(true); + mspi_timing_enter_high_speed_early(); /** * Tuning may change SPI1 regs, whereas legacy spi_flash APIs rely on these regs. diff --git a/components/esp_psram/esp32s3/esp_psram_impl_quad.c b/components/esp_psram/esp32s3/esp_psram_impl_quad.c index 6e78ed00281..d9b7f9ad69f 100644 --- a/components/esp_psram/esp32s3/esp_psram_impl_quad.c +++ b/components/esp_psram/esp32s3/esp_psram_impl_quad.c @@ -327,8 +327,8 @@ esp_err_t esp_psram_impl_enable(void) //psram init psram_gpio_config(); psram_set_cs_timing(); - //enter MSPI slow mode to init PSRAM device registers - mspi_timing_enter_low_speed_mode(true); + //enter MSPI slow mode to init PSRAM device registers (early init: see mspi_timing_enter_low_speed_early) + mspi_timing_enter_low_speed_early(); //We use SPI1 to init PSRAM psram_disable_qio_mode(SPI1_NUM); @@ -369,7 +369,7 @@ esp_err_t esp_psram_impl_enable(void) //psram init //Configure SPI0 PSRAM related SPI Phases config_psram_spi_phases(); //Back to the high speed mode. Flash/PSRAM clocks are set to the clock that user selected. SPI0/1 registers are all set correctly - mspi_timing_enter_high_speed_mode(true); + mspi_timing_enter_high_speed_early(); return ESP_OK; } diff --git a/components/spi_flash/include/esp_private/cache_utils.h b/components/spi_flash/include/esp_private/cache_utils.h index ddb0a9259ca..8b764312777 100644 --- a/components/spi_flash/include/esp_private/cache_utils.h +++ b/components/spi_flash/include/esp_private/cache_utils.h @@ -81,6 +81,22 @@ bool spi_flash_cache_enabled(void); */ void spi_flash_enable_cache(uint32_t cpuid); +/** + * @brief Suspend the Cache access to external memory, will disable branch predictor if supported. + * + * @param cpuid the core number to enable the cache for, meaning less on shared cache. + * @param saved_state Cache status hold by hal (Used only on ROM impl. in idf, this param unused) + */ +void spi_flash_disable_cache(uint32_t cpuid, uint32_t *saved_state); + +/** + * @brief Resume the Cache access to external memory, will enable branch predictor if supported. + * + * @param cpuid the core number to enable the cache for, meaning less on shared cache. + * @param saved_state Cache status hold by hal (Used only on ROM impl. in idf, this param unused) + */ +void spi_flash_restore_cache(uint32_t cpuid, uint32_t saved_state); + #ifdef __cplusplus } #endif