diff --git a/components/esp_system/include/esp_task_wdt.h b/components/esp_system/include/esp_task_wdt.h index cf64cfc147..e5a08eabe8 100644 --- a/components/esp_system/include/esp_task_wdt.h +++ b/components/esp_system/include/esp_task_wdt.h @@ -1,16 +1,8 @@ -// 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. +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once @@ -23,125 +15,98 @@ extern "C" { #endif /** - * @brief Initialize the Task Watchdog Timer (TWDT) - * - * This function configures and initializes the TWDT. If the TWDT is already - * initialized when this function is called, this function will update the - * TWDT's timeout period and panic configurations instead. After initializing - * the TWDT, any task can elect to be watched by the TWDT by subscribing to it - * using esp_task_wdt_add(). - * - * @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 - * - * @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 - */ + * @brief Initialize the Task Watchdog Timer (TWDT) + * + * This function configures and initializes the TWDT. If the TWDT is already initialized when this function is called, + * this function will update the TWDT's timeout period and panic configurations instead. After initializing 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] panic Flag that controls whether the panic handler will be executed when the TWDT times out + * @return + * - ESP_OK: Initialization was successful + * - ESP_ERR_NO_MEM: Initialization failed due insufficient memory + */ esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic); /** * @brief Deinitialize the Task Watchdog Timer (TWDT) * - * This function will deinitialize the TWDT. Calling this function whilst tasks - * are still subscribed to the TWDT, or when the TWDT is already deinitialized, - * will result in an error code being returned. + * This function will deinitialize the TWDT. Calling this function whilst tasks are still subscribed to the TWDT, or + * when the TWDT is already deinitialized, will result in an error code being returned. * * @return * - ESP_OK: TWDT successfully deinitialized - * - ESP_ERR_INVALID_STATE: Error, tasks are still subscribed to the TWDT - * - ESP_ERR_NOT_FOUND: Error, TWDT has already been deinitialized + * - ESP_ERR_INVALID_STATE: TWDT was never initialized, or tasks are still subscribed */ esp_err_t esp_task_wdt_deinit(void); /** - * @brief Subscribe a task to the Task Watchdog Timer (TWDT) - * - * This function subscribes a task to the TWDT. Each subscribed task must - * periodically call esp_task_wdt_reset() to prevent the TWDT from elapsing its - * timeout period. Failure to do so will result in a TWDT timeout. If the task - * being subscribed is one of the Idle Tasks, this function will automatically - * 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 - * subscribe an already subscribed task will result in an error code being - * returned. - * - * @param[in] handle Handle of the task. Input NULL to subscribe the current - * running task to the TWDT - * - * @return - * - ESP_OK: Successfully subscribed the task to the TWDT - * - 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 - */ + * @brief Subscribe a task to the Task Watchdog Timer (TWDT) + * + * This function subscribes a task to the TWDT. Each subscribed task must periodically call esp_task_wdt_reset() to + * prevent the TWDT from elapsing its timeout period. Failure to do so will result in a TWDT timeout. If the task being + * subscribed is one of the Idle Tasks, this function will automatically 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 subscribe an already subscribed task will + * result in an error code being returned. + * + * @param handle Handle of the task. Input NULL to subscribe the current running task to the TWDT + * @return + * - ESP_OK: Successfully subscribed the task to the TWDT + * - ESP_ERR_INVALID_ARG: The task is already subscribed + * - ESP_ERR_NO_MEM: Could not subscribe the insufficient memory + * - ESP_ERR_INVALID_STATE: TWDT was never initialized + */ esp_err_t esp_task_wdt_add(TaskHandle_t handle); /** - * @brief Reset the Task Watchdog Timer (TWDT) on behalf of the currently - * running task - * - * This function will reset the TWDT on behalf of the currently running task. - * Each subscribed task must periodically call this function to prevent the - * TWDT from timing out. If one or more subscribed tasks fail to reset the - * TWDT on their own behalf, a TWDT timeout will occur. If the IDLE tasks have - * 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 - * to the TWDT, or when the TWDT is uninitialized will result in an error code - * being returned. - * - * @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 - */ + * @brief Reset the Task Watchdog Timer (TWDT) on behalf of the currently running task + * + * This function will reset the TWDT on behalf of the currently running task. Each subscribed task must periodically + * call this function to prevent the TWDT from timing out. If one or more subscribed tasks fail to reset the TWDT on + * their own behalf, a TWDT timeout will occur. If the IDLE tasks have 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 to + * the TWDT, or when the TWDT is uninitialized will result in an error code being returned. + * + * @return + * - ESP_OK: Successfully reset the TWDT on behalf of the currently running task + * - ESP_ERR_NOT_FOUND: The task is not subscribed + * - ESP_ERR_INVALID_STATE: TWDT was never initialized + */ esp_err_t esp_task_wdt_reset(void); /** - * @brief Unsubscribes a task from the Task Watchdog Timer (TWDT) - * - * This function will unsubscribe a task from the TWDT. After being - * unsubscribed, the task should no longer call esp_task_wdt_reset(). If the - * task is an IDLE task, this function will automatically disable the calling - * of esp_task_wdt_reset() from the Idle Hook. Calling this function whilst the - * 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 - * - 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 - */ + * @brief Unsubscribes a task from the Task Watchdog Timer (TWDT) + * + * This function will unsubscribe a task from the TWDT. After being unsubscribed, the task should no longer call + * esp_task_wdt_reset(). If the task is an IDLE task, this function will automatically disable the calling of + * esp_task_wdt_reset() from the Idle Hook. Calling this function whilst the 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 + * - ESP_OK: Successfully unsubscribed the task from the TWDT + * - ESP_ERR_NOT_FOUND: The task is not subscribed + * - ESP_ERR_INVALID_STATE: TWDT was never initialized + */ esp_err_t esp_task_wdt_delete(TaskHandle_t handle); /** - * @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, - * or whether the TWDT is initialized. - * - * @param[in] handle Handle of the task. Input NULL to query the current - * running task. - * - * @return: - * - 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 - */ + * @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, or whether the TWDT is initialized. + * + * @param[in] handle Handle of the task. Input NULL to query the current running task. + * @return: + * - ESP_OK: The task is currently subscribed to the TWDT + * - ESP_ERR_NOT_FOUND: The task is not subscribed + * - ESP_ERR_INVALID_STATE: TWDT was never initialized + */ esp_err_t esp_task_wdt_status(TaskHandle_t handle); - #ifdef __cplusplus } #endif diff --git a/components/esp_system/task_wdt.c b/components/esp_system/task_wdt.c index f576e5ea03..39f8fe7bdb 100644 --- a/components/esp_system/task_wdt.c +++ b/components/esp_system/task_wdt.c @@ -14,8 +14,10 @@ #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/semphr.h" +#include #include #include "esp_err.h" +#include "esp_check.h" #include "esp_intr_alloc.h" #include "esp_attr.h" #include "esp_debug_helpers.h" @@ -29,50 +31,90 @@ #include "hal/timer_types.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' -#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 +// HAL related variables and constants #define TWDT_INSTANCE WDT_MWDT0 #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 -static wdt_hal_context_t twdt_context; +#define TWDT_PRESCALER MWDT0_TICK_PRESCALER // Tick period of 500us if WDT source clock is 80MHz -//Structure used for each subscribed task -typedef struct twdt_task_t twdt_task_t; -struct twdt_task_t { +// ---------------------- Typedefs ------------------------- + +// 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; bool has_reset; - twdt_task_t *next; }; -//Structure used to hold run time configuration of the TWDT -typedef struct twdt_config_t twdt_config_t; -struct twdt_config_t { - twdt_task_t *list; //Linked list of subscribed tasks - uint32_t timeout; //Timeout period of TWDT - bool panic; //Flag to trigger panic when TWDT times out +// Structure used to hold run time configuration of the TWDT +typedef struct twdt_obj twdt_obj_t; +struct twdt_obj { + wdt_hal_context_t hal; + SLIST_HEAD(entry_list_head, twdt_entry) entries_slist; + bool panic; // Flag to trigger panic when TWDT times out intr_handle_t intr_handle; }; -static twdt_config_t *twdt_config = NULL; -static portMUX_TYPE twdt_spinlock = portMUX_INITIALIZER_UNLOCKED; +// ----------------------- Objects ------------------------- -/* - * 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. +static const char *TAG = "task_wdt"; +static portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED; +static twdt_obj_t *p_twdt_obj = NULL; + +// ----------------------------------------------------- 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) { @@ -80,316 +122,281 @@ static bool idle_hook_cb(void) return true; } -/* - * Internal function that looks for the target task in the TWDT task list. - * 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. - */ -static twdt_task_t *find_task_in_twdt_list(TaskHandle_t handle, bool *all_reset) -{ - 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. +/** + * @brief User ISR callback placeholder + * + * 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) { } -/* - * ISR for when TWDT times out. Checks for which tasks have not reset. Also - * triggers panic if configured to do so +/** + * @brief TWDT timeout ISR function + * + * 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) { - portENTER_CRITICAL_ISR(&twdt_spinlock); - twdt_task_t *twdttask; - const char *cpu; - //Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset) - wdt_hal_write_protect_disable(&twdt_context); - wdt_hal_handle_intr(&twdt_context); //Feeds WDT and clears acknowledges interrupt - wdt_hal_write_protect_enable(&twdt_context); - - //We are taking a spinlock while doing I/O (ESP_EARLY_LOGE) 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. - - //Return immediately if no tasks have been added to task list - ASSERT_EXIT_CRIT_RETURN((twdt_config->list != NULL), VOID_RETURN); - - //Watchdog got triggered because at least one task did not reset in time. + portENTER_CRITICAL_ISR(&spinlock); + // Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset) + wdt_hal_write_protect_disable(&p_twdt_obj->hal); + wdt_hal_handle_intr(&p_twdt_obj->hal); // Feeds WDT and clears acknowledges interrupt + wdt_hal_write_protect_enable(&p_twdt_obj->hal); + // If there are no entries, there's nothing to do. + if (SLIST_EMPTY(&p_twdt_obj->entries_slist)) { + portEXIT_CRITICAL_ISR(&spinlock); + return; + } + // Find what entries triggered the TWDT timeout (i.e., which entries have not been reset) + /* + Note: We are currently in a critical section, thus under normal circumstances, logging should not be allowed. + However, TWDT timeouts count as fatal errors, thus reporting the fatal error is considered more important than + minimizing interrupt latency. Thus we allow logging in critical sections in this narrow case. + */ 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) { - if (!twdttask->has_reset) { - cpu=xTaskGetAffinity(twdttask->task_handle)==0?DRAM_STR("CPU 0"):DRAM_STR("CPU 1"); - if (xTaskGetAffinity(twdttask->task_handle)==tskNO_AFFINITY) { - cpu=DRAM_STR("CPU 0/1"); + twdt_entry_t *entry; + SLIST_FOREACH(entry, &p_twdt_obj->entries_slist, slist_entry) { + if (!entry->has_reset) { + BaseType_t task_affinity = xTaskGetAffinity(entry->task_handle); + 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:")); - for (int x=0; xpanic){ //Trigger Panic if configured to do so + // Trigger configured timeout behavior (e.g., panic or print backtrace) + if (p_twdt_obj->panic) { ESP_EARLY_LOGE(TAG, "Aborting."); - portEXIT_CRITICAL_ISR(&twdt_spinlock); esp_reset_reason_set_hint(ESP_RST_TASK_WDT); abort(); - } else { - + } else { // Print #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 && !CONFIG_IDF_TARGET_ESP32C2 // TODO: ESP32-C3 IDF-2986 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_backtrace_print(100); #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_crosscore_int_send_print_backtrace(!current_core); #endif #endif } - portEXIT_CRITICAL_ISR(&twdt_spinlock); } -/* - * 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. - */ +// ----------------------------------------------------- Public -------------------------------------------------------- + esp_err_t esp_task_wdt_init(uint32_t timeout, bool panic) { - portENTER_CRITICAL(&twdt_spinlock); - 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); + esp_err_t ret; - twdt_config->list = NULL; - twdt_config->timeout = timeout; - twdt_config->panic = panic; - - //Register Interrupt and ISR - ESP_ERROR_CHECK(esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &twdt_config->intr_handle)); - - //Configure hardware timer + twdt_obj_t *obj = NULL; + if (p_twdt_obj == NULL) { + // Allocate and initialize TWDT driver object + obj = calloc(1, sizeof(twdt_obj_t)); + ESP_GOTO_ON_FALSE((obj != NULL), ESP_ERR_NO_MEM, err, TAG, "insufficient memory"); + SLIST_INIT(&obj->entries_slist); + obj->panic = panic; + 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); - wdt_hal_init(&twdt_context, TWDT_INSTANCE, TWDT_PRESCALER, true); - wdt_hal_write_protect_disable(&twdt_context); - //Configure 1st stage timeout and behavior - wdt_hal_config_stage(&twdt_context, WDT_STAGE0, twdt_config->timeout * (1000000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT); - //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); + wdt_hal_init(&obj->hal, TWDT_INSTANCE, TWDT_PRESCALER, true); + // Assign the driver object + p_twdt_obj = obj; + portEXIT_CRITICAL(&spinlock); } - portEXIT_CRITICAL(&twdt_spinlock); - return ESP_OK; + portENTER_CRITICAL(&spinlock); + 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) { - portENTER_CRITICAL(&twdt_spinlock); - //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); + esp_err_t ret; - //Disable hardware timer - wdt_hal_deinit(&twdt_context); + 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"); + 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(twdt_config); //Free twdt_config - twdt_config = NULL; - portEXIT_CRITICAL(&twdt_spinlock); + // Free driver resources + ESP_ERROR_CHECK(esp_intr_free(obj->intr_handle)); //Deregister interrupt + free(obj); //Free p_twdt_obj return ESP_OK; + +err: + portEXIT_CRITICAL(&spinlock); + return ret; } esp_err_t esp_task_wdt_add(TaskHandle_t handle) { - portENTER_CRITICAL(&twdt_spinlock); - //TWDT must already be initialized - 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 + esp_err_t ret; + 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 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 - target_task = calloc(1,sizeof(twdt_task_t)); - ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NO_MEM); - target_task->task_handle = handle; - target_task->has_reset = true; - target_task->next = NULL; - if (twdt_config->list == NULL) { //Adding to empty list - twdt_config->list = target_task; - } else { //Adding to tail of list - twdt_task_t *task; - for (task = twdt_config->list; task->next != NULL; task = task->next){ - ; //point task to current tail of TWDT task list - } - task->next = target_task; + // Allocate entry for task + twdt_entry_t *entry = calloc(1, sizeof(twdt_entry_t)); + ESP_GOTO_ON_FALSE((entry != NULL), ESP_ERR_NO_MEM, alloc_err, TAG, "insufficient memory"); + entry->task_handle = handle; + entry->has_reset = false; + + portENTER_CRITICAL(&spinlock); + // Check TWDT state + ESP_GOTO_ON_FALSE((p_twdt_obj != NULL), ESP_ERR_INVALID_STATE, state_err, TAG, "task watchdog was never initialized"); + // Check if the task is an entry, and if all entries have been reset + bool all_reset; + 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"); + // 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 - for(int i = 0; i < portNUM_PROCESSORS; i++){ - if(handle == xTaskGetIdleTaskHandleForCPU(i)){ + // If the task was the idle task, register the idle hook callback to appropriate core + for (int i = 0; i < portNUM_PROCESSORS; i++) { + if (handle == xTaskGetIdleTaskHandleForCPU(i)) { ESP_ERROR_CHECK(esp_register_freertos_idle_hook_for_cpu(idle_hook_cb, i)); 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; + +state_err: + portEXIT_CRITICAL(&spinlock); + free(entry); +alloc_err: + return ret; } esp_err_t esp_task_wdt_reset(void) { - portENTER_CRITICAL(&twdt_spinlock); - //TWDT must already be initialized - ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE); - + esp_err_t ret; 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; - - //Check if task exists in task list, and if all other tasks have reset - target_task = find_task_in_twdt_list(handle, &all_reset); - //Return error if trying to reset task that is not on the task list - ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_NOT_FOUND); - - 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 + twdt_entry_t *entry; + entry = find_entry_from_handle_and_check_all_reset(handle, &all_reset); + ESP_GOTO_ON_FALSE((entry != NULL), ESP_ERR_NOT_FOUND, err, TAG, "task not found"); + // Mark entry as reset and issue timer reset if all entries have been reset + 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 reset_hw_timer(); } + ret = ESP_OK; +err: + portEXIT_CRITICAL(&spinlock); - portEXIT_CRITICAL(&twdt_spinlock); - return ESP_OK; + return ret; } esp_err_t esp_task_wdt_delete(TaskHandle_t handle) { - if(handle == NULL){ + esp_err_t ret; + if (handle == NULL) { 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; - target_task = find_task_in_twdt_list(handle, &all_reset); - //Task doesn't exist on list. Return error - ASSERT_EXIT_CRIT_RETURN((target_task != NULL), ESP_ERR_INVALID_ARG); - - if(target_task == twdt_config->list){ //target_task is head of list. Delete - twdt_config->list = target_task->next; - free(target_task); - }else{ //target_task not head of list. Delete - 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); + twdt_entry_t *entry; + entry = find_entry_from_handle_and_check_all_reset(handle, &all_reset); + ESP_GOTO_ON_FALSE((entry != NULL), ESP_ERR_NOT_FOUND, err, TAG, "task not found"); + // Remove entry + SLIST_REMOVE(&p_twdt_obj->entries_slist, entry, twdt_entry, slist_entry); + // Reset hardware timer if all remaining tasks have reset + if (all_reset) { + reset_hw_timer(); } + portEXIT_CRITICAL(&spinlock); - //If idle task, deregister idle hook callback form appropriate core - for(int i = 0; i < portNUM_PROCESSORS; i++){ - if(handle == xTaskGetIdleTaskHandleForCPU(i)){ + // Free the entry + free(entry); + // 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); break; } } - - if(all_reset){ //Reset hardware timer if all remaining tasks have reset - reset_hw_timer(); - } - - portEXIT_CRITICAL(&twdt_spinlock); return ESP_OK; + +err: + portEXIT_CRITICAL(&spinlock); + return ret; } esp_err_t esp_task_wdt_status(TaskHandle_t handle) { - if(handle == NULL){ + esp_err_t ret; + if (handle == NULL) { handle = xTaskGetCurrentTaskHandle(); } - portENTER_CRITICAL(&twdt_spinlock); - //Return if TWDT is not initialized - ASSERT_EXIT_CRIT_RETURN((twdt_config != NULL), ESP_ERR_INVALID_STATE); + 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; + 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; - 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; + return ret; } diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index 7cc6f8d07b..984779b735 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -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/usb_console.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/xtensa/expression_with_stack.c components/esp_system/port/public_compat/brownout.h