watchdog.c revision 279413
1126370Sphk/*- 2126370Sphk * Copyright (c) 2004 Poul-Henning Kamp 3247405Salfred * Copyright (c) 2013 iXsystems.com, 4247405Salfred * author: Alfred Perlstein <alfred@freebsd.org> 5247405Salfred * 6126370Sphk * All rights reserved. 7126370Sphk * 8126370Sphk * Redistribution and use in source and binary forms, with or without 9126370Sphk * modification, are permitted provided that the following conditions 10126370Sphk * are met: 11126370Sphk * 1. Redistributions of source code must retain the above copyright 12126370Sphk * notice, this list of conditions and the following disclaimer 13126370Sphk * in this position and unchanged. 14126370Sphk * 2. Redistributions in binary form must reproduce the above copyright 15126370Sphk * notice, this list of conditions and the following disclaimer in the 16126370Sphk * documentation and/or other materials provided with the distribution. 17126370Sphk * 18126370Sphk * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 19126370Sphk * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20126370Sphk * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21126370Sphk * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22126370Sphk * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23126370Sphk * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24126370Sphk * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25126370Sphk * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26126370Sphk * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27126370Sphk * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28126370Sphk * 29126370Sphk */ 30126370Sphk 31279413Srstone#include "opt_ddb.h" 32279413Srstone 33126370Sphk#include <sys/cdefs.h> 34126370Sphk__FBSDID("$FreeBSD: head/sys/dev/watchdog/watchdog.c 279413 2015-02-28 22:37:48Z rstone $"); 35126370Sphk 36126370Sphk#include <sys/param.h> 37247405Salfred#include <sys/types.h> 38126370Sphk#include <sys/systm.h> 39126370Sphk#include <sys/conf.h> 40126370Sphk#include <sys/uio.h> 41126370Sphk#include <sys/kernel.h> 42279413Srstone#include <sys/kdb.h> 43126370Sphk#include <sys/malloc.h> 44126370Sphk#include <sys/module.h> 45253719Salfred#include <sys/sysctl.h> 46247405Salfred#include <sys/syslog.h> 47126370Sphk#include <sys/watchdog.h> 48126370Sphk#include <sys/bus.h> 49126370Sphk#include <machine/bus.h> 50126370Sphk 51247405Salfred#include <sys/syscallsubr.h> /* kern_clock_gettime() */ 52247405Salfred 53247405Salfredstatic int wd_set_pretimeout(int newtimeout, int disableiftoolong); 54247405Salfredstatic void wd_timeout_cb(void *arg); 55247405Salfred 56247405Salfredstatic struct callout wd_pretimeo_handle; 57247405Salfredstatic int wd_pretimeout; 58247405Salfredstatic int wd_pretimeout_act = WD_SOFT_LOG; 59247405Salfred 60247405Salfredstatic struct callout wd_softtimeo_handle; 61247405Salfredstatic int wd_softtimer; /* true = use softtimer instead of hardware 62247405Salfred watchdog */ 63247405Salfredstatic int wd_softtimeout_act = WD_SOFT_LOG; /* action for the software timeout */ 64247405Salfred 65130585Sphkstatic struct cdev *wd_dev; 66247405Salfredstatic volatile u_int wd_last_u; /* last timeout value set by kern_do_pat */ 67253719Salfredstatic u_int wd_last_u_sysctl; /* last timeout value set by kern_do_pat */ 68253719Salfredstatic u_int wd_last_u_sysctl_secs; /* wd_last_u in seconds */ 69126370Sphk 70253719SalfredSYSCTL_NODE(_hw, OID_AUTO, watchdog, CTLFLAG_RD, 0, "Main watchdog device"); 71253719SalfredSYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u, CTLFLAG_RD, 72253719Salfred &wd_last_u_sysctl, 0, "Watchdog last update time"); 73253719SalfredSYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u_secs, CTLFLAG_RD, 74253719Salfred &wd_last_u_sysctl_secs, 0, "Watchdog last update time"); 75253719Salfred 76247405Salfredstatic int wd_lastpat_valid = 0; 77247405Salfredstatic time_t wd_lastpat = 0; /* when the watchdog was last patted */ 78247405Salfred 79253719Salfredstatic void 80253719Salfredpow2ns_to_ts(int pow2ns, struct timespec *ts) 81253719Salfred{ 82253719Salfred uint64_t ns; 83253719Salfred 84253719Salfred ns = 1ULL << pow2ns; 85253719Salfred ts->tv_sec = ns / 1000000000ULL; 86253719Salfred ts->tv_nsec = ns % 1000000000ULL; 87253719Salfred} 88253719Salfred 89253719Salfredstatic int 90253719Salfredpow2ns_to_ticks(int pow2ns) 91253719Salfred{ 92253719Salfred struct timeval tv; 93253719Salfred struct timespec ts; 94253719Salfred 95253719Salfred pow2ns_to_ts(pow2ns, &ts); 96253719Salfred TIMESPEC_TO_TIMEVAL(&tv, &ts); 97253719Salfred return (tvtohz(&tv)); 98253719Salfred} 99253719Salfred 100253719Salfredstatic int 101253719Salfredseconds_to_pow2ns(int seconds) 102253719Salfred{ 103253719Salfred uint64_t power; 104253719Salfred uint64_t ns; 105253719Salfred uint64_t shifted; 106253719Salfred 107253719Salfred ns = ((uint64_t)seconds) * 1000000000ULL; 108253719Salfred power = flsll(ns); 109253719Salfred shifted = 1ULL << power; 110253719Salfred if (shifted <= ns) { 111253719Salfred power++; 112253719Salfred } 113253719Salfred return (power); 114253719Salfred} 115253719Salfred 116253719Salfred 117247405Salfredint 118247405Salfredwdog_kern_pat(u_int utim) 119221121Sattilio{ 120221121Sattilio int error; 121221121Sattilio 122221121Sattilio if ((utim & WD_LASTVAL) != 0 && (utim & WD_INTERVAL) > 0) 123221121Sattilio return (EINVAL); 124221121Sattilio 125221121Sattilio if ((utim & WD_LASTVAL) != 0) { 126247405Salfred /* 127247405Salfred * if WD_LASTVAL is set, fill in the bits for timeout 128247405Salfred * from the saved value in wd_last_u. 129247405Salfred */ 130221121Sattilio MPASS((wd_last_u & ~WD_INTERVAL) == 0); 131221121Sattilio utim &= ~WD_LASTVAL; 132221121Sattilio utim |= wd_last_u; 133247405Salfred } else { 134247405Salfred /* 135247405Salfred * Otherwise save the new interval. 136247405Salfred * This can be zero (to disable the watchdog) 137247405Salfred */ 138221121Sattilio wd_last_u = (utim & WD_INTERVAL); 139253719Salfred wd_last_u_sysctl = wd_last_u; 140253719Salfred wd_last_u_sysctl_secs = pow2ns_to_ticks(wd_last_u) / hz; 141247405Salfred } 142221121Sattilio if ((utim & WD_INTERVAL) == WD_TO_NEVER) { 143221121Sattilio utim = 0; 144221121Sattilio 145221121Sattilio /* Assume all is well; watchdog signals failure. */ 146221121Sattilio error = 0; 147221121Sattilio } else { 148221121Sattilio /* Assume no watchdog available; watchdog flags success */ 149221121Sattilio error = EOPNOTSUPP; 150221121Sattilio } 151247405Salfred if (wd_softtimer) { 152247405Salfred if (utim == 0) { 153247405Salfred callout_stop(&wd_softtimeo_handle); 154247405Salfred } else { 155247405Salfred (void) callout_reset(&wd_softtimeo_handle, 156253719Salfred pow2ns_to_ticks(utim), wd_timeout_cb, "soft"); 157247405Salfred } 158247405Salfred error = 0; 159247405Salfred } else { 160247405Salfred EVENTHANDLER_INVOKE(watchdog_list, utim, &error); 161247405Salfred } 162247405Salfred wd_set_pretimeout(wd_pretimeout, true); 163247405Salfred /* 164247405Salfred * If we were able to arm/strobe the watchdog, then 165247405Salfred * update the last time it was strobed for WDIOC_GETTIMELEFT 166247405Salfred */ 167247405Salfred if (!error) { 168247405Salfred struct timespec ts; 169247405Salfred 170247405Salfred error = kern_clock_gettime(curthread /* XXX */, 171247405Salfred CLOCK_MONOTONIC_FAST, &ts); 172247405Salfred if (!error) { 173247405Salfred wd_lastpat = ts.tv_sec; 174247405Salfred wd_lastpat_valid = 1; 175247405Salfred } 176247405Salfred } 177221121Sattilio return (error); 178221121Sattilio} 179221121Sattilio 180221121Sattiliostatic int 181247405Salfredwd_valid_act(int act) 182126370Sphk{ 183247405Salfred 184247405Salfred if ((act & ~(WD_SOFT_MASK)) != 0) 185247405Salfred return false; 186247405Salfred return true; 187247405Salfred} 188247405Salfred 189247405Salfredstatic int 190247405Salfredwd_ioctl_patpat(caddr_t data) 191247405Salfred{ 192126370Sphk u_int u; 193126370Sphk 194126370Sphk u = *(u_int *)data; 195221121Sattilio if (u & ~(WD_ACTIVE | WD_PASSIVE | WD_LASTVAL | WD_INTERVAL)) 196126370Sphk return (EINVAL); 197126370Sphk if ((u & (WD_ACTIVE | WD_PASSIVE)) == (WD_ACTIVE | WD_PASSIVE)) 198126370Sphk return (EINVAL); 199221121Sattilio if ((u & (WD_ACTIVE | WD_PASSIVE)) == 0 && ((u & WD_INTERVAL) > 0 || 200221121Sattilio (u & WD_LASTVAL) != 0)) 201167950Sn_hibma return (EINVAL); 202165260Sn_hibma if (u & WD_PASSIVE) 203165260Sn_hibma return (ENOSYS); /* XXX Not implemented yet */ 204221121Sattilio u &= ~(WD_ACTIVE | WD_PASSIVE); 205221121Sattilio 206247405Salfred return (wdog_kern_pat(u)); 207126370Sphk} 208126370Sphk 209247405Salfredstatic int 210247405Salfredwd_get_time_left(struct thread *td, time_t *remainp) 211221121Sattilio{ 212247405Salfred struct timespec ts; 213247405Salfred int error; 214221121Sattilio 215247405Salfred error = kern_clock_gettime(td, CLOCK_MONOTONIC_FAST, &ts); 216247405Salfred if (error) 217247405Salfred return (error); 218247405Salfred if (!wd_lastpat_valid) 219247405Salfred return (ENOENT); 220247405Salfred *remainp = ts.tv_sec - wd_lastpat; 221247405Salfred return (0); 222221121Sattilio} 223221121Sattilio 224247405Salfredstatic void 225247405Salfredwd_timeout_cb(void *arg) 226221121Sattilio{ 227247405Salfred const char *type = arg; 228221121Sattilio 229247405Salfred#ifdef DDB 230247405Salfred if ((wd_pretimeout_act & WD_SOFT_DDB)) { 231247405Salfred char kdb_why[80]; 232261495Sed snprintf(kdb_why, sizeof(kdb_why), "watchdog %s timeout", type); 233247405Salfred kdb_backtrace(); 234247405Salfred kdb_enter(KDB_WHY_WATCHDOG, kdb_why); 235247405Salfred } 236247405Salfred#endif 237247405Salfred if ((wd_pretimeout_act & WD_SOFT_LOG)) 238247405Salfred log(LOG_EMERG, "watchdog %s-timeout, WD_SOFT_LOG", type); 239247405Salfred if ((wd_pretimeout_act & WD_SOFT_PRINTF)) 240247405Salfred printf("watchdog %s-timeout, WD_SOFT_PRINTF\n", type); 241247405Salfred if ((wd_pretimeout_act & WD_SOFT_PANIC)) 242247405Salfred panic("watchdog %s-timeout, WD_SOFT_PANIC set", type); 243247405Salfred} 244221121Sattilio 245247405Salfred/* 246247405Salfred * Called to manage timeouts. 247247405Salfred * newtimeout needs to be in the range of 0 to actual watchdog timeout. 248247405Salfred * if 0, we disable the pre-timeout. 249247405Salfred * otherwise we set the pre-timeout provided it's not greater than the 250247405Salfred * current actual watchdog timeout. 251247405Salfred */ 252247405Salfredstatic int 253247405Salfredwd_set_pretimeout(int newtimeout, int disableiftoolong) 254247405Salfred{ 255247405Salfred u_int utime; 256253719Salfred struct timespec utime_ts; 257253719Salfred int timeout_ticks; 258247405Salfred 259247405Salfred utime = wdog_kern_last_timeout(); 260253719Salfred pow2ns_to_ts(utime, &utime_ts); 261247405Salfred /* do not permit a pre-timeout >= than the timeout. */ 262253719Salfred if (newtimeout >= utime_ts.tv_sec) { 263247405Salfred /* 264247405Salfred * If 'disableiftoolong' then just fall through 265247405Salfred * so as to disable the pre-watchdog 266247405Salfred */ 267247405Salfred if (disableiftoolong) 268247405Salfred newtimeout = 0; 269247405Salfred else 270247405Salfred return EINVAL; 271247405Salfred } 272247405Salfred 273247405Salfred /* disable the pre-timeout */ 274247405Salfred if (newtimeout == 0) { 275247405Salfred wd_pretimeout = 0; 276247405Salfred callout_stop(&wd_pretimeo_handle); 277247405Salfred return 0; 278247405Salfred } 279247405Salfred 280253719Salfred timeout_ticks = pow2ns_to_ticks(utime) - (hz*newtimeout); 281253719Salfred#if 0 282253719Salfred printf("wd_set_pretimeout: " 283253719Salfred "newtimeout: %d, " 284253719Salfred "utime: %d -> utime_ticks: %d, " 285253719Salfred "hz*newtimeout: %d, " 286253719Salfred "timeout_ticks: %d -> sec: %d\n", 287253719Salfred newtimeout, 288253719Salfred utime, pow2ns_to_ticks(utime), 289253719Salfred hz*newtimeout, 290253719Salfred timeout_ticks, timeout_ticks / hz); 291253719Salfred#endif 292253719Salfred 293247405Salfred /* We determined the value is sane, so reset the callout */ 294253719Salfred (void) callout_reset(&wd_pretimeo_handle, 295253719Salfred timeout_ticks, 296247405Salfred wd_timeout_cb, "pre-timeout"); 297247405Salfred wd_pretimeout = newtimeout; 298247405Salfred return 0; 299221121Sattilio} 300221121Sattilio 301247405Salfredstatic int 302247405Salfredwd_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data, 303247405Salfred int flags __unused, struct thread *td) 304247405Salfred{ 305247405Salfred u_int u; 306247405Salfred time_t timeleft; 307247405Salfred int error; 308247405Salfred 309247405Salfred error = 0; 310247405Salfred 311247405Salfred switch (cmd) { 312247405Salfred case WDIOC_SETSOFT: 313247405Salfred u = *(int *)data; 314247405Salfred /* do nothing? */ 315247405Salfred if (u == wd_softtimer) 316247405Salfred break; 317247405Salfred /* If there is a pending timeout disallow this ioctl */ 318247405Salfred if (wd_last_u != 0) { 319247405Salfred error = EINVAL; 320247405Salfred break; 321247405Salfred } 322247405Salfred wd_softtimer = u; 323247405Salfred break; 324247405Salfred case WDIOC_SETSOFTTIMEOUTACT: 325247405Salfred u = *(int *)data; 326247405Salfred if (wd_valid_act(u)) { 327247405Salfred wd_softtimeout_act = u; 328247405Salfred } else { 329247405Salfred error = EINVAL; 330247405Salfred } 331247405Salfred break; 332247405Salfred case WDIOC_SETPRETIMEOUTACT: 333247405Salfred u = *(int *)data; 334247405Salfred if (wd_valid_act(u)) { 335247405Salfred wd_pretimeout_act = u; 336247405Salfred } else { 337247405Salfred error = EINVAL; 338247405Salfred } 339247405Salfred break; 340247405Salfred case WDIOC_GETPRETIMEOUT: 341247405Salfred *(int *)data = (int)wd_pretimeout; 342247405Salfred break; 343247405Salfred case WDIOC_SETPRETIMEOUT: 344247405Salfred error = wd_set_pretimeout(*(int *)data, false); 345247405Salfred break; 346247405Salfred case WDIOC_GETTIMELEFT: 347247405Salfred error = wd_get_time_left(td, &timeleft); 348247405Salfred if (error) 349247405Salfred break; 350247405Salfred *(int *)data = (int)timeleft; 351247405Salfred break; 352247405Salfred case WDIOC_SETTIMEOUT: 353247405Salfred u = *(u_int *)data; 354253719Salfred error = wdog_kern_pat(seconds_to_pow2ns(u)); 355247405Salfred break; 356247405Salfred case WDIOC_GETTIMEOUT: 357247405Salfred u = wdog_kern_last_timeout(); 358247405Salfred *(u_int *)data = u; 359247405Salfred break; 360247405Salfred case WDIOCPATPAT: 361247405Salfred error = wd_ioctl_patpat(data); 362247405Salfred break; 363247405Salfred default: 364247405Salfred error = ENOIOCTL; 365247405Salfred break; 366247405Salfred } 367247405Salfred return (error); 368247405Salfred} 369247405Salfred 370247405Salfred/* 371247405Salfred * Return the last timeout set, this is NOT the seconds from NOW until timeout, 372247405Salfred * rather it is the amount of seconds passed to WDIOCPATPAT/WDIOC_SETTIMEOUT. 373247405Salfred */ 374247405Salfredu_int 375247405Salfredwdog_kern_last_timeout(void) 376247405Salfred{ 377247405Salfred 378247405Salfred return (wd_last_u); 379247405Salfred} 380247405Salfred 381126370Sphkstatic struct cdevsw wd_cdevsw = { 382126370Sphk .d_version = D_VERSION, 383126370Sphk .d_ioctl = wd_ioctl, 384126370Sphk .d_name = "watchdog", 385126370Sphk}; 386126370Sphk 387126370Sphkstatic int 388126370Sphkwatchdog_modevent(module_t mod __unused, int type, void *data __unused) 389126370Sphk{ 390126370Sphk switch(type) { 391126370Sphk case MOD_LOAD: 392247405Salfred callout_init(&wd_pretimeo_handle, true); 393247405Salfred callout_init(&wd_softtimeo_handle, true); 394126370Sphk wd_dev = make_dev(&wd_cdevsw, 0, 395126370Sphk UID_ROOT, GID_WHEEL, 0600, _PATH_WATCHDOG); 396126370Sphk return 0; 397126370Sphk case MOD_UNLOAD: 398247405Salfred callout_stop(&wd_pretimeo_handle); 399247405Salfred callout_stop(&wd_softtimeo_handle); 400247405Salfred callout_drain(&wd_pretimeo_handle); 401247405Salfred callout_drain(&wd_softtimeo_handle); 402126370Sphk destroy_dev(wd_dev); 403126370Sphk return 0; 404126370Sphk case MOD_SHUTDOWN: 405126370Sphk return 0; 406126370Sphk default: 407126370Sphk return EOPNOTSUPP; 408126370Sphk } 409126370Sphk} 410126370Sphk 411126370SphkDEV_MODULE(watchdog, watchdog_modevent, NULL); 412