From 75a11589a16a0f0a6e451ed2eecf9279ce2eb2bc Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Tue, 25 Oct 2016 17:05:13 +0800 Subject: [PATCH] Disable brown-out WDT, fix thread WDT, add panic reason indication to _xt_panic() --- components/esp32/Kconfig | 12 +++-- components/esp32/brownout.c | 10 +++- components/esp32/int_wdt.c | 12 +++-- components/esp32/task_wdt.c | 43 ++++++++++----- .../include/freertos/FreeRTOSConfig.h | 1 + components/freertos/include/freertos/panic.h | 13 +++++ components/freertos/panic.c | 54 ++++++++++++++++++- components/freertos/tasks.c | 1 - components/freertos/xtensa_vectors.S | 11 ++++ 9 files changed, 134 insertions(+), 23 deletions(-) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index a2faa926a8..dbd1cfb6f6 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -140,7 +140,6 @@ config ULP_COPROC_RESERVE_MEM default 0 depends on !ULP_COPROC_ENABLED -menu "Watchdogs & brown-out detection" config INT_WDT bool "Interrupt watchdog" @@ -155,7 +154,7 @@ config INT_WDT_TIMEOUT_MS int "Interrupt watchdog timeout (ms)" depends on INT_WDT default 10 - range INT_WDT_TIMEOUT_MIN 10000 + range 10 10000 help The timeout of the watchdog, in miliseconds. Make this higher than the FreeRTOS tick rate. @@ -190,11 +189,15 @@ config TASK_WDT_CHECK_IDLE_TASK With this turned on, the task WDT can detect if the idle task is not called within the task watchdog timeout period. The idle task not being called usually is a symptom of another task hoarding the CPU. It is also a bad thing because FreeRTOS household tasks depend on the - idle task getting some runtime every now and then. + idle task getting some runtime every now and then. Take Care: With this disabled, this + watchdog will trigger if no tasks register themselves within the timeout value. +#The brownout detector code is disabled (by making it depend on a nonexisting symbol) because the current revision of ESP32 +#silicon has a bug in the brown-out detector, rendering it unusable for resetting the CPU. config BROWNOUT_DET bool "Hardware brownout detect & reset" default y + depends on NEEDS_ESP32_NEW_SILICON_REV help The ESP32 has a built-in brownout detector which can detect if the voltage is lower than a specific value. If this happens, it will reset the chip in order to prevent unintended @@ -207,6 +210,8 @@ choice BROWNOUT_DET_LVL_SEL help The brownout detector will reset the chip when the supply voltage is below this level. +#The voltage levels here are estimates, more work needs to be done to figure out the exact voltages +#of the brownout threshold levels. config BROWNOUT_DET_LVL_SEL_0 bool "2.1V" config BROWNOUT_DET_LVL_SEL_1 @@ -246,7 +251,6 @@ config BROWNOUT_DET_RESETDELAY The brownout detector can reset the chip after a certain delay, in order to make sure e.g. a voltage dip has entirely passed before trying to restart the chip. You can set the delay here. -endmenu diff --git a/components/esp32/brownout.c b/components/esp32/brownout.c index 8e8805b8e5..97846ae8c0 100644 --- a/components/esp32/brownout.c +++ b/components/esp32/brownout.c @@ -22,10 +22,18 @@ #include "soc/rtc_cntl_reg.h" +#if CONFIG_BROWNOUT_DET +/* +This file ins included in esp-idf, but the menuconfig option for this is disabled because a silicon bug +prohibits the brownout detector from functioning correctly on the ESP32. +*/ + void esp_brownout_init() { WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, RTC_CNTL_BROWN_OUT_ENA | (CONFIG_BROWNOUT_DET_LVL << RTC_CNTL_DBROWN_OUT_THRES_S) | RTC_CNTL_BROWN_OUT_RST_ENA | (((CONFIG_BROWNOUT_DET_RESETDELAY*150)/1000) << RTC_CNTL_BROWN_OUT_RST_WAIT_S) | RTC_CNTL_BROWN_OUT_PD_RF_ENA|RTC_CNTL_BROWN_OUT_CLOSE_FLASH_ENA); -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c index 2b4d9e0841..5fb7a63bae 100644 --- a/components/esp32/int_wdt.c +++ b/components/esp32/int_wdt.c @@ -44,7 +44,6 @@ This uses the TIMERG1 WDT. #define WDT_INT_NUM 24 - #define WDT_WRITE_KEY 0x50D83AA1 void int_wdt_init() { @@ -55,11 +54,15 @@ void int_wdt_init() { TIMERG1.wdt_config0.stg0=1; //1st stage timeout: interrupt TIMERG1.wdt_config0.stg1=3; //2nd stage timeout: reset system TIMERG1.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS - TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt - TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset + //The timer configs initially are set to 5 seconds, to make sure the CPU can start up. The tick hook sets + //it to their actual value. + TIMERG1.wdt_config2=10000; + TIMERG1.wdt_config3=10000; TIMERG1.wdt_config0.en=1; TIMERG1.wdt_feed=1; TIMERG1.wdt_wprotect=0; + TIMERG1.int_clr_timers.wdt=1; + TIMERG1.int_ena.wdt=1; ESP_INTR_DISABLE(WDT_INT_NUM); intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, WDT_INT_NUM); //We do not register a handler for the interrupt because it is interrupt level 4 which @@ -69,9 +72,10 @@ void int_wdt_init() { } - void vApplicationTickHook(void) { TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config2=CONFIG_INT_WDT_TIMEOUT_MS*2; //Set timeout before interrupt + TIMERG1.wdt_config3=CONFIG_INT_WDT_TIMEOUT_MS*4; //Set timeout before reset TIMERG1.wdt_feed=1; TIMERG1.wdt_wprotect=0; } diff --git a/components/esp32/task_wdt.c b/components/esp32/task_wdt.c index 7ae3dfb2e8..3e4c436394 100644 --- a/components/esp32/task_wdt.c +++ b/components/esp32/task_wdt.c @@ -33,6 +33,7 @@ This uses the TIMERG0 WDT. #include #include "esp_err.h" #include "esp_intr.h" +#include "esp_attr.h" #include "soc/timer_group_struct.h" #include "esp_log.h" @@ -42,7 +43,6 @@ This uses the TIMERG0 WDT. static const char* TAG = "task_wdt"; - typedef struct wdt_task_t wdt_task_t; struct wdt_task_t { TaskHandle_t task_handle; @@ -50,16 +50,35 @@ struct wdt_task_t { wdt_task_t *next; }; - static wdt_task_t *wdt_task_list=NULL; +//We use this interrupt number on whatever task calls task_wdt_init. #define WDT_INT_NUM 24 - #define WDT_WRITE_KEY 0x50D83AA1 -static void task_wdt_isr(void *arg) { +static void IRAM_ATTR task_wdt_isr(void *arg) { + wdt_task_t *wdttask; + const char *cpu; + //Feed the watchdog so we do not reset + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_feed=1; + TIMERG0.wdt_wprotect=0; + //Ack interrupt + TIMERG0.int_clr_timers.wdt=1; + //Watchdog got triggered because at least one task did not report in. + ets_printf("Task watchdog got triggered. The following tasks did not feed the watchdog in time:\n"); + for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) { + if (!wdttask->fed_watchdog) { + cpu=xTaskGetAffinity(wdttask->task_handle)==0?"CPU 0":"CPU 1"; + if (xTaskGetAffinity(wdttask->task_handle)==tskNO_AFFINITY) cpu="CPU 0/1"; + printf(" - %s (%s)\n", pcTaskGetTaskName(wdttask->task_handle), cpu); + } + } +#if CONFIG_TASK_WDT_PANIC + ets_printf("Aborting.\n"); abort(); +#endif } @@ -69,7 +88,7 @@ void task_wdt_feed() { TaskHandle_t handle=xTaskGetCurrentTaskHandle(); //Walk the linked list of wdt tasks to find this one, as well as see if we need to feed //the real watchdog timer. - while (wdttask!=NULL) { + for (wdttask=wdt_task_list; wdttask!=NULL; wdttask=wdttask->next) { //See if we are at the current task. if (wdttask->task_handle == handle) { wdttask->fed_watchdog=true; @@ -77,8 +96,6 @@ void task_wdt_feed() { } //If even one task in the list doesn't have the do_feed_wdt var set, we do not feed the watchdog. if (!wdttask->fed_watchdog) do_feed_wdt=false; - //Next entry. - wdttask=wdttask->next; } if (!found_task) { @@ -91,9 +108,8 @@ void task_wdt_feed() { if (wdt_task_list == NULL) { wdt_task_list=newtask; } else { - wdttask=wdt_task_list; - while (!(wdttask->next == NULL)) wdttask=wdttask->next; - wdttask->next=wdttask; + for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) ; + wdttask->next=newtask; } } if (do_feed_wdt) { @@ -101,6 +117,8 @@ void task_wdt_feed() { TIMERG0.wdt_wprotect=WDT_WRITE_KEY; TIMERG0.wdt_feed=1; TIMERG0.wdt_wprotect=0; + //Reset fed_watchdog status + for (wdttask=wdt_task_list; wdttask->next!=NULL; wdttask=wdttask->next) wdttask->fed_watchdog=false; } } @@ -143,12 +161,13 @@ void task_wdt_init() { TIMERG0.wdt_feed=1; TIMERG0.wdt_wprotect=0; ESP_INTR_DISABLE(ETS_T0_WDT_INUM); - intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, ETS_T0_WDT_INUM); + intr_matrix_set(xPortGetCoreID(), ETS_TG0_WDT_LEVEL_INTR_SOURCE, ETS_T0_WDT_INUM); xt_set_interrupt_handler(ETS_T0_WDT_INUM, task_wdt_isr, NULL); + TIMERG0.int_clr_timers.wdt=1; + TIMERG0.int_ena.wdt=1; ESP_INTR_ENABLE(ETS_T0_WDT_INUM); } - #if CONFIG_TASK_WDT_CHECK_IDLE_TASK void vApplicationIdleHook(void) { task_wdt_feed(); diff --git a/components/freertos/include/freertos/FreeRTOSConfig.h b/components/freertos/include/freertos/FreeRTOSConfig.h index a5483d6bbd..b732d1c07b 100644 --- a/components/freertos/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/include/freertos/FreeRTOSConfig.h @@ -231,6 +231,7 @@ #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 #define INCLUDE_uxTaskGetStackHighWaterMark 1 +#define INCLUDE_pcTaskGetTaskName 1 #if CONFIG_ENABLE_MEMORY_DEBUG #define configENABLE_MEMORY_DEBUG 1 diff --git a/components/freertos/include/freertos/panic.h b/components/freertos/include/freertos/panic.h index 9e902ed20b..ba47e3d439 100644 --- a/components/freertos/include/freertos/panic.h +++ b/components/freertos/include/freertos/panic.h @@ -1,7 +1,20 @@ #ifndef PANIC_H #define PANIC_H + +#define PANIC_RSN_NONE 0 +#define PANIC_RSN_DEBUGEXCEPTION 1 +#define PANIC_RSN_DOUBLEEXCEPTION 2 +#define PANIC_RSN_KERNELEXCEPTION 3 +#define PANIC_RSN_COPROCEXCEPTION 4 +#define PANIC_RSN_INTWDT 5 +#define PANIC_RSN_MAX 5 + + +#ifndef __ASSEMBLER__ + void setBreakpointIfJtag(void *fn); +#endif #endif \ No newline at end of file diff --git a/components/freertos/panic.c b/components/freertos/panic.c index 6a87679d13..0c912e56fc 100644 --- a/components/freertos/panic.c +++ b/components/freertos/panic.c @@ -25,8 +25,13 @@ #include "soc/io_mux_reg.h" #include "soc/dport_reg.h" #include "soc/rtc_cntl_reg.h" +#include "soc/timer_group_struct.h" #include "gdbstub.h" +#include "panic.h" + +#define WDT_WRITE_KEY 0x50D83AA1 + /* Panic handlers; these get called when an unhandled exception occurs or the assembly-level @@ -130,10 +135,25 @@ static int inOCDMode() { } void panicHandler(XtExcFrame *frame) { + int *regs=(int*)frame; + //Please keep in sync with PANIC_RSN_* defines + const char *reasons[]={ + "Unknown reason", + "Unhandled debug exception", + "Double exception", + "Unhandled kernel exception", + "Coprocessor exception", + "Interrupt wdt timeout" + }; + const char *reason=reasons[0]; + //The panic reason is stored in the EXCCAUSE register. + if (regs[20]<=PANIC_RSN_MAX) reason=reasons[regs[20]]; haltOtherCore(); panicPutStr("Guru Meditation Error: Core "); panicPutDec(xPortGetCoreID()); - panicPutStr(" panic'ed.\r\n"); + panicPutStr(" panic'ed ("); + panicPutStr(reason); + panicPutStr(")\r\n"); if (inOCDMode()) { asm("break.n 1"); @@ -175,6 +195,33 @@ void xt_unhandled_exception(XtExcFrame *frame) { } +//Disables all but one WDT, and allows enough time on that WDT to do what we need to do. +static void reconfigureAllWdts() { + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_feed=1; + TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS + TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS + TIMERG0.wdt_config0.stg0=3; //1st stage timeout: reset system + TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS + TIMERG0.wdt_config2=2000; //1 second before reset + TIMERG0.wdt_config0.en=1; + TIMERG0.wdt_wprotect=0; + //Disable wdt 1 + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config0.en=0; + TIMERG1.wdt_wprotect=0; +} + +static void disableAllWdts() { + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_config0.en=0; + TIMERG0.wdt_wprotect=0; + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config0.en=0; + TIMERG0.wdt_wprotect=0; +} + + /* We arrive here after a panic or unhandled exception, when no OCD is detected. Dump the registers to the serial port and either jump to the gdb stub, halt the CPU or reboot. @@ -187,6 +234,9 @@ void commonErrorHandler(XtExcFrame *frame) { "A6 ","A7 ","A8 ","A9 ","A10 ","A11 ","A12 ","A13 ", "A14 ","A15 ","SAR ","EXCCAUSE","EXCVADDR","LBEG ","LEND ","LCOUNT "}; + //Feed the watchdogs, so they will give us time to print out debug info + reconfigureAllWdts(); + panicPutStr("Register dump:\r\n"); for (x=0; x<24; x+=4) { @@ -201,6 +251,7 @@ void commonErrorHandler(XtExcFrame *frame) { panicPutStr("\r\n"); } #if CONFIG_FREERTOS_PANIC_GDBSTUB + disableAllWdts(); panicPutStr("Entering gdb stub now.\r\n"); gdbstubPanicHandler(frame); #elif CONFIG_FREERTOS_PANIC_PRINT_REBOOT || CONFIG_FREERTOS_PANIC_SILENT_REBOOT @@ -208,6 +259,7 @@ void commonErrorHandler(XtExcFrame *frame) { for (x=0; x<100; x++) ets_delay_us(1000); software_reset(); #else + disableAllWdts(); panicPutStr("CPU halted.\r\n"); while(1); #endif diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 3cde3bf136..221bf476c6 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -1832,7 +1832,6 @@ UBaseType_t uxTaskGetNumberOfTasks( void ) /*-----------------------------------------------------------*/ #if ( INCLUDE_pcTaskGetTaskName == 1 ) - char *pcTaskGetTaskName( TaskHandle_t xTaskToQuery ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ { TCB_t *pxTCB; diff --git a/components/freertos/xtensa_vectors.S b/components/freertos/xtensa_vectors.S index 89a47f7415..5b31eb0a87 100644 --- a/components/freertos/xtensa_vectors.S +++ b/components/freertos/xtensa_vectors.S @@ -91,6 +91,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *******************************************************************************/ #include "xtensa_rtos.h" +#include "panic.h" /* Define for workaround: pin no-cpu-affinity tasks to a cpu when fpu is used. @@ -462,6 +463,8 @@ _DebugExceptionVector: jx a3 #else wsr a0, EXCSAVE+XCHAL_DEBUGLEVEL /* save original a0 somewhere */ + movi a0,PANIC_RSN_DEBUGEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* does not return */ rfi XCHAL_DEBUGLEVEL /* make a0 point here not later */ #endif @@ -489,6 +492,8 @@ _DoubleExceptionVector: #if XCHAL_HAVE_DEBUG break 1, 4 /* unhandled double exception */ #endif + movi a0,PANIC_RSN_DOUBLEEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* does not return */ rfde /* make a0 point here not later */ @@ -522,6 +527,8 @@ _xt_kernel_exc: #if XCHAL_HAVE_DEBUG break 1, 0 /* unhandled kernel exception */ #endif + movi a0,PANIC_RSN_KERNELEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* does not return */ rfe /* make a0 point here not there */ @@ -1024,6 +1031,8 @@ _xt_coproc_exc: #if XCHAL_HAVE_DEBUG break 1, 1 /* unhandled user exception */ #endif + movi a0,PANIC_RSN_COPROCEXCEPTION + wsr a0,EXCCAUSE call0 _xt_panic /* not in a thread (invalid) */ /* never returns */ @@ -1611,6 +1620,8 @@ _xt_highint4: off and the CPU should panic. */ rsr a0, EXCSAVE_4 wsr a0, EXCSAVE_1 /* panic handler reads this register */ + movi a0,PANIC_RSN_INTWDT + wsr a0,EXCCAUSE call0 _xt_panic