1 /* $NetBSD: sysmon_envsys_events.c,v 1.48 2008/02/02 02:02:38 xtraeme Exp $ */ 2 3 /*- 4 * Copyright (c) 2007 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.48 2008/02/02 02:02:38 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 kmutex_t sme_mtx, sme_events_mtx, sme_callout_mtx; 66 kcondvar_t sme_cv; 67 static bool sysmon_low_power = false; 68 69 #define SME_EVTIMO (SME_EVENTS_DEFTIMEOUT * hz) 70 71 static bool sme_event_check_low_power(void); 72 static bool sme_battery_check(void); 73 static bool sme_battery_critical(envsys_data_t *); 74 static bool sme_acadapter_check(void); 75 76 /* 77 * sme_event_register: 78 * 79 * + Registers a new sysmon envsys event or updates any event 80 * already in the queue. 81 */ 82 int 83 sme_event_register(prop_dictionary_t sdict, envsys_data_t *edata, 84 struct sysmon_envsys *sme, const char *objkey, 85 int32_t critval, int crittype, int powertype) 86 { 87 sme_event_t *see = NULL; 88 prop_object_t obj; 89 bool critvalup = false; 90 int error = 0; 91 92 KASSERT(sdict != NULL || edata != NULL || sme != NULL); 93 KASSERT(mutex_owned(&sme_mtx)); 94 95 /* 96 * check if the event is already on the list and return 97 * EEXIST if value provided hasn't been changed. 98 */ 99 LIST_FOREACH(see, &sme->sme_events_list, see_list) { 100 if (strcmp(edata->desc, see->see_pes.pes_sensname) == 0) 101 if (crittype == see->see_type) { 102 if (see->see_critval == critval) { 103 DPRINTF(("%s: dev=%s sensor=%s type=%d " 104 "(already exists)\n", __func__, 105 see->see_pes.pes_dvname, 106 see->see_pes.pes_sensname, 107 see->see_type)); 108 return EEXIST; 109 } 110 critvalup = true; 111 break; 112 } 113 } 114 115 /* 116 * Critical condition operation requested by userland. 117 */ 118 if (objkey && critval && critvalup) { 119 obj = prop_dictionary_get(sdict, objkey); 120 if (obj && prop_object_type(obj) == PROP_TYPE_NUMBER) { 121 /* 122 * object is already in dictionary and value 123 * provided is not the same than we have 124 * currently, update the critical value. 125 */ 126 see->see_critval = critval; 127 DPRINTF(("%s: (%s) sensor=%s type=%d " 128 "(critval updated)\n", __func__, sme->sme_name, 129 edata->desc, see->see_type)); 130 error = sme_sensor_upint32(sdict, objkey, critval); 131 return error; 132 } 133 } 134 135 /* 136 * The event is not in on the list or in a dictionary, create a new 137 * sme event, assign required members and update the object in 138 * the dictionary. 139 */ 140 see = kmem_zalloc(sizeof(*see), KM_NOSLEEP); 141 if (see == NULL) 142 return ENOMEM; 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) registering 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 mutex_enter(&sme_events_mtx); 168 if ((sme->sme_flags & SME_CALLOUT_INITIALIZED) == 0) 169 error = sme_events_init(sme); 170 mutex_exit(&sme_events_mtx); 171 out: 172 if (error) 173 kmem_free(see, sizeof(*see)); 174 return error; 175 } 176 177 /* 178 * sme_event_unregister_all: 179 * 180 * + Unregisters all sysmon envsys events associated with a 181 * sysmon envsys device. 182 */ 183 void 184 sme_event_unregister_all(struct sysmon_envsys *sme) 185 { 186 sme_event_t *see; 187 int evcounter = 0; 188 189 KASSERT(mutex_owned(&sme_mtx)); 190 KASSERT(sme != NULL); 191 192 LIST_FOREACH(see, &sme->sme_events_list, see_list) { 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 DPRINTF(("%s: event %s %d removed (%s)\n", __func__, 206 see->see_pes.pes_sensname, see->see_type, 207 sme->sme_name)); 208 209 while (see->see_flags & SME_EVENT_WORKING) 210 cv_wait(&sme_cv, &sme_mtx); 211 212 LIST_REMOVE(see, see_list); 213 kmem_free(see, sizeof(*see)); 214 evcounter--; 215 } 216 } 217 218 if (LIST_EMPTY(&sme->sme_events_list)) { 219 mutex_enter(&sme_events_mtx); 220 if (sme->sme_flags & SME_CALLOUT_INITIALIZED) 221 sme_events_destroy(sme); 222 mutex_exit(&sme_events_mtx); 223 } 224 } 225 226 /* 227 * sme_event_unregister: 228 * 229 * + Unregisters an event from the specified sysmon envsys device. 230 */ 231 int 232 sme_event_unregister(struct sysmon_envsys *sme, const char *sensor, int type) 233 { 234 sme_event_t *see; 235 bool found = false; 236 237 KASSERT(mutex_owned(&sme_mtx)); 238 KASSERT(sensor != NULL); 239 240 LIST_FOREACH(see, &sme->sme_events_list, see_list) { 241 if (strcmp(see->see_pes.pes_sensname, sensor) == 0) { 242 if (see->see_type == type) { 243 found = true; 244 break; 245 } 246 } 247 } 248 249 if (!found) 250 return EINVAL; 251 252 while (see->see_flags & SME_EVENT_WORKING) 253 cv_wait(&sme_cv, &sme_mtx); 254 255 DPRINTF(("%s: removing 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 mutex_enter(&sme_events_mtx); 266 sme_events_destroy(sme); 267 mutex_exit(&sme_events_mtx); 268 } 269 270 kmem_free(see, sizeof(*see)); 271 return 0; 272 } 273 274 /* 275 * sme_event_drvadd: 276 * 277 * + Registers a new event for a device that had enabled any of 278 * the monitoring flags in the driver. 279 */ 280 void 281 sme_event_drvadd(void *arg) 282 { 283 sme_event_drv_t *sed_t = arg; 284 int error = 0; 285 286 KASSERT(sed_t != NULL); 287 288 #define SEE_REGEVENT(a, b, c) \ 289 do { \ 290 if (sed_t->sed_edata->flags & (a)) { \ 291 char str[ENVSYS_DESCLEN] = "monitoring-state-"; \ 292 \ 293 sysmon_envsys_acquire(sed_t->sed_sme); \ 294 error = sme_event_register(sed_t->sed_sdict, \ 295 sed_t->sed_edata, \ 296 sed_t->sed_sme, \ 297 NULL, \ 298 0, \ 299 (b), \ 300 sed_t->sed_powertype); \ 301 if (error && error != EEXIST) \ 302 printf("%s: failed to add event! " \ 303 "error=%d sensor=%s event=%s\n", \ 304 __func__, error, \ 305 sed_t->sed_edata->desc, (c)); \ 306 else { \ 307 (void)strlcat(str, (c), sizeof(str)); \ 308 prop_dictionary_set_bool(sed_t->sed_sdict, \ 309 str, \ 310 true); \ 311 } \ 312 sysmon_envsys_release(sed_t->sed_sme); \ 313 } \ 314 } while (/* CONSTCOND */ 0) 315 316 mutex_enter(&sme_mtx); 317 SEE_REGEVENT(ENVSYS_FMONCRITICAL, 318 PENVSYS_EVENT_CRITICAL, 319 "critical"); 320 321 SEE_REGEVENT(ENVSYS_FMONCRITUNDER, 322 PENVSYS_EVENT_CRITUNDER, 323 "critunder"); 324 325 SEE_REGEVENT(ENVSYS_FMONCRITOVER, 326 PENVSYS_EVENT_CRITOVER, 327 "critover"); 328 329 SEE_REGEVENT(ENVSYS_FMONWARNUNDER, 330 PENVSYS_EVENT_WARNUNDER, 331 "warnunder"); 332 333 SEE_REGEVENT(ENVSYS_FMONWARNOVER, 334 PENVSYS_EVENT_WARNOVER, 335 "warnover"); 336 337 SEE_REGEVENT(ENVSYS_FMONSTCHANGED, 338 PENVSYS_EVENT_STATE_CHANGED, 339 "state-changed"); 340 mutex_exit(&sme_mtx); 341 342 /* 343 * we are done, free memory now. 344 */ 345 kmem_free(sed_t, sizeof(*sed_t)); 346 } 347 348 /* 349 * sme_events_init: 350 * 351 * + Initialize the events framework for this device. 352 */ 353 int 354 sme_events_init(struct sysmon_envsys *sme) 355 { 356 int error; 357 uint64_t timo; 358 359 KASSERT(sme != NULL); 360 KASSERT(mutex_owned(&sme_events_mtx)); 361 362 if (sme->sme_events_timeout) 363 timo = sme->sme_events_timeout * hz; 364 else 365 timo = SME_EVTIMO; 366 367 error = workqueue_create(&sme->sme_wq, sme->sme_name, 368 sme_events_worker, sme, PRI_NONE, IPL_SOFTCLOCK, WQ_MPSAFE); 369 if (error) 370 goto out; 371 372 callout_setfunc(&sme->sme_callout, sme_events_check, sme); 373 callout_schedule(&sme->sme_callout, timo); 374 sme->sme_flags |= SME_CALLOUT_INITIALIZED; 375 DPRINTF(("%s: events framework initialized for '%s'\n", 376 __func__, sme->sme_name)); 377 378 out: 379 return error; 380 } 381 382 /* 383 * sme_events_destroy: 384 * 385 * + Destroys the events framework for this device: the callout 386 * is stopped and its workqueue is destroyed because the queue 387 * is empty. 388 */ 389 void 390 sme_events_destroy(struct sysmon_envsys *sme) 391 { 392 KASSERT(mutex_owned(&sme_events_mtx)); 393 394 callout_stop(&sme->sme_callout); 395 sme->sme_flags &= ~SME_CALLOUT_INITIALIZED; 396 DPRINTF(("%s: events framework destroyed for '%s'\n", 397 __func__, sme->sme_name)); 398 workqueue_destroy(sme->sme_wq); 399 } 400 401 /* 402 * sme_events_check: 403 * 404 * + Runs the work on the passed sysmon envsys device for all 405 * registered events. 406 */ 407 void 408 sme_events_check(void *arg) 409 { 410 struct sysmon_envsys *sme = arg; 411 sme_event_t *see; 412 uint64_t timo; 413 414 KASSERT(sme != NULL); 415 416 mutex_enter(&sme_callout_mtx); 417 418 LIST_FOREACH(see, &sme->sme_events_list, see_list) 419 workqueue_enqueue(sme->sme_wq, &see->see_wk, NULL); 420 421 /* 422 * Now that the events list was checked, reset the refresh value. 423 */ 424 LIST_FOREACH(see, &sme->sme_events_list, see_list) 425 see->see_flags &= ~SME_EVENT_REFRESHED; 426 427 if (sme->sme_events_timeout) 428 timo = sme->sme_events_timeout * hz; 429 else 430 timo = SME_EVTIMO; 431 432 if (!sysmon_low_power) 433 callout_schedule(&sme->sme_callout, timo); 434 435 mutex_exit(&sme_callout_mtx); 436 } 437 438 /* 439 * sme_events_worker: 440 * 441 * + workqueue thread that checks if there's a critical condition 442 * and sends an event if the condition was triggered. 443 */ 444 void 445 sme_events_worker(struct work *wk, void *arg) 446 { 447 const struct sme_description_table *sdt = NULL; 448 const struct sme_sensor_event *sse = sme_sensor_event; 449 sme_event_t *see = (void *)wk; 450 struct sysmon_envsys *sme; 451 int i, state, error; 452 453 KASSERT(wk == &see->see_wk); 454 KASSERT(see != NULL); 455 456 state = error = 0; 457 458 mutex_enter(&sme_mtx); 459 see->see_flags |= SME_EVENT_WORKING; 460 sme = see->see_sme; 461 462 /* 463 * refresh the sensor that was marked with a critical event 464 * only if it wasn't refreshed before or if the driver doesn't 465 * use its own method for refreshing. 466 */ 467 if ((sme->sme_flags & SME_DISABLE_REFRESH) == 0) { 468 if ((see->see_flags & SME_EVENT_REFRESHED) == 0) { 469 (*sme->sme_refresh)(sme, see->see_edata); 470 see->see_flags |= SME_EVENT_REFRESHED; 471 } 472 } 473 474 DPRINTFOBJ(("%s: (%s) desc=%s sensor=%d units=%d value_cur=%d\n", 475 __func__, sme->sme_name, see->see_edata->desc, 476 see->see_edata->sensor, 477 see->see_edata->units, see->see_edata->value_cur)); 478 479 #define SME_SEND_NORMALEVENT() \ 480 do { \ 481 see->see_evsent = false; \ 482 sysmon_penvsys_event(&see->see_pes, PENVSYS_EVENT_NORMAL); \ 483 } while (/* CONSTCOND */ 0) 484 485 #define SME_SEND_EVENT(type) \ 486 do { \ 487 see->see_evsent = true; \ 488 sysmon_penvsys_event(&see->see_pes, (type)); \ 489 } while (/* CONSTCOND */ 0) 490 491 492 switch (see->see_type) { 493 /* 494 * if state is the same than the one that matches sse[i].state, 495 * send the event... 496 */ 497 case PENVSYS_EVENT_CRITICAL: 498 case PENVSYS_EVENT_CRITUNDER: 499 case PENVSYS_EVENT_CRITOVER: 500 case PENVSYS_EVENT_WARNUNDER: 501 case PENVSYS_EVENT_WARNOVER: 502 for (i = 0; sse[i].state != -1; i++) 503 if (sse[i].event == see->see_type) 504 break; 505 506 if (see->see_evsent && see->see_edata->state == ENVSYS_SVALID) 507 SME_SEND_NORMALEVENT(); 508 509 if (!see->see_evsent && see->see_edata->state == sse[i].state) 510 SME_SEND_EVENT(see->see_type); 511 512 break; 513 /* 514 * if value_cur is lower than the limit, send the event... 515 */ 516 case PENVSYS_EVENT_BATT_USERCAP: 517 case PENVSYS_EVENT_USER_CRITMIN: 518 if (see->see_evsent && 519 see->see_edata->value_cur > see->see_critval) 520 SME_SEND_NORMALEVENT(); 521 522 if (!see->see_evsent && 523 see->see_edata->value_cur < see->see_critval) 524 SME_SEND_EVENT(see->see_type); 525 526 break; 527 /* 528 * if value_cur is higher than the limit, send the event... 529 */ 530 case PENVSYS_EVENT_USER_CRITMAX: 531 if (see->see_evsent && 532 see->see_edata->value_cur < see->see_critval) 533 SME_SEND_NORMALEVENT(); 534 535 if (!see->see_evsent && 536 see->see_edata->value_cur > see->see_critval) 537 SME_SEND_EVENT(see->see_type); 538 539 break; 540 /* 541 * if value_cur is not normal (battery) or online (drive), 542 * send the event... 543 */ 544 case PENVSYS_EVENT_STATE_CHANGED: 545 /* 546 * the state has not been changed, just ignore the event. 547 */ 548 if (see->see_edata->value_cur == see->see_evsent) 549 break; 550 551 switch (see->see_edata->units) { 552 case ENVSYS_DRIVE: 553 sdt = sme_get_description_table(SME_DESC_DRIVE_STATES); 554 state = ENVSYS_DRIVE_ONLINE; 555 break; 556 case ENVSYS_BATTERY_CAPACITY: 557 sdt = 558 sme_get_description_table(SME_DESC_BATTERY_CAPACITY); 559 state = ENVSYS_BATTERY_CAPACITY_NORMAL; 560 break; 561 default: 562 panic("%s: invalid units for ENVSYS_FMONSTCHANGED", 563 __func__); 564 } 565 566 for (i = 0; sdt[i].type != -1; i++) 567 if (sdt[i].type == see->see_edata->value_cur) 568 break; 569 570 /* 571 * copy current state description. 572 */ 573 (void)strlcpy(see->see_pes.pes_statedesc, sdt[i].desc, 574 sizeof(see->see_pes.pes_statedesc)); 575 576 /* 577 * state is ok again... send a normal event. 578 */ 579 if (see->see_evsent && see->see_edata->value_cur == state) 580 SME_SEND_NORMALEVENT(); 581 582 /* 583 * state has been changed... send event. 584 */ 585 if (see->see_evsent || see->see_edata->value_cur != state) { 586 /* 587 * save current drive state. 588 */ 589 see->see_evsent = see->see_edata->value_cur; 590 sysmon_penvsys_event(&see->see_pes, see->see_type); 591 } 592 593 /* 594 * Check if the system is running in low power and send the 595 * event to powerd (if running) or shutdown the system 596 * otherwise. 597 */ 598 if (!sysmon_low_power && sme_event_check_low_power()) { 599 struct penvsys_state pes; 600 601 /* 602 * Stop the callout and send the 'low-power' event. 603 */ 604 sysmon_low_power = true; 605 callout_stop(&sme->sme_callout); 606 pes.pes_type = PENVSYS_TYPE_BATTERY; 607 sysmon_penvsys_event(&pes, PENVSYS_EVENT_LOW_POWER); 608 } 609 break; 610 } 611 612 see->see_flags &= ~SME_EVENT_WORKING; 613 cv_broadcast(&sme_cv); 614 mutex_exit(&sme_mtx); 615 } 616 617 /* 618 * Returns true if the system is in low power state: all AC adapters 619 * are OFF and all batteries are in LOW/CRITICAL state. 620 */ 621 static bool 622 sme_event_check_low_power(void) 623 { 624 KASSERT(mutex_owned(&sme_mtx)); 625 626 if (!sme_acadapter_check()) 627 return false; 628 629 return sme_battery_check(); 630 } 631 632 static bool 633 sme_acadapter_check(void) 634 { 635 struct sysmon_envsys *sme; 636 envsys_data_t *edata; 637 bool sensor = false; 638 639 KASSERT(mutex_owned(&sme_mtx)); 640 641 LIST_FOREACH(sme, &sysmon_envsys_list, sme_list) 642 if (sme->sme_class == SME_CLASS_ACADAPTER) 643 break; 644 /* 645 * No AC Adapter devices were found. 646 */ 647 if (!sme) 648 return false; 649 /* 650 * Check if there's an AC adapter device connected. 651 */ 652 TAILQ_FOREACH(edata, &sme->sme_sensors_list, sensors_head) { 653 if (edata->units == ENVSYS_INDICATOR) { 654 sensor = true; 655 if (edata->value_cur) 656 return false; 657 } 658 } 659 if (!sensor) 660 return false; 661 662 return true; 663 } 664 665 static bool 666 sme_battery_check(void) 667 { 668 struct sysmon_envsys *sme; 669 envsys_data_t *edata; 670 bool battery, batterycap, batterycharge; 671 672 KASSERT(mutex_owned(&sme_mtx)); 673 674 battery = batterycap = batterycharge = false; 675 676 /* 677 * Check for battery devices and its state. 678 */ 679 LIST_FOREACH(sme, &sysmon_envsys_list, sme_list) { 680 if (sme->sme_class != SME_CLASS_BATTERY) 681 continue; 682 683 /* 684 * We've found a battery device... 685 */ 686 battery = true; 687 TAILQ_FOREACH(edata, &sme->sme_sensors_list, sensors_head) { 688 if (edata->units == ENVSYS_BATTERY_CAPACITY) { 689 batterycap = true; 690 if (!sme_battery_critical(edata)) 691 return false; 692 } else if (edata->units == ENVSYS_BATTERY_CHARGE) { 693 batterycharge = true; 694 if (edata->value_cur) 695 return false; 696 } 697 } 698 } 699 if (!battery || !batterycap || !batterycharge) 700 return false; 701 702 /* 703 * All batteries in low/critical capacity and discharging. 704 */ 705 return true; 706 } 707 708 static bool 709 sme_battery_critical(envsys_data_t *edata) 710 { 711 KASSERT(mutex_owned(&sme_mtx)); 712 713 if (edata->value_cur == ENVSYS_BATTERY_CAPACITY_CRITICAL || 714 edata->value_cur == ENVSYS_BATTERY_CAPACITY_LOW) 715 return true; 716 717 return false; 718 } 719