The branch main has been updated by jhibbits: URL: https://cgit.FreeBSD.org/src/commit/?id=26d6617f3e5406d9f92d87674f1667dd856828a3
commit 26d6617f3e5406d9f92d87674f1667dd856828a3 Author: Justin Hibbits <jhibb...@freebsd.org> AuthorDate: 2025-02-26 20:25:36 +0000 Commit: Justin Hibbits <jhibb...@freebsd.org> CommitDate: 2025-08-14 19:02:46 +0000 watchdog: Convert to using sbintime_t format Summary: Some watchdogs are now based on a countdown timer instead of a bit check. To deal with these, convert the watchdog framework to use sbintime_t instead of power-of-2-nanoseconds. This allows more precision, and more variety of watchdog timeouts. Keep the old method as a compatibility layer, so that drivers can be migrated slowly, as needed. Reviewed by: jhb Sponsored by: Juniper Networks, Inc. Differential Revision: https://reviews.freebsd.org/D49183 --- sys/dev/watchdog/watchdog.c | 202 ++++++++++++++++++++++------------------- sys/sys/watchdog.h | 17 ++-- usr.sbin/watchdogd/watchdogd.c | 99 +++++--------------- 3 files changed, 143 insertions(+), 175 deletions(-) diff --git a/sys/dev/watchdog/watchdog.c b/sys/dev/watchdog/watchdog.c index c0babef1b29b..e1b2e08c3f10 100644 --- a/sys/dev/watchdog/watchdog.c +++ b/sys/dev/watchdog/watchdog.c @@ -50,11 +50,20 @@ #include <sys/syscallsubr.h> /* kern_clock_gettime() */ -static int wd_set_pretimeout(int newtimeout, int disableiftoolong); +#ifdef COMPAT_FREEBSD14 +#define WDIOCPATPAT_14 _IOW('W', 42, u_int) /* pat the watchdog */ +#define WDIOC_SETTIMEOUT_14 _IOW('W', 43, int) /* set/reset the timer */ +#define WDIOC_GETTIMEOUT_14 _IOR('W', 44, int) /* get total timeout */ +#define WDIOC_GETTIMELEFT_14 _IOR('W', 45, int) /* get time left */ +#define WDIOC_GETPRETIMEOUT_14 _IOR('W', 46, int) /* get the pre-timeout */ +#define WDIOC_SETPRETIMEOUT_14 _IOW('W', 47, int) /* set the pre-timeout */ +#endif + +static int wd_set_pretimeout(sbintime_t newtimeout, int disableiftoolong); static void wd_timeout_cb(void *arg); static struct callout wd_pretimeo_handle; -static int wd_pretimeout; +static sbintime_t wd_pretimeout; static int wd_pretimeout_act = WD_SOFT_LOG; static struct callout wd_softtimeo_handle; @@ -63,6 +72,8 @@ static int wd_softtimer; /* true = use softtimer instead of hardware static int wd_softtimeout_act = WD_SOFT_LOG; /* action for the software timeout */ static struct cdev *wd_dev; +static volatile sbintime_t wd_last_sbt; /* last timeout value (sbt) */ +static sbintime_t wd_last_sbt_sysctl; /* last timeout value (sbt) */ static volatile u_int wd_last_u; /* last timeout value set by kern_do_pat */ static u_int wd_last_u_sysctl; /* last timeout value set by kern_do_pat */ static u_int wd_last_u_sysctl_secs; /* wd_last_u in seconds */ @@ -73,6 +84,8 @@ SYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u, CTLFLAG_RD, &wd_last_u_sysctl, 0, "Watchdog last update time"); SYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u_secs, CTLFLAG_RD, &wd_last_u_sysctl_secs, 0, "Watchdog last update time"); +SYSCTL_SBINTIME_MSEC(_hw_watchdog, OID_AUTO, wd_last_msecs, CTLFLAG_RD, + &wd_last_sbt_sysctl, "Watchdog last update time (milliseconds)"); static int wd_lastpat_valid = 0; static time_t wd_lastpat = 0; /* when the watchdog was last patted */ @@ -80,41 +93,26 @@ static time_t wd_lastpat = 0; /* when the watchdog was last patted */ /* Hook for external software watchdog to register for use if needed */ void (*wdog_software_attach)(void); -static void -pow2ns_to_ts(int pow2ns, struct timespec *ts) +/* Legacy interface to watchdog. */ +int +wdog_kern_pat(u_int utim) { - uint64_t ns; + sbintime_t sbt; - ns = 1ULL << pow2ns; - ts->tv_sec = ns / 1000000000ULL; - ts->tv_nsec = ns % 1000000000ULL; -} + if ((utim & WD_LASTVAL) != 0 && (utim & WD_INTERVAL) > 0) + return (EINVAL); -static int -pow2ns_to_ticks(int pow2ns) -{ - struct timeval tv; - struct timespec ts; + if ((utim & WD_LASTVAL) != 0) { + return (wdog_control(WD_CTRL_RESET)); + } - pow2ns_to_ts(pow2ns, &ts); - TIMESPEC_TO_TIMEVAL(&tv, &ts); - return (tvtohz(&tv)); -} + utim &= WD_INTERVAL; + if (utim == WD_TO_NEVER) + sbt = 0; + else + sbt = nstosbt(1 << utim); -static int -seconds_to_pow2ns(int seconds) -{ - uint64_t power; - uint64_t ns; - uint64_t shifted; - - ns = ((uint64_t)seconds) * 1000000000ULL; - power = flsll(ns); - shifted = 1ULL << power; - if (shifted <= ns) { - power++; - } - return (power); + return (wdog_kern_pat_sbt(sbt)); } int @@ -126,76 +124,63 @@ wdog_control(int ctrl) } if ((ctrl & WD_CTRL_RESET) != 0) { - wdog_kern_pat(WD_ACTIVE | WD_LASTVAL); + wdog_kern_pat_sbt(wd_last_sbt); } else if ((ctrl & WD_CTRL_ENABLE) != 0) { - wdog_kern_pat(WD_ACTIVE | WD_LASTVAL); + wdog_kern_pat_sbt(wd_last_sbt); } return (0); } int -wdog_kern_pat(u_int utim) +wdog_kern_pat_sbt(sbintime_t sbt) { - int error; - static int first = 1; - - if ((utim & WD_LASTVAL) != 0 && (utim & WD_INTERVAL) > 0) - return (EINVAL); - - if ((utim & WD_LASTVAL) != 0) { - /* - * if WD_LASTVAL is set, fill in the bits for timeout - * from the saved value in wd_last_u. - */ - MPASS((wd_last_u & ~WD_INTERVAL) == 0); - utim &= ~WD_LASTVAL; - utim |= wd_last_u; - } else { - /* - * Otherwise save the new interval. - * This can be zero (to disable the watchdog) - */ - wd_last_u = (utim & WD_INTERVAL); + sbintime_t error_sbt = 0; + int pow2ns = 0; + int error = 0; + static bool first = true; + + /* legacy uses power-of-2-nanoseconds time. */ + if (sbt != 0) { + pow2ns = flsl(sbttons(sbt)); + } + if (wd_last_sbt != sbt) { + wd_last_u = pow2ns; wd_last_u_sysctl = wd_last_u; - wd_last_u_sysctl_secs = pow2ns_to_ticks(wd_last_u) / hz; + wd_last_u_sysctl_secs = sbt / SBT_1S; + + wd_last_sbt = sbt; } - if ((utim & WD_INTERVAL) == WD_TO_NEVER) { - utim = 0; - /* Assume all is well; watchdog signals failure. */ - error = 0; - } else { - /* Assume no watchdog available; watchdog flags success */ + if (sbt != 0) error = EOPNOTSUPP; - } + if (wd_softtimer) { - if (utim == 0) { + if (sbt == 0) { callout_stop(&wd_softtimeo_handle); } else { - (void) callout_reset(&wd_softtimeo_handle, - pow2ns_to_ticks(utim), wd_timeout_cb, "soft"); + (void) callout_reset_sbt(&wd_softtimeo_handle, + sbt, 0, wd_timeout_cb, "soft", 0); } error = 0; } else { - EVENTHANDLER_INVOKE(watchdog_list, utim, &error); + EVENTHANDLER_INVOKE(watchdog_sbt_list, sbt, &error_sbt, &error); + EVENTHANDLER_INVOKE(watchdog_list, pow2ns, &error); } /* - * If we no hardware watchdog responded, we have not tried to + * If no hardware watchdog responded, we have not tried to * attach an external software watchdog, and one is available, * attach it now and retry. */ - if (error == EOPNOTSUPP && first && *wdog_software_attach != NULL) { + if (error == EOPNOTSUPP && first && wdog_software_attach != NULL) { (*wdog_software_attach)(); - EVENTHANDLER_INVOKE(watchdog_list, utim, &error); + EVENTHANDLER_INVOKE(watchdog_sbt_list, sbt, &error_sbt, &error); + EVENTHANDLER_INVOKE(watchdog_list, pow2ns, &error); } - first = 0; + first = false; + /* TODO: Print a (rate limited?) warning if error_sbt is too far away */ wd_set_pretimeout(wd_pretimeout, true); - /* - * If we were able to arm/strobe the watchdog, then - * update the last time it was strobed for WDIOC_GETTIMELEFT - */ if (!error) { struct timespec ts; @@ -206,6 +191,7 @@ wdog_kern_pat(u_int utim) wd_lastpat_valid = 1; } } + return (error); } @@ -282,16 +268,14 @@ wd_timeout_cb(void *arg) * current actual watchdog timeout. */ static int -wd_set_pretimeout(int newtimeout, int disableiftoolong) +wd_set_pretimeout(sbintime_t newtimeout, int disableiftoolong) { - u_int utime; - struct timespec utime_ts; - int timeout_ticks; + sbintime_t utime; + sbintime_t timeout_left; - utime = wdog_kern_last_timeout(); - pow2ns_to_ts(utime, &utime_ts); + utime = wdog_kern_last_timeout_sbt(); /* do not permit a pre-timeout >= than the timeout. */ - if (newtimeout >= utime_ts.tv_sec) { + if (newtimeout >= utime) { /* * If 'disableiftoolong' then just fall through * so as to disable the pre-watchdog @@ -309,7 +293,7 @@ wd_set_pretimeout(int newtimeout, int disableiftoolong) return 0; } - timeout_ticks = pow2ns_to_ticks(utime) - (hz*newtimeout); + timeout_left = utime - newtimeout; #if 0 printf("wd_set_pretimeout: " "newtimeout: %d, " @@ -323,8 +307,8 @@ wd_set_pretimeout(int newtimeout, int disableiftoolong) #endif /* We determined the value is sane, so reset the callout */ - (void) callout_reset(&wd_pretimeo_handle, - timeout_ticks, wd_timeout_cb, "pre"); + (void) callout_reset_sbt(&wd_pretimeo_handle, + timeout_left, 0, wd_timeout_cb, "pre", 0); wd_pretimeout = newtimeout; return 0; } @@ -333,6 +317,7 @@ static int wd_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data, int flags __unused, struct thread *td) { + sbintime_t sb; u_int u; time_t timeleft; int error; @@ -368,32 +353,55 @@ wd_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data, error = EINVAL; } break; - case WDIOC_GETPRETIMEOUT: - *(int *)data = (int)wd_pretimeout; +#ifdef COMPAT_FREEBSD14 + case WDIOC_GETPRETIMEOUT_14: + *(int *)data = (int)(wd_pretimeout / SBT_1S); break; - case WDIOC_SETPRETIMEOUT: - error = wd_set_pretimeout(*(int *)data, false); + case WDIOC_SETPRETIMEOUT_14: + error = wd_set_pretimeout(*(int *)data * SBT_1S, false); break; - case WDIOC_GETTIMELEFT: + case WDIOC_GETTIMELEFT_14: error = wd_get_time_left(td, &timeleft); if (error) break; *(int *)data = (int)timeleft; break; - case WDIOC_SETTIMEOUT: + case WDIOC_SETTIMEOUT_14: u = *(u_int *)data; - error = wdog_kern_pat(seconds_to_pow2ns(u)); + error = wdog_kern_pat_sbt(mstosbt(u * 1000ULL)); break; - case WDIOC_GETTIMEOUT: + case WDIOC_GETTIMEOUT_14: u = wdog_kern_last_timeout(); *(u_int *)data = u; break; - case WDIOCPATPAT: + case WDIOCPATPAT_14: error = wd_ioctl_patpat(data); break; +#endif + + /* New API */ case WDIOC_CONTROL: wdog_control(*(int *)data); break; + case WDIOC_SETTIMEOUT: + sb = *(sbintime_t *)data; + error = wdog_kern_pat_sbt(sb); + break; + case WDIOC_GETTIMEOUT: + *(sbintime_t *)data = wdog_kern_last_timeout_sbt(); + break; + case WDIOC_GETTIMELEFT: + error = wd_get_time_left(td, &timeleft); + if (error) + break; + *(sbintime_t *)data = (sbintime_t)timeleft * SBT_1S; + break; + case WDIOC_GETPRETIMEOUT: + *(sbintime_t *)data = wd_pretimeout; + break; + case WDIOC_SETPRETIMEOUT: + error = wd_set_pretimeout(*(sbintime_t *)data, false); + break; default: error = ENOIOCTL; break; @@ -412,6 +420,12 @@ wdog_kern_last_timeout(void) return (wd_last_u); } +sbintime_t +wdog_kern_last_timeout_sbt(void) +{ + return (wd_last_sbt); +} + static struct cdevsw wd_cdevsw = { .d_version = D_VERSION, .d_ioctl = wd_ioctl, diff --git a/sys/sys/watchdog.h b/sys/sys/watchdog.h index 3c9d31eb577b..8401d343a6b7 100644 --- a/sys/sys/watchdog.h +++ b/sys/sys/watchdog.h @@ -32,15 +32,16 @@ #define _SYS_WATCHDOG_H #include <sys/ioccom.h> +#include <sys/_types.h> #define _PATH_WATCHDOG "fido" -#define WDIOCPATPAT _IOW('W', 42, u_int) /* pat the watchdog */ -#define WDIOC_SETTIMEOUT _IOW('W', 43, int) /* set/reset the timer */ -#define WDIOC_GETTIMEOUT _IOR('W', 44, int) /* get total timeout */ -#define WDIOC_GETTIMELEFT _IOR('W', 45, int) /* get time left */ -#define WDIOC_GETPRETIMEOUT _IOR('W', 46, int) /* get the pre-timeout */ -#define WDIOC_SETPRETIMEOUT _IOW('W', 47, int) /* set the pre-timeout */ +#define WDIOC_PATPAT _IOW('W', 52, sbintime_t) /* pat the watchdog */ +#define WDIOC_SETTIMEOUT _IOW('W', 53, sbintime_t) /* set/reset the timer */ +#define WDIOC_GETTIMEOUT _IOR('W', 54, sbintime_t) /* get total timeout */ +#define WDIOC_GETTIMELEFT _IOR('W', 55, sbintime_t) /* get time left */ +#define WDIOC_GETPRETIMEOUT _IOR('W', 56, sbintime_t) /* get the pre-timeout */ +#define WDIOC_SETPRETIMEOUT _IOW('W', 57, sbintime_t) /* set the pre-timeout */ /* set the action when a pre-timeout occurs see: WD_SOFT_* */ #define WDIOC_SETPRETIMEOUTACT _IOW('W', 48, int) @@ -112,11 +113,15 @@ #include <sys/_eventhandler.h> typedef void (*watchdog_fn)(void *, u_int, int *); +typedef void (*watchdog_sbt_fn)(void *, sbintime_t, sbintime_t *, int *); EVENTHANDLER_DECLARE(watchdog_list, watchdog_fn); +EVENTHANDLER_DECLARE(watchdog_sbt_list, watchdog_sbt_fn); u_int wdog_kern_last_timeout(void); int wdog_kern_pat(u_int utim); +sbintime_t wdog_kern_last_timeout_sbt(void); +int wdog_kern_pat_sbt(sbintime_t utim); int wdog_control(int ctrl); /* diff --git a/usr.sbin/watchdogd/watchdogd.c b/usr.sbin/watchdogd/watchdogd.c index 228438955006..27123f2143d0 100644 --- a/usr.sbin/watchdogd/watchdogd.c +++ b/usr.sbin/watchdogd/watchdogd.c @@ -63,25 +63,25 @@ static long fetchtimeout(int opt, const char *longopt, const char *myoptarg, int zero_ok); static void parseargs(int, char *[]); -static int seconds_to_pow2ns(int); static void sighandler(int); static void watchdog_loop(void); static int watchdog_init(void); static int watchdog_onoff(int onoff); -static int watchdog_patpat(u_int timeout); +static int watchdog_patpat(sbintime_t); static void usage(void); -static int tstotv(struct timeval *tv, struct timespec *ts); static int tvtohz(struct timeval *tv); static int debugging = 0; static int end_program = 0; static const char *pidfile = _PATH_VARRUN "watchdogd.pid"; -static u_int timeout = WD_TO_128SEC; +static sbintime_t timeout = 128 * SBT_1S; static u_int exit_timeout = WD_TO_NEVER; static u_int pretimeout = 0; static u_int timeout_sec; static u_int nap = 10; +#ifdef notyet static int passive = 0; +#endif static int is_daemon = 0; static int is_dry_run = 0; /* do not arm the watchdog, only report on timing of the watch @@ -174,38 +174,23 @@ main(int argc, char *argv[]) pidfile_remove(pfh); return (EX_OK); } else { - if (passive) - timeout |= WD_PASSIVE; - else - timeout |= WD_ACTIVE; if (watchdog_patpat(timeout) < 0) err(EX_OSERR, "patting the dog"); return (EX_OK); } } -static void -pow2ns_to_ts(int pow2ns, struct timespec *ts) -{ - uint64_t ns; - - ns = 1ULL << pow2ns; - ts->tv_sec = ns / 1000000000ULL; - ts->tv_nsec = ns % 1000000000ULL; -} - /* * Convert a timeout in seconds to N where 2^N nanoseconds is close to * "seconds". * * The kernel expects the timeouts for watchdogs in "2^N nanosecond format". */ -static u_int -parse_timeout_to_pow2ns(char opt, const char *longopt, const char *myoptarg) +static sbintime_t +parse_timeout_to_sbt(char opt, const char *longopt, const char *myoptarg) { - double a; - u_int rv; - struct timespec ts; + long a; + sbintime_t rv; struct timeval tv; int ticks; char shortopt[] = "- "; @@ -216,19 +201,17 @@ parse_timeout_to_pow2ns(char opt, const char *longopt, const char *myoptarg) a = fetchtimeout(opt, longopt, myoptarg, 1); if (a == 0) - rv = WD_TO_NEVER; + rv = 0; else - rv = seconds_to_pow2ns(a); - pow2ns_to_ts(rv, &ts); - tstotv(&tv, &ts); + rv = a * SBT_1S; + tv = sbttotv(rv); ticks = tvtohz(&tv); if (debugging) { printf("Timeout for %s%s " - "is 2^%d nanoseconds " - "(in: %s sec -> out: %jd sec %ld ns -> %d ticks)\n", + "is " + "(in: %s sec -> out: %jd sec %ld us -> %d ticks)\n", longopt ? "-" : "", longopt ? longopt : shortopt, - rv, - myoptarg, (intmax_t)ts.tv_sec, ts.tv_nsec, ticks); + myoptarg, (intmax_t)tv.tv_sec, tv.tv_usec, ticks); } if (ticks <= 0) { errx(1, "Timeout for %s%s is too small, please choose a higher timeout.", longopt ? "-" : "", longopt ? longopt : shortopt); @@ -364,7 +347,7 @@ watchdog_loop(void) } if (failed == 0) - watchdog_patpat(timeout|WD_ACTIVE); + watchdog_patpat(timeout); waited = watchdog_check_dogfunction_time(&ts_start, &ts_end); if (nap - waited > 0) @@ -387,13 +370,13 @@ try_end: * to keep the watchdog from firing. */ static int -watchdog_patpat(u_int t) +watchdog_patpat(sbintime_t sbt) { if (is_dry_run) return 0; - return ioctl(fd, WDIOCPATPAT, &t); + return ioctl(fd, WDIOC_SETTIMEOUT, &sbt); } static int @@ -429,7 +412,7 @@ watchdog_onoff(int onoff) warn("setting WDIOC_SETSOFT %d", softtimeout_set); return (error); } - error = watchdog_patpat((timeout|WD_ACTIVE)); + error = watchdog_patpat(timeout); if (error) { warn("watchdog_patpat failed"); goto failsafe; @@ -461,7 +444,7 @@ watchdog_onoff(int onoff) } } /* pat one more time for good measure */ - return watchdog_patpat((timeout|WD_ACTIVE)); + return watchdog_patpat(timeout); } else { return watchdog_control(WD_CTRL_DISABLE); } @@ -576,15 +559,6 @@ timeout_act_str2int(const char *lopt, const char *acts) return rv; } -int -tstotv(struct timeval *tv, struct timespec *ts) -{ - - tv->tv_sec = ts->tv_sec; - tv->tv_usec = ts->tv_nsec / 1000; - return 0; -} - /* * Convert a timeval to a number of ticks. * Mostly copied from the kernel. @@ -656,30 +630,6 @@ tvtohz(struct timeval *tv) return ((int)ticks); } -static int -seconds_to_pow2ns(int seconds) -{ - uint64_t power; - uint64_t ns; - uint64_t shifted; - - if (seconds <= 0) - errx(1, "seconds %d < 0", seconds); - ns = ((uint64_t)seconds) * 1000000000ULL; - power = flsll(ns); - shifted = 1ULL << power; - if (shifted <= ns) { - power++; - } - if (debugging) { - printf("shifted %lld\n", (long long)shifted); - printf("seconds_to_pow2ns: seconds: %d, ns %lld, power %d\n", - seconds, (long long)ns, (int)power); - } - return (power); -} - - /* * Handle the few command line arguments supported. */ @@ -692,8 +642,7 @@ parseargs(int argc, char *argv[]) const char *lopt; /* Get the default value of timeout_sec from the default timeout. */ - pow2ns_to_ts(timeout, &ts); - timeout_sec = ts.tv_sec; + timeout_sec = sbintime_getsec(timeout); /* * if we end with a 'd' aka 'watchdogd' then we are the daemon program, @@ -736,10 +685,10 @@ parseargs(int argc, char *argv[]) break; case 't': timeout_sec = atoi(optarg); - timeout = parse_timeout_to_pow2ns(c, NULL, optarg); + timeout = parse_timeout_to_sbt(c, NULL, optarg); if (debugging) - printf("Timeout is 2^%d nanoseconds\n", - timeout); + printf("Timeout is %d\n", + (int)(timeout / SBT_1S)); break; case 'T': carp_thresh_seconds = @@ -749,7 +698,7 @@ parseargs(int argc, char *argv[]) do_timedog = 1; break; case 'x': - exit_timeout = parse_timeout_to_pow2ns(c, NULL, optarg); + exit_timeout = parse_timeout_to_sbt(c, NULL, optarg); if (exit_timeout != 0) exit_timeout |= WD_ACTIVE; break;