Ver Fonte

add soft solution for esp32 eco3 live lock issue

Li Shuai há 5 anos atrás
pai
commit
72f583dfa7

+ 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

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

+ 7 - 0
components/esp32/system_api_esp32.c

@@ -172,3 +172,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

+ 30 - 1
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.

+ 11 - 0
components/esp_system/include/esp_system.h

@@ -275,6 +275,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

+ 287 - 2
components/esp_system/port/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
+    .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
+    /* 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
+
+/*
+--------------------------------------------------------------------------------
+  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