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.
This commit is contained in:
parent
24e94a1aa8
commit
fdd68bea0e
@ -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
|
||||
|
@ -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();
|
||||
|
47
sys/libkern/flsll.c
Normal file
47
sys/libkern/flsll.c
Normal file
@ -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);
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user