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 31126370Sphk#include <sys/cdefs.h> 32126370Sphk__FBSDID("$FreeBSD$"); 33126370Sphk 34126370Sphk#include <sys/param.h> 35247405Salfred#include <sys/types.h> 36126370Sphk#include <sys/systm.h> 37126370Sphk#include <sys/conf.h> 38126370Sphk#include <sys/uio.h> 39126370Sphk#include <sys/kernel.h> 40126370Sphk#include <sys/malloc.h> 41126370Sphk#include <sys/module.h> 42253719Salfred#include <sys/sysctl.h> 43247405Salfred#include <sys/syslog.h> 44126370Sphk#include <sys/watchdog.h> 45126370Sphk#include <sys/bus.h> 46126370Sphk#include <machine/bus.h> 47126370Sphk 48247405Salfred#include <sys/syscallsubr.h> /* kern_clock_gettime() */ 49247405Salfred 50247405Salfredstatic int wd_set_pretimeout(int newtimeout, int disableiftoolong); 51247405Salfredstatic void wd_timeout_cb(void *arg); 52247405Salfred 53247405Salfredstatic struct callout wd_pretimeo_handle; 54247405Salfredstatic int wd_pretimeout; 55247405Salfredstatic int wd_pretimeout_act = WD_SOFT_LOG; 56247405Salfred 57247405Salfredstatic struct callout wd_softtimeo_handle; 58247405Salfredstatic int wd_softtimer; /* true = use softtimer instead of hardware 59247405Salfred watchdog */ 60247405Salfredstatic int wd_softtimeout_act = WD_SOFT_LOG; /* action for the software timeout */ 61247405Salfred 62130585Sphkstatic struct cdev *wd_dev; 63247405Salfredstatic volatile u_int wd_last_u; /* last timeout value set by kern_do_pat */ 64253719Salfredstatic u_int wd_last_u_sysctl; /* last timeout value set by kern_do_pat */ 65253719Salfredstatic u_int wd_last_u_sysctl_secs; /* wd_last_u in seconds */ 66126370Sphk 67253719SalfredSYSCTL_NODE(_hw, OID_AUTO, watchdog, CTLFLAG_RD, 0, "Main watchdog device"); 68253719SalfredSYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u, CTLFLAG_RD, 69253719Salfred &wd_last_u_sysctl, 0, "Watchdog last update time"); 70253719SalfredSYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u_secs, CTLFLAG_RD, 71253719Salfred &wd_last_u_sysctl_secs, 0, "Watchdog last update time"); 72253719Salfred 73247405Salfredstatic int wd_lastpat_valid = 0; 74247405Salfredstatic time_t wd_lastpat = 0; /* when the watchdog was last patted */ 75247405Salfred 76253719Salfredstatic void 77253719Salfredpow2ns_to_ts(int pow2ns, struct timespec *ts) 78253719Salfred{ 79253719Salfred uint64_t ns; 80253719Salfred 81253719Salfred ns = 1ULL << pow2ns; 82253719Salfred ts->tv_sec = ns / 1000000000ULL; 83253719Salfred ts->tv_nsec = ns % 1000000000ULL; 84253719Salfred} 85253719Salfred 86253719Salfredstatic int 87253719Salfredpow2ns_to_ticks(int pow2ns) 88253719Salfred{ 89253719Salfred struct timeval tv; 90253719Salfred struct timespec ts; 91253719Salfred 92253719Salfred pow2ns_to_ts(pow2ns, &ts); 93253719Salfred TIMESPEC_TO_TIMEVAL(&tv, &ts); 94253719Salfred return (tvtohz(&tv)); 95253719Salfred} 96253719Salfred 97253719Salfredstatic int 98253719Salfredseconds_to_pow2ns(int seconds) 99253719Salfred{ 100253719Salfred uint64_t power; 101253719Salfred uint64_t ns; 102253719Salfred uint64_t shifted; 103253719Salfred 104253719Salfred ns = ((uint64_t)seconds) * 1000000000ULL; 105253719Salfred power = flsll(ns); 106253719Salfred shifted = 1ULL << power; 107253719Salfred if (shifted <= ns) { 108253719Salfred power++; 109253719Salfred } 110253719Salfred return (power); 111253719Salfred} 112253719Salfred 113253719Salfred 114247405Salfredint 115247405Salfredwdog_kern_pat(u_int utim) 116221121Sattilio{ 117221121Sattilio int error; 118221121Sattilio 119221121Sattilio if ((utim & WD_LASTVAL) != 0 && (utim & WD_INTERVAL) > 0) 120221121Sattilio return (EINVAL); 121221121Sattilio 122221121Sattilio if ((utim & WD_LASTVAL) != 0) { 123247405Salfred /* 124247405Salfred * if WD_LASTVAL is set, fill in the bits for timeout 125247405Salfred * from the saved value in wd_last_u. 126247405Salfred */ 127221121Sattilio MPASS((wd_last_u & ~WD_INTERVAL) == 0); 128221121Sattilio utim &= ~WD_LASTVAL; 129221121Sattilio utim |= wd_last_u; 130247405Salfred } else { 131247405Salfred /* 132247405Salfred * Otherwise save the new interval. 133247405Salfred * This can be zero (to disable the watchdog) 134247405Salfred */ 135221121Sattilio wd_last_u = (utim & WD_INTERVAL); 136253719Salfred wd_last_u_sysctl = wd_last_u; 137253719Salfred wd_last_u_sysctl_secs = pow2ns_to_ticks(wd_last_u) / hz; 138247405Salfred } 139221121Sattilio if ((utim & WD_INTERVAL) == WD_TO_NEVER) { 140221121Sattilio utim = 0; 141221121Sattilio 142221121Sattilio /* Assume all is well; watchdog signals failure. */ 143221121Sattilio error = 0; 144221121Sattilio } else { 145221121Sattilio /* Assume no watchdog available; watchdog flags success */ 146221121Sattilio error = EOPNOTSUPP; 147221121Sattilio } 148247405Salfred if (wd_softtimer) { 149247405Salfred if (utim == 0) { 150247405Salfred callout_stop(&wd_softtimeo_handle); 151247405Salfred } else { 152247405Salfred (void) callout_reset(&wd_softtimeo_handle, 153253719Salfred pow2ns_to_ticks(utim), wd_timeout_cb, "soft"); 154247405Salfred } 155247405Salfred error = 0; 156247405Salfred } else { 157247405Salfred EVENTHANDLER_INVOKE(watchdog_list, utim, &error); 158247405Salfred } 159247405Salfred wd_set_pretimeout(wd_pretimeout, true); 160247405Salfred /* 161247405Salfred * If we were able to arm/strobe the watchdog, then 162247405Salfred * update the last time it was strobed for WDIOC_GETTIMELEFT 163247405Salfred */ 164247405Salfred if (!error) { 165247405Salfred struct timespec ts; 166247405Salfred 167247405Salfred error = kern_clock_gettime(curthread /* XXX */, 168247405Salfred CLOCK_MONOTONIC_FAST, &ts); 169247405Salfred if (!error) { 170247405Salfred wd_lastpat = ts.tv_sec; 171247405Salfred wd_lastpat_valid = 1; 172247405Salfred } 173247405Salfred } 174221121Sattilio return (error); 175221121Sattilio} 176221121Sattilio 177221121Sattiliostatic int 178247405Salfredwd_valid_act(int act) 179126370Sphk{ 180247405Salfred 181247405Salfred if ((act & ~(WD_SOFT_MASK)) != 0) 182247405Salfred return false; 183247405Salfred return true; 184247405Salfred} 185247405Salfred 186247405Salfredstatic int 187247405Salfredwd_ioctl_patpat(caddr_t data) 188247405Salfred{ 189126370Sphk u_int u; 190126370Sphk 191126370Sphk u = *(u_int *)data; 192221121Sattilio if (u & ~(WD_ACTIVE | WD_PASSIVE | WD_LASTVAL | WD_INTERVAL)) 193126370Sphk return (EINVAL); 194126370Sphk if ((u & (WD_ACTIVE | WD_PASSIVE)) == (WD_ACTIVE | WD_PASSIVE)) 195126370Sphk return (EINVAL); 196221121Sattilio if ((u & (WD_ACTIVE | WD_PASSIVE)) == 0 && ((u & WD_INTERVAL) > 0 || 197221121Sattilio (u & WD_LASTVAL) != 0)) 198167950Sn_hibma return (EINVAL); 199165260Sn_hibma if (u & WD_PASSIVE) 200165260Sn_hibma return (ENOSYS); /* XXX Not implemented yet */ 201221121Sattilio u &= ~(WD_ACTIVE | WD_PASSIVE); 202221121Sattilio 203247405Salfred return (wdog_kern_pat(u)); 204126370Sphk} 205126370Sphk 206247405Salfredstatic int 207247405Salfredwd_get_time_left(struct thread *td, time_t *remainp) 208221121Sattilio{ 209247405Salfred struct timespec ts; 210247405Salfred int error; 211221121Sattilio 212247405Salfred error = kern_clock_gettime(td, CLOCK_MONOTONIC_FAST, &ts); 213247405Salfred if (error) 214247405Salfred return (error); 215247405Salfred if (!wd_lastpat_valid) 216247405Salfred return (ENOENT); 217247405Salfred *remainp = ts.tv_sec - wd_lastpat; 218247405Salfred return (0); 219221121Sattilio} 220221121Sattilio 221247405Salfredstatic void 222247405Salfredwd_timeout_cb(void *arg) 223221121Sattilio{ 224247405Salfred const char *type = arg; 225221121Sattilio 226247405Salfred#ifdef DDB 227247405Salfred if ((wd_pretimeout_act & WD_SOFT_DDB)) { 228247405Salfred char kdb_why[80]; 229247405Salfred snprintf(kdb_why, sizeof(buf), "watchdog %s timeout", type); 230247405Salfred kdb_backtrace(); 231247405Salfred kdb_enter(KDB_WHY_WATCHDOG, kdb_why); 232247405Salfred } 233247405Salfred#endif 234247405Salfred if ((wd_pretimeout_act & WD_SOFT_LOG)) 235247405Salfred log(LOG_EMERG, "watchdog %s-timeout, WD_SOFT_LOG", type); 236247405Salfred if ((wd_pretimeout_act & WD_SOFT_PRINTF)) 237247405Salfred printf("watchdog %s-timeout, WD_SOFT_PRINTF\n", type); 238247405Salfred if ((wd_pretimeout_act & WD_SOFT_PANIC)) 239247405Salfred panic("watchdog %s-timeout, WD_SOFT_PANIC set", type); 240247405Salfred} 241221121Sattilio 242247405Salfred/* 243247405Salfred * Called to manage timeouts. 244247405Salfred * newtimeout needs to be in the range of 0 to actual watchdog timeout. 245247405Salfred * if 0, we disable the pre-timeout. 246247405Salfred * otherwise we set the pre-timeout provided it's not greater than the 247247405Salfred * current actual watchdog timeout. 248247405Salfred */ 249247405Salfredstatic int 250247405Salfredwd_set_pretimeout(int newtimeout, int disableiftoolong) 251247405Salfred{ 252247405Salfred u_int utime; 253253719Salfred struct timespec utime_ts; 254253719Salfred int timeout_ticks; 255247405Salfred 256247405Salfred utime = wdog_kern_last_timeout(); 257253719Salfred pow2ns_to_ts(utime, &utime_ts); 258247405Salfred /* do not permit a pre-timeout >= than the timeout. */ 259253719Salfred if (newtimeout >= utime_ts.tv_sec) { 260247405Salfred /* 261247405Salfred * If 'disableiftoolong' then just fall through 262247405Salfred * so as to disable the pre-watchdog 263247405Salfred */ 264247405Salfred if (disableiftoolong) 265247405Salfred newtimeout = 0; 266247405Salfred else 267247405Salfred return EINVAL; 268247405Salfred } 269247405Salfred 270247405Salfred /* disable the pre-timeout */ 271247405Salfred if (newtimeout == 0) { 272247405Salfred wd_pretimeout = 0; 273247405Salfred callout_stop(&wd_pretimeo_handle); 274247405Salfred return 0; 275247405Salfred } 276247405Salfred 277253719Salfred timeout_ticks = pow2ns_to_ticks(utime) - (hz*newtimeout); 278253719Salfred#if 0 279253719Salfred printf("wd_set_pretimeout: " 280253719Salfred "newtimeout: %d, " 281253719Salfred "utime: %d -> utime_ticks: %d, " 282253719Salfred "hz*newtimeout: %d, " 283253719Salfred "timeout_ticks: %d -> sec: %d\n", 284253719Salfred newtimeout, 285253719Salfred utime, pow2ns_to_ticks(utime), 286253719Salfred hz*newtimeout, 287253719Salfred timeout_ticks, timeout_ticks / hz); 288253719Salfred#endif 289253719Salfred 290247405Salfred /* We determined the value is sane, so reset the callout */ 291253719Salfred (void) callout_reset(&wd_pretimeo_handle, 292253719Salfred timeout_ticks, 293247405Salfred wd_timeout_cb, "pre-timeout"); 294247405Salfred wd_pretimeout = newtimeout; 295247405Salfred return 0; 296221121Sattilio} 297221121Sattilio 298247405Salfredstatic int 299247405Salfredwd_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data, 300247405Salfred int flags __unused, struct thread *td) 301247405Salfred{ 302247405Salfred u_int u; 303247405Salfred time_t timeleft; 304247405Salfred int error; 305247405Salfred 306247405Salfred error = 0; 307247405Salfred 308247405Salfred switch (cmd) { 309247405Salfred case WDIOC_SETSOFT: 310247405Salfred u = *(int *)data; 311247405Salfred /* do nothing? */ 312247405Salfred if (u == wd_softtimer) 313247405Salfred break; 314247405Salfred /* If there is a pending timeout disallow this ioctl */ 315247405Salfred if (wd_last_u != 0) { 316247405Salfred error = EINVAL; 317247405Salfred break; 318247405Salfred } 319247405Salfred wd_softtimer = u; 320247405Salfred break; 321247405Salfred case WDIOC_SETSOFTTIMEOUTACT: 322247405Salfred u = *(int *)data; 323247405Salfred if (wd_valid_act(u)) { 324247405Salfred wd_softtimeout_act = u; 325247405Salfred } else { 326247405Salfred error = EINVAL; 327247405Salfred } 328247405Salfred break; 329247405Salfred case WDIOC_SETPRETIMEOUTACT: 330247405Salfred u = *(int *)data; 331247405Salfred if (wd_valid_act(u)) { 332247405Salfred wd_pretimeout_act = u; 333247405Salfred } else { 334247405Salfred error = EINVAL; 335247405Salfred } 336247405Salfred break; 337247405Salfred case WDIOC_GETPRETIMEOUT: 338247405Salfred *(int *)data = (int)wd_pretimeout; 339247405Salfred break; 340247405Salfred case WDIOC_SETPRETIMEOUT: 341247405Salfred error = wd_set_pretimeout(*(int *)data, false); 342247405Salfred break; 343247405Salfred case WDIOC_GETTIMELEFT: 344247405Salfred error = wd_get_time_left(td, &timeleft); 345247405Salfred if (error) 346247405Salfred break; 347247405Salfred *(int *)data = (int)timeleft; 348247405Salfred break; 349247405Salfred case WDIOC_SETTIMEOUT: 350247405Salfred u = *(u_int *)data; 351253719Salfred error = wdog_kern_pat(seconds_to_pow2ns(u)); 352247405Salfred break; 353247405Salfred case WDIOC_GETTIMEOUT: 354247405Salfred u = wdog_kern_last_timeout(); 355247405Salfred *(u_int *)data = u; 356247405Salfred break; 357247405Salfred case WDIOCPATPAT: 358247405Salfred error = wd_ioctl_patpat(data); 359247405Salfred break; 360247405Salfred default: 361247405Salfred error = ENOIOCTL; 362247405Salfred break; 363247405Salfred } 364247405Salfred return (error); 365247405Salfred} 366247405Salfred 367247405Salfred/* 368247405Salfred * Return the last timeout set, this is NOT the seconds from NOW until timeout, 369247405Salfred * rather it is the amount of seconds passed to WDIOCPATPAT/WDIOC_SETTIMEOUT. 370247405Salfred */ 371247405Salfredu_int 372247405Salfredwdog_kern_last_timeout(void) 373247405Salfred{ 374247405Salfred 375247405Salfred return (wd_last_u); 376247405Salfred} 377247405Salfred 378126370Sphkstatic struct cdevsw wd_cdevsw = { 379126370Sphk .d_version = D_VERSION, 380126370Sphk .d_ioctl = wd_ioctl, 381126370Sphk .d_name = "watchdog", 382126370Sphk}; 383126370Sphk 384126370Sphkstatic int 385126370Sphkwatchdog_modevent(module_t mod __unused, int type, void *data __unused) 386126370Sphk{ 387126370Sphk switch(type) { 388126370Sphk case MOD_LOAD: 389247405Salfred callout_init(&wd_pretimeo_handle, true); 390247405Salfred callout_init(&wd_softtimeo_handle, true); 391126370Sphk wd_dev = make_dev(&wd_cdevsw, 0, 392126370Sphk UID_ROOT, GID_WHEEL, 0600, _PATH_WATCHDOG); 393126370Sphk return 0; 394126370Sphk case MOD_UNLOAD: 395247405Salfred callout_stop(&wd_pretimeo_handle); 396247405Salfred callout_stop(&wd_softtimeo_handle); 397247405Salfred callout_drain(&wd_pretimeo_handle); 398247405Salfred callout_drain(&wd_softtimeo_handle); 399126370Sphk destroy_dev(wd_dev); 400126370Sphk return 0; 401126370Sphk case MOD_SHUTDOWN: 402126370Sphk return 0; 403126370Sphk default: 404126370Sphk return EOPNOTSUPP; 405126370Sphk } 406126370Sphk} 407126370Sphk 408126370SphkDEV_MODULE(watchdog, watchdog_modevent, NULL); 409