diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 92081e35f4..dbe79dcdb4 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -4,6 +4,11 @@ menu "ESP32-specific" # not working so we just hide all items here visible if IDF_TARGET_ESP32 + config ESP32_ECO3_CACHE_LOCK_FIX + bool + default y + depends on !FREERTOS_UNICORE && ESP32_SPIRAM_SUPPORT + choice ESP32_REV_MIN prompt "Minimum Supported ESP32 Revision" default ESP32_REV_MIN_0 @@ -19,6 +24,7 @@ menu "ESP32-specific" bool "Rev 2" config ESP32_REV_MIN_3 bool "Rev 3" + select ESP_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 27f646c319..2aa6d9c462 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -407,6 +407,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/system_api_esp32.c b/components/esp32/system_api_esp32.c index b93c222ce4..251c46e6e5 100644 --- a/components/esp32/system_api_esp32.c +++ b/components/esp32/system_api_esp32.c @@ -161,3 +161,10 @@ void esp_chip_info(esp_chip_info_t* out_info) out_info->features |= CHIP_FEATURE_EMB_FLASH; } } + +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +inline bool soc_has_cache_lock_bug(void) +{ + return (esp_efuse_get_chip_ver() == 3); +} +#endif diff --git a/components/esp_common/src/int_wdt.c b/components/esp_common/src/int_wdt.c index 2e205192f6..77b2e09389 100644 --- a/components/esp_common/src/int_wdt.c +++ b/components/esp_common/src/int_wdt.c @@ -34,7 +34,7 @@ #if CONFIG_ESP_INT_WDT -#define WDT_INT_NUM 24 +#define WDT_INT_NUM ETS_T1_WDT_INUM #define IWDT_INSTANCE WDT_MWDT1 #define IWDT_PRESCALER MWDT1_TICK_PRESCALER //Tick period of 500us if WDT source clock is 80MHz @@ -42,6 +42,16 @@ #define IWDT_INITIAL_TIMEOUT_S 5 static wdt_hal_context_t iwdt_context; +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +/* + * This parameter is indicates the response time of Interrupt watchdog to + * identify the live lock. + */ +#define IWDT_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_ESP_INT_WDT_CHECK_CPU1 //Not static; the ISR assembly checks this. @@ -56,7 +66,13 @@ static void IRAM_ATTR tick_hook(void) { //Todo: Check if there's a way to avoid reconfiguring the stages on each feed. wdt_hal_write_protect_disable(&iwdt_context); //Reconfigure stage timeouts +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX + _l4_intr_livelock_counter = 0; + wdt_hal_config_stage(&iwdt_context, WDT_STAGE0, + CONFIG_ESP_INT_WDT_TIMEOUT_MS*1000/IWDT_TICKS_PER_US/(_l4_intr_livelock_max+1), WDT_STAGE_ACTION_INT); //Set timeout before interrupt +#else wdt_hal_config_stage(&iwdt_context, WDT_STAGE0, CONFIG_ESP_INT_WDT_TIMEOUT_MS*1000/IWDT_TICKS_PER_US, WDT_STAGE_ACTION_INT); //Set timeout before interrupt +#endif wdt_hal_config_stage(&iwdt_context, WDT_STAGE1, 2*CONFIG_ESP_INT_WDT_TIMEOUT_MS*1000/IWDT_TICKS_PER_US, WDT_STAGE_ACTION_RESET_SYSTEM); //Set timeout before reset wdt_hal_feed(&iwdt_context); wdt_hal_write_protect_enable(&iwdt_context); @@ -101,9 +117,22 @@ void esp_int_wdt_init(void) { void esp_int_wdt_cpu_init(void) { + assert((CONFIG_ESP_INT_WDT_TIMEOUT_MS >= (portTICK_PERIOD_MS<<1)) && "Interrupt watchdog timeout needs to meet twice the RTOS tick 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_counter = 0; + if (soc_has_cache_lock_bug()) { + assert((portTICK_PERIOD_MS<<1) <= IWDT_LIVELOCK_TIMEOUT_MS); + assert(CONFIG_ESP_INT_WDT_TIMEOUT_MS >= (IWDT_LIVELOCK_TIMEOUT_MS*3)); + _l4_intr_livelock_max = CONFIG_ESP_INT_WDT_TIMEOUT_MS/IWDT_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/esp_system/include/esp_system.h b/components/esp_system/include/esp_system.h index c0686739d5..91c54a197b 100644 --- a/components/esp_system/include/esp_system.h +++ b/components/esp_system/include/esp_system.h @@ -285,6 +285,17 @@ typedef struct { */ void esp_chip_info(esp_chip_info_t* out_info); +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +/** + * @brief Cache lock bug exists or not + * + * @return + * - ture : bug exists + * - false : bug not exists + */ +bool soc_has_cache_lock_bug(void); +#endif + #ifdef __cplusplus } #endif diff --git a/components/esp_system/port/esp32/dport_panic_highint_hdl.S b/components/esp_system/port/esp32/dport_panic_highint_hdl.S index 22dceddaaa..e554a42d43 100644 --- a/components/esp_system/port/esp32/dport_panic_highint_hdl.S +++ b/components/esp_system/port/esp32/dport_panic_highint_hdl.S @@ -17,10 +17,12 @@ #include #include #include "freertos/xtensa_context.h" +#include "freertos/xtensa_rtos.h" #include "esp_private/panic_reason.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_ESP_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_ESP_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_ESP_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_ESP_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_ESP_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_ESPTOOLPY_FLASHMODE_QIO) || defined(CONFIG_ESPTOOLPY_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_ESPTOOLPY_FLASHMODE_DIO) || defined(CONFIG_ESPTOOLPY_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