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