diff options
Diffstat (limited to '')
-rw-r--r-- | src/shared/watchdog.c | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c new file mode 100644 index 0000000..c423af6 --- /dev/null +++ b/src/shared/watchdog.c @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <errno.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <syslog.h> +#include <unistd.h> +#include <linux/watchdog.h> + +#include "fd-util.h" +#include "log.h" +#include "string-util.h" +#include "time-util.h" +#include "watchdog.h" + +static int watchdog_fd = -1; +static char *watchdog_device = NULL; +static usec_t watchdog_timeout = USEC_INFINITY; + +static int update_timeout(void) { + int r; + + if (watchdog_fd < 0) + return 0; + + if (watchdog_timeout == USEC_INFINITY) + return 0; + else if (watchdog_timeout == 0) { + int flags; + + flags = WDIOS_DISABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) + return log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); + } else { + int sec, flags; + char buf[FORMAT_TIMESPAN_MAX]; + + sec = (int) DIV_ROUND_UP(watchdog_timeout, USEC_PER_SEC); + r = ioctl(watchdog_fd, WDIOC_SETTIMEOUT, &sec); + if (r < 0) + return log_warning_errno(errno, "Failed to set timeout to %is: %m", sec); + + watchdog_timeout = (usec_t) sec * USEC_PER_SEC; + log_info("Set hardware watchdog to %s.", format_timespan(buf, sizeof(buf), watchdog_timeout, 0)); + + flags = WDIOS_ENABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) { + /* ENOTTY means the watchdog is always enabled so we're fine */ + log_full(errno == ENOTTY ? LOG_DEBUG : LOG_WARNING, + "Failed to enable hardware watchdog: %m"); + if (errno != ENOTTY) + return -errno; + } + + r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); + if (r < 0) + return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); + } + + return 0; +} + +static int open_watchdog(void) { + struct watchdog_info ident; + + if (watchdog_fd >= 0) + return 0; + + watchdog_fd = open(watchdog_device ?: "/dev/watchdog", + O_WRONLY|O_CLOEXEC); + if (watchdog_fd < 0) + return -errno; + + if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) >= 0) + log_info("Hardware watchdog '%s', version %x", + ident.identity, + ident.firmware_version); + + return update_timeout(); +} + +int watchdog_set_device(char *path) { + return free_and_strdup(&watchdog_device, path); +} + +int watchdog_set_timeout(usec_t *usec) { + int r; + + watchdog_timeout = *usec; + + /* If we didn't open the watchdog yet and didn't get any + * explicit timeout value set, don't do anything */ + if (watchdog_fd < 0 && watchdog_timeout == USEC_INFINITY) + return 0; + + if (watchdog_fd < 0) + r = open_watchdog(); + else + r = update_timeout(); + + *usec = watchdog_timeout; + + return r; +} + +int watchdog_ping(void) { + int r; + + if (watchdog_fd < 0) { + r = open_watchdog(); + if (r < 0) + return r; + } + + r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); + if (r < 0) + return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); + + return 0; +} + +void watchdog_close(bool disarm) { + int r; + + if (watchdog_fd < 0) + return; + + if (disarm) { + int flags; + + /* Explicitly disarm it */ + flags = WDIOS_DISABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) + log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); + + /* To be sure, use magic close logic, too */ + for (;;) { + static const char v = 'V'; + + if (write(watchdog_fd, &v, 1) > 0) + break; + + if (errno != EINTR) { + log_error_errno(errno, "Failed to disarm watchdog timer: %m"); + break; + } + } + } + + watchdog_fd = safe_close(watchdog_fd); +} |