modules: add task watchdog (#26535)

* modules: add task watchdog

* modules: adapt task watchdog to new module descriptor

* add review feedback
This commit is contained in:
Alexander Lerach
2026-02-27 13:09:21 +01:00
committed by GitHub
parent d317113dc8
commit 9ff373c325
9 changed files with 721 additions and 2 deletions
+1
View File
@@ -55,6 +55,7 @@ CONFIG_MODULES_HARDFAULT_STREAM=y
CONFIG_MODULES_LAND_DETECTOR=y
CONFIG_MODULES_LANDING_TARGET_ESTIMATOR=y
CONFIG_MODULES_LOAD_MON=y
CONFIG_MODULES_TASK_WATCHDOG=y
CONFIG_MODULES_LOGGER=y
CONFIG_LOGGER_STACK_SIZE=4100
CONFIG_MODULES_MAG_BIAS_ESTIMATOR=y
@@ -34,5 +34,8 @@ nshterm /dev/ttyS3 &
# Start the time_persistor to cyclically store the RTC in FRAM
time_persistor start
# Start the task_watchdog as we do not have the logger watchdog
task_watchdog start
# Start the ESC telemetry
dshot telemetry -d /dev/ttyS5 -x
@@ -126,6 +126,57 @@ void SdCardChecks::checkAndReport(const Context &context, Report &reporter)
}
}
#if CONFIG_MODULES_TASK_WATCHDOG
// Check for task watchdog dump files
if (!_watchdog_checked_once && _param_com_arm_hardfault_check.get()) {
_watchdog_checked_once = true;
DIR *wdp = opendir(PX4_STORAGEDIR "/task_watchdog");
if (wdp != nullptr) {
struct dirent *wresult;
while ((wresult = readdir(wdp)) && !_watchdog_file_present) {
// Check for pattern wdg_*.log or load_*.log
const size_t len = strlen(wresult->d_name);
if (len > 5 && strcmp(wresult->d_name + len - 4, ".log") == 0) {
if (strncmp("wdg_", wresult->d_name, 4) == 0
|| strncmp("load_", wresult->d_name, 5) == 0) {
_watchdog_file_present = true;
}
}
}
closedir(wdp);
}
}
if (_watchdog_file_present && _param_com_arm_hardfault_check.get()) {
/* EVENT
* @description
* The SD card contains task watchdog dump files from a previous task starvation event.
*
* <profile name="dev">
* Remove the files in the 'task_watchdog' directory on the SD card after analysis.
*
* This check can be configured via <param>COM_ARM_HFLT_CHK</param> parameter.
* </profile>
*/
reporter.healthFailure(NavModes::All, health_component_t::system,
events::ID("check_task_watchdog_present"),
events::Log::Error, "Task watchdog dumps present on SD card");
if (reporter.mavlink_log_pub()) {
mavlink_log_critical(reporter.mavlink_log_pub(), "Preflight Fail: Task watchdog dumps present on SD");
}
}
#endif /* CONFIG_MODULES_TASK_WATCHDOG */
#endif /* __PX4_NUTTX */
#endif /* PX4_STORAGEDIR */
}
@@ -50,6 +50,8 @@ private:
#ifdef __PX4_NUTTX
bool _hardfault_checked_once {false};
bool _hardfault_file_present {false};
bool _watchdog_checked_once {false};
bool _watchdog_file_present {false};
#endif
#endif
+2 -2
View File
@@ -812,9 +812,9 @@ PARAM_DEFINE_FLOAT(COM_LKDOWN_TKO, 3.0f);
PARAM_DEFINE_INT32(COM_ARM_SDCARD, 1);
/**
* Enable FMU SD card hardfault detection check
* Enable FMU SD card hardfault / watchdog detection check
*
* This check detects if there are hardfault files present on the
* This check detects if there are hardfault / watchdog files present on the
* SD card. If so, and the parameter is enabled, arming is prevented.
*
* @group Commander
+41
View File
@@ -0,0 +1,41 @@
############################################################################
#
# Copyright (c) 2026 PX4 Development Team. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# 3. Neither the name PX4 nor the names of its contributors may be
# used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
############################################################################
px4_add_module(
MODULE modules__task_watchdog
MAIN task_watchdog
SRCS
TaskWatchdog.cpp
DEPENDS
version
)
+6
View File
@@ -0,0 +1,6 @@
menuconfig MODULES_TASK_WATCHDOG
bool "task_watchdog"
default n
depends on PLATFORM_NUTTX
---help---
Detect tasks running too long and dump their register/stack state and cpuload to files
+486
View File
@@ -0,0 +1,486 @@
/****************************************************************************
*
* Copyright (c) 2026 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#include "TaskWatchdog.hpp"
#include <px4_platform_common/log.h>
#include <px4_platform_common/posix.h>
#include <px4_platform_common/px4_config.h>
#include <px4_platform_common/tasks.h>
#include <version/version.h>
#include <fcntl.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include <sys/stat.h>
#ifndef CONFIG_BUILD_FLAT
# error Task Watchdog requires flat build, please enable it to use this module
#endif
using namespace time_literals;
namespace task_watchdog
{
ModuleBase::Descriptor TaskWatchdog::desc{task_spawn, custom_command, print_usage};
TaskWatchdog::TaskWatchdog()
{
}
TaskWatchdog::~TaskWatchdog()
{
hrt_cancel(&_hrt_call);
px4_sem_destroy(&_sem);
}
int TaskWatchdog::run_trampoline(int argc, char *argv[])
{
return ModuleBase::run_trampoline_impl(desc, [](int ac, char *av[]) -> ModuleBase * {
TaskWatchdog *instance = new TaskWatchdog();
if (instance == nullptr)
{
PX4_ERR("alloc failed");
}
return instance;
}, argc, argv);
}
int TaskWatchdog::task_spawn(int argc, char *argv[])
{
desc.task_id = px4_task_spawn_cmd("task_watchdog",
SCHED_DEFAULT,
SCHED_PRIORITY_LOG_WRITER,
PX4_STACK_ADJUSTED(3250),
(px4_main_t)&run_trampoline,
(char *const *)argv);
if (desc.task_id < 0) {
desc.task_id = -1;
PX4_ERR("task start failed: %d", errno);
return -errno;
}
return 0;
}
void TaskWatchdog::start()
{
px4_sem_init(&_sem, 0, 0);
px4_sem_setprotocol(&_sem, SEM_PRIO_NONE);
_shared.sem = &_sem;
// Find our own task in the system_load table
const pid_t my_pid = getpid();
sched_lock();
for (int i = 0; i < CONFIG_FS_PROCFS_MAX_TASKS; i++) {
if (system_load.tasks[i].valid && system_load.tasks[i].tcb->pid == my_pid) {
_shared.monitored_task_index = i;
break;
}
}
sched_unlock();
if (_shared.monitored_task_index < 0) {
PX4_ERR("could not find own task in system_load");
}
_shared.ready_to_run_timestamp = hrt_absolute_time();
// Start HRT ISR
hrt_call_every(&_hrt_call, 400_ms, 400_ms, isr_callback, &_shared);
}
void TaskWatchdog::isr_callback(void *arg)
{
watchdog_shared_s *shared = static_cast<watchdog_shared_s *>(arg);
if (!system_load.initialized || shared->monitored_task_index < 0) {
return;
}
const system_load_taskinfo_s &watchdog_task = system_load.tasks[shared->monitored_task_index];
const hrt_abstime now = hrt_absolute_time();
if (!watchdog_task.valid) {
shared->monitored_task_index = -1;
return;
}
// Already triggered — wait for the task side to process
if (shared->triggered.load()) {
// Wake the task, otherwise it can get stuck when starved before entering the sem wait
px4_sem_post(shared->sem);
return;
}
// Already in post-trigger priority restore phase
if (shared->trigger_time != 0 && now > shared->trigger_time + 1500_ms) {
// Restore our original priority
sched_param param{};
param.sched_priority = shared->original_priority;
sched_setparam(watchdog_task.tcb->pid, &param);
// Only trigger once as the system may be in an unstable state
shared->monitored_task_index = -1;
return;
}
// If the task ran recently, update the timestamp
if (watchdog_task.curr_start_time > shared->ready_to_run_timestamp) {
shared->ready_to_run_timestamp = watchdog_task.curr_start_time;
}
// Reset timestamp on state transitions into/out of READYTORUN
uint8_t current_state = watchdog_task.tcb->task_state;
if (current_state != TSTATE_TASK_READYTORUN ||
(shared->last_state != TSTATE_TASK_READYTORUN && current_state == TSTATE_TASK_READYTORUN)) {
shared->ready_to_run_timestamp = now;
}
shared->last_state = current_state;
// Check if we've been starved for too long
if (!shared->manual_trigger && now <= shared->ready_to_run_timestamp + TRIGGER_THRESHOLD) {
// Wake the task so it runs briefly, keeping curr_start_time fresh
px4_sem_post(shared->sem);
return;
}
// --- TRIGGERED ---
shared->manual_trigger = false;
// Boost our own priority so we actually get to run and write the dump
sched_param param{};
sched_getparam(watchdog_task.tcb->pid, &param);
shared->original_priority = param.sched_priority;
param.sched_priority = SCHED_PRIORITY_LOG_WATCHDOG;
sched_setparam(watchdog_task.tcb->pid, &param);
shared->trigger_time = now;
shared->triggered.store(true);
px4_sem_post(shared->sem);
}
void TaskWatchdog::run()
{
start();
while (!should_exit()) {
if (_cpuload_pending) {
_cpuload_pending = false;
cpuload_monitor_stop();
write_cpuload_file();
}
if (_shared.triggered.load()) {
_shared.triggered.store(false);
PX4_ERR("task starvation detected!");
capture_and_write_dump();
cpuload_monitor_start();
init_print_load(&_load);
_cpuload_pending = true;
}
if (_cpuload_pending) {
px4_usleep(CPULOAD_ACCUMULATE_TIME);
} else {
// Drain any extra posts that accumulated while the task was asleep
int val;
px4_sem_getvalue(&_sem, &val);
while (val > 0) {
px4_sem_trywait(&_sem);
px4_sem_getvalue(&_sem, &val);
}
while (px4_sem_wait(&_sem) != 0) {}
}
}
hrt_cancel(&_hrt_call);
}
void TaskWatchdog::capture_and_write_dump()
{
mkdir(LOG_PATH_BASE, S_IRWXU | S_IRWXG | S_IRWXO);
char path[64];
int ret = format_file_path(LOG_WDG, path, sizeof(path));
if (ret != PX4_OK) {
PX4_ERR("Could not create watchdog dump file: %d", ret);
return;
}
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
PX4_ERR("failed to create %s: %d", path, errno);
return;
}
dprintf(fd, "Task Watchdog Dump\n");
dprintf(fd, "FW git-hash: %s\n", px4_firmware_version_string());
dprintf(fd, "Build datetime: %s %s\n", __DATE__, __TIME__);
dprintf(fd, "Build url: %s \n", px4_build_uri());
const hrt_abstime now = hrt_absolute_time();
/* Loop 1: capture under sched_lock */
sched_lock();
for (int i = 0; i < CONFIG_FS_PROCFS_MAX_TASKS; i++) {
const system_load_taskinfo_s &task = system_load.tasks[i];
if (!task.valid || !task.tcb || task.tcb->pid == 0) {
continue;
}
task_dump_s &d = _dumps[i];
d.pid = task.tcb->pid;
d.state = task.tcb->task_state;
#if CONFIG_TASK_NAME_SIZE > 0
strncpy(d.name, task.tcb->name, CONFIG_TASK_NAME_SIZE);
d.name[CONFIG_TASK_NAME_SIZE] = '\0';
#endif
if (task.tcb->xcp.regs && task.tcb->task_state != TSTATE_TASK_RUNNING) {
memcpy(d.regs, task.tcb->xcp.regs, sizeof(d.regs));
d.has_regs = true;
const uintptr_t sp = d.regs[REG_R13];
const uintptr_t stack_top = (uintptr_t)task.tcb->stack_base_ptr
+ task.tcb->adj_stack_size;
if (sp >= (uintptr_t)task.tcb->stack_base_ptr && sp < stack_top) {
unsigned words_avail = (stack_top - sp) / sizeof(uint32_t);
d.stack_words = (words_avail < STACK_DUMP_WORDS) ? words_avail : STACK_DUMP_WORDS;
memcpy(d.stack, (const void *)sp, d.stack_words * sizeof(uint32_t));
}
}
d.valid = true;
}
sched_unlock();
/* Loop 2: write to file — no locks held */
for (int i = 0; i < CONFIG_FS_PROCFS_MAX_TASKS; i++) {
const task_dump_s &d = _dumps[i];
if (!d.valid) {
continue;
}
dprintf(fd, "T:%" PRIu64 " %s(%d) state:%d",
now,
#if CONFIG_TASK_NAME_SIZE > 0
d.name,
#else
"?",
#endif
(int)d.pid,
(int)d.state);
if (d.has_regs) {
dprintf(fd, "\n r0:%08" PRIx32 " r1:%08" PRIx32 " r2:%08" PRIx32 " r3:%08" PRIx32
" r4:%08" PRIx32 " r5:%08" PRIx32 " r6:%08" PRIx32 " r7:%08" PRIx32 "\n",
d.regs[REG_R0], d.regs[REG_R1],
d.regs[REG_R2], d.regs[REG_R3],
d.regs[REG_R4], d.regs[REG_R5],
d.regs[REG_R6], d.regs[REG_R7]);
dprintf(fd, " r8:%08" PRIx32 " r9:%08" PRIx32 " r10:%08" PRIx32 " r11:%08" PRIx32
" r12:%08" PRIx32 " sp:%08" PRIx32 " lr:%08" PRIx32 " pc:%08" PRIx32 "\n",
d.regs[REG_R8], d.regs[REG_R9],
d.regs[REG_R10], d.regs[REG_R11],
d.regs[REG_R12], d.regs[REG_R13],
d.regs[REG_R14], d.regs[REG_R15]);
#ifdef CONFIG_ARMV7M_USEBASEPRI
dprintf(fd, " xpsr:%08" PRIx32 " basepri:%08" PRIx32 "\n",
d.regs[REG_XPSR], d.regs[REG_BASEPRI]);
#else
dprintf(fd, " xpsr:%08" PRIx32 " primask:%08" PRIx32 "\n",
d.regs[REG_XPSR], d.regs[REG_PRIMASK]);
#endif
if (d.stack_words > 0) {
dprintf(fd, "STK(%u@%08" PRIx32 "):", d.stack_words, d.regs[REG_R13]);
for (unsigned j = 0; j < d.stack_words; j++) {
if (j > 0 && (j % 8) == 0) {
dprintf(fd, "\n");
}
dprintf(fd, " %08" PRIx32, d.stack[j]);
}
dprintf(fd, "\n");
}
} else {
dprintf(fd, " [no saved regs]\n");
}
}
fsync(fd);
close(fd);
PX4_INFO("dump written to %s", path);
}
void TaskWatchdog::write_cpuload_file()
{
mkdir(LOG_PATH_BASE, S_IRWXU | S_IRWXG | S_IRWXO);
char path[64];
int ret = format_file_path(LOG_LOAD, path, sizeof(path));
if (ret != PX4_OK) {
PX4_ERR("Could not create load dump file: %d", ret);
return;
}
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
PX4_ERR("failed to create %s: %d", path, errno);
return;
}
print_load(fd, &_load);
fsync(fd);
close(fd);
PX4_INFO("cpuload written to %s", path);
}
int TaskWatchdog::format_file_path(log_type_t type, char *buf, size_t bufsz)
{
if (!buf || bufsz == 0) {
return -EINVAL;
}
struct timespec ts;
struct tm tm;
char timebuf[32];
if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
return -errno;
}
if (!localtime_r(&ts.tv_sec, &tm)) {
return -EINVAL;
}
if (strftime(timebuf, sizeof(timebuf), TIME_FMT, &tm) == 0) {
return -EINVAL;
}
const char *fmt = (type == LOG_WDG) ? LOG_WDG_NAME_FMT : LOG_LOAD_NAME_FMT;
int n = snprintf(buf, bufsz, "%s/", LOG_PATH_BASE);
if (n < 0 || (size_t)n >= bufsz) {
return -ENAMETOOLONG;
}
int m = snprintf(buf + n, bufsz - n, fmt, timebuf);
if (m < 0 || (size_t)m >= bufsz - n) {
return -ENAMETOOLONG;
}
return PX4_OK;
}
int TaskWatchdog::custom_command(int argc, char *argv[])
{
if (!is_running(desc)) {
print_usage("task_watchdog not running");
return 1;
}
if (!strcmp(argv[0], "trigger")) {
get_instance<TaskWatchdog>(desc)->_shared.manual_trigger = true;
PX4_INFO("manual watchdog trigger requested");
return 0;
}
return print_usage("unknown command");
}
int TaskWatchdog::print_usage(const char *reason)
{
if (reason) {
PX4_WARN("%s\n", reason);
}
PRINT_MODULE_DESCRIPTION(
R"DESCR_STR(
### Description
Detects when a higher-priority task starves the system by running too long.
When starvation is detected, dumps the offending task's registers and stack,
and saves a cpuload snapshot.
)DESCR_STR");
PRINT_MODULE_USAGE_NAME("task_watchdog", "system");
PRINT_MODULE_USAGE_COMMAND("start");
PRINT_MODULE_USAGE_COMMAND_DESCR("trigger", "Manually trigger the watchdog");
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
return 0;
}
} // namespace task_watchdog
extern "C" __EXPORT int task_watchdog_main(int argc, char *argv[])
{
return ModuleBase::main(task_watchdog::TaskWatchdog::desc, argc, argv);
}
+129
View File
@@ -0,0 +1,129 @@
/****************************************************************************
*
* Copyright (c) 2026 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#pragma once
#include <drivers/drv_hrt.h>
#include <px4_platform_common/px4_config.h>
#include <px4_platform_common/module.h>
#include <px4_platform_common/module_params.h>
#include <px4_platform_common/printload.h>
#include <px4_platform/cpuload.h>
#include <px4_platform_common/atomic.h>
#include <px4_platform_common/sem.h>
#include <nuttx/sched.h>
#define LOG_PATH_BASE CONFIG_BOARD_ROOT_PATH "/task_watchdog"
#define LOG_WDG_NAME_FMT "wdg_%s.log"
#define LOG_LOAD_NAME_FMT "load_%s.log"
#define TIME_FMT "%Y_%m_%d_%H_%M_%S"
using namespace time_literals;
namespace task_watchdog
{
static constexpr unsigned STACK_DUMP_WORDS = 16;
struct task_dump_s {
pid_t pid{0};
uint8_t state{0};
#if CONFIG_TASK_NAME_SIZE > 0
char name[CONFIG_TASK_NAME_SIZE + 1] {};
#endif
uint32_t regs[XCPTCONTEXT_REGS] {};
uint32_t stack[STACK_DUMP_WORDS] {};
unsigned stack_words{0};
bool has_regs{false};
bool valid{false};
};
/* Shared state between ISR and task */
struct watchdog_shared_s {
int monitored_task_index{-1}; ///< system_load index of this module's task
hrt_abstime ready_to_run_timestamp{0};
uint8_t last_state{TSTATE_TASK_INVALID};
int original_priority{0};
hrt_abstime trigger_time{0};
px4::atomic_bool triggered{false};
bool manual_trigger{false}; ///< set from custom_command, checked by ISR
px4_sem_t *sem{nullptr}; ///< posted by ISR to wake the task immediately
};
typedef enum {
LOG_WDG,
LOG_LOAD
} log_type_t;
class TaskWatchdog : public ModuleBase
{
public:
static Descriptor desc;
TaskWatchdog();
~TaskWatchdog() override;
static int task_spawn(int argc, char *argv[]);
static int run_trampoline(int argc, char *argv[]);
static int custom_command(int argc, char *argv[]);
static int print_usage(const char *reason = nullptr);
void run() override;
private:
void start();
/* Capture all tasks and write register+stack dump to a file. Called from task context at boosted priority. */
void capture_and_write_dump();
/* Write cpuload snapshot to a file. Called from task context. */
void write_cpuload_file();
int format_file_path(log_type_t type, char *buf, size_t bufsz);
/* HRT ISR callback which monitors whether this task is being starved. */
static void isr_callback(void *arg);
hrt_call _hrt_call{};
watchdog_shared_s _shared{};
px4_sem_t _sem;
task_dump_s _dumps[CONFIG_FS_PROCFS_MAX_TASKS] {};
print_load_s _load{};
bool _cpuload_pending{false};
static constexpr hrt_abstime TRIGGER_THRESHOLD = 2_s; ///< time in ready to run to trigger watchdog
static constexpr hrt_abstime CPULOAD_ACCUMULATE_TIME = 300_ms; ///< how long to accumulate cpuload data
};
} // namespace task_watchdog