sysmon_envsys_events.c revision 1.31 1 /* $NetBSD: sysmon_envsys_events.c,v 1.31 2007/09/08 03:17:38 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.31 2007/09/08 03:17:38 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_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_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_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_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_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_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_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(mutex_owned(&sme_mtx));
258 KASSERT(sme_name != NULL);
259
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_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 if (sme_events_initialized)
288 sme_events_destroy();
289 mutex_exit(&sme_event_init_mtx);
290 }
291 }
292
293 /*
294 * sme_event_unregister:
295 *
296 * + Unregisters a sysmon envsys event.
297 */
298 int
299 sme_event_unregister(const char *sensor, int type)
300 {
301 sme_event_t *see;
302 bool found = false;
303
304 KASSERT(sensor != NULL);
305
306 mutex_enter(&sme_mtx);
307 LIST_FOREACH(see, &sme_events_list, see_list) {
308 if (strcmp(see->pes.pes_sensname, sensor) == 0) {
309 if (see->type == type) {
310 found = true;
311 break;
312 }
313 }
314 }
315
316 if (!found) {
317 mutex_exit(&sme_mtx);
318 return EINVAL;
319 }
320
321 while (see->see_flags & SME_EVENT_WORKING)
322 cv_wait(&sme_event_cv, &sme_mtx);
323
324 DPRINTF(("%s: removing dev=%s sensor=%s type=%d\n",
325 __func__, see->pes.pes_dvname, sensor, type));
326 LIST_REMOVE(see, see_list);
327 /*
328 * So the events list is empty, we'll do the following:
329 *
330 * - stop and destroy the callout.
331 * - destroy the workqueue.
332 */
333 if (LIST_EMPTY(&sme_events_list)) {
334 mutex_enter(&sme_event_init_mtx);
335 mutex_exit(&sme_mtx);
336 sme_events_destroy();
337 mutex_exit(&sme_event_init_mtx);
338 goto out;
339 }
340 mutex_exit(&sme_mtx);
341
342 out:
343 kmem_free(see, sizeof(*see));
344 return 0;
345 }
346
347 /*
348 * sme_event_drvadd:
349 *
350 * + Adds a new sysmon envsys event for a driver if a sensor
351 * has set any accepted monitoring flag.
352 */
353 void
354 sme_event_drvadd(void *arg)
355 {
356 sme_event_drv_t *sed_t = arg;
357 int error = 0;
358
359 KASSERT(sed_t != NULL);
360
361 #define SEE_REGEVENT(a, b, c) \
362 do { \
363 if (sed_t->edata->flags & (a)) { \
364 char str[32] = "monitoring-state-"; \
365 \
366 error = sme_event_register(sed_t->sdict, \
367 sed_t->edata, \
368 sed_t->sme->sme_name, \
369 NULL, \
370 0, \
371 (b), \
372 sed_t->powertype); \
373 if (error && error != EEXIST) \
374 printf("%s: failed to add event! " \
375 "error=%d sensor=%s event=%s\n", \
376 __func__, error, sed_t->edata->desc, (c)); \
377 else { \
378 (void)strlcat(str, (c), sizeof(str)); \
379 mutex_enter(&sme_mtx); \
380 prop_dictionary_set_bool(sed_t->sdict, \
381 str, \
382 true); \
383 mutex_exit(&sme_mtx); \
384 } \
385 } \
386 } while (/* CONSTCOND */ 0)
387
388 SEE_REGEVENT(ENVSYS_FMONCRITICAL,
389 PENVSYS_EVENT_CRITICAL,
390 "critical");
391
392 SEE_REGEVENT(ENVSYS_FMONCRITUNDER,
393 PENVSYS_EVENT_CRITUNDER,
394 "critunder");
395
396 SEE_REGEVENT(ENVSYS_FMONCRITOVER,
397 PENVSYS_EVENT_CRITOVER,
398 "critover");
399
400 SEE_REGEVENT(ENVSYS_FMONWARNUNDER,
401 PENVSYS_EVENT_WARNUNDER,
402 "warnunder");
403
404 SEE_REGEVENT(ENVSYS_FMONWARNOVER,
405 PENVSYS_EVENT_WARNOVER,
406 "warnover");
407
408 SEE_REGEVENT(ENVSYS_FMONSTCHANGED,
409 PENVSYS_EVENT_STATE_CHANGED,
410 "state-changed");
411
412 /* we are done, free memory now */
413 kmem_free(sed_t, sizeof(*sed_t));
414 }
415
416 /*
417 * sme_events_init:
418 *
419 * + Initializes the events framework.
420 */
421 int
422 sme_events_init(void)
423 {
424 int error;
425
426 KASSERT(mutex_owned(&sme_event_init_mtx));
427
428 error = workqueue_create(&seewq, "envsysev",
429 sme_events_worker, NULL, 0, IPL_SOFTCLOCK, WQ_MPSAFE);
430 if (error)
431 goto out;
432
433 callout_init(&seeco, 0);
434 callout_setfunc(&seeco, sme_events_check, NULL);
435 callout_schedule(&seeco, SME_EVTIMO);
436 sme_events_initialized = true;
437 DPRINTF(("%s: events framework initialized\n", __func__));
438
439 out:
440 return error;
441 }
442
443 /*
444 * sme_events_destroy:
445 *
446 * + Destroys the events framework: the workqueue and the
447 * callout are stopped and destroyed because there are not
448 * events in the queue.
449 */
450 void
451 sme_events_destroy(void)
452 {
453 KASSERT(mutex_owned(&sme_event_init_mtx));
454
455 callout_stop(&seeco);
456 sme_events_initialized = false;
457 DPRINTF(("%s: events framework destroyed\n", __func__));
458 callout_destroy(&seeco);
459 workqueue_destroy(seewq);
460 }
461
462 /*
463 * sme_events_check:
464 *
465 * + Runs the work on each sysmon envsys event in our
466 * workqueue periodically with callout.
467 */
468 void
469 sme_events_check(void *arg)
470 {
471 sme_event_t *see;
472
473 LIST_FOREACH(see, &sme_events_list, see_list) {
474 DPRINTFOBJ(("%s: dev=%s sensor=%s type=%d\n",
475 __func__,
476 see->pes.pes_dvname,
477 see->pes.pes_sensname,
478 see->type));
479 workqueue_enqueue(seewq, &see->see_wk, NULL);
480 }
481 callout_schedule(&seeco, SME_EVTIMO);
482 }
483
484 /*
485 * sme_events_worker:
486 *
487 * + workqueue thread that checks if there's a critical condition
488 * and sends an event if the condition was triggered.
489 */
490 void
491 sme_events_worker(struct work *wk, void *arg)
492 {
493 const struct sme_description_table *sdt = NULL;
494 const struct sme_sensor_event *sse = sme_sensor_event;
495 sme_event_t *see = (void *)wk;
496 struct sysmon_envsys *sme;
497 envsys_data_t *edata;
498 int i, state, error;
499
500 KASSERT(wk == &see->see_wk);
501
502 state = error = 0;
503
504 mutex_enter(&sme_mtx);
505 see->see_flags |= SME_EVENT_WORKING;
506
507 /*
508 * We have to find the sme device by looking
509 * at the power envsys device name.
510 */
511 LIST_FOREACH(sme, &sysmon_envsys_list, sme_list)
512 if (strcmp(sme->sme_name, see->pes.pes_dvname) == 0)
513 break;
514 if (sme == NULL)
515 goto out;
516
517 /* get the sensor with the index specified in see->snum */
518 edata = &sme->sme_sensor_data[see->snum];
519
520 /*
521 * refresh the sensor that was marked with a critical
522 * event.
523 */
524 if ((sme->sme_flags & SME_DISABLE_GTREDATA) == 0) {
525 error = (*sme->sme_gtredata)(sme, edata);
526 if (error)
527 goto out;
528 }
529
530 DPRINTFOBJ(("%s: desc=%s sensor=%d units=%d value_cur=%d\n",
531 __func__, edata->desc, edata->sensor,
532 edata->units, edata->value_cur));
533
534 #define SME_SEND_NORMALEVENT() \
535 do { \
536 see->evsent = false; \
537 sysmon_penvsys_event(&see->pes, PENVSYS_EVENT_NORMAL); \
538 } while (/* CONSTCOND */ 0)
539
540 #define SME_SEND_EVENT(type) \
541 do { \
542 see->evsent = true; \
543 sysmon_penvsys_event(&see->pes, (type)); \
544 } while (/* CONSTCOND */ 0)
545
546 switch (see->type) {
547 /*
548 * if state is the same than the one that matches sse[i].state,
549 * send the event...
550 */
551 case PENVSYS_EVENT_CRITICAL:
552 case PENVSYS_EVENT_CRITUNDER:
553 case PENVSYS_EVENT_CRITOVER:
554 case PENVSYS_EVENT_WARNUNDER:
555 case PENVSYS_EVENT_WARNOVER:
556 for (i = 0; sse[i].state != -1; i++)
557 if (sse[i].event == see->type)
558 break;
559
560 if (see->evsent && edata->state == ENVSYS_SVALID)
561 SME_SEND_NORMALEVENT();
562
563 if (!see->evsent && edata->state == sse[i].state)
564 SME_SEND_EVENT(see->type);
565
566 break;
567 /*
568 * if value_cur is lower than the limit, send the event...
569 */
570 case PENVSYS_EVENT_BATT_USERCAP:
571 case PENVSYS_EVENT_USER_CRITMIN:
572 if (see->evsent && edata->value_cur > see->critval)
573 SME_SEND_NORMALEVENT();
574
575 if (!see->evsent && edata->value_cur < see->critval)
576 SME_SEND_EVENT(see->type);
577
578 break;
579 /*
580 * if value_cur is higher than the limit, send the event...
581 */
582 case PENVSYS_EVENT_USER_CRITMAX:
583 if (see->evsent && edata->value_cur < see->critval)
584 SME_SEND_NORMALEVENT();
585
586 if (!see->evsent && edata->value_cur > see->critval)
587 SME_SEND_EVENT(see->type);
588
589 break;
590 /*
591 * if value_cur is not normal (battery) or online (drive),
592 * send the event...
593 */
594 case PENVSYS_EVENT_STATE_CHANGED:
595 /* the state has not been changed, just ignore the event */
596 if (edata->value_cur == see->evsent)
597 break;
598
599 switch (edata->units) {
600 case ENVSYS_DRIVE:
601 sdt = sme_get_description_table(SME_DESC_DRIVE_STATES);
602 state = ENVSYS_DRIVE_ONLINE;
603 break;
604 case ENVSYS_BATTERY_STATE:
605 sdt =
606 sme_get_description_table(SME_DESC_BATTERY_STATES);
607 state = ENVSYS_BATTERY_STATE_NORMAL;
608 break;
609 }
610
611 for (i = 0; sdt[i].type != -1; i++)
612 if (sdt[i].type == edata->value_cur)
613 break;
614
615 /* copy current state description */
616 (void)strlcpy(see->pes.pes_statedesc, sdt[i].desc,
617 sizeof(see->pes.pes_statedesc));
618
619 /* state is ok again... send a normal event */
620 if (see->evsent && edata->value_cur == state)
621 SME_SEND_NORMALEVENT();
622
623 /* state has been changed... send event */
624 if (see->evsent || edata->value_cur != state) {
625 /* save current drive state */
626 see->evsent = edata->value_cur;
627 sysmon_penvsys_event(&see->pes, see->type);
628 }
629
630 break;
631 }
632 out:
633 see->see_flags &= ~SME_EVENT_WORKING;
634 cv_broadcast(&sme_event_cv);
635 mutex_exit(&sme_mtx);
636 }
637