Home | History | Annotate | Line # | Download | only in sysmon
      1 /*	$NetBSD: sysmon_wdog.c,v 1.30 2021/12/31 11:05:41 riastradh Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2000 Zembu Labs, Inc.
      5  * All rights reserved.
      6  *
      7  * Author: Jason R. Thorpe <thorpej (at) 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.30 2021/12/31 11:05:41 riastradh 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/condvar.h>
     51 #include <sys/mutex.h>
     52 #include <sys/callout.h>
     53 #include <sys/kernel.h>
     54 #include <sys/systm.h>
     55 #include <sys/proc.h>
     56 #include <sys/module.h>
     57 #include <sys/once.h>
     58 
     59 #include <dev/sysmon/sysmonvar.h>
     60 
     61 static LIST_HEAD(, sysmon_wdog) sysmon_wdog_list =
     62     LIST_HEAD_INITIALIZER(&sysmon_wdog_list);
     63 static int sysmon_wdog_count;
     64 static kmutex_t sysmon_wdog_list_mtx, sysmon_wdog_mtx;
     65 static kcondvar_t sysmon_wdog_cv;
     66 static struct sysmon_wdog *sysmon_armed_wdog;
     67 static callout_t sysmon_wdog_callout;
     68 static void *sysmon_wdog_sdhook;
     69 static void *sysmon_wdog_cphook;
     70 
     71 struct sysmon_wdog *sysmon_wdog_find(const char *);
     72 void	sysmon_wdog_release(struct sysmon_wdog *);
     73 int	sysmon_wdog_setmode(struct sysmon_wdog *, int, u_int);
     74 void	sysmon_wdog_ktickle(void *);
     75 void	sysmon_wdog_critpoll(void *);
     76 void	sysmon_wdog_shutdown(void *);
     77 void	sysmon_wdog_ref(struct sysmon_wdog *);
     78 
     79 static struct sysmon_opvec sysmon_wdog_opvec = {
     80         sysmonopen_wdog, sysmonclose_wdog, sysmonioctl_wdog,
     81         NULL, NULL, NULL
     82 };
     83 
     84 MODULE(MODULE_CLASS_DRIVER, sysmon_wdog, "sysmon");
     85 
     86 ONCE_DECL(once_wdog);
     87 
     88 static int
     89 wdog_preinit(void)
     90 {
     91 
     92 	mutex_init(&sysmon_wdog_list_mtx, MUTEX_DEFAULT, IPL_NONE);
     93 	mutex_init(&sysmon_wdog_mtx, MUTEX_DEFAULT, IPL_SOFTCLOCK);
     94 	cv_init(&sysmon_wdog_cv, "wdogref");
     95 	callout_init(&sysmon_wdog_callout, 0);
     96 
     97 	return 0;
     98 }
     99 
    100 int
    101 sysmon_wdog_init(void)
    102 {
    103 	int error;
    104 
    105 	(void)RUN_ONCE(&once_wdog, wdog_preinit);
    106 
    107 	sysmon_wdog_sdhook = shutdownhook_establish(sysmon_wdog_shutdown, NULL);
    108 	if (sysmon_wdog_sdhook == NULL)
    109 		printf("WARNING: unable to register watchdog shutdown hook\n");
    110 	sysmon_wdog_cphook = critpollhook_establish(sysmon_wdog_critpoll, NULL);
    111 	if (sysmon_wdog_cphook == NULL)
    112 		printf("WARNING: unable to register watchdog critpoll hook\n");
    113 
    114 	error = sysmon_attach_minor(SYSMON_MINOR_WDOG, &sysmon_wdog_opvec);
    115 
    116 	return error;
    117 }
    118 
    119 int
    120 sysmon_wdog_fini(void)
    121 {
    122 	int error;
    123 
    124 	if ( ! LIST_EMPTY(&sysmon_wdog_list))
    125 		return EBUSY;
    126 
    127 	error = sysmon_attach_minor(SYSMON_MINOR_WDOG, NULL);
    128 
    129 	if (error == 0) {
    130 		callout_destroy(&sysmon_wdog_callout);
    131 		critpollhook_disestablish(sysmon_wdog_cphook);
    132 		shutdownhook_disestablish(sysmon_wdog_sdhook);
    133 		cv_destroy(&sysmon_wdog_cv);
    134 		mutex_destroy(&sysmon_wdog_mtx);
    135 		mutex_destroy(&sysmon_wdog_list_mtx);
    136 	}
    137 
    138 	return error;
    139 }
    140 
    141 /*
    142  * sysmonopen_wdog:
    143  *
    144  *	Open the system monitor device.
    145  */
    146 int
    147 sysmonopen_wdog(dev_t dev, int flag, int mode, struct lwp *l)
    148 {
    149 
    150 	return 0;
    151 }
    152 
    153 /*
    154  * sysmonclose_wdog:
    155  *
    156  *	Close the system monitor device.
    157  */
    158 int
    159 sysmonclose_wdog(dev_t dev, int flag, int mode, struct lwp *l)
    160 {
    161 	struct sysmon_wdog *smw;
    162 	int error = 0;
    163 
    164 	/*
    165 	 * If this is the last close, and there is a watchdog
    166 	 * running in UTICKLE mode, we need to disable it,
    167 	 * otherwise the system will reset in short order.
    168 	 *
    169 	 * XXX Maybe we should just go into KTICKLE mode?
    170 	 */
    171 	mutex_enter(&sysmon_wdog_mtx);
    172 	if ((smw = sysmon_armed_wdog) != NULL) {
    173 		if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_UTICKLE) {
    174 			error = sysmon_wdog_setmode(smw,
    175 			    WDOG_MODE_DISARMED, smw->smw_period);
    176 			if (error) {
    177 				printf("WARNING: UNABLE TO DISARM "
    178 				    "WATCHDOG %s ON CLOSE!\n",
    179 				    smw->smw_name);
    180 				/*
    181 				 * ...we will probably reboot soon.
    182 				 */
    183 			}
    184 		}
    185 	}
    186 	mutex_exit(&sysmon_wdog_mtx);
    187 
    188 	return error;
    189 }
    190 
    191 /*
    192  * sysmonioctl_wdog:
    193  *
    194  *	Perform a watchdog control request.
    195  */
    196 int
    197 sysmonioctl_wdog(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
    198 {
    199 	struct sysmon_wdog *smw;
    200 	int error = 0;
    201 
    202 	switch (cmd) {
    203 	case WDOGIOC_GMODE:
    204 	    {
    205 		struct wdog_mode *wm = (void *) data;
    206 
    207 		wm->wm_name[sizeof(wm->wm_name) - 1] = '\0';
    208 		smw = sysmon_wdog_find(wm->wm_name);
    209 		if (smw == NULL) {
    210 			error = ESRCH;
    211 			break;
    212 		}
    213 
    214 		wm->wm_mode = smw->smw_mode;
    215 		wm->wm_period = smw->smw_period;
    216 		sysmon_wdog_release(smw);
    217 		break;
    218 	    }
    219 
    220 	case WDOGIOC_SMODE:
    221 	    {
    222 		struct wdog_mode *wm = (void *) data;
    223 
    224 		if ((flag & FWRITE) == 0) {
    225 			error = EPERM;
    226 			break;
    227 		}
    228 
    229 		wm->wm_name[sizeof(wm->wm_name) - 1] = '\0';
    230 		smw = sysmon_wdog_find(wm->wm_name);
    231 		if (smw == NULL) {
    232 			error = ESRCH;
    233 			break;
    234 		}
    235 
    236 		if (wm->wm_mode & ~(WDOG_MODE_MASK|WDOG_FEATURE_MASK))
    237 			error = EINVAL;
    238 		else {
    239 			mutex_enter(&sysmon_wdog_mtx);
    240 			error = sysmon_wdog_setmode(smw, wm->wm_mode,
    241 			    wm->wm_period);
    242 			mutex_exit(&sysmon_wdog_mtx);
    243 		}
    244 
    245 		sysmon_wdog_release(smw);
    246 		break;
    247 	    }
    248 
    249 	case WDOGIOC_WHICH:
    250 	    {
    251 		struct wdog_mode *wm = (void *) data;
    252 
    253 		mutex_enter(&sysmon_wdog_mtx);
    254 		if ((smw = sysmon_armed_wdog) != NULL) {
    255 			strcpy(wm->wm_name, smw->smw_name);
    256 			wm->wm_mode = smw->smw_mode;
    257 			wm->wm_period = smw->smw_period;
    258 		} else
    259 			error = ESRCH;
    260 		mutex_exit(&sysmon_wdog_mtx);
    261 		break;
    262 	    }
    263 
    264 	case WDOGIOC_TICKLE:
    265 		if ((flag & FWRITE) == 0) {
    266 			error = EPERM;
    267 			break;
    268 		}
    269 
    270 		mutex_enter(&sysmon_wdog_mtx);
    271 		if ((smw = sysmon_armed_wdog) != NULL) {
    272 			error = (*smw->smw_tickle)(smw);
    273 			if (error == 0)
    274 				smw->smw_tickler = l->l_proc->p_pid;
    275 		} else
    276 			error = ESRCH;
    277 		mutex_exit(&sysmon_wdog_mtx);
    278 		break;
    279 
    280 	case WDOGIOC_GTICKLER:
    281 		if ((smw = sysmon_armed_wdog) != NULL)
    282 			*(pid_t *)data = smw->smw_tickler;
    283 		else
    284 			error = ESRCH;
    285 		break;
    286 
    287 	case WDOGIOC_GWDOGS:
    288 	    {
    289 		struct wdog_conf *wc = (void *) data;
    290 		char *cp;
    291 		int i;
    292 
    293 		mutex_enter(&sysmon_wdog_list_mtx);
    294 		if (wc->wc_names == NULL)
    295 			wc->wc_count = sysmon_wdog_count;
    296 		else {
    297 			for (i = 0, cp = wc->wc_names,
    298 			       smw = LIST_FIRST(&sysmon_wdog_list);
    299 			     i < sysmon_wdog_count && smw != NULL && error == 0;
    300 			     i++, cp += WDOG_NAMESIZE,
    301 			       smw = LIST_NEXT(smw, smw_list))
    302 				error = copyout(smw->smw_name, cp,
    303 				    strlen(smw->smw_name) + 1);
    304 			wc->wc_count = i;
    305 		}
    306 		mutex_exit(&sysmon_wdog_list_mtx);
    307 		break;
    308 	    }
    309 
    310 	default:
    311 		error = ENOTTY;
    312 	}
    313 
    314 	return error;
    315 }
    316 
    317 /*
    318  * sysmon_wdog_register:
    319  *
    320  *	Register a watchdog device.
    321  */
    322 int
    323 sysmon_wdog_register(struct sysmon_wdog *smw)
    324 {
    325 	struct sysmon_wdog *lsmw;
    326 	int error = 0;
    327 
    328 	(void)RUN_ONCE(&once_wdog, wdog_preinit);
    329 
    330 	mutex_enter(&sysmon_wdog_list_mtx);
    331 
    332 	LIST_FOREACH(lsmw, &sysmon_wdog_list, smw_list) {
    333 		if (strcmp(lsmw->smw_name, smw->smw_name) == 0) {
    334 			error = EEXIST;
    335 			goto out;
    336 		}
    337 	}
    338 
    339 	smw->smw_mode = WDOG_MODE_DISARMED;
    340 	smw->smw_tickler = (pid_t) -1;
    341 	smw->smw_refcnt = 0;
    342 	sysmon_wdog_count++;
    343 	LIST_INSERT_HEAD(&sysmon_wdog_list, smw, smw_list);
    344 
    345  out:
    346 	mutex_exit(&sysmon_wdog_list_mtx);
    347 	return error;
    348 }
    349 
    350 /*
    351  * sysmon_wdog_unregister:
    352  *
    353  *	Unregister a watchdog device.
    354  */
    355 int
    356 sysmon_wdog_unregister(struct sysmon_wdog *smw)
    357 {
    358 	int rc = 0;
    359 
    360 	mutex_enter(&sysmon_wdog_list_mtx);
    361 	while (smw->smw_refcnt > 0 && rc == 0) {
    362 		aprint_debug("%s: %d users remain\n", smw->smw_name,
    363 		    smw->smw_refcnt);
    364 		rc = cv_wait_sig(&sysmon_wdog_cv, &sysmon_wdog_list_mtx);
    365 	}
    366 	if (rc == 0) {
    367 		sysmon_wdog_count--;
    368 		LIST_REMOVE(smw, smw_list);
    369 	}
    370 	mutex_exit(&sysmon_wdog_list_mtx);
    371 	return rc;
    372 }
    373 
    374 /*
    375  * sysmon_wdog_critpoll:
    376  *
    377  *	Perform critical operations during long polling periods
    378  */
    379 void
    380 sysmon_wdog_critpoll(void *arg)
    381 {
    382 	struct sysmon_wdog *smw = sysmon_armed_wdog;
    383 
    384 	if (smw == NULL)
    385 		return;
    386 
    387 	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) {
    388 		if ((*smw->smw_tickle)(smw) != 0) {
    389 			printf("WARNING: KERNEL TICKLE OF WATCHDOG %s "
    390 			    "FAILED!\n", smw->smw_name);
    391 		}
    392 	}
    393 }
    394 
    395 /*
    396  * sysmon_wdog_find:
    397  *
    398  *	Find a watchdog device.  We increase the reference
    399  *	count on a match.
    400  */
    401 struct sysmon_wdog *
    402 sysmon_wdog_find(const char *name)
    403 {
    404 	struct sysmon_wdog *smw;
    405 
    406 	mutex_enter(&sysmon_wdog_list_mtx);
    407 
    408 	LIST_FOREACH(smw, &sysmon_wdog_list, smw_list) {
    409 		if (strcmp(smw->smw_name, name) == 0)
    410 			break;
    411 	}
    412 
    413 	if (smw != NULL)
    414 		smw->smw_refcnt++;
    415 
    416 	mutex_exit(&sysmon_wdog_list_mtx);
    417 	return smw;
    418 }
    419 
    420 /*
    421  * sysmon_wdog_release:
    422  *
    423  *	Release a watchdog device.
    424  */
    425 void
    426 sysmon_wdog_release(struct sysmon_wdog *smw)
    427 {
    428 
    429 	mutex_enter(&sysmon_wdog_list_mtx);
    430 	KASSERT(smw->smw_refcnt != 0);
    431 	smw->smw_refcnt--;
    432 	cv_signal(&sysmon_wdog_cv);
    433 	mutex_exit(&sysmon_wdog_list_mtx);
    434 }
    435 
    436 void
    437 sysmon_wdog_ref(struct sysmon_wdog *smw)
    438 {
    439 	mutex_enter(&sysmon_wdog_list_mtx);
    440 	smw->smw_refcnt++;
    441 	mutex_exit(&sysmon_wdog_list_mtx);
    442 }
    443 
    444 /*
    445  * sysmon_wdog_setmode:
    446  *
    447  *	Set the mode of a watchdog device.
    448  */
    449 int
    450 sysmon_wdog_setmode(struct sysmon_wdog *smw, int mode, u_int period)
    451 {
    452 	u_int operiod = smw->smw_period;
    453 	int omode = smw->smw_mode;
    454 	int error = 0;
    455 
    456 	smw->smw_period = period;
    457 	smw->smw_mode = mode;
    458 
    459 	switch (mode & WDOG_MODE_MASK) {
    460 	case WDOG_MODE_DISARMED:
    461 		if (smw != sysmon_armed_wdog) {
    462 			error = EINVAL;
    463 			goto out;
    464 		}
    465 		break;
    466 
    467 	case WDOG_MODE_KTICKLE:
    468 	case WDOG_MODE_UTICKLE:
    469 	case WDOG_MODE_ETICKLE:
    470 		if (sysmon_armed_wdog != NULL) {
    471 			error = EBUSY;
    472 			goto out;
    473 		}
    474 		break;
    475 
    476 	default:
    477 		error = EINVAL;
    478 		goto out;
    479 	}
    480 
    481 	error = (*smw->smw_setmode)(smw);
    482 
    483  out:
    484 	if (error) {
    485 		smw->smw_period = operiod;
    486 		smw->smw_mode = omode;
    487 	} else {
    488 		if ((mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
    489 			sysmon_armed_wdog = NULL;
    490 			smw->smw_tickler = (pid_t) -1;
    491 			sysmon_wdog_release(smw);
    492 			if ((omode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE)
    493 				callout_stop(&sysmon_wdog_callout);
    494 		} else {
    495 			sysmon_armed_wdog = smw;
    496 			sysmon_wdog_ref(smw);
    497 			if ((mode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) {
    498 				callout_reset(&sysmon_wdog_callout,
    499 				    WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2,
    500 				    sysmon_wdog_ktickle, NULL);
    501 			}
    502 		}
    503 	}
    504 	return error;
    505 }
    506 
    507 /*
    508  * sysmon_wdog_ktickle:
    509  *
    510  *	Kernel watchdog tickle routine.
    511  */
    512 void
    513 sysmon_wdog_ktickle(void *arg)
    514 {
    515 	struct sysmon_wdog *smw;
    516 
    517 	mutex_enter(&sysmon_wdog_mtx);
    518 	if ((smw = sysmon_armed_wdog) != NULL) {
    519 		if ((*smw->smw_tickle)(smw) != 0) {
    520 			printf("WARNING: KERNEL TICKLE OF WATCHDOG %s "
    521 			    "FAILED!\n", smw->smw_name);
    522 			/*
    523 			 * ...we will probably reboot soon.
    524 			 */
    525 		}
    526 		callout_reset(&sysmon_wdog_callout,
    527 		    WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2,
    528 		    sysmon_wdog_ktickle, NULL);
    529 	}
    530 	mutex_exit(&sysmon_wdog_mtx);
    531 }
    532 
    533 /*
    534  * sysmon_wdog_shutdown:
    535  *
    536  *	Perform shutdown-time operations.
    537  */
    538 void
    539 sysmon_wdog_shutdown(void *arg)
    540 {
    541 	struct sysmon_wdog *smw;
    542 
    543 	/*
    544 	 * XXX Locking here?  I don't think it's necessary.
    545 	 */
    546 
    547 	if ((smw = sysmon_armed_wdog) != NULL) {
    548 		if (sysmon_wdog_setmode(smw, WDOG_MODE_DISARMED,
    549 		    smw->smw_period))
    550 			printf("WARNING: FAILED TO SHUTDOWN WATCHDOG %s!\n",
    551 			    smw->smw_name);
    552 	}
    553 }
    554 
    555 static int
    556 sysmon_wdog_modcmd(modcmd_t cmd, void *arg)
    557 {
    558         int ret;
    559 
    560         switch (cmd) {
    561         case MODULE_CMD_INIT:
    562                 ret = sysmon_wdog_init();
    563                 break;
    564         case MODULE_CMD_FINI:
    565                 ret = sysmon_wdog_fini();
    566                 break;
    567         case MODULE_CMD_STAT:
    568         default:
    569                 ret = ENOTTY;
    570         }
    571 
    572         return ret;
    573 }
    574