Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 27 Jul 2013 20:47:02 +0000 (UTC)
From:      Alfred Perlstein <alfred@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r253719 - in head: sys/conf sys/dev/watchdog sys/libkern sys/sys usr.sbin/watchdogd
Message-ID:  <201307272047.r6RKl2vI023259@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: alfred
Date: Sat Jul 27 20:47:01 2013
New Revision: 253719
URL: http://svnweb.freebsd.org/changeset/base/253719

Log:
  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.

Added:
  head/sys/libkern/flsll.c   (contents, props changed)
Modified:
  head/sys/conf/files
  head/sys/dev/watchdog/watchdog.c
  head/sys/sys/libkern.h
  head/usr.sbin/watchdogd/watchdogd.c

Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files	Sat Jul 27 20:15:18 2013	(r253718)
+++ head/sys/conf/files	Sat Jul 27 20:47:01 2013	(r253719)
@@ -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

Modified: head/sys/dev/watchdog/watchdog.c
==============================================================================
--- head/sys/dev/watchdog/watchdog.c	Sat Jul 27 20:15:18 2013	(r253718)
+++ head/sys/dev/watchdog/watchdog.c	Sat Jul 27 20:47:01 2013	(r253719)
@@ -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_
 
 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 di
 		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_lo
 		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();

Added: head/sys/libkern/flsll.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/libkern/flsll.c	Sat Jul 27 20:47:01 2013	(r253719)
@@ -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);
+}

Modified: head/sys/sys/libkern.h
==============================================================================
--- head/sys/sys/libkern.h	Sat Jul 27 20:15:18 2013	(r253718)
+++ head/sys/sys/libkern.h	Sat Jul 27 20:47:01 2013	(r253719)
@@ -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);

Modified: head/usr.sbin/watchdogd/watchdogd.c
==============================================================================
--- head/usr.sbin/watchdogd/watchdogd.c	Sat Jul 27 20:15:18 2013	(r253718)
+++ head/usr.sbin/watchdogd/watchdogd.c	Sat Jul 27 20:47:01 2013	(r253719)
@@ -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, co
 	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);
+		}
+	}
 }



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201307272047.r6RKl2vI023259>