sysmon_envsys_events.c revision 1.51 1 /* $NetBSD: sysmon_envsys_events.c,v 1.51 2008/04/01 12:25:30 xtraeme Exp $ */
2
3 /*-
4 * Copyright (c) 2007, 2008 Juan Romero Pardines.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /*
29 * sysmon_envsys(9) events framework.
30 */
31
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: sysmon_envsys_events.c,v 1.51 2008/04/01 12:25:30 xtraeme Exp $");
34
35 #include <sys/param.h>
36 #include <sys/types.h>
37 #include <sys/conf.h>
38 #include <sys/errno.h>
39 #include <sys/kernel.h>
40 #include <sys/systm.h>
41 #include <sys/proc.h>
42 #include <sys/mutex.h>
43 #include <sys/kmem.h>
44 #include <sys/callout.h>
45
46 /* #define ENVSYS_DEBUG */
47 #include <dev/sysmon/sysmonvar.h>
48 #include <dev/sysmon/sysmon_envsysvar.h>
49
50 struct sme_sensor_event {
51 int state;
52 int event;
53 };
54
55 static const struct sme_sensor_event sme_sensor_event[] = {
56 { ENVSYS_SVALID, PENVSYS_EVENT_NORMAL },
57 { ENVSYS_SCRITICAL, PENVSYS_EVENT_CRITICAL },
58 { ENVSYS_SCRITOVER, PENVSYS_EVENT_CRITOVER },
59 { ENVSYS_SCRITUNDER, PENVSYS_EVENT_CRITUNDER },
60 { ENVSYS_SWARNOVER, PENVSYS_EVENT_WARNOVER },
61 { ENVSYS_SWARNUNDER, PENVSYS_EVENT_WARNUNDER },
62 { -1, -1 }
63 };
64
65 static bool sysmon_low_power;
66
67 #define SME_EVTIMO (SME_EVENTS_DEFTIMEOUT * hz)
68
69 static bool sme_event_check_low_power(void);
70 static bool sme_battery_check(void);
71 static bool sme_battery_critical(envsys_data_t *);
72 static bool sme_acadapter_check(void);
73
74 /*
75 * sme_event_register:
76 *
77 * + Registers a new sysmon envsys event or updates any event
78 * already in the queue.
79 */
80 int
81 sme_event_register(prop_dictionary_t sdict, envsys_data_t *edata,
82 struct sysmon_envsys *sme, const char *objkey,
83 int32_t critval, int crittype, int powertype)
84 {
85 sme_event_t *see = NULL, *osee = NULL;
86 prop_object_t obj;
87 bool critvalup = false;
88 int error = 0;
89
90 KASSERT(sdict != NULL || edata != NULL || sme != NULL);
91 /*
92 * Allocate a new sysmon_envsys event.
93 */
94 see = kmem_zalloc(sizeof(*see), KM_SLEEP);
95 if (see == NULL)
96 return ENOMEM;
97
98 /*
99 * check if the event is already on the list and return
100 * EEXIST if value provided hasn't been changed.
101 */
102 mutex_enter(&sme->sme_mtx);
103 LIST_FOREACH(osee, &sme->sme_events_list, see_list) {
104 if (strcmp(edata->desc, osee->see_pes.pes_sensname) == 0)
105 if (crittype == osee->see_type) {
106 if (osee->see_critval == critval) {
107 DPRINTF(("%s: dev=%s sensor=%s type=%d "
108 "(already exists)\n", __func__,
109 osee->see_pes.pes_dvname,
110 osee->see_pes.pes_sensname,
111 osee->see_type));
112 mutex_exit(&sme->sme_mtx);
113 return EEXIST;
114 }
115 critvalup = true;
116 break;
117 }
118 }
119
120 /*
121 * Critical condition operation requested by userland.
122 */
123 if (objkey && critval && critvalup) {
124 obj = prop_dictionary_get(sdict, objkey);
125 if (obj && prop_object_type(obj) == PROP_TYPE_NUMBER) {
126 /*
127 * object is already in dictionary and value
128 * provided is not the same than we have
129 * currently, update the critical value.
130 */
131 osee->see_critval = critval;
132 DPRINTF(("%s: (%s) event [sensor=%s type=%d] "
133 "(critval updated)\n", __func__, sme->sme_name,
134 edata->desc, osee->see_type));
135 error = sme_sensor_upint32(sdict, objkey, critval);
136 mutex_exit(&sme->sme_mtx);
137 return error;
138 }
139 }
140
141 /*
142 * New event requested.
143 */
144 see->see_edata = edata;
145 see->see_critval = critval;
146 see->see_type = crittype;
147 see->see_sme = sme;
148 (void)strlcpy(see->see_pes.pes_dvname, sme->sme_name,
149 sizeof(see->see_pes.pes_dvname));
150 see->see_pes.pes_type = powertype;
151 (void)strlcpy(see->see_pes.pes_sensname, edata->desc,
152 sizeof(see->see_pes.pes_sensname));
153
154 LIST_INSERT_HEAD(&sme->sme_events_list, see, see_list);
155 if (objkey && critval) {
156 error = sme_sensor_upint32(sdict, objkey, critval);
157 if (error)
158 goto out;
159 }
160 DPRINTF(("%s: (%s) event registered (sensor=%s snum=%d type=%d "
161 "critval=%" PRIu32 ")\n", __func__,
162 see->see_sme->sme_name, see->see_pes.pes_sensname,
163 see->see_edata->sensor, see->see_type, see->see_critval));
164 /*
165 * Initialize the events framework if it wasn't initialized before.
166 */
167 if ((sme->sme_flags & SME_CALLOUT_INITIALIZED) == 0)
168 error = sme_events_init(sme);
169 out:
170 mutex_exit(&sme->sme_mtx);
171 if (error)
172 kmem_free(see, sizeof(*see));
173 return error;
174 }
175
176 /*
177 * sme_event_unregister_all:
178 *
179 * + Unregisters all events associated with a sysmon envsys device.
180 */
181 void
182 sme_event_unregister_all(struct sysmon_envsys *sme)
183 {
184 sme_event_t *see;
185 int evcounter = 0;
186
187 KASSERT(sme != NULL);
188
189 mutex_enter(&sme->sme_mtx);
190 LIST_FOREACH(see, &sme->sme_events_list, see_list) {
191 while (see->see_flags & SME_EVENT_WORKING)
192 cv_wait(&sme->sme_condvar, &sme->sme_mtx);
193
194 if (strcmp(see->see_pes.pes_dvname, sme->sme_name) == 0)
195 evcounter++;
196 }
197
198 DPRINTF(("%s: total events %d (%s)\n", __func__,
199 evcounter, sme->sme_name));
200
201 while ((see = LIST_FIRST(&sme->sme_events_list))) {
202 if (evcounter == 0)
203 break;
204
205 if (strcmp(see->see_pes.pes_dvname, sme->sme_name) == 0) {
206 LIST_REMOVE(see, see_list);
207 DPRINTF(("%s: event %s %d removed (%s)\n", __func__,
208 see->see_pes.pes_sensname, see->see_type,
209 sme->sme_name));
210 kmem_free(see, sizeof(*see));
211 evcounter--;
212 }
213 }
214
215 if (LIST_EMPTY(&sme->sme_events_list))
216 if (sme->sme_flags & SME_CALLOUT_INITIALIZED)
217 sme_events_destroy(sme);
218 mutex_exit(&sme->sme_mtx);
219 }
220
221 /*
222 * sme_event_unregister:
223 *
224 * + Unregisters an event from the specified sysmon envsys device.
225 */
226 int
227 sme_event_unregister(struct sysmon_envsys *sme, const char *sensor, int type)
228 {
229 sme_event_t *see;
230 bool found = false;
231
232 KASSERT(sensor != NULL);
233
234 mutex_enter(&sme->sme_mtx);
235 LIST_FOREACH(see, &sme->sme_events_list, see_list) {
236 if (strcmp(see->see_pes.pes_sensname, sensor) == 0) {
237 if (see->see_type == type) {
238 found = true;
239 break;
240 }
241 }
242 }
243
244 if (!found) {
245 mutex_exit(&sme->sme_mtx);
246 return EINVAL;
247 }
248
249 /*
250 * Wait for the event to finish its work, remove from the list
251 * and release resouces.
252 */
253 while (see->see_flags & SME_EVENT_WORKING)
254 cv_wait(&sme->sme_condvar, &sme->sme_mtx);
255
256 DPRINTF(("%s: removed dev=%s sensor=%s type=%d\n",
257 __func__, see->see_pes.pes_dvname, sensor, type));
258 LIST_REMOVE(see, see_list);
259 /*
260 * So the events list is empty, we'll do the following:
261 *
262 * - stop and destroy the callout.
263 * - destroy the workqueue.
264 */
265 if (LIST_EMPTY(&sme->sme_events_list))
266 sme_events_destroy(sme);
267 mutex_exit(&sme->sme_mtx);
268
269 kmem_free(see, sizeof(*see));
270 return 0;
271 }
272
273 /*
274 * sme_event_drvadd:
275 *
276 * + Registers a new event for a device that had enabled any of
277 * the monitoring flags in the driver.
278 */
279 void
280 sme_event_drvadd(void *arg)
281 {
282 sme_event_drv_t *sed_t = arg;
283 int error = 0;
284
285 KASSERT(sed_t != NULL);
286
287 #define SEE_REGEVENT(a, b, c) \
288 do { \
289 if (sed_t->sed_edata->flags & (a)) { \
290 char str[ENVSYS_DESCLEN] = "monitoring-state-"; \
291 \
292 error = sme_event_register(sed_t->sed_sdict, \
293 sed_t->sed_edata, \
294 sed_t->sed_sme, \
295 NULL, \
296 0, \
297 (b), \
298 sed_t->sed_powertype); \
299 if (error && error != EEXIST) \
300 printf("%s: failed to add event! " \
301 "error=%d sensor=%s event=%s\n", \
302 __func__, error, \
303 sed_t->sed_edata->desc, (c)); \
304 else { \
305 (void)strlcat(str, (c), sizeof(str)); \
306 prop_dictionary_set_bool(sed_t->sed_sdict, \
307 str, \
308 true); \
309 } \
310 } \
311 } while (/* CONSTCOND */ 0)
312
313 SEE_REGEVENT(ENVSYS_FMONCRITICAL,
314 PENVSYS_EVENT_CRITICAL,
315 "critical");
316
317 SEE_REGEVENT(ENVSYS_FMONCRITUNDER,
318 PENVSYS_EVENT_CRITUNDER,
319 "critunder");
320
321 SEE_REGEVENT(ENVSYS_FMONCRITOVER,
322 PENVSYS_EVENT_CRITOVER,
323 "critover");
324
325 SEE_REGEVENT(ENVSYS_FMONWARNUNDER,
326 PENVSYS_EVENT_WARNUNDER,
327 "warnunder");
328
329 SEE_REGEVENT(ENVSYS_FMONWARNOVER,
330 PENVSYS_EVENT_WARNOVER,
331 "warnover");
332
333 SEE_REGEVENT(ENVSYS_FMONSTCHANGED,
334 PENVSYS_EVENT_STATE_CHANGED,
335 "state-changed");
336
337 /*
338 * we are done, free memory now.
339 */
340 kmem_free(sed_t, sizeof(*sed_t));
341 }
342
343 /*
344 * sme_events_init:
345 *
346 * + Initialize the events framework for this device.
347 */
348 int
349 sme_events_init(struct sysmon_envsys *sme)
350 {
351 int error = 0;
352 uint64_t timo;
353
354 KASSERT(sme != NULL);
355 KASSERT(mutex_owned(&sme->sme_mtx));
356
357 if (sme->sme_events_timeout)
358 timo = sme->sme_events_timeout * hz;
359 else
360 timo = SME_EVTIMO;
361
362 error = workqueue_create(&sme->sme_wq, sme->sme_name,
363 sme_events_worker, sme, PRI_NONE, IPL_SOFTCLOCK, WQ_MPSAFE);
364 if (error)
365 return error;
366
367 mutex_init(&sme->sme_callout_mtx, MUTEX_DEFAULT, IPL_SOFTCLOCK);
368 callout_setfunc(&sme->sme_callout, sme_events_check, sme);
369 callout_schedule(&sme->sme_callout, timo);
370 sme->sme_flags |= SME_CALLOUT_INITIALIZED;
371 DPRINTF(("%s: events framework initialized for '%s'\n",
372 __func__, sme->sme_name));
373
374 return error;
375 }
376
377 /*
378 * sme_events_destroy:
379 *
380 * + Destroys the event framework for this device: callout
381 * stopped, workqueue destroyed and callout mutex destroyed.
382 */
383 void
384 sme_events_destroy(struct sysmon_envsys *sme)
385 {
386 KASSERT(mutex_owned(&sme->sme_mtx));
387
388 callout_stop(&sme->sme_callout);
389 sme->sme_flags &= ~SME_CALLOUT_INITIALIZED;
390 DPRINTF(("%s: events framework destroyed for '%s'\n",
391 __func__, sme->sme_name));
392 workqueue_destroy(sme->sme_wq);
393 mutex_destroy(&sme->sme_callout_mtx);
394 }
395
396 /*
397 * sme_events_check:
398 *
399 * + Passes the events to the workqueue thread and stops
400 * the callout if the 'low-power' condition is triggered.
401 */
402 void
403 sme_events_check(void *arg)
404 {
405 struct sysmon_envsys *sme = arg;
406 sme_event_t *see;
407 uint64_t timo;
408
409 KASSERT(sme != NULL);
410
411 mutex_enter(&sme->sme_callout_mtx);
412 LIST_FOREACH(see, &sme->sme_events_list, see_list) {
413 workqueue_enqueue(sme->sme_wq, &see->see_wk, NULL);
414 see->see_flags &= ~SME_EVENT_REFRESHED;
415 }
416 if (sme->sme_events_timeout)
417 timo = sme->sme_events_timeout * hz;
418 else
419 timo = SME_EVTIMO;
420 if (!sysmon_low_power)
421 callout_schedule(&sme->sme_callout, timo);
422 mutex_exit(&sme->sme_callout_mtx);
423 }
424
425 /*
426 * sme_events_worker:
427 *
428 * + workqueue thread that checks if there's a critical condition
429 * and sends an event if it was triggered.
430 */
431 void
432 sme_events_worker(struct work *wk, void *arg)
433 {
434 const struct sme_description_table *sdt = NULL;
435 const struct sme_sensor_event *sse = sme_sensor_event;
436 sme_event_t *see = (void *)wk;
437 struct sysmon_envsys *sme = see->see_sme;
438 envsys_data_t *edata = see->see_edata;
439 int i, state = 0;
440
441 KASSERT(wk == &see->see_wk);
442 KASSERT(sme != NULL || edata != NULL);
443
444 mutex_enter(&sme->sme_mtx);
445 if ((see->see_flags & SME_EVENT_WORKING) == 0)
446 see->see_flags |= SME_EVENT_WORKING;
447 /*
448 * refresh the sensor that was marked with a critical event
449 * only if it wasn't refreshed before or if the driver doesn't
450 * use its own method for refreshing.
451 */
452 if ((sme->sme_flags & SME_DISABLE_REFRESH) == 0) {
453 if ((see->see_flags & SME_EVENT_REFRESHED) == 0) {
454 /* refresh sensor in device */
455 (*sme->sme_refresh)(sme, edata);
456 see->see_flags |= SME_EVENT_REFRESHED;
457 }
458 }
459
460 DPRINTFOBJ(("%s: (%s) desc=%s sensor=%d state=%d units=%d "
461 "value_cur=%d\n", __func__, sme->sme_name, edata->desc,
462 edata->sensor, edata->state, edata->units, edata->value_cur));
463
464 /* skip the event if current sensor is in invalid state */
465 if (edata->state == ENVSYS_SINVALID)
466 goto out;
467
468 #define SME_SEND_NORMALEVENT() \
469 do { \
470 see->see_evsent = false; \
471 sysmon_penvsys_event(&see->see_pes, PENVSYS_EVENT_NORMAL); \
472 } while (/* CONSTCOND */ 0)
473
474 #define SME_SEND_EVENT(type) \
475 do { \
476 see->see_evsent = true; \
477 sysmon_penvsys_event(&see->see_pes, (type)); \
478 } while (/* CONSTCOND */ 0)
479
480
481 switch (see->see_type) {
482 /*
483 * if state is the same than the one that matches sse[i].state,
484 * send the event...
485 */
486 case PENVSYS_EVENT_CRITICAL:
487 case PENVSYS_EVENT_CRITUNDER:
488 case PENVSYS_EVENT_CRITOVER:
489 case PENVSYS_EVENT_WARNUNDER:
490 case PENVSYS_EVENT_WARNOVER:
491 for (i = 0; sse[i].state != -1; i++)
492 if (sse[i].event == see->see_type)
493 break;
494
495 if (sse[i].state == -1)
496 break;
497
498 if (see->see_evsent && edata->state == ENVSYS_SVALID)
499 SME_SEND_NORMALEVENT();
500
501 if (!see->see_evsent && edata->state == sse[i].state)
502 SME_SEND_EVENT(see->see_type);
503
504 break;
505 /*
506 * if value_cur is lower than the limit, send the event...
507 */
508 case PENVSYS_EVENT_BATT_USERCAP:
509 case PENVSYS_EVENT_USER_CRITMIN:
510 if (see->see_evsent && edata->value_cur > see->see_critval)
511 SME_SEND_NORMALEVENT();
512
513 if (!see->see_evsent && edata->value_cur < see->see_critval)
514 SME_SEND_EVENT(see->see_type);
515
516 break;
517 /*
518 * if value_cur is higher than the limit, send the event...
519 */
520 case PENVSYS_EVENT_USER_CRITMAX:
521 if (see->see_evsent && edata->value_cur < see->see_critval)
522 SME_SEND_NORMALEVENT();
523
524 if (!see->see_evsent && edata->value_cur > see->see_critval)
525 SME_SEND_EVENT(see->see_type);
526
527 break;
528 /*
529 * if value_cur is not normal (battery) or online (drive),
530 * send the event...
531 */
532 case PENVSYS_EVENT_STATE_CHANGED:
533 /*
534 * the state has not been changed, just ignore the event.
535 */
536 if (edata->value_cur == see->see_evsent)
537 break;
538
539 switch (edata->units) {
540 case ENVSYS_DRIVE:
541 sdt = sme_get_description_table(SME_DESC_DRIVE_STATES);
542 state = ENVSYS_DRIVE_ONLINE;
543 break;
544 case ENVSYS_BATTERY_CAPACITY:
545 sdt = sme_get_description_table(
546 SME_DESC_BATTERY_CAPACITY);
547 state = ENVSYS_BATTERY_CAPACITY_NORMAL;
548 break;
549 default:
550 panic("%s: invalid units for ENVSYS_FMONSTCHANGED",
551 __func__);
552 }
553
554 for (i = 0; sdt[i].type != -1; i++)
555 if (sdt[i].type == edata->value_cur)
556 break;
557
558 if (sdt[i].type == -1)
559 break;
560
561 /*
562 * copy current state description.
563 */
564 (void)strlcpy(see->see_pes.pes_statedesc, sdt[i].desc,
565 sizeof(see->see_pes.pes_statedesc));
566
567 /*
568 * state is ok again... send a normal event.
569 */
570 if (see->see_evsent && edata->value_cur == state)
571 SME_SEND_NORMALEVENT();
572
573 /*
574 * state has been changed... send event.
575 */
576 if (see->see_evsent || edata->value_cur != state) {
577 /*
578 * save current drive state.
579 */
580 see->see_evsent = edata->value_cur;
581 sysmon_penvsys_event(&see->see_pes, see->see_type);
582 }
583
584 /*
585 * There's no need to continue if it's a drive sensor.
586 */
587 if (edata->units == ENVSYS_DRIVE)
588 break;
589
590 /*
591 * Check if the system is running in low power and send the
592 * event to powerd (if running) or shutdown the system
593 * otherwise.
594 */
595 if (!sysmon_low_power && sme_event_check_low_power()) {
596 struct penvsys_state pes;
597
598 /*
599 * Stop the callout and send the 'low-power' event.
600 */
601 sysmon_low_power = true;
602 callout_stop(&sme->sme_callout);
603 pes.pes_type = PENVSYS_TYPE_BATTERY;
604 sysmon_penvsys_event(&pes, PENVSYS_EVENT_LOW_POWER);
605 }
606 break;
607 }
608
609 out:
610 see->see_flags &= ~SME_EVENT_WORKING;
611 cv_broadcast(&sme->sme_condvar);
612 mutex_exit(&sme->sme_mtx);
613 }
614
615 /*
616 * Returns true if the system is in low power state: an AC adapter
617 * is OFF and all batteries are in LOW/CRITICAL state.
618 */
619 static bool
620 sme_event_check_low_power(void)
621 {
622 if (!sme_acadapter_check())
623 return false;
624
625 return sme_battery_check();
626 }
627
628 /*
629 * Called with the sysmon_envsys device mtx held through the
630 * workqueue thread.
631 */
632 static bool
633 sme_acadapter_check(void)
634 {
635 struct sysmon_envsys *sme;
636 envsys_data_t *edata;
637 bool dev = false, sensor = false;
638
639 LIST_FOREACH(sme, &sysmon_envsys_list, sme_list) {
640 if (sme->sme_class == SME_CLASS_ACADAPTER) {
641 dev = true;
642 break;
643 }
644 }
645
646 /*
647 * No AC Adapter devices were found.
648 */
649 if (!dev)
650 return false;
651
652 /*
653 * Check if there's an AC adapter device connected.
654 */
655 TAILQ_FOREACH(edata, &sme->sme_sensors_list, sensors_head) {
656 if (edata->units == ENVSYS_INDICATOR) {
657 sensor = true;
658 /* refresh current sensor */
659 (*sme->sme_refresh)(sme, edata);
660 if (edata->value_cur)
661 return false;
662 }
663 }
664
665 if (!sensor)
666 return false;
667
668 /*
669 * AC adapter found and not connected.
670 */
671 return true;
672 }
673
674 /*
675 * Called with the sysmon_envsys device mtx held through the
676 * workqueue thread.
677 */
678 static bool
679 sme_battery_check(void)
680 {
681 struct sysmon_envsys *sme;
682 envsys_data_t *edata;
683 bool battery, batterycap, batterycharge;
684
685 battery = batterycap = batterycharge = false;
686
687 /*
688 * Check for battery devices and its state.
689 */
690 LIST_FOREACH(sme, &sysmon_envsys_list, sme_list) {
691 if (sme->sme_class != SME_CLASS_BATTERY)
692 continue;
693
694 /*
695 * We've found a battery device...
696 */
697 battery = true;
698 TAILQ_FOREACH(edata, &sme->sme_sensors_list, sensors_head) {
699 if (edata->units == ENVSYS_BATTERY_CAPACITY) {
700 batterycap = true;
701 if (!sme_battery_critical(edata))
702 return false;
703 } else if (edata->units == ENVSYS_BATTERY_CHARGE) {
704 batterycharge = true;
705 if (edata->value_cur)
706 return false;
707 }
708 }
709 }
710
711 if (!battery || !batterycap || !batterycharge)
712 return false;
713
714 /*
715 * All batteries in low/critical capacity and discharging.
716 */
717 return true;
718 }
719
720 static bool
721 sme_battery_critical(envsys_data_t *edata)
722 {
723 if (edata->value_cur == ENVSYS_BATTERY_CAPACITY_CRITICAL ||
724 edata->value_cur == ENVSYS_BATTERY_CAPACITY_LOW)
725 return true;
726
727 return false;
728 }
729