sysmon_envsys_events.c revision 1.52 1 /* $NetBSD: sysmon_envsys_events.c,v 1.52 2008/04/01 16:48:18 rmind 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.52 2008/04/01 16:48:18 rmind 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 error = EEXIST;
113 goto out;
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 goto out;
137 }
138 }
139
140 /*
141 * New event requested.
142 */
143 see->see_edata = edata;
144 see->see_critval = critval;
145 see->see_type = crittype;
146 see->see_sme = sme;
147 (void)strlcpy(see->see_pes.pes_dvname, sme->sme_name,
148 sizeof(see->see_pes.pes_dvname));
149 see->see_pes.pes_type = powertype;
150 (void)strlcpy(see->see_pes.pes_sensname, edata->desc,
151 sizeof(see->see_pes.pes_sensname));
152
153 LIST_INSERT_HEAD(&sme->sme_events_list, see, see_list);
154 if (objkey && critval) {
155 error = sme_sensor_upint32(sdict, objkey, critval);
156 if (error)
157 goto out;
158 }
159 DPRINTF(("%s: (%s) event registered (sensor=%s snum=%d type=%d "
160 "critval=%" PRIu32 ")\n", __func__,
161 see->see_sme->sme_name, see->see_pes.pes_sensname,
162 see->see_edata->sensor, see->see_type, see->see_critval));
163 /*
164 * Initialize the events framework if it wasn't initialized before.
165 */
166 if ((sme->sme_flags & SME_CALLOUT_INITIALIZED) == 0)
167 error = sme_events_init(sme);
168 out:
169 mutex_exit(&sme->sme_mtx);
170 if (error)
171 kmem_free(see, sizeof(*see));
172 return error;
173 }
174
175 /*
176 * sme_event_unregister_all:
177 *
178 * + Unregisters all events associated with a sysmon envsys device.
179 */
180 void
181 sme_event_unregister_all(struct sysmon_envsys *sme)
182 {
183 sme_event_t *see;
184 int evcounter = 0;
185
186 KASSERT(sme != NULL);
187
188 mutex_enter(&sme->sme_mtx);
189 LIST_FOREACH(see, &sme->sme_events_list, see_list) {
190 while (see->see_flags & SME_EVENT_WORKING)
191 cv_wait(&sme->sme_condvar, &sme->sme_mtx);
192
193 if (strcmp(see->see_pes.pes_dvname, sme->sme_name) == 0)
194 evcounter++;
195 }
196
197 DPRINTF(("%s: total events %d (%s)\n", __func__,
198 evcounter, sme->sme_name));
199
200 while ((see = LIST_FIRST(&sme->sme_events_list))) {
201 if (evcounter == 0)
202 break;
203
204 if (strcmp(see->see_pes.pes_dvname, sme->sme_name) == 0) {
205 LIST_REMOVE(see, see_list);
206 DPRINTF(("%s: event %s %d removed (%s)\n", __func__,
207 see->see_pes.pes_sensname, see->see_type,
208 sme->sme_name));
209 kmem_free(see, sizeof(*see));
210 evcounter--;
211 }
212 }
213
214 if (LIST_EMPTY(&sme->sme_events_list))
215 if (sme->sme_flags & SME_CALLOUT_INITIALIZED)
216 sme_events_destroy(sme);
217 mutex_exit(&sme->sme_mtx);
218 }
219
220 /*
221 * sme_event_unregister:
222 *
223 * + Unregisters an event from the specified sysmon envsys device.
224 */
225 int
226 sme_event_unregister(struct sysmon_envsys *sme, const char *sensor, int type)
227 {
228 sme_event_t *see;
229 bool found = false;
230
231 KASSERT(sensor != NULL);
232
233 mutex_enter(&sme->sme_mtx);
234 LIST_FOREACH(see, &sme->sme_events_list, see_list) {
235 if (strcmp(see->see_pes.pes_sensname, sensor) == 0) {
236 if (see->see_type == type) {
237 found = true;
238 break;
239 }
240 }
241 }
242
243 if (!found) {
244 mutex_exit(&sme->sme_mtx);
245 return EINVAL;
246 }
247
248 /*
249 * Wait for the event to finish its work, remove from the list
250 * and release resouces.
251 */
252 while (see->see_flags & SME_EVENT_WORKING)
253 cv_wait(&sme->sme_condvar, &sme->sme_mtx);
254
255 DPRINTF(("%s: removed dev=%s sensor=%s type=%d\n",
256 __func__, see->see_pes.pes_dvname, sensor, type));
257 LIST_REMOVE(see, see_list);
258 /*
259 * So the events list is empty, we'll do the following:
260 *
261 * - stop and destroy the callout.
262 * - destroy the workqueue.
263 */
264 if (LIST_EMPTY(&sme->sme_events_list))
265 sme_events_destroy(sme);
266 mutex_exit(&sme->sme_mtx);
267
268 kmem_free(see, sizeof(*see));
269 return 0;
270 }
271
272 /*
273 * sme_event_drvadd:
274 *
275 * + Registers a new event for a device that had enabled any of
276 * the monitoring flags in the driver.
277 */
278 void
279 sme_event_drvadd(void *arg)
280 {
281 sme_event_drv_t *sed_t = arg;
282 int error = 0;
283
284 KASSERT(sed_t != NULL);
285
286 #define SEE_REGEVENT(a, b, c) \
287 do { \
288 if (sed_t->sed_edata->flags & (a)) { \
289 char str[ENVSYS_DESCLEN] = "monitoring-state-"; \
290 \
291 error = sme_event_register(sed_t->sed_sdict, \
292 sed_t->sed_edata, \
293 sed_t->sed_sme, \
294 NULL, \
295 0, \
296 (b), \
297 sed_t->sed_powertype); \
298 if (error && error != EEXIST) \
299 printf("%s: failed to add event! " \
300 "error=%d sensor=%s event=%s\n", \
301 __func__, error, \
302 sed_t->sed_edata->desc, (c)); \
303 else { \
304 (void)strlcat(str, (c), sizeof(str)); \
305 prop_dictionary_set_bool(sed_t->sed_sdict, \
306 str, \
307 true); \
308 } \
309 } \
310 } while (/* CONSTCOND */ 0)
311
312 SEE_REGEVENT(ENVSYS_FMONCRITICAL,
313 PENVSYS_EVENT_CRITICAL,
314 "critical");
315
316 SEE_REGEVENT(ENVSYS_FMONCRITUNDER,
317 PENVSYS_EVENT_CRITUNDER,
318 "critunder");
319
320 SEE_REGEVENT(ENVSYS_FMONCRITOVER,
321 PENVSYS_EVENT_CRITOVER,
322 "critover");
323
324 SEE_REGEVENT(ENVSYS_FMONWARNUNDER,
325 PENVSYS_EVENT_WARNUNDER,
326 "warnunder");
327
328 SEE_REGEVENT(ENVSYS_FMONWARNOVER,
329 PENVSYS_EVENT_WARNOVER,
330 "warnover");
331
332 SEE_REGEVENT(ENVSYS_FMONSTCHANGED,
333 PENVSYS_EVENT_STATE_CHANGED,
334 "state-changed");
335
336 /*
337 * we are done, free memory now.
338 */
339 kmem_free(sed_t, sizeof(*sed_t));
340 }
341
342 /*
343 * sme_events_init:
344 *
345 * + Initialize the events framework for this device.
346 */
347 int
348 sme_events_init(struct sysmon_envsys *sme)
349 {
350 int error = 0;
351 uint64_t timo;
352
353 KASSERT(sme != NULL);
354 KASSERT(mutex_owned(&sme->sme_mtx));
355
356 if (sme->sme_events_timeout)
357 timo = sme->sme_events_timeout * hz;
358 else
359 timo = SME_EVTIMO;
360
361 error = workqueue_create(&sme->sme_wq, sme->sme_name,
362 sme_events_worker, sme, PRI_NONE, IPL_SOFTCLOCK, WQ_MPSAFE);
363 if (error)
364 return error;
365
366 mutex_init(&sme->sme_callout_mtx, MUTEX_DEFAULT, IPL_SOFTCLOCK);
367 callout_setfunc(&sme->sme_callout, sme_events_check, sme);
368 callout_schedule(&sme->sme_callout, timo);
369 sme->sme_flags |= SME_CALLOUT_INITIALIZED;
370 DPRINTF(("%s: events framework initialized for '%s'\n",
371 __func__, sme->sme_name));
372
373 return error;
374 }
375
376 /*
377 * sme_events_destroy:
378 *
379 * + Destroys the event framework for this device: callout
380 * stopped, workqueue destroyed and callout mutex destroyed.
381 */
382 void
383 sme_events_destroy(struct sysmon_envsys *sme)
384 {
385 KASSERT(mutex_owned(&sme->sme_mtx));
386
387 callout_stop(&sme->sme_callout);
388 sme->sme_flags &= ~SME_CALLOUT_INITIALIZED;
389 DPRINTF(("%s: events framework destroyed for '%s'\n",
390 __func__, sme->sme_name));
391 workqueue_destroy(sme->sme_wq);
392 mutex_destroy(&sme->sme_callout_mtx);
393 }
394
395 /*
396 * sme_events_check:
397 *
398 * + Passes the events to the workqueue thread and stops
399 * the callout if the 'low-power' condition is triggered.
400 */
401 void
402 sme_events_check(void *arg)
403 {
404 struct sysmon_envsys *sme = arg;
405 sme_event_t *see;
406 uint64_t timo;
407
408 KASSERT(sme != NULL);
409
410 mutex_enter(&sme->sme_callout_mtx);
411 LIST_FOREACH(see, &sme->sme_events_list, see_list) {
412 workqueue_enqueue(sme->sme_wq, &see->see_wk, NULL);
413 see->see_flags &= ~SME_EVENT_REFRESHED;
414 }
415 if (sme->sme_events_timeout)
416 timo = sme->sme_events_timeout * hz;
417 else
418 timo = SME_EVTIMO;
419 if (!sysmon_low_power)
420 callout_schedule(&sme->sme_callout, timo);
421 mutex_exit(&sme->sme_callout_mtx);
422 }
423
424 /*
425 * sme_events_worker:
426 *
427 * + workqueue thread that checks if there's a critical condition
428 * and sends an event if it was triggered.
429 */
430 void
431 sme_events_worker(struct work *wk, void *arg)
432 {
433 const struct sme_description_table *sdt = NULL;
434 const struct sme_sensor_event *sse = sme_sensor_event;
435 sme_event_t *see = (void *)wk;
436 struct sysmon_envsys *sme = see->see_sme;
437 envsys_data_t *edata = see->see_edata;
438 int i, state = 0;
439
440 KASSERT(wk == &see->see_wk);
441 KASSERT(sme != NULL || edata != NULL);
442
443 mutex_enter(&sme->sme_mtx);
444 if ((see->see_flags & SME_EVENT_WORKING) == 0)
445 see->see_flags |= SME_EVENT_WORKING;
446 /*
447 * refresh the sensor that was marked with a critical event
448 * only if it wasn't refreshed before or if the driver doesn't
449 * use its own method for refreshing.
450 */
451 if ((sme->sme_flags & SME_DISABLE_REFRESH) == 0) {
452 if ((see->see_flags & SME_EVENT_REFRESHED) == 0) {
453 /* refresh sensor in device */
454 (*sme->sme_refresh)(sme, edata);
455 see->see_flags |= SME_EVENT_REFRESHED;
456 }
457 }
458
459 DPRINTFOBJ(("%s: (%s) desc=%s sensor=%d state=%d units=%d "
460 "value_cur=%d\n", __func__, sme->sme_name, edata->desc,
461 edata->sensor, edata->state, edata->units, edata->value_cur));
462
463 /* skip the event if current sensor is in invalid state */
464 if (edata->state == ENVSYS_SINVALID)
465 goto out;
466
467 #define SME_SEND_NORMALEVENT() \
468 do { \
469 see->see_evsent = false; \
470 sysmon_penvsys_event(&see->see_pes, PENVSYS_EVENT_NORMAL); \
471 } while (/* CONSTCOND */ 0)
472
473 #define SME_SEND_EVENT(type) \
474 do { \
475 see->see_evsent = true; \
476 sysmon_penvsys_event(&see->see_pes, (type)); \
477 } while (/* CONSTCOND */ 0)
478
479
480 switch (see->see_type) {
481 /*
482 * if state is the same than the one that matches sse[i].state,
483 * send the event...
484 */
485 case PENVSYS_EVENT_CRITICAL:
486 case PENVSYS_EVENT_CRITUNDER:
487 case PENVSYS_EVENT_CRITOVER:
488 case PENVSYS_EVENT_WARNUNDER:
489 case PENVSYS_EVENT_WARNOVER:
490 for (i = 0; sse[i].state != -1; i++)
491 if (sse[i].event == see->see_type)
492 break;
493
494 if (sse[i].state == -1)
495 break;
496
497 if (see->see_evsent && edata->state == ENVSYS_SVALID)
498 SME_SEND_NORMALEVENT();
499
500 if (!see->see_evsent && edata->state == sse[i].state)
501 SME_SEND_EVENT(see->see_type);
502
503 break;
504 /*
505 * if value_cur is lower than the limit, send the event...
506 */
507 case PENVSYS_EVENT_BATT_USERCAP:
508 case PENVSYS_EVENT_USER_CRITMIN:
509 if (see->see_evsent && edata->value_cur > see->see_critval)
510 SME_SEND_NORMALEVENT();
511
512 if (!see->see_evsent && edata->value_cur < see->see_critval)
513 SME_SEND_EVENT(see->see_type);
514
515 break;
516 /*
517 * if value_cur is higher than the limit, send the event...
518 */
519 case PENVSYS_EVENT_USER_CRITMAX:
520 if (see->see_evsent && edata->value_cur < see->see_critval)
521 SME_SEND_NORMALEVENT();
522
523 if (!see->see_evsent && edata->value_cur > see->see_critval)
524 SME_SEND_EVENT(see->see_type);
525
526 break;
527 /*
528 * if value_cur is not normal (battery) or online (drive),
529 * send the event...
530 */
531 case PENVSYS_EVENT_STATE_CHANGED:
532 /*
533 * the state has not been changed, just ignore the event.
534 */
535 if (edata->value_cur == see->see_evsent)
536 break;
537
538 switch (edata->units) {
539 case ENVSYS_DRIVE:
540 sdt = sme_get_description_table(SME_DESC_DRIVE_STATES);
541 state = ENVSYS_DRIVE_ONLINE;
542 break;
543 case ENVSYS_BATTERY_CAPACITY:
544 sdt = sme_get_description_table(
545 SME_DESC_BATTERY_CAPACITY);
546 state = ENVSYS_BATTERY_CAPACITY_NORMAL;
547 break;
548 default:
549 panic("%s: invalid units for ENVSYS_FMONSTCHANGED",
550 __func__);
551 }
552
553 for (i = 0; sdt[i].type != -1; i++)
554 if (sdt[i].type == edata->value_cur)
555 break;
556
557 if (sdt[i].type == -1)
558 break;
559
560 /*
561 * copy current state description.
562 */
563 (void)strlcpy(see->see_pes.pes_statedesc, sdt[i].desc,
564 sizeof(see->see_pes.pes_statedesc));
565
566 /*
567 * state is ok again... send a normal event.
568 */
569 if (see->see_evsent && edata->value_cur == state)
570 SME_SEND_NORMALEVENT();
571
572 /*
573 * state has been changed... send event.
574 */
575 if (see->see_evsent || edata->value_cur != state) {
576 /*
577 * save current drive state.
578 */
579 see->see_evsent = edata->value_cur;
580 sysmon_penvsys_event(&see->see_pes, see->see_type);
581 }
582
583 /*
584 * There's no need to continue if it's a drive sensor.
585 */
586 if (edata->units == ENVSYS_DRIVE)
587 break;
588
589 /*
590 * Check if the system is running in low power and send the
591 * event to powerd (if running) or shutdown the system
592 * otherwise.
593 */
594 if (!sysmon_low_power && sme_event_check_low_power()) {
595 struct penvsys_state pes;
596
597 /*
598 * Stop the callout and send the 'low-power' event.
599 */
600 sysmon_low_power = true;
601 callout_stop(&sme->sme_callout);
602 pes.pes_type = PENVSYS_TYPE_BATTERY;
603 sysmon_penvsys_event(&pes, PENVSYS_EVENT_LOW_POWER);
604 }
605 break;
606 }
607
608 out:
609 see->see_flags &= ~SME_EVENT_WORKING;
610 cv_broadcast(&sme->sme_condvar);
611 mutex_exit(&sme->sme_mtx);
612 }
613
614 /*
615 * Returns true if the system is in low power state: an AC adapter
616 * is OFF and all batteries are in LOW/CRITICAL state.
617 */
618 static bool
619 sme_event_check_low_power(void)
620 {
621 if (!sme_acadapter_check())
622 return false;
623
624 return sme_battery_check();
625 }
626
627 /*
628 * Called with the sysmon_envsys device mtx held through the
629 * workqueue thread.
630 */
631 static bool
632 sme_acadapter_check(void)
633 {
634 struct sysmon_envsys *sme;
635 envsys_data_t *edata;
636 bool dev = false, sensor = false;
637
638 LIST_FOREACH(sme, &sysmon_envsys_list, sme_list) {
639 if (sme->sme_class == SME_CLASS_ACADAPTER) {
640 dev = true;
641 break;
642 }
643 }
644
645 /*
646 * No AC Adapter devices were found.
647 */
648 if (!dev)
649 return false;
650
651 /*
652 * Check if there's an AC adapter device connected.
653 */
654 TAILQ_FOREACH(edata, &sme->sme_sensors_list, sensors_head) {
655 if (edata->units == ENVSYS_INDICATOR) {
656 sensor = true;
657 /* refresh current sensor */
658 (*sme->sme_refresh)(sme, edata);
659 if (edata->value_cur)
660 return false;
661 }
662 }
663
664 if (!sensor)
665 return false;
666
667 /*
668 * AC adapter found and not connected.
669 */
670 return true;
671 }
672
673 /*
674 * Called with the sysmon_envsys device mtx held through the
675 * workqueue thread.
676 */
677 static bool
678 sme_battery_check(void)
679 {
680 struct sysmon_envsys *sme;
681 envsys_data_t *edata;
682 bool battery, batterycap, batterycharge;
683
684 battery = batterycap = batterycharge = false;
685
686 /*
687 * Check for battery devices and its state.
688 */
689 LIST_FOREACH(sme, &sysmon_envsys_list, sme_list) {
690 if (sme->sme_class != SME_CLASS_BATTERY)
691 continue;
692
693 /*
694 * We've found a battery device...
695 */
696 battery = true;
697 TAILQ_FOREACH(edata, &sme->sme_sensors_list, sensors_head) {
698 if (edata->units == ENVSYS_BATTERY_CAPACITY) {
699 batterycap = true;
700 if (!sme_battery_critical(edata))
701 return false;
702 } else if (edata->units == ENVSYS_BATTERY_CHARGE) {
703 batterycharge = true;
704 if (edata->value_cur)
705 return false;
706 }
707 }
708 }
709
710 if (!battery || !batterycap || !batterycharge)
711 return false;
712
713 /*
714 * All batteries in low/critical capacity and discharging.
715 */
716 return true;
717 }
718
719 static bool
720 sme_battery_critical(envsys_data_t *edata)
721 {
722 if (edata->value_cur == ENVSYS_BATTERY_CAPACITY_CRITICAL ||
723 edata->value_cur == ENVSYS_BATTERY_CAPACITY_LOW)
724 return true;
725
726 return false;
727 }
728