Jelajahi Sumber

add soft solution for esp32 eco3 live lock issue

Li Shuai 5 tahun lalu
induk
melakukan
b89a2bb266

+ 5 - 0
components/efuse/include/esp_efuse.h

@@ -380,6 +380,11 @@ esp_err_t esp_efuse_batch_write_cancel(void);
  */
 esp_err_t esp_efuse_batch_write_commit(void);
 
+inline static bool soc_has_cache_lock_bug(void)
+{
+    return (esp_efuse_get_chip_ver() == 3);
+}
+
 #ifdef __cplusplus
 }
 #endif

+ 6 - 0
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

+ 4 - 0
components/esp32/cpu_start.c

@@ -390,6 +390,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();

+ 287 - 2
components/esp32/dport_panic_highint_hdl.S

@@ -17,10 +17,12 @@
 #include <xtensa/corebits.h>
 #include <xtensa/config/system.h>
 #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, 240
+# 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

+ 29 - 1
components/esp32/int_wdt.c

@@ -31,12 +31,22 @@
 #include "driver/periph_ctrl.h"
 #include "esp_int_wdt.h"
 #include "hal/timer_ll.h"
+#include "esp_efuse.h"
 
 #if CONFIG_ESP_INT_WDT
 
 #define TG1_WDT_TICK_US 500
-#define WDT_INT_NUM 24
+#define WDT_INT_NUM ETS_T1_WDT_INUM
 
+#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX
+/*
+ * 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_ESP_INT_WDT_CHECK_CPU1
@@ -51,8 +61,14 @@ static void IRAM_ATTR tick_hook(void) {
         if (int_wdt_app_cpu_ticked) {
             timer_ll_wdt_set_protect(&TIMERG1, false);
             //Set timeout before interrupt
+#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX
+            _l4_intr_livelock_counter = 0;
+            timer_ll_wdt_set_timeout(&TIMERG1, 0,
+                CONFIG_ESP_INT_WDT_TIMEOUT_MS*1000/TG1_WDT_TICK_US/(_l4_intr_livelock_max+1));
+#else
             timer_ll_wdt_set_timeout(&TIMERG1, 0,
                 CONFIG_ESP_INT_WDT_TIMEOUT_MS*1000/TG1_WDT_TICK_US);
+#endif
             //Set timeout before reset
             timer_ll_wdt_set_timeout(&TIMERG1, 1,
                 2*CONFIG_ESP_INT_WDT_TIMEOUT_MS*1000/TG1_WDT_TICK_US);
@@ -99,9 +115,21 @@ 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 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_ESP_INT_WDT_TIMEOUT_MS >= (TG1_WDT_LIVELOCK_TIMEOUT_MS*3));
+        _l4_intr_livelock_max = CONFIG_ESP_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.

+ 2 - 2
components/esp32/panic.c

@@ -247,9 +247,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) {