OVMS3-idf/components/esp32/task_wdt.c
Darian Leung 616baa239d esp32: New Task Watchdog API
Legacy API of task watchdog used the same function esp_task_wdt_feed() to add
and feed a task. This caused issues of implicitly adding a task to the wdt list
if the function was used in shared code.

The new API introduces init, adding, feeding, deleting, deinit functions. Tasks
must now be explicitly added to the task watchdog using their handles. Deletion
must also be explicit using task handles. This resolves the issue of implicit
task additions to the task watchdog due to shared code calling
esp_task_wdt_feed().

Task watchdog is now fully configurable at runtime by calling the init and
deinit functions.

Also added functions to get the handles of idle tasks of the other core. This
helps when adding idle tasks to the watchdog at run time.

Configuring the task watchdog using menu config is still available, however
menu config will only result in calling the init and add functions for idle
tasks shortly after the scheduler starts.

Menu config also allows for using legacy behavior, however the legacy behavior
willcall the new API functions but with slight variations to make them legacy
compatible.

Documentation and example have also been updated

gcov_rtio.c headers updated to prevent error of freertos header files being
included in the wrong order.

Resolves issue TW#13265
2017-09-29 23:10:55 +08:00

383 lines
14 KiB
C

// 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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOSConfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include <esp_types.h>
#include "esp_err.h"
#include "esp_intr.h"
#include "esp_intr_alloc.h"
#include "esp_ipc.h"
#include "esp_attr.h"
#include "esp_freertos_hooks.h"
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
#include "esp_log.h"
#include "driver/timer.h"
#include "esp_task_wdt.h"
//Assertion macro that exits from critical section and returns 'ret'
#define ASSERT_EXIT_CRIT_RET_ERR(cond, ret) ({ \
if(!(cond)){ \
portEXIT_CRITICAL(&taskwdt_spinlock); \
return ret; \
} \
})
typedef struct wdt_task_t wdt_task_t;
struct wdt_task_t {
TaskHandle_t task_handle;
bool fed;
wdt_task_t *next;
};
typedef struct task_wdt_config_t task_wdt_config_t;
struct task_wdt_config_t {
wdt_task_t *list;
uint32_t timeout;
bool panic;
bool idle_enable[portNUM_PROCESSORS];
TaskHandle_t idle_handles[portNUM_PROCESSORS];
intr_handle_t intr_handle;
};
static task_wdt_config_t *wdt_config = NULL;
static portMUX_TYPE taskwdt_spinlock = portMUX_INITIALIZER_UNLOCKED;
/*
* Internal function that checks the list for target task. Returns the list item
* if found and returns null if not found. Also checks if the all the other
* tasks have checked in. Called within critical.
*/
static wdt_task_t *check_list(TaskHandle_t handle, bool *checked)
{
wdt_task_t *target = NULL;
*checked = true;
for(wdt_task_t *task = wdt_config->list; task != NULL; task = task->next){
if(task->task_handle == handle){
target = task; //Get pointer to target task list member
}else{
if(task->fed == false){ //If a task has yet to check in
*checked = false;
}
}
}
return target;
}
/*
* Resets the wdt and fed flags of each task on the list. Called within critical
*/
static void feed_wdt()
{
//All tasks have checked in; time to feed the hw watchdog.
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
TIMERG0.wdt_feed=1;
TIMERG0.wdt_wprotect=0;
//Reset fed flags on all tasks in list
for (wdt_task_t *task = wdt_config->list; task != NULL; task = task->next){
task->fed=false;
}
}
/*
* ISR for when wdt expires. Checks for which tasks have not fed. Trigger panic
* if configured to do so in initialization
*/
static void task_wdt_isr(void *arg)
{
portENTER_CRITICAL(&taskwdt_spinlock);
wdt_task_t *wdttask;
const char *cpu;
//Feed the watchdog so we don't reset
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
TIMERG0.wdt_feed=1;
TIMERG0.wdt_wprotect=0;
//Ack interrupt
TIMERG0.int_clr_timers.wdt=1;
//We are taking a spinlock while doing I/O (ets_printf) here. Normally, that is a pretty
//bad thing, possibly (temporarily) hanging up the 2nd core and stopping FreeRTOS. In this case,
//something bad already happened and reporting this is considered more important
//than the badness caused by a spinlock here.
if (!wdt_config->list) { //No tasks on list
portEXIT_CRITICAL(&taskwdt_spinlock);
return;
}
//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_config->list; wdttask!=NULL; wdttask=wdttask->next) {
if (!wdttask->fed) {
cpu=xTaskGetAffinity(wdttask->task_handle)==0?DRAM_STR("CPU 0"):DRAM_STR("CPU 1");
if (xTaskGetAffinity(wdttask->task_handle)==tskNO_AFFINITY) cpu=DRAM_STR("CPU 0/1");
ets_printf(" - %s (%s)\n", pcTaskGetTaskName(wdttask->task_handle), cpu);
}
}
ets_printf(DRAM_STR("Tasks currently running:\n"));
for (int x=0; x<portNUM_PROCESSORS; x++) {
ets_printf("CPU %d: %s\n", x, pcTaskGetTaskName(xTaskGetCurrentTaskHandleForCPU(x)));
}
if (wdt_config->panic){
ets_printf("Aborting.\n");
portEXIT_CRITICAL(&taskwdt_spinlock);
abort();
}
portEXIT_CRITICAL(&taskwdt_spinlock);
}
/*
* Idle hook for Idle Task to feed the wdt. Will only call feed if the idle task
* of that core has been added to wdt task list.
*/
static bool idle_hook(void)
{
uint32_t core_id = xPortGetCoreID();
if (wdt_config->idle_enable[core_id]){
esp_task_wdt_feed();
}
return true;
}
/*
* Initializes the task watchdog timer by allocating memory for the config data
* structure, obtaining the idle task handles/registering idle hooks, and
* setting the hardware timer registers. If reconfiguring, it will just modify
* wdt_config and reset the hardware timer.
*/
esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic)
{
portENTER_CRITICAL(&taskwdt_spinlock);
if(wdt_config == NULL){ //wdt not initialized yet
//Allocate memory for wdt_config
wdt_config = calloc(1, sizeof(task_wdt_config_t));
ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_NO_MEM);
wdt_config->list = NULL;
wdt_config->timeout = timeout;
wdt_config->panic = panic;
//Get idle task handles
for(int i = 0; i < portNUM_PROCESSORS; i++){
wdt_config->idle_handles[i] = xTaskGetIdleTaskHandleForCPU( i );
wdt_config->idle_enable[i] = false;
}
//Register Idle Hook and Interrupts
esp_register_freertos_idle_hook(idle_hook);
ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &wdt_config->intr_handle))
//Configure hardware timer
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
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=TIMG_WDT_STG_SEL_INT; //1st stage timeout: interrupt
TIMERG0.wdt_config0.stg1=TIMG_WDT_STG_SEL_RESET_SYSTEM; //2nd stage timeout: reset system
TIMERG0.wdt_config1.clk_prescale=80*500; //Prescaler: wdt counts in ticks of 0.5mS
TIMERG0.wdt_config2=wdt_config->timeout*2; //Set timeout before interrupt
TIMERG0.wdt_config3=wdt_config->timeout*4; //Set timeout before reset
TIMERG0.wdt_config0.en=1;
TIMERG0.wdt_feed=1;
TIMERG0.wdt_wprotect=0; //Enable write protection
}else{ //wdt_config previously initialized
//Reconfigure task wdt
wdt_config->panic = panic;
wdt_config->timeout = timeout;
//Reconfigure hardware timer
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
TIMERG0.wdt_config0.en=0; //Disable timer
TIMERG0.wdt_config2=wdt_config->timeout*2; //Set timeout before interrupt
TIMERG0.wdt_config3=wdt_config->timeout*4; //Set timeout before reset
TIMERG0.wdt_config0.en=1; //Renable timer
TIMERG0.wdt_feed=1; //Reset timer
TIMERG0.wdt_wprotect=0; //Enable write protection
}
portEXIT_CRITICAL(&taskwdt_spinlock);
return ESP_OK;
}
esp_err_t esp_task_wdt_deinit()
{
portENTER_CRITICAL(&taskwdt_spinlock);
//wdt must already be initialized
ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE);
//Task list must be empty
ASSERT_EXIT_CRIT_RET_ERR((wdt_config->list == NULL), ESP_ERR_INVALID_STATE);
//Disable hardware timer
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; //Disable write protection
TIMERG0.wdt_config0.en=0; //Disable timer
TIMERG0.wdt_wprotect=0; //Enable write protection
esp_deregister_freertos_idle_hook(idle_hook); //deregister idle hook
ESP_ERROR_CHECK(esp_intr_free(wdt_config->intr_handle)) //Unregister interrupt
free(wdt_config); //Free wdt_config
wdt_config = NULL;
portEXIT_CRITICAL(&taskwdt_spinlock);
return ESP_OK;
}
esp_err_t esp_task_wdt_add(TaskHandle_t handle)
{
portENTER_CRITICAL(&taskwdt_spinlock); //Nested critical in Legacy (called from feed)
#ifdef CONFIG_TASK_WDT_LEGACY_BEHAVIOR
wdt_task_t *target_task;
#else
//Return error if wdt has not been initialized
ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE);
wdt_task_t *target_task;
bool all_fed;
if (handle == NULL){ //Get handle of current task if none is provided
handle = xTaskGetCurrentTaskHandle();
}
//Check if tasks exists in task list, and if all other tasks have checked in
target_task = check_list(handle, &all_fed);
//Return error if task has already been added
ASSERT_EXIT_CRIT_RET_ERR((target_task == NULL), ESP_ERR_INVALID_ARG);
#endif //CONFIG_TASK_WDT_LEGACY_BEHAVIOR
//Add task to wdt list
target_task = calloc(1,sizeof(wdt_task_t));
//Return error if calloc failed
ASSERT_EXIT_CRIT_RET_ERR((target_task != NULL), ESP_ERR_NO_MEM);
target_task->task_handle = handle;
target_task->fed = true;
target_task->next = NULL;
if (wdt_config->list == NULL) { //Adding to empty list
wdt_config->list = target_task;
} else { //Adding to tail of list
wdt_task_t *task;
for (task = wdt_config->list; task->next != NULL; task = task->next){
; //point task to current tail of wdt task list
}
task->next = target_task;
}
//If idle task, set idle_enable flag
for(int i = 0; i < portNUM_PROCESSORS; i++){
if(handle == wdt_config->idle_handles[i]){
wdt_config->idle_enable[i] = true;
break;
}
}
#ifndef CONFIG_TASK_WDT_LEGACY_BEHAVIOR
if(all_fed){ //Reset if all other tasks in list have checked in
feed_wdt();
}
#endif
portEXIT_CRITICAL(&taskwdt_spinlock); //Nested critical if Legacy
return ESP_OK;
}
esp_err_t esp_task_wdt_feed()
{
portENTER_CRITICAL(&taskwdt_spinlock);
//Return error if wdt has not been initialized
ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE);
TaskHandle_t handle = xTaskGetCurrentTaskHandle();
wdt_task_t *target_task;
bool all_fed;
//Check if tasks exists in task list, and if all other tasks have checked in
target_task = check_list(handle, &all_fed);
if(target_task == NULL){
#ifdef CONFIG_TASK_WDT_LEGACY_BEHAVIOR
//Add task to wdt task list if it doesn't exist, return error if failed to add
ASSERT_EXIT_CRIT_RET_ERR((esp_task_wdt_add(handle) == ESP_OK), ESP_ERR_NO_MEM);
#else
portEXIT_CRITICAL(&taskwdt_spinlock);
return ESP_ERR_INVALID_STATE; //Return error if task does not exist
#endif //CONFIG_TASK_WDT_LEGACY_BEHAVIOR
}else{
target_task->fed = true; //Feed the task
}
if(all_fed){ //Reset if all other tasks in list have checked in
feed_wdt();
}
portEXIT_CRITICAL(&taskwdt_spinlock);
return ESP_OK;
}
#ifdef CONFIG_TASK_WDT_LEGACY_BEHAVIOR
esp_err_t esp_task_wdt_delete()
{
TaskHandle_t handle = xTaskGetCurrentTaskHandle();
#else
esp_err_t esp_task_wdt_delete(TaskHandle_t handle)
{
if(handle == NULL){
handle = xTaskGetCurrentTaskHandle();
}
#endif //CONFIG_TASK_WDT_LEGACY_BEHAVIOR
portENTER_CRITICAL(&taskwdt_spinlock);
//Return error if wdt has not been initialized
ASSERT_EXIT_CRIT_RET_ERR((wdt_config != NULL), ESP_ERR_INVALID_STATE);
wdt_task_t *target_task;
bool all_fed;
target_task = check_list(handle, &all_fed);
//Task doesn't exist on list. Return error
ASSERT_EXIT_CRIT_RET_ERR((target_task != NULL), ESP_ERR_INVALID_ARG);
if(target_task == wdt_config->list){ //target_task is head of list. Delete
wdt_config->list = target_task->next;
free(target_task);
}else{ //target_task not head of list. Delete
wdt_task_t *prev;
for (prev = wdt_config->list; prev->next != target_task; prev = prev->next){
; //point prev to task preceding target_task
}
prev->next = target_task->next;
free(target_task);
}
//If idle task, disable idle_enable flag
for(int i = 0; i < portNUM_PROCESSORS; i++){
if(handle == wdt_config->idle_handles[i]){
wdt_config->idle_enable[i] = false;
}
}
if(all_fed){ //Reset timer if all remaining tasks have checked in
feed_wdt();
}
portEXIT_CRITICAL(&taskwdt_spinlock);
return ESP_OK;
}