diff --git a/components/esp_pm/linker.lf b/components/esp_pm/linker.lf index 89e5bb5d47..77f9f6b2cb 100644 --- a/components/esp_pm/linker.lf +++ b/components/esp_pm/linker.lf @@ -37,6 +37,10 @@ entries: task_wdt:find_entry_from_task_handle_and_check_all_reset (noflash) task_wdt:esp_task_wdt_reset (noflash) task_wdt:esp_task_wdt_reset_user (noflash) + if ESP_TASK_WDT_USE_ESP_TIMER = y: + task_wdt_impl_esp_timer:esp_task_wdt_impl_timer_feed (noflash) + else: + task_wdt_impl_timergroup:esp_task_wdt_impl_timer_feed (noflash) [mapping:esp_timer_pm] archive: libesp_timer.a @@ -44,7 +48,8 @@ entries: if PM_SLP_IRAM_OPT = y: # esp_timer_feed is called from task_wdt_timer_feed, so put it # in IRAM if task_wdt_timer_feed itself is in IRAM. - esp_timer:esp_timer_feed (noflash) + if ESP_TASK_WDT_USE_ESP_TIMER = y: + esp_timer:esp_timer_feed (noflash) if ESP_TIMER_IMPL_TG0_LAC = y: esp_timer_impl_lac:esp_timer_impl_lock (noflash) esp_timer_impl_lac:esp_timer_impl_unlock (noflash) diff --git a/components/esp_system/CMakeLists.txt b/components/esp_system/CMakeLists.txt index eac85f6bdc..d00732c59e 100644 --- a/components/esp_system/CMakeLists.txt +++ b/components/esp_system/CMakeLists.txt @@ -26,7 +26,13 @@ else() "debug_stubs.c") if(CONFIG_ESP_TASK_WDT) - list(APPEND srcs "task_wdt.c") + list(APPEND srcs "task_wdt/task_wdt.c") + + if(CONFIG_ESP_TASK_WDT_USE_ESP_TIMER) + list(APPEND srcs "task_wdt/task_wdt_impl_esp_timer.c") + else() + list(APPEND srcs "task_wdt/task_wdt_impl_timergroup.c") + endif() endif() if(CONFIG_ESP_SYSTEM_USE_EH_FRAME) diff --git a/components/esp_system/include/esp_private/crosscore_int.h b/components/esp_system/include/esp_private/crosscore_int.h index 0acdbb8159..e392fd1551 100644 --- a/components/esp_system/include/esp_private/crosscore_int.h +++ b/components/esp_system/include/esp_private/crosscore_int.h @@ -61,6 +61,7 @@ void esp_crosscore_int_send_gdb_call(int core_id); */ void esp_crosscore_int_send_print_backtrace(int core_id); +#if CONFIG_ESP_TASK_WDT /** * Send an interrupt to a CPU indicating it call `task_wdt_timeout_abort_xtensa`. * This will make the CPU abort, using the interrupted task frame. @@ -72,7 +73,9 @@ void esp_crosscore_int_send_print_backtrace(int core_id); * @param core_id Core that should abort */ void esp_crosscore_int_send_twdt_abort(int core_id); -#endif + +#endif // CONFIG_ESP_TASK_WDT +#endif // !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 && !CONFIG_IDF_TARGET_ESP32C2 #ifdef __cplusplus } diff --git a/components/esp_system/include/esp_private/esp_task_wdt.h b/components/esp_system/include/esp_private/esp_task_wdt.h index ecbbdd04d1..a9d376f272 100644 --- a/components/esp_system/include/esp_private/esp_task_wdt.h +++ b/components/esp_system/include/esp_private/esp_task_wdt.h @@ -15,6 +15,17 @@ extern "C" { #endif +/** + * @brief Type used to define the context of a Task WatchDog Timer implementation. + * This is used internally in the TWDT driver, it is implementation specific. + */ +typedef void* twdt_ctx_t; + +/** + * @brief Type of the function used as an ISR callback. + */ +typedef void (*twdt_isr_callback)(void*); + /** * @brief Stop the Task Watchdog Timer (TWDT) * diff --git a/components/esp_system/include/esp_private/esp_task_wdt_impl.h b/components/esp_system/include/esp_private/esp_task_wdt_impl.h new file mode 100644 index 0000000000..43adc3238f --- /dev/null +++ b/components/esp_system/include/esp_private/esp_task_wdt_impl.h @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "../esp_task_wdt.h" +#include "esp_private/esp_task_wdt.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Allocate and initialize the Task Watchdog Timer (TWDT) with the given configuration. + * + * @param[in] config Pointer to the configuration structure + * @param[out] obj Abstract context for the current timer, this will be passed to all the other functions + * + * @return + * - ESP_OK: Successfully initialized and configured the timer + * - Other: Failed to initialize the timer + */ +esp_err_t esp_task_wdt_impl_timer_allocate(const esp_task_wdt_config_t *config, + twdt_isr_callback callback, + twdt_ctx_t *obj); + + +/** + * @brief Reconfigure a timer. + * + * The timer must be stopped when calling this function. The timer will not be restarted at the end of this + * function. + * + * @param[in] config Pointer to the configuration structure + * + * @return + * - ESP_OK: Successfully reconfigured the timer + * - Other: Failed to reconfigure the timer + */ +esp_err_t esp_task_wdt_impl_timer_reconfigure(twdt_ctx_t obj, const esp_task_wdt_config_t *config); + +/** + * @brief Free the Task Watchdog Timer (TWDT). + * + * @param[in] obj Abstract implementation context + * + */ +void esp_task_wdt_impl_timer_free(twdt_ctx_t obj); + + +/** + * @brief Feed the Task Watchdog Timer (TWDT) + * + * Feed the timer underneath to prevent it from triggering for the next period (configured at initialization). + * + * @param[in] obj Abstract implementation context + * @return + * - ESP_OK: timer successfully feeded + * - Other: failed to feed the timer + */ +esp_err_t esp_task_wdt_impl_timer_feed(twdt_ctx_t obj); + + +/** + * @brief Function invoked as soon as the Task Watchdog Timer (TWDT) ISR callback is called. + * + * @param[in] obj Abstract implementation context + */ +void esp_task_wdt_impl_timeout_triggered(twdt_ctx_t obj); + + +/** + * @brief Stop the Task Watchdog Timer (TWDT). + * + * @param[in] obj Abstract implementation context + * + */ +esp_err_t esp_task_wdt_impl_timer_stop(twdt_ctx_t obj); + + +/** + * @brief Restart the Task Watchdog Timer (TWDT) + * + * This function will restart/resume the timer after it has been stopped. + * + * @param[in] obj Abstract implementation context + * @return + * - ESP_OK: timer successfully stopped + * - Other: failed to stop the timer + */ +esp_err_t esp_task_wdt_impl_timer_restart(twdt_ctx_t obj); + + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_system/include/esp_private/system_internal.h b/components/esp_system/include/esp_private/system_internal.h index 1e3f1f1410..6851a3fc85 100644 --- a/components/esp_system/include/esp_private/system_internal.h +++ b/components/esp_system/include/esp_private/system_internal.h @@ -11,8 +11,9 @@ extern "C" { #endif #include "esp_system.h" +#include "soc/soc_caps.h" -#if !CONFIG_ESP_TASK_WDT_USE_ESP_TIMER +#if SOC_TIMER_GROUPS >= 2 /* All the targets that have more than one timer group are using * APB clock by default, which frequency is 80MHz. @@ -24,8 +25,9 @@ extern "C" { #else -/* The targets that have a single timer group use XTAL clock as the - * default clock. XTAL clock frequency is 40MHz. */ +/* The targets that have a single timer group use a 40MHz clock for the + * Timer Group 0. Let's adapt the prescaler value accordingly. + */ #define MWDT0_TICK_PRESCALER 20000 #define MWDT0_TICKS_PER_US 500 diff --git a/components/esp_system/include/esp_task_wdt.h b/components/esp_system/include/esp_task_wdt.h index 42ff54963b..c5c1d5fa11 100644 --- a/components/esp_system/include/esp_task_wdt.h +++ b/components/esp_system/include/esp_task_wdt.h @@ -32,19 +32,36 @@ typedef struct esp_task_wdt_user_handle_s * esp_task_wdt_user_handle_t; /** * @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 current configuration. This funciton will also subscribe the idle tasks if + * This function configures and initializes the TWDT. This function will subscribe the idle tasks if * configured to do so. For other tasks, users can subscribe them using esp_task_wdt_add() or esp_task_wdt_add_user(). + * This function won't start the timer if no task have been registered yet. * * @note esp_task_wdt_init() must only be called after the scheduler is started. Moreover, it must not be called by * multiple tasks simultaneously. * @param[in] config Configuration structure * @return * - ESP_OK: Initialization was successful + * - ESP_ERR_INVALID_STATE: Already initialized * - Other: Failed to initialize TWDT */ esp_err_t esp_task_wdt_init(const esp_task_wdt_config_t *config); +/** + * @brief Reconfigure the Task Watchdog Timer (TWDT) + * + * The function reconfigures the running TWDT. It must already be initialized when this function is called. + * + * @note esp_task_wdt_reconfigure() must not be called by multiple tasks simultaneously. + * + * @param[in] config Configuration structure + * + * @return + * - ESP_OK: Reconfiguring was successful + * - ESP_ERR_INVALID_STATE: TWDT not initialized yet + * - Other: Failed to initialize TWDT + */ +esp_err_t esp_task_wdt_reconfigure(const esp_task_wdt_config_t *config); + /** * @brief Deinitialize the Task Watchdog Timer (TWDT) * diff --git a/components/esp_system/task_wdt.c b/components/esp_system/task_wdt/task_wdt.c similarity index 81% rename from components/esp_system/task_wdt.c rename to components/esp_system/task_wdt/task_wdt.c index bf287a1e9e..9bd7770ca2 100644 --- a/components/esp_system/task_wdt.c +++ b/components/esp_system/task_wdt/task_wdt.c @@ -11,20 +11,18 @@ #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "hal/wdt_hal.h" +#include "freertos/task_snapshot.h" #include "esp_err.h" #include "esp_attr.h" #include "esp_check.h" #include "esp_log.h" -#include "esp_intr_alloc.h" #include "esp_debug_helpers.h" #include "esp_freertos_hooks.h" #include "esp_task_wdt.h" -#include "esp_private/periph_ctrl.h" #include "esp_private/system_internal.h" #include "esp_private/crosscore_int.h" -#include "freertos/task_snapshot.h" -#include "esp_timer.h" +#include "esp_private/esp_task_wdt.h" +#include "esp_private/esp_task_wdt_impl.h" #if CONFIG_ESP_SYSTEM_USE_EH_FRAME #include "esp_private/eh_frame_parser.h" @@ -40,6 +38,9 @@ extern void panic_print_registers(const void *frame, int core); * a different context than the one it's called from. */ extern void xt_unhandled_exception(void *frame); +/* Forward declaration of the idle hook callback */ +static bool idle_hook_cb(void); + /* Global flag set to make the `panic` mechanism think a real `abort()` was * called. This is used in the ISR handler, in case we have to panic when * a task doesn't feed its timer. */ @@ -50,18 +51,6 @@ bool g_twdt_isr = false; // --------------------------------------------------- Definitions ----------------------------------------------------- -// ----------------------- Macros -------------------------- - -// Use a hardware timer implementation or a software implementation -#define TWDT_HARDWARE_IMPL !CONFIG_ESP_TASK_WDT_USE_ESP_TIMER - -#if TWDT_HARDWARE_IMPL -// HAL related variables and constants only defined in a hardware implementation -#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 -#endif // TWDT_HARDWARE_IMPL - // ---------------------- Typedefs ------------------------- /** @@ -78,16 +67,11 @@ struct twdt_entry { // Structure used to hold run time configuration of the TWDT typedef struct twdt_obj twdt_obj_t; struct twdt_obj { -#if TWDT_HARDWARE_IMPL - wdt_hal_context_t hal; - intr_handle_t intr_handle; -#else // TWDT_HARDWARE_IMPL - esp_timer_handle_t sw_timer; // We use esp_timer to simulate a hardware WDT - uint32_t period_ms; -#endif // TWDT_HARDWARE_IMPL + twdt_ctx_t impl_ctx; SLIST_HEAD(entry_list_head, twdt_entry) entries_slist; uint32_t idle_core_mask; // Current core's who's idle tasks are subscribed bool panic; // Flag to trigger panic when TWDT times out + bool waiting_for_task; // Flag to start the timer as soon as a task is added }; // ----------------------- Objects ------------------------- @@ -104,109 +88,17 @@ static char core_user_names[portNUM_PROCESSORS][CORE_USER_NAME_LEN]; // ----------------------------------------------------- Private ------------------------------------------------------- -// ---------------------- Callbacks ------------------------ - -/** - * @brief Idle hook callback - * - * Idle hook callback called by the idle tasks to feed the TWDT - * - * @return Whether the idle tasks should continue idling - */ -static bool idle_hook_cb(void) -{ -#if CONFIG_FREERTOS_SMP - esp_task_wdt_reset_user(core_user_handles[xPortGetCoreID()]); -#else // CONFIG_FREERTOS_SMP - esp_task_wdt_reset(); -#endif // CONFIG_FREERTOS_SMP - return true; -} - // ----------------------- Helpers ------------------------- -#if !TWDT_HARDWARE_IMPL - -/** - * Private API provided by esp_timer component to feed a timer without - * the need of disabling it, removing it and inserting it manually. - */ -esp_err_t esp_timer_feed(esp_timer_handle_t timer); - -#endif // !TWDT_HARDWARE_IMPL - - -static esp_err_t task_wdt_timer_stop(twdt_obj_t *obj) -{ - esp_err_t ret = ESP_OK; - - if (obj == NULL) { - return ESP_ERR_INVALID_STATE; - } - -#if TWDT_HARDWARE_IMPL - // All tasks have reset; Feed the underlying timer. - wdt_hal_write_protect_disable(&obj->hal); - wdt_hal_disable(&obj->hal); - wdt_hal_write_protect_enable(&obj->hal); -#else // TWDT_HARDWARE_IMPL - if (obj->sw_timer == NULL) { - ret = ESP_ERR_INVALID_STATE; - } - - if (ret == ESP_OK) { - esp_timer_stop(obj->sw_timer); - } -#endif // TWDT_HARDWARE_IMPL - - return ret; -} - - -static esp_err_t task_wdt_timer_restart(twdt_obj_t *obj) -{ - esp_err_t ret = ESP_OK; - - if (obj == NULL) { - return ESP_ERR_INVALID_STATE; - } - -#if TWDT_HARDWARE_IMPL - // All tasks have reset; Feed the underlying timer. - wdt_hal_write_protect_disable(&obj->hal); - wdt_hal_enable(&obj->hal); - wdt_hal_feed(&obj->hal); - wdt_hal_write_protect_enable(&obj->hal); -#else // TWDT_HARDWARE_IMPL - if (obj->sw_timer == NULL) { - ret = ESP_ERR_INVALID_STATE; - } - - if (ret == ESP_OK) { - esp_timer_start_periodic(obj->sw_timer, obj->period_ms * 1000); - } -#endif // TWDT_HARDWARE_IMPL - - return ret; -} - /** * @brief Reset the timer and reset flags of each entry * When entering this function, the spinlock has already been taken, no need to take it back. */ static void task_wdt_timer_feed(void) { -#if TWDT_HARDWARE_IMPL - // 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); -#else // TWDT_HARDWARE_IMPL - /* No matter if feeding succeeded or not, we have to reset each list entry's flags. - * Thus, ignore the return value. */ - esp_timer_feed(p_twdt_obj->sw_timer); -#endif // TWDT_HARDWARE_IMPL - //Clear the has_reset flag in each entry + esp_task_wdt_impl_timer_feed(p_twdt_obj->impl_ctx); + + /* Clear 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; @@ -300,6 +192,11 @@ static esp_err_t add_entry(bool is_task, void *entry_data, twdt_entry_t **entry_ } // Add entry to list SLIST_INSERT_HEAD(&p_twdt_obj->entries_slist, entry, slist_entry); + // Start the timer if it has not been started yet and was waiting on a task to registered + if (p_twdt_obj->waiting_for_task) { + esp_task_wdt_impl_timer_restart(p_twdt_obj->impl_ctx); + p_twdt_obj->waiting_for_task = false; + } if (all_reset) { //Reset hardware timer if all other tasks in list have reset in task_wdt_timer_feed(); } @@ -340,8 +237,15 @@ static esp_err_t delete_entry(bool is_task, void *entry_data) } // 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) { + /* Stop the timer if we don't have any more tasks/objects to watch */ + if (SLIST_EMPTY(&p_twdt_obj->entries_slist)) { + p_twdt_obj->waiting_for_task = true; + esp_task_wdt_impl_timer_stop(p_twdt_obj->impl_ctx); + } else { + p_twdt_obj->waiting_for_task = false; + } + /* Reset hardware timer if all remaining tasks have reset and if the list of tasks is not empty */ + if (!p_twdt_obj->waiting_for_task && all_reset) { task_wdt_timer_feed(); } portEXIT_CRITICAL(&spinlock); @@ -569,6 +473,25 @@ static void task_wdt_timeout_handling(int cores_fail, bool panic) #endif // CONFIG_IDF_TARGET_ARCH_RISCV +// ---------------------- Callbacks ------------------------ + +/** + * @brief Idle hook callback + * + * Idle hook callback called by the idle tasks to feed the TWDT + * + * @return Whether the idle tasks should continue idling + */ +static bool idle_hook_cb(void) +{ +#if CONFIG_FREERTOS_SMP + esp_task_wdt_reset_user(core_user_handles[xPortGetCoreID()]); +#else // CONFIG_FREERTOS_SMP + esp_task_wdt_reset(); +#endif // CONFIG_FREERTOS_SMP + return true; +} + /** * @brief TWDT timeout ISR function * @@ -580,12 +503,7 @@ static void task_wdt_timeout_handling(int cores_fail, bool panic) static void task_wdt_isr(void *arg) { portENTER_CRITICAL_ISR(&spinlock); -#if TWDT_HARDWARE_IMPL - // 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); -#endif // TWDT_HARDWARE_IMPL + esp_task_wdt_impl_timeout_triggered(p_twdt_obj->impl_ctx); // If there are no entries, there's nothing to do. if (SLIST_EMPTY(&p_twdt_obj->entries_slist)) { @@ -667,78 +585,6 @@ static void task_wdt_isr(void *arg) task_wdt_timeout_handling(cpus_fail, panic); } -static esp_err_t task_wdt_timer_allocate(twdt_obj_t *obj, const esp_task_wdt_config_t *config) -{ -#if TWDT_HARDWARE_IMPL - esp_err_t ret = esp_intr_alloc(ETS_TG0_WDT_LEVEL_INTR_SOURCE, 0, task_wdt_isr, NULL, &obj->intr_handle); - - if (ret == ESP_OK) { - periph_module_enable(PERIPH_TIMG0_MODULE); - wdt_hal_init(&obj->hal, TWDT_INSTANCE, TWDT_PRESCALER, true); - // Assign the driver object - wdt_hal_write_protect_disable(&obj->hal); - // Configure 1st stage timeout and behavior - wdt_hal_config_stage(&obj->hal, WDT_STAGE0, config->timeout_ms * (1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT); - // Configure 2nd stage timeout and behavior - wdt_hal_config_stage(&obj->hal, WDT_STAGE1, config->timeout_ms * (2 * 1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM); - // Enable the WDT - wdt_hal_enable(&obj->hal); - wdt_hal_write_protect_enable(&obj->hal); - } - - return ret; -#else // TWDT_HARDWARE_IMPL - - const esp_timer_create_args_t timer_args = { - .callback = task_wdt_isr, - .arg = NULL, - .dispatch_method = ESP_TIMER_ISR, - .name = "Task software watchdog", - .skip_unhandled_events = true - }; - - /* Software Task timer. As we don't have a spare hardware watchdog timer, we will use esp_timer to simulate one. */ - esp_err_t ret = esp_timer_create(&timer_args, &obj->sw_timer); - ESP_GOTO_ON_FALSE((ret == ESP_OK), ret, reterr, TAG, "could not start periodic timer"); - - /* Configure it as a periodic timer, so that we check the Tasks everytime it is triggered. - * Its parameter is in microseconds, but the config's is in milliseconds, convert it. */ - obj->period_ms = config->timeout_ms; - ret = esp_timer_start_periodic(obj->sw_timer, config->timeout_ms * 1000); - ESP_GOTO_ON_FALSE((ret == ESP_OK), ret, freeret, TAG, "could not start periodic timer"); - - return ret; -freeret: - /* If we reach this point, it means that we were unable to program the timer as a periodic one, so - * no need to stop it before deleting it. */ - esp_timer_delete(obj->sw_timer); -reterr: - return ret; -#endif // TWDT_HARDWARE_IMPL -} - -static void task_wdt_timer_disable(twdt_obj_t *obj) -{ - esp_err_t ret = ESP_OK; - ret = task_wdt_timer_stop(obj); -#if TWDT_HARDWARE_IMPL - // Stop hardware timer and the interrupt associated - wdt_hal_deinit(&obj->hal); - esp_intr_disable(obj->intr_handle); -#endif // TWDT_HARDWARE_IMPL - assert(ret == ESP_OK); -} - - -static void task_wdt_timer_free(twdt_obj_t *obj) -{ -#if TWDT_HARDWARE_IMPL - ESP_ERROR_CHECK(esp_intr_free(obj->intr_handle)); // Deregister interrupt -#else // TWDT_HARDWARE_IMPL - esp_timer_delete(obj->sw_timer); -#endif // TWDT_HARDWARE_IMPL -} - // ----------------------------------------------------- Public -------------------------------------------------------- esp_err_t esp_task_wdt_init(const esp_task_wdt_config_t *config) @@ -747,56 +593,123 @@ esp_err_t esp_task_wdt_init(const esp_task_wdt_config_t *config) ESP_RETURN_ON_FALSE(p_twdt_obj == NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT already initialized"); esp_err_t ret = ESP_OK; twdt_obj_t *obj = NULL; - uint32_t old_core_mask = 0; - // Allocate and initialize the global object + /* Allocate and initialize the global 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 = config->trigger_panic; - // Allocate the timer itself - ret = task_wdt_timer_allocate(obj, config); + /* Allocate the timer itself, NOT STARTED */ + ret = esp_task_wdt_impl_timer_allocate(config, task_wdt_isr, &obj->impl_ctx); if (ret != ESP_OK) { goto err; } - // No error so far, we can assign it to the driver object + /* No error so far, we can assign it to the driver object */ p_twdt_obj = obj; - // Update which core's idle tasks are subscribed - old_core_mask = p_twdt_obj->idle_core_mask; + /* Update which core's idle tasks are subscribed */ p_twdt_obj->idle_core_mask = config->idle_core_mask; - if (old_core_mask) { - // Unsubscribe all previously watched core idle tasks - unsubscribe_idle(old_core_mask); - } if (config->idle_core_mask) { - // Subscribe the new cores idle tasks + /* Subscribe the new cores idle tasks */ subscribe_idle(config->idle_core_mask); } + /* Start the timer only if we are watching some tasks */ + if (!SLIST_EMPTY(&p_twdt_obj->entries_slist)) { + p_twdt_obj->waiting_for_task = false; + esp_task_wdt_impl_timer_restart(p_twdt_obj->impl_ctx); + } else { + p_twdt_obj->waiting_for_task = true; + } + return ESP_OK; err: free(obj); return ret; } +esp_err_t esp_task_wdt_reconfigure(const esp_task_wdt_config_t *config) +{ + ESP_RETURN_ON_FALSE((config != NULL && config->idle_core_mask < (1 << portNUM_PROCESSORS)), ESP_ERR_INVALID_ARG, TAG, "Invalid arguments"); + ESP_RETURN_ON_FALSE(p_twdt_obj != NULL, ESP_ERR_INVALID_STATE, TAG, "TWDT not initialized yet"); + uint32_t old_core_mask = 0; + esp_err_t ret = ESP_OK; + + /* Stop the timer to make sure we don't get into the ISR while reconfiguring the TWDT */ + portENTER_CRITICAL(&spinlock); + ret = esp_task_wdt_impl_timer_stop(p_twdt_obj->impl_ctx); + if (ret != ESP_OK) { + goto err; + } + + /* We can start reconfiguring the tasks */ + p_twdt_obj->panic = config->trigger_panic; + + /* Reconfigure the timer underneath (without restarting it) */ + ret = esp_task_wdt_impl_timer_reconfigure(p_twdt_obj->impl_ctx, config); + if (ret != ESP_OK) { + goto err; + } + + old_core_mask = p_twdt_obj->idle_core_mask; + /* If the new mask is different than the old one, we have to subscribe the new idle tasks */ + if (old_core_mask != config->idle_core_mask) { + p_twdt_obj->idle_core_mask = config->idle_core_mask; + + /* Unsubscribe all previously watched core idle tasks */ + unsubscribe_idle(old_core_mask); + + if (config->idle_core_mask) { + /* Subscribe the new cores idle tasks */ + subscribe_idle(config->idle_core_mask); + } + } + + /* Start the timer only if we are watching some tasks */ + if (!SLIST_EMPTY(&p_twdt_obj->entries_slist)) { + esp_task_wdt_impl_timer_restart(p_twdt_obj->impl_ctx); + } + + portEXIT_CRITICAL(&spinlock); +err: + return ESP_OK; +} + esp_err_t esp_task_wdt_stop(void) { esp_err_t ret = ESP_OK; - portENTER_CRITICAL(&spinlock); - ret = task_wdt_timer_stop(p_twdt_obj); - portEXIT_CRITICAL(&spinlock); + + /* If the timer has not been initialized, do not attempt to stop it */ + if (p_twdt_obj == NULL) { + ret = ESP_ERR_INVALID_STATE; + } + + if (ret == ESP_OK) { + portENTER_CRITICAL(&spinlock); + ret = esp_task_wdt_impl_timer_stop(p_twdt_obj->impl_ctx); + portEXIT_CRITICAL(&spinlock); + } + return ret; } esp_err_t esp_task_wdt_restart(void) { esp_err_t ret = ESP_OK; - portENTER_CRITICAL(&spinlock); - ret = task_wdt_timer_restart(p_twdt_obj); - portEXIT_CRITICAL(&spinlock); + + /* If the timer has not been initialized, do not attempt to stop it */ + if (p_twdt_obj == NULL) { + ret = ESP_ERR_INVALID_STATE; + } + + if (ret == ESP_OK) { + portENTER_CRITICAL(&spinlock); + ret = esp_task_wdt_impl_timer_restart(p_twdt_obj->impl_ctx); + portEXIT_CRITICAL(&spinlock); + } + return ret; } @@ -813,10 +726,10 @@ esp_err_t esp_task_wdt_deinit(void) ESP_GOTO_ON_FALSE_ISR(SLIST_EMPTY(&p_twdt_obj->entries_slist), ESP_ERR_INVALID_STATE, err, TAG, "Tasks/users still subscribed"); // Disable the timer - task_wdt_timer_disable(p_twdt_obj); + esp_task_wdt_impl_timer_stop(p_twdt_obj->impl_ctx); // Free driver resources - task_wdt_timer_free(p_twdt_obj); + esp_task_wdt_impl_timer_free(p_twdt_obj->impl_ctx); // Free the global object free(p_twdt_obj); diff --git a/components/esp_system/task_wdt/task_wdt_impl_esp_timer.c b/components/esp_system/task_wdt/task_wdt_impl_esp_timer.c new file mode 100644 index 0000000000..35bc61bc3e --- /dev/null +++ b/components/esp_system/task_wdt/task_wdt_impl_esp_timer.c @@ -0,0 +1,155 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "sdkconfig.h" +#include "hal/wdt_hal.h" +#include "esp_err.h" +#include "esp_attr.h" +#include "esp_check.h" +#include "esp_log.h" +#include "esp_debug_helpers.h" +#include "esp_timer.h" +#include "esp_private/esp_task_wdt_impl.h" + +/** + * Private API provided by esp_timer component to feed a timer without + * the need of disabling it, removing it and inserting it manually. + */ +esp_err_t esp_timer_feed(esp_timer_handle_t timer); + +/** + * Context for the software implementation of the Task WatchDog Timer. + * This will be passed as a parameter to public functions below. */ +typedef struct { + esp_timer_handle_t sw_timer; + uint32_t period_ms; +} twdt_ctx_soft_t; + +/** + * Declare the initial context as static. It will be passed to the + * task_wdt implementation as the implementation context in the + * init function. */ +static twdt_ctx_soft_t init_context; + +static const char *TAG = "task_wdt_impl_soft"; + + +esp_err_t esp_task_wdt_impl_timer_allocate(const esp_task_wdt_config_t *config, + twdt_isr_callback callback, + twdt_ctx_t *obj) +{ + twdt_ctx_soft_t *ctx = &init_context; + const esp_timer_create_args_t timer_args = { + .callback = callback, + .arg = NULL, + .dispatch_method = ESP_TIMER_ISR, + .name = "Task software watchdog", + .skip_unhandled_events = true + }; + + /* Software Task timer. As we don't have a spare hardware watchdog timer, we will use esp_timer to simulate one */ + esp_err_t ret = esp_timer_create(&timer_args, &ctx->sw_timer); + ESP_GOTO_ON_FALSE((ret == ESP_OK), ret, reterr, TAG, "could not start periodic timer"); + + /* Configure it as a periodic timer, so that we check the Tasks everytime it is triggered. + * No need to start the timer here, it will be started later with `esp_task_wdt_impl_timer_restart` */ + ctx->period_ms = config->timeout_ms; + + /* Return our context to the caller */ + *obj = (twdt_ctx_t) ctx; + +reterr: + return ret; +} + +esp_err_t esp_task_wdt_impl_timer_reconfigure(twdt_ctx_t obj, const esp_task_wdt_config_t *config) +{ + esp_err_t ret = ESP_OK; + twdt_ctx_soft_t* ctx = (twdt_ctx_soft_t*) obj; + + if (config == NULL || ctx == NULL) { + ret = ESP_ERR_INVALID_STATE; + } + + if (ret == ESP_OK) { + /* The timer is stopped, we only need to update the period in our context, next time we start the + * timer with `esp_task_wdt_impl_timer_restart`, we will pass the context's period to the + * underlying esp_timer instance. */ + ctx->period_ms = config->timeout_ms; + } + + return ret; +} + + +void esp_task_wdt_impl_timer_free(twdt_ctx_t obj) +{ + const twdt_ctx_soft_t* ctx = (twdt_ctx_soft_t*) obj; + + if (ctx != NULL && ctx->sw_timer != NULL) { + ESP_ERROR_CHECK(esp_timer_delete(ctx->sw_timer)); + } +} + + +esp_err_t esp_task_wdt_impl_timer_feed(twdt_ctx_t obj) +{ + esp_err_t ret = ESP_OK; + const twdt_ctx_soft_t* ctx = (twdt_ctx_soft_t*) obj; + + if (ctx == NULL) { + ret = ESP_ERR_INVALID_STATE; + } + + if (ret == ESP_OK) { + ret = esp_timer_feed(ctx->sw_timer); + } + + return ret; +} + + +void esp_task_wdt_impl_timeout_triggered(twdt_ctx_t obj) +{ + (void) obj; +} + + +esp_err_t esp_task_wdt_impl_timer_stop(twdt_ctx_t obj) +{ + esp_err_t ret = ESP_OK; + const twdt_ctx_soft_t* ctx = (twdt_ctx_soft_t*) obj; + + if (ctx == NULL || ctx->sw_timer == NULL) { + ret = ESP_ERR_INVALID_STATE; + } + + if (ret == ESP_OK) { + ret = esp_timer_stop(ctx->sw_timer); + } + + return ret; +} + + +esp_err_t esp_task_wdt_impl_timer_restart(twdt_ctx_t obj) +{ + esp_err_t ret = ESP_OK; + twdt_ctx_soft_t* ctx = (twdt_ctx_soft_t*) obj; + + if (ctx == NULL || ctx->sw_timer == NULL) { + ret = ESP_ERR_INVALID_STATE; + } + + if (ret == ESP_OK) { + esp_timer_start_periodic(ctx->sw_timer, ctx->period_ms * 1000); + } + + return ret; +} diff --git a/components/esp_system/task_wdt/task_wdt_impl_timergroup.c b/components/esp_system/task_wdt/task_wdt_impl_timergroup.c new file mode 100644 index 0000000000..57af2cd7e0 --- /dev/null +++ b/components/esp_system/task_wdt/task_wdt_impl_timergroup.c @@ -0,0 +1,184 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "sdkconfig.h" +#include "hal/wdt_hal.h" +#include "esp_err.h" +#include "esp_attr.h" +#include "esp_intr_alloc.h" +#include "esp_private/system_internal.h" +#include "esp_private/periph_ctrl.h" +#include "esp_private/esp_task_wdt_impl.h" + +#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 +#define TWDT_PERIPH_MODULE PERIPH_TIMG0_MODULE +#define TWDT_INTR_SOURCE ETS_TG0_WDT_LEVEL_INTR_SOURCE + +/** + * Context for the software implementation of the Task WatchDog Timer. + * This will be passed as a parameter to public functions below. */ +typedef struct { + wdt_hal_context_t hal; + intr_handle_t intr_handle; +} twdt_ctx_hard_t; + +/** + * Declare the initial context as static. It will be passed to the + * task_wdt implementation as the implementation context in the + * init function. */ +static twdt_ctx_hard_t init_context; + + + +esp_err_t esp_task_wdt_impl_timer_allocate(const esp_task_wdt_config_t *config, + twdt_isr_callback callback, + twdt_ctx_t *obj) +{ + esp_err_t ret = ESP_OK; + twdt_ctx_hard_t *ctx = &init_context; + + if (config == NULL || obj == NULL) { + ret = ESP_ERR_INVALID_STATE; + } + + if (ret == ESP_OK) { + esp_intr_alloc(TWDT_INTR_SOURCE, 0, callback, NULL, &ctx->intr_handle); + } + + if (ret == ESP_OK) { + periph_module_enable(TWDT_PERIPH_MODULE); + wdt_hal_init(&ctx->hal, TWDT_INSTANCE, TWDT_PRESCALER, true); + + wdt_hal_write_protect_disable(&ctx->hal); + // Configure 1st stage timeout and behavior + wdt_hal_config_stage(&ctx->hal, WDT_STAGE0, config->timeout_ms * (1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT); + // Configure 2nd stage timeout and behavior + wdt_hal_config_stage(&ctx->hal, WDT_STAGE1, config->timeout_ms * (2 * 1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM); + // No need to enable to enable the WDT here, it will be enabled with `esp_task_wdt_impl_timer_restart` + wdt_hal_write_protect_enable(&ctx->hal); + + /* Return the implementation context to the caller */ + *obj = (twdt_ctx_t) ctx; + } + + return ret; +} + + +esp_err_t esp_task_wdt_impl_timer_reconfigure(twdt_ctx_t obj, const esp_task_wdt_config_t *config) +{ + esp_err_t ret = ESP_OK; + + twdt_ctx_hard_t* ctx = (twdt_ctx_hard_t*) obj; + + if (config == NULL || ctx == NULL) { + ret = ESP_ERR_INVALID_STATE; + } + + if (ret == ESP_OK) { + wdt_hal_write_protect_disable(&ctx->hal); + /* Reconfigure the 1st and 2nd stage timeout */ + wdt_hal_config_stage(&ctx->hal, WDT_STAGE0, config->timeout_ms * (1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_INT); + wdt_hal_config_stage(&ctx->hal, WDT_STAGE1, config->timeout_ms * (2 * 1000 / TWDT_TICKS_PER_US), WDT_STAGE_ACTION_RESET_SYSTEM); + wdt_hal_write_protect_enable(&ctx->hal); + } + + return ret; +} + + +void esp_task_wdt_impl_timer_free(twdt_ctx_t obj) +{ + twdt_ctx_hard_t* ctx = (twdt_ctx_hard_t*) obj; + + if (ctx != NULL) { + /* Stop hardware timer and the interrupt associated */ + wdt_hal_deinit(&ctx->hal); + ESP_ERROR_CHECK(esp_intr_disable(ctx->intr_handle)); + + /* Disable the Timer Group module */ + periph_module_enable(TWDT_PERIPH_MODULE); + + /* Deregister interrupt */ + ESP_ERROR_CHECK(esp_intr_free(ctx->intr_handle)); + } +} + + +esp_err_t esp_task_wdt_impl_timer_feed(twdt_ctx_t obj) +{ + esp_err_t ret = ESP_OK; + twdt_ctx_hard_t* ctx = (twdt_ctx_hard_t*) obj; + + if (ctx == NULL) { + ret = ESP_ERR_INVALID_STATE; + } + + if (ret == ESP_OK) { + wdt_hal_write_protect_disable(&ctx->hal); + wdt_hal_feed(&ctx->hal); + wdt_hal_write_protect_enable(&ctx->hal); + } + + return ret; +} + + +void esp_task_wdt_impl_timeout_triggered(twdt_ctx_t obj) +{ + twdt_ctx_hard_t* ctx = (twdt_ctx_hard_t*) obj; + + if (ctx != NULL) { + /* Reset hardware timer so that 2nd stage timeout is not reached (will trigger system reset) */ + wdt_hal_write_protect_disable(&ctx->hal); + wdt_hal_handle_intr(&ctx->hal); // Feeds WDT and clears acknowledges interrupt + wdt_hal_write_protect_enable(&ctx->hal); + } +} + + +esp_err_t esp_task_wdt_impl_timer_stop(twdt_ctx_t obj) +{ + esp_err_t ret = ESP_OK; + twdt_ctx_hard_t* ctx = (twdt_ctx_hard_t*) obj; + + if (ctx == NULL) { + ret = ESP_ERR_INVALID_STATE; + } + + if (ret == ESP_OK) { + wdt_hal_write_protect_disable(&ctx->hal); + wdt_hal_disable(&ctx->hal); + wdt_hal_write_protect_enable(&ctx->hal); + } + + return ret; +} + + +esp_err_t esp_task_wdt_impl_timer_restart(twdt_ctx_t obj) +{ + esp_err_t ret = ESP_OK; + twdt_ctx_hard_t* ctx = (twdt_ctx_hard_t*) obj; + + if (ctx == NULL) { + ret = ESP_ERR_INVALID_STATE; + } + + if (ret == ESP_OK) { + wdt_hal_write_protect_disable(&ctx->hal); + wdt_hal_enable(&ctx->hal); + wdt_hal_feed(&ctx->hal); + wdt_hal_write_protect_enable(&ctx->hal); + } + + return ret; +} diff --git a/components/esp_system/test/test_task_wdt.c b/components/esp_system/test/test_task_wdt.c index 6d3bb6bc10..6684066707 100644 --- a/components/esp_system/test/test_task_wdt.c +++ b/components/esp_system/test/test_task_wdt.c @@ -40,6 +40,60 @@ TEST_CASE("Task WDT task timeout", "[task_wdt]") TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_deinit()); } +TEST_CASE("Task WDT inactive when no task to watch", "[task_wdt]") +{ + /* Make sure a timeout is NOT trigger when we have no task to watch */ + timeout_flag = false; + esp_task_wdt_config_t twdt_config = { + .timeout_ms = TASK_WDT_TIMEOUT_MS, + .idle_core_mask = 0, + .trigger_panic = false, + }; + TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_init(&twdt_config)); + esp_rom_delay_us(2 * TASK_WDT_TIMEOUT_MS * 1000); + TEST_ASSERT_EQUAL(false, timeout_flag); + /* Add a task to watch, it should start the watchdog */ + TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_add(NULL)); + esp_rom_delay_us(TASK_WDT_TIMEOUT_MS * 1000); + TEST_ASSERT_EQUAL(true, timeout_flag); + /* Remove the task we just addded and make sure the WDT is stopped*/ + timeout_flag = false; + TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_delete(NULL)); + esp_rom_delay_us(2 * TASK_WDT_TIMEOUT_MS * 1000); + TEST_ASSERT_EQUAL(false, timeout_flag); + /* Success, terminate the test */ + TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_deinit()); +} + +TEST_CASE("Task WDT can be reconfigured", "[task_wdt]") +{ + /* Make sure a timeout is NOT trigger when we have no task to watch */ + timeout_flag = false; + esp_task_wdt_config_t twdt_config = { + .timeout_ms = TASK_WDT_TIMEOUT_MS / 2, + .idle_core_mask = 0, + .trigger_panic = false, + }; + TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_init(&twdt_config)); + TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_add(NULL)); + /* Timer started, check that a timeout is raised after a while */ + esp_rom_delay_us((TASK_WDT_TIMEOUT_MS / 2 + 1) * 1000); + TEST_ASSERT_EQUAL(true, timeout_flag); + /* Reconfigure the timer with a bigger timeout. The timer is restarted + * after reconfiguring it. */ + twdt_config.timeout_ms = TASK_WDT_TIMEOUT_MS; + timeout_flag = false; + TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_reconfigure(&twdt_config)); + esp_rom_delay_us((TASK_WDT_TIMEOUT_MS / 2 + 1) * 1000); + TEST_ASSERT_EQUAL(false, timeout_flag); + /* Should be triggered now, we've spent TASK_WDT_TIMEOUT_MS waiting */ + esp_rom_delay_us((TASK_WDT_TIMEOUT_MS / 2 + 1) * 1000); + TEST_ASSERT_EQUAL(true, timeout_flag); + /* Success, terminate the test */ + TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_delete(NULL)); + TEST_ASSERT_EQUAL(ESP_OK, esp_task_wdt_deinit()); +} + TEST_CASE("Task WDT task feed", "[task_wdt]") { timeout_flag = false; diff --git a/components/esp_timer/test/test_esp_timer.c b/components/esp_timer/test/test_esp_timer.c index bb24c44ac4..e26b0c40c2 100644 --- a/components/esp_timer/test/test_esp_timer.c +++ b/components/esp_timer/test/test_esp_timer.c @@ -866,6 +866,49 @@ TEST_CASE("Test a latency between a call of callback and real event", "[esp_time TEST_ESP_OK(esp_timer_delete(periodic_timer)); } +static void test_periodic_timer_feed(void* timer1_fed) +{ + *((int*) timer1_fed) = 1; +} + +/** + * Feed function is not part of the esp_timer header file: it's a public in the sense that it is not static, + * but it is only meant to be used in IDF components. + */ +esp_err_t esp_timer_feed(esp_timer_handle_t timer); + +TEST_CASE("periodic esp_timer can be fed", "[esp_timer]") +{ + const int delay_ms = 100; + int timer_fed = 0; + esp_timer_handle_t timer1; + esp_timer_create_args_t create_args = { + .callback = &test_periodic_timer_feed, + .arg = &timer_fed, + .name = "timer1", + }; + TEST_ESP_OK(esp_timer_create(&create_args, &timer1)); + TEST_ESP_OK(esp_timer_start_periodic(timer1, delay_ms * 1000)); + /* Sleep for delay_ms/2 and feed the timer */ + vTaskDelay((delay_ms / 2) * portTICK_PERIOD_MS); + /* Check that the alarm was not triggered */ + TEST_ASSERT_EQUAL(0, timer_fed); + /* Reaching this point, the timer will be triggered in delay_ms/2. + * Let's feed the timer now. */ + TEST_ESP_OK(esp_timer_feed(timer1)); + /* Sleep for a bit more than delay_ms/2 */ + vTaskDelay(((delay_ms / 2) + 1) * portTICK_PERIOD_MS); + /* If the alarm was triggered, feed didn't work */ + TEST_ASSERT_EQUAL(0, timer_fed); + /* Else, wait for another delay_ms/2, which should trigger the alarm */ + vTaskDelay(((delay_ms / 2) + 1) * portTICK_PERIOD_MS); + TEST_ASSERT_EQUAL(1, timer_fed); + + TEST_ESP_OK( esp_timer_stop(timer1) ); + TEST_ESP_OK( esp_timer_delete(timer1) ); +} + + #ifdef CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD static int64_t old_time[2];