1 /* $NetBSD: sysmon_wdog.c,v 1.9 2003/10/30 01:58:18 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.9 2003/10/30 01:58:18 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 /* 87 * sysmonopen_wdog: 88 * 89 * Open the system monitor device. 90 */ 91 int 92 sysmonopen_wdog(dev_t dev, int flag, int mode, struct proc *p) 93 { 94 95 simple_lock(&sysmon_wdog_list_slock); 96 if (sysmon_wdog_sdhook == NULL) { 97 sysmon_wdog_sdhook = 98 shutdownhook_establish(sysmon_wdog_shutdown, NULL); 99 if (sysmon_wdog_sdhook == NULL) 100 printf("WARNING: unable to register watchdog " 101 "shutdown hook\n"); 102 } 103 simple_unlock(&sysmon_wdog_list_slock); 104 105 return (0); 106 } 107 108 /* 109 * sysmonclose_wdog: 110 * 111 * Close the system monitor device. 112 */ 113 int 114 sysmonclose_wdog(dev_t dev, int flag, int mode, struct proc *p) 115 { 116 struct sysmon_wdog *smw; 117 int s, error = 0; 118 119 /* 120 * If this is the last close, and there is a watchdog 121 * running in UTICKLE mode, we need to disable it, 122 * otherwise the system will reset in short order. 123 * 124 * XXX Maybe we should just go into KTICKLE mode? 125 */ 126 SYSMON_WDOG_LOCK(s); 127 if ((smw = sysmon_armed_wdog) != NULL) { 128 if (smw->smw_mode == WDOG_MODE_UTICKLE) { 129 error = sysmon_wdog_setmode(smw, 130 WDOG_MODE_DISARMED, smw->smw_period); 131 if (error) { 132 printf("WARNING: UNABLE TO DISARM " 133 "WATCHDOG %s ON CLOSE!\n", 134 smw->smw_name); 135 /* 136 * ...we will probably reboot soon. 137 */ 138 } 139 } 140 } 141 SYSMON_WDOG_UNLOCK(s); 142 143 return (error); 144 } 145 146 /* 147 * sysmonioctl_wdog: 148 * 149 * Perform a watchdog control request. 150 */ 151 int 152 sysmonioctl_wdog(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) 153 { 154 struct sysmon_wdog *smw; 155 int s, error = 0; 156 157 switch (cmd) { 158 case WDOGIOC_GMODE: 159 { 160 struct wdog_mode *wm = (void *) data; 161 162 wm->wm_name[sizeof(wm->wm_name) - 1] = '\0'; 163 smw = sysmon_wdog_find(wm->wm_name); 164 if (smw == NULL) { 165 error = ESRCH; 166 break; 167 } 168 169 wm->wm_mode = smw->smw_mode; 170 wm->wm_period = smw->smw_period; 171 sysmon_wdog_release(smw); 172 break; 173 } 174 175 case WDOGIOC_SMODE: 176 { 177 struct wdog_mode *wm = (void *) data; 178 179 if ((flag & FWRITE) == 0) { 180 error = EPERM; 181 break; 182 } 183 184 wm->wm_name[sizeof(wm->wm_name) - 1] = '\0'; 185 smw = sysmon_wdog_find(wm->wm_name); 186 if (smw == NULL) { 187 error = ESRCH; 188 break; 189 } 190 191 if (wm->wm_mode & ~(WDOG_MODE_MASK|WDOG_FEATURE_MASK)) 192 error = EINVAL; 193 else { 194 SYSMON_WDOG_LOCK(s); 195 error = sysmon_wdog_setmode(smw, wm->wm_mode, 196 wm->wm_period); 197 SYSMON_WDOG_UNLOCK(s); 198 } 199 200 sysmon_wdog_release(smw); 201 break; 202 } 203 204 case WDOGIOC_WHICH: 205 { 206 struct wdog_mode *wm = (void *) data; 207 208 SYSMON_WDOG_LOCK(s); 209 if ((smw = sysmon_armed_wdog) != NULL) { 210 strcpy(wm->wm_name, smw->smw_name); 211 wm->wm_mode = smw->smw_mode; 212 wm->wm_period = smw->smw_period; 213 } else 214 error = ESRCH; 215 SYSMON_WDOG_UNLOCK(s); 216 break; 217 } 218 219 case WDOGIOC_TICKLE: 220 if ((flag & FWRITE) == 0) { 221 error = EPERM; 222 break; 223 } 224 225 SYSMON_WDOG_LOCK(s); 226 if ((smw = sysmon_armed_wdog) != NULL) { 227 error = (*smw->smw_tickle)(smw); 228 if (error == 0) 229 smw->smw_tickler = p->p_pid; 230 } else 231 error = ESRCH; 232 SYSMON_WDOG_UNLOCK(s); 233 break; 234 235 case WDOGIOC_GTICKLER: 236 if ((smw = sysmon_armed_wdog) != NULL) 237 *(pid_t *)data = smw->smw_tickler; 238 else 239 error = ESRCH; 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