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
61static LIST_HEAD(, sysmon_wdog) sysmon_wdog_list =
62    LIST_HEAD_INITIALIZER(&sysmon_wdog_list);
63static int sysmon_wdog_count;
64static kmutex_t sysmon_wdog_list_mtx, sysmon_wdog_mtx;
65static kcondvar_t sysmon_wdog_cv;
66static struct sysmon_wdog *sysmon_armed_wdog;
67static callout_t sysmon_wdog_callout;
68static void *sysmon_wdog_sdhook;
69static void *sysmon_wdog_cphook;
70
71struct sysmon_wdog *sysmon_wdog_find(const char *);
72void	sysmon_wdog_release(struct sysmon_wdog *);
73int	sysmon_wdog_setmode(struct sysmon_wdog *, int, u_int);
74void	sysmon_wdog_ktickle(void *);
75void	sysmon_wdog_critpoll(void *);
76void	sysmon_wdog_shutdown(void *);
77void	sysmon_wdog_ref(struct sysmon_wdog *);
78
79static struct sysmon_opvec sysmon_wdog_opvec = {
80        sysmonopen_wdog, sysmonclose_wdog, sysmonioctl_wdog,
81        NULL, NULL, NULL
82};
83
84MODULE(MODULE_CLASS_DRIVER, sysmon_wdog, "sysmon");
85
86ONCE_DECL(once_wdog);
87
88static int
89wdog_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
100int
101sysmon_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
119int
120sysmon_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 */
146int
147sysmonopen_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 */
158int
159sysmonclose_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 */
196int
197sysmonioctl_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 */
322int
323sysmon_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 */
355int
356sysmon_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 */
379void
380sysmon_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 */
401struct sysmon_wdog *
402sysmon_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 */
425void
426sysmon_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
436void
437sysmon_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 */
449int
450sysmon_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 */
512void
513sysmon_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 */
538void
539sysmon_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
555static int
556sysmon_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