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