summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoralfred <alfred@FreeBSD.org>2013-07-27 20:47:01 +0000
committeralfred <alfred@FreeBSD.org>2013-07-27 20:47:01 +0000
commitfdd68bea0e12d92e6afb245364fba13c3f8b499b (patch)
tree56df7df9ac8f7b431ecb8a19f2ae000fa5ebdd85
parent24e94a1aa8ae0ae20697703ef7605e13c814621f (diff)
downloadFreeBSD-src-fdd68bea0e12d92e6afb245364fba13c3f8b499b.zip
FreeBSD-src-fdd68bea0e12d92e6afb245364fba13c3f8b499b.tar.gz
Fix watchdog pretimeout.
The original API calls for pow2ns, however the new APIs from Linux call for seconds. We need to be able to convert to/from 2^Nns to seconds in both userland and kernel to fix this and properly compare units.
-rw-r--r--sys/conf/files1
-rw-r--r--sys/dev/watchdog/watchdog.c74
-rw-r--r--sys/libkern/flsll.c47
-rw-r--r--sys/sys/libkern.h4
-rw-r--r--usr.sbin/watchdogd/watchdogd.c196
5 files changed, 301 insertions, 21 deletions
diff --git a/sys/conf/files b/sys/conf/files
index c74aa71..4a1a432 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -2976,6 +2976,7 @@ libkern/arc4random.c standard
libkern/bcd.c standard
libkern/bsearch.c standard
libkern/crc32.c standard
+libkern/flsll.c standard
libkern/fnmatch.c standard
libkern/iconv.c optional libiconv
libkern/iconv_converter_if.m optional libiconv
diff --git a/sys/dev/watchdog/watchdog.c b/sys/dev/watchdog/watchdog.c
index 71d8ecd..54a5db9 100644
--- a/sys/dev/watchdog/watchdog.c
+++ b/sys/dev/watchdog/watchdog.c
@@ -39,6 +39,7 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
+#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/watchdog.h>
#include <sys/bus.h>
@@ -60,10 +61,56 @@ static int wd_softtimeout_act = WD_SOFT_LOG; /* action for the software timeout
static struct cdev *wd_dev;
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 */
+
+SYSCTL_NODE(_hw, OID_AUTO, watchdog, CTLFLAG_RD, 0, "Main watchdog device");
+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");
static int wd_lastpat_valid = 0;
static time_t wd_lastpat = 0; /* when the watchdog was last patted */
+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;
+}
+
+static int
+pow2ns_to_ticks(int pow2ns)
+{
+ struct timeval tv;
+ struct timespec ts;
+
+ pow2ns_to_ts(pow2ns, &ts);
+ TIMESPEC_TO_TIMEVAL(&tv, &ts);
+ return (tvtohz(&tv));
+}
+
+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);
+}
+
+
int
wdog_kern_pat(u_int utim)
{
@@ -86,6 +133,8 @@ wdog_kern_pat(u_int utim)
* This can be zero (to disable the watchdog)
*/
wd_last_u = (utim & WD_INTERVAL);
+ wd_last_u_sysctl = wd_last_u;
+ wd_last_u_sysctl_secs = pow2ns_to_ticks(wd_last_u) / hz;
}
if ((utim & WD_INTERVAL) == WD_TO_NEVER) {
utim = 0;
@@ -101,7 +150,7 @@ wdog_kern_pat(u_int utim)
callout_stop(&wd_softtimeo_handle);
} else {
(void) callout_reset(&wd_softtimeo_handle,
- hz*utim, wd_timeout_cb, "soft");
+ pow2ns_to_ticks(utim), wd_timeout_cb, "soft");
}
error = 0;
} else {
@@ -201,10 +250,13 @@ static int
wd_set_pretimeout(int newtimeout, int disableiftoolong)
{
u_int utime;
+ struct timespec utime_ts;
+ int timeout_ticks;
utime = wdog_kern_last_timeout();
+ pow2ns_to_ts(utime, &utime_ts);
/* do not permit a pre-timeout >= than the timeout. */
- if (newtimeout >= utime) {
+ if (newtimeout >= utime_ts.tv_sec) {
/*
* If 'disableiftoolong' then just fall through
* so as to disable the pre-watchdog
@@ -222,8 +274,22 @@ wd_set_pretimeout(int newtimeout, int disableiftoolong)
return 0;
}
+ timeout_ticks = pow2ns_to_ticks(utime) - (hz*newtimeout);
+#if 0
+ printf("wd_set_pretimeout: "
+ "newtimeout: %d, "
+ "utime: %d -> utime_ticks: %d, "
+ "hz*newtimeout: %d, "
+ "timeout_ticks: %d -> sec: %d\n",
+ newtimeout,
+ utime, pow2ns_to_ticks(utime),
+ hz*newtimeout,
+ timeout_ticks, timeout_ticks / hz);
+#endif
+
/* We determined the value is sane, so reset the callout */
- (void) callout_reset(&wd_pretimeo_handle, hz*(utime - newtimeout),
+ (void) callout_reset(&wd_pretimeo_handle,
+ timeout_ticks,
wd_timeout_cb, "pre-timeout");
wd_pretimeout = newtimeout;
return 0;
@@ -282,7 +348,7 @@ wd_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data,
break;
case WDIOC_SETTIMEOUT:
u = *(u_int *)data;
- error = wdog_kern_pat(u);
+ error = wdog_kern_pat(seconds_to_pow2ns(u));
break;
case WDIOC_GETTIMEOUT:
u = wdog_kern_last_timeout();
diff --git a/sys/libkern/flsll.c b/sys/libkern/flsll.c
new file mode 100644
index 0000000..0629480
--- /dev/null
+++ b/sys/libkern/flsll.c
@@ -0,0 +1,47 @@
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. 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.
+ * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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 <sys/cdefs.h>
+#include <sys/libkern.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Find Last Set bit
+ */
+int
+flsll(long long mask)
+{
+ int bit;
+
+ if (mask == 0)
+ return (0);
+ for (bit = 1; mask != 1; bit++)
+ mask = (unsigned long long)mask >> 1;
+ return (bit);
+}
diff --git a/sys/sys/libkern.h b/sys/sys/libkern.h
index c04e88d..3665b3a 100644
--- a/sys/sys/libkern.h
+++ b/sys/sys/libkern.h
@@ -94,6 +94,10 @@ int fls(int);
#ifndef HAVE_INLINE_FLSL
int flsl(long);
#endif
+#ifndef HAVE_INLINE_FLSLL
+int flsll(long long);
+#endif
+
int fnmatch(const char *, const char *, int);
int locc(int, char *, u_int);
void *memchr(const void *s, int c, size_t n);
diff --git a/usr.sbin/watchdogd/watchdogd.c b/usr.sbin/watchdogd/watchdogd.c
index 5416751..7b6db94 100644
--- a/usr.sbin/watchdogd/watchdogd.c
+++ b/usr.sbin/watchdogd/watchdogd.c
@@ -39,6 +39,7 @@ __FBSDID("$FreeBSD$");
#include <sys/rtprio.h>
#include <sys/stat.h>
#include <sys/time.h>
+#include <sys/sysctl.h>
#include <sys/watchdog.h>
#include <err.h>
@@ -58,19 +59,24 @@ __FBSDID("$FreeBSD$");
#include <getopt.h>
+static long fetchtimeout(int opt, const char *longopt, const char *myoptarg);
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 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 u_int pretimeout = 0;
+static u_int timeout_sec;
static u_int passive = 0;
static int is_daemon = 0;
static int is_dry_run = 0; /* do not arm the watchdog, only
@@ -183,6 +189,59 @@ main(int argc, char *argv[])
}
}
+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)
+{
+ double a;
+ u_int rv;
+ struct timespec ts;
+ struct timeval tv;
+ int ticks;
+ char shortopt[] = "- ";
+
+ if (!longopt)
+ shortopt[1] = opt;
+
+ a = fetchtimeout(opt, longopt, myoptarg);
+
+ if (a == 0)
+ rv = WD_TO_NEVER;
+ else
+ rv = seconds_to_pow2ns(a);
+ pow2ns_to_ts(rv, &ts);
+ tstotv(&tv, &ts);
+ ticks = tvtohz(&tv);
+ if (debugging) {
+ printf("Timeout for %s%s "
+ "is 2^%d nanoseconds "
+ "(in: %s sec -> out: %ld sec %ld ns -> %d ticks)\n",
+ longopt ? "-" : "", longopt ? longopt : shortopt,
+ rv,
+ myoptarg, ts.tv_sec, ts.tv_nsec, ticks);
+ }
+ if (ticks <= 0) {
+ errx(1, "Timeout for %s%s is too small, please choose a higher timeout.", longopt ? "-" : "", longopt ? longopt : shortopt);
+ }
+
+ return (rv);
+}
+
/*
* Catch signals and begin shutdown process.
*/
@@ -513,6 +572,110 @@ 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.
+ */
+int
+tvtohz(struct timeval *tv)
+{
+ register unsigned long ticks;
+ register long sec, usec;
+ int hz;
+ size_t hzsize;
+ int error;
+ int tick;
+
+ hzsize = sizeof(hz);
+
+ error = sysctlbyname("kern.hz", &hz, &hzsize, NULL, 0);
+ if (error)
+ err(1, "sysctlbyname kern.hz");
+
+ tick = 1000000 / hz;
+
+ /*
+ * If the number of usecs in the whole seconds part of the time
+ * difference fits in a long, then the total number of usecs will
+ * fit in an unsigned long. Compute the total and convert it to
+ * ticks, rounding up and adding 1 to allow for the current tick
+ * to expire. Rounding also depends on unsigned long arithmetic
+ * to avoid overflow.
+ *
+ * Otherwise, if the number of ticks in the whole seconds part of
+ * the time difference fits in a long, then convert the parts to
+ * ticks separately and add, using similar rounding methods and
+ * overflow avoidance. This method would work in the previous
+ * case but it is slightly slower and assumes that hz is integral.
+ *
+ * Otherwise, round the time difference down to the maximum
+ * representable value.
+ *
+ * If ints have 32 bits, then the maximum value for any timeout in
+ * 10ms ticks is 248 days.
+ */
+ sec = tv->tv_sec;
+ usec = tv->tv_usec;
+ if (usec < 0) {
+ sec--;
+ usec += 1000000;
+ }
+ if (sec < 0) {
+#ifdef DIAGNOSTIC
+ if (usec > 0) {
+ sec++;
+ usec -= 1000000;
+ }
+ printf("tvotohz: negative time difference %ld sec %ld usec\n",
+ sec, usec);
+#endif
+ ticks = 1;
+ } else if (sec <= LONG_MAX / 1000000)
+ ticks = (sec * 1000000 + (unsigned long)usec + (tick - 1))
+ / tick + 1;
+ else if (sec <= LONG_MAX / hz)
+ ticks = sec * hz
+ + ((unsigned long)usec + (tick - 1)) / tick + 1;
+ else
+ ticks = LONG_MAX;
+ if (ticks > INT_MAX)
+ ticks = INT_MAX;
+ 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.
*/
@@ -521,9 +684,7 @@ parseargs(int argc, char *argv[])
{
int longindex;
int c;
- char *p;
const char *lopt;
- double a;
/*
* if we end with a 'd' aka 'watchdogd' then we are the daemon program,
@@ -565,21 +726,11 @@ parseargs(int argc, char *argv[])
do_syslog = 0;
break;
case 't':
- p = NULL;
- errno = 0;
- a = strtod(optarg, &p);
- if ((p != NULL && *p != '\0') || errno != 0)
- errx(EX_USAGE, "-t argument is not a number");
- if (a < 0)
- errx(EX_USAGE, "-t argument must be positive");
-
- if (a == 0)
- timeout = WD_TO_NEVER;
- else
- timeout = flsll(a * 1e9);
- if (debugging)
- printf("Timeout is 2^%d nanoseconds\n",
- timeout);
+ timeout_sec = atoi(optarg);
+ timeout = parse_timeout_to_pow2ns(c, NULL, optarg);
+ if (debugging)
+ printf("Timeout is 2^%d nanoseconds\n",
+ timeout);
break;
case 'T':
carp_thresh_seconds = fetchtimeout(c, "NULL", optarg);
@@ -618,4 +769,15 @@ parseargs(int argc, char *argv[])
errx(EX_USAGE, "extra arguments.");
if (is_daemon && timeout < WD_TO_1SEC)
errx(EX_USAGE, "-t argument is less than one second.");
+ if (pretimeout_set) {
+ struct timespec ts;
+
+ pow2ns_to_ts(timeout, &ts);
+ if (pretimeout >= ts.tv_sec) {
+ errx(EX_USAGE,
+ "pretimeout (%d) >= timeout (%d -> %ld)\n"
+ "see manual section TIMEOUT RESOLUTION",
+ pretimeout, timeout_sec, (long)ts.tv_sec);
+ }
+ }
}
OpenPOWER on IntegriCloud