sysmon.c revision 1.3 1 /* $NetBSD: sysmon.c,v 1.3 2000/11/04 18:37:19 thorpej 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 * Clearing house for system monitoring hardware. This pseudo-device
38 * is a place where hardware monitors such as the LM78 and VIA 82C686A
39 * (or even ACPI, eventually) can register themselves to provide
40 * backplane fan and temperature information, etc. It also provides
41 * a place to register watchdog timers, and provides an abstract
42 * interface to them.
43 */
44
45 #include <sys/param.h>
46 #include <sys/conf.h>
47 #include <sys/errno.h>
48 #include <sys/fcntl.h>
49 #include <sys/lock.h>
50 #include <sys/callout.h>
51 #include <sys/kernel.h>
52 #include <sys/systm.h>
53 #include <sys/proc.h>
54
55 #include <dev/sysmon/sysmonvar.h>
56
57 /*
58 * We run at ENVSYS version 1.
59 */
60 #define SYSMON_ENVSYS_VERSION (1 * 1000)
61
62 struct lock sysmon_envsys_lock;
63
64 LIST_HEAD(, sysmon_envsys) sysmon_envsys_list =
65 LIST_HEAD_INITIALIZER(&sysmon_envsys_list);
66 struct simplelock sysmon_envsys_list_slock = SIMPLELOCK_INITIALIZER;
67 u_int sysmon_envsys_next_sensor_index;
68
69 int sysmon_envsys_initialized;
70 struct simplelock sysmon_envsys_initialized_slock = SIMPLELOCK_INITIALIZER;
71
72 cdev_decl(sysmon);
73
74 void sysmon_envsys_init(void);
75
76 int sysmonioctl_envsys(dev_t, u_long, caddr_t, int, struct proc *);
77 int sysmonioctl_wdog(dev_t, u_long, caddr_t, int, struct proc *);
78
79 struct sysmon_envsys *sysmon_envsys_find(u_int);
80 void sysmon_envsys_release(struct sysmon_envsys *);
81
82 LIST_HEAD(, sysmon_wdog) sysmon_wdog_list =
83 LIST_HEAD_INITIALIZER(&sysmon_wdog_list);
84 int sysmon_wdog_count;
85 struct simplelock sysmon_wdog_list_slock = SIMPLELOCK_INITIALIZER;
86
87 struct simplelock sysmon_wdog_slock = SIMPLELOCK_INITIALIZER;
88 struct sysmon_wdog *sysmon_armed_wdog;
89 struct callout sysmon_wdog_callout = CALLOUT_INITIALIZER;
90 void *sysmon_wdog_sdhook;
91
92 #define SYSMON_WDOG_LOCK(s) \
93 do { \
94 s = splsoftclock(); \
95 simple_lock(&sysmon_wdog_slock); \
96 } while (0)
97
98 #define SYSMON_WDOG_UNLOCK(s) \
99 do { \
100 simple_unlock(&sysmon_wdog_slock); \
101 splx(s); \
102 } while (0)
103
104 struct sysmon_wdog *sysmon_wdog_find(const char *);
105 void sysmon_wdog_release(struct sysmon_wdog *);
106 int sysmon_wdog_setmode(struct sysmon_wdog *, int, u_int);
107 void sysmon_wdog_ktickle(void *);
108 void sysmon_wdog_shutdown(void *);
109
110 #define SYSMON_MINOR_ENVSYS 0
111 #define SYSMON_MINOR_WDOG 1
112
113 /*
114 * sysmon_envsys_init:
115 *
116 * Initialize the system monitor.
117 */
118 void
119 sysmon_envsys_init(void)
120 {
121
122 lockinit(&sysmon_envsys_lock, PWAIT|PCATCH, "smenv", 0, 0);
123 sysmon_envsys_initialized = 1;
124 }
125
126 /*
127 * sysmonopen:
128 *
129 * Open the system monitor device.
130 */
131 int
132 sysmonopen(dev_t dev, int flag, int mode, struct proc *p)
133 {
134 int error = 0;
135
136 switch (minor(dev)) {
137 case SYSMON_MINOR_ENVSYS:
138 simple_lock(&sysmon_envsys_initialized_slock);
139 if (sysmon_envsys_initialized == 0)
140 sysmon_envsys_init();
141
142 error = lockmgr(&sysmon_envsys_lock,
143 LK_EXCLUSIVE | LK_INTERLOCK |
144 ((flag & O_NONBLOCK) ? LK_NOWAIT : 0),
145 &sysmon_envsys_initialized_slock);
146 break;
147
148 case SYSMON_MINOR_WDOG:
149 simple_lock(&sysmon_wdog_list_slock);
150 if (sysmon_wdog_sdhook == NULL) {
151 sysmon_wdog_sdhook =
152 shutdownhook_establish(sysmon_wdog_shutdown, NULL);
153 if (sysmon_wdog_sdhook == NULL)
154 printf("WARNING: unable to register watchdog "
155 "shutdown hook\n");
156 }
157 simple_unlock(&sysmon_wdog_list_slock);
158 break;
159
160 default:
161 error = ENODEV;
162 }
163
164 return (error);
165 }
166
167 /*
168 * sysmonclose:
169 *
170 * Close the system monitor device.
171 */
172 int
173 sysmonclose(dev_t dev, int flag, int mode, struct proc *p)
174 {
175 int error = 0;
176
177 switch (minor(dev)) {
178 case SYSMON_MINOR_ENVSYS:
179 (void) lockmgr(&sysmon_envsys_lock, LK_RELEASE, NULL);
180 break;
181
182 case SYSMON_MINOR_WDOG:
183 {
184 struct sysmon_wdog *smw;
185 int omode, s;
186
187 /*
188 * If this is the last close, and there is a watchdog
189 * running in UTICKLE mode, we need to disable it,
190 * otherwise the system will reset in short order.
191 *
192 * XXX Maybe we should just go into KTICKLE mode?
193 */
194 SYSMON_WDOG_LOCK(s);
195 if ((smw = sysmon_armed_wdog) != NULL) {
196 if ((omode = smw->smw_mode) == WDOG_MODE_UTICKLE) {
197 error = sysmon_wdog_setmode(smw,
198 WDOG_MODE_DISARMED, smw->smw_period);
199 if (error) {
200 printf("WARNING: UNABLE TO DISARM "
201 "WATCHDOG %s ON CLOSE!\n",
202 smw->smw_name);
203 /*
204 * ...we will probably reboot soon.
205 */
206 }
207 }
208 }
209 SYSMON_WDOG_UNLOCK(s);
210 break;
211 }
212
213 default:
214 error = ENODEV;
215 }
216
217 return (error);
218 }
219
220 /*
221 * sysmonioctl:
222 *
223 * Perform a control request.
224 */
225 int
226 sysmonioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
227 {
228 int error;
229
230 switch (minor(dev)) {
231 case SYSMON_MINOR_ENVSYS:
232 error = sysmonioctl_envsys(dev, cmd, data, flag, p);
233 break;
234
235 case SYSMON_MINOR_WDOG:
236 error = sysmonioctl_wdog(dev, cmd, data, flag, p);
237 break;
238
239 default:
240 error = ENODEV;
241 }
242
243 return (error);
244 }
245
246 /*
247 * sysmonioctl_envsys:
248 *
249 * Perform an envsys control request.
250 */
251 int
252 sysmonioctl_envsys(dev_t dev, u_long cmd, caddr_t data, int flag,
253 struct proc *p)
254 {
255 struct sysmon_envsys *sme;
256 int error = 0;
257 u_int oidx;
258
259 switch (cmd) {
260 /*
261 * For ENVSYS commands, we translate the absolute sensor index
262 * to a device-relative sensor index.
263 */
264 case ENVSYS_VERSION:
265 *(int32_t *)data = SYSMON_ENVSYS_VERSION;
266 break;
267
268 case ENVSYS_GRANGE:
269 {
270 struct envsys_range *rng = (void *) data;
271
272 sme = sysmon_envsys_find(0); /* XXX */
273 if (sme == NULL) {
274 /* Return empty range for `no sensors'. */
275 rng->low = 1;
276 rng->high = 0;
277 break;
278 }
279
280 if (rng->units < ENVSYS_NSENSORS)
281 *rng = sme->sme_ranges[rng->units];
282 else {
283 /* Return empty range for unsupported sensor types. */
284 rng->low = 1;
285 rng->high = 0;
286 }
287 sysmon_envsys_release(sme);
288 break;
289 }
290
291 case ENVSYS_GTREDATA:
292 {
293 struct envsys_tre_data *tred = (void *) data;
294
295 tred->validflags = 0;
296
297 sme = sysmon_envsys_find(tred->sensor);
298 if (sme == NULL)
299 break;
300 oidx = tred->sensor;
301 tred->sensor = SME_SENSOR_IDX(sme, tred->sensor);
302 if (tred->sensor < sme->sme_nsensors)
303 error = (*sme->sme_gtredata)(sme, tred);
304 tred->sensor = oidx;
305 sysmon_envsys_release(sme);
306 break;
307 }
308
309 case ENVSYS_STREINFO:
310 {
311 struct envsys_basic_info *binfo = (void *) data;
312
313 sme = sysmon_envsys_find(binfo->sensor);
314 if (sme == NULL) {
315 binfo->validflags = 0;
316 break;
317 }
318 oidx = binfo->sensor;
319 binfo->sensor = SME_SENSOR_IDX(sme, binfo->sensor);
320 if (binfo->sensor < sme->sme_nsensors)
321 error = (*sme->sme_streinfo)(sme, binfo);
322 else
323 binfo->validflags = 0;
324 binfo->sensor = oidx;
325 sysmon_envsys_release(sme);
326 break;
327 }
328
329 case ENVSYS_GTREINFO:
330 {
331 struct envsys_basic_info *binfo = (void *) data;
332
333 binfo->validflags = 0;
334
335 sme = sysmon_envsys_find(binfo->sensor);
336 if (sme == NULL)
337 break;
338 oidx = binfo->sensor;
339 binfo->sensor = SME_SENSOR_IDX(sme, binfo->sensor);
340 if (binfo->sensor < sme->sme_nsensors)
341 *binfo = sme->sme_sensor_info[binfo->sensor];
342 binfo->sensor = oidx;
343 sysmon_envsys_release(sme);
344 break;
345 }
346
347 default:
348 error = ENOTTY;
349 }
350
351 return (error);
352 }
353
354 /*
355 * sysmonioctl_wdog:
356 *
357 * Perform a watchdog control request.
358 */
359 int
360 sysmonioctl_wdog(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
361 {
362 struct sysmon_wdog *smw;
363 int s, error = 0;
364
365 switch (cmd) {
366 case WDOGIOC_GMODE:
367 {
368 struct wdog_mode *wm = (void *) data;
369
370 wm->wm_name[sizeof(wm->wm_name) - 1] = '\0';
371 smw = sysmon_wdog_find(wm->wm_name);
372 if (smw == NULL) {
373 error = ESRCH;
374 break;
375 }
376
377 wm->wm_mode = smw->smw_mode;
378 wm->wm_period = smw->smw_period;
379 sysmon_wdog_release(smw);
380 break;
381 }
382
383 case WDOGIOC_SMODE:
384 {
385 struct wdog_mode *wm = (void *) data;
386
387 if ((flag & FWRITE) == 0) {
388 error = EPERM;
389 break;
390 }
391
392 wm->wm_name[sizeof(wm->wm_name) - 1] = '\0';
393 smw = sysmon_wdog_find(wm->wm_name);
394 if (smw == NULL) {
395 error = ESRCH;
396 break;
397 }
398
399 if (wm->wm_mode & ~(WDOG_MODE_MASK|WDOG_FEATURE_MASK))
400 error = EINVAL;
401 else {
402 SYSMON_WDOG_LOCK(s);
403 error = sysmon_wdog_setmode(smw, wm->wm_mode,
404 wm->wm_period);
405 SYSMON_WDOG_UNLOCK(s);
406 }
407
408 sysmon_wdog_release(smw);
409 break;
410 }
411
412 case WDOGIOC_WHICH:
413 {
414 struct wdog_mode *wm = (void *) data;
415
416 SYSMON_WDOG_LOCK(s);
417 if ((smw = sysmon_armed_wdog) != NULL) {
418 strcpy(wm->wm_name, smw->smw_name);
419 wm->wm_mode = smw->smw_mode;
420 wm->wm_period = smw->smw_period;
421 } else
422 error = ESRCH;
423 SYSMON_WDOG_UNLOCK(s);
424 break;
425 }
426
427 case WDOGIOC_TICKLE:
428 if ((flag & FWRITE) == 0) {
429 error = EPERM;
430 break;
431 }
432
433 SYSMON_WDOG_LOCK(s);
434 if ((smw = sysmon_armed_wdog) != NULL) {
435 error = (*smw->smw_tickle)(smw);
436 if (error == 0)
437 smw->smw_tickler = p->p_pid;
438 } else
439 error = ESRCH;
440 SYSMON_WDOG_UNLOCK(s);
441 break;
442
443 case WDOGIOC_GTICKLER:
444 *(pid_t *)data = smw->smw_tickler;
445 break;
446
447 case WDOGIOC_GWDOGS:
448 {
449 struct wdog_conf *wc = (void *) data;
450 char *cp;
451 int i;
452
453 simple_lock(&sysmon_wdog_list_slock);
454 if (wc->wc_names == NULL)
455 wc->wc_count = sysmon_wdog_count;
456 else {
457 for (i = 0, cp = wc->wc_names,
458 smw = LIST_FIRST(&sysmon_wdog_list);
459 i < sysmon_wdog_count && smw != NULL && error == 0;
460 i++, cp += WDOG_NAMESIZE,
461 smw = LIST_NEXT(smw, smw_list))
462 error = copyout(smw->smw_name, cp,
463 strlen(smw->smw_name) + 1);
464 wc->wc_count = i;
465 }
466 simple_unlock(&sysmon_wdog_list_slock);
467 break;
468 }
469
470 default:
471 error = ENOTTY;
472 }
473
474 return (error);
475 }
476
477 /*
478 * sysmon_envsys_register:
479 *
480 * Register an ENVSYS device.
481 */
482 int
483 sysmon_envsys_register(struct sysmon_envsys *sme)
484 {
485 int error = 0;
486
487 simple_lock(&sysmon_envsys_list_slock);
488
489 /* XXX Only get to register one, for now. */
490 if (LIST_FIRST(&sysmon_envsys_list) != NULL) {
491 error = EEXIST;
492 goto out;
493 }
494
495 if (sme->sme_envsys_version != SYSMON_ENVSYS_VERSION) {
496 error = EINVAL;
497 goto out;
498 }
499
500 sme->sme_fsensor = sysmon_envsys_next_sensor_index;
501 sysmon_envsys_next_sensor_index += sme->sme_nsensors;
502 LIST_INSERT_HEAD(&sysmon_envsys_list, sme, sme_list);
503
504 out:
505 simple_unlock(&sysmon_envsys_list_slock);
506 return (error);
507 }
508
509 /*
510 * sysmon_envsys_unregister:
511 *
512 * Unregister an ENVSYS device.
513 */
514 void
515 sysmon_envsys_unregister(struct sysmon_envsys *sme)
516 {
517
518 simple_lock(&sysmon_envsys_list_slock);
519 LIST_REMOVE(sme, sme_list);
520 simple_unlock(&sysmon_envsys_list_slock);
521 }
522
523 /*
524 * sysmon_envsys_find:
525 *
526 * Find an ENVSYS device. The list remains locked upon
527 * a match.
528 */
529 struct sysmon_envsys *
530 sysmon_envsys_find(u_int idx)
531 {
532 struct sysmon_envsys *sme;
533
534 simple_lock(&sysmon_envsys_list_slock);
535
536 for (sme = LIST_FIRST(&sysmon_envsys_list); sme != NULL;
537 sme = LIST_NEXT(sme, sme_list)) {
538 if (idx >= sme->sme_fsensor &&
539 idx < (sme->sme_fsensor + sme->sme_nsensors))
540 return (sme);
541 }
542
543 simple_unlock(&sysmon_envsys_list_slock);
544 return (NULL);
545 }
546
547 /*
548 * sysmon_envsys_release:
549 *
550 * Release an ENVSYS device.
551 */
552 /* ARGSUSED */
553 void
554 sysmon_envsys_release(struct sysmon_envsys *sme)
555 {
556
557 simple_unlock(&sysmon_envsys_list_slock);
558 }
559
560 /*
561 * sysmon_wdog_register:
562 *
563 * Register a watchdog device.
564 */
565 int
566 sysmon_wdog_register(struct sysmon_wdog *smw)
567 {
568 struct sysmon_wdog *lsmw;
569 int error = 0;
570
571 simple_lock(&sysmon_wdog_list_slock);
572
573 for (lsmw = LIST_FIRST(&sysmon_wdog_list); lsmw != NULL;
574 lsmw = LIST_NEXT(lsmw, smw_list)) {
575 if (strcmp(lsmw->smw_name, smw->smw_name) == 0) {
576 error = EEXIST;
577 goto out;
578 }
579 }
580
581 smw->smw_mode = WDOG_MODE_DISARMED;
582 smw->smw_tickler = (pid_t) -1;
583 smw->smw_refcnt = 0;
584 sysmon_wdog_count++;
585 LIST_INSERT_HEAD(&sysmon_wdog_list, smw, smw_list);
586
587 out:
588 simple_unlock(&sysmon_wdog_list_slock);
589 return (error);
590 }
591
592 /*
593 * sysmon_wdog_unregister:
594 *
595 * Unregister a watchdog device.
596 */
597 void
598 sysmon_wdog_unregister(struct sysmon_wdog *smw)
599 {
600
601 simple_lock(&sysmon_wdog_list_slock);
602 sysmon_wdog_count--;
603 LIST_REMOVE(smw, smw_list);
604 simple_unlock(&sysmon_wdog_list_slock);
605 }
606
607 /*
608 * sysmon_wdog_find:
609 *
610 * Find a watchdog device. We increase the reference
611 * count on a match.
612 */
613 struct sysmon_wdog *
614 sysmon_wdog_find(const char *name)
615 {
616 struct sysmon_wdog *smw;
617
618 simple_lock(&sysmon_wdog_list_slock);
619
620 for (smw = LIST_FIRST(&sysmon_wdog_list); smw != NULL;
621 smw = LIST_NEXT(smw, smw_list)) {
622 if (strcmp(smw->smw_name, name) == 0)
623 break;
624 }
625
626 if (smw != NULL)
627 smw->smw_refcnt++;
628
629 simple_unlock(&sysmon_wdog_list_slock);
630 return (smw);
631 }
632
633 /*
634 * sysmon_wdog_release:
635 *
636 * Release a watchdog device.
637 */
638 void
639 sysmon_wdog_release(struct sysmon_wdog *smw)
640 {
641
642 simple_lock(&sysmon_wdog_list_slock);
643 KASSERT(smw->smw_refcnt != 0);
644 smw->smw_refcnt--;
645 simple_unlock(&sysmon_wdog_list_slock);
646 }
647
648 /*
649 * sysmon_wdog_setmode:
650 *
651 * Set the mode of a watchdog device.
652 */
653 int
654 sysmon_wdog_setmode(struct sysmon_wdog *smw, int mode, u_int period)
655 {
656 u_int operiod = smw->smw_period;
657 int omode = smw->smw_mode;
658 int error = 0;
659
660 smw->smw_period = period;
661 smw->smw_mode = mode;
662
663 switch (mode & WDOG_MODE_MASK) {
664 case WDOG_MODE_DISARMED:
665 if (smw != sysmon_armed_wdog) {
666 error = EINVAL;
667 goto out;
668 }
669 break;
670
671 case WDOG_MODE_KTICKLE:
672 case WDOG_MODE_UTICKLE:
673 if (sysmon_armed_wdog != NULL) {
674 error = EBUSY;
675 goto out;
676 }
677 break;
678
679 default:
680 error = EINVAL;
681 goto out;
682 }
683
684 error = (*smw->smw_setmode)(smw);
685
686 out:
687 if (error) {
688 smw->smw_period = operiod;
689 smw->smw_mode = omode;
690 } else {
691 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
692 smw->smw_tickler = (pid_t) -1;
693 smw->smw_refcnt--;
694 if ((omode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE)
695 callout_stop(&sysmon_wdog_callout);
696 } else {
697 sysmon_armed_wdog = smw;
698 smw->smw_refcnt++;
699 if ((mode & WDOG_MODE_MASK) == WDOG_MODE_KTICKLE) {
700 callout_reset(&sysmon_wdog_callout,
701 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2,
702 sysmon_wdog_ktickle, NULL);
703 }
704 }
705 }
706 return (error);
707 }
708
709 /*
710 * sysmon_wdog_ktickle:
711 *
712 * Kernel watchdog tickle routine.
713 */
714 void
715 sysmon_wdog_ktickle(void *arg)
716 {
717 struct sysmon_wdog *smw;
718 int s;
719
720 SYSMON_WDOG_LOCK(s);
721 if ((smw = sysmon_armed_wdog) != NULL) {
722 if ((*smw->smw_tickle)(smw) != 0) {
723 printf("WARNING: KERNEL TICKLE OF WATCHDOG %s "
724 "FAILED!\n", smw->smw_name);
725 /*
726 * ...we will probably reboot soon.
727 */
728 }
729 callout_reset(&sysmon_wdog_callout,
730 WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2,
731 sysmon_wdog_ktickle, NULL);
732 }
733 SYSMON_WDOG_UNLOCK(s);
734 }
735
736 /*
737 * sysmon_wdog_shutdown:
738 *
739 * Perform shutdown-time operations.
740 */
741 void
742 sysmon_wdog_shutdown(void *arg)
743 {
744 struct sysmon_wdog *smw;
745
746 /*
747 * XXX Locking here? I don't think it's necessary.
748 */
749
750 if ((smw = sysmon_armed_wdog) != NULL) {
751 if (sysmon_wdog_setmode(smw, WDOG_MODE_DISARMED,
752 smw->smw_period))
753 printf("WARNING: FAILED TO SHUTDOWN WATCHDOG %s!\n",
754 smw->smw_name);
755 }
756 }
757