1 /* $NetBSD: sysmon_wdog.c,v 1.25 2011/01/04 01:51:06 matt Exp $ */ 2 3 /*- 4 * Copyright (c) 2000 Zembu Labs, Inc. 5 * All rights reserved. 6 * 7 * Author: Jason R. Thorpe <thorpej@zembu.com> 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. All advertising materials mentioning features or use of this software 18 * must display the following acknowledgement: 19 * This product includes software developed by Zembu Labs, Inc. 20 * 4. Neither the name of Zembu Labs nor the names of its employees may 21 * be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY ZEMBU LABS, INC. ``AS IS'' AND ANY EXPRESS 25 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WAR- 26 * RANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DIS- 27 * CLAIMED. IN NO EVENT SHALL ZEMBU LABS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 29 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 */ 35 36 /* 37 * Watchdog timer framework for sysmon. Hardware (and software) 38 * watchdog timers can register themselves here to provide a 39 * watchdog function, which provides an abstract interface to the 40 * user. 41 */ 42 43 #include <sys/cdefs.h> 44 __KERNEL_RCSID(0, "$NetBSD: sysmon_wdog.c,v 1.25 2011/01/04 01:51:06 matt Exp $"); 45 46 #include <sys/param.h> 47 #include <sys/conf.h> 48 #include <sys/errno.h> 49 #include <sys/fcntl.h> 50 #include <sys/condvar.h> 51 #include <sys/mutex.h> 52 #include <sys/callout.h> 53 #include <sys/kernel.h> 54 #include <sys/systm.h> 55 #include <sys/proc.h> 56 57 #include <dev/sysmon/sysmonvar.h> 58 59 static LIST_HEAD(, sysmon_wdog) sysmon_wdog_list = 60 LIST_HEAD_INITIALIZER(&sysmon_wdog_list); 61 static int sysmon_wdog_count; 62 static kmutex_t sysmon_wdog_list_mtx, sysmon_wdog_mtx; 63 static kcondvar_t sysmon_wdog_cv; 64 static struct sysmon_wdog *sysmon_armed_wdog; 65 static callout_t sysmon_wdog_callout; 66 static void *sysmon_wdog_sdhook; 67 static void *sysmon_wdog_cphook; 68 69 struct sysmon_wdog *sysmon_wdog_find(const char *); 70 void sysmon_wdog_release(struct sysmon_wdog *); 71 int sysmon_wdog_setmode(struct sysmon_wdog *, int, u_int); 72 void sysmon_wdog_ktickle(void *); 73 void sysmon_wdog_critpoll(void *); 74 void sysmon_wdog_shutdown(void *); 75 void sysmon_wdog_ref(struct sysmon_wdog *); 76 77 void 78 sysmon_wdog_init(void) 79 { 80 mutex_init(&sysmon_wdog_list_mtx, MUTEX_DEFAULT, IPL_NONE); 81 mutex_init(&sysmon_wdog_mtx, MUTEX_DEFAULT, IPL_SOFTCLOCK); 82 cv_init(&sysmon_wdog_cv, "wdogref"); 83 sysmon_wdog_sdhook = shutdownhook_establish(sysmon_wdog_shutdown, NULL); 84 if (sysmon_wdog_sdhook == NULL) 85 printf("WARNING: unable to register watchdog shutdown hook\n"); 86 sysmon_wdog_cphook = critpollhook_establish(sysmon_wdog_critpoll, NULL); 87 if (sysmon_wdog_cphook == NULL) 88 printf("WARNING: unable to register watchdog critpoll hook\n"); 89 callout_init(&sysmon_wdog_callout, 0); 90 } 91 92 /* 93 * sysmonopen_wdog: 94 * 95 * Open the system monitor device. 96 */ 97 int 98 sysmonopen_wdog(dev_t dev, int flag, int mode, struct lwp *l) 99 { 100 101 return 0; 102 } 103 104 /* 105 * sysmonclose_wdog: 106 * 107 * Close the system monitor device. 108 */ 109 int 110 sysmonclose_wdog(dev_t dev, int flag, int mode, struct lwp *l) 111 { 112 struct sysmon_wdog *smw; 113 int error = 0; 114 115 /* 116 * If this is the last close, and there is a watchdog 117 * running in UTICKLE mode, we need to disable it, 118 * otherwise the system will reset in short order. 119 * 120 * XXX Maybe we should just go into KTICKLE mode? 121 */ 122 mutex_enter(&sysmon_wdog_mtx); 123 if ((smw = sysmon_armed_wdog) != NULL) { 124 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_UTICKLE) { 125 error = sysmon_wdog_setmode(smw, 126 WDOG_MODE_DISARMED, smw->smw_period); 127 if (error) { 128 printf("WARNING: UNABLE TO DISARM " 129 "WATCHDOG %s ON CLOSE!\n", 130 smw->smw_name); 131 /* 132 * ...we will probably reboot soon. 133 */ 134 } 135 } 136 } 137 mutex_exit(&sysmon_wdog_mtx); 138 139 return error; 140 } 141 142 /* 143 * sysmonioctl_wdog: 144 * 145 * Perform a watchdog control request. 146 */ 147 int 148 sysmonioctl_wdog(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) 149 { 150 struct sysmon_wdog *smw; 151 int error = 0; 152 153 switch (cmd) { 154 case WDOGIOC_GMODE: 155 { 156 struct wdog_mode *wm = (void *) data; 157 158 wm->wm_name[sizeof(wm->wm_name) - 1] = '\0'; 159 smw = sysmon_wdog_find(wm->wm_name); 160 if (smw == NULL) { 161 error = ESRCH; 162 break; 163 } 164 165 wm->wm_mode = smw->smw_mode; 166 wm->wm_period = smw->smw_period; 167 sysmon_wdog_release(smw); 168 break; 169 } 170 171 case WDOGIOC_SMODE: 172 { 173 struct wdog_mode *wm = (void *) data; 174 175 if ((flag & FWRITE) == 0) { 176 error = EPERM; 177 break; 178 } 179 180 wm->wm_name[sizeof(wm->wm_name) - 1] = '\0'; 181 smw = sysmon_wdog_find(wm->wm_name); 182 if (smw == NULL) { 183 error = ESRCH; 184 break; 185 } 186 187 if (wm->wm_mode & ~(WDOG_MODE_MASK|WDOG_FEATURE_MASK)) 188 error = EINVAL; 189 else { 190 mutex_enter(&sysmon_wdog_mtx); 191 error = sysmon_wdog_setmode(smw, wm->wm_mode, 192 wm->wm_period); 193 mutex_exit(&sysmon_wdog_mtx); 194 } 195 196 sysmon_wdog_release(smw); 197 break; 198 } 199 200 case WDOGIOC_WHICH: 201 { 202 struct wdog_mode *wm = (void *) data; 203 204 mutex_enter(&sysmon_wdog_mtx); 205 if ((smw = sysmon_armed_wdog) != NULL) { 206 strcpy(wm->wm_name, smw->smw_name); 207 wm->wm_mode = smw->smw_mode; 208 wm->wm_period = smw->smw_period; 209 } else 210 error = ESRCH; 211 mutex_exit(&sysmon_wdog_mtx); 212 break; 213 } 214 215 case WDOGIOC_TICKLE: 216 if ((flag & FWRITE) == 0) { 217 error = EPERM; 218 break; 219 } 220 221 mutex_enter(&sysmon_wdog_mtx); 222 if ((smw = sysmon_armed_wdog) != NULL) { 223 error = (*smw->smw_tickle)(smw); 224 if (error == 0) 225 smw->smw_tickler = l->l_proc->p_pid; 226 } else 227 error = ESRCH; 228 mutex_exit(&sysmon_wdog_mtx); 229 break; 230 231 case WDOGIOC_GTICKLER: 232 if ((smw = sysmon_armed_wdog) != NULL) 233 *(pid_t *)data = smw->smw_tickler; 234 else 235 error = ESRCH; 236 break; 237 238 case WDOGIOC_GWDOGS: 239 { 240 struct wdog_conf *wc = (void *) data; 241 char *cp; 242 int i; 243 244 mutex_enter(&sysmon_wdog_list_mtx); 245 if (wc->wc_names == NULL) 246 wc->wc_count = sysmon_wdog_count; 247 else { 248 for (i = 0, cp = wc->wc_names, 249 smw = LIST_FIRST(&sysmon_wdog_list); 250 i < sysmon_wdog_count && smw != NULL && error == 0; 251 i++, cp += WDOG_NAMESIZE, 252 smw = LIST_NEXT(smw, smw_list)) 253 error = copyout(smw->smw_name, cp, 254 strlen(smw->smw_name) + 1); 255 wc->wc_count = i; 256 } 257 mutex_exit(&sysmon_wdog_list_mtx); 258 break; 259 } 260 261 default: 262 error = ENOTTY; 263 } 264 265 return error; 266 } 267 268 /* 269 * sysmon_wdog_register: 270 * 271 * Register a watchdog device. 272 */ 273 int 274 sysmon_wdog_register(struct sysmon_wdog *smw) 275 { 276 struct sysmon_wdog *lsmw; 277 int error = 0; 278 279 mutex_enter(&sysmon_wdog_list_mtx); 280 281 LIST_FOREACH(lsmw, &sysmon_wdog_list, smw_list) { 282 if (strcmp(lsmw->smw_name, smw->smw_name) == 0) { 283 error = EEXIST; 284 goto out; 285 } 286 } 287 288 smw->smw_mode = WDOG_MODE_DISARMED; 289 smw->smw_tickler = (pid_t) -1; 290 smw->smw_refcnt = 0; 291 sysmon_wdog_count++; 292 LIST_INSERT_HEAD(&sysmon_wdog_list, smw, smw_list); 293 294 out: 295 mutex_exit(&sysmon_wdog_list_mtx); 296 return error; 297 } 298 299 /* 300 * sysmon_wdog_unregister: 301 * 302 * Unregister a watchdog device. 303 */ 304 int 305 sysmon_wdog_unregister(struct sysmon_wdog *smw) 306 { 307 int rc = 0; 308 309 mutex_enter(&sysmon_wdog_list_mtx); 310 while (smw->smw_refcnt > 0 && rc == 0) { 311 aprint_debug("%s: %d users remain\n", smw->smw_name, 312 smw->smw_refcnt); 313 rc = cv_wait_sig(&sysmon_wdog_cv, &sysmon_wdog_list_mtx); 314 } 315 if (rc == 0) { 316 sysmon_wdog_count--; 317 LIST_REMOVE(smw, smw_list); 318 } 319 mutex_exit(&sysmon_wdog_list_mtx); 320 return rc; 321 } 322 323 /* 324 * sysmon_wdog_critpoll: 325 * 326 * Perform critical operations during long polling periods 327 */ 328 void 329 sysmon_wdog_critpoll(void *arg) 330 { 331 struct sysmon_wdog *smw = sysmon_armed_wdog; 332 333 if (smw == NULL) 334 return; 335 336 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) { 337 if ((*smw->smw_tickle)(smw) != 0) { 338 printf("WARNING: KERNEL TICKLE OF WATCHDOG %s " 339 "FAILED!\n", smw->smw_name); 340 } 341 } 342 } 343 344 /* 345 * sysmon_wdog_find: 346 * 347 * Find a watchdog device. We increase the reference 348 * count on a match. 349 */ 350 struct sysmon_wdog * 351 sysmon_wdog_find(const char *name) 352 { 353 struct sysmon_wdog *smw; 354 355 mutex_enter(&sysmon_wdog_list_mtx); 356 357 LIST_FOREACH(smw, &sysmon_wdog_list, smw_list) { 358 if (strcmp(smw->smw_name, name) == 0) 359 break; 360 } 361 362 if (smw != NULL) 363 smw->smw_refcnt++; 364 365 mutex_exit(&sysmon_wdog_list_mtx); 366 return smw; 367 } 368 369 /* 370 * sysmon_wdog_release: 371 * 372 * Release a watchdog device. 373 */ 374 void 375 sysmon_wdog_release(struct sysmon_wdog *smw) 376 { 377 378 mutex_enter(&sysmon_wdog_list_mtx); 379 KASSERT(smw->smw_refcnt != 0); 380 smw->smw_refcnt--; 381 cv_signal(&sysmon_wdog_cv); 382 mutex_exit(&sysmon_wdog_list_mtx); 383 } 384 385 void 386 sysmon_wdog_ref(struct sysmon_wdog *smw) 387 { 388 mutex_enter(&sysmon_wdog_list_mtx); 389 smw->smw_refcnt++; 390 mutex_exit(&sysmon_wdog_list_mtx); 391 } 392 393 /* 394 * sysmon_wdog_setmode: 395 * 396 * Set the mode of a watchdog device. 397 */ 398 int 399 sysmon_wdog_setmode(struct sysmon_wdog *smw, int mode, u_int period) 400 { 401 u_int operiod = smw->smw_period; 402 int omode = smw->smw_mode; 403 int error = 0; 404 405 smw->smw_period = period; 406 smw->smw_mode = mode; 407 408 switch (mode & WDOG_MODE_MASK) { 409 case WDOG_MODE_DISARMED: 410 if (smw != sysmon_armed_wdog) { 411 error = EINVAL; 412 goto out; 413 } 414 break; 415 416 case WDOG_MODE_KTICKLE: 417 case WDOG_MODE_UTICKLE: 418 case WDOG_MODE_ETICKLE: 419 if (sysmon_armed_wdog != NULL) { 420 error = EBUSY; 421 goto out; 422 } 423 break; 424 425 default: 426 error = EINVAL; 427 goto out; 428 } 429 430 error = (*smw->smw_setmode)(smw); 431 432 out: 433 if (error) { 434 smw->smw_period = operiod; 435 smw->smw_mode = omode; 436 } else { 437 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 438 sysmon_armed_wdog = NULL; 439 smw->smw_tickler = (pid_t) -1; 440 sysmon_wdog_release(smw); 441 if ((omode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) 442 callout_stop(&sysmon_wdog_callout); 443 } else { 444 sysmon_armed_wdog = smw; 445 sysmon_wdog_ref(smw); 446 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) { 447 callout_reset(&sysmon_wdog_callout, 448 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2, 449 sysmon_wdog_ktickle, NULL); 450 } 451 } 452 } 453 return error; 454 } 455 456 /* 457 * sysmon_wdog_ktickle: 458 * 459 * Kernel watchdog tickle routine. 460 */ 461 void 462 sysmon_wdog_ktickle(void *arg) 463 { 464 struct sysmon_wdog *smw; 465 466 mutex_enter(&sysmon_wdog_mtx); 467 if ((smw = sysmon_armed_wdog) != NULL) { 468 if ((*smw->smw_tickle)(smw) != 0) { 469 printf("WARNING: KERNEL TICKLE OF WATCHDOG %s " 470 "FAILED!\n", smw->smw_name); 471 /* 472 * ...we will probably reboot soon. 473 */ 474 } 475 callout_reset(&sysmon_wdog_callout, 476 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2, 477 sysmon_wdog_ktickle, NULL); 478 } 479 mutex_exit(&sysmon_wdog_mtx); 480 } 481 482 /* 483 * sysmon_wdog_shutdown: 484 * 485 * Perform shutdown-time operations. 486 */ 487 void 488 sysmon_wdog_shutdown(void *arg) 489 { 490 struct sysmon_wdog *smw; 491 492 /* 493 * XXX Locking here? I don't think it's necessary. 494 */ 495 496 if ((smw = sysmon_armed_wdog) != NULL) { 497 if (sysmon_wdog_setmode(smw, WDOG_MODE_DISARMED, 498 smw->smw_period)) 499 printf("WARNING: FAILED TO SHUTDOWN WATCHDOG %s!\n", 500 smw->smw_name); 501 } 502 } 503