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