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