1 /* $NetBSD: sysmon_wdog.c,v 1.3 2001/11/13 06:28:55 lukem 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.3 2001/11/13 06:28:55 lukem 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 *(pid_t *)data = smw->smw_tickler; 240 break; 241 242 case WDOGIOC_GWDOGS: 243 { 244 struct wdog_conf *wc = (void *) data; 245 char *cp; 246 int i; 247 248 simple_lock(&sysmon_wdog_list_slock); 249 if (wc->wc_names == NULL) 250 wc->wc_count = sysmon_wdog_count; 251 else { 252 for (i = 0, cp = wc->wc_names, 253 smw = LIST_FIRST(&sysmon_wdog_list); 254 i < sysmon_wdog_count && smw != NULL && error == 0; 255 i++, cp += WDOG_NAMESIZE, 256 smw = LIST_NEXT(smw, smw_list)) 257 error = copyout(smw->smw_name, cp, 258 strlen(smw->smw_name) + 1); 259 wc->wc_count = i; 260 } 261 simple_unlock(&sysmon_wdog_list_slock); 262 break; 263 } 264 265 default: 266 error = ENOTTY; 267 } 268 269 return (error); 270 } 271 272 /* 273 * sysmon_wdog_register: 274 * 275 * Register a watchdog device. 276 */ 277 int 278 sysmon_wdog_register(struct sysmon_wdog *smw) 279 { 280 struct sysmon_wdog *lsmw; 281 int error = 0; 282 283 simple_lock(&sysmon_wdog_list_slock); 284 285 for (lsmw = LIST_FIRST(&sysmon_wdog_list); lsmw != NULL; 286 lsmw = LIST_NEXT(lsmw, smw_list)) { 287 if (strcmp(lsmw->smw_name, smw->smw_name) == 0) { 288 error = EEXIST; 289 goto out; 290 } 291 } 292 293 smw->smw_mode = WDOG_MODE_DISARMED; 294 smw->smw_tickler = (pid_t) -1; 295 smw->smw_refcnt = 0; 296 sysmon_wdog_count++; 297 LIST_INSERT_HEAD(&sysmon_wdog_list, smw, smw_list); 298 299 out: 300 simple_unlock(&sysmon_wdog_list_slock); 301 return (error); 302 } 303 304 /* 305 * sysmon_wdog_unregister: 306 * 307 * Unregister a watchdog device. 308 */ 309 void 310 sysmon_wdog_unregister(struct sysmon_wdog *smw) 311 { 312 313 simple_lock(&sysmon_wdog_list_slock); 314 sysmon_wdog_count--; 315 LIST_REMOVE(smw, smw_list); 316 simple_unlock(&sysmon_wdog_list_slock); 317 } 318 319 /* 320 * sysmon_wdog_find: 321 * 322 * Find a watchdog device. We increase the reference 323 * count on a match. 324 */ 325 struct sysmon_wdog * 326 sysmon_wdog_find(const char *name) 327 { 328 struct sysmon_wdog *smw; 329 330 simple_lock(&sysmon_wdog_list_slock); 331 332 for (smw = LIST_FIRST(&sysmon_wdog_list); smw != NULL; 333 smw = LIST_NEXT(smw, smw_list)) { 334 if (strcmp(smw->smw_name, name) == 0) 335 break; 336 } 337 338 if (smw != NULL) 339 smw->smw_refcnt++; 340 341 simple_unlock(&sysmon_wdog_list_slock); 342 return (smw); 343 } 344 345 /* 346 * sysmon_wdog_release: 347 * 348 * Release a watchdog device. 349 */ 350 void 351 sysmon_wdog_release(struct sysmon_wdog *smw) 352 { 353 354 simple_lock(&sysmon_wdog_list_slock); 355 KASSERT(smw->smw_refcnt != 0); 356 smw->smw_refcnt--; 357 simple_unlock(&sysmon_wdog_list_slock); 358 } 359 360 /* 361 * sysmon_wdog_setmode: 362 * 363 * Set the mode of a watchdog device. 364 */ 365 int 366 sysmon_wdog_setmode(struct sysmon_wdog *smw, int mode, u_int period) 367 { 368 u_int operiod = smw->smw_period; 369 int omode = smw->smw_mode; 370 int error = 0; 371 372 smw->smw_period = period; 373 smw->smw_mode = mode; 374 375 switch (mode & WDOG_MODE_MASK) { 376 case WDOG_MODE_DISARMED: 377 if (smw != sysmon_armed_wdog) { 378 error = EINVAL; 379 goto out; 380 } 381 break; 382 383 case WDOG_MODE_KTICKLE: 384 case WDOG_MODE_UTICKLE: 385 if (sysmon_armed_wdog != NULL) { 386 error = EBUSY; 387 goto out; 388 } 389 break; 390 391 default: 392 error = EINVAL; 393 goto out; 394 } 395 396 error = (*smw->smw_setmode)(smw); 397 398 out: 399 if (error) { 400 smw->smw_period = operiod; 401 smw->smw_mode = omode; 402 } else { 403 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 404 sysmon_armed_wdog = NULL; 405 smw->smw_tickler = (pid_t) -1; 406 smw->smw_refcnt--; 407 if ((omode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) 408 callout_stop(&sysmon_wdog_callout); 409 } else { 410 sysmon_armed_wdog = smw; 411 smw->smw_refcnt++; 412 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) { 413 callout_reset(&sysmon_wdog_callout, 414 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2, 415 sysmon_wdog_ktickle, NULL); 416 } 417 } 418 } 419 return (error); 420 } 421 422 /* 423 * sysmon_wdog_ktickle: 424 * 425 * Kernel watchdog tickle routine. 426 */ 427 void 428 sysmon_wdog_ktickle(void *arg) 429 { 430 struct sysmon_wdog *smw; 431 int s; 432 433 SYSMON_WDOG_LOCK(s); 434 if ((smw = sysmon_armed_wdog) != NULL) { 435 if ((*smw->smw_tickle)(smw) != 0) { 436 printf("WARNING: KERNEL TICKLE OF WATCHDOG %s " 437 "FAILED!\n", smw->smw_name); 438 /* 439 * ...we will probably reboot soon. 440 */ 441 } 442 callout_reset(&sysmon_wdog_callout, 443 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2, 444 sysmon_wdog_ktickle, NULL); 445 } 446 SYSMON_WDOG_UNLOCK(s); 447 } 448 449 /* 450 * sysmon_wdog_shutdown: 451 * 452 * Perform shutdown-time operations. 453 */ 454 void 455 sysmon_wdog_shutdown(void *arg) 456 { 457 struct sysmon_wdog *smw; 458 459 /* 460 * XXX Locking here? I don't think it's necessary. 461 */ 462 463 if ((smw = sysmon_armed_wdog) != NULL) { 464 if (sysmon_wdog_setmode(smw, WDOG_MODE_DISARMED, 465 smw->smw_period)) 466 printf("WARNING: FAILED TO SHUTDOWN WATCHDOG %s!\n", 467 smw->smw_name); 468 } 469 } 470