sysmon_envsys_events.c revision 1.29 1 /* $NetBSD: sysmon_envsys_events.c,v 1.29 2007/09/07 23:28:33 xtraeme Exp $ */
2
3 /*-
4 * Copyright (c) 2007 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Juan Romero Pardines.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by Juan Romero Pardines
21 * for the NetBSD Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 * contributors may be used to endorse or promote products derived
24 * from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39 /*
40 * sysmon_envsys(9) events framework.
41 */
42
43 #include <sys/cdefs.h>
44 __KERNEL_RCSID(0, "$NetBSD: sysmon_envsys_events.c,v 1.29 2007/09/07 23:28:33 xtraeme Exp $");
45
46 #include <sys/param.h>
47 #include <sys/types.h>
48 #include <sys/conf.h>
49 #include <sys/errno.h>
50 #include <sys/kernel.h>
51 #include <sys/sysctl.h>
52 #include <sys/systm.h>
53 #include <sys/proc.h>
54 #include <sys/mutex.h>
55 #include <sys/kmem.h>
56 #include <sys/callout.h>
57
58 #include <dev/sysmon/sysmonvar.h>
59 #include <dev/sysmon/sysmon_envsysvar.h>
60
61 struct sme_sensor_event {
62 int state;
63 int event;
64 };
65
66 static const struct sme_sensor_event sme_sensor_event[] = {
67 { ENVSYS_SVALID, PENVSYS_EVENT_NORMAL },
68 { ENVSYS_SCRITICAL, PENVSYS_EVENT_CRITICAL },
69 { ENVSYS_SCRITOVER, PENVSYS_EVENT_CRITOVER },
70 { ENVSYS_SCRITUNDER, PENVSYS_EVENT_CRITUNDER },
71 { ENVSYS_SWARNOVER, PENVSYS_EVENT_WARNOVER },
72 { ENVSYS_SWARNUNDER, PENVSYS_EVENT_WARNUNDER },
73 { -1, -1 }
74 };
75
76 static struct workqueue *seewq;
77 static struct callout seeco;
78 static bool sme_events_initialized = false;
79 kmutex_t sme_list_mtx, sme_event_mtx, sme_event_init_mtx;
80 kcondvar_t sme_event_cv;
81
82 /* 10 seconds of timeout for the callout */
83 static int sme_events_timeout = 10;
84 static int sme_events_timeout_sysctl(SYSCTLFN_PROTO);
85 #define SME_EVTIMO (sme_events_timeout * hz)
86
87 /*
88 * sysctl(9) stuff to handle the refresh value in the callout
89 * function.
90 */
91 static int
92 sme_events_timeout_sysctl(SYSCTLFN_ARGS)
93 {
94 struct sysctlnode node;
95 int timo, error;
96
97 node = *rnode;
98 timo = sme_events_timeout;
99 node.sysctl_data = &timo;
100
101 error = sysctl_lookup(SYSCTLFN_CALL(&node));
102 if (error || newp == NULL)
103 return error;
104
105 /* min 1s */
106 if (timo < 1)
107 return EINVAL;
108
109 sme_events_timeout = timo;
110 return 0;
111 }
112
113 SYSCTL_SETUP(sysctl_kern_envsys_timeout_setup, "sysctl kern.envsys subtree")
114 {
115 const struct sysctlnode *node, *envsys_node;
116
117 sysctl_createv(clog, 0, NULL, &node,
118 CTLFLAG_PERMANENT,
119 CTLTYPE_NODE, "kern", NULL,
120 NULL, 0, NULL, 0,
121 CTL_KERN, CTL_EOL);
122
123 sysctl_createv(clog, 0, &node, &envsys_node,
124 0,
125 CTLTYPE_NODE, "envsys", NULL,
126 NULL, 0, NULL, 0,
127 CTL_CREATE, CTL_EOL);
128
129 sysctl_createv(clog, 0, &envsys_node, &node,
130 CTLFLAG_READWRITE,
131 CTLTYPE_INT, "refresh_value",
132 SYSCTL_DESCR("wait time in seconds to refresh "
133 "sensors being monitored"),
134 sme_events_timeout_sysctl, 0, &sme_events_timeout, 0,
135 CTL_CREATE, CTL_EOL);
136 }
137
138 /*
139 * sme_event_register:
140 *
141 * + Registers a new sysmon envsys event or updates any event
142 * already in the queue.
143 */
144 int
145 sme_event_register(prop_dictionary_t sdict, envsys_data_t *edata,
146 const char *drvn, const char *objkey,
147 int32_t critval, int crittype, int powertype)
148 {
149 sme_event_t *see = NULL;
150 prop_object_t obj;
151 bool critvalup = false;
152 int error = 0;
153
154 KASSERT(sdict != NULL || edata != NULL);
155
156 mutex_enter(&sme_event_mtx);
157 /*
158 * check if the event is already on the list and return
159 * EEXIST if value provided hasn't been changed.
160 */
161 LIST_FOREACH(see, &sme_events_list, see_list) {
162 if (strcmp(edata->desc, see->pes.pes_sensname) == 0)
163 if (crittype == see->type) {
164 if (see->critval == critval) {
165 DPRINTF(("%s: dev=%s sensor=%s type=%d "
166 "(already exists)\n", __func__,
167 see->pes.pes_dvname,
168 see->pes.pes_sensname, see->type));
169 mutex_exit(&sme_event_mtx);
170 return EEXIST;
171 }
172 critvalup = true;
173 break;
174 }
175 }
176
177 /*
178 * Critical condition operation requested by userland.
179 */
180 if (objkey && critval && critvalup) {
181 obj = prop_dictionary_get(sdict, objkey);
182 if (obj != NULL) {
183 /*
184 * object is already in dictionary and value
185 * provided is not the same than we have
186 * currently, update the critical value.
187 */
188 see->critval = critval;
189 DPRINTF(("%s: sensor=%s type=%d (critval updated)\n",
190 __func__, edata->desc, see->type));
191 error = sme_sensor_upint32(sdict, objkey, critval);
192 mutex_exit(&sme_event_mtx);
193 return error;
194 }
195 }
196
197 /*
198 * The event is not in on the list or in a dictionary, create a new
199 * sme event, assign required members and update the object in
200 * the dictionary.
201 */
202 see = NULL;
203 see = kmem_zalloc(sizeof(*see), KM_NOSLEEP);
204 if (see == NULL) {
205 mutex_exit(&sme_event_mtx);
206 return ENOMEM;
207 }
208
209 see->critval = critval;
210 see->type = crittype;
211 (void)strlcpy(see->pes.pes_dvname, drvn,
212 sizeof(see->pes.pes_dvname));
213 see->pes.pes_type = powertype;
214 (void)strlcpy(see->pes.pes_sensname, edata->desc,
215 sizeof(see->pes.pes_sensname));
216 see->snum = edata->sensor;
217
218 LIST_INSERT_HEAD(&sme_events_list, see, see_list);
219 if (objkey && critval) {
220 error = sme_sensor_upint32(sdict, objkey, critval);
221 if (error) {
222 mutex_exit(&sme_event_mtx);
223 goto out;
224 }
225 }
226 DPRINTF(("%s: registering dev=%s sensor=%s snum=%d type=%d "
227 "critval=%" PRIu32 "\n", __func__,
228 see->pes.pes_dvname, see->pes.pes_sensname,
229 see->snum, see->type, see->critval));
230 /*
231 * Initialize the events framework if it wasn't initialized
232 * before.
233 */
234 mutex_enter(&sme_event_init_mtx);
235 mutex_exit(&sme_event_mtx);
236 if (sme_events_initialized == false)
237 error = sme_events_init();
238 mutex_exit(&sme_event_init_mtx);
239 out:
240 if (error)
241 kmem_free(see, sizeof(*see));
242 return error;
243 }
244
245 /*
246 * sme_event_unregister_all:
247 *
248 * + Unregisters all sysmon envsys events associated with a
249 * sysmon envsys device.
250 */
251 void
252 sme_event_unregister_all(const char *sme_name)
253 {
254 sme_event_t *see;
255 int evcounter = 0;
256
257 KASSERT(sme_name != NULL);
258
259 mutex_enter(&sme_event_mtx);
260 LIST_FOREACH(see, &sme_events_list, see_list) {
261 if (strcmp(see->pes.pes_dvname, sme_name) == 0)
262 evcounter++;
263 }
264
265 DPRINTF(("%s: total events %d (%s)\n", __func__,
266 evcounter, sme_name));
267
268 while ((see = LIST_FIRST(&sme_events_list)) != NULL) {
269 if (evcounter == 0)
270 break;
271
272 if (strcmp(see->pes.pes_dvname, sme_name) == 0) {
273 DPRINTF(("%s: event %s %d removed (%s)\n", __func__,
274 see->pes.pes_sensname, see->type, sme_name));
275
276 while (see->see_flags & SME_EVENT_WORKING)
277 cv_wait(&sme_event_cv, &sme_event_mtx);
278
279 LIST_REMOVE(see, see_list);
280 kmem_free(see, sizeof(*see));
281 evcounter--;
282 }
283 }
284
285 if (LIST_EMPTY(&sme_events_list)) {
286 mutex_enter(&sme_event_init_mtx);
287 mutex_exit(&sme_event_mtx);
288 if (sme_events_initialized)
289 sme_events_destroy();
290 mutex_exit(&sme_event_init_mtx);
291 return;
292 }
293
294 mutex_exit(&sme_event_mtx);
295 }
296
297 /*
298 * sme_event_unregister:
299 *
300 * + Unregisters a sysmon envsys event.
301 */
302 int
303 sme_event_unregister(const char *sensor, int type)
304 {
305 sme_event_t *see;
306 bool found = false;
307
308 KASSERT(sensor != NULL);
309
310 mutex_enter(&sme_event_mtx);
311 LIST_FOREACH(see, &sme_events_list, see_list) {
312 if (strcmp(see->pes.pes_sensname, sensor) == 0) {
313 if (see->type == type) {
314 found = true;
315 break;
316 }
317 }
318 }
319
320 if (!found) {
321 mutex_exit(&sme_event_mtx);
322 return EINVAL;
323 }
324
325 while (see->see_flags & SME_EVENT_WORKING)
326 cv_wait(&sme_event_cv, &sme_event_mtx);
327
328 DPRINTF(("%s: removing dev=%s sensor=%s type=%d\n",
329 __func__, see->pes.pes_dvname, sensor, type));
330 LIST_REMOVE(see, see_list);
331 /*
332 * So the events list is empty, we'll do the following:
333 *
334 * - stop and destroy the callout.
335 * - destroy the workqueue.
336 */
337 if (LIST_EMPTY(&sme_events_list)) {
338 mutex_enter(&sme_event_init_mtx);
339 mutex_exit(&sme_event_mtx);
340 sme_events_destroy();
341 mutex_exit(&sme_event_init_mtx);
342 goto out;
343 }
344 mutex_exit(&sme_event_mtx);
345
346 out:
347 kmem_free(see, sizeof(*see));
348 return 0;
349 }
350
351 /*
352 * sme_event_drvadd:
353 *
354 * + Adds a new sysmon envsys event for a driver if a sensor
355 * has set any accepted monitoring flag.
356 */
357 void
358 sme_event_drvadd(void *arg)
359 {
360 sme_event_drv_t *sed_t = arg;
361 int error = 0;
362
363 KASSERT(sed_t != NULL);
364
365 #define SEE_REGEVENT(a, b, c) \
366 do { \
367 if (sed_t->edata->flags & (a)) { \
368 char str[32] = "monitoring-state-"; \
369 \
370 error = sme_event_register(sed_t->sdict, \
371 sed_t->edata, \
372 sed_t->sme->sme_name, \
373 NULL, \
374 0, \
375 (b), \
376 sed_t->powertype); \
377 if (error && error != EEXIST) \
378 printf("%s: failed to add event! " \
379 "error=%d sensor=%s event=%s\n", \
380 __func__, error, sed_t->edata->desc, (c)); \
381 else { \
382 (void)strlcat(str, (c), sizeof(str)); \
383 mutex_enter(&sme_event_mtx); \
384 prop_dictionary_set_bool(sed_t->sdict, \
385 str, \
386 true); \
387 mutex_exit(&sme_event_mtx); \
388 } \
389 } \
390 } while (/* CONSTCOND */ 0)
391
392 SEE_REGEVENT(ENVSYS_FMONCRITICAL,
393 PENVSYS_EVENT_CRITICAL,
394 "critical");
395
396 SEE_REGEVENT(ENVSYS_FMONCRITUNDER,
397 PENVSYS_EVENT_CRITUNDER,
398 "critunder");
399
400 SEE_REGEVENT(ENVSYS_FMONCRITOVER,
401 PENVSYS_EVENT_CRITOVER,
402 "critover");
403
404 SEE_REGEVENT(ENVSYS_FMONWARNUNDER,
405 PENVSYS_EVENT_WARNUNDER,
406 "warnunder");
407
408 SEE_REGEVENT(ENVSYS_FMONWARNOVER,
409 PENVSYS_EVENT_WARNOVER,
410 "warnover");
411
412 SEE_REGEVENT(ENVSYS_FMONSTCHANGED,
413 PENVSYS_EVENT_STATE_CHANGED,
414 "state-changed");
415
416 /* we are done, free memory now */
417 kmem_free(sed_t, sizeof(*sed_t));
418 }
419
420 /*
421 * sme_events_init:
422 *
423 * + Initializes the events framework.
424 */
425 int
426 sme_events_init(void)
427 {
428 int error;
429
430 KASSERT(mutex_owned(&sme_event_init_mtx));
431
432 error = workqueue_create(&seewq, "envsysev",
433 sme_events_worker, NULL, 0, IPL_SOFTCLOCK, WQ_MPSAFE);
434 if (error)
435 goto out;
436
437 callout_init(&seeco, 0);
438 callout_setfunc(&seeco, sme_events_check, NULL);
439 callout_schedule(&seeco, SME_EVTIMO);
440 sme_events_initialized = true;
441 DPRINTF(("%s: events framework initialized\n", __func__));
442
443 out:
444 return error;
445 }
446
447 /*
448 * sme_events_destroy:
449 *
450 * + Destroys the events framework: the workqueue and the
451 * callout are stopped and destroyed because there are not
452 * events in the queue.
453 */
454 void
455 sme_events_destroy(void)
456 {
457 KASSERT(mutex_owned(&sme_event_init_mtx));
458
459 callout_stop(&seeco);
460 sme_events_initialized = false;
461 DPRINTF(("%s: events framework destroyed\n", __func__));
462 callout_destroy(&seeco);
463 workqueue_destroy(seewq);
464 }
465
466 /*
467 * sme_events_check:
468 *
469 * + Runs the work on each sysmon envsys event in our
470 * workqueue periodically with callout.
471 */
472 void
473 sme_events_check(void *arg)
474 {
475 sme_event_t *see;
476
477 LIST_FOREACH(see, &sme_events_list, see_list) {
478 DPRINTFOBJ(("%s: dev=%s sensor=%s type=%d\n",
479 __func__,
480 see->pes.pes_dvname,
481 see->pes.pes_sensname,
482 see->type));
483 workqueue_enqueue(seewq, &see->see_wk, NULL);
484 }
485 callout_schedule(&seeco, SME_EVTIMO);
486 }
487
488 /*
489 * sme_events_worker:
490 *
491 * + workqueue thread that checks if there's a critical condition
492 * and sends an event if the condition was triggered.
493 */
494 void
495 sme_events_worker(struct work *wk, void *arg)
496 {
497 const struct sme_description_table *sdt = NULL;
498 const struct sme_sensor_event *sse = sme_sensor_event;
499 sme_event_t *see = (void *)wk;
500 struct sysmon_envsys *sme;
501 envsys_data_t *edata;
502 int i, state, error;
503
504 KASSERT(wk == &see->see_wk);
505
506 state = error = 0;
507
508 mutex_enter(&sme_event_mtx);
509 see->see_flags |= SME_EVENT_WORKING;
510
511 /*
512 * We have to find the sme device by looking
513 * at the power envsys device name.
514 */
515 mutex_enter(&sme_list_mtx);
516 LIST_FOREACH(sme, &sysmon_envsys_list, sme_list)
517 if (strcmp(sme->sme_name, see->pes.pes_dvname) == 0)
518 break;
519 mutex_exit(&sme_list_mtx);
520 if (sme == NULL)
521 return;
522
523 /* get the sensor with the index specified in see->snum */
524 edata = &sme->sme_sensor_data[see->snum];
525
526 /*
527 * refresh the sensor that was marked with a critical
528 * event.
529 */
530 if ((sme->sme_flags & SME_DISABLE_GTREDATA) == 0) {
531 error = (*sme->sme_gtredata)(sme, edata);
532 if (error)
533 return;
534 }
535
536 DPRINTFOBJ(("%s: desc=%s sensor=%d units=%d value_cur=%d\n",
537 __func__, edata->desc, edata->sensor,
538 edata->units, edata->value_cur));
539
540 #define SME_SEND_NORMALEVENT() \
541 do { \
542 see->evsent = false; \
543 sysmon_penvsys_event(&see->pes, PENVSYS_EVENT_NORMAL); \
544 } while (/* CONSTCOND */ 0)
545
546 #define SME_SEND_EVENT(type) \
547 do { \
548 see->evsent = true; \
549 sysmon_penvsys_event(&see->pes, (type)); \
550 } while (/* CONSTCOND */ 0)
551
552 switch (see->type) {
553 /*
554 * if state is the same than the one that matches sse[i].state,
555 * send the event...
556 */
557 case PENVSYS_EVENT_CRITICAL:
558 case PENVSYS_EVENT_CRITUNDER:
559 case PENVSYS_EVENT_CRITOVER:
560 case PENVSYS_EVENT_WARNUNDER:
561 case PENVSYS_EVENT_WARNOVER:
562 for (i = 0; sse[i].state != -1; i++)
563 if (sse[i].event == see->type)
564 break;
565
566 if (see->evsent && edata->state == ENVSYS_SVALID)
567 SME_SEND_NORMALEVENT();
568
569 if (!see->evsent && edata->state == sse[i].state)
570 SME_SEND_EVENT(see->type);
571
572 break;
573 /*
574 * if value_cur is lower than the limit, send the event...
575 */
576 case PENVSYS_EVENT_BATT_USERCAP:
577 case PENVSYS_EVENT_USER_CRITMIN:
578 if (see->evsent && edata->value_cur > see->critval)
579 SME_SEND_NORMALEVENT();
580
581 if (!see->evsent && edata->value_cur < see->critval)
582 SME_SEND_EVENT(see->type);
583
584 break;
585 /*
586 * if value_cur is higher than the limit, send the event...
587 */
588 case PENVSYS_EVENT_USER_CRITMAX:
589 if (see->evsent && edata->value_cur < see->critval)
590 SME_SEND_NORMALEVENT();
591
592 if (!see->evsent && edata->value_cur > see->critval)
593 SME_SEND_EVENT(see->type);
594
595 break;
596 /*
597 * if value_cur is not normal (battery) or online (drive),
598 * send the event...
599 */
600 case PENVSYS_EVENT_STATE_CHANGED:
601 /* the state has not been changed, just ignore the event */
602 if (edata->value_cur == see->evsent)
603 break;
604
605 switch (edata->units) {
606 case ENVSYS_DRIVE:
607 sdt = sme_get_description_table(SME_DESC_DRIVE_STATES);
608 state = ENVSYS_DRIVE_ONLINE;
609 break;
610 case ENVSYS_BATTERY_STATE:
611 sdt =
612 sme_get_description_table(SME_DESC_BATTERY_STATES);
613 state = ENVSYS_BATTERY_STATE_NORMAL;
614 break;
615 }
616
617 for (i = 0; sdt[i].type != -1; i++)
618 if (sdt[i].type == edata->value_cur)
619 break;
620
621 /* copy current state description */
622 (void)strlcpy(see->pes.pes_statedesc, sdt[i].desc,
623 sizeof(see->pes.pes_statedesc));
624
625 /* state is ok again... send a normal event */
626 if (see->evsent && edata->value_cur == state)
627 SME_SEND_NORMALEVENT();
628
629 /* state has been changed... send event */
630 if (see->evsent || edata->value_cur != state) {
631 /* save current drive state */
632 see->evsent = edata->value_cur;
633 sysmon_penvsys_event(&see->pes, see->type);
634 }
635
636 break;
637 }
638 see->see_flags &= ~SME_EVENT_WORKING;
639 cv_broadcast(&sme_event_cv);
640 mutex_exit(&sme_event_mtx);
641 }
642