1 /* $NetBSD: sysmon_wdog.c,v 1.1 2000/11/05 04:06:14 thorpej 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/param.h> 44 #include <sys/conf.h> 45 #include <sys/errno.h> 46 #include <sys/fcntl.h> 47 #include <sys/lock.h> 48 #include <sys/callout.h> 49 #include <sys/kernel.h> 50 #include <sys/systm.h> 51 #include <sys/proc.h> 52 53 #include <dev/sysmon/sysmonvar.h> 54 55 LIST_HEAD(, sysmon_wdog) sysmon_wdog_list = 56 LIST_HEAD_INITIALIZER(&sysmon_wdog_list); 57 int sysmon_wdog_count; 58 struct simplelock sysmon_wdog_list_slock = SIMPLELOCK_INITIALIZER; 59 60 struct simplelock sysmon_wdog_slock = SIMPLELOCK_INITIALIZER; 61 struct sysmon_wdog *sysmon_armed_wdog; 62 struct callout sysmon_wdog_callout = CALLOUT_INITIALIZER; 63 void *sysmon_wdog_sdhook; 64 65 #define SYSMON_WDOG_LOCK(s) \ 66 do { \ 67 s = splsoftclock(); \ 68 simple_lock(&sysmon_wdog_slock); \ 69 } while (0) 70 71 #define SYSMON_WDOG_UNLOCK(s) \ 72 do { \ 73 simple_unlock(&sysmon_wdog_slock); \ 74 splx(s); \ 75 } while (0) 76 77 struct sysmon_wdog *sysmon_wdog_find(const char *); 78 void sysmon_wdog_release(struct sysmon_wdog *); 79 int sysmon_wdog_setmode(struct sysmon_wdog *, int, u_int); 80 void sysmon_wdog_ktickle(void *); 81 void sysmon_wdog_shutdown(void *); 82 83 #define SYSMON_MINOR_ENVSYS 0 84 #define SYSMON_MINOR_WDOG 1 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 omode, s, error; 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 ((omode = 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 *(pid_t *)data = smw->smw_tickler; 237 break; 238 239 case WDOGIOC_GWDOGS: 240 { 241 struct wdog_conf *wc = (void *) data; 242 char *cp; 243 int i; 244 245 simple_lock(&sysmon_wdog_list_slock); 246 if (wc->wc_names == NULL) 247 wc->wc_count = sysmon_wdog_count; 248 else { 249 for (i = 0, cp = wc->wc_names, 250 smw = LIST_FIRST(&sysmon_wdog_list); 251 i < sysmon_wdog_count && smw != NULL && error == 0; 252 i++, cp += WDOG_NAMESIZE, 253 smw = LIST_NEXT(smw, smw_list)) 254 error = copyout(smw->smw_name, cp, 255 strlen(smw->smw_name) + 1); 256 wc->wc_count = i; 257 } 258 simple_unlock(&sysmon_wdog_list_slock); 259 break; 260 } 261 262 default: 263 error = ENOTTY; 264 } 265 266 return (error); 267 } 268 269 /* 270 * sysmon_wdog_register: 271 * 272 * Register a watchdog device. 273 */ 274 int 275 sysmon_wdog_register(struct sysmon_wdog *smw) 276 { 277 struct sysmon_wdog *lsmw; 278 int error = 0; 279 280 simple_lock(&sysmon_wdog_list_slock); 281 282 for (lsmw = LIST_FIRST(&sysmon_wdog_list); lsmw != NULL; 283 lsmw = LIST_NEXT(lsmw, 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 simple_unlock(&sysmon_wdog_list_slock); 298 return (error); 299 } 300 301 /* 302 * sysmon_wdog_unregister: 303 * 304 * Unregister a watchdog device. 305 */ 306 void 307 sysmon_wdog_unregister(struct sysmon_wdog *smw) 308 { 309 310 simple_lock(&sysmon_wdog_list_slock); 311 sysmon_wdog_count--; 312 LIST_REMOVE(smw, smw_list); 313 simple_unlock(&sysmon_wdog_list_slock); 314 } 315 316 /* 317 * sysmon_wdog_find: 318 * 319 * Find a watchdog device. We increase the reference 320 * count on a match. 321 */ 322 struct sysmon_wdog * 323 sysmon_wdog_find(const char *name) 324 { 325 struct sysmon_wdog *smw; 326 327 simple_lock(&sysmon_wdog_list_slock); 328 329 for (smw = LIST_FIRST(&sysmon_wdog_list); smw != NULL; 330 smw = LIST_NEXT(smw, smw_list)) { 331 if (strcmp(smw->smw_name, name) == 0) 332 break; 333 } 334 335 if (smw != NULL) 336 smw->smw_refcnt++; 337 338 simple_unlock(&sysmon_wdog_list_slock); 339 return (smw); 340 } 341 342 /* 343 * sysmon_wdog_release: 344 * 345 * Release a watchdog device. 346 */ 347 void 348 sysmon_wdog_release(struct sysmon_wdog *smw) 349 { 350 351 simple_lock(&sysmon_wdog_list_slock); 352 KASSERT(smw->smw_refcnt != 0); 353 smw->smw_refcnt--; 354 simple_unlock(&sysmon_wdog_list_slock); 355 } 356 357 /* 358 * sysmon_wdog_setmode: 359 * 360 * Set the mode of a watchdog device. 361 */ 362 int 363 sysmon_wdog_setmode(struct sysmon_wdog *smw, int mode, u_int period) 364 { 365 u_int operiod = smw->smw_period; 366 int omode = smw->smw_mode; 367 int error = 0; 368 369 smw->smw_period = period; 370 smw->smw_mode = mode; 371 372 switch (mode & WDOG_MODE_MASK) { 373 case WDOG_MODE_DISARMED: 374 if (smw != sysmon_armed_wdog) { 375 error = EINVAL; 376 goto out; 377 } 378 break; 379 380 case WDOG_MODE_KTICKLE: 381 case WDOG_MODE_UTICKLE: 382 if (sysmon_armed_wdog != NULL) { 383 error = EBUSY; 384 goto out; 385 } 386 break; 387 388 default: 389 error = EINVAL; 390 goto out; 391 } 392 393 error = (*smw->smw_setmode)(smw); 394 395 out: 396 if (error) { 397 smw->smw_period = operiod; 398 smw->smw_mode = omode; 399 } else { 400 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 401 smw->smw_tickler = (pid_t) -1; 402 smw->smw_refcnt--; 403 if ((omode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) 404 callout_stop(&sysmon_wdog_callout); 405 } else { 406 sysmon_armed_wdog = smw; 407 smw->smw_refcnt++; 408 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) { 409 callout_reset(&sysmon_wdog_callout, 410 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2, 411 sysmon_wdog_ktickle, NULL); 412 } 413 } 414 } 415 return (error); 416 } 417 418 /* 419 * sysmon_wdog_ktickle: 420 * 421 * Kernel watchdog tickle routine. 422 */ 423 void 424 sysmon_wdog_ktickle(void *arg) 425 { 426 struct sysmon_wdog *smw; 427 int s; 428 429 SYSMON_WDOG_LOCK(s); 430 if ((smw = sysmon_armed_wdog) != NULL) { 431 if ((*smw->smw_tickle)(smw) != 0) { 432 printf("WARNING: KERNEL TICKLE OF WATCHDOG %s " 433 "FAILED!\n", smw->smw_name); 434 /* 435 * ...we will probably reboot soon. 436 */ 437 } 438 callout_reset(&sysmon_wdog_callout, 439 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2, 440 sysmon_wdog_ktickle, NULL); 441 } 442 SYSMON_WDOG_UNLOCK(s); 443 } 444 445 /* 446 * sysmon_wdog_shutdown: 447 * 448 * Perform shutdown-time operations. 449 */ 450 void 451 sysmon_wdog_shutdown(void *arg) 452 { 453 struct sysmon_wdog *smw; 454 455 /* 456 * XXX Locking here? I don't think it's necessary. 457 */ 458 459 if ((smw = sysmon_armed_wdog) != NULL) { 460 if (sysmon_wdog_setmode(smw, WDOG_MODE_DISARMED, 461 smw->smw_period)) 462 printf("WARNING: FAILED TO SHUTDOWN WATCHDOG %s!\n", 463 smw->smw_name); 464 } 465 } 466