xref: /netbsd/sys/dev/sysmon/sysmon_wdog.c (revision 02b0acbc)
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@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
wdog_preinit(void)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
sysmon_wdog_init(void)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
sysmon_wdog_fini(void)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
sysmonopen_wdog(dev_t dev,int flag,int mode,struct lwp * l)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
sysmonclose_wdog(dev_t dev,int flag,int mode,struct lwp * l)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
sysmonioctl_wdog(dev_t dev,u_long cmd,void * data,int flag,struct lwp * l)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
sysmon_wdog_register(struct sysmon_wdog * smw)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
sysmon_wdog_unregister(struct sysmon_wdog * smw)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
sysmon_wdog_critpoll(void * arg)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 *
sysmon_wdog_find(const char * name)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
sysmon_wdog_release(struct sysmon_wdog * smw)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
sysmon_wdog_ref(struct sysmon_wdog * smw)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
sysmon_wdog_setmode(struct sysmon_wdog * smw,int mode,u_int period)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
sysmon_wdog_ktickle(void * arg)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
sysmon_wdog_shutdown(void * arg)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
sysmon_wdog_modcmd(modcmd_t cmd,void * arg)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