From ab63b48f6f61beef0e1413d7ea74d96156db6fb2 Mon Sep 17 00:00:00 2001 From: Li Shuai Date: Wed, 17 Jun 2020 16:32:22 +0800 Subject: [PATCH] add soft solution for esp32 eco3 live lock issue --- components/esp32/Kconfig | 6 + components/esp32/cpu_start.c | 4 + components/esp32/dport_panic_highint_hdl.S | 289 ++++++++++++++++++++- components/esp32/include/esp_system.h | 9 + components/esp32/int_wdt.c | 28 +- components/esp32/panic.c | 4 +- components/esp32/system_api.c | 5 + 7 files changed, 340 insertions(+), 5 deletions(-) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 1540d4549..ff2f3ca84 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -6,6 +6,11 @@ menu "ESP32-specific" default "y" if IDF_TARGET="esp32" default "n" + config ESP32_ECO3_CACHE_LOCK_FIX + bool + default y + depends on !FREERTOS_UNICORE && SPIRAM_SUPPORT + choice ESP32_REV_MIN prompt "Minimum Supported ESP32 Revision" default ESP32_REV_MIN_0 @@ -21,6 +26,7 @@ menu "ESP32-specific" bool "Rev 2" config ESP32_REV_MIN_3 bool "Rev 3" + select INT_WDT if ESP32_ECO3_CACHE_LOCK_FIX endchoice config ESP32_REV_MIN diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 909f2486d..b02462630 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -387,6 +387,10 @@ void start_cpu0_default(void) esp_int_wdt_init(); //Initialize the interrupt watch dog for CPU0. esp_int_wdt_cpu_init(); +#else +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX + assert(!soc_has_cache_lock_bug() && "ESP32 Rev 3 + Dual Core + PSRAM requires INT WDT enabled in project config!"); +#endif #endif esp_cache_err_int_init(); esp_crosscore_int_init(); diff --git a/components/esp32/dport_panic_highint_hdl.S b/components/esp32/dport_panic_highint_hdl.S index 35412deba..5a206bba7 100644 --- a/components/esp32/dport_panic_highint_hdl.S +++ b/components/esp32/dport_panic_highint_hdl.S @@ -17,10 +17,12 @@ #include #include #include "freertos/xtensa_context.h" +#include "freertos/xtensa_rtos.h" #include "esp_panic.h" #include "sdkconfig.h" #include "soc/soc.h" #include "soc/dport_reg.h" +#include "soc/timer_group_reg.h" /* @@ -37,7 +39,23 @@ Interrupt , a high-priority interrupt, is used for several things: #define L4_INTR_A4_OFFSET 8 .data _l4_intr_stack: - .space L4_INTR_STACK_SIZE + .space L4_INTR_STACK_SIZE*portNUM_PROCESSORS /* This allocates stacks for each individual CPU. */ + +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_INT_WDT + .global _l4_intr_livelock_counter + .global _l4_intr_livelock_max + .align 16 +_l4_intr_livelock_counter: + .word 0 +_l4_intr_livelock_max: + .word 0 +_l4_intr_livelock_sync: + .word 0, 0 +_l4_intr_livelock_app: + .word 0 +_l4_intr_livelock_pro: + .word 0 +#endif .section .iram1,"ax" .global xt_highint4 @@ -52,8 +70,24 @@ xt_highint4: bnez a0, .handle_dport_access_int #endif // CONFIG_FREERTOS_UNICORE +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_INT_WDT + /* See if we're here for the tg1 watchdog interrupt */ + rsr a0, INTERRUPT + extui a0, a0, ETS_T1_WDT_INUM, 1 + beqz a0, 1f + + wsr a5, depc /* use DEPC as temp storage */ + movi a0, _l4_intr_livelock_counter + l32i a0, a0, 0 + movi a5, _l4_intr_livelock_max + l32i a5, a5, 0 + bltu a0, a5, .handle_livelock_int /* _l4_intr_livelock_counter < _l4_intr_livelock_max */ + + rsr a5, depc /* restore a5 */ +#endif + /* Allocate exception frame and save minimal context. */ - mov a0, sp +1: mov a0, sp addi sp, sp, -XT_STK_FRMSZ s32i a0, sp, XT_STK_A1 #if XCHAL_HAVE_WINDOWED @@ -129,6 +163,257 @@ xt_highint4: rfi 4 +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_INT_WDT + +/* +-------------------------------------------------------------------------------- + Macro intr_matrix_map - Attach an CPU interrupt to a hardware source. + + Input : "addr" - Interrupt map configuration base address + Input : "src" - Interrupt source. + Input : "inum" - Interrupt number. +-------------------------------------------------------------------------------- +*/ + .macro intr_matrix_map addr src inum + movi a2, \src + slli a2, a2, 2 + movi a3, \addr + add a3, a3, a2 + movi a2, \inum + s32i a2, a3, 0 + memw + .endm + +/* +-------------------------------------------------------------------------------- + Macro wdt_clr_intr_status - Clear the WDT interrupt status. + Macro wdt_feed - Feed the WDT. + + Input : "dev" - Beginning address of the peripheral registers +-------------------------------------------------------------------------------- +*/ + +#define TIMG1_REG_OFFSET(reg) ((reg) - REG_TIMG_BASE(1)) +#define TIMG1_WDTWPROTECT_OFFSET TIMG1_REG_OFFSET(TIMG_WDTWPROTECT_REG(1)) +#define TIMG1_INT_CLR_OFFSET TIMG1_REG_OFFSET(TIMG_INT_CLR_TIMERS_REG(1)) +#define TIMG1_WDT_STG0_HOLD_OFFSET TIMG1_REG_OFFSET(TIMG_WDTCONFIG2_REG(1)) +#define TIMG1_WDT_STG1_HOLD_OFFSET TIMG1_REG_OFFSET(TIMG_WDTCONFIG3_REG(1)) +#define TIMG1_WDT_FEED_OFFSET TIMG1_REG_OFFSET(TIMG_WDTFEED_REG(1)) + + .macro wdt_clr_intr_status dev + movi a2, \dev + movi a3, TIMG_WDT_WKEY_VALUE + s32i a3, a2, TIMG1_WDTWPROTECT_OFFSET /* disable write protect */ + memw + l32i a4, a2, TIMG1_INT_CLR_OFFSET + memw + movi a3, 4 + or a3, a4, a3 + s32i a3, a2, TIMG1_INT_CLR_OFFSET /* clear 1st stage timeout interrupt */ + memw + movi a3, 0 + s32i a3, a2, TIMG1_WDTWPROTECT_OFFSET /* enable write protect */ + memw + .endm + + .macro wdt_feed dev + movi a2, \dev + movi a3, TIMG_WDT_WKEY_VALUE + s32i a3, a2, TIMG1_WDTWPROTECT_OFFSET /* disable write protect */ + memw + movi a4, _l4_intr_livelock_max + l32i a4, a4, 0 + memw + addi a4, a4, 1 + movi a3, (CONFIG_INT_WDT_TIMEOUT_MS<<1) + quou a3, a3, a4 + s32i a3, a2, TIMG1_WDT_STG0_HOLD_OFFSET /* set timeout before interrupt */ + memw + movi a3, (CONFIG_INT_WDT_TIMEOUT_MS<<2) + s32i a3, a2, TIMG1_WDT_STG1_HOLD_OFFSET /* set timeout before system reset */ + memw + movi a3, 1 + s32i a3, a2, TIMG1_WDT_FEED_OFFSET /* feed wdt */ + memw + movi a3, 0 + s32i a3, a2, TIMG1_WDTWPROTECT_OFFSET /* enable write protect */ + memw + .endm + + .align 4 +.handle_livelock_int: + + getcoreid a5 + + /* Save A2, A3, A4 so we can use those registers */ + movi a0, L4_INTR_STACK_SIZE + mull a5, a5, a0 + movi a0, _l4_intr_stack + add a0, a0, a5 + s32i a2, a0, L4_INTR_A2_OFFSET + s32i a3, a0, L4_INTR_A3_OFFSET + s32i a4, a0, L4_INTR_A4_OFFSET + + /* Here, we can use a0, a2, a3, a4, a5 registers */ + getcoreid a5 + + rsil a0, CONFIG_ESP32_DPORT_DIS_INTERRUPT_LVL /* disable nested interrupt */ + + beqz a5, 1f + movi a2, _l4_intr_livelock_app + l32i a3, a2, 0 + addi a3, a3, 1 + s32i a3, a2, 0 + + /* Dual core synchronization, ensuring that both cores enter interrupts */ +1: movi a4, 0x1 + movi a2, _l4_intr_livelock_sync + addx4 a3, a5, a2 + s32i a4, a3, 0 + +1: movi a2, _l4_intr_livelock_sync + movi a3, 1 + addx4 a3, a3, a2 + l32i a2, a2, 0 + l32i a3, a3, 0 + and a2, a2, a3 + beqz a2, 1b + + beqz a5, 1f /* Pro cpu (Core 0) jump bypass */ + + movi a2, _l4_intr_livelock_app + l32i a2, a2, 0 + bnei a2, 2, 1f + movi a2, _l4_intr_livelock_counter /* _l4_intr_livelock_counter++ */ + l32i a3, a2, 0 + addi a3, a3, 1 + s32i a3, a2, 0 + + /* + The delay time can be calculated by the following formula: + T = ceil(0.25 + max(t1, t2)) us + + t1 = 80 / f1, t2 = (1 + 14/N) * 20 / f2 + + f1: PSRAM access frequency, unit: MHz. + f2: Flash access frequency, unit: MHz. + + When flash is slow/fast read, N = 1. + When flash is DOUT/DIO read, N = 2. + When flash is QOUT/QIO read, N = 4. + */ +1: rsr.ccount a2 +#if defined(CONFIG_FLASHMODE_QIO) || defined(CONFIG_FLASHMODE_QOUT) +# if defined(CONFIG_ESPTOOLPY_FLASHFREQ_80M) && defined(CONFIG_SPIRAM_SPEED_80M) + movi a3, 480 +# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_80M) && defined(CONFIG_SPIRAM_SPEED_40M) + movi a3, 720 +# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_40M) && defined(CONFIG_SPIRAM_SPEED_40M) + movi a3, 720 +# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_26M) && defined(CONFIG_SPIRAM_SPEED_40M) + movi a3, 960 +# else + movi a3, 1200 +# endif +#elif defined(CONFIG_FLASHMODE_DIO) || defined(CONFIG_FLASHMODE_DOUT) +# if defined(CONFIG_ESPTOOLPY_FLASHFREQ_80M) && defined(CONFIG_SPIRAM_SPEED_80M) + movi a3, 720 +# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_80M) && defined(CONFIG_SPIRAM_SPEED_40M) + movi a3, 720 +# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_40M) && defined(CONFIG_SPIRAM_SPEED_40M) + movi a3, 1200 +# elif defined(CONFIG_ESPTOOLPY_FLASHFREQ_26M) && defined(CONFIG_SPIRAM_SPEED_40M) + movi a3, 1680 +# else + movi a3, 2160 +# endif +#endif +2: rsr.ccount a4 /* delay_us(N) */ + sub a4, a4, a2 + bltu a4, a3, 2b + + beqz a5, 2f + movi a2, _l4_intr_livelock_app + l32i a2, a2, 0 + beqi a2, 2, 8f + j 3f + +2: movi a2, _l4_intr_livelock_pro + l32i a4, a2, 0 + addi a4, a4, 1 + s32i a4, a2, 0 + + movi a2, _l4_intr_livelock_sync + movi a3, 1 + addx4 a3, a3, a2 + l32i a2, a2, 0 + l32i a3, a3, 0 + and a2, a2, a3 + beqz a2, 5f + j 1b +5: bgei a4, 2, 4f + j 1b + + /* + Pro cpu (Core 0) jump bypass, continue waiting, App cpu (Core 1) + can execute to here, unmap itself tg1 1st stage timeout interrupt + then restore registers and exit highint4. + */ +3: intr_matrix_map DPORT_APP_MAC_INTR_MAP_REG, ETS_TG1_WDT_LEVEL_INTR_SOURCE, 16 + j 9f + + /* + Here, App cpu (Core 1) has exited isr, Pro cpu (Core 0) help the + App cpu map tg1 1st stage timeout interrupt clear tg1 interrupt. + */ +4: intr_matrix_map DPORT_APP_MAC_INTR_MAP_REG, ETS_TG1_WDT_LEVEL_INTR_SOURCE, ETS_T1_WDT_INUM + +1: movi a2, _l4_intr_livelock_sync + movi a4, 1 + addx4 a3, a4, a2 + l32i a2, a2, 0 + l32i a3, a3, 0 + and a2, a2, a3 + beqz a2, 1b /* Wait for App cpu to enter highint4 again */ + + wdt_clr_intr_status TIMERG1 + j 9f + + /* Feed watchdog */ +8: wdt_feed TIMERG1 + +9: wsr a0, PS /* restore iterrupt level */ + + movi a0, 0 + beqz a5, 1f + movi a2, _l4_intr_livelock_app + l32i a3, a2, 0 + bnei a3, 2, 1f + s32i a0, a2, 0 + +1: bnez a5, 2f + movi a2, _l4_intr_livelock_pro + s32i a0, a2, 0 +2: movi a2, _l4_intr_livelock_sync + addx4 a2, a5, a2 + s32i a0, a2, 0 + + /* Done. Restore registers and return. */ + movi a0, L4_INTR_STACK_SIZE + mull a5, a5, a0 + movi a0, _l4_intr_stack + add a0, a0, a5 + l32i a2, a0, L4_INTR_A2_OFFSET + l32i a3, a0, L4_INTR_A3_OFFSET + l32i a4, a0, L4_INTR_A4_OFFSET + rsync /* ensure register restored */ + + rsr a5, depc + + rsr a0, EXCSAVE_4 /* restore a0 */ + rfi 4 + +#endif #ifndef CONFIG_FREERTOS_UNICORE diff --git a/components/esp32/include/esp_system.h b/components/esp32/include/esp_system.h index 1a51999d3..782147181 100644 --- a/components/esp32/include/esp_system.h +++ b/components/esp32/include/esp_system.h @@ -322,6 +322,15 @@ typedef struct { */ void esp_chip_info(esp_chip_info_t* out_info); +/** + * @brief Cache lock bug exists or not + * + * @return + * - true : bug exists + * - false : bug not exists + */ +bool soc_has_cache_lock_bug(void); + #ifdef __cplusplus } #endif diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c index efef92511..7d5f4c6f5 100644 --- a/components/esp32/int_wdt.c +++ b/components/esp32/int_wdt.c @@ -35,8 +35,17 @@ #if CONFIG_INT_WDT -#define WDT_INT_NUM 24 +#define WDT_INT_NUM ETS_T1_WDT_INUM +#if !defined(CONFIG_FREERTOS_UNICORE) && defined(CONFIG_SPIRAM_SUPPORT) +/* + * This parameter is indicates the response time of tg1 watchdog to + * identify the live lock, + */ +#define TG1_WDT_LIVELOCK_TIMEOUT_MS (20) + +extern uint32_t _l4_intr_livelock_counter, _l4_intr_livelock_max; +#endif //Take care: the tick hook can also be called before esp_int_wdt_init() is called. #if CONFIG_INT_WDT_CHECK_CPU1 @@ -50,7 +59,12 @@ static void IRAM_ATTR tick_hook(void) { //Only feed wdt if app cpu also ticked. if (int_wdt_app_cpu_ticked) { TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX + _l4_intr_livelock_counter = 0; + TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2/(_l4_intr_livelock_max+1); //Set timeout before interrupt +#else TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt +#endif TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset TIMERG1.wdt_feed=1; TIMERG1.wdt_wprotect=0; @@ -92,9 +106,21 @@ void esp_int_wdt_init() { void esp_int_wdt_cpu_init() { + assert((CONFIG_INT_WDT_TIMEOUT_MS >= (portTICK_PERIOD_MS<<1)) && "Interrupt watchdog timeout needs to meet double SysTick period!"); esp_register_freertos_tick_hook_for_cpu(tick_hook, xPortGetCoreID()); ESP_INTR_DISABLE(WDT_INT_NUM); intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, WDT_INT_NUM); +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX + /* + * This is a workaround for issue 3.15 in "ESP32 ECO and Workarounds for Bugs" document. + */ + _l4_intr_livelock_max = 0; + if (soc_has_cache_lock_bug()) { + assert((portTICK_PERIOD_MS<<1) <= TG1_WDT_LIVELOCK_TIMEOUT_MS); + assert(CONFIG_INT_WDT_TIMEOUT_MS >= (TG1_WDT_LIVELOCK_TIMEOUT_MS*3)); + _l4_intr_livelock_max = CONFIG_INT_WDT_TIMEOUT_MS/TG1_WDT_LIVELOCK_TIMEOUT_MS - 1; + } +#endif //We do not register a handler for the interrupt because it is interrupt level 4 which //is not servicable from C. Instead, xtensa_vectors.S has a call to the panic handler for //this interrupt. diff --git a/components/esp32/panic.c b/components/esp32/panic.c index 15455c42d..f5eff073d 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -244,9 +244,9 @@ void panicHandler(XtExcFrame *frame) while (1); } - //The core which triggers the interrupt watchdog will delay 1 us, so the other core can save its frame. + //The core which triggers the interrupt watchdog will delay 500 us, so the other core can save its frame. if (frame->exccause == PANIC_RSN_INTWDT_CPU0 || frame->exccause == PANIC_RSN_INTWDT_CPU1) { - ets_delay_us(1); + ets_delay_us(500); } if (frame->exccause == PANIC_RSN_CACHEERR && esp_cache_err_get_cpuid() != core_id) { diff --git a/components/esp32/system_api.c b/components/esp32/system_api.c index 0dc168378..2cc8303fc 100644 --- a/components/esp32/system_api.c +++ b/components/esp32/system_api.c @@ -379,3 +379,8 @@ void esp_chip_info(esp_chip_info_t* out_info) out_info->features |= CHIP_FEATURE_EMB_FLASH; } } + +inline bool soc_has_cache_lock_bug(void) +{ + return (esp_efuse_get_chip_ver() == 3); +}