TWDT: refactor the TWDT to be driver agnostic

This refactoring brings a private API for the TWDT implementation, which
can now use a hardware timer (Timer Group) or a software timer (esp_timer)
pull/9803/head
Omar Chebib 2022-08-12 18:27:11 +08:00
rodzic 53c7dd4efc
commit 30d12af191
12 zmienionych plików z 721 dodań i 227 usunięć

Wyświetl plik

@ -38,6 +38,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
@ -45,7 +49,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)

Wyświetl plik

@ -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)

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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)
*

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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)
*

Wyświetl plik

@ -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);

Wyświetl plik

@ -0,0 +1,155 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#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;
}

Wyświetl plik

@ -0,0 +1,184 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#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;
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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];