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