esp_system: Refactor task_wdt

This commit refactors the task watchdog as follows:

- Renamed variables, types, and functions
- Replaced manual linked list implementation with SLIST()
- Moved calloc()/free() calls out of critical sections
- Shortened ISR critical sections
- Updated API description
- Updated code formatting
This commit is contained in:
Darian Leung
2022-04-14 18:08:08 +08:00
parent 4bd5c4ef53
commit 4877a9fcba
3 changed files with 332 additions and 361 deletions

View File

@@ -1,16 +1,8 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD /*
// * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
// Licensed under the Apache License, Version 2.0 (the "License"); *
// you may not use this file except in compliance with the License. * SPDX-License-Identifier: Apache-2.0
// 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.
#pragma once #pragma once
@@ -23,125 +15,98 @@ extern "C" {
#endif #endif
/** /**
* @brief Initialize the Task Watchdog Timer (TWDT) * @brief Initialize the Task Watchdog Timer (TWDT)
* *
* This function configures and initializes the TWDT. If the TWDT is already * This function configures and initializes the TWDT. If the TWDT is already initialized when this function is called,
* initialized when this function is called, this function will update the * this function will update the TWDT's timeout period and panic configurations instead. After initializing the TWDT,
* TWDT's timeout period and panic configurations instead. After initializing * any task can elect to be watched by the TWDT by subscribing to it using esp_task_wdt_add().
* the TWDT, any task can elect to be watched by the TWDT by subscribing to it *
* using esp_task_wdt_add(). * @note esp_task_wdt_init() must only be called after the scheduler started
* * @param[in] timeout Timeout period of TWDT in seconds
* @param[in] timeout Timeout period of TWDT in seconds * @param[in] panic Flag that controls whether the panic handler will be executed when the TWDT times out
* @param[in] panic Flag that controls whether the panic handler will be * @return
* executed when the TWDT times out * - ESP_OK: Initialization was successful
* * - ESP_ERR_NO_MEM: Initialization failed due insufficient memory
* @return */
* - ESP_OK: Initialization was successful
* - ESP_ERR_NO_MEM: Initialization failed due to lack of memory
*
* @note esp_task_wdt_init() must only be called after the scheduler
* started
*/
esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic); esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic);
/** /**
* @brief Deinitialize the Task Watchdog Timer (TWDT) * @brief Deinitialize the Task Watchdog Timer (TWDT)
* *
* This function will deinitialize the TWDT. Calling this function whilst tasks * This function will deinitialize the TWDT. Calling this function whilst tasks are still subscribed to the TWDT, or
* are still subscribed to the TWDT, or when the TWDT is already deinitialized, * when the TWDT is already deinitialized, will result in an error code being returned.
* will result in an error code being returned.
* *
* @return * @return
* - ESP_OK: TWDT successfully deinitialized * - ESP_OK: TWDT successfully deinitialized
* - ESP_ERR_INVALID_STATE: Error, tasks are still subscribed to the TWDT * - ESP_ERR_INVALID_STATE: TWDT was never initialized, or tasks are still subscribed
* - ESP_ERR_NOT_FOUND: Error, TWDT has already been deinitialized
*/ */
esp_err_t esp_task_wdt_deinit(void); esp_err_t esp_task_wdt_deinit(void);
/** /**
* @brief Subscribe a task to the Task Watchdog Timer (TWDT) * @brief Subscribe a task to the Task Watchdog Timer (TWDT)
* *
* This function subscribes a task to the TWDT. Each subscribed task must * This function subscribes a task to the TWDT. Each subscribed task must periodically call esp_task_wdt_reset() to
* periodically call esp_task_wdt_reset() to prevent the TWDT from elapsing its * prevent the TWDT from elapsing its timeout period. Failure to do so will result in a TWDT timeout. If the task being
* timeout period. Failure to do so will result in a TWDT timeout. If the task * subscribed is one of the Idle Tasks, this function will automatically enable esp_task_wdt_reset() to called from the
* being subscribed is one of the Idle Tasks, this function will automatically * Idle Hook of the Idle Task.
* enable esp_task_wdt_reset() to called from the Idle Hook of the Idle Task. *
* Calling this function whilst the TWDT is uninitialized or attempting to * Calling this function whilst the TWDT is uninitialized or attempting to subscribe an already subscribed task will
* subscribe an already subscribed task will result in an error code being * result in an error code being returned.
* returned. *
* * @param handle Handle of the task. Input NULL to subscribe the current running task to the TWDT
* @param[in] handle Handle of the task. Input NULL to subscribe the current * @return
* running task to the TWDT * - ESP_OK: Successfully subscribed the task to the TWDT
* * - ESP_ERR_INVALID_ARG: The task is already subscribed
* @return * - ESP_ERR_NO_MEM: Could not subscribe the insufficient memory
* - ESP_OK: Successfully subscribed the task to the TWDT * - ESP_ERR_INVALID_STATE: TWDT was never initialized
* - ESP_ERR_INVALID_ARG: Error, the task is already subscribed */
* - ESP_ERR_NO_MEM: Error, could not subscribe the task due to lack of
* memory
* - ESP_ERR_INVALID_STATE: Error, the TWDT has not been initialized yet
*/
esp_err_t esp_task_wdt_add(TaskHandle_t handle); esp_err_t esp_task_wdt_add(TaskHandle_t handle);
/** /**
* @brief Reset the Task Watchdog Timer (TWDT) on behalf of the currently * @brief Reset the Task Watchdog Timer (TWDT) on behalf of the currently running task
* running task *
* * This function will reset the TWDT on behalf of the currently running task. Each subscribed task must periodically
* This function will reset the TWDT on behalf of the currently running task. * call this function to prevent the TWDT from timing out. If one or more subscribed tasks fail to reset the TWDT on
* Each subscribed task must periodically call this function to prevent the * their own behalf, a TWDT timeout will occur. If the IDLE tasks have been subscribed to the TWDT, they will
* TWDT from timing out. If one or more subscribed tasks fail to reset the * automatically call this function from their idle hooks. Calling this function from a task that has not subscribed to
* TWDT on their own behalf, a TWDT timeout will occur. If the IDLE tasks have * the TWDT, or when the TWDT is uninitialized will result in an error code being returned.
* been subscribed to the TWDT, they will automatically call this function from *
* their idle hooks. Calling this function from a task that has not subscribed * @return
* to the TWDT, or when the TWDT is uninitialized will result in an error code * - ESP_OK: Successfully reset the TWDT on behalf of the currently running task
* being returned. * - ESP_ERR_NOT_FOUND: The task is not subscribed
* * - ESP_ERR_INVALID_STATE: TWDT was never initialized
* @return */
* - ESP_OK: Successfully reset the TWDT on behalf of the currently
* running task
* - ESP_ERR_NOT_FOUND: Error, the current running task has not subscribed
* to the TWDT
* - ESP_ERR_INVALID_STATE: Error, the TWDT has not been initialized yet
*/
esp_err_t esp_task_wdt_reset(void); esp_err_t esp_task_wdt_reset(void);
/** /**
* @brief Unsubscribes a task from the Task Watchdog Timer (TWDT) * @brief Unsubscribes a task from the Task Watchdog Timer (TWDT)
* *
* This function will unsubscribe a task from the TWDT. After being * This function will unsubscribe a task from the TWDT. After being unsubscribed, the task should no longer call
* unsubscribed, the task should no longer call esp_task_wdt_reset(). If the * esp_task_wdt_reset(). If the task is an IDLE task, this function will automatically disable the calling of
* task is an IDLE task, this function will automatically disable the calling * esp_task_wdt_reset() from the Idle Hook. Calling this function whilst the TWDT is uninitialized or attempting to
* of esp_task_wdt_reset() from the Idle Hook. Calling this function whilst the * unsubscribe an already unsubscribed task from the TWDT will result in an error code being returned.
* TWDT is uninitialized or attempting to unsubscribe an already unsubscribed *
* task from the TWDT will result in an error code being returned. * @param[in] handle Handle of the task. Input NULL to unsubscribe the current running task.
* * @return
* @param[in] handle Handle of the task. Input NULL to unsubscribe the * - ESP_OK: Successfully unsubscribed the task from the TWDT
* current running task. * - ESP_ERR_NOT_FOUND: The task is not subscribed
* * - ESP_ERR_INVALID_STATE: TWDT was never initialized
* @return */
* - ESP_OK: Successfully unsubscribed the task from the TWDT
* - ESP_ERR_INVALID_ARG: Error, the task is already unsubscribed
* - ESP_ERR_INVALID_STATE: Error, the TWDT has not been initialized yet
*/
esp_err_t esp_task_wdt_delete(TaskHandle_t handle); esp_err_t esp_task_wdt_delete(TaskHandle_t handle);
/** /**
* @brief Query whether a task is subscribed to the Task Watchdog Timer (TWDT) * @brief Query whether a task is subscribed to the Task Watchdog Timer (TWDT)
* *
* This function will query whether a task is currently subscribed to the TWDT, * This function will query whether a task is currently subscribed to the TWDT, or whether the TWDT is initialized.
* or whether the TWDT is initialized. *
* * @param[in] handle Handle of the task. Input NULL to query the current running task.
* @param[in] handle Handle of the task. Input NULL to query the current * @return:
* running task. * - ESP_OK: The task is currently subscribed to the TWDT
* * - ESP_ERR_NOT_FOUND: The task is not subscribed
* @return: * - ESP_ERR_INVALID_STATE: TWDT was never initialized
* - ESP_OK: The task is currently subscribed to the TWDT */
* - ESP_ERR_NOT_FOUND: The task is currently not subscribed to the TWDT
* - ESP_ERR_INVALID_STATE: The TWDT is not initialized, therefore no tasks
* can be subscribed
*/
esp_err_t esp_task_wdt_status(TaskHandle_t handle); esp_err_t esp_task_wdt_status(TaskHandle_t handle);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -14,8 +14,10 @@
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/queue.h" #include "freertos/queue.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include <sys/queue.h>
#include <esp_types.h> #include <esp_types.h>
#include "esp_err.h" #include "esp_err.h"
#include "esp_check.h"
#include "esp_intr_alloc.h" #include "esp_intr_alloc.h"
#include "esp_attr.h" #include "esp_attr.h"
#include "esp_debug_helpers.h" #include "esp_debug_helpers.h"
@@ -29,50 +31,90 @@
#include "hal/timer_types.h" #include "hal/timer_types.h"
#include "hal/wdt_hal.h" #include "hal/wdt_hal.h"
// --------------------------------------------------- Definitions -----------------------------------------------------
static const char *TAG = "task_wdt"; // ----------------------- Macros --------------------------
//Assertion macro where, if 'cond' is false, will exit the critical section and return 'ret' // HAL related variables and constants
#define ASSERT_EXIT_CRIT_RETURN(cond, ret) ({ \
if(!(cond)){ \
portEXIT_CRITICAL(&twdt_spinlock); \
return ret; \
} \
})
//Empty define used in ASSERT_EXIT_CRIT_RETURN macro when returning in void
#define VOID_RETURN
//HAL related variables and constants
#define TWDT_INSTANCE WDT_MWDT0 #define TWDT_INSTANCE WDT_MWDT0
#define TWDT_TICKS_PER_US MWDT0_TICKS_PER_US #define TWDT_TICKS_PER_US MWDT0_TICKS_PER_US
#define TWDT_PRESCALER MWDT0_TICK_PRESCALER //Tick period of 500us if WDT source clock is 80MHz #define TWDT_PRESCALER MWDT0_TICK_PRESCALER // Tick period of 500us if WDT source clock is 80MHz
static wdt_hal_context_t twdt_context;
//Structure used for each subscribed task // ---------------------- Typedefs -------------------------
typedef struct twdt_task_t twdt_task_t;
struct twdt_task_t { // Structure used for each subscribed task
typedef struct twdt_entry twdt_entry_t;
struct twdt_entry {
SLIST_ENTRY(twdt_entry) slist_entry;
TaskHandle_t task_handle; TaskHandle_t task_handle;
bool has_reset; bool has_reset;
twdt_task_t *next;
}; };
//Structure used to hold run time configuration of the TWDT // Structure used to hold run time configuration of the TWDT
typedef struct twdt_config_t twdt_config_t; typedef struct twdt_obj twdt_obj_t;
struct twdt_config_t { struct twdt_obj {
twdt_task_t *list; //Linked list of subscribed tasks wdt_hal_context_t hal;
uint32_t timeout; //Timeout period of TWDT SLIST_HEAD(entry_list_head, twdt_entry) entries_slist;
bool panic; //Flag to trigger panic when TWDT times out bool panic; // Flag to trigger panic when TWDT times out
intr_handle_t intr_handle; intr_handle_t intr_handle;
}; };
static twdt_config_t *twdt_config = NULL; // ----------------------- Objects -------------------------
static portMUX_TYPE twdt_spinlock = portMUX_INITIALIZER_UNLOCKED;
/* static const char *TAG = "task_wdt";
* Idle hook callback for Idle Tasks to reset the TWDT. This callback will only static portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED;
* be registered to the Idle Hook of a particular core when the corresponding static twdt_obj_t *p_twdt_obj = NULL;
* Idle Task subscribes to the TWDT.
// ----------------------------------------------------- Private -------------------------------------------------------
/**
* @brief Find an entry from its task handle, and checks if all other entries have been reset
*
* @param[in] handle Task handle
* @param[out] all_reset Whether all entries have been reset
* @return Entry, or NULL if not found
*/
static twdt_entry_t *find_entry_from_handle_and_check_all_reset(TaskHandle_t handle, bool *all_reset)
{
twdt_entry_t *target = NULL;
bool found_non_reset = false;
twdt_entry_t *entry;
SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) {
if (entry->task_handle == handle) {
target = entry;
} else if (entry->has_reset == false) {
found_non_reset = true;
}
}
*all_reset = !found_non_reset;
return target;
}
/**
* @brief Reset hardware timer and entry flags
*/
static void reset_hw_timer(void)
{
// All tasks have reset; time to reset the hardware timer.
wdt_hal_write_protect_disable(&p_twdt_obj->hal);
wdt_hal_feed(&p_twdt_obj->hal);
wdt_hal_write_protect_enable(&p_twdt_obj->hal);
//C lear the has_reset flag in each entry
twdt_entry_t *entry;
SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) {
entry->has_reset = false;
}
}
/**
* @brief Idle hook callback
*
* Idle hook callback for Idle Tasks to reset the TWDT. This callback will only be registered to the Idle Hook of a
* particular core when the corresponding Idle Task subscribes to the TWDT.
*
* @return Always returns true
*/ */
static bool idle_hook_cb(void) static bool idle_hook_cb(void)
{ {
@@ -80,316 +122,281 @@ static bool idle_hook_cb(void)
return true; return true;
} }
/* /**
* Internal function that looks for the target task in the TWDT task list. * @brief User ISR callback placeholder
* Returns the list item if found and returns null if not found. Also checks if *
* all the other tasks have reset. Should be called within critical. * This function is called by task_wdt_isr function (ISR for when TWDT times out). It can be redefined in user code to
*/ * handle twdt events.
static twdt_task_t *find_task_in_twdt_list(TaskHandle_t handle, bool *all_reset) *
{ * @note It has the same limitations as the interrupt function. Do not use ESP_LOGI functions inside.
twdt_task_t *target = NULL;
*all_reset = true;
for(twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
if(task->task_handle == handle){
target = task; //Get pointer to target task list member
}else{
if(task->has_reset == false){ //If a task has yet to reset
*all_reset = false;
}
}
}
return target;
}
/*
* Resets the hardware timer and has_reset flags of each task on the list.
* Called within critical
*/
static void reset_hw_timer(void)
{
//All tasks have reset; time to reset the hardware timer.
wdt_hal_write_protect_disable(&twdt_context);
wdt_hal_feed(&twdt_context);
wdt_hal_write_protect_enable(&twdt_context);
//Clear all has_reset flags in list
for (twdt_task_t *task = twdt_config->list; task != NULL; task = task->next){
task->has_reset=false;
}
}
/*
* This function is called by task_wdt_isr function (ISR for when TWDT times out).
* It can be redefined in user code to handle twdt events.
* Note: It has the same limitations as the interrupt function.
* Do not use ESP_LOGI functions inside.
*/ */
void __attribute__((weak)) esp_task_wdt_isr_user_handler(void) void __attribute__((weak)) esp_task_wdt_isr_user_handler(void)
{ {
} }
/* /**
* ISR for when TWDT times out. Checks for which tasks have not reset. Also * @brief TWDT timeout ISR function
* triggers panic if configured to do so *
* Tee ISR checks which entries have not been reset, prints some debugging information, and triggers a panic if
* configured to do so.
*
* @param arg ISR argument
*/ */
static void task_wdt_isr(void *arg) static void task_wdt_isr(void *arg)
{ {
portENTER_CRITICAL_ISR(&twdt_spinlock); portENTER_CRITICAL_ISR(&spinlock);
twdt_task_t *twdttask; // Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset)
const char *cpu; wdt_hal_write_protect_disable(&p_twdt_obj->hal);
//Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset) wdt_hal_handle_intr(&p_twdt_obj->hal); // Feeds WDT and clears acknowledges interrupt
wdt_hal_write_protect_disable(&twdt_context); wdt_hal_write_protect_enable(&p_twdt_obj->hal);
wdt_hal_handle_intr(&twdt_context); //Feeds WDT and clears acknowledges interrupt // If there are no entries, there's nothing to do.
wdt_hal_write_protect_enable(&twdt_context); if (SLIST_EMPTY(&p_twdt_obj->entries_slist)) {
portEXIT_CRITICAL_ISR(&spinlock);
//We are taking a spinlock while doing I/O (ESP_EARLY_LOGE) here. Normally, that is a pretty return;
//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 // Find what entries triggered the TWDT timeout (i.e., which entries have not been reset)
//than the badness caused by a spinlock here. /*
Note: We are currently in a critical section, thus under normal circumstances, logging should not be allowed.
//Return immediately if no tasks have been added to task list However, TWDT timeouts count as fatal errors, thus reporting the fatal error is considered more important than
ASSERT_EXIT_CRIT_RETURN((twdt_config->list != NULL), VOID_RETURN); minimizing interrupt latency. Thus we allow logging in critical sections in this narrow case.
*/
//Watchdog got triggered because at least one task did not reset in time.
ESP_EARLY_LOGE(TAG, "Task watchdog got triggered. The following tasks did not reset the watchdog in time:"); ESP_EARLY_LOGE(TAG, "Task watchdog got triggered. The following tasks did not reset the watchdog in time:");
for (twdttask=twdt_config->list; twdttask!=NULL; twdttask=twdttask->next) { twdt_entry_t *entry;
if (!twdttask->has_reset) { SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) {
cpu=xTaskGetAffinity(twdttask->task_handle)==0?DRAM_STR("CPU 0"):DRAM_STR("CPU 1"); if (!entry->has_reset) {
if (xTaskGetAffinity(twdttask->task_handle)==tskNO_AFFINITY) { BaseType_t task_affinity = xTaskGetAffinity(entry->task_handle);
cpu=DRAM_STR("CPU 0/1"); const char *cpu;
if (task_affinity == 0) {
cpu = DRAM_STR("CPU 0");
} else if (task_affinity == 1) {
cpu = DRAM_STR("CPU 1");
} else {
cpu = DRAM_STR("CPU 0/1");
} }
ESP_EARLY_LOGE(TAG, " - %s (%s)", pcTaskGetName(twdttask->task_handle), cpu); ESP_EARLY_LOGE(TAG, " - %s (%s)", pcTaskGetName(entry->task_handle), cpu);
} }
} }
ESP_EARLY_LOGE(TAG, "%s", DRAM_STR("Tasks currently running:")); ESP_EARLY_LOGE(TAG, "%s", DRAM_STR("Tasks currently running:"));
for (int x=0; x<portNUM_PROCESSORS; x++) { for (int x = 0; x < portNUM_PROCESSORS; x++) {
ESP_EARLY_LOGE(TAG, "CPU %d: %s", x, pcTaskGetName(xTaskGetCurrentTaskHandleForCPU(x))); ESP_EARLY_LOGE(TAG, "CPU %d: %s", x, pcTaskGetName(xTaskGetCurrentTaskHandleForCPU(x)));
} }
portEXIT_CRITICAL_ISR(&spinlock);
// Run user ISR handler
esp_task_wdt_isr_user_handler(); esp_task_wdt_isr_user_handler();
// Trigger configured timeout behavior (e.g., panic or print backtrace)
if (twdt_config->panic){ //Trigger Panic if configured to do so if (p_twdt_obj->panic) {
ESP_EARLY_LOGE(TAG, "Aborting."); ESP_EARLY_LOGE(TAG, "Aborting.");
portEXIT_CRITICAL_ISR(&twdt_spinlock);
esp_reset_reason_set_hint(ESP_RST_TASK_WDT); esp_reset_reason_set_hint(ESP_RST_TASK_WDT);
abort(); abort();
} else { } else { // Print
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 && !CONFIG_IDF_TARGET_ESP32C2 // TODO: ESP32-C3 IDF-2986 #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 && !CONFIG_IDF_TARGET_ESP32C2 // TODO: ESP32-C3 IDF-2986
int current_core = xPortGetCoreID(); int current_core = xPortGetCoreID();
//Print backtrace of current core // Print backtrace of current core
ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core); ESP_EARLY_LOGE(TAG, "Print CPU %d (current core) backtrace", current_core);
esp_backtrace_print(100); esp_backtrace_print(100);
#if !CONFIG_FREERTOS_UNICORE #if !CONFIG_FREERTOS_UNICORE
//Print backtrace of other core // Print backtrace of other core
ESP_EARLY_LOGE(TAG, "Print CPU %d backtrace", !current_core); ESP_EARLY_LOGE(TAG, "Print CPU %d backtrace", !current_core);
esp_crosscore_int_send_print_backtrace(!current_core); esp_crosscore_int_send_print_backtrace(!current_core);
#endif #endif
#endif #endif
} }
portEXIT_CRITICAL_ISR(&twdt_spinlock);
} }
/* // ----------------------------------------------------- Public --------------------------------------------------------
* Initializes the TWDT 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) esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic)
{ {
portENTER_CRITICAL(&twdt_spinlock); esp_err_t ret;
if(twdt_config == NULL){ //TWDT not initialized yet
//Allocate memory for wdt_config
twdt_config = calloc(1, sizeof(twdt_config_t));
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NO_MEM);
twdt_config->list = NULL; twdt_obj_t *obj = NULL;
twdt_config->timeout = timeout; if (p_twdt_obj == NULL) {
twdt_config->panic = panic; // Allocate and initialize TWDT driver object
obj = calloc(1, sizeof(twdt_obj_t));
//Register Interrupt and ISR ESP_GOTO_ON_FALSE((obj != NULL), ESP_ERR_NO_MEM, err, TAG, "insufficient memory");
ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &twdt_config->intr_handle)); SLIST_INIT(&obj->entries_slist);
obj->panic = panic;
//Configure hardware timer ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &obj->intr_handle));
portENTER_CRITICAL(&spinlock);
// Configure hardware timer
periph_module_enable(PERIPH_TIMG0_MODULE); periph_module_enable(PERIPH_TIMG0_MODULE);
wdt_hal_init(&twdt_context, TWDT_INSTANCE, TWDT_PRESCALER, true); wdt_hal_init(&obj->hal, TWDT_INSTANCE, TWDT_PRESCALER, true);
wdt_hal_write_protect_disable(&twdt_context); // Assign the driver object
//Configure 1st stage timeout and behavior p_twdt_obj = obj;
wdt_hal_config_stage(&twdt_context, WDT_STAGE0, twdt_config->timeout * (1000000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT); portEXIT_CRITICAL(&spinlock);
//Configure 2nd stage timeout and behavior
wdt_hal_config_stage(&twdt_context, WDT_STAGE1, twdt_config->timeout * (2 * 1000000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM);
//Enable the WDT
wdt_hal_enable(&twdt_context);
wdt_hal_write_protect_enable(&twdt_context);
} else { //twdt_config previously initialized
//Reconfigure task wdt
twdt_config->panic = panic;
twdt_config->timeout = timeout;
//Reconfigure hardware timer
wdt_hal_write_protect_disable(&twdt_context);
wdt_hal_disable(&twdt_context);
wdt_hal_config_stage(&twdt_context, WDT_STAGE0, twdt_config->timeout * (1000 * 1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT);
wdt_hal_config_stage(&twdt_context, WDT_STAGE1, twdt_config->timeout * (2 * 1000 * 1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM);
wdt_hal_enable(&twdt_context);
wdt_hal_write_protect_enable(&twdt_context);
} }
portEXIT_CRITICAL(&twdt_spinlock); portENTER_CRITICAL(&spinlock);
return ESP_OK; wdt_hal_write_protect_disable(&p_twdt_obj->hal);
// Configure 1st stage timeout and behavior
wdt_hal_config_stage(&p_twdt_obj->hal, WDT_STAGE0, timeout * (1000000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT);
// Configure 2nd stage timeout and behavior
wdt_hal_config_stage(&p_twdt_obj->hal, WDT_STAGE1, timeout * (2 * 1000000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM);
// Enable the WDT
wdt_hal_enable(&p_twdt_obj->hal);
wdt_hal_write_protect_enable(&p_twdt_obj->hal);
portEXIT_CRITICAL(&spinlock);
ret = ESP_OK;
err:
return ret;
} }
esp_err_t esp_task_wdt_deinit(void) esp_err_t esp_task_wdt_deinit(void)
{ {
portENTER_CRITICAL(&twdt_spinlock); esp_err_t ret;
//TWDT must already be initialized
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
//Task list must be empty
ASSERT_EXIT_CRIT_RETURN((twdt_config->list == NULL), ESP_ERR_INVALID_STATE);
//Disable hardware timer portENTER_CRITICAL(&spinlock);
wdt_hal_deinit(&twdt_context); // Check TWDT state
ESP_GOTO_ON_FALSE((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, err, TAG, "task watchdog was never initialized");
ESP_GOTO_ON_FALSE(SLIST_EMPTY(&p_twdt_obj->entries_slist), ESP_ERR_INVALID_STATE, err, TAG, "all tasks must be deleted");
// Disable hardware timer and the interrupt
wdt_hal_write_protect_disable(&p_twdt_obj->hal);
wdt_hal_disable(&p_twdt_obj->hal);
wdt_hal_write_protect_enable(&p_twdt_obj->hal);
wdt_hal_deinit(&p_twdt_obj->hal);
esp_intr_disable(p_twdt_obj->intr_handle);
// Unassign driver object
twdt_obj_t *obj = p_twdt_obj;
p_twdt_obj = NULL;
portEXIT_CRITICAL(&spinlock);
ESP_ERROR_CHECK(esp_intr_free(twdt_config->intr_handle)); //Unregister interrupt // Free driver resources
free(twdt_config); //Free twdt_config ESP_ERROR_CHECK(esp_intr_free(obj->intr_handle)); //Deregister interrupt
twdt_config = NULL; free(obj); //Free p_twdt_obj
portEXIT_CRITICAL(&twdt_spinlock);
return ESP_OK; return ESP_OK;
err:
portEXIT_CRITICAL(&spinlock);
return ret;
} }
esp_err_t esp_task_wdt_add(TaskHandle_t handle) esp_err_t esp_task_wdt_add(TaskHandle_t handle)
{ {
portENTER_CRITICAL(&twdt_spinlock); esp_err_t ret;
//TWDT must already be initialized if (handle == NULL) { //Get handle of current task if none is provided
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
twdt_task_t *target_task;
bool all_reset;
if (handle == NULL){ //Get handle of current task if none is provided
handle = xTaskGetCurrentTaskHandle(); handle = xTaskGetCurrentTaskHandle();
} }
//Check if tasks exists in task list, and if all other tasks have reset
target_task = find_task_in_twdt_list(handle, &all_reset);
//task cannot be already subscribed
ASSERT_EXIT_CRIT_RETURN((target_task == NULL), ESP_ERR_INVALID_ARG);
//Add target task to TWDT task list // Allocate entry for task
target_task = calloc(1,sizeof(twdt_task_t)); twdt_entry_t *entry = calloc(1, sizeof(twdt_entry_t));
ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NO_MEM); ESP_GOTO_ON_FALSE((entry != NULL), ESP_ERR_NO_MEM, alloc_err, TAG, "insufficient memory");
target_task->task_handle = handle; entry->task_handle = handle;
target_task->has_reset = true; entry->has_reset = false;
target_task->next = NULL;
if (twdt_config->list == NULL) { //Adding to empty list portENTER_CRITICAL(&spinlock);
twdt_config->list = target_task; // Check TWDT state
} else { //Adding to tail of list ESP_GOTO_ON_FALSE((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, state_err, TAG, "task watchdog was never initialized");
twdt_task_t *task; // Check if the task is an entry, and if all entries have been reset
for (task = twdt_config->list; task->next != NULL; task = task->next){ bool all_reset;
; //point task to current tail of TWDT task list twdt_entry_t *found_entry = find_entry_from_handle_and_check_all_reset(handle, &all_reset);
} ESP_GOTO_ON_FALSE((found_entry == NULL), ESP_ERR_INVALID_ARG, state_err, TAG, "task is already subscribed");
task->next = target_task; // Add task to entry list
SLIST_INSERT_HEAD(&p_twdt_obj->entries_slist, entry, slist_entry);
if (all_reset) { //Reset hardware timer if all other tasks in list have reset in
reset_hw_timer();
} }
portEXIT_CRITICAL(&spinlock); //Nested critical if Legacy
//If idle task, register the idle hook callback to appropriate core // If the task was the idle task, register the idle hook callback to appropriate core
for(int i = 0; i < portNUM_PROCESSORS; i++){ for (int i = 0; i < portNUM_PROCESSORS; i++) {
if(handle == xTaskGetIdleTaskHandleForCPU(i)){ if (handle == xTaskGetIdleTaskHandleForCPU(i)) {
ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, i)); ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, i));
break; break;
} }
} }
if(all_reset){ //Reset hardware timer if all other tasks in list have reset in
reset_hw_timer();
}
portEXIT_CRITICAL(&twdt_spinlock); //Nested critical if Legacy
return ESP_OK; return ESP_OK;
state_err:
portEXIT_CRITICAL(&spinlock);
free(entry);
alloc_err:
return ret;
} }
esp_err_t esp_task_wdt_reset(void) esp_err_t esp_task_wdt_reset(void)
{ {
portENTER_CRITICAL(&twdt_spinlock); esp_err_t ret;
//TWDT must already be initialized
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE);
TaskHandle_t handle = xTaskGetCurrentTaskHandle(); TaskHandle_t handle = xTaskGetCurrentTaskHandle();
twdt_task_t *target_task;
portENTER_CRITICAL(&spinlock);
// Check TWDT state
ESP_GOTO_ON_FALSE((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, err, TAG, "task watchdog was never initialized");
// Find entry for task
bool all_reset; bool all_reset;
twdt_entry_t *entry;
//Check if task exists in task list, and if all other tasks have reset entry = find_entry_from_handle_and_check_all_reset(handle, &all_reset);
target_task = find_task_in_twdt_list(handle, &all_reset); ESP_GOTO_ON_FALSE((entry != NULL), ESP_ERR_NOT_FOUND, err, TAG, "task not found");
//Return error if trying to reset task that is not on the task list // Mark entry as reset and issue timer reset if all entries have been reset
ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NOT_FOUND); entry->has_reset = true; //Reset the task if it's on the task list
if (all_reset) { //Reset if all other tasks in list have reset in
target_task->has_reset = true; //Reset the task if it's on the task list
if(all_reset){ //Reset if all other tasks in list have reset in
reset_hw_timer(); reset_hw_timer();
} }
ret = ESP_OK;
err:
portEXIT_CRITICAL(&spinlock);
portEXIT_CRITICAL(&twdt_spinlock); return ret;
return ESP_OK;
} }
esp_err_t esp_task_wdt_delete(TaskHandle_t handle) esp_err_t esp_task_wdt_delete(TaskHandle_t handle)
{ {
if(handle == NULL){ esp_err_t ret;
if (handle == NULL) {
handle = xTaskGetCurrentTaskHandle(); handle = xTaskGetCurrentTaskHandle();
} }
portENTER_CRITICAL(&twdt_spinlock);
//Return error if twdt has not been initialized
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_NOT_FOUND);
twdt_task_t *target_task; portENTER_CRITICAL(&spinlock);
// Check TWDT state
ESP_GOTO_ON_FALSE((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, err, TAG, "task watchdog was never initialized");
// Find entry for task
bool all_reset; bool all_reset;
target_task = find_task_in_twdt_list(handle, &all_reset); twdt_entry_t *entry;
//Task doesn't exist on list. Return error entry = find_entry_from_handle_and_check_all_reset(handle, &all_reset);
ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_INVALID_ARG); ESP_GOTO_ON_FALSE((entry != NULL), ESP_ERR_NOT_FOUND, err, TAG, "task not found");
// Remove entry
if(target_task == twdt_config->list){ //target_task is head of list. Delete SLIST_REMOVE(&p_twdt_obj->entries_slist, entry, twdt_entry, slist_entry);
twdt_config->list = target_task->next; // Reset hardware timer if all remaining tasks have reset
free(target_task); if (all_reset) {
}else{ //target_task not head of list. Delete reset_hw_timer();
twdt_task_t *prev;
for (prev = twdt_config->list; prev->next != target_task; prev = prev->next){
; //point prev to task preceding target_task
}
prev->next = target_task->next;
free(target_task);
} }
portEXIT_CRITICAL(&spinlock);
//If idle task, deregister idle hook callback form appropriate core // Free the entry
for(int i = 0; i < portNUM_PROCESSORS; i++){ free(entry);
if(handle == xTaskGetIdleTaskHandleForCPU(i)){ // If idle task, deregister idle hook callback form appropriate core
for (int i = 0; i < portNUM_PROCESSORS; i++) {
if (handle == xTaskGetIdleTaskHandleForCPU(i)) {
esp_deregister_freertos_idle_hook_for_cpu(idle_hook_cb, i); esp_deregister_freertos_idle_hook_for_cpu(idle_hook_cb, i);
break; break;
} }
} }
if(all_reset){ //Reset hardware timer if all remaining tasks have reset
reset_hw_timer();
}
portEXIT_CRITICAL(&twdt_spinlock);
return ESP_OK; return ESP_OK;
err:
portEXIT_CRITICAL(&spinlock);
return ret;
} }
esp_err_t esp_task_wdt_status(TaskHandle_t handle) esp_err_t esp_task_wdt_status(TaskHandle_t handle)
{ {
if(handle == NULL){ esp_err_t ret;
if (handle == NULL) {
handle = xTaskGetCurrentTaskHandle(); handle = xTaskGetCurrentTaskHandle();
} }
portENTER_CRITICAL(&twdt_spinlock); portENTER_CRITICAL(&spinlock);
//Return if TWDT is not initialized // Check TWDT state
ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE); ESP_GOTO_ON_FALSE((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, err, TAG, "task watchdog was never initialized");
// Find entry for task
bool all_reset;
twdt_entry_t *entry;
entry = find_entry_from_handle_and_check_all_reset(handle, &all_reset);
(void) all_reset; // Unused
ret = (entry != NULL) ? ESP_OK : ESP_ERR_NOT_FOUND;
err:
portEXIT_CRITICAL(&spinlock);
twdt_task_t *task; return ret;
for(task = twdt_config->list; task!=NULL; task=task->next){
//Return ESP_OK if task is found
ASSERT_EXIT_CRIT_RETURN((task->task_handle != handle), ESP_OK);
}
//Task could not be found
portEXIT_CRITICAL(&twdt_spinlock);
return ESP_ERR_NOT_FOUND;
} }

View File

@@ -676,7 +676,6 @@ components/esp_system/include/esp_private/startup_internal.h
components/esp_system/include/esp_private/system_internal.h components/esp_system/include/esp_private/system_internal.h
components/esp_system/include/esp_private/usb_console.h components/esp_system/include/esp_private/usb_console.h
components/esp_system/include/esp_task.h components/esp_system/include/esp_task.h
components/esp_system/include/esp_task_wdt.h
components/esp_system/port/arch/riscv/expression_with_stack.c components/esp_system/port/arch/riscv/expression_with_stack.c
components/esp_system/port/arch/xtensa/expression_with_stack.c components/esp_system/port/arch/xtensa/expression_with_stack.c
components/esp_system/port/public_compat/brownout.h components/esp_system/port/public_compat/brownout.h