From 11cbaa78fc80b1d75b10a6341e4f8a1f0d90f25d Mon Sep 17 00:00:00 2001 From: Will James <39811085+wjames111@users.noreply.github.com> Date: Wed, 7 Oct 2026 12:40:29 -0400 Subject: [PATCH 1/2] watchdog: disarm on any exit once the watchdog is armed watchdog_start() runs before sdk_start(), but several exits after that point never reach the watchdog_stop() at the end of main(): the HAL_ERROR returns for a failed sdk_start() and for empty RTSP credentials, and the exit() in handle_error() after a fatal signal. Each leaves the watchdog armed with nothing feeding it, so on a NOWAYOUT kernel a startup failure turns into a board reset. Register watchdog_stop() with atexit() as soon as the device is opened. watchdog_stop() already returns early when nothing is open, so the normal teardown call and the atexit call do not clash. --- src/watchdog.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/watchdog.c b/src/watchdog.c index 1db88774..f9c63a56 100644 --- a/src/watchdog.c +++ b/src/watchdog.c @@ -21,6 +21,11 @@ int watchdog_start(int timeout) { ioctl(fd, WDIOC_SETTIMEOUT, &timeout); + /* Disarm on any exit from here on, including the error returns out of + main() and the exit() in handle_error(), so the board is not reset + by a watchdog nobody feeds. watchdog_stop() is safe to call twice. */ + atexit(watchdog_stop); + HAL_INFO("watchdog", "Watchdog started!\n"); return EXIT_SUCCESS; } From ebdbcb989f2370feda5e3ff6880ae5c806d07fb4 Mon Sep 17 00:00:00 2001 From: Will James <39811085+wjames111@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:02:42 -0400 Subject: [PATCH 2/2] main: leave fatal signals through _exit() handle_error() runs as a signal handler for SIGSEGV, SIGBUS and the other fatal signals, but called exit(), which runs the atexit handlers and flushes stdio. That is not async-signal-safe, and with the watchdog now disarmed from atexit it means a crash could disarm the watchdog and then block on the stderr lock, leaving a hung process that nothing resets. Use _exit() so a crash skips the atexit handlers: the watchdog stays armed and the board recovers, while startup failures and normal exits still disarm it. --- src/main.c | 5 ++++- src/watchdog.c | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main.c b/src/main.c index 6c027acf..fb90b590 100644 --- a/src/main.c +++ b/src/main.c @@ -23,7 +23,10 @@ void handle_error(int signo) { sprintf(msg, "Error occured (%d)! Quitting...\n", signo); write(STDERR_FILENO, msg, strlen(msg)); keepRunning = 0; - exit(EXIT_FAILURE); + /* _exit(), not exit(): from a signal handler only async-signal-safe + calls are allowed, and skipping the atexit handlers leaves the + watchdog armed so a crash still ends in a reset */ + _exit(EXIT_FAILURE); } void handle_exit(int signo) { diff --git a/src/watchdog.c b/src/watchdog.c index f9c63a56..2143edb9 100644 --- a/src/watchdog.c +++ b/src/watchdog.c @@ -21,9 +21,10 @@ int watchdog_start(int timeout) { ioctl(fd, WDIOC_SETTIMEOUT, &timeout); - /* Disarm on any exit from here on, including the error returns out of - main() and the exit() in handle_error(), so the board is not reset - by a watchdog nobody feeds. watchdog_stop() is safe to call twice. */ + /* Disarm on any normal exit from here on, including the error returns + out of main(), so a startup failure does not end in a reset. A fatal + signal leaves through _exit() in handle_error() and keeps it armed. + watchdog_stop() is safe to call twice. */ atexit(watchdog_stop); HAL_INFO("watchdog", "Watchdog started!\n");