diff --git a/components/esp_hw_support/clk_utils.c b/components/esp_hw_support/clk_utils.c index 4415d41754..99eabf816e 100644 --- a/components/esp_hw_support/clk_utils.c +++ b/components/esp_hw_support/clk_utils.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "sdkconfig.h" #include "esp_check.h" #include "esp_log.h" @@ -17,25 +18,31 @@ #include "esp_private/mspi_timing_tuning.h" #include "esp_private/esp_clk_utils.h" +// Not directly divide to avoid truncation issue +// DIG-498 +#if CONFIG_IDF_TARGET_ESP32P4 +#define BELOW_FREQ_THRESHOLD(freq) ((freq) < CONFIG_SPIRAM_SPEED) +#elif CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61 || CONFIG_IDF_TARGET_ESP32H4 +#define BELOW_FREQ_THRESHOLD(freq) ((freq) * 8 < CONFIG_SPIRAM_SPEED) +#endif + #if !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP void esp_clk_utils_mspi_speed_mode_sync_before_cpu_freq_switching(uint32_t target_cpu_src_freq, uint32_t target_cpu_freq) { #if MSPI_TIMING_LL_FLASH_CPU_CLK_SRC_BINDED (void) target_cpu_freq; + /* For ESP32S3, the clock source of MSPI is same as the CPU. When CPU use XTAL as clock source, we need to sync the + * MSPI speed mode. */ if (target_cpu_src_freq <= clk_ll_xtal_load_freq_mhz()) { mspi_timing_change_speed_mode_cache_safe(true); } -#elif CONFIG_IDF_TARGET_ESP32P4 - (void) target_cpu_src_freq; - /** - * Workaround for ESP32P4, - * f_cpu >= f_mspi +#elif SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED && CONFIG_SPIRAM + /* On chips with AXI bus, currently there is a restriction that AXI frequency (usually equals to a portion of CPU + * frequency) needs to be greater than or equal to MSPI PSRAM frequency to avoid writing MSPI FIFO overflow. */ - if (((target_cpu_freq) < CONFIG_ESPTOOLPY_FLASHFREQ_VAL) -#if CONFIG_SPIRAM - || ((target_cpu_freq) < CONFIG_SPIRAM_SPEED) -#endif - ) { + if (BELOW_FREQ_THRESHOLD(target_cpu_freq)) { + // Before switching to low speed mode, verify CPU frequency meets the constraint + assert(target_cpu_freq >= mspi_timing_get_psram_low_speed_freq_mhz()); mspi_timing_change_speed_mode_cache_safe(true); } #else @@ -51,17 +58,11 @@ void esp_clk_utils_mspi_speed_mode_sync_after_cpu_freq_switching(uint32_t target if (target_cpu_src_freq > clk_ll_xtal_load_freq_mhz()) { mspi_timing_change_speed_mode_cache_safe(false); } -#elif CONFIG_IDF_TARGET_ESP32P4 - (void) target_cpu_src_freq; - /** - * Workaround for ESP32P4, - * f_cpu >= f_mspi +#elif SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED && CONFIG_SPIRAM + /* On chips with AXI bus, currently there is a restriction that AXI frequency (usually equals to a portion of CPU + * frequency) needs to be greater than or equal to MSPI PSRAM frequency to avoid writing MSPI FIFO overflow. */ - if (((target_cpu_freq) >= CONFIG_ESPTOOLPY_FLASHFREQ_VAL) -#if CONFIG_SPIRAM - && ((target_cpu_freq) >= CONFIG_SPIRAM_SPEED) -#endif - ) { + if (!BELOW_FREQ_THRESHOLD(target_cpu_freq)) { mspi_timing_change_speed_mode_cache_safe(false); } #else diff --git a/components/esp_hw_support/mspi_timing_tuning/include/esp_private/mspi_timing_tuning.h b/components/esp_hw_support/mspi_timing_tuning/include/esp_private/mspi_timing_tuning.h index c1e999cc18..be59ef7dfb 100644 --- a/components/esp_hw_support/mspi_timing_tuning/include/esp_private/mspi_timing_tuning.h +++ b/components/esp_hw_support/mspi_timing_tuning/include/esp_private/mspi_timing_tuning.h @@ -24,6 +24,12 @@ extern "C" { */ void mspi_timing_enter_low_speed_mode(bool control_spi1); +/** + * @brief Get PSRAM frequency in low speed mode (MHz) + * @return PSRAM frequency in MHz when in low speed mode + */ +uint32_t mspi_timing_get_psram_low_speed_freq_mhz(void); + /** * @brief Make MSPI work under the frequency as users set, may add certain delays to MSPI RX direction to meet timing requirements. * @param control_spi1 Select whether to control SPI1. For tuning, we need to use SPI1. After tuning (during startup stage), let the flash driver to control SPI1 @@ -54,7 +60,6 @@ void mspi_timing_psram_tuning(void); */ void mspi_timing_set_pin_drive_strength(void); - #ifdef __cplusplus } #endif diff --git a/components/esp_hw_support/mspi_timing_tuning/mspi_timing_tuning.c b/components/esp_hw_support/mspi_timing_tuning/mspi_timing_tuning.c index 97517c565e..e5451d5216 100644 --- a/components/esp_hw_support/mspi_timing_tuning/mspi_timing_tuning.c +++ b/components/esp_hw_support/mspi_timing_tuning/mspi_timing_tuning.c @@ -481,6 +481,11 @@ void mspi_timing_psram_tuning(void) /*------------------------------------------------------------------------------ * APIs to make SPI0 (and SPI1) FLASH work for high/low freq *----------------------------------------------------------------------------*/ +uint32_t mspi_timing_get_psram_low_speed_freq_mhz(void) +{ + return 20; +} + void mspi_timing_enter_low_speed_mode(bool control_spi1) { #if MSPI_TIMING_LL_FLASH_CLK_SRC_CHANGEABLE @@ -500,14 +505,14 @@ void mspi_timing_enter_low_speed_mode(bool control_spi1) * Should be extended to other no-timing-tuning chips if needed. e.g.: * we still need to turn down Flash / PSRAM clock speed at a certain period of time */ - mspi_timing_config_set_flash_clock(20, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1); - mspi_timing_config_set_psram_clock(20, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1); -#endif //#if SOC_SPI_MEM_SUPPORT_TIMING_TUNING - + uint32_t low_speed_freq_mhz = mspi_timing_get_psram_low_speed_freq_mhz(); + mspi_timing_config_set_flash_clock(low_speed_freq_mhz, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1); + mspi_timing_config_set_psram_clock(low_speed_freq_mhz, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1); #if MSPI_TIMING_FLASH_NEEDS_TUNING || MSPI_TIMING_PSRAM_NEEDS_TUNING mspi_timing_flash_config_clear_tuning_regs(control_spi1); mspi_timing_psram_config_clear_tuning_regs(control_spi1); #endif //#if MSPI_TIMING_FLASH_NEEDS_TUNING || MSPI_TIMING_PSRAM_NEEDS_TUNING +#endif //#if SOC_SPI_MEM_SUPPORT_TIMING_TUNING } /** @@ -568,10 +573,24 @@ void mspi_timing_change_speed_mode_cache_safe(bool switch_down) if (switch_down) { //enter MSPI low speed mode, extra delays should be removed +#if CONFIG_IDF_TARGET_ESP32C5 + // ESP32-C5 needs to perform encrypted flash writes even when CPU frequency is reduced. + // Since encrypted writes use SPI1, we need to configure SPI1 timing registers as well + // during runtime frequency switching to ensure proper operation. + mspi_timing_enter_low_speed_mode(true); +#else mspi_timing_enter_low_speed_mode(false); +#endif } else { //enter MSPI high speed mode, extra delays should be considered +#if CONFIG_IDF_TARGET_ESP32C5 + // ESP32-C5 needs to perform encrypted flash writes even when CPU frequency is reduced. + // Since encrypted writes use SPI1, we need to configure SPI1 timing registers as well + // during runtime frequency switching to ensure proper operation. + mspi_timing_enter_high_speed_mode(true); +#else mspi_timing_enter_high_speed_mode(false); +#endif } #if SOC_CACHE_FREEZE_SUPPORTED diff --git a/components/esp_pm/CMakeLists.txt b/components/esp_pm/CMakeLists.txt index 27ce9ada3c..e540b64b32 100644 --- a/components/esp_pm/CMakeLists.txt +++ b/components/esp_pm/CMakeLists.txt @@ -1,10 +1,21 @@ idf_build_get_property(target IDF_TARGET) +set(priv_requires esp_system esp_driver_gpio esp_timer) if(${target} STREQUAL "linux") return() # This component is not supported by the POSIX/Linux simulator endif() -idf_component_register(SRCS "pm_locks.c" "pm_trace.c" "pm_impl.c" +set(srcs "pm_locks.c" "pm_trace.c" "pm_impl.c") +if(CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED) + list(APPEND srcs "pm_c5_flash_freq_limit.c") +endif() + +if(${target} STREQUAL "esp32c5") + # pm_c5_flash_freq_limit.c needs spi_flash header when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled + list(APPEND priv_requires spi_flash) +endif() + +idf_component_register(SRCS ${srcs} INCLUDE_DIRS include - PRIV_REQUIRES esp_system esp_driver_gpio esp_timer + PRIV_REQUIRES "${priv_requires}" LDFRAGMENTS linker.lf) diff --git a/components/esp_pm/Kconfig b/components/esp_pm/Kconfig index 71573bbb14..2c772640e8 100644 --- a/components/esp_pm/Kconfig +++ b/components/esp_pm/Kconfig @@ -239,4 +239,12 @@ menu "Power Management" NOTE: Enabling these callbacks may change sleep duration calculations based on time spent in callback and hence it is highly recommended to keep them as short as possible + config PM_WORKAROUND_FREQ_LIMIT_ENABLED + bool + default y if SPI_FLASH_FREQ_LIMIT_C5_240MHZ + help + Workaround for frequency limit during encrypted flash writes. + Only enabled when SPI_FLASH_FREQ_LIMIT_C5_240MHZ is enabled. + This is an internal configuration, automatically set based on SPI Flash configuration. + endmenu # "Power Management" diff --git a/components/esp_pm/include/esp_private/pm_impl.h b/components/esp_pm/include/esp_private/pm_impl.h index 02364050c7..56fab2fec9 100644 --- a/components/esp_pm/include/esp_private/pm_impl.h +++ b/components/esp_pm/include/esp_private/pm_impl.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -15,7 +15,6 @@ #include "soc/rtc.h" #include "esp_pm.h" -#include "esp_timer.h" #include "sdkconfig.h" #ifdef __cplusplus @@ -26,11 +25,11 @@ extern "C" { * This is an enum of possible power modes supported by the implementation */ typedef enum { - PM_MODE_LIGHT_SLEEP,//!< Light sleep - PM_MODE_APB_MIN, //!< Idle (no CPU frequency or APB frequency locks) - PM_MODE_APB_MAX, //!< Maximum APB frequency mode - PM_MODE_CPU_MAX, //!< Maximum CPU frequency mode - PM_MODE_COUNT //!< Number of items + PM_MODE_LIGHT_SLEEP, //!< Light sleep + PM_MODE_APB_MIN, //!< Idle (no CPU frequency or APB frequency locks) + PM_MODE_APB_MAX, //!< Maximum APB frequency mode + PM_MODE_CPU_MAX, //!< Maximum CPU frequency mode + PM_MODE_COUNT //!< Number of items } pm_mode_t; /** @@ -141,8 +140,18 @@ esp_err_t esp_pm_register_skip_light_sleep_callback(skip_light_sleep_cb_t cb); */ esp_err_t esp_pm_unregister_skip_light_sleep_callback(skip_light_sleep_cb_t cb); + +/** + * @brief Initialize flash frequency limit + * + * This function initializes the flash frequency limit. + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled. + */ +void esp_pm_flash_freq_limit_init(void); + #ifdef CONFIG_PM_PROFILING #define WITH_PROFILING +#include "esp_timer.h" #endif #ifdef WITH_PROFILING diff --git a/components/esp_pm/include/esp_private/pm_impl_freq_limit.h b/components/esp_pm/include/esp_private/pm_impl_freq_limit.h new file mode 100644 index 0000000000..252ca780d7 --- /dev/null +++ b/components/esp_pm/include/esp_private/pm_impl_freq_limit.h @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED + +/** + * @brief Initialize and pre-calculate forced CPU_MAX frequency configuration (private function for spi_flash) + * + * This function pre-calculates and stores the forced CPU_MAX frequency configuration + * based on the given frequency limit. The configuration is computed once during + * initialization and reused during runtime for better performance. + * + * @param limit_freq_mhz Frequency limit in MHz + * @note This is a private function, only for use by spi_flash component. + * @note Must be called during initialization before esp_pm_impl_cpu_max_freq_force(). + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is enabled. + */ +void esp_pm_impl_cpu_max_freq_force_init(uint32_t limit_freq_mhz); + +/** + * @brief Force CPU_MAX frequency to pre-configured limit (private function for spi_flash) + * + * This function activates the pre-configured forced CPU_MAX frequency limit. + * When forced, all reads of CPU_MAX frequency will use the pre-configured value + * instead of the configured value. + * + * @note This is a private function, only for use by spi_flash component. + * @note The forced frequency configuration must be pre-calculated during initialization. + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is enabled. + */ +void esp_pm_impl_cpu_max_freq_force(void); + +/** + * @brief Unforce CPU_MAX frequency (private function for spi_flash) + * + * This function removes the forced CPU_MAX frequency, allowing the configured + * value to be used again. + * @note This is a private function, only for use by spi_flash component. + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is enabled. + */ +void esp_pm_impl_cpu_max_freq_unforce(void); + +#endif // CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_pm/pm_c5_flash_freq_limit.c b/components/esp_pm/pm_c5_flash_freq_limit.c new file mode 100644 index 0000000000..f077015650 --- /dev/null +++ b/components/esp_pm/pm_c5_flash_freq_limit.c @@ -0,0 +1,168 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + //This file implements esp_flash_freq_limit_cb and esp_flash_freq_unlimit_cb callbacks to limit and unlimit CPU + //frequency when encrypted flash writes are performed. + +#include "sdkconfig.h" + +#include "esp_log.h" +#include "esp_attr.h" +#include "esp_rom_sys.h" +#include "soc/chip_revision.h" +#include "soc/rtc.h" +#include "hal/efuse_hal.h" + +#include "esp_private/pm_impl_freq_limit.h" +#include "esp_private/spi_flash_freq_limit_cbs.h" +#include "esp_private/esp_clk_utils.h" + +/** + * @brief Get the frequency limit for flash encryption lock based on chip revision + * + * @return uint32_t Frequency limit (MHz) + * + * Logic: + * - v1.2+: limit to 160MHz + * - v1.0: limit to 80MHz + */ +static uint32_t IRAM_ATTR get_encrypt_lock_freq_limit(void) +{ + unsigned int chip_revision = efuse_hal_chip_revision(); + if (ESP_CHIP_REV_ABOVE(chip_revision, 102)) { + return 160; + } else { + return 80; + } +} + +void esp_pm_flash_freq_limit_init(void) +{ + uint32_t limit_freq_mhz = get_encrypt_lock_freq_limit(); + ESP_EARLY_LOGW("spi_flash", "CPU frequency is set to 240MHz. esp_flash_write_encrypted() will automatically limit CPU frequency to %dMHz during execution.", limit_freq_mhz); +#ifdef CONFIG_PM_ENABLE + /* Pre-calculate and store forced frequency configuration during initialization. + * This is done here to avoid runtime calculation overhead in lock/unlock functions. + */ + esp_pm_impl_cpu_max_freq_force_init(limit_freq_mhz); +#endif +} + +#if !CONFIG_PM_ENABLE +/* Saved original frequency for !PM_ENABLE case (0 means no change was made) */ +static uint32_t s_saved_freq_mhz = 0; + +// Switch CPU frequency with all necessary synchronization (for !PM_ENABLE case) +// Similar to do_switch() but without PM-specific state management +static void IRAM_ATTR esp_pm_impl_switch_cpu_freq(const rtc_cpu_freq_config_t *old_config, const rtc_cpu_freq_config_t *new_config) +{ + if (new_config->freq_mhz != old_config->freq_mhz) { + //No need to run on_freq_update for ccount, since that's not supported on C5. +#if !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP + /* Synchronize MSPI speed mode before CPU frequency switching */ + esp_clk_utils_mspi_speed_mode_sync_before_cpu_freq_switching(new_config->source_freq_mhz, new_config->freq_mhz); +#endif + + //Not taking s_time_update_lock since C5 have fixed divider for systimer + rtc_clk_cpu_freq_set_config_fast(new_config); + +#if !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP + /* Synchronize MSPI speed mode after CPU frequency switching */ + esp_clk_utils_mspi_speed_mode_sync_after_cpu_freq_switching(new_config->source_freq_mhz, new_config->freq_mhz); +#endif + } +} + +/** + * @brief Limit CPU frequency to target frequency (for !PM_ENABLE case only) + * + * This function automatically reads the current CPU frequency, saves it, + * and switches to the target frequency if current frequency is higher. + * The original frequency can be restored by calling unlimit_cpu_freq(). + * + * @param target_freq_mhz Target frequency limit in MHz + * @note limit->unlimit cannot be nested, and this function must not be called concurrently + * @note This is a private function, only for use by spi_flash component. + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is not set. + */ +static void IRAM_ATTR limit_cpu_freq(uint32_t target_freq_mhz) +{ + /* PM not enabled, directly switch frequency and save original frequency */ + rtc_cpu_freq_config_t old_config, new_config; + rtc_clk_cpu_freq_get_config(&old_config); + + if (old_config.freq_mhz > target_freq_mhz) { + s_saved_freq_mhz = old_config.freq_mhz; + if (rtc_clk_cpu_freq_mhz_to_config(target_freq_mhz, &new_config)) { + /* Use PM implementation function to switch frequency with all necessary synchronization */ + esp_pm_impl_switch_cpu_freq(&old_config, &new_config); + const unsigned wait_clock_stable_us = 10; + esp_rom_delay_us(wait_clock_stable_us); + } + } else { + s_saved_freq_mhz = 0; /* No change needed */ + } +} + +/** + * @brief Unlimit CPU frequency, restoring to original frequency (for !PM_ENABLE case only) + * + * This function restores the CPU frequency to the value saved by + * limit_cpu_freq(). If no frequency change was made, this function + * does nothing. + * + * @note limit->unlimit cannot be nested, and this function must not be called concurrently + * @note This is a private function, only for use by spi_flash component. + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is not set. + */ +static void IRAM_ATTR unlimit_cpu_freq(void) +{ + /* PM not enabled, restore to original frequency */ + if (s_saved_freq_mhz > 0) { + rtc_cpu_freq_config_t old_config, new_config; + rtc_clk_cpu_freq_get_config(&old_config); + if (rtc_clk_cpu_freq_mhz_to_config(s_saved_freq_mhz, &new_config)) { + /* Use PM implementation function to switch frequency with all necessary synchronization */ + esp_pm_impl_switch_cpu_freq(&old_config, &new_config); + const unsigned wait_clock_stable_us = 10; + esp_rom_delay_us(wait_clock_stable_us); + } + } + /* If s_saved_freq_mhz == 0, no change was made, so no need to restore */ + s_saved_freq_mhz = 0; +} +#endif // !CONFIG_PM_ENABLE + +void IRAM_ATTR esp_flash_freq_limit_cb(void) +{ + /* Limit the frequency */ +#ifdef CONFIG_PM_ENABLE + /* PM enabled, use force mechanism */ + esp_pm_impl_cpu_max_freq_force(); +#else + /* PM not enabled, use limit mechanism */ + uint32_t limit_freq_mhz = get_encrypt_lock_freq_limit(); + /* Call PM implementation function to limit CPU frequency. + * This function automatically reads current frequency, saves it, and switches to target frequency. + */ + limit_cpu_freq(limit_freq_mhz); +#endif +} + +void IRAM_ATTR esp_flash_freq_unlimit_cb(void) +{ + /* Restore the frequency */ +#ifdef CONFIG_PM_ENABLE + /* PM enabled, use unforce mechanism */ + esp_pm_impl_cpu_max_freq_unforce(); +#else + /* PM not enabled, use unlimit mechanism */ + /* Call PM implementation function to unlimit CPU frequency. + * This function restores the original frequency saved by limit_cpu_freq(). + */ + unlimit_cpu_freq(); +#endif +} diff --git a/components/esp_pm/pm_impl.c b/components/esp_pm/pm_impl.c index 0f4852defe..492cfacf94 100644 --- a/components/esp_pm/pm_impl.c +++ b/components/esp_pm/pm_impl.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "sdkconfig.h" #include "esp_attr.h" @@ -33,6 +34,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/portmacro.h" #if CONFIG_FREERTOS_SYSTICK_USES_CCOUNT #include "xtensa_timer.h" #include "xtensa/core-macros.h" @@ -41,6 +43,7 @@ #include "esp_private/pm_impl.h" #include "esp_private/pm_trace.h" #include "esp_private/esp_timer_private.h" +#include "esp_timer.h" #include "esp_private/esp_clk.h" #include "esp_private/esp_clk_tree_common.h" #include "esp_private/sleep_cpu.h" @@ -50,7 +53,7 @@ #include "esp_private/esp_clk_utils.h" #include "esp_sleep.h" #include "esp_memory_utils.h" - +#include "esp_rom_sys.h" #if SOC_PERIPH_CLK_CTRL_SHARED #define HP_UART_SRC_CLK_ATOMIC() PERIPH_RCC_ATOMIC() @@ -167,6 +170,16 @@ static esp_pm_lock_handle_t s_rtos_lock_handle[CONFIG_FREERTOS_NUMBER_OF_CORES]; */ static rtc_cpu_freq_config_t s_cpu_freq_by_mode[PM_MODE_COUNT]; +#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED +/* Forced CPU_MAX frequency configuration. + * s_cpu_max_freq_forced indicates whether CPU_MAX frequency is forced. + * s_cpu_max_freq_force_config stores the forced frequency configuration. + * Protected by s_switch_lock, same as s_cpu_freq_by_mode. + */ +static bool s_cpu_max_freq_forced = false; +static rtc_cpu_freq_config_t s_cpu_max_freq_force_config; +#endif + /* Whether automatic light sleep is enabled */ static bool s_light_sleep_en = false; @@ -392,6 +405,32 @@ static esp_err_t esp_pm_sleep_configure(const esp_pm_config_t *config) return err; } +/** + * @brief Get frequency configuration for a given mode, considering forced frequency + * + * This function returns a pointer to the frequency configuration for the given mode. + * For PM_MODE_CPU_MAX, if s_cpu_max_freq_forced is true, it returns + * a pointer to s_cpu_max_freq_force_config. Otherwise, it returns a pointer to + * s_cpu_freq_by_mode[mode]. + * + * @param mode Power mode to get configuration for + * @note Must be called with s_switch_lock held + * @note Must be in IRAM when called from ISR context (e.g., do_switch) + * @return rtc_cpu_freq_config_t* Pointer to frequency configuration for the given mode + */ +static inline IRAM_ATTR rtc_cpu_freq_config_t *get_cpu_freq_config_by_mode(pm_mode_t mode) +{ +#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED + if (mode == PM_MODE_CPU_MAX && s_cpu_max_freq_forced) { + return &s_cpu_max_freq_force_config; + } else { + return &s_cpu_freq_by_mode[mode]; + } +#else + return &s_cpu_freq_by_mode[mode]; +#endif +} + esp_err_t esp_pm_configure(const void* vconfig) { #ifndef CONFIG_PM_ENABLE @@ -641,18 +680,19 @@ static void IRAM_ATTR do_switch(pm_mode_t new_mode) s_is_switching = true; bool config_changed = s_config_changed; s_config_changed = false; - portENTER_CRITICAL_ISR(&s_cpu_freq_switch_lock[core_id]); - portEXIT_CRITICAL_ISR(&s_switch_lock); - rtc_cpu_freq_config_t new_config = s_cpu_freq_by_mode[new_mode]; + rtc_cpu_freq_config_t new_config = *get_cpu_freq_config_by_mode(new_mode); rtc_cpu_freq_config_t old_config; if (!config_changed) { - old_config = s_cpu_freq_by_mode[s_mode]; + old_config = *get_cpu_freq_config_by_mode(s_mode); } else { rtc_clk_cpu_freq_get_config(&old_config); } + portENTER_CRITICAL_ISR(&s_cpu_freq_switch_lock[core_id]); + portEXIT_CRITICAL_ISR(&s_switch_lock); + if (new_config.freq_mhz != old_config.freq_mhz) { uint32_t old_ticks_per_us = old_config.freq_mhz; uint32_t new_ticks_per_us = new_config.freq_mhz; @@ -865,6 +905,7 @@ void vApplicationSleep( TickType_t xExpectedIdleTime ) void esp_pm_impl_dump_stats(FILE* out) { pm_time_t time_in_mode[PM_MODE_COUNT]; + uint32_t freq_mhz_by_mode[PM_MODE_COUNT]; portENTER_CRITICAL_ISR(&s_switch_lock); memcpy(time_in_mode, s_time_in_mode, sizeof(time_in_mode)); @@ -874,6 +915,10 @@ void esp_pm_impl_dump_stats(FILE* out) bool light_sleep_en = s_light_sleep_en; uint32_t light_sleep_counts = s_light_sleep_counts; uint32_t light_sleep_reject_counts = s_light_sleep_reject_counts; + // Read all frequency configs while holding s_switch_lock + for (int i = 0; i < PM_MODE_COUNT; ++i) { + freq_mhz_by_mode[i] = get_cpu_freq_config_by_mode(i)->freq_mhz; + } portEXIT_CRITICAL_ISR(&s_switch_lock); time_in_mode[cur_mode] += now - last_mode_change_time; @@ -887,7 +932,7 @@ void esp_pm_impl_dump_stats(FILE* out) } fprintf(out, "%-8s %-3"PRIu32"M%-7s %-10lld %-2d%%\n", s_mode_names[i], - s_cpu_freq_by_mode[i].freq_mhz, + freq_mhz_by_mode[i], "", //Empty space to align columns time_in_mode[i], (int) (time_in_mode[i] * 100 / now)); @@ -904,7 +949,7 @@ int esp_pm_impl_get_cpu_freq(pm_mode_t mode) int freq_mhz; if (mode >= PM_MODE_LIGHT_SLEEP && mode < PM_MODE_COUNT) { portENTER_CRITICAL(&s_switch_lock); - freq_mhz = s_cpu_freq_by_mode[mode].freq_mhz; + freq_mhz = get_cpu_freq_config_by_mode(mode)->freq_mhz; portEXIT_CRITICAL(&s_switch_lock); } else { abort(); @@ -1050,3 +1095,42 @@ void esp_pm_impl_waiti(void) esp_cpu_wait_for_intr(); #endif // CONFIG_FREERTOS_USE_TICKLESS_IDLE } + +#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED && CONFIG_PM_ENABLE +void esp_pm_impl_cpu_max_freq_force_init(uint32_t limit_freq_mhz) +{ + // Pre-calculate frequency config (done outside critical section) + rtc_cpu_freq_config_t force_config; + bool res = rtc_clk_cpu_freq_mhz_to_config(limit_freq_mhz, &force_config); + assert(res && "Failed to convert forced CPU_MAX frequency to config"); + + // Store the pre-calculated config in critical section + portENTER_CRITICAL(&s_switch_lock); + s_cpu_max_freq_force_config = force_config; + portEXIT_CRITICAL(&s_switch_lock); +} + +void IRAM_ATTR esp_pm_impl_cpu_max_freq_force(void) +{ + portENTER_CRITICAL_SAFE(&s_switch_lock); + // Activate pre-configured forced frequency (no calculation needed at runtime) + s_cpu_max_freq_forced = true; + s_config_changed = true; + portEXIT_CRITICAL_SAFE(&s_switch_lock); + do_switch(PM_MODE_CPU_MAX); + const unsigned wait_clock_stable_us = 10; + esp_rom_delay_us(wait_clock_stable_us); +} + +void IRAM_ATTR esp_pm_impl_cpu_max_freq_unforce(void) +{ + portENTER_CRITICAL_SAFE(&s_switch_lock); + s_cpu_max_freq_forced = false; + s_config_changed = true; + portEXIT_CRITICAL_SAFE(&s_switch_lock); + do_switch(PM_MODE_CPU_MAX); + const unsigned wait_clock_stable_us = 10; + esp_rom_delay_us(wait_clock_stable_us); +} + +#endif // CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED && CONFIG_PM_ENABLE diff --git a/components/esp_system/port/soc/esp32c5/Kconfig.cpu b/components/esp_system/port/soc/esp32c5/Kconfig.cpu index cf42222229..95c3b92cbc 100644 --- a/components/esp_system/port/soc/esp32c5/Kconfig.cpu +++ b/components/esp_system/port/soc/esp32c5/Kconfig.cpu @@ -20,6 +20,13 @@ choice ESP_DEFAULT_CPU_FREQ_MHZ # Please see SoC Errata document for details. depends on !SECURE_FLASH_ENC_ENABLED bool "240 MHz" + help + When 240MHz is selected, esp_flash_write_encrypted() will automatically limit CPU frequency during + execution: + - v1.2 and above chips: limited to 160MHz + - v1.0 and below chips: limited to 80MHz + When 160MHz or lower is selected, no frequency limiting occurs during encrypted writes. Please refer to + the ESP32-C5 SoC Errata document for more details. endchoice config ESP_DEFAULT_CPU_FREQ_MHZ diff --git a/components/esp_system/startup_funcs.c b/components/esp_system/startup_funcs.c index 008a9a9e6c..c3e9de4734 100644 --- a/components/esp_system/startup_funcs.c +++ b/components/esp_system/startup_funcs.c @@ -31,7 +31,7 @@ #include "private/esp_coexist_internal.h" #endif -#if CONFIG_PM_ENABLE +#if CONFIG_PM_ENABLE || CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED #include "esp_pm.h" #include "esp_private/pm_impl.h" #endif @@ -120,6 +120,9 @@ ESP_SYSTEM_INIT_FN(init_flash, CORE, BIT(0), 130) #endif // CONFIG_SPI_FLASH_BROWNOUT_RESET // The log library will call the registered callback function to check if the cache is disabled. esp_log_util_set_cache_enabled_cb(spi_flash_cache_enabled); +#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED + esp_pm_flash_freq_limit_init(); +#endif // CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED return ESP_OK; } #endif // !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP diff --git a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in index f40a98bbb8..a50f214bba 100644 --- a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in @@ -1119,6 +1119,10 @@ config SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR bool default y +config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED + bool + default y + config SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY bool default y diff --git a/components/soc/esp32c5/include/soc/soc_caps.h b/components/soc/esp32c5/include/soc/soc_caps.h index 832184bc30..d24d26f490 100644 --- a/components/soc/esp32c5/include/soc/soc_caps.h +++ b/components/soc/esp32c5/include/soc/soc_caps.h @@ -453,6 +453,7 @@ #define SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP (1) #define SOC_SPI_MEM_SUPPORT_TIMING_TUNING (1) #define SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR (1) +#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1) #define SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY (1) #define SOC_MEMSPI_SRC_FREQ_120M_SUPPORTED 1 diff --git a/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in index d58e7f1a34..d3d25d0336 100644 --- a/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in @@ -815,6 +815,10 @@ config SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR bool default y +config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED + bool + default y + config SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY bool default y diff --git a/components/soc/esp32c61/include/soc/soc_caps.h b/components/soc/esp32c61/include/soc/soc_caps.h index 74542d20fb..67e2df0c68 100644 --- a/components/soc/esp32c61/include/soc/soc_caps.h +++ b/components/soc/esp32c61/include/soc/soc_caps.h @@ -341,6 +341,7 @@ #define SOC_SPI_MEM_SUPPORT_WRAP (1) #define SOC_SPI_MEM_SUPPORT_TIMING_TUNING (1) #define SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR (1) +#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1) #define SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY (1) #define SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED 1 diff --git a/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in index b4b28fbb59..6735105785 100644 --- a/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in @@ -715,6 +715,10 @@ config SOC_SPI_MEM_SUPPORT_WRAP bool default y +config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED + bool + default y + config SOC_SYSTIMER_COUNTER_NUM int default 2 diff --git a/components/soc/esp32h4/include/soc/soc_caps.h b/components/soc/esp32h4/include/soc/soc_caps.h index ffffa6ce8f..6ce6d4102b 100644 --- a/components/soc/esp32h4/include/soc/soc_caps.h +++ b/components/soc/esp32h4/include/soc/soc_caps.h @@ -394,6 +394,7 @@ #define SOC_SPI_MEM_SUPPORT_SW_SUSPEND (1) #define SOC_SPI_MEM_SUPPORT_CHECK_SUS (1) #define SOC_SPI_MEM_SUPPORT_WRAP (1) +#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1) /*-------------------------- SYSTIMER CAPS ----------------------------------*/ #define SOC_SYSTIMER_COUNTER_NUM 2 // Number of counter units diff --git a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in index 6aafc1629d..a4af3dbed7 100644 --- a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in @@ -1499,6 +1499,10 @@ config SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP bool default y +config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED + bool + default y + config SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR bool default y diff --git a/components/soc/esp32p4/include/soc/soc_caps.h b/components/soc/esp32p4/include/soc/soc_caps.h index 9f5ed1ff72..c62e41c525 100644 --- a/components/soc/esp32p4/include/soc/soc_caps.h +++ b/components/soc/esp32p4/include/soc/soc_caps.h @@ -572,6 +572,7 @@ #define SOC_MEMSPI_TIMING_TUNING_BY_DQS (1) #define SOC_MEMSPI_TIMING_TUNING_BY_FLASH_DELAY (1) #define SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP (1) +#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1) #define SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR (1) #define SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUT (1) diff --git a/components/spi_flash/Kconfig b/components/spi_flash/Kconfig index 81d6feef39..c788633f1c 100644 --- a/components/spi_flash/Kconfig +++ b/components/spi_flash/Kconfig @@ -458,4 +458,11 @@ menu "SPI Flash driver" application is not using flash encryption feature and is in need of some additional memory from IRAM region (~1KB) then this config can be disabled. + config SPI_FLASH_FREQ_LIMIT_C5_240MHZ + bool + default y if IDF_TARGET_ESP32C5 && ESP_DEFAULT_CPU_FREQ_MHZ_240 + help + Enable frequency limit workaround for encrypted flash writes on ESP32-C5 at 240MHz default CPU frequency. + This is an internal configuration, automatically set based on target chip and CPU frequency. + endmenu diff --git a/components/spi_flash/esp_flash_api.c b/components/spi_flash/esp_flash_api.c index b21b84069d..bf31ca9a24 100644 --- a/components/spi_flash/esp_flash_api.c +++ b/components/spi_flash/esp_flash_api.c @@ -135,15 +135,6 @@ static ESP_LOG_ATTR const char io_mode_str[][IO_STR_LEN] = { _Static_assert(sizeof(io_mode_str)/IO_STR_LEN == SPI_FLASH_READ_MODE_MAX, "the io_mode_str should be consistent with the esp_flash_io_mode_t defined in spi_flash_types.h"); -esp_err_t esp_flash_read_chip_id(esp_flash_t* chip, uint32_t* flash_id); - -#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV -static esp_err_t spiflash_start_default(esp_flash_t *chip); -static esp_err_t spiflash_end_default(esp_flash_t *chip, esp_err_t err); -static esp_err_t check_chip_pointer_default(esp_flash_t **inout_chip); -static esp_err_t flash_end_flush_cache(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length); -#endif // !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV - typedef struct { esp_err_t (*start)(esp_flash_t *chip); esp_err_t (*end)(esp_flash_t *chip, esp_err_t err); @@ -151,19 +142,70 @@ typedef struct { esp_err_t (*flash_end_flush_cache)(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length); } rom_spiflash_api_func_t; + +esp_err_t esp_flash_read_chip_id(esp_flash_t* chip, uint32_t* flash_id); + +#if CONFIG_SPI_FLASH_ROM_IMPL +extern rom_spiflash_api_func_t *esp_flash_api_funcs; +#define rom_spiflash_api_funcs esp_flash_api_funcs +#else +#define rom_spiflash_api_funcs esp_flash_api_funcs_patched_ptr +#endif + #if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV +// API funcs case 1 & 2 +static esp_err_t spiflash_start_default(esp_flash_t *chip); +static esp_err_t spiflash_end_default(esp_flash_t *chip, esp_err_t err); +static esp_err_t check_chip_pointer_default(esp_flash_t **inout_chip); +static esp_err_t flash_end_flush_cache(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length); + // These functions can be placed in the ROM. For now we use the code in IDF. -DRAM_ATTR static rom_spiflash_api_func_t default_spiflash_rom_api = { +DRAM_ATTR static rom_spiflash_api_func_t esp_flash_api_funcs_patched = { .start = spiflash_start_default, .end = spiflash_end_default, .chip_check = check_chip_pointer_default, .flash_end_flush_cache = flash_end_flush_cache, }; -DRAM_ATTR rom_spiflash_api_func_t *rom_spiflash_api_funcs = &default_spiflash_rom_api; -#else -extern rom_spiflash_api_func_t *esp_flash_api_funcs; -#define rom_spiflash_api_funcs esp_flash_api_funcs +# if !CONFIG_SPI_FLASH_ROM_IMPL +// API funcs case 1: Not using ROM - define our own pointer and all functions +DRAM_ATTR static rom_spiflash_api_func_t *esp_flash_api_funcs_patched_ptr = &esp_flash_api_funcs_patched; + +# else // CONFIG_SPI_FLASH_ROM_IMPL +// API funcs case 2: Using ROM APIs but patch all api_funcs by updating esp_flash_api_funcs from ROM +void esp_flash_rom_api_funcs_init(void) +{ + // Point esp_flash_api_funcs to our default structure + esp_flash_api_funcs = &esp_flash_api_funcs_patched; +} + +# endif // CONFIG_SPI_FLASH_ROM_IMPL + +#else // CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV +// Using ROM implementation + +# if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ +// API funcs case 3: Using ROM APIs but patch start function to support flags parameter +static esp_err_t spiflash_start_default(esp_flash_t *chip); +DRAM_ATTR static rom_spiflash_api_func_t esp_flash_api_funcs_patched; + +// Copy ROM structure to RAM and patch start function to support flags +void esp_flash_rom_api_funcs_init(void) +{ + rom_spiflash_api_func_t *rom_ptr = esp_flash_api_funcs; + memcpy(&esp_flash_api_funcs_patched, rom_ptr, sizeof(rom_spiflash_api_func_t)); + esp_flash_api_funcs_patched.start = spiflash_start_default; + esp_flash_api_funcs = &esp_flash_api_funcs_patched; +} + +# else +// API funcs case 4: Using All ROM APIs directly +void esp_flash_rom_api_funcs_init(void) +{ + // Do nothing +} + +# endif // CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ #endif // !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV /* Static function to notify OS of a new SPI flash operation. @@ -171,11 +213,13 @@ extern rom_spiflash_api_func_t *esp_flash_api_funcs; If returns an error result, caller must abort. If returns ESP_OK, caller must call rom_spiflash_api_funcs->end() before returning. */ -#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV -static esp_err_t spiflash_start_default(esp_flash_t *chip) +#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ +//Avoid constprop issue that place this function into flash. +__attribute__((optimize("O0"))) //IDF-14941 +static esp_err_t spiflash_start_core(esp_flash_t *chip, uint32_t flags) { if (chip->os_func != NULL && chip->os_func->start != NULL) { - esp_err_t err = chip->os_func->start(chip->os_func_data); + esp_err_t err = chip->os_func->start(chip->os_func_data, flags); if (err != ESP_OK) { return err; } @@ -184,6 +228,13 @@ static esp_err_t spiflash_start_default(esp_flash_t *chip) return ESP_OK; } +static esp_err_t spiflash_start_default(esp_flash_t *chip) +{ + return spiflash_start_core(chip, 0); +} +#endif //!CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ + +#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV /* Static function to notify OS that SPI flash operation is complete. */ static esp_err_t spiflash_end_default(esp_flash_t *chip, esp_err_t err) @@ -1235,54 +1286,20 @@ esp_err_t esp_flash_set_io_mode(esp_flash_t* chip, bool qe) } #endif //CONFIG_SPI_FLASH_ROM_IMPL -#if CONFIG_IDF_TARGET_ESP32C5 -// Hardware workaround: ESP32-C5 encrypted flash writes require CPU freq ≤ 160 MHz -#include "soc/rtc.h" - -static int s_esp32c5_saved_cpu_freq_mhz; - -static IRAM_ATTR void esp32c5_freq_limit_acquire(void) +#if !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ +// use `esp_flash_write_encrypted` ROM version on chips later than C3, S3 +// For ESP32-C5, use IDF implementation when CPU frequency is 240MHz (calling start() with arg is required) +FORCE_INLINE_ATTR esp_err_t s_encryption_write_lock(esp_flash_t *chip) { - rtc_cpu_freq_config_t old_config, new_config; - rtc_clk_cpu_freq_get_config(&old_config); - - if (old_config.freq_mhz <= 160) { - s_esp32c5_saved_cpu_freq_mhz = 0; // No change needed - return; - } - s_esp32c5_saved_cpu_freq_mhz = old_config.freq_mhz; - - if (rtc_clk_cpu_freq_mhz_to_config(160, &new_config)) { - rtc_clk_cpu_freq_set_config_fast(&new_config); - } -} - -static IRAM_ATTR void esp32c5_freq_limit_release(void) -{ - if (s_esp32c5_saved_cpu_freq_mhz == 0) { - return; // No change was made - } - - rtc_cpu_freq_config_t new_config; - - if (rtc_clk_cpu_freq_mhz_to_config(s_esp32c5_saved_cpu_freq_mhz, &new_config)) { - rtc_clk_cpu_freq_set_config_fast(&new_config); - } - - s_esp32c5_saved_cpu_freq_mhz = 0; -} -#endif // CONFIG_IDF_TARGET_ESP32C5 - -#if !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) -// use `esp_flash_write_encrypted` ROM version on chips later than C3 and S3 -FORCE_INLINE_ATTR esp_err_t s_encryption_write_lock(esp_flash_t *chip) { -#if CONFIG_IDF_TARGET_ESP32C5 - esp32c5_freq_limit_acquire(); -#endif #if CONFIG_IDF_TARGET_ESP32S2 esp_crypto_dma_lock_acquire(); #endif //CONFIG_IDF_TARGET_ESP32S2 +#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ + // Use start_core with LIMIT_CPU_FREQ flag to trigger freq_limit_lock in OS layer + return spiflash_start_core(chip, ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ); +#else return rom_spiflash_api_funcs->start(chip); +#endif } FORCE_INLINE_ATTR esp_err_t s_encryption_write_unlock(esp_flash_t *chip) { @@ -1290,9 +1307,6 @@ FORCE_INLINE_ATTR esp_err_t s_encryption_write_unlock(esp_flash_t *chip) { #if CONFIG_IDF_TARGET_ESP32S2 esp_crypto_dma_lock_release(); #endif //CONFIG_IDF_TARGET_ESP32S2 -#if CONFIG_IDF_TARGET_ESP32C5 - esp32c5_freq_limit_release(); -#endif return err; } @@ -1535,16 +1549,9 @@ esp_err_t IRAM_ATTR esp_flash_write_encrypted(esp_flash_t *chip, uint32_t addres if (length > chip->size - address) { return ESP_ERR_INVALID_ARG; } -#if CONFIG_IDF_TARGET_ESP32C5 - esp32c5_freq_limit_acquire(); -#endif - err = rom_esp_flash_write_encrypted(chip, address, buffer, length); -#if CONFIG_IDF_TARGET_ESP32C5 - esp32c5_freq_limit_release(); -#endif - return err; + return rom_esp_flash_write_encrypted(chip, address, buffer, length); } -#endif // !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) +#endif // !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ //init suspend mode cmd, uses internal. esp_err_t esp_flash_suspend_cmd_init(esp_flash_t* chip) diff --git a/components/spi_flash/esp_flash_spi_init.c b/components/spi_flash/esp_flash_spi_init.c index 2ec9c1abaa..8beb942537 100644 --- a/components/spi_flash/esp_flash_spi_init.c +++ b/components/spi_flash/esp_flash_spi_init.c @@ -145,6 +145,19 @@ esp_flash_t *esp_flash_default_chip = NULL; #endif //!CONFIG_SPI_FLASH_AUTO_SUSPEND #endif // Other target +// Dynamic flash configuration is only needed when: +// 1. Frequency limit workaround is enabled (CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ) +// 2. Flash frequency requires timing tuning (80MHz or 120MHz, i.e., > 40MHz) +// 3. CPU frequency reduction will trigger MSPI timing tuning to enter low speed mode +// This happens when: SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED && CONFIG_SPIRAM && +// (target_cpu_freq < CONFIG_SPIRAM_SPEED) +// Note: The runtime check for CPU freq < PSRAM speed is done in clk_utils.c, +// which calls mspi_timing_change_speed_mode_cache_safe(true) to enter low speed mode. +// For ESP32-C5, if PSRAM is enabled and CPU freq < PSRAM speed, timing tuning will be disabled. +#define C5_NEEDS_DYNAMIC_CONFIG (CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ && CONFIG_SPIRAM && \ + (CONFIG_ESPTOOLPY_FLASHFREQ_80M || CONFIG_ESPTOOLPY_FLASHFREQ_120M)) + + static IRAM_ATTR NOINLINE_ATTR void cs_initialize(esp_flash_t *chip, const esp_flash_spi_device_config_t *config, bool cs_use_iomux, int cs_id) { //Not using spicommon_cs_initialize since we don't want to put the whole @@ -160,7 +173,7 @@ static IRAM_ATTR NOINLINE_ATTR void cs_initialize(esp_flash_t *chip, const esp_f //To avoid the panic caused by flash data line conflicts during cs line //initialization, disable the cache temporarily - chip->os_func->start(chip->os_func_data); + chip->os_func->start(chip->os_func_data, 0); gpio_hal_input_enable(&gpio_hal, cs_io_num); if (cs_use_iomux) { gpio_hal_func_sel(&gpio_hal, cs_io_num, spics_func); @@ -554,6 +567,16 @@ esp_err_t esp_flash_init_default_chip(void) if (err != ESP_OK) { return err; } +#if C5_NEEDS_DYNAMIC_CONFIG + err = memspi_host_init_c5_dynamic_config(&esp_flash_default_host); + if (err != ESP_OK) { + return err; + } +#endif + +#if CONFIG_SPI_FLASH_ROM_IMPL + esp_flash_rom_api_funcs_init(); +#endif // CONFIG_SPI_FLASH_ROM_IMPL // ROM TODO: account for non-standard default pins in efuse // ROM TODO: to account for chips which are slow to power on, maybe keep probing in a loop here @@ -615,6 +638,7 @@ esp_err_t esp_flash_app_init(void) spi_flash_init_lock(); spi_flash_guard_set(&g_flash_guard_default_ops); + #if CONFIG_SPI_FLASH_ENABLE_COUNTERS esp_flash_reset_counters(); #endif diff --git a/components/spi_flash/include/esp_flash.h b/components/spi_flash/include/esp_flash.h index 44e325adc9..c3b29269ef 100644 --- a/components/spi_flash/include/esp_flash.h +++ b/components/spi_flash/include/esp_flash.h @@ -38,11 +38,17 @@ typedef struct { * risk. */ typedef struct { + /** + * Flags for start function + */ + /** Limit CPU frequency during flash operations (ESP32-C5 only, 240MHz). + */ + #define ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ BIT(0) /** * Called before commencing any flash operation. Does not need to be * recursive (ie is called at most once for each call to 'end'). */ - esp_err_t (*start)(void *arg); + esp_err_t (*start)(void *arg, uint32_t flags); /** Called after completing any flash operation. */ esp_err_t (*end)(void *arg); diff --git a/components/spi_flash/include/esp_flash_internal.h b/components/spi_flash/include/esp_flash_internal.h index d07b3e2290..b3a87206bf 100644 --- a/components/spi_flash/include/esp_flash_internal.h +++ b/components/spi_flash/include/esp_flash_internal.h @@ -117,6 +117,16 @@ esp_err_t esp_flash_app_disable_os_functions(esp_flash_t* chip); */ esp_err_t esp_flash_set_dangerous_write_protection(esp_flash_t *chip, const bool protect); +#if CONFIG_SPI_FLASH_ROM_IMPL +/** + * @brief Initialize ROM API functions structure + * + * This function initializes the ROM API functions structure, either by pointing + * to a custom structure or by patching the ROM structure in RAM. + */ +void esp_flash_rom_api_funcs_init(void); +#endif // CONFIG_SPI_FLASH_ROM_IMPL + #ifdef __cplusplus } #endif diff --git a/components/spi_flash/include/esp_private/spi_flash_freq_limit_cbs.h b/components/spi_flash/include/esp_private/spi_flash_freq_limit_cbs.h new file mode 100644 index 0000000000..3e95f36965 --- /dev/null +++ b/components/spi_flash/include/esp_private/spi_flash_freq_limit_cbs.h @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" +#include "esp_flash.h" +#include "esp_attr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ +/** + * @brief Callback to limit CPU frequency for flash encryption writes (ESP32-C5 only, 240MHz) + * + * This function limits CPU frequency to <= 160MHz during encrypted flash writes. + * It should be called before starting an encrypted flash write operation. + * + * @note This is an internal function, not exposed to users. + * @note This function is placed in IRAM (implementation uses IRAM_ATTR). + * @note This function is called from spi_flash_os_func_app.c::spi1_start() after cache + * and scheduler locks are acquired. As a result, there is no concurrency concern + * and no need for internal locking or reference counting. + */ +void esp_flash_freq_limit_cb(void); + +/** + * @brief Callback to unlimit CPU frequency after flash encryption writes (ESP32-C5 only, 240MHz) + * + * This function restores the CPU frequency after an encrypted flash write operation. + * It should be called after completing an encrypted flash write operation. + * + * @note This is an internal function, not exposed to users. + * @note This function is placed in IRAM (implementation uses IRAM_ATTR). + * @note This function is called from spi_flash_os_func_app.c::spi1_end() before cache + * and scheduler locks are released. As a result, there is no concurrency concern + * and no need for internal locking or reference counting. + */ +void esp_flash_freq_unlimit_cb(void); +#endif // CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ + +#ifdef __cplusplus +} +#endif diff --git a/components/spi_flash/include/memspi_host_driver.h b/components/spi_flash/include/memspi_host_driver.h index 29379e21a2..269ea554c3 100644 --- a/components/spi_flash/include/memspi_host_driver.h +++ b/components/spi_flash/include/memspi_host_driver.h @@ -48,6 +48,17 @@ typedef spi_flash_hal_context_t memspi_host_inst_t; */ esp_err_t memspi_host_init_pointers(memspi_host_inst_t *host, const memspi_host_config_t *cfg); +/** + * Initialize the memory SPI host for ESP32-C5 dynamic configuration. + * + * @param host Pointer to the host structure. + * + * @note This function is available only when CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ is defined. + * @note This function can only be called once at startup, after memspi_host_init_pointers() is called. + * @return always return ESP_OK + */ +esp_err_t memspi_host_init_c5_dynamic_config(memspi_host_inst_t *host); + /******************************************************************************* * NOTICE * Rest part of this file are part of the HAL layer @@ -161,7 +172,7 @@ int memspi_host_read_data_slicer(spi_flash_host_inst_t *host, uint32_t address, /** * @brief Slicer for write data used in non-encrypted regions. This slicer limit the length to the - * maximum size the host supports, and truncate if the write data lie accross the page boundary + * maximum size the host supports, and truncate if the write data lie across the page boundary * (256 bytes) * * @param address Flash address to write diff --git a/components/spi_flash/linker.lf b/components/spi_flash/linker.lf index aa86cb039e..201a2257cb 100644 --- a/components/spi_flash/linker.lf +++ b/components/spi_flash/linker.lf @@ -45,12 +45,15 @@ entries: if SPI_FLASH_VERIFY_WRITE = y: esp_flash_api: s_verify_write (noflash) - if SPI_FLASH_ROM_IMPL = n || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV = y: + if SPI_FLASH_ROM_IMPL = n || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV = y || SPI_FLASH_FREQ_LIMIT_C5_240MHZ = y: esp_flash_api: spiflash_start_default (noflash) + esp_flash_api: spiflash_start_core (noflash) + esp_flash_api: esp_flash_write_encrypted (noflash) + + if SPI_FLASH_ROM_IMPL = n || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV = y: esp_flash_api: spiflash_end_default (noflash) esp_flash_api: check_chip_pointer_default (noflash) esp_flash_api: flash_end_flush_cache (noflash) - esp_flash_api: esp_flash_write_encrypted (noflash) if SPI_FLASH_ROM_IMPL = n: esp_flash_api: esp_flash_get_size (noflash) diff --git a/components/spi_flash/memspi_host_driver.c b/components/spi_flash/memspi_host_driver.c index d8210f81fc..8f690ad084 100644 --- a/components/spi_flash/memspi_host_driver.c +++ b/components/spi_flash/memspi_host_driver.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "sdkconfig.h" #include "soc/soc_caps.h" #include "spi_flash_defs.h" #include "memspi_host_driver.h" @@ -12,12 +13,13 @@ #include "esp_private/cache_utils.h" #include "esp_flash_partitions.h" #include "esp_memory_utils.h" +#include "hal/mspi_ll.h" #define SPI_FLASH_HAL_MAX_WRITE_BYTES 64 #define SPI_FLASH_HAL_MAX_READ_BYTES 64 -DRAM_ATTR static const spi_flash_host_driver_t esp_flash_default_host = ESP_FLASH_DEFAULT_HOST_DRIVER(); +DRAM_ATTR static spi_flash_host_driver_t esp_flash_default_host = ESP_FLASH_DEFAULT_HOST_DRIVER(); #if SOC_MEMSPI_IS_INDEPENDENT extern void spi_flash_hal_gpspi_poll_cmd_done(spi_flash_host_inst_t *host); @@ -71,9 +73,9 @@ esp_err_t memspi_host_init_pointers(memspi_host_inst_t *host, const memspi_host_ } #if SOC_MEMSPI_IS_INDEPENDENT - if (cfg->host_id == SPI1_HOST) + if (cfg->host_id == SPI1_HOST) { host->inst.driver = &esp_flash_default_host; - else { + } else { host->inst.driver = &esp_flash_gpspi_host; } #else @@ -253,3 +255,52 @@ int memspi_host_read_data_slicer(spi_flash_host_inst_t *host, uint32_t address, } #endif // CONFIG_SPI_FLASH_ROM_IMPL + +#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ +// Dynamic flash configuration based on timing tuning state: +// 1. The timing tuning system automatically sets core clock and timing parameters (including din_mode/num, etc.). +// It also generates configurations such as clock configuration and dummy configuration for driver use. +// Without calling this function, the driver gets these configurations during initialization and uses them forever. +// The timing tuning provides clock configuration (frequency division) and dummy count. +// 2. When dynamic configuration is needed, call this function. The function supports two states: +// - Timing tuned state (high speed): Restore the values generated by the timing tuning system to the driver, +// as if there was no frequency reduction. +// - Without timing tuning state (low speed): Set configuration to maximum frequency (40 MHz on C5), dummy=0. +static uint32_t s_high_speed_clock_reg; +static uint32_t s_high_speed_extra_dummy; + +ESP_STATIC_ASSERT(MSPI_TIMING_LL_CORE_CLOCK_MHZ_DEFAULT == 80); +#define LOW_SPEED_DIV (MSPI_TIMING_LL_CORE_CLOCK_MHZ_DEFAULT/40) +static esp_err_t spi_flash_hal_device_config_c5(spi_flash_host_inst_t *host) +{ + spi_flash_hal_context_t* ctx = (spi_flash_hal_context_t*)host; + + // Note: SPI0 and SPI1 have separate TIMING_CALI_REG registers, but SPI0's register reflects the timing tuning state + // since SPI0 and SPI1 share din_mode/din_num registers and timing tuning is configured on SPI0 + uint32_t timing_cali_reg = REG_READ(SPI_MEM_TIMING_CALI_REG(MSPI_TIMING_LL_MSPI_ID_0)); + bool is_low_speed = !(timing_cali_reg & SPI_MEM_TIMING_CALI_M); + + if (is_low_speed) { + // Low speed mode: Set to safe division, dummy=0 + uint32_t low_speed_clock_reg = mspi_timing_ll_calculate_clock_reg(LOW_SPEED_DIV); + ctx->clock_conf.spimem = low_speed_clock_reg; + ctx->extra_dummy = 0; + } else { + // High speed mode: Restore timing tuning values + ctx->clock_conf.spimem = s_high_speed_clock_reg; + ctx->extra_dummy = s_high_speed_extra_dummy; + } + return spi_flash_hal_device_config(host); +} + +esp_err_t memspi_host_init_c5_dynamic_config(memspi_host_inst_t *host) +{ + assert(s_high_speed_clock_reg == 0 && s_high_speed_extra_dummy == 0); + + spi_flash_hal_context_t* ctx = (spi_flash_hal_context_t*)host; + s_high_speed_clock_reg = ctx->clock_conf.spimem; + s_high_speed_extra_dummy = ctx->extra_dummy; + esp_flash_default_host.dev_config = spi_flash_hal_device_config_c5; + return ESP_OK; +} +#endif diff --git a/components/spi_flash/spi_flash_os_func_app.c b/components/spi_flash/spi_flash_os_func_app.c index e10976b77d..073b102df0 100644 --- a/components/spi_flash/spi_flash_os_func_app.c +++ b/components/spi_flash/spi_flash_os_func_app.c @@ -20,9 +20,12 @@ #include "esp_rom_sys.h" #include "esp_private/spi_flash_os.h" #include "esp_private/cache_utils.h" - #include "esp_private/spi_share_hw_ctrl.h" +// For C5 encrypted write workaround +// Functions are only available when CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ is true +#include "esp_private/spi_flash_freq_limit_cbs.h" + #define SPI_FLASH_CACHE_NO_DISABLE (CONFIG_SPI_FLASH_AUTO_SUSPEND || (CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_RODATA) || CONFIG_APP_BUILD_TYPE_RAM) static const char TAG[] = "spi_flash"; @@ -53,6 +56,7 @@ typedef struct { bool no_protect; //to decide whether to check protected region (for the main chip) or not. uint32_t acquired_since_us; // Time since last explicit yield() uint32_t released_since_us; // Time since last end() (implicit yield) + uint32_t start_flags; // Flags passed to start() function, used to determine if freq_limit was called } app_func_arg_t; static inline void on_spi_released(app_func_arg_t* ctx); @@ -90,21 +94,27 @@ static IRAM_ATTR esp_err_t release_spi_bus_lock(void *arg) return spi_bus_lock_acquire_end(((app_func_arg_t *)arg)->dev_lock); } -static esp_err_t spi23_start(void *arg){ +static esp_err_t spi23_start(void *arg, uint32_t flags) +{ + (void)flags; esp_err_t ret = acquire_spi_bus_lock(arg); on_spi_acquired((app_func_arg_t*)arg); return ret; } -static esp_err_t spi23_end(void *arg){ +static esp_err_t spi23_end(void *arg) +{ esp_err_t ret = release_spi_bus_lock(arg); on_spi_released((app_func_arg_t*)arg); return ret; } -static IRAM_ATTR esp_err_t spi1_start(void *arg) +static IRAM_ATTR esp_err_t spi1_start(void *arg, uint32_t flags) { esp_err_t ret = ESP_OK; + app_func_arg_t* ctx = (app_func_arg_t*)arg; + ctx->start_flags = flags; + /** * There are three ways for ESP Flash API lock: * 1. spi bus lock, this is used when SPI1 is shared with GPSPI Master Driver @@ -136,13 +146,28 @@ static IRAM_ATTR esp_err_t spi1_start(void *arg) } #endif // CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND - on_spi_acquired((app_func_arg_t*)arg); +#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ + if (flags & ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ) { + esp_flash_freq_limit_cb(); + } +#endif + + on_spi_acquired(ctx); return ret; } static IRAM_ATTR esp_err_t spi1_end(void *arg) { esp_err_t ret = ESP_OK; + app_func_arg_t* ctx = (app_func_arg_t*)arg; + + // Call freq_limit_unlock if needed, before releasing the lock +#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ + uint32_t flags = ctx->start_flags; + if (flags & ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ) { + esp_flash_freq_unlimit_cb(); + } +#endif /** * There are three ways for ESP Flash API lock, see `spi1_start` @@ -166,7 +191,7 @@ static IRAM_ATTR esp_err_t spi1_end(void *arg) } #endif // CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND - on_spi_released((app_func_arg_t*)arg); + on_spi_released(ctx); return ret; } diff --git a/components/spi_flash/spi_flash_os_func_noos.c b/components/spi_flash/spi_flash_os_func_noos.c index e65efd6c1b..0ba7c006c5 100644 --- a/components/spi_flash/spi_flash_os_func_noos.c +++ b/components/spi_flash/spi_flash_os_func_noos.c @@ -15,7 +15,7 @@ #include "hal/cache_ll.h" #include "soc/soc_caps.h" -static IRAM_ATTR esp_err_t start(void *arg) +static IRAM_ATTR esp_err_t start(void *arg, uint32_t flags) { #if SOC_BRANCH_PREDICTOR_SUPPORTED //branch predictor will start cache request as well diff --git a/components/spi_flash/test_apps/.build-test-rules.yml b/components/spi_flash/test_apps/.build-test-rules.yml index b45c66fc93..67c68fcb10 100644 --- a/components/spi_flash/test_apps/.build-test-rules.yml +++ b/components/spi_flash/test_apps/.build-test-rules.yml @@ -24,6 +24,15 @@ components/spi_flash/test_apps/esp_flash_blockdev: depends_components: - spi_flash +components/spi_flash/test_apps/esp_flash_freq_limit: + enable: + - if: IDF_TARGET == "esp32c5" + depends_components: + - spi_flash + - esp_pm + - esp_driver_gptimer + - esp_hw_support + components/spi_flash/test_apps/esp_flash_stress: disable: - if: IDF_TARGET == "esp32h4" diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/CMakeLists.txt b/components/spi_flash/test_apps/esp_flash_freq_limit/CMakeLists.txt new file mode 100644 index 0000000000..69536d7667 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/CMakeLists.txt @@ -0,0 +1,17 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.22) + +set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components") + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. We also depend on +# esptool_py as we set CONFIG_ESPTOOLPY_... options. +set(COMPONENTS main esptool_py) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +project(test_esp_flash_freq_limit) + +message(STATUS "Checking memspi registers are not read-write by half-word") +include($ENV{IDF_PATH}/tools/ci/check_register_rw_half_word.cmake) +check_register_rw_half_word(SOC_MODULES "spi_mem*" "spi1_mem*" + HAL_MODULES "spimem_flash") diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/README.md b/components/spi_flash/test_apps/esp_flash_freq_limit/README.md new file mode 100644 index 0000000000..1d5f1c0a57 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-C5 | +| ----------------- | -------- | diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/main/CMakeLists.txt b/components/spi_flash/test_apps/esp_flash_freq_limit/main/CMakeLists.txt new file mode 100644 index 0000000000..801934c79b --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/main/CMakeLists.txt @@ -0,0 +1,9 @@ +set(srcs "test_app_main.c" + "test_esp_flash_freq_limit.c") + +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE +idf_component_register(SRCS ${srcs} + PRIV_REQUIRES unity test_utils spi_flash esp_pm bootloader_support freertos esp_timer + esp_driver_gptimer esp_hw_support esp_psram + WHOLE_ARCHIVE) diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_app_main.c b/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_app_main.c new file mode 100644 index 0000000000..888d52aeab --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_app_main.c @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_utils.h" + +#define TEST_MEMORY_LEAK_THRESHOLD (700) + +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +void tearDown(void) +{ + unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD); +} + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_esp_flash_freq_limit.c b/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_esp_flash_freq_limit.c new file mode 100644 index 0000000000..c76fa62508 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_esp_flash_freq_limit.c @@ -0,0 +1,1250 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" + +#if CONFIG_IDF_TARGET_ESP32C5 + +#include +#include +#include +#include +#include "unity.h" +#include "esp_flash.h" +#include "soc/rtc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_pm.h" +#include "driver/gptimer.h" +#include "esp_private/esp_clk.h" +#include "esp_log.h" +#include "test_utils.h" +#include "esp_partition.h" +#include "spi_flash_mmap.h" // For SPI_FLASH_SEC_SIZE + +#define TAG "test_freq_limit" + +/** + * Note: All tests in this file do NOT enable flash encryption. + * Even without encryption enabled, esp_flash_write_encrypted() will still trigger + * frequency switching (which is what we're testing). + * This test does not verify the correctness of encrypted writes. + */ + +// Use test data partition for flash operations +#define TEST_DATA_SIZE 64 +#define FREQ_LIMIT_MHZ 160 +#define MHZ (1000000) + +static uint8_t test_write_buf[TEST_DATA_SIZE]; +static uint32_t s_test_flash_offset = 0; // Will be initialized from test partition +static uint32_t s_test_flash_offset_area_a = 0; // Area A: for normal write/read test +static uint32_t s_test_flash_offset_area_b = 0; // Area B: for encrypted write test (no verification) + +// Note: No erase needed for these tests. +// The tests focus on frequency behavior during esp_flash_write_encrypted, not data integrity or encryption functionality. +// esp_flash_write_encrypted overwrites existing data. +// Erase operations are blocking and may disable interrupts/task switching, potentially interfering with concurrency test scenarios. + +static uint32_t get_cpu_freq_mhz(void) +{ + rtc_cpu_freq_config_t config; + rtc_clk_cpu_freq_get_config(&config); + return config.freq_mhz; +} + +static void verify_freq_restored(uint32_t original_freq) +{ + uint32_t freq = get_cpu_freq_mhz(); + TEST_ASSERT_EQUAL_UINT32(original_freq, freq); +} + +static esp_flash_t* get_test_flash_chip(void) +{ + // Initialize test flash offset from test data partition if not already initialized + if (s_test_flash_offset == 0) { + const esp_partition_t *test_part = get_test_data_partition(); + TEST_ASSERT_NOT_NULL(test_part); + s_test_flash_offset = test_part->address; + // Area A: start from partition start + s_test_flash_offset_area_a = test_part->address; + // Area B: start from partition start + TEST_DATA_SIZE (aligned to sector) + uint32_t sector_aligned_size = ((TEST_DATA_SIZE + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE) * SPI_FLASH_SEC_SIZE; + s_test_flash_offset_area_b = test_part->address + sector_aligned_size; + ESP_LOGI(TAG, "Using test partition at offset 0x%x, size 0x%x", + test_part->address, test_part->size); + ESP_LOGI(TAG, "Area A: 0x%x, Area B: 0x%x", s_test_flash_offset_area_a, s_test_flash_offset_area_b); + } + return esp_flash_default_chip; +} + +static void prepare_test_data(void) +{ + for (int i = 0; i < TEST_DATA_SIZE; i++) { + test_write_buf[i] = (uint8_t)(i & 0xFF); + } +} + +#if CONFIG_PM_ENABLE +// Test frequency configurations: dynamically built based on CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ +// Only include frequencies that are <= CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ +#if CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ >= 240 +// Support 240MHz: test default (0), 240MHz, 160MHz, 80MHz +static const uint32_t s_test_max_freqs[] = {0, 240, 160, 80}; +static const uint32_t s_test_min_freqs[] = {0, 80, 80, 80}; +#elif CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ >= 160 +// Support up to 160MHz: test default (0), 160MHz, 80MHz +static const uint32_t s_test_max_freqs[] = {0, 160, 80}; +static const uint32_t s_test_min_freqs[] = {0, 80, 80}; +#elif CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ >= 80 +// Support up to 80MHz: test default (0), 80MHz +static const uint32_t s_test_max_freqs[] = {0, 80}; +static const uint32_t s_test_min_freqs[] = {0, 80}; +#else +// Support lower frequencies: test default (0) only +static const uint32_t s_test_max_freqs[] = {0}; +static const uint32_t s_test_min_freqs[] = {0}; +#endif +#define NUM_TEST_CONFIGS (sizeof(s_test_max_freqs) / sizeof(s_test_max_freqs[0])) + +/** + * Setup PM configuration for testing + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + * @return Expected frequency after setup (max_freq_mhz if configured, CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ if max_freq_mhz is 0) + */ +static uint32_t setup_pm_config(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + uint32_t actual_max_freq = max_freq_mhz; + uint32_t actual_min_freq = min_freq_mhz; + + if (max_freq_mhz == 0) { + // Configure with default values (same as CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ) + actual_max_freq = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; + if (min_freq_mhz == 0) { + // Use XTAL frequency as min when not specified + actual_min_freq = esp_clk_xtal_freq() / MHZ; + } + } + + // Configure PM with specific frequencies + esp_pm_config_t pm_config = { + .max_freq_mhz = actual_max_freq, + .min_freq_mhz = actual_min_freq, + .light_sleep_enable = false + }; + TEST_ESP_OK(esp_pm_configure(&pm_config)); + + // Wait a bit for PM to stabilize + vTaskDelay(pdMS_TO_TICKS(100)); + + uint32_t current_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "PM configured: max=%"PRIu32" MHz, min=%"PRIu32" MHz, current=%"PRIu32" MHz", + actual_max_freq, actual_min_freq, current_freq); + + // Verify current frequency matches max_freq_mhz (since no locks are held) + TEST_ASSERT_EQUAL_UINT32(actual_max_freq, current_freq); + return actual_max_freq; +} +#endif // CONFIG_PM_ENABLE + +// Note: We do NOT erase flash before write_encrypted operations in these tests. +// Reasons: +// 1. These tests focus on frequency behavior, not data integrity or encryption functionality. +// 2. We do not perform data verification (no read_encrypted or data comparison). +// 3. esp_flash_write_encrypted will overwrite existing data anyway. +// 4. Erase operations are blocking and may disable interrupts/task switching, which could +// interfere with concurrent testing scenarios (interrupts, task scheduling). +// 5. Skipping erase operations simplifies the tests and avoids potential timing issues. + +/** + * Helper function: Test frequency limit basic behavior with PM configuration + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_freq_limit_basic_behavior_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + + // Step 1: Erase and write known data to Area A, then verify + uint32_t erase_size = ((TEST_DATA_SIZE + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE) * SPI_FLASH_SEC_SIZE; + ESP_LOGI(TAG, "Step 1: Erasing Area A at 0x%x, size %"PRIu32" bytes", s_test_flash_offset_area_a, erase_size); + esp_err_t ret = esp_flash_erase_region(chip, s_test_flash_offset_area_a, erase_size); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Flash erase failed (0x%x), continuing anyway.", ret); + } else { + ESP_LOGI(TAG, "Area A erased successfully"); + } + + ESP_LOGI(TAG, "Writing known data to Area A at 0x%x", s_test_flash_offset_area_a); + ret = esp_flash_write(chip, test_write_buf, s_test_flash_offset_area_a, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + ESP_LOGI(TAG, "Data written to Area A successfully"); + + // Verify written data in Area A + uint8_t read_buf[TEST_DATA_SIZE]; + ESP_LOGI(TAG, "Reading back data from Area A at 0x%x", s_test_flash_offset_area_a); + ret = esp_flash_read(chip, read_buf, s_test_flash_offset_area_a, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + ESP_LOGI(TAG, "Verifying data integrity in Area A"); + ESP_LOG_BUFFER_HEXDUMP(TAG, test_write_buf, TEST_DATA_SIZE, ESP_LOG_INFO); + ESP_LOG_BUFFER_HEXDUMP(TAG, read_buf, TEST_DATA_SIZE, ESP_LOG_INFO); + TEST_ASSERT_EQUAL_HEX8_ARRAY(test_write_buf, read_buf, TEST_DATA_SIZE); + ESP_LOGI(TAG, "Area A data verification passed"); + + // Step 2: Encrypted write to Area B (no verification) + uint32_t freq_before = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Step 2: Frequency before write_encrypted: %"PRIu32" MHz", freq_before); + ESP_LOGI(TAG, "About to call esp_flash_write_encrypted to Area B, addr=0x%x, len=%d", s_test_flash_offset_area_b, TEST_DATA_SIZE); + // Note: This test does not verify the correctness of encrypted writes + ret = esp_flash_write_encrypted(chip, s_test_flash_offset_area_b, test_write_buf, TEST_DATA_SIZE); + ESP_LOGI(TAG, "esp_flash_write_encrypted returned: %d", ret); + TEST_ESP_OK(ret); + + // Wait for flash operation to complete + vTaskDelay(pdMS_TO_TICKS(10)); + + // Frequency should be restored after the call completes (or unchanged if already <= 160MHz) + uint32_t freq_after = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency after write_encrypted: %"PRIu32" MHz", freq_after); + + // Verify frequency behavior: + // - If original frequency > 160MHz: should be restored to original + // - If original frequency <= 160MHz: should remain unchanged + if (original_freq > FREQ_LIMIT_MHZ) { + verify_freq_restored(original_freq); + ESP_LOGI(TAG, "Frequency restored to original %"PRIu32" MHz (was > 160MHz)", original_freq); + } else { + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + ESP_LOGI(TAG, "Frequency unchanged at %"PRIu32" MHz (was <= 160MHz)", original_freq); + } + + // Step 3: Read and verify Area A again to confirm read API still works after encrypted write + ESP_LOGI(TAG, "Step 3: Reading Area A again at 0x%x to verify read API still works", s_test_flash_offset_area_a); + ret = esp_flash_read(chip, read_buf, s_test_flash_offset_area_a, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + ESP_LOGI(TAG, "Verifying data integrity in Area A after encrypted write"); + ESP_LOG_BUFFER_HEXDUMP(TAG, test_write_buf, TEST_DATA_SIZE, ESP_LOG_INFO); + ESP_LOG_BUFFER_HEXDUMP(TAG, read_buf, TEST_DATA_SIZE, ESP_LOG_INFO); + TEST_ASSERT_EQUAL_HEX8_ARRAY(test_write_buf, read_buf, TEST_DATA_SIZE); + ESP_LOGI(TAG, "Area A data verification passed after encrypted write - read API is still functional"); +} + +/** + * Test: Frequency limit: basic behavior + * Target: Verify that esp_flash_write_encrypted properly limits CPU frequency to <= 160MHz during execution + * and restores the original frequency after completion, with different PM configurations. + * Expected: + * - If original frequency > 160MHz: frequency should be limited to 160MHz during write, restored to original after + * - If original frequency <= 160MHz: frequency should remain unchanged + * - Written data should be verified by reading it back + */ +TEST_CASE("Frequency limit: basic behavior", "[esp_flash_freq_limit]") +{ +#if CONFIG_PM_ENABLE + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_freq_limit_basic_behavior_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +#else + test_freq_limit_basic_behavior_common(0, 0); +#endif +} + +/** + * Helper function: Test read Flash ID after write_encrypted with PM configuration + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_read_flash_id_after_write_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + // Read Flash ID before write_encrypted + uint32_t flash_id_before = 0; + ESP_LOGI(TAG, "Reading Flash ID before write_encrypted"); + esp_err_t ret = esp_flash_read_id(chip, &flash_id_before); + TEST_ESP_OK(ret); + ESP_LOGI(TAG, "Flash ID before write_encrypted: 0x%08"PRIx32, flash_id_before); + TEST_ASSERT_NOT_EQUAL(0, flash_id_before); + TEST_ASSERT_NOT_EQUAL(0xFFFFFFFF, flash_id_before); + + prepare_test_data(); + + // Erase the test region before writing + // esp_flash_erase_region requires size to be aligned to sector size (4KB) + // Calculate the number of sectors needed (round up) + uint32_t erase_size = ((TEST_DATA_SIZE + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE) * SPI_FLASH_SEC_SIZE; + ESP_LOGI(TAG, "Erasing flash region at 0x%x, size %d (aligned to %"PRIu32" bytes)", + s_test_flash_offset, TEST_DATA_SIZE, erase_size); + ret = esp_flash_erase_region(chip, s_test_flash_offset, erase_size); + // If erase fails, it might be because the region is already erased or in use + // Try to continue anyway - write_encrypted will overwrite the data + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Flash erase failed (0x%x), continuing anyway", ret); + } else { + ESP_LOGI(TAG, "Flash region erased successfully"); + } + + // Perform write_encrypted operation + uint32_t freq_before = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency before write_encrypted: %"PRIu32" MHz", freq_before); + + ESP_LOGI(TAG, "Calling esp_flash_write_encrypted, addr=0x%x, len=%d", s_test_flash_offset, TEST_DATA_SIZE); + ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Verify frequency is restored + uint32_t freq_after = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency after write_encrypted: %"PRIu32" MHz", freq_after); + if (original_freq > FREQ_LIMIT_MHZ) { + verify_freq_restored(original_freq); + } else { + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + } + + // Read Flash ID after write_encrypted to verify driver is still functional + // This uses a real read operation that accesses the Flash hardware + uint32_t flash_id_after = 0; + ESP_LOGI(TAG, "Reading Flash ID after write_encrypted"); + ret = esp_flash_read_id(chip, &flash_id_after); + TEST_ESP_OK(ret); + ESP_LOGI(TAG, "Flash ID after write_encrypted: 0x%08"PRIx32, flash_id_after); + + // Verify Flash ID matches (driver should still be functional) + TEST_ASSERT_EQUAL_UINT32(flash_id_before, flash_id_after); + ESP_LOGI(TAG, "Flash ID verification passed - driver is still functional"); +} + +/** + * Helper function: Test read Flash ID only (no write operations) + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +#if CONFIG_PM_ENABLE +static void test_read_flash_id_only_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + + setup_pm_config(max_freq_mhz, min_freq_mhz); + + uint32_t current_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Current frequency: %"PRIu32" MHz", current_freq); + + // Read Flash ID multiple times to verify consistency + // Note: At high frequencies (240MHz, 160MHz), Flash timing may not be properly configured, + // causing incorrect Flash ID reads. We need to temporarily lower CPU frequency to 80MHz + // to read Flash ID correctly, then restore original frequency. + uint32_t flash_id = 0; + + // If CPU frequency is high (>= 160MHz), temporarily lower CPU frequency to 80MHz for reading Flash ID + uint32_t original_max_freq = 0; + uint32_t original_min_freq = 0; + bool freq_was_lowered = false; + if (current_freq >= 160) { + ESP_LOGI(TAG, "CPU frequency is %"PRIu32" MHz, temporarily lowering to 80MHz for Flash ID read", current_freq); + + // Save current PM config + esp_pm_config_t pm_config; + esp_pm_get_configuration(&pm_config); + original_max_freq = pm_config.max_freq_mhz; + original_min_freq = pm_config.min_freq_mhz; + + // Temporarily set max and min to 80MHz + esp_pm_config_t temp_pm_config = { + .max_freq_mhz = 80, + .min_freq_mhz = 80, + .light_sleep_enable = false + }; + TEST_ESP_OK(esp_pm_configure(&temp_pm_config)); + vTaskDelay(pdMS_TO_TICKS(100)); // Wait for frequency to stabilize + + uint32_t temp_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency lowered to %"PRIu32" MHz", temp_freq); + TEST_ASSERT_EQUAL_UINT32(80, temp_freq); + freq_was_lowered = true; + } + + extern esp_flash_t *esp_flash_default_chip; + uint32_t expect_chip_id = esp_flash_default_chip->chip_id; + for (int i = 0; i < 5; i++) { + ESP_LOGI(TAG, "Reading Flash ID (attempt %d)", i + 1); + esp_err_t ret = esp_flash_read_id(chip, &flash_id); + TEST_ESP_OK(ret); + uint32_t read_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Flash ID at %"PRIu32" MHz: 0x%08"PRIx32, read_freq, flash_id); + TEST_ASSERT_EQUAL_UINT32(expect_chip_id, flash_id); + vTaskDelay(pdMS_TO_TICKS(10)); + } + + // Restore original frequency if we lowered it + if (freq_was_lowered) { + ESP_LOGI(TAG, "Restoring original frequency: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + original_max_freq, original_min_freq); + esp_pm_config_t restore_pm_config = { + .max_freq_mhz = original_max_freq, + .min_freq_mhz = original_min_freq, + .light_sleep_enable = false + }; + TEST_ESP_OK(esp_pm_configure(&restore_pm_config)); + vTaskDelay(pdMS_TO_TICKS(100)); // Wait for frequency to stabilize + + uint32_t restored_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency restored to %"PRIu32" MHz", restored_freq); + TEST_ASSERT_EQUAL_UINT32(original_max_freq, restored_freq); + } + + ESP_LOGI(TAG, "Final Flash ID: 0x%08"PRIx32, flash_id); + TEST_ASSERT_EQUAL_HEX32(expect_chip_id, flash_id); +} + +/** + * Test: Frequency limit: read Flash ID only + * Target: Verify Flash ID reading at different frequencies + * Expected: + * - Flash ID should be readable at all frequencies + * - Flash ID should be same as the default chip id + */ +TEST_CASE("Frequency limit: read Flash ID only", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing read Flash ID only with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_read_flash_id_only_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +} +#endif // CONFIG_PM_ENABLE + +/** + * Test: Frequency limit: read Flash ID after write_encrypted + * Target: Verify that the Flash driver remains functional after esp_flash_write_encrypted + * by reading the Flash ID using a real read operation. + * Expected: + * - Flash ID should be readable before and after write_encrypted + * - Flash ID should match before and after (driver state should be consistent) + * - This verifies that the driver is still in a normal state after write_encrypted + */ +TEST_CASE("Frequency limit: read Flash ID after write_encrypted", "[esp_flash_freq_limit]") +{ +#if CONFIG_PM_ENABLE + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing read Flash ID with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_read_flash_id_after_write_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +#else + test_read_flash_id_after_write_common(0, 0); +#endif +} + +/** + * Helper function: Test multiple write_encrypted calls with PM configuration + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_multiple_write_encrypted_calls_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + // Note: No erase needed - see comment above erase_test_region function + + for (int i = 0; i < 3; i++) { + uint32_t offset = s_test_flash_offset + (i * TEST_DATA_SIZE); + uint32_t freq_before = get_cpu_freq_mhz(); + + esp_err_t ret = esp_flash_write_encrypted(chip, offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + uint32_t freq_after = get_cpu_freq_mhz(); + verify_freq_restored(original_freq); + + ESP_LOGI(TAG, "Iteration %d: freq_before=%"PRIu32" MHz, freq_after=%"PRIu32" MHz", i, freq_before, freq_after); + } +} + +/** + * Test: Frequency limit: multiple write_encrypted calls + * Target: Verify that frequency limiting works correctly across multiple consecutive write_encrypted calls. + * Expected: Frequency should be limited during each call and restored after each call completes. + */ +TEST_CASE("Frequency limit: multiple write_encrypted calls", "[esp_flash_freq_limit]") +{ +#if CONFIG_PM_ENABLE + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_multiple_write_encrypted_calls_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +#else + test_multiple_write_encrypted_calls_common(0, 0); +#endif +} + +/** + * Test: Frequency limit: zero length write + * Target: Verify that zero-length write_encrypted calls do not trigger frequency limiting (early return). + * Expected: Frequency should remain unchanged when length is 0. + */ +TEST_CASE("Frequency limit: zero length write", "[esp_flash_freq_limit]") +{ + esp_flash_t* chip = get_test_flash_chip(); +#if CONFIG_PM_ENABLE + // Initialize PM config to ensure frequency is at max + setup_pm_config(0, 0); // Use default max frequency +#endif + uint32_t original_freq = get_cpu_freq_mhz(); + + // Verify initial frequency matches CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, original_freq); + + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, 0); + TEST_ESP_OK(ret); + + uint32_t freq_after = get_cpu_freq_mhz(); + + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + ESP_LOGI(TAG, "Zero length write: freq unchanged (%"PRIu32" MHz)", freq_after); +} + +/** + * Test: Frequency limit: misaligned address + * Target: Verify that frequency limiting is not triggered when write fails due to misaligned address. + * Expected: Function should return ESP_ERR_INVALID_ARG and frequency should remain unchanged. + */ +TEST_CASE("Frequency limit: misaligned address", "[esp_flash_freq_limit]") +{ + esp_flash_t* chip = get_test_flash_chip(); +#if CONFIG_PM_ENABLE + // Initialize PM config to ensure frequency is at max + setup_pm_config(0, 0); // Use default max frequency +#endif + uint32_t original_freq = get_cpu_freq_mhz(); + + // Verify initial frequency matches CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, original_freq); + + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset + 1, test_write_buf, TEST_DATA_SIZE); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); + + uint32_t freq_after = get_cpu_freq_mhz(); + + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + ESP_LOGI(TAG, "Misaligned address: freq unchanged (%"PRIu32" MHz)", freq_after); +} + +/** + * Test: Frequency limit: misaligned length + * Target: Verify that frequency limiting is not triggered when write fails due to misaligned length. + * Expected: Function should return ESP_ERR_INVALID_SIZE and frequency should remain unchanged. + */ +TEST_CASE("Frequency limit: misaligned length", "[esp_flash_freq_limit]") +{ + esp_flash_t* chip = get_test_flash_chip(); +#if CONFIG_PM_ENABLE + // Initialize PM config to ensure frequency is at max + setup_pm_config(0, 0); // Use default max frequency +#endif + uint32_t original_freq = get_cpu_freq_mhz(); + + // Verify initial frequency matches CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, original_freq); + + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE + 1); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_SIZE, ret); + + uint32_t freq_after = get_cpu_freq_mhz(); + + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + ESP_LOGI(TAG, "Misaligned length: freq unchanged (%"PRIu32" MHz)", freq_after); +} + +/** + * Test: Frequency limit: write failure recovery + * Target: Verify that frequency is properly restored even when write_encrypted fails with an error. + * Expected: Function should return error and frequency should be restored to original value. + */ +TEST_CASE("Frequency limit: write failure recovery", "[esp_flash_freq_limit]") +{ + esp_flash_t* chip = get_test_flash_chip(); +#if CONFIG_PM_ENABLE + // Initialize PM config to ensure frequency is at max + setup_pm_config(0, 0); // Use default max frequency +#endif + uint32_t original_freq = get_cpu_freq_mhz(); + + // Verify initial frequency matches CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, original_freq); + + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset + 1, test_write_buf, TEST_DATA_SIZE); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); + + uint32_t freq_after = get_cpu_freq_mhz(); + + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + ESP_LOGI(TAG, "Write failure: freq restored to original (%"PRIu32" MHz)", freq_after); +} + +/** + * Helper function: Test multiple write_encrypted operations with PM configuration + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_multiple_write_encrypted_operations_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + // Note: No erase needed - see comment above erase_test_region function + + // First write - frequency should be limited during call and restored after + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + verify_freq_restored(original_freq); + + // Second write - frequency should be limited during call and restored after + ret = esp_flash_write_encrypted(chip, s_test_flash_offset + TEST_DATA_SIZE, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + verify_freq_restored(original_freq); + + ESP_LOGI(TAG, "Multiple operations: freq properly restored after each (%"PRIu32" MHz)", original_freq); +} + +/** + * Test: Frequency limit: multiple write_encrypted operations + * Target: Verify that frequency limiting and restoration work correctly for multiple independent write operations. + * Expected: Frequency should be limited during each write and restored after each write completes. + */ +TEST_CASE("Frequency limit: multiple write_encrypted operations", "[esp_flash_freq_limit]") +{ +#if CONFIG_PM_ENABLE + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_multiple_write_encrypted_operations_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +#else + test_multiple_write_encrypted_operations_common(0, 0); +#endif +} + +// ============================================================================ +// Interrupt concurrency test cases +// ============================================================================ + +#if CONFIG_PM_ENABLE + +/** + * Helper function: Test CPU lock held before encrypt with PM configuration + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_cpu_lock_held_before_encrypt_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + uint32_t configured_max_freq = setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); + uint32_t configured_max_freq = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + // Note: No erase needed - see comment above erase_test_region function + + // Create and acquire CPU lock BEFORE encrypt + esp_pm_lock_handle_t cpu_lock = NULL; + TEST_ESP_OK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "test_lock", &cpu_lock)); + TEST_ASSERT_NOT_NULL(cpu_lock); + + TEST_ESP_OK(esp_pm_lock_acquire(cpu_lock)); + + // Verify frequency is at max + uint32_t freq_after_lock = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency after acquiring CPU lock: %"PRIu32" MHz", freq_after_lock); + TEST_ASSERT_EQUAL_UINT32(configured_max_freq, freq_after_lock); + + // Perform encrypt operation - frequency should still be limited to 160MHz + // even though CPU lock is held (encrypt lock has higher priority) + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Verify frequency is restored to max (CPU lock still held) + uint32_t freq_after_encrypt = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency after encrypt (lock still held): %"PRIu32" MHz", freq_after_encrypt); + TEST_ASSERT_EQUAL_UINT32(freq_after_lock, freq_after_encrypt); + + // Release CPU lock + esp_pm_lock_release(cpu_lock); + + // Verify frequency restored + verify_freq_restored(original_freq); + + // Clean up + esp_pm_lock_delete(cpu_lock); +} + +/** + * Test: Frequency limit: CPU lock held before encrypt + * Target: Verify that frequency limiting works correctly when a PM CPU lock is already held before encrypt execution. + * The encrypt frequency limit lock (PM_MODE_CPU_ENCRYPT_LOCK, limits to <= 160MHz) has higher priority than CPU lock (PM_MODE_CPU_MAX). + * Expected: Frequency should be limited to 160MHz during encrypt (even with CPU lock held), then restored to max + * after encrypt completes (CPU lock still held). + */ +TEST_CASE("Frequency limit: CPU lock held before encrypt", "[esp_flash_freq_limit]") +{ +#if CONFIG_PM_ENABLE + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_cpu_lock_held_before_encrypt_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +#else + test_cpu_lock_held_before_encrypt_common(0, 0); +#endif +} + +#endif // CONFIG_PM_ENABLE + +// ============================================================================ +// Interrupt concurrency test cases +// ============================================================================ + +// Shared state for interrupt tests +static volatile bool s_interrupt_occurred = false; +static gptimer_handle_t s_test_timer = NULL; + +#if CONFIG_PM_ENABLE +// PM lock handles for ISR tests (must be set before interrupt) +static esp_pm_lock_handle_t s_isr_pm_lock_cpu = NULL; +static esp_pm_lock_handle_t s_isr_pm_lock_apb = NULL; +static volatile bool s_isr_lock_acquired = false; +static volatile bool s_isr_lock_released = false; +#endif // CONFIG_PM_ENABLE + +// ISR callback - must be IRAM-safe (declared before use) +static bool IRAM_ATTR timer_isr_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + s_interrupt_occurred = true; + return false; +} + +#if CONFIG_PM_ENABLE + +// ISR callback for test 3: acquire and release lock in ISR +static bool IRAM_ATTR timer_isr_callback_acquire_release(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + s_interrupt_occurred = true; + + // Acquire lock in ISR + if (s_isr_pm_lock_cpu != NULL) { + esp_pm_lock_acquire(s_isr_pm_lock_cpu); + s_isr_lock_acquired = true; + // Release immediately + esp_pm_lock_release(s_isr_pm_lock_cpu); + s_isr_lock_released = true; + } + if (s_isr_pm_lock_apb != NULL) { + esp_pm_lock_acquire(s_isr_pm_lock_apb); + s_isr_lock_acquired = true; + // Release immediately + esp_pm_lock_release(s_isr_pm_lock_apb); + s_isr_lock_released = true; + } + + return false; +} + +// ISR callback for test 4: acquire lock in ISR, release after write completes +static bool IRAM_ATTR timer_isr_callback_acquire(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + s_interrupt_occurred = true; + + // Acquire lock in ISR (will be released after write completes in test code) + if (s_isr_pm_lock_cpu != NULL) { + esp_pm_lock_acquire(s_isr_pm_lock_cpu); + s_isr_lock_acquired = true; + } + if (s_isr_pm_lock_apb != NULL) { + esp_pm_lock_acquire(s_isr_pm_lock_apb); + s_isr_lock_acquired = true; + } + + return false; +} + +// ISR callback for test 5: release lock that was acquired before encrypt +static bool IRAM_ATTR timer_isr_callback_release(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + s_interrupt_occurred = true; + + // Release lock in ISR (was acquired before encrypt) + if (s_isr_pm_lock_cpu != NULL) { + esp_pm_lock_release(s_isr_pm_lock_cpu); + s_isr_lock_released = true; + } + if (s_isr_pm_lock_apb != NULL) { + esp_pm_lock_release(s_isr_pm_lock_apb); + s_isr_lock_released = true; + } + + return false; +} + +#endif // CONFIG_PM_ENABLE + +// Helper function to setup GPTimer for interrupt during encrypt +static void setup_interrupt_timer(uint32_t delay_us, gptimer_alarm_cb_t callback) +{ + // Clean up any existing timer first to avoid resource leaks + if (s_test_timer != NULL) { + gptimer_stop(s_test_timer); + gptimer_disable(s_test_timer); + gptimer_del_timer(s_test_timer); + s_test_timer = NULL; + } + + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1000000, // 1MHz, 1 tick = 1us + }; + TEST_ESP_OK(gptimer_new_timer(&timer_config, &s_test_timer)); + + gptimer_event_callbacks_t cbs = { + .on_alarm = callback, + }; + TEST_ESP_OK(gptimer_register_event_callbacks(s_test_timer, &cbs, NULL)); + TEST_ESP_OK(gptimer_enable(s_test_timer)); + + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = delay_us, + .flags.auto_reload_on_alarm = false, + }; + TEST_ESP_OK(gptimer_set_alarm_action(s_test_timer, &alarm_config)); + + s_interrupt_occurred = false; +#if CONFIG_PM_ENABLE + s_isr_lock_acquired = false; + s_isr_lock_released = false; +#endif // CONFIG_PM_ENABLE + TEST_ESP_OK(gptimer_start(s_test_timer)); +} + +static void cleanup_interrupt_timer(void) +{ + if (s_test_timer != NULL) { + gptimer_stop(s_test_timer); + gptimer_disable(s_test_timer); + gptimer_del_timer(s_test_timer); + s_test_timer = NULL; + } +} + + +/** + * Helper function: Test interrupt during encrypt with PM configuration + * Common test logic for both PM enabled and disabled scenarios + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_interrupt_during_encrypt_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + prepare_test_data(); + + // Setup interrupt to trigger during encrypt (ensure it happens before write completes) + setup_interrupt_timer(50, timer_isr_callback); // 50us delay - should trigger during write + + // Perform encrypt operation - interrupt should occur during this call + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Wait for interrupt to be processed + vTaskDelay(pdMS_TO_TICKS(10)); + + // Verify interrupt occurred + TEST_ASSERT_TRUE(s_interrupt_occurred); + + // Verify frequency was restored + verify_freq_restored(original_freq); + + cleanup_interrupt_timer(); +} + +/** + * Test: Frequency limit: interrupt during encrypt (PM disabled) + * Target: Verify that frequency limiting works correctly when an interrupt occurs during encrypt execution with PM disabled. + * Expected: Interrupt should occur during write, frequency should be limited during write, and restored after write completes. + */ +TEST_CASE("Frequency limit: interrupt during encrypt (PM disabled)", "[esp_flash_freq_limit]") +{ + test_interrupt_during_encrypt_common(0, 0); +} + +#if CONFIG_PM_ENABLE +/** + * Test: Frequency limit: interrupt during encrypt (PM enabled) + * Target: Verify that frequency limiting works correctly when an interrupt occurs during encrypt execution with PM enabled. + * Expected: Interrupt should occur during write, frequency should be limited during write, and restored after write completes. + */ +TEST_CASE("Frequency limit: interrupt during encrypt (PM enabled)", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_interrupt_during_encrypt_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +/** + * Helper function: Test interrupt with PM lock acquire/release with PM configuration + * @param lock_type Lock type (CPU or APB) + * @param lock_name Lock name for logging + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_interrupt_with_pm_lock_acquire_release_common(esp_pm_lock_type_t lock_type, const char* lock_name, uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + prepare_test_data(); + + // Create PM lock + esp_pm_lock_handle_t pm_lock = NULL; + TEST_ESP_OK(esp_pm_lock_create(lock_type, 0, "isr_test", &pm_lock)); + TEST_ASSERT_NOT_NULL(pm_lock); + + // Set lock handle for ISR callback + if (lock_type == ESP_PM_CPU_FREQ_MAX) { + s_isr_pm_lock_cpu = pm_lock; + s_isr_pm_lock_apb = NULL; + } else { + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = pm_lock; + } + + // Setup interrupt to trigger during encrypt (50us delay) + // ISR will acquire and release lock + setup_interrupt_timer(50, timer_isr_callback_acquire_release); + + // Perform encrypt operation - interrupt will trigger PM lock acquire/release in ISR + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Wait for interrupt to be processed + vTaskDelay(pdMS_TO_TICKS(10)); + + // Verify interrupt occurred and lock operations happened + TEST_ASSERT_TRUE(s_interrupt_occurred); + TEST_ASSERT_TRUE(s_isr_lock_acquired); + TEST_ASSERT_TRUE(s_isr_lock_released); + + // Verify frequency was restored (pm_impl.c assertions will verify frequency during encrypt) + verify_freq_restored(original_freq); + + // Cleanup + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = NULL; + esp_pm_lock_delete(pm_lock); + cleanup_interrupt_timer(); +} + +/** + * Test: Frequency limit: interrupt with CPU lock acquire/release + * Target: Same as test_interrupt_with_pm_lock_acquire_release, but specifically for CPU lock (ESP_PM_CPU_FREQ_MAX). + */ +TEST_CASE("Frequency limit: interrupt with CPU lock acquire/release", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing CPU lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_interrupt_with_pm_lock_acquire_release_common(ESP_PM_CPU_FREQ_MAX, "CPU", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +/** + * Test: Frequency limit: interrupt with APB lock acquire/release + * Target: Same as test_interrupt_with_pm_lock_acquire_release, but specifically for APB lock (ESP_PM_APB_FREQ_MAX). + */ +TEST_CASE("Frequency limit: interrupt with APB lock acquire/release", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing APB lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_interrupt_with_pm_lock_acquire_release_common(ESP_PM_APB_FREQ_MAX, "APB", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +/** + * Helper function: Test interrupt with PM lock held after ISR with PM configuration + * @param lock_type Lock type (CPU or APB) + * @param lock_name Lock name for logging + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_interrupt_with_pm_lock_held_common(esp_pm_lock_type_t lock_type, const char* lock_name, uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + uint32_t configured_max_freq = setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); + uint32_t configured_max_freq = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + prepare_test_data(); + + // Create PM lock + esp_pm_lock_handle_t pm_lock = NULL; + TEST_ESP_OK(esp_pm_lock_create(lock_type, 0, "hold_test", &pm_lock)); + TEST_ASSERT_NOT_NULL(pm_lock); + + // Set lock handle for ISR callback + if (lock_type == ESP_PM_CPU_FREQ_MAX) { + s_isr_pm_lock_cpu = pm_lock; + s_isr_pm_lock_apb = NULL; + } else { + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = pm_lock; + } + + // Setup interrupt to trigger during encrypt + // ISR will acquire lock, we'll release it after write completes + setup_interrupt_timer(50, timer_isr_callback_acquire); + + // Perform encrypt operation - interrupt will trigger PM lock acquire in ISR + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Wait for interrupt to be processed + vTaskDelay(pdMS_TO_TICKS(10)); + + // Verify interrupt occurred and lock was acquired + TEST_ASSERT_TRUE(s_interrupt_occurred); + TEST_ASSERT_TRUE(s_isr_lock_acquired); + + // Check frequency after write completes but before releasing lock + // Note: PM_MODE_CPU_ENCRYPT_LOCK lock (frequency limit) is released when write_encrypted returns, + // so frequency should be restored to the mode corresponding to the lock held in ISR + // If ISR acquired ESP_PM_CPU_FREQ_MAX lock, frequency should be configured max freq (PM_MODE_CPU_MAX) + // If ISR acquired ESP_PM_APB_FREQ_MAX lock, frequency depends on PM configuration + uint32_t freq_after_write = get_cpu_freq_mhz(); + uint32_t expected_freq; + if (lock_type == ESP_PM_CPU_FREQ_MAX) { + // CPU lock held -> PM_MODE_CPU_MAX -> configured max frequency + expected_freq = configured_max_freq; + } else { + // APB lock held -> depends on PM config, but typically >= 80MHz + // For simplicity, just verify frequency is restored (not limited to 160MHz) + expected_freq = configured_max_freq; // APB lock held, should be restored to configured max freq + } + ESP_LOGI(TAG, "Frequency after write (lock still held): %"PRIu32" MHz (expected: %"PRIu32" MHz)", + freq_after_write, expected_freq); + TEST_ASSERT_EQUAL_UINT32(expected_freq, freq_after_write); + + // Release lock after write completes (as per requirement) + esp_pm_lock_release(pm_lock); + + // Verify frequency was restored after lock release + verify_freq_restored(original_freq); + + // Cleanup + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = NULL; + esp_pm_lock_delete(pm_lock); + cleanup_interrupt_timer(); +} + +/** + * Test: Frequency limit: interrupt with CPU lock held after ISR + * Target: Same as test_interrupt_with_pm_lock_held, but specifically for CPU lock (ESP_PM_CPU_FREQ_MAX). + */ +TEST_CASE("Frequency limit: interrupt with CPU lock held after ISR", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing CPU lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_interrupt_with_pm_lock_held_common(ESP_PM_CPU_FREQ_MAX, "CPU", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +/** + * Test: Frequency limit: interrupt with APB lock held after ISR + * Target: Same as test_interrupt_with_pm_lock_held, but specifically for APB lock (ESP_PM_APB_FREQ_MAX). + */ +TEST_CASE("Frequency limit: interrupt with APB lock held after ISR", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing APB lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_interrupt_with_pm_lock_held_common(ESP_PM_APB_FREQ_MAX, "APB", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +/** + * Helper function: Test PM lock released in ISR with PM configuration + * @param lock_type Lock type (CPU or APB) + * @param lock_name Lock name for logging + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_lock_released_in_isr_common(esp_pm_lock_type_t lock_type, const char* lock_name, uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + prepare_test_data(); + + // Create and acquire PM lock BEFORE encrypt + esp_pm_lock_handle_t pm_lock = NULL; + TEST_ESP_OK(esp_pm_lock_create(lock_type, 0, "release_test", &pm_lock)); + TEST_ASSERT_NOT_NULL(pm_lock); + TEST_ESP_OK(esp_pm_lock_acquire(pm_lock)); + + // Set lock handle for ISR callback + if (lock_type == ESP_PM_CPU_FREQ_MAX) { + s_isr_pm_lock_cpu = pm_lock; + s_isr_pm_lock_apb = NULL; + } else { + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = pm_lock; + } + + // Setup interrupt to trigger during encrypt + // ISR will release the lock that was acquired before encrypt + setup_interrupt_timer(50, timer_isr_callback_release); + + // Perform encrypt operation - lock is already held, ISR will release it + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Wait for interrupt to be processed + vTaskDelay(pdMS_TO_TICKS(10)); + + // Verify interrupt occurred and lock was released + TEST_ASSERT_TRUE(s_interrupt_occurred); + TEST_ASSERT_TRUE(s_isr_lock_released); + + // Check frequency after write completes and after ISR released lock + // Frequency should be restored because lock was released in ISR + uint32_t freq_after_write = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency after write (lock released in ISR): %"PRIu32" MHz (original: %"PRIu32" MHz)", + freq_after_write, original_freq); + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after_write); + + // Verify frequency was restored (pm_impl.c assertions will verify frequency during encrypt) + verify_freq_restored(original_freq); + + // Cleanup + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = NULL; + esp_pm_lock_delete(pm_lock); + cleanup_interrupt_timer(); +} + +TEST_CASE("Frequency limit: CPU lock released in ISR", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing CPU lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_lock_released_in_isr_common(ESP_PM_CPU_FREQ_MAX, "CPU", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +TEST_CASE("Frequency limit: APB lock released in ISR", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing APB lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_lock_released_in_isr_common(ESP_PM_APB_FREQ_MAX, "APB", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} +#endif // CONFIG_PM_ENABLE + + +#endif // CONFIG_IDF_TARGET_ESP32C5 diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/partitions.csv b/components/spi_flash/test_apps/esp_flash_freq_limit/partitions.csv new file mode 100644 index 0000000000..9903f621fb --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +factory, 0, 0, 0x10000, 1M +flash_test, data, fat, , 700K diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/pytest_esp_flash_freq_limit.py b/components/spi_flash/test_apps/esp_flash_freq_limit/pytest_esp_flash_freq_limit.py new file mode 100644 index 0000000000..279ad61241 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/pytest_esp_flash_freq_limit.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'pm_disabled_240mhz', + 'pm_disabled_160mhz', + 'pm_enabled_240mhz', + 'pm_enabled_160mhz', + 'pm_disabled_240mhz_xip_psram', + 'pm_enabled_240mhz_xip_psram', + ], + indirect=True, +) +@idf_parametrize('target', ['esp32c5'], indirect=['target']) +def test_esp_flash_freq_limit(dut: Dut) -> None: + dut.run_all_single_board_cases(group='esp_flash_freq_limit', timeout=10) diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_160mhz b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_160mhz new file mode 100644 index 0000000000..0314f5699b --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_160mhz @@ -0,0 +1,2 @@ +CONFIG_PM_ENABLE=n +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz new file mode 100644 index 0000000000..478b872a3a --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz @@ -0,0 +1,2 @@ +CONFIG_PM_ENABLE=n +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz_xip_psram b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz_xip_psram new file mode 100644 index 0000000000..f52050a58f --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz_xip_psram @@ -0,0 +1,8 @@ +CONFIG_PM_ENABLE=n +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_BOOT_HW_INIT=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_SPIRAM_SPEED_120M=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_160mhz b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_160mhz new file mode 100644 index 0000000000..48f2036344 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_160mhz @@ -0,0 +1,6 @@ +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_PM_ENABLE=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz new file mode 100644 index 0000000000..e1433523a3 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz @@ -0,0 +1,6 @@ +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_PM_ENABLE=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram new file mode 100644 index 0000000000..5e105cba02 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram @@ -0,0 +1,12 @@ +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_PM_ENABLE=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_BOOT_HW_INIT=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_SPIRAM_SPEED_120M=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl new file mode 100644 index 0000000000..c376424651 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl @@ -0,0 +1,13 @@ +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_PM_ENABLE=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_BOOT_HW_INIT=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_SPIRAM_SPEED_120M=y +CONFIG_SPI_FLASH_ROM_IMPL=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.defaults b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.defaults new file mode 100644 index 0000000000..91d7574d37 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" diff --git a/docs/en/migration-guides/release-6.x/6.0/peripherals.rst b/docs/en/migration-guides/release-6.x/6.0/peripherals.rst index e4874f5fbd..9986e477ef 100644 --- a/docs/en/migration-guides/release-6.x/6.0/peripherals.rst +++ b/docs/en/migration-guides/release-6.x/6.0/peripherals.rst @@ -303,6 +303,7 @@ SPI Flash Driver - Deprecated API ``spi_flash_dump_counters`` has been removed. Please use :cpp:func:`esp_flash_dump_counters` instead. - Deprecated API ``spi_flash_get_counters`` has been removed. Please use :cpp:func:`esp_flash_get_counters` instead. - Deprecated API ``spi_flash_reset_counters`` has been removed. Please use :cpp:func:`esp_flash_reset_counters` instead. +- New argument ``flags`` is added to ``esp_flash_os_functions_t::start``. Caller and implementer should handle this argument properly. - Kconfig option ``CONFIG_SPI_FLASH_ROM_DRIVER_PATCH`` has been removed. Considering that this option is unlikely to be widely used by users and may cause serious issues if misused, it has been decided to remove it. .. note:: diff --git a/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst b/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst index bf7c8f235f..da148670e2 100644 --- a/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst +++ b/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst @@ -303,6 +303,7 @@ SPI flash 驱动 - 已弃用的 API ``spi_flash_dump_counters`` 已被移除。请改用 :cpp:func:`esp_flash_dump_counters`。 - 已弃用的 API ``spi_flash_get_counters`` 已被移除。请改用 :cpp:func:`esp_flash_get_counters`。 - 已弃用的 API ``spi_flash_reset_counters`` 已被移除。请改用 :cpp:func:`esp_flash_reset_counters`。 +- ``esp_flash_os_functions_t::start`` 新增了一个参数 ``flags``。调用者和实现者应正确处理此参数。 - Kconfig 选项 ``CONFIG_SPI_FLASH_ROM_DRIVER_PATCH`` 已被移除,考虑到这个选项不会被广泛被用户使用,且有因误用而导致出现严重的问题,遂决定移除。 .. note::