1 /* $NetBSD: sysmon_wdog.c,v 1.24 2007/12/16 21:07:45 dyoung 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.24 2007/12/16 21:07:45 dyoung 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 68 struct sysmon_wdog *sysmon_wdog_find(const char *); 69 void sysmon_wdog_release(struct sysmon_wdog *); 70 int sysmon_wdog_setmode(struct sysmon_wdog *, int, u_int); 71 void sysmon_wdog_ktickle(void *); 72 void sysmon_wdog_shutdown(void *); 73 void sysmon_wdog_ref(struct sysmon_wdog *); 74 75 void 76 sysmon_wdog_init(void) 77 { 78 mutex_init(&sysmon_wdog_list_mtx, MUTEX_DEFAULT, IPL_NONE); 79 mutex_init(&sysmon_wdog_mtx, MUTEX_DEFAULT, IPL_SOFTCLOCK); 80 cv_init(&sysmon_wdog_cv, "wdogref"); 81 } 82 83 /* 84 * sysmonopen_wdog: 85 * 86 * Open the system monitor device. 87 */ 88 int 89 sysmonopen_wdog(dev_t dev, int flag, int mode, struct lwp *l) 90 { 91 92 mutex_enter(&sysmon_wdog_list_mtx); 93 if (sysmon_wdog_sdhook == NULL) { 94 sysmon_wdog_sdhook = 95 shutdownhook_establish(sysmon_wdog_shutdown, NULL); 96 if (sysmon_wdog_sdhook == NULL) 97 printf("WARNING: unable to register watchdog " 98 "shutdown hook\n"); 99 callout_init(&sysmon_wdog_callout, 0); 100 } 101 mutex_exit(&sysmon_wdog_list_mtx); 102 103 return 0; 104 } 105 106 /* 107 * sysmonclose_wdog: 108 * 109 * Close the system monitor device. 110 */ 111 int 112 sysmonclose_wdog(dev_t dev, int flag, int mode, struct lwp *l) 113 { 114 struct sysmon_wdog *smw; 115 int error = 0; 116 117 /* 118 * If this is the last close, and there is a watchdog 119 * running in UTICKLE mode, we need to disable it, 120 * otherwise the system will reset in short order. 121 * 122 * XXX Maybe we should just go into KTICKLE mode? 123 */ 124 mutex_enter(&sysmon_wdog_mtx); 125 if ((smw = sysmon_armed_wdog) != NULL) { 126 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_UTICKLE) { 127 error = sysmon_wdog_setmode(smw, 128 WDOG_MODE_DISARMED, smw->smw_period); 129 if (error) { 130 printf("WARNING: UNABLE TO DISARM " 131 "WATCHDOG %s ON CLOSE!\n", 132 smw->smw_name); 133 /* 134 * ...we will probably reboot soon. 135 */ 136 } 137 } 138 } 139 mutex_exit(&sysmon_wdog_mtx); 140 141 return error; 142 } 143 144 /* 145 * sysmonioctl_wdog: 146 * 147 * Perform a watchdog control request. 148 */ 149 int 150 sysmonioctl_wdog(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) 151 { 152 struct sysmon_wdog *smw; 153 int error = 0; 154 155 switch (cmd) { 156 case WDOGIOC_GMODE: 157 { 158 struct wdog_mode *wm = (void *) data; 159 160 wm->wm_name[sizeof(wm->wm_name) - 1] = '\0'; 161 smw = sysmon_wdog_find(wm->wm_name); 162 if (smw == NULL) { 163 error = ESRCH; 164 break; 165 } 166 167 wm->wm_mode = smw->smw_mode; 168 wm->wm_period = smw->smw_period; 169 sysmon_wdog_release(smw); 170 break; 171 } 172 173 case WDOGIOC_SMODE: 174 { 175 struct wdog_mode *wm = (void *) data; 176 177 if ((flag & FWRITE) == 0) { 178 error = EPERM; 179 break; 180 } 181 182 wm->wm_name[sizeof(wm->wm_name) - 1] = '\0'; 183 smw = sysmon_wdog_find(wm->wm_name); 184 if (smw == NULL) { 185 error = ESRCH; 186 break; 187 } 188 189 if (wm->wm_mode & ~(WDOG_MODE_MASK|WDOG_FEATURE_MASK)) 190 error = EINVAL; 191 else { 192 mutex_enter(&sysmon_wdog_mtx); 193 error = sysmon_wdog_setmode(smw, wm->wm_mode, 194 wm->wm_period); 195 mutex_exit(&sysmon_wdog_mtx); 196 } 197 198 sysmon_wdog_release(smw); 199 break; 200 } 201 202 case WDOGIOC_WHICH: 203 { 204 struct wdog_mode *wm = (void *) data; 205 206 mutex_enter(&sysmon_wdog_mtx); 207 if ((smw = sysmon_armed_wdog) != NULL) { 208 strcpy(wm->wm_name, smw->smw_name); 209 wm->wm_mode = smw->smw_mode; 210 wm->wm_period = smw->smw_period; 211 } else 212 error = ESRCH; 213 mutex_exit(&sysmon_wdog_mtx); 214 break; 215 } 216 217 case WDOGIOC_TICKLE: 218 if ((flag & FWRITE) == 0) { 219 error = EPERM; 220 break; 221 } 222 223 mutex_enter(&sysmon_wdog_mtx); 224 if ((smw = sysmon_armed_wdog) != NULL) { 225 error = (*smw->smw_tickle)(smw); 226 if (error == 0) 227 smw->smw_tickler = l->l_proc->p_pid; 228 } else 229 error = ESRCH; 230 mutex_exit(&sysmon_wdog_mtx); 231 break; 232 233 case WDOGIOC_GTICKLER: 234 if ((smw = sysmon_armed_wdog) != NULL) 235 *(pid_t *)data = smw->smw_tickler; 236 else 237 error = ESRCH; 238 break; 239 240 case WDOGIOC_GWDOGS: 241 { 242 struct wdog_conf *wc = (void *) data; 243 char *cp; 244 int i; 245 246 mutex_enter(&sysmon_wdog_list_mtx); 247 if (wc->wc_names == NULL) 248 wc->wc_count = sysmon_wdog_count; 249 else { 250 for (i = 0, cp = wc->wc_names, 251 smw = LIST_FIRST(&sysmon_wdog_list); 252 i < sysmon_wdog_count && smw != NULL && error == 0; 253 i++, cp += WDOG_NAMESIZE, 254 smw = LIST_NEXT(smw, smw_list)) 255 error = copyout(smw->smw_name, cp, 256 strlen(smw->smw_name) + 1); 257 wc->wc_count = i; 258 } 259 mutex_exit(&sysmon_wdog_list_mtx); 260 break; 261 } 262 263 default: 264 error = ENOTTY; 265 } 266 267 return error; 268 } 269 270 /* 271 * sysmon_wdog_register: 272 * 273 * Register a watchdog device. 274 */ 275 int 276 sysmon_wdog_register(struct sysmon_wdog *smw) 277 { 278 struct sysmon_wdog *lsmw; 279 int error = 0; 280 281 mutex_enter(&sysmon_wdog_list_mtx); 282 283 LIST_FOREACH(lsmw, &sysmon_wdog_list, smw_list) { 284 if (strcmp(lsmw->smw_name, smw->smw_name) == 0) { 285 error = EEXIST; 286 goto out; 287 } 288 } 289 290 smw->smw_mode = WDOG_MODE_DISARMED; 291 smw->smw_tickler = (pid_t) -1; 292 smw->smw_refcnt = 0; 293 sysmon_wdog_count++; 294 LIST_INSERT_HEAD(&sysmon_wdog_list, smw, smw_list); 295 296 out: 297 mutex_exit(&sysmon_wdog_list_mtx); 298 return error; 299 } 300 301 /* 302 * sysmon_wdog_unregister: 303 * 304 * Unregister a watchdog device. 305 */ 306 int 307 sysmon_wdog_unregister(struct sysmon_wdog *smw) 308 { 309 int rc = 0; 310 311 mutex_enter(&sysmon_wdog_list_mtx); 312 while (smw->smw_refcnt > 0 && rc == 0) { 313 aprint_debug("%s: %d users remain\n", smw->smw_name, 314 smw->smw_refcnt); 315 rc = cv_wait_sig(&sysmon_wdog_cv, &sysmon_wdog_list_mtx); 316 } 317 if (rc == 0) { 318 sysmon_wdog_count--; 319 LIST_REMOVE(smw, smw_list); 320 } 321 mutex_exit(&sysmon_wdog_list_mtx); 322 return rc; 323 } 324 325 /* 326 * sysmon_wdog_find: 327 * 328 * Find a watchdog device. We increase the reference 329 * count on a match. 330 */ 331 struct sysmon_wdog * 332 sysmon_wdog_find(const char *name) 333 { 334 struct sysmon_wdog *smw; 335 336 mutex_enter(&sysmon_wdog_list_mtx); 337 338 LIST_FOREACH(smw, &sysmon_wdog_list, smw_list) { 339 if (strcmp(smw->smw_name, name) == 0) 340 break; 341 } 342 343 if (smw != NULL) 344 smw->smw_refcnt++; 345 346 mutex_exit(&sysmon_wdog_list_mtx); 347 return smw; 348 } 349 350 /* 351 * sysmon_wdog_release: 352 * 353 * Release a watchdog device. 354 */ 355 void 356 sysmon_wdog_release(struct sysmon_wdog *smw) 357 { 358 359 mutex_enter(&sysmon_wdog_list_mtx); 360 KASSERT(smw->smw_refcnt != 0); 361 smw->smw_refcnt--; 362 cv_signal(&sysmon_wdog_cv); 363 mutex_exit(&sysmon_wdog_list_mtx); 364 } 365 366 void 367 sysmon_wdog_ref(struct sysmon_wdog *smw) 368 { 369 mutex_enter(&sysmon_wdog_list_mtx); 370 smw->smw_refcnt++; 371 mutex_exit(&sysmon_wdog_list_mtx); 372 } 373 374 /* 375 * sysmon_wdog_setmode: 376 * 377 * Set the mode of a watchdog device. 378 */ 379 int 380 sysmon_wdog_setmode(struct sysmon_wdog *smw, int mode, u_int period) 381 { 382 u_int operiod = smw->smw_period; 383 int omode = smw->smw_mode; 384 int error = 0; 385 386 smw->smw_period = period; 387 smw->smw_mode = mode; 388 389 switch (mode & WDOG_MODE_MASK) { 390 case WDOG_MODE_DISARMED: 391 if (smw != sysmon_armed_wdog) { 392 error = EINVAL; 393 goto out; 394 } 395 break; 396 397 case WDOG_MODE_KTICKLE: 398 case WDOG_MODE_UTICKLE: 399 case WDOG_MODE_ETICKLE: 400 if (sysmon_armed_wdog != NULL) { 401 error = EBUSY; 402 goto out; 403 } 404 break; 405 406 default: 407 error = EINVAL; 408 goto out; 409 } 410 411 error = (*smw->smw_setmode)(smw); 412 413 out: 414 if (error) { 415 smw->smw_period = operiod; 416 smw->smw_mode = omode; 417 } else { 418 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 419 sysmon_armed_wdog = NULL; 420 smw->smw_tickler = (pid_t) -1; 421 sysmon_wdog_release(smw); 422 if ((omode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) 423 callout_stop(&sysmon_wdog_callout); 424 } else { 425 sysmon_armed_wdog = smw; 426 sysmon_wdog_ref(smw); 427 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) { 428 callout_reset(&sysmon_wdog_callout, 429 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2, 430 sysmon_wdog_ktickle, NULL); 431 } 432 } 433 } 434 return error; 435 } 436 437 /* 438 * sysmon_wdog_ktickle: 439 * 440 * Kernel watchdog tickle routine. 441 */ 442 void 443 sysmon_wdog_ktickle(void *arg) 444 { 445 struct sysmon_wdog *smw; 446 447 mutex_enter(&sysmon_wdog_mtx); 448 if ((smw = sysmon_armed_wdog) != NULL) { 449 if ((*smw->smw_tickle)(smw) != 0) { 450 printf("WARNING: KERNEL TICKLE OF WATCHDOG %s " 451 "FAILED!\n", smw->smw_name); 452 /* 453 * ...we will probably reboot soon. 454 */ 455 } 456 callout_reset(&sysmon_wdog_callout, 457 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2, 458 sysmon_wdog_ktickle, NULL); 459 } 460 mutex_exit(&sysmon_wdog_mtx); 461 } 462 463 /* 464 * sysmon_wdog_shutdown: 465 * 466 * Perform shutdown-time operations. 467 */ 468 void 469 sysmon_wdog_shutdown(void *arg) 470 { 471 struct sysmon_wdog *smw; 472 473 /* 474 * XXX Locking here? I don't think it's necessary. 475 */ 476 477 if ((smw = sysmon_armed_wdog) != NULL) { 478 if (sysmon_wdog_setmode(smw, WDOG_MODE_DISARMED, 479 smw->smw_period)) 480 printf("WARNING: FAILED TO SHUTDOWN WATCHDOG %s!\n", 481 smw->smw_name); 482 } 483 } 484