diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 535df23eb5..c40d7930d0 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -140,4 +140,117 @@ config ULP_COPROC_RESERVE_MEM default 0 depends on !ULP_COPROC_ENABLED +menu "Watchdogs & brown-out detection" + +config INT_WDT + bool "Interrupt watchdog" + default y + help + This watchdog timer can detect if the FreeRTOS tick interrupt has not been called for a certain time, + either because a task turned off interrupts and did not turn them on for a long time, or because an + interrupt handler did not return. It will try to invoke the panic handler first and failing that + reset the SoC. + +config INT_WDT_TIMEOUT_MS_MIN + default (2000/CONFIG_FREERTOS_HZ) + +config INT_WDT_TIMEOUT_MS + int "Interrupt watchdog timeout (ms)" + depends on INT_WDT + default 100 + range INT_WDT_TIMEOUT_MS_MIN 10000 + help + The timeout of the watchdog, in miliseconds. + +config TASK_WDT + bool "Task watchdog" + default y + help + This watchdog timer can be used to make sure individual tasks are still running. + +config TASK_WDT_PANIC + bool "Invoke panic handler when Task Watchdog is triggered" + depends on TASK_WDT + default n + help + Normally, the Task Watchdog will only print out a warning if it detects it has not + been fed. If this is enabled, it will invoke the panic handler instead, which + can then halt or reboot the chip. + +config TASK_WDT_TIMEOUT_S + int "Task watchdog timeout (seconds)" + depends on TASK_WDT + range 1 60 + default 5 + help + Timeout for the task WDT, in seconds. + +config TASK_WDT_CHECK_IDLE_TASK + bool "Task watchdog watches idle tasks" + depends on TASK_WDT + default y + help + 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. + +config BROWNOUT_DET + bool "Hardware brownout detect & reset" + default y + 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 + behaviour. + +choice BROWNOUT_DET_LVL_SEL + prompt "Brownout voltage level" + depends on BROWNOUT_DET + default BROWNOUT_DET_LVL_SEL_25 + help + The brownout detector will reset the chip when the supply voltage is below this level. + +config BROWNOUT_DET_LVL_SEL_0 + bool "2.1V" +config BROWNOUT_DET_LVL_SEL_1 + bool "2.2V" +config BROWNOUT_DET_LVL_SEL_2 + bool "2.3V" +config BROWNOUT_DET_LVL_SEL_3 + bool "2.4V" +config BROWNOUT_DET_LVL_SEL_4 + bool "2.5V" +config BROWNOUT_DET_LVL_SEL_5 + bool "2.6V" +config BROWNOUT_DET_LVL_SEL_6 + bool "2.7V" +config BROWNOUT_DET_LVL_SEL_7 + bool "2.8V" +endchoice + +config BROWNOUT_DET_LVL + int + default 0 if BROWNOUT_DET_LVL_SEL_0 + default 1 if BROWNOUT_DET_LVL_SEL_1 + default 2 if BROWNOUT_DET_LVL_SEL_2 + default 3 if BROWNOUT_DET_LVL_SEL_3 + default 4 if BROWNOUT_DET_LVL_SEL_4 + default 5 if BROWNOUT_DET_LVL_SEL_5 + default 6 if BROWNOUT_DET_LVL_SEL_6 + default 7 if BROWNOUT_DET_LVL_SEL_7 + + +config BROWNOUT_DET_RESETDELAY + int "Brownout reset delay (in uS)" + depends on BROWNOUT_DET + range 0 6820 + default 1000 + help + 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 + + + endmenu diff --git a/components/esp32/brownout.c b/components/esp32/brownout.c new file mode 100644 index 0000000000..173d63a0c1 --- /dev/null +++ b/components/esp32/brownout.c @@ -0,0 +1,37 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#include +#include +#include +#include +#include "sdkconfig.h" +#include "soc/soc.h" +#include "soc/rtc_cntl_reg.h" + + +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); + + + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, + RTC_CNTL_BROWN_OUT_ENA | (4 << RTC_CNTL_DBROWN_OUT_THRES_S) | + RTC_CNTL_BROWN_OUT_RST_ENA | (0x3FF << 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 diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 7b2ccdc609..6a482c539c 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -43,6 +43,8 @@ #include "esp_ipc.h" #include "esp_log.h" +#include "esp_brownout.h" + void start_cpu0(void) __attribute__((weak, alias("start_cpu0_default"))); void start_cpu0_default(void) IRAM_ATTR; #if !CONFIG_FREERTOS_UNICORE @@ -137,6 +139,16 @@ void start_cpu0_default(void) do_global_ctors(); esp_ipc_init(); spi_flash_init(); +#if CONFIG_BROWNOUT_DET + esp_brownout_init(); +#endif +#if CONFIG_INT_WDT + int_wdt_init() +#endif +#if CONFIG_TASK_WDT + task_wdt_init() +#endif + xTaskCreatePinnedToCore(&main_task, "main", ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, NULL, 0); diff --git a/components/esp32/include/esp_brownout.h b/components/esp32/include/esp_brownout.h new file mode 100644 index 0000000000..acce05e0db --- /dev/null +++ b/components/esp32/include/esp_brownout.h @@ -0,0 +1,6 @@ +#ifndef BROWNOUT_H +#define BROWNOUT_H + +void esp_brownout_init(); + +#endif \ No newline at end of file diff --git a/components/esp32/include/esp_int_wdt.h b/components/esp32/include/esp_int_wdt.h new file mode 100644 index 0000000000..81404e3053 --- /dev/null +++ b/components/esp32/include/esp_int_wdt.h @@ -0,0 +1,6 @@ +#ifndef INT_WDT_H +#define INT_WDT_H + +void int_wdt_init(); + +#endif \ No newline at end of file diff --git a/components/esp32/include/esp_task_wdt.h b/components/esp32/include/esp_task_wdt.h new file mode 100644 index 0000000000..9163e6907a --- /dev/null +++ b/components/esp32/include/esp_task_wdt.h @@ -0,0 +1,8 @@ +#ifndef TASK_WDT_H +#define TASK_WDT_H + +void task_wdt_feed(); +void task_wdt_delete(); +void task_wdt_init(); + +#endif \ No newline at end of file diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c new file mode 100644 index 0000000000..392ed6b6a1 --- /dev/null +++ b/components/esp32/int_wdt.c @@ -0,0 +1,82 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/* +This routine enables a watchdog to catch instances of processes disabling +interrupts for too long, or code within interrupt handlers taking too long. +It does this by setting up a watchdog which gets fed from the FreeRTOS +task switch interrupt. When this watchdog times out, initially it will call +a high-level interrupt routine that will panic FreeRTOS in order to allow +for forensic examination of the state of the CPU. When this interrupt +handler is not called and the watchdog times out a second time, it will +reset the SoC. + +This uses the TIMERG1 WDT. +*/ + +#include "sdkconfig.h" +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include "esp_err.h" +#include "esp_intr.h" +#include "soc/timer_group_struct.h" + +#include "esp_int_wdt.h" + +#if CONFIG_INT_WDT + + +#define WDT_INT_NUM 24 + + +#define WDT_WRITE_KEY 0x50D83AA1 + +static void esp_int_wdt_isr(void *arg) { + abort(); +} + + +void int_wdt_init() { + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_config0.sys_reset_length=7; //3.2uS + TIMERG1.wdt_config0.cpu_reset_length=7; //3.2uS + TIMERG1.wdt_config0.level_int_en=1; + 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 + TIMERG1.wdt_config0.en=1; + TIMERG1.wdt_feed=1; + TIMERG1.wdt_wprotect=0; + ESP_INTR_DISABLE(WDT_INT_NUM); + intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, WDT_INT_NUM); + xt_set_interrupt_handler(WDT_INT_NUM, int_wdt_isr, NULL); + ESP_INTR_ENABLE(WDT_INT_NUM); +} + + + +void vApplicationTickHook(void) { + TIMERG1.wdt_wprotect=WDT_WRITE_KEY; + TIMERG1.wdt_feed=1; + TIMERG1.wdt_wprotect=0; +} + +#endif \ No newline at end of file diff --git a/components/esp32/task_wdt.c b/components/esp32/task_wdt.c new file mode 100644 index 0000000000..7ae3dfb2e8 --- /dev/null +++ b/components/esp32/task_wdt.c @@ -0,0 +1,158 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/* +This routine enables a more general-purpose task watchdog: tasks can individually +feed the watchdog and the watchdog will bark if one or more tasks haven't fed the +watchdog within the specified time. Optionally, the idle tasks can also configured +to feed the watchdog in a similar fashion, to detect CPU starvation. + +This uses the TIMERG0 WDT. +*/ + +#include +#include +#include +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include "esp_err.h" +#include "esp_intr.h" +#include "soc/timer_group_struct.h" +#include "esp_log.h" + +#include "esp_task_wdt.h" + +#if CONFIG_TASK_WDT + +static const char* TAG = "task_wdt"; + + +typedef struct wdt_task_t wdt_task_t; +struct wdt_task_t { + TaskHandle_t task_handle; + bool fed_watchdog; + wdt_task_t *next; +}; + + +static wdt_task_t *wdt_task_list=NULL; + +#define WDT_INT_NUM 24 + + +#define WDT_WRITE_KEY 0x50D83AA1 + +static void task_wdt_isr(void *arg) { + abort(); +} + + +void task_wdt_feed() { + wdt_task_t *wdttask=wdt_task_list; + bool found_task=false, do_feed_wdt=true; + 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) { + //See if we are at the current task. + if (wdttask->task_handle == handle) { + wdttask->fed_watchdog=true; + found_task=true; + } + //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) { + //This is the first time the task calls the task_wdt_feed function. Create a new entry for it in + //the linked list. + wdt_task_t *newtask=malloc(sizeof(wdt_task_t)); + memset(newtask, 0, sizeof(wdt_task_t)); + newtask->task_handle=handle; + newtask->fed_watchdog=true; + if (wdt_task_list == NULL) { + wdt_task_list=newtask; + } else { + wdttask=wdt_task_list; + while (!(wdttask->next == NULL)) wdttask=wdttask->next; + wdttask->next=wdttask; + } + } + if (do_feed_wdt) { + //All tasks have checked in; time to feed the hw watchdog. + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_feed=1; + TIMERG0.wdt_wprotect=0; + } +} + +void task_wdt_delete() { + TaskHandle_t handle=xTaskGetCurrentTaskHandle(); + wdt_task_t *wdttask=wdt_task_list; + //Wdt task list can't be empty + if (!wdt_task_list) { + ESP_LOGE(TAG, "task_wdt_delete: No tasks in list?"); + return; + } + if (handle==wdt_task_list) { + //Current task is first on list. + wdt_task_list=wdt_task_list->next; + free(wdttask); + } else { + //Find current task in list + while (wdttask->next!=NULL && wdttask->next->task_handle!=handle) wdttask=wdttask->next; + if (!wdttask->next) { + ESP_LOGE(TAG, "task_wdt_delete: Task never called task_wdt_feed!"); + return; + } + wdt_task_t *freeme=wdttask->next; + wdttask->next=wdttask->next->next; + free(freeme); + } +} + +void task_wdt_init() { + TIMERG0.wdt_wprotect=WDT_WRITE_KEY; + TIMERG0.wdt_config0.sys_reset_length=7; //3.2uS + TIMERG0.wdt_config0.cpu_reset_length=7; //3.2uS + TIMERG0.wdt_config0.level_int_en=1; + TIMERG0.wdt_config0.stg0=1; //1st stage timeout: interrupt + TIMERG0.wdt_config0.stg1=3; //2nd stage timeout: reset system + TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS + TIMERG0.wdt_config2=CONFIG_TASK_WDT_TIMEOUT_S*2000; //Set timeout before interrupt + TIMERG0.wdt_config3=CONFIG_TASK_WDT_TIMEOUT_S*4000; //Set timeout before reset + TIMERG0.wdt_config0.en=1; + 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); + xt_set_interrupt_handler(ETS_T0_WDT_INUM, task_wdt_isr, NULL); + ESP_INTR_ENABLE(ETS_T0_WDT_INUM); +} + + +#if CONFIG_TASK_WDT_CHECK_IDLE_TASK +void vApplicationIdleHook(void) { + task_wdt_feed(); +} +#endif + +#endif \ No newline at end of file diff --git a/components/freertos/include/freertos/FreeRTOSConfig.h b/components/freertos/include/freertos/FreeRTOSConfig.h index d1958e7701..a5483d6bbd 100644 --- a/components/freertos/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/include/freertos/FreeRTOSConfig.h @@ -152,9 +152,9 @@ *----------------------------------------------------------*/ #define configUSE_PREEMPTION 1 -#define configUSE_IDLE_HOOK 0 +#define configUSE_IDLE_HOOK ( CONFIG_TASK_WDT_CHECK_IDLE_TASK ) -#define configUSE_TICK_HOOK 0 +#define configUSE_TICK_HOOK ( CONFIG_INT_WDT ) #define configTICK_RATE_HZ ( CONFIG_FREERTOS_HZ )