Systemd/src/shared/watchdog.c
Alin Popa c5f8a179a2 watchdog: reduce watchdog pings in timeout interval
The watchdog ping is performed for every iteration of manager event
loop. This results in a lot of ioctls on watchdog device driver
especially during boot or if services are aggressively using sd_notify.
Depending on the watchdog device driver this may have performance
impact on embedded systems.
The patch skips sending the watchdog to device driver if the ping is
requested before half of the watchdog timeout.
2020-04-16 16:32:05 +02:00

198 lines
5.8 KiB
C

/* 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 usec_t watchdog_last_ping = 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");
watchdog_last_ping = now(clock_boottime_or_monotonic());
}
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) {
int r;
r = free_and_strdup(&watchdog_device, path);
if (r < 0)
return r;
if (r > 0) /* watchdog_device changed */
watchdog_fd = safe_close(watchdog_fd);
return r;
}
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;
}
usec_t watchdog_runtime_wait(void) {
usec_t rtwait;
usec_t ntime;
if (!timestamp_is_set(watchdog_timeout))
return USEC_INFINITY;
/* Sleep half the watchdog timeout since the last succesful ping at most */
if (timestamp_is_set(watchdog_last_ping)) {
ntime = now(clock_boottime_or_monotonic());
assert(ntime >= watchdog_last_ping);
rtwait = usec_sub_unsigned(watchdog_last_ping + (watchdog_timeout / 2), ntime);
} else
rtwait = watchdog_timeout / 2;
return rtwait;
}
int watchdog_ping(void) {
usec_t ntime;
int r;
ntime = now(clock_boottime_or_monotonic());
/* Never ping earlier than watchdog_timeout/4 and try to ping
* by watchdog_timeout/2 plus scheduling latencies the latest */
if (timestamp_is_set(watchdog_last_ping)) {
assert(ntime >= watchdog_last_ping);
if ((ntime - watchdog_last_ping) < (watchdog_timeout / 4))
return 0;
}
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");
watchdog_last_ping = ntime;
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);
}