From 3641c8d194cee6009c8fcd114ba545c9670f303a Mon Sep 17 00:00:00 2001 From: Zhao Ke Ke Date: Thu, 19 Mar 2026 11:47:13 +0800 Subject: [PATCH] spi_flash: Making flash works more stable when brownout detected --- components/bootloader_support/CMakeLists.txt | 2 +- .../include_bootloader/bootloader_flash.h | 7 +++ .../include_bootloader/flash_qio_mode.h | 10 +++ .../bootloader_support/src/bootloader_flash.c | 26 ++++++++ .../bootloader_support/src/flash_qio_mode.c | 5 ++ components/driver/include/driver/rtc_cntl.h | 16 ++++- components/driver/rtc_module.c | 62 ++++++++++++++++--- components/esp32/Kconfig | 7 +++ components/esp32/brownout.c | 58 ++++++++++++----- components/esp32/component.mk | 3 + components/esp32/cpu_start.c | 4 ++ components/esp32/intr_alloc.c | 3 + components/spi_flash/CMakeLists.txt | 1 + components/spi_flash/Kconfig | 20 ++++++ components/spi_flash/flash_brownout_hook.c | 41 ++++++++++++ components/spi_flash/flash_ops.c | 9 +++ components/spi_flash/include/esp_flash.h | 4 ++ .../include/esp_private/spi_flash_os.h | 17 +++++ components/spi_flash/linker.lf | 1 + components/spi_flash/spi_flash_chip_generic.c | 16 +++++ components/spi_flash/spi_flash_os_func_app.c | 17 ++++- 21 files changed, 302 insertions(+), 27 deletions(-) create mode 100644 components/spi_flash/flash_brownout_hook.c create mode 100644 components/spi_flash/include/esp_private/spi_flash_os.h diff --git a/components/bootloader_support/CMakeLists.txt b/components/bootloader_support/CMakeLists.txt index 3dc065e664..90ff748410 100644 --- a/components/bootloader_support/CMakeLists.txt +++ b/components/bootloader_support/CMakeLists.txt @@ -24,7 +24,7 @@ else() list(APPEND srcs "src/idf/bootloader_sha.c" "src/idf/secure_boot_signatures.c") - set(include_dirs "include") + set(include_dirs "include" "include_bootloader") set(priv_include_dirs "include_bootloader") set(requires soc) #unfortunately the header directly uses SOC registers set(priv_requires spi_flash mbedtls efuse) diff --git a/components/bootloader_support/include_bootloader/bootloader_flash.h b/components/bootloader_support/include_bootloader/bootloader_flash.h index 705fa44e91..e564a8d19a 100644 --- a/components/bootloader_support/include_bootloader/bootloader_flash.h +++ b/components/bootloader_support/include_bootloader/bootloader_flash.h @@ -119,6 +119,13 @@ esp_err_t bootloader_flash_erase_sector(size_t sector); */ esp_err_t bootloader_flash_erase_range(uint32_t start_addr, uint32_t size); +/** + * @brief Reset the flash chip (66H + 99H). + * + * @return ESP_OK if success, otherwise ESP_FAIL. + */ +esp_err_t bootloader_flash_reset_chip(void); + /* Cache MMU block size */ #define MMU_BLOCK_SIZE 0x00010000 diff --git a/components/bootloader_support/include_bootloader/flash_qio_mode.h b/components/bootloader_support/include_bootloader/flash_qio_mode.h index 9d9ae59783..41d74a4316 100644 --- a/components/bootloader_support/include_bootloader/flash_qio_mode.h +++ b/components/bootloader_support/include_bootloader/flash_qio_mode.h @@ -58,6 +58,16 @@ uint32_t bootloader_flash_read_sfdp(uint32_t sfdp_addr, unsigned int miso_byte_n */ esp_err_t bootloader_flash_xmc_startup(void); +/** + * @brief Execute a flash command (for use in brownout reset flow). + * @param command Command byte + * @param mosi_data MOSI data + * @param mosi_len MOSI length in bits + * @param miso_len MISO length in bits + * @return MISO data read + */ +uint32_t bootloader_execute_flash_command(uint8_t command, uint32_t mosi_data, uint8_t mosi_len, uint8_t miso_len); + #ifdef __cplusplus } #endif diff --git a/components/bootloader_support/src/bootloader_flash.c b/components/bootloader_support/src/bootloader_flash.c index 2312a6799e..8c415430ec 100644 --- a/components/bootloader_support/src/bootloader_flash.c +++ b/components/bootloader_support/src/bootloader_flash.c @@ -18,6 +18,8 @@ #include /* including in bootloader for error values */ #include #include "flash_qio_mode.h" +#include "esp_attr.h" +#include "soc/spi_struct.h" #ifndef BOOTLOADER_BUILD /* Normal app version maps to esp_spi_flash.h operations... @@ -292,3 +294,27 @@ esp_err_t bootloader_flash_erase_range(uint32_t start_addr, uint32_t size) return spi_to_esp_err(rc); } #endif + +/* ESP32-only: brownout flash reset (66H+99H) - built for both app and bootloader */ +static inline void bootloader_mspi_reset(void) +{ + SPI1.slave.sync_reset = 0; + SPI0.slave.sync_reset = 0; + SPI1.slave.sync_reset = 1; + SPI0.slave.sync_reset = 1; + SPI1.slave.sync_reset = 0; + SPI0.slave.sync_reset = 0; +} + +esp_err_t IRAM_ATTR bootloader_flash_reset_chip(void) +{ + bootloader_mspi_reset(); + /* Send extra command so host is idle before reset command */ + bootloader_execute_flash_command(0x05, 0, 0, 0); + if (SPI1.ext2.st != 0) { + return ESP_FAIL; + } + bootloader_execute_flash_command(0x66, 0, 0, 0); + bootloader_execute_flash_command(0x99, 0, 0, 0); + return ESP_OK; +} diff --git a/components/bootloader_support/src/flash_qio_mode.c b/components/bootloader_support/src/flash_qio_mode.c index fc766fc1b4..26c1eccb45 100644 --- a/components/bootloader_support/src/flash_qio_mode.c +++ b/components/bootloader_support/src/flash_qio_mode.c @@ -345,6 +345,11 @@ static uint32_t IRAM_ATTR execute_flash_command(uint8_t command, uint32_t mosi_d dummy_len, mosi_len, mosi_data, miso_len); } +uint32_t IRAM_ATTR bootloader_execute_flash_command(uint8_t command, uint32_t mosi_data, uint8_t mosi_len, uint8_t miso_len) +{ + return execute_flash_command(command, mosi_data, mosi_len, miso_len); +} + // cmd(0x5A) + 24bit address + 8 cycles dummy uint32_t IRAM_ATTR bootloader_flash_read_sfdp(uint32_t sfdp_addr, unsigned int miso_byte_num) { diff --git a/components/driver/include/driver/rtc_cntl.h b/components/driver/include/driver/rtc_cntl.h index 44fd015096..5e2bab871c 100644 --- a/components/driver/include/driver/rtc_cntl.h +++ b/components/driver/include/driver/rtc_cntl.h @@ -22,6 +22,8 @@ extern "C" { #endif +#define RTC_INTR_FLAG_IRAM (BIT(0)) /*< Some rtc interrupts can be called with cache disabled */ + /** * @brief Register a handler for specific RTC_CNTL interrupts * @@ -33,13 +35,15 @@ extern "C" { * @param handler_arg argument to be passed to the handler * @param rtc_intr_mask combination of RTC_CNTL_*_INT_ENA bits indicating the * sources to call the handler for + * @param flags An ORred mask of the RTC_INTR_FLAG_* defines. Pass 0 for default. + * RTC_INTR_FLAG_IRAM: interrupt can be triggered with cache disabled. * @return * - ESP_OK on success * - ESP_ERR_NO_MEM not enough memory to allocate handler structure * - other errors returned by esp_intr_alloc */ esp_err_t rtc_isr_register(intr_handler_t handler, void* handler_arg, - uint32_t rtc_intr_mask); + uint32_t rtc_intr_mask, uint32_t flags); /** * @brief Deregister the handler previously registered using rtc_isr_register * @param handler handler function to call (as passed to rtc_isr_register) @@ -51,6 +55,16 @@ esp_err_t rtc_isr_register(intr_handler_t handler, void* handler_arg, */ esp_err_t rtc_isr_deregister(intr_handler_t handler, void* handler_arg); +/** + * @brief Disable the RTC interrupt that is allowed to be executed when cache is disabled. + */ +void rtc_isr_noniram_disable(uint32_t cpu); + +/** + * @brief Enable the RTC interrupt that is allowed to be executed when cache is disabled. + */ +void rtc_isr_noniram_enable(uint32_t cpu); + #ifdef __cplusplus } #endif diff --git a/components/driver/rtc_module.c b/components/driver/rtc_module.c index c0ef9426d3..dc62949910 100644 --- a/components/driver/rtc_module.c +++ b/components/driver/rtc_module.c @@ -30,9 +30,11 @@ #include "freertos/semphr.h" #include "freertos/timers.h" #include "esp_intr_alloc.h" +#include "esp_heap_caps.h" #include "sys/lock.h" #include "driver/rtc_cntl.h" #include "driver/gpio.h" +#include "esp_attr.h" #include "adc1_i2s_private.h" #ifndef NDEBUG @@ -41,6 +43,11 @@ #endif #include "sys/queue.h" +#define NOT_REGISTERED (-1) + +static void s_rtc_isr_noniram_hook(uint32_t rtc_intr_mask); +static void s_rtc_isr_noniram_hook_relieve(uint32_t rtc_intr_mask); + #define ADC_FSM_RSTB_WAIT_DEFAULT (8) #define ADC_FSM_START_WAIT_DEFAULT (5) @@ -440,7 +447,7 @@ inline static touch_pad_t touch_pad_num_wrap(touch_pad_t touch_num) esp_err_t touch_pad_isr_register(intr_handler_t fn, void* arg) { RTC_MODULE_CHECK(fn, "Touch_Pad ISR null", ESP_ERR_INVALID_ARG); - return rtc_isr_register(fn, arg, RTC_CNTL_TOUCH_INT_ST_M); + return rtc_isr_register(fn, arg, RTC_CNTL_TOUCH_INT_ST_M, 0); } esp_err_t touch_pad_isr_deregister(intr_handler_t fn, void *arg) @@ -1949,15 +1956,20 @@ typedef struct rtc_isr_handler_ { uint32_t mask; intr_handler_t handler; void* handler_arg; + uint32_t flags; SLIST_ENTRY(rtc_isr_handler_) next; } rtc_isr_handler_t; -static SLIST_HEAD(rtc_isr_handler_list_, rtc_isr_handler_) s_rtc_isr_handler_list = +static DRAM_ATTR uint32_t rtc_intr_cache; +static DRAM_ATTR uint32_t rtc_intr_enabled; +static DRAM_ATTR int rtc_isr_cpu = NOT_REGISTERED; + +static DRAM_ATTR SLIST_HEAD(rtc_isr_handler_list_, rtc_isr_handler_) s_rtc_isr_handler_list = SLIST_HEAD_INITIALIZER(s_rtc_isr_handler_list); -portMUX_TYPE s_rtc_isr_handler_list_lock = portMUX_INITIALIZER_UNLOCKED; +static DRAM_ATTR portMUX_TYPE s_rtc_isr_handler_list_lock = portMUX_INITIALIZER_UNLOCKED; static intr_handle_t s_rtc_isr_handle; -static void rtc_isr(void* arg) +IRAM_ATTR static void rtc_isr(void* arg) { uint32_t status = REG_READ(RTC_CNTL_INT_ST_REG); rtc_isr_handler_t* it; @@ -1983,10 +1995,11 @@ static esp_err_t rtc_isr_ensure_installed() REG_WRITE(RTC_CNTL_INT_ENA_REG, 0); REG_WRITE(RTC_CNTL_INT_CLR_REG, UINT32_MAX); - err = esp_intr_alloc(ETS_RTC_CORE_INTR_SOURCE, 0, &rtc_isr, NULL, &s_rtc_isr_handle); + err = esp_intr_alloc(ETS_RTC_CORE_INTR_SOURCE, ESP_INTR_FLAG_IRAM, &rtc_isr, NULL, &s_rtc_isr_handle); if (err != ESP_OK) { goto out; } + rtc_isr_cpu = esp_intr_get_cpu(s_rtc_isr_handle); out: portEXIT_CRITICAL(&s_rtc_isr_handler_list_lock); @@ -1994,21 +2007,27 @@ out: } -esp_err_t rtc_isr_register(intr_handler_t handler, void* handler_arg, uint32_t rtc_intr_mask) +esp_err_t rtc_isr_register(intr_handler_t handler, void* handler_arg, uint32_t rtc_intr_mask, uint32_t flags) { esp_err_t err = rtc_isr_ensure_installed(); if (err != ESP_OK) { return err; } - rtc_isr_handler_t* item = malloc(sizeof(*item)); + rtc_isr_handler_t* item = heap_caps_malloc(sizeof(*item), MALLOC_CAP_INTERNAL); if (item == NULL) { return ESP_ERR_NO_MEM; } item->handler = handler; item->handler_arg = handler_arg; item->mask = rtc_intr_mask; + item->flags = flags; portENTER_CRITICAL(&s_rtc_isr_handler_list_lock); + if (flags & RTC_INTR_FLAG_IRAM) { + s_rtc_isr_noniram_hook(rtc_intr_mask); + } else { + s_rtc_isr_noniram_hook_relieve(rtc_intr_mask); + } SLIST_INSERT_HEAD(&s_rtc_isr_handler_list, item, next); portEXIT_CRITICAL(&s_rtc_isr_handler_list_lock); return ESP_OK; @@ -2028,6 +2047,9 @@ esp_err_t rtc_isr_deregister(intr_handler_t handler, void* handler_arg) SLIST_REMOVE_AFTER(prev, next); } found = true; + if (it->flags & RTC_INTR_FLAG_IRAM) { + s_rtc_isr_noniram_hook_relieve(it->mask); + } free(it); break; } @@ -2036,3 +2058,29 @@ esp_err_t rtc_isr_deregister(intr_handler_t handler, void* handler_arg) portEXIT_CRITICAL(&s_rtc_isr_handler_list_lock); return found ? ESP_OK : ESP_ERR_INVALID_STATE; } + +static void s_rtc_isr_noniram_hook(uint32_t rtc_intr_mask) +{ + rtc_intr_cache |= rtc_intr_mask; +} + +static void s_rtc_isr_noniram_hook_relieve(uint32_t rtc_intr_mask) +{ + rtc_intr_cache &= ~rtc_intr_mask; +} + +IRAM_ATTR void rtc_isr_noniram_disable(uint32_t cpu) +{ + if (rtc_isr_cpu == cpu) { + rtc_intr_enabled |= RTCCNTL.int_ena.val; + RTCCNTL.int_ena.val &= rtc_intr_cache; + } +} + +IRAM_ATTR void rtc_isr_noniram_enable(uint32_t cpu) +{ + if (rtc_isr_cpu == cpu) { + RTCCNTL.int_ena.val = rtc_intr_enabled; + rtc_intr_enabled = 0; + } +} diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index f86983ca37..cb25a73786 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -14,6 +14,8 @@ menu "ESP32-specific" config ESP32_REV_MIN_0 bool "Rev 0" + # Brownout on Rev 0 is bugged, must use interrupt + select ESP_SYSTEM_BROWNOUT_INTR config ESP32_REV_MIN_1 bool "Rev 1" config ESP32_REV_MIN_2 @@ -586,6 +588,11 @@ menu "ESP32-specific" default 6 if ESP32_BROWNOUT_DET_LVL_SEL_6 default 7 if ESP32_BROWNOUT_DET_LVL_SEL_7 + config ESP_SYSTEM_BROWNOUT_INTR + bool + default n + help + Brownout restart via interrupt (for ESP32 Rev0 or XMC flash brownout reset). #Reduce PHY TX power when brownout reset config ESP32_REDUCE_PHY_TX_POWER diff --git a/components/esp32/brownout.c b/components/esp32/brownout.c index 35589330fb..02e0e5fa4e 100644 --- a/components/esp32/brownout.c +++ b/components/esp32/brownout.c @@ -20,43 +20,67 @@ #include "soc/soc.h" #include "soc/cpu.h" #include "soc/rtc_periph.h" +#include "soc/rtc_cntl_reg.h" #include "esp32/rom/ets_sys.h" #include "esp_private/system_internal.h" #include "driver/rtc_cntl.h" #include "freertos/FreeRTOS.h" +#include "esp_attr.h" + +#if CONFIG_ESP_SYSTEM_BROWNOUT_INTR +#include "esp_intr_alloc.h" +#include "bootloader_flash.h" +#include "esp_private/spi_flash_os.h" +#endif #ifdef CONFIG_ESP32_BROWNOUT_DET_LVL #define BROWNOUT_DET_LVL CONFIG_ESP32_BROWNOUT_DET_LVL #else #define BROWNOUT_DET_LVL 0 -#endif //CONFIG_ESP32_BROWNOUT_DET_LVL +#endif -static void rtc_brownout_isr_handler() +IRAM_ATTR static void rtc_brownout_isr_handler(void *arg) { - /* Normally RTC ISR clears the interrupt flag after the application-supplied - * handler returns. Since restart is called here, the flag needs to be - * cleared manually. - */ REG_WRITE(RTC_CNTL_INT_CLR_REG, RTC_CNTL_BROWN_OUT_INT_CLR); - /* Stall the other CPU to make sure the code running there doesn't use UART - * at the same time as the following ets_printf. - */ esp_cpu_stall(!xPortGetCoreID()); esp_reset_reason_set_hint(ESP_RST_BROWNOUT); - ets_printf("\r\nBrownout detector was triggered\r\n\r\n"); +#if CONFIG_SPI_FLASH_BROWNOUT_RESET + if (spi_flash_brownout_need_reset()) { + if (spi_flash_brownout_check_erase()) { + bootloader_flash_reset_chip(); + } + } +#endif + ets_printf(DRAM_STR("\r\nBrownout detector was triggered\r\n\r\n")); esp_restart_noos(); } -void esp_brownout_init() +void esp_brownout_init(void) { +#if CONFIG_ESP_SYSTEM_BROWNOUT_INTR REG_WRITE(RTC_CNTL_BROWN_OUT_REG, - RTC_CNTL_BROWN_OUT_ENA /* Enable BOD */ - | RTC_CNTL_BROWN_OUT_PD_RF_ENA /* Automatically power down RF */ - /* Reset timeout must be set to >1 even if BOR feature is not used */ + RTC_CNTL_BROWN_OUT_ENA + | RTC_CNTL_BROWN_OUT_CLOSE_FLASH_ENA_M + | RTC_CNTL_BROWN_OUT_PD_RF_ENA_M | (2 << RTC_CNTL_BROWN_OUT_RST_WAIT_S) | (BROWNOUT_DET_LVL << RTC_CNTL_DBROWN_OUT_THRES_S)); - - ESP_ERROR_CHECK( rtc_isr_register(rtc_brownout_isr_handler, NULL, RTC_CNTL_BROWN_OUT_INT_ENA_M) ); - + REG_WRITE(RTC_CNTL_INT_CLR_REG, RTC_CNTL_BROWN_OUT_INT_CLR); +#else + REG_WRITE(RTC_CNTL_BROWN_OUT_REG, + RTC_CNTL_BROWN_OUT_ENA + | RTC_CNTL_BROWN_OUT_RST_ENA + | RTC_CNTL_BROWN_OUT_PD_RF_ENA_M + | (2 << RTC_CNTL_BROWN_OUT_RST_WAIT_S) + | (BROWNOUT_DET_LVL << RTC_CNTL_DBROWN_OUT_THRES_S)); +#endif + ESP_ERROR_CHECK(rtc_isr_register(rtc_brownout_isr_handler, NULL, RTC_CNTL_BROWN_OUT_INT_ENA_M, RTC_INTR_FLAG_IRAM)); REG_SET_BIT(RTC_CNTL_INT_ENA_REG, RTC_CNTL_BROWN_OUT_INT_ENA_M); + +} + +void esp_brownout_disable(void) +{ + REG_CLR_BIT(RTC_CNTL_INT_ENA_REG, RTC_CNTL_BROWN_OUT_INT_ENA_M); + rtc_isr_deregister(rtc_brownout_isr_handler, NULL); + REG_WRITE(RTC_CNTL_BROWN_OUT_REG, 0); } diff --git a/components/esp32/component.mk b/components/esp32/component.mk index 8f999b26d6..24ce1bc82d 100644 --- a/components/esp32/component.mk +++ b/components/esp32/component.mk @@ -4,6 +4,9 @@ COMPONENT_SRCDIRS := . +# Access bootloader_support private headers (bootloader_flash.h) used by brownout.c +COMPONENT_PRIV_INCLUDEDIRS += ../bootloader_support/include_bootloader + ifdef CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY # This linker script must come before esp32.project.ld LINKER_SCRIPTS += esp32.extram.bss.ld diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 5430a15104..99b5d5d555 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -62,6 +62,7 @@ #include "esp_app_trace.h" #include "esp_private/dbg_stubs.h" #include "esp_flash_encrypt.h" +#include "esp_private/spi_flash_os.h" #include "esp32/spiram.h" #include "esp_clk_internal.h" #include "esp_timer.h" @@ -431,6 +432,9 @@ void start_cpu0_default(void) esp_flash_app_init(); esp_err_t flash_ret = esp_flash_init_default_chip(); assert(flash_ret == ESP_OK); +#if CONFIG_SPI_FLASH_BROWNOUT_RESET + spi_flash_needs_reset_check(); +#endif #ifdef CONFIG_PM_ENABLE esp_pm_impl_init(); diff --git a/components/esp32/intr_alloc.c b/components/esp32/intr_alloc.c index 3b5f1b8ea7..1c9a2d579d 100644 --- a/components/esp32/intr_alloc.c +++ b/components/esp32/intr_alloc.c @@ -28,6 +28,7 @@ #include "esp_log.h" #include "esp_intr_alloc.h" #include "esp_attr.h" +#include "driver/rtc_cntl.h" #include #include #if !CONFIG_FREERTOS_UNICORE @@ -866,6 +867,7 @@ void IRAM_ATTR esp_intr_noniram_disable() { int oldint; int cpu=xPortGetCoreID(); + rtc_isr_noniram_disable(cpu); int intmask=~non_iram_int_mask[cpu]; if (non_iram_int_disabled_flag[cpu]) abort(); non_iram_int_disabled_flag[cpu]=true; @@ -884,6 +886,7 @@ void IRAM_ATTR esp_intr_noniram_disable() void IRAM_ATTR esp_intr_noniram_enable() { int cpu=xPortGetCoreID(); + rtc_isr_noniram_enable(cpu); int intmask=non_iram_int_disabled[cpu]; if (!non_iram_int_disabled_flag[cpu]) abort(); non_iram_int_disabled_flag[cpu]=false; diff --git a/components/spi_flash/CMakeLists.txt b/components/spi_flash/CMakeLists.txt index 61900d09ba..9c04a7eb2e 100644 --- a/components/spi_flash/CMakeLists.txt +++ b/components/spi_flash/CMakeLists.txt @@ -13,6 +13,7 @@ else() set(srcs "partition.c" "spi_flash_rom_patch.c" + "flash_brownout_hook.c" ) # New implementation after IDF v4.0 list(APPEND cache_srcs diff --git a/components/spi_flash/Kconfig b/components/spi_flash/Kconfig index f40b0b8340..f1ba085408 100644 --- a/components/spi_flash/Kconfig +++ b/components/spi_flash/Kconfig @@ -122,6 +122,26 @@ menu "SPI Flash driver" Enable this option to override flash size with latest ESPTOOLPY_FLASHSIZE value from the app header if the size in the bootloader header is incorrect. + menu "SPI Flash behavior when brownout" + + config SPI_FLASH_BROWNOUT_RESET_XMC + bool "Enable sending reset when brownout for XMC flash chips" + default y + select SPI_FLASH_BROWNOUT_RESET + help + When this option is selected, the patch will be enabled for XMC. + Follow the recommended flow by XMC for better stability. + + config SPI_FLASH_BROWNOUT_RESET + bool + default n + select ESP_SYSTEM_BROWNOUT_INTR + help + When brownout happens during flash erase/write operations, + send reset command to stop the flash operations to improve stability. + + endmenu + menu "Auto-detect flash chips" config SPI_FLASH_SUPPORT_ISSI_CHIP diff --git a/components/spi_flash/flash_brownout_hook.c b/components/spi_flash/flash_brownout_hook.c new file mode 100644 index 0000000000..ada5dd0451 --- /dev/null +++ b/components/spi_flash/flash_brownout_hook.c @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_attr.h" +#include "sdkconfig.h" +#include "esp32/rom/spi_flash.h" + +#if CONFIG_SPI_FLASH_BROWNOUT_RESET + +static bool flash_brownout_needs_reset = false; +static bool flash_erasing = false; + +void spi_flash_needs_reset_check(void) +{ +#if CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC + if ((g_rom_flashchip.device_id >> 16) == 0x20) { + flash_brownout_needs_reset = true; + } +#endif +} + +void IRAM_ATTR spi_flash_set_erasing_flag(bool status) +{ + flash_erasing = status; +} + +bool spi_flash_brownout_need_reset(void) +{ + return flash_brownout_needs_reset; +} + +bool spi_flash_brownout_check_erase(void) +{ + return flash_erasing; +} + +#endif /* CONFIG_SPI_FLASH_BROWNOUT_RESET */ diff --git a/components/spi_flash/flash_ops.c b/components/spi_flash/flash_ops.c index d7e96ec544..3f48e597a7 100644 --- a/components/spi_flash/flash_ops.c +++ b/components/spi_flash/flash_ops.c @@ -36,6 +36,9 @@ #include "cache_utils.h" #include "esp_flash.h" #include "esp_timer.h" +#if CONFIG_SPI_FLASH_BROWNOUT_RESET +#include "esp_private/spi_flash_os.h" +#endif /* bytes erased by SPIEraseBlock() ROM function */ #define BLOCK_ERASE_SIZE 65536 @@ -246,6 +249,9 @@ esp_err_t IRAM_ATTR spi_flash_erase_range(size_t start_addr, size_t size) int64_t start_time_us = esp_timer_get_time(); #endif spi_flash_guard_start(); +#if CONFIG_SPI_FLASH_BROWNOUT_RESET + spi_flash_set_erasing_flag(true); +#endif #ifndef CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE if (sector % sectors_per_block == 0 && end - sector >= sectors_per_block) { rc = esp_rom_spiflash_erase_block(sector / sectors_per_block); @@ -258,6 +264,9 @@ esp_err_t IRAM_ATTR spi_flash_erase_range(size_t start_addr, size_t size) ++sector; COUNTER_ADD_BYTES(erase, SPI_FLASH_SEC_SIZE); } +#if CONFIG_SPI_FLASH_BROWNOUT_RESET + spi_flash_set_erasing_flag(false); +#endif spi_flash_guard_end(); #ifdef CONFIG_SPI_FLASH_YIELD_DURING_ERASE no_yield_time_us += (esp_timer_get_time() - start_time_us); diff --git a/components/spi_flash/include/esp_flash.h b/components/spi_flash/include/esp_flash.h index ccea0d0722..e3aff9ed71 100644 --- a/components/spi_flash/include/esp_flash.h +++ b/components/spi_flash/include/esp_flash.h @@ -53,6 +53,10 @@ typedef struct { /** Yield to other tasks. Called during erase operations. */ esp_err_t (*yield)(void *arg); + +#define SPI_FLASH_OS_IS_ERASING_STATUS_FLAG (1u << 0) + /** Call to set flash operation status (e.g. erasing). */ + void (*set_flash_op_status)(uint32_t op_status); } esp_flash_os_functions_t; /** @brief Structure to describe a SPI flash chip connected to the system. diff --git a/components/spi_flash/include/esp_private/spi_flash_os.h b/components/spi_flash/include/esp_private/spi_flash_os.h new file mode 100644 index 0000000000..efd9ed2097 --- /dev/null +++ b/components/spi_flash/include/esp_private/spi_flash_os.h @@ -0,0 +1,17 @@ +// Copyright 2015-2022 Espressif Systems (Shanghai) CO LTD +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void spi_flash_needs_reset_check(void); +void spi_flash_set_erasing_flag(bool status); +bool spi_flash_brownout_need_reset(void); +bool spi_flash_brownout_check_erase(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/spi_flash/linker.lf b/components/spi_flash/linker.lf index 0ea72aaa43..da1ed86514 100644 --- a/components/spi_flash/linker.lf +++ b/components/spi_flash/linker.lf @@ -2,6 +2,7 @@ archive: libspi_flash.a entries: spi_flash_rom_patch (noflash_text) + flash_brownout_hook (noflash) spi_flash_chip_generic (noflash) spi_flash_chip_issi (noflash) spi_flash_chip_gd(noflash) diff --git a/components/spi_flash/spi_flash_chip_generic.c b/components/spi_flash/spi_flash_chip_generic.c index aad19474f3..ab51e83107 100644 --- a/components/spi_flash/spi_flash_chip_generic.c +++ b/components/spi_flash/spi_flash_chip_generic.c @@ -17,6 +17,13 @@ #include "spi_flash_chip_generic.h" #include "spi_flash_defs.h" #include "esp_log.h" +#include "esp_private/spi_flash_os.h" + +#define SET_FLASH_ERASE_STATUS(chip, status) do { \ + if ((chip)->os_func->set_flash_op_status) { \ + (chip)->os_func->set_flash_op_status(status); \ + } \ +} while (0) static const char TAG[] = "chip_generic"; @@ -86,15 +93,18 @@ esp_err_t spi_flash_chip_generic_erase_chip(esp_flash_t *chip) err = chip->chip_drv->wait_idle(chip, SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS * 1000); } if (err == ESP_OK) { + SET_FLASH_ERASE_STATUS(chip, SPI_FLASH_OS_IS_ERASING_STATUS_FLAG); chip->host->erase_chip(chip->host); //to save time, flush cache here if (chip->host->flush_cache) { err = chip->host->flush_cache(chip->host, 0, chip->size); if (err != ESP_OK) { + SET_FLASH_ERASE_STATUS(chip, 0); return err; } } err = chip->chip_drv->wait_idle(chip, SPI_FLASH_GENERIC_CHIP_ERASE_TIMEOUT_MS * 1000); + SET_FLASH_ERASE_STATUS(chip, 0); } return err; } @@ -106,15 +116,18 @@ esp_err_t spi_flash_chip_generic_erase_sector(esp_flash_t *chip, uint32_t start_ err = chip->chip_drv->wait_idle(chip, SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS * 1000); } if (err == ESP_OK) { + SET_FLASH_ERASE_STATUS(chip, SPI_FLASH_OS_IS_ERASING_STATUS_FLAG); chip->host->erase_sector(chip->host, start_address); //to save time, flush cache here if (chip->host->flush_cache) { err = chip->host->flush_cache(chip->host, start_address, chip->chip_drv->sector_size); if (err != ESP_OK) { + SET_FLASH_ERASE_STATUS(chip, 0); return err; } } err = chip->chip_drv->wait_idle(chip, SPI_FLASH_GENERIC_SECTOR_ERASE_TIMEOUT_MS * 1000); + SET_FLASH_ERASE_STATUS(chip, 0); } return err; } @@ -126,15 +139,18 @@ esp_err_t spi_flash_chip_generic_erase_block(esp_flash_t *chip, uint32_t start_a err = chip->chip_drv->wait_idle(chip, SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS * 1000); } if (err == ESP_OK) { + SET_FLASH_ERASE_STATUS(chip, SPI_FLASH_OS_IS_ERASING_STATUS_FLAG); chip->host->erase_block(chip->host, start_address); //to save time, flush cache here if (chip->host->flush_cache) { err = chip->host->flush_cache(chip->host, start_address, chip->chip_drv->block_erase_size); if (err != ESP_OK) { + SET_FLASH_ERASE_STATUS(chip, 0); return err; } } err = chip->chip_drv->wait_idle(chip, SPI_FLASH_GENERIC_BLOCK_ERASE_TIMEOUT_MS * 1000); + SET_FLASH_ERASE_STATUS(chip, 0); } return err; } diff --git a/components/spi_flash/spi_flash_os_func_app.c b/components/spi_flash/spi_flash_os_func_app.c index 349acb4821..f97fac234a 100644 --- a/components/spi_flash/spi_flash_os_func_app.c +++ b/components/spi_flash/spi_flash_os_func_app.c @@ -18,6 +18,7 @@ #include "esp_spi_flash.h" //for ``g_flash_guard_default_ops`` #include "esp_flash.h" #include "esp_flash_partitions.h" +#include "esp_private/spi_flash_os.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -103,6 +104,14 @@ static IRAM_ATTR esp_err_t main_flash_region_protected(void* arg, size_t start_a } } +#if CONFIG_SPI_FLASH_BROWNOUT_RESET +static IRAM_ATTR void main_flash_op_status(uint32_t op_status) +{ + bool is_erasing = (op_status & SPI_FLASH_OS_IS_ERASING_STATUS_FLAG) != 0; + spi_flash_set_erasing_flag(is_erasing); +} +#endif + static DRAM_ATTR spi1_app_func_arg_t spi1_arg = { .host_id = 0, //for SPI1, .no_protect = true, @@ -128,13 +137,19 @@ const DRAM_ATTR esp_flash_os_functions_t esp_flash_spi1_default_os_functions = { .region_protected = main_flash_region_protected, .delay_us = delay_us, .yield = spi_flash_os_yield, +#if CONFIG_SPI_FLASH_BROWNOUT_RESET + .set_flash_op_status = main_flash_op_status, +#else + .set_flash_op_status = NULL, +#endif }; const esp_flash_os_functions_t esp_flash_spi23_default_os_functions = { .start = spi23_start, .end = spi23_end, .delay_us = delay_us, - .yield = spi_flash_os_yield + .yield = spi_flash_os_yield, + .set_flash_op_status = NULL, }; esp_err_t esp_flash_init_os_functions(esp_flash_t *chip, int host_id)