sysmon_wdog.c revision 1.19 1 /* $NetBSD: sysmon_wdog.c,v 1.19 2007/07/09 21:01:24 ad 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.19 2007/07/09 21:01:24 ad 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/lock.h>
51 #include <sys/callout.h>
52 #include <sys/kernel.h>
53 #include <sys/systm.h>
54 #include <sys/proc.h>
55
56 #include <dev/sysmon/sysmonvar.h>
57
58 LIST_HEAD(, sysmon_wdog) sysmon_wdog_list =
59 LIST_HEAD_INITIALIZER(&sysmon_wdog_list);
60 int sysmon_wdog_count;
61 struct simplelock sysmon_wdog_list_slock = SIMPLELOCK_INITIALIZER;
62
63 struct simplelock sysmon_wdog_slock = SIMPLELOCK_INITIALIZER;
64 struct sysmon_wdog *sysmon_armed_wdog;
65 callout_t sysmon_wdog_callout;
66 void *sysmon_wdog_sdhook;
67
68 #define SYSMON_WDOG_LOCK(s) \
69 do { \
70 s = splsoftclock(); \
71 simple_lock(&sysmon_wdog_slock); \
72 } while (0)
73
74 #define SYSMON_WDOG_UNLOCK(s) \
75 do { \
76 simple_unlock(&sysmon_wdog_slock); \
77 splx(s); \
78 } while (0)
79
80 struct sysmon_wdog *sysmon_wdog_find(const char *);
81 void sysmon_wdog_release(struct sysmon_wdog *);
82 int sysmon_wdog_setmode(struct sysmon_wdog *, int, u_int);
83 void sysmon_wdog_ktickle(void *);
84 void sysmon_wdog_shutdown(void *);
85
86 /*
87 * sysmonopen_wdog:
88 *
89 * Open the system monitor device.
90 */
91 int
92 sysmonopen_wdog(dev_t dev, int flag, int mode,
93 struct lwp *l)
94 {
95
96 simple_lock(&sysmon_wdog_list_slock);
97 if (sysmon_wdog_sdhook == NULL) {
98 sysmon_wdog_sdhook =
99 shutdownhook_establish(sysmon_wdog_shutdown, NULL);
100 if (sysmon_wdog_sdhook == NULL)
101 printf("WARNING: unable to register watchdog "
102 "shutdown hook\n");
103 callout_init(&sysmon_wdog_callout, 0);
104 }
105 simple_unlock(&sysmon_wdog_list_slock);
106
107 return (0);
108 }
109
110 /*
111 * sysmonclose_wdog:
112 *
113 * Close the system monitor device.
114 */
115 int
116 sysmonclose_wdog(dev_t dev, int flag, int mode,
117 struct lwp *l)
118 {
119 struct sysmon_wdog *smw;
120 int s, error = 0;
121
122 /*
123 * If this is the last close, and there is a watchdog
124 * running in UTICKLE mode, we need to disable it,
125 * otherwise the system will reset in short order.
126 *
127 * XXX Maybe we should just go into KTICKLE mode?
128 */
129 SYSMON_WDOG_LOCK(s);
130 if ((smw = sysmon_armed_wdog) != NULL) {
131 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_UTICKLE) {
132 error = sysmon_wdog_setmode(smw,
133 WDOG_MODE_DISARMED, smw->smw_period);
134 if (error) {
135 printf("WARNING: UNABLE TO DISARM "
136 "WATCHDOG %s ON CLOSE!\n",
137 smw->smw_name);
138 /*
139 * ...we will probably reboot soon.
140 */
141 }
142 }
143 }
144 SYSMON_WDOG_UNLOCK(s);
145
146 return (error);
147 }
148
149 /*
150 * sysmonioctl_wdog:
151 *
152 * Perform a watchdog control request.
153 */
154 int
155 sysmonioctl_wdog(dev_t dev, u_long cmd, void *data, int flag,
156 struct lwp *l)
157 {
158 struct sysmon_wdog *smw;
159 int s, error = 0;
160
161 switch (cmd) {
162 case WDOGIOC_GMODE:
163 {
164 struct wdog_mode *wm = (void *) data;
165
166 wm->wm_name[sizeof(wm->wm_name) - 1] = '\0';
167 smw = sysmon_wdog_find(wm->wm_name);
168 if (smw == NULL) {
169 error = ESRCH;
170 break;
171 }
172
173 wm->wm_mode = smw->smw_mode;
174 wm->wm_period = smw->smw_period;
175 sysmon_wdog_release(smw);
176 break;
177 }
178
179 case WDOGIOC_SMODE:
180 {
181 struct wdog_mode *wm = (void *) data;
182
183 if ((flag & FWRITE) == 0) {
184 error = EPERM;
185 break;
186 }
187
188 wm->wm_name[sizeof(wm->wm_name) - 1] = '\0';
189 smw = sysmon_wdog_find(wm->wm_name);
190 if (smw == NULL) {
191 error = ESRCH;
192 break;
193 }
194
195 if (wm->wm_mode & ~(WDOG_MODE_MASK|WDOG_FEATURE_MASK))
196 error = EINVAL;
197 else {
198 SYSMON_WDOG_LOCK(s);
199 error = sysmon_wdog_setmode(smw, wm->wm_mode,
200 wm->wm_period);
201 SYSMON_WDOG_UNLOCK(s);
202 }
203
204 sysmon_wdog_release(smw);
205 break;
206 }
207
208 case WDOGIOC_WHICH:
209 {
210 struct wdog_mode *wm = (void *) data;
211
212 SYSMON_WDOG_LOCK(s);
213 if ((smw = sysmon_armed_wdog) != NULL) {
214 strcpy(wm->wm_name, smw->smw_name);
215 wm->wm_mode = smw->smw_mode;
216 wm->wm_period = smw->smw_period;
217 } else
218 error = ESRCH;
219 SYSMON_WDOG_UNLOCK(s);
220 break;
221 }
222
223 case WDOGIOC_TICKLE:
224 if ((flag & FWRITE) == 0) {
225 error = EPERM;
226 break;
227 }
228
229 SYSMON_WDOG_LOCK(s);
230 if ((smw = sysmon_armed_wdog) != NULL) {
231 error = (*smw->smw_tickle)(smw);
232 if (error == 0)
233 smw->smw_tickler = l->l_proc->p_pid;
234 } else
235 error = ESRCH;
236 SYSMON_WDOG_UNLOCK(s);
237 break;
238
239 case WDOGIOC_GTICKLER:
240 if ((smw = sysmon_armed_wdog) != NULL)
241 *(pid_t *)data = smw->smw_tickler;
242 else
243 error = ESRCH;
244 break;
245
246 case WDOGIOC_GWDOGS:
247 {
248 struct wdog_conf *wc = (void *) data;
249 char *cp;
250 int i;
251
252 simple_lock(&sysmon_wdog_list_slock);
253 if (wc->wc_names == NULL)
254 wc->wc_count = sysmon_wdog_count;
255 else {
256 for (i = 0, cp = wc->wc_names,
257 smw = LIST_FIRST(&sysmon_wdog_list);
258 i < sysmon_wdog_count && smw != NULL && error == 0;
259 i++, cp += WDOG_NAMESIZE,
260 smw = LIST_NEXT(smw, smw_list))
261 error = copyout(smw->smw_name, cp,
262 strlen(smw->smw_name) + 1);
263 wc->wc_count = i;
264 }
265 simple_unlock(&sysmon_wdog_list_slock);
266 break;
267 }
268
269 default:
270 error = ENOTTY;
271 }
272
273 return (error);
274 }
275
276 /*
277 * sysmon_wdog_register:
278 *
279 * Register a watchdog device.
280 */
281 int
282 sysmon_wdog_register(struct sysmon_wdog *smw)
283 {
284 struct sysmon_wdog *lsmw;
285 int error = 0;
286
287 simple_lock(&sysmon_wdog_list_slock);
288
289 for (lsmw = LIST_FIRST(&sysmon_wdog_list); lsmw != NULL;
290 lsmw = LIST_NEXT(lsmw, smw_list)) {
291 if (strcmp(lsmw->smw_name, smw->smw_name) == 0) {
292 error = EEXIST;
293 goto out;
294 }
295 }
296
297 smw->smw_mode = WDOG_MODE_DISARMED;
298 smw->smw_tickler = (pid_t) -1;
299 smw->smw_refcnt = 0;
300 sysmon_wdog_count++;
301 LIST_INSERT_HEAD(&sysmon_wdog_list, smw, smw_list);
302
303 out:
304 simple_unlock(&sysmon_wdog_list_slock);
305 return (error);
306 }
307
308 /*
309 * sysmon_wdog_unregister:
310 *
311 * Unregister a watchdog device.
312 */
313 void
314 sysmon_wdog_unregister(struct sysmon_wdog *smw)
315 {
316
317 simple_lock(&sysmon_wdog_list_slock);
318 sysmon_wdog_count--;
319 LIST_REMOVE(smw, smw_list);
320 simple_unlock(&sysmon_wdog_list_slock);
321 }
322
323 /*
324 * sysmon_wdog_find:
325 *
326 * Find a watchdog device. We increase the reference
327 * count on a match.
328 */
329 struct sysmon_wdog *
330 sysmon_wdog_find(const char *name)
331 {
332 struct sysmon_wdog *smw;
333
334 simple_lock(&sysmon_wdog_list_slock);
335
336 for (smw = LIST_FIRST(&sysmon_wdog_list); smw != NULL;
337 smw = LIST_NEXT(smw, smw_list)) {
338 if (strcmp(smw->smw_name, name) == 0)
339 break;
340 }
341
342 if (smw != NULL)
343 smw->smw_refcnt++;
344
345 simple_unlock(&sysmon_wdog_list_slock);
346 return (smw);
347 }
348
349 /*
350 * sysmon_wdog_release:
351 *
352 * Release a watchdog device.
353 */
354 void
355 sysmon_wdog_release(struct sysmon_wdog *smw)
356 {
357
358 simple_lock(&sysmon_wdog_list_slock);
359 KASSERT(smw->smw_refcnt != 0);
360 smw->smw_refcnt--;
361 simple_unlock(&sysmon_wdog_list_slock);
362 }
363
364 /*
365 * sysmon_wdog_setmode:
366 *
367 * Set the mode of a watchdog device.
368 */
369 int
370 sysmon_wdog_setmode(struct sysmon_wdog *smw, int mode, u_int period)
371 {
372 u_int operiod = smw->smw_period;
373 int omode = smw->smw_mode;
374 int error = 0;
375
376 smw->smw_period = period;
377 smw->smw_mode = mode;
378
379 switch (mode & WDOG_MODE_MASK) {
380 case WDOG_MODE_DISARMED:
381 if (smw != sysmon_armed_wdog) {
382 error = EINVAL;
383 goto out;
384 }
385 break;
386
387 case WDOG_MODE_KTICKLE:
388 case WDOG_MODE_UTICKLE:
389 case WDOG_MODE_ETICKLE:
390 if (sysmon_armed_wdog != NULL) {
391 error = EBUSY;
392 goto out;
393 }
394 break;
395
396 default:
397 error = EINVAL;
398 goto out;
399 }
400
401 error = (*smw->smw_setmode)(smw);
402
403 out:
404 if (error) {
405 smw->smw_period = operiod;
406 smw->smw_mode = omode;
407 } else {
408 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
409 sysmon_armed_wdog = NULL;
410 smw->smw_tickler = (pid_t) -1;
411 smw->smw_refcnt--;
412 if ((omode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE)
413 callout_stop(&sysmon_wdog_callout);
414 } else {
415 sysmon_armed_wdog = smw;
416 smw->smw_refcnt++;
417 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) {
418 callout_reset(&sysmon_wdog_callout,
419 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2,
420 sysmon_wdog_ktickle, NULL);
421 }
422 }
423 }
424 return (error);
425 }
426
427 /*
428 * sysmon_wdog_ktickle:
429 *
430 * Kernel watchdog tickle routine.
431 */
432 void
433 sysmon_wdog_ktickle(void *arg)
434 {
435 struct sysmon_wdog *smw;
436 int s;
437
438 SYSMON_WDOG_LOCK(s);
439 if ((smw = sysmon_armed_wdog) != NULL) {
440 if ((*smw->smw_tickle)(smw) != 0) {
441 printf("WARNING: KERNEL TICKLE OF WATCHDOG %s "
442 "FAILED!\n", smw->smw_name);
443 /*
444 * ...we will probably reboot soon.
445 */
446 }
447 callout_reset(&sysmon_wdog_callout,
448 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2,
449 sysmon_wdog_ktickle, NULL);
450 }
451 SYSMON_WDOG_UNLOCK(s);
452 }
453
454 /*
455 * sysmon_wdog_shutdown:
456 *
457 * Perform shutdown-time operations.
458 */
459 void
460 sysmon_wdog_shutdown(void *arg)
461 {
462 struct sysmon_wdog *smw;
463
464 /*
465 * XXX Locking here? I don't think it's necessary.
466 */
467
468 if ((smw = sysmon_armed_wdog) != NULL) {
469 if (sysmon_wdog_setmode(smw, WDOG_MODE_DISARMED,
470 smw->smw_period))
471 printf("WARNING: FAILED TO SHUTDOWN WATCHDOG %s!\n",
472 smw->smw_name);
473 }
474 }
475