1 /* $NetBSD: sysmon_envsys_events.c,v 1.46 2007/11/20 17:24:32 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.46 2007/11/20 17:24:32 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_critical(envsys_data_t *); 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; 86 prop_object_t obj; 87 bool critvalup = false; 88 int error = 0; 89 90 KASSERT(sdict != NULL || edata != NULL || sme != NULL); 91 KASSERT(mutex_owned(&sme_mtx)); 92 93 /* 94 * check if the event is already on the list and return 95 * EEXIST if value provided hasn't been changed. 96 */ 97 LIST_FOREACH(see, &sme->sme_events_list, see_list) { 98 if (strcmp(edata->desc, see->see_pes.pes_sensname) == 0) 99 if (crittype == see->see_type) { 100 if (see->see_critval == critval) { 101 DPRINTF(("%s: dev=%s sensor=%s type=%d " 102 "(already exists)\n", __func__, 103 see->see_pes.pes_dvname, 104 see->see_pes.pes_sensname, 105 see->see_type)); 106 return EEXIST; 107 } 108 critvalup = true; 109 break; 110 } 111 } 112 113 /* 114 * Critical condition operation requested by userland. 115 */ 116 if (objkey && critval && critvalup) { 117 obj = prop_dictionary_get(sdict, objkey); 118 if (obj && prop_object_type(obj) == PROP_TYPE_NUMBER) { 119 /* 120 * object is already in dictionary and value 121 * provided is not the same than we have 122 * currently, update the critical value. 123 */ 124 see->see_critval = critval; 125 DPRINTF(("%s: (%s) sensor=%s type=%d " 126 "(critval updated)\n", __func__, sme->sme_name, 127 edata->desc, see->see_type)); 128 error = sme_sensor_upint32(sdict, objkey, critval); 129 return error; 130 } 131 } 132 133 /* 134 * The event is not in on the list or in a dictionary, create a new 135 * sme event, assign required members and update the object in 136 * the dictionary. 137 */ 138 see = kmem_zalloc(sizeof(*see), KM_NOSLEEP); 139 if (see == NULL) 140 return ENOMEM; 141 142 see->see_edata = edata; 143 see->see_critval = critval; 144 see->see_type = crittype; 145 see->see_sme = sme; 146 (void)strlcpy(see->see_pes.pes_dvname, sme->sme_name, 147 sizeof(see->see_pes.pes_dvname)); 148 see->see_pes.pes_type = powertype; 149 (void)strlcpy(see->see_pes.pes_sensname, edata->desc, 150 sizeof(see->see_pes.pes_sensname)); 151 152 LIST_INSERT_HEAD(&sme->sme_events_list, see, see_list); 153 if (objkey && critval) { 154 error = sme_sensor_upint32(sdict, objkey, critval); 155 if (error) 156 goto out; 157 } 158 DPRINTF(("%s: (%s) registering sensor=%s snum=%d type=%d " 159 "critval=%" PRIu32 "\n", __func__, 160 see->see_sme->sme_name, see->see_pes.pes_sensname, 161 see->see_edata->sensor, see->see_type, see->see_critval)); 162 /* 163 * Initialize the events framework if it wasn't initialized before. 164 */ 165 mutex_enter(&sme_events_mtx); 166 if ((sme->sme_flags & SME_CALLOUT_INITIALIZED) == 0) 167 error = sme_events_init(sme); 168 mutex_exit(&sme_events_mtx); 169 out: 170 if (error) 171 kmem_free(see, sizeof(*see)); 172 return error; 173 } 174 175 /* 176 * sme_event_unregister_all: 177 * 178 * + Unregisters all sysmon envsys events associated with a 179 * 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(mutex_owned(&sme_mtx)); 188 KASSERT(sme != NULL); 189 190 LIST_FOREACH(see, &sme->sme_events_list, see_list) { 191 if (strcmp(see->see_pes.pes_dvname, sme->sme_name) == 0) 192 evcounter++; 193 } 194 195 DPRINTF(("%s: total events %d (%s)\n", __func__, 196 evcounter, sme->sme_name)); 197 198 while ((see = LIST_FIRST(&sme->sme_events_list))) { 199 if (evcounter == 0) 200 break; 201 202 if (strcmp(see->see_pes.pes_dvname, sme->sme_name) == 0) { 203 DPRINTF(("%s: event %s %d removed (%s)\n", __func__, 204 see->see_pes.pes_sensname, see->see_type, 205 sme->sme_name)); 206 207 while (see->see_flags & SME_EVENT_WORKING) 208 cv_wait(&sme_cv, &sme_mtx); 209 210 LIST_REMOVE(see, see_list); 211 kmem_free(see, sizeof(*see)); 212 evcounter--; 213 } 214 } 215 216 if (LIST_EMPTY(&sme->sme_events_list)) { 217 mutex_enter(&sme_events_mtx); 218 if (sme->sme_flags & SME_CALLOUT_INITIALIZED) 219 sme_events_destroy(sme); 220 mutex_exit(&sme_events_mtx); 221 } 222 } 223 224 /* 225 * sme_event_unregister: 226 * 227 * + Unregisters an event from the specified sysmon envsys device. 228 */ 229 int 230 sme_event_unregister(struct sysmon_envsys *sme, const char *sensor, int type) 231 { 232 sme_event_t *see; 233 bool found = false; 234 235 KASSERT(mutex_owned(&sme_mtx)); 236 KASSERT(sensor != NULL); 237 238 LIST_FOREACH(see, &sme->sme_events_list, see_list) { 239 if (strcmp(see->see_pes.pes_sensname, sensor) == 0) { 240 if (see->see_type == type) { 241 found = true; 242 break; 243 } 244 } 245 } 246 247 if (!found) 248 return EINVAL; 249 250 while (see->see_flags & SME_EVENT_WORKING) 251 cv_wait(&sme_cv, &sme_mtx); 252 253 DPRINTF(("%s: removing dev=%s sensor=%s type=%d\n", 254 __func__, see->see_pes.pes_dvname, sensor, type)); 255 LIST_REMOVE(see, see_list); 256 /* 257 * So the events list is empty, we'll do the following: 258 * 259 * - stop and destroy the callout. 260 * - destroy the workqueue. 261 */ 262 if (LIST_EMPTY(&sme->sme_events_list)) { 263 mutex_enter(&sme_events_mtx); 264 sme_events_destroy(sme); 265 mutex_exit(&sme_events_mtx); 266 } 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 sysmon_envsys_acquire(sed_t->sed_sme); \ 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 sysmon_envsys_release(sed_t->sed_sme); \ 311 } \ 312 } while (/* CONSTCOND */ 0) 313 314 mutex_enter(&sme_mtx); 315 SEE_REGEVENT(ENVSYS_FMONCRITICAL, 316 PENVSYS_EVENT_CRITICAL, 317 "critical"); 318 319 SEE_REGEVENT(ENVSYS_FMONCRITUNDER, 320 PENVSYS_EVENT_CRITUNDER, 321 "critunder"); 322 323 SEE_REGEVENT(ENVSYS_FMONCRITOVER, 324 PENVSYS_EVENT_CRITOVER, 325 "critover"); 326 327 SEE_REGEVENT(ENVSYS_FMONWARNUNDER, 328 PENVSYS_EVENT_WARNUNDER, 329 "warnunder"); 330 331 SEE_REGEVENT(ENVSYS_FMONWARNOVER, 332 PENVSYS_EVENT_WARNOVER, 333 "warnover"); 334 335 SEE_REGEVENT(ENVSYS_FMONSTCHANGED, 336 PENVSYS_EVENT_STATE_CHANGED, 337 "state-changed"); 338 mutex_exit(&sme_mtx); 339 340 /* 341 * we are done, free memory now. 342 */ 343 kmem_free(sed_t, sizeof(*sed_t)); 344 } 345 346 /* 347 * sme_events_init: 348 * 349 * + Initialize the events framework for this device. 350 */ 351 int 352 sme_events_init(struct sysmon_envsys *sme) 353 { 354 int error; 355 uint64_t timo; 356 357 KASSERT(sme != NULL); 358 KASSERT(mutex_owned(&sme_events_mtx)); 359 360 if (sme->sme_events_timeout) 361 timo = sme->sme_events_timeout * hz; 362 else 363 timo = SME_EVTIMO; 364 365 error = workqueue_create(&sme->sme_wq, sme->sme_name, 366 sme_events_worker, sme, PRI_NONE, IPL_SOFTCLOCK, WQ_MPSAFE); 367 if (error) 368 goto out; 369 370 callout_init(&sme->sme_callout, CALLOUT_MPSAFE); 371 callout_setfunc(&sme->sme_callout, sme_events_check, sme); 372 callout_schedule(&sme->sme_callout, timo); 373 sme->sme_flags |= SME_CALLOUT_INITIALIZED; 374 DPRINTF(("%s: events framework initialized for '%s'\n", 375 __func__, sme->sme_name)); 376 377 out: 378 return error; 379 } 380 381 /* 382 * sme_events_destroy: 383 * 384 * + Destroys the events framework for this device: the workqueue and the 385 * callout are stopped/destroyed because the queue is empty. 386 */ 387 void 388 sme_events_destroy(struct sysmon_envsys *sme) 389 { 390 KASSERT(mutex_owned(&sme_events_mtx)); 391 392 callout_stop(&sme->sme_callout); 393 sme->sme_flags &= ~SME_CALLOUT_INITIALIZED; 394 DPRINTF(("%s: events framework destroyed for '%s'\n", 395 __func__, sme->sme_name)); 396 callout_destroy(&sme->sme_callout); 397 workqueue_destroy(sme->sme_wq); 398 } 399 400 /* 401 * sme_events_check: 402 * 403 * + Runs the work on the passed sysmon envsys device for all 404 * registered events. 405 */ 406 void 407 sme_events_check(void *arg) 408 { 409 struct sysmon_envsys *sme = arg; 410 sme_event_t *see; 411 uint64_t timo; 412 413 KASSERT(sme != NULL); 414 415 mutex_enter(&sme_callout_mtx); 416 417 LIST_FOREACH(see, &sme->sme_events_list, see_list) 418 workqueue_enqueue(sme->sme_wq, &see->see_wk, NULL); 419 420 /* 421 * Now that the events list was checked, reset the refresh value. 422 */ 423 LIST_FOREACH(see, &sme->sme_events_list, see_list) 424 see->see_flags &= ~SME_EVENT_REFRESHED; 425 426 if (sme->sme_events_timeout) 427 timo = sme->sme_events_timeout * hz; 428 else 429 timo = SME_EVTIMO; 430 431 if (!sysmon_low_power) 432 callout_schedule(&sme->sme_callout, timo); 433 434 mutex_exit(&sme_callout_mtx); 435 } 436 437 /* 438 * sme_events_worker: 439 * 440 * + workqueue thread that checks if there's a critical condition 441 * and sends an event if the condition was triggered. 442 */ 443 void 444 sme_events_worker(struct work *wk, void *arg) 445 { 446 const struct sme_description_table *sdt = NULL; 447 const struct sme_sensor_event *sse = sme_sensor_event; 448 sme_event_t *see = (void *)wk; 449 struct sysmon_envsys *sme; 450 int i, state, error; 451 452 KASSERT(wk == &see->see_wk); 453 KASSERT(see != NULL); 454 455 state = error = 0; 456 457 mutex_enter(&sme_mtx); 458 see->see_flags |= SME_EVENT_WORKING; 459 sme = see->see_sme; 460 461 /* 462 * refresh the sensor that was marked with a critical event 463 * only if it wasn't refreshed before or if the driver doesn't 464 * use its own method for refreshing. 465 */ 466 if ((sme->sme_flags & SME_DISABLE_REFRESH) == 0) { 467 if ((see->see_flags & SME_EVENT_REFRESHED) == 0) { 468 (*sme->sme_refresh)(sme, see->see_edata); 469 see->see_flags |= SME_EVENT_REFRESHED; 470 } 471 } 472 473 DPRINTFOBJ(("%s: (%s) desc=%s sensor=%d units=%d value_cur=%d\n", 474 __func__, sme->sme_name, see->see_edata->desc, 475 see->see_edata->sensor, 476 see->see_edata->units, see->see_edata->value_cur)); 477 478 #define SME_SEND_NORMALEVENT() \ 479 do { \ 480 see->see_evsent = false; \ 481 sysmon_penvsys_event(&see->see_pes, PENVSYS_EVENT_NORMAL); \ 482 } while (/* CONSTCOND */ 0) 483 484 #define SME_SEND_EVENT(type) \ 485 do { \ 486 see->see_evsent = true; \ 487 sysmon_penvsys_event(&see->see_pes, (type)); \ 488 } while (/* CONSTCOND */ 0) 489 490 491 switch (see->see_type) { 492 /* 493 * if state is the same than the one that matches sse[i].state, 494 * send the event... 495 */ 496 case PENVSYS_EVENT_CRITICAL: 497 case PENVSYS_EVENT_CRITUNDER: 498 case PENVSYS_EVENT_CRITOVER: 499 case PENVSYS_EVENT_WARNUNDER: 500 case PENVSYS_EVENT_WARNOVER: 501 for (i = 0; sse[i].state != -1; i++) 502 if (sse[i].event == see->see_type) 503 break; 504 505 if (see->see_evsent && see->see_edata->state == ENVSYS_SVALID) 506 SME_SEND_NORMALEVENT(); 507 508 if (!see->see_evsent && see->see_edata->state == sse[i].state) 509 SME_SEND_EVENT(see->see_type); 510 511 break; 512 /* 513 * if value_cur is lower than the limit, send the event... 514 */ 515 case PENVSYS_EVENT_BATT_USERCAP: 516 case PENVSYS_EVENT_USER_CRITMIN: 517 if (see->see_evsent && 518 see->see_edata->value_cur > see->see_critval) 519 SME_SEND_NORMALEVENT(); 520 521 if (!see->see_evsent && 522 see->see_edata->value_cur < see->see_critval) 523 SME_SEND_EVENT(see->see_type); 524 525 break; 526 /* 527 * if value_cur is higher than the limit, send the event... 528 */ 529 case PENVSYS_EVENT_USER_CRITMAX: 530 if (see->see_evsent && 531 see->see_edata->value_cur < see->see_critval) 532 SME_SEND_NORMALEVENT(); 533 534 if (!see->see_evsent && 535 see->see_edata->value_cur > see->see_critval) 536 SME_SEND_EVENT(see->see_type); 537 538 break; 539 /* 540 * if value_cur is not normal (battery) or online (drive), 541 * send the event... 542 */ 543 case PENVSYS_EVENT_STATE_CHANGED: 544 /* 545 * the state has not been changed, just ignore the event. 546 */ 547 if (see->see_edata->value_cur == see->see_evsent) 548 break; 549 550 switch (see->see_edata->units) { 551 case ENVSYS_DRIVE: 552 sdt = sme_get_description_table(SME_DESC_DRIVE_STATES); 553 state = ENVSYS_DRIVE_ONLINE; 554 break; 555 case ENVSYS_BATTERY_CAPACITY: 556 sdt = 557 sme_get_description_table(SME_DESC_BATTERY_CAPACITY); 558 state = ENVSYS_BATTERY_CAPACITY_NORMAL; 559 break; 560 default: 561 panic("%s: invalid units for ENVSYS_FMONSTCHANGED", 562 __func__); 563 } 564 565 for (i = 0; sdt[i].type != -1; i++) 566 if (sdt[i].type == see->see_edata->value_cur) 567 break; 568 569 /* 570 * copy current state description. 571 */ 572 (void)strlcpy(see->see_pes.pes_statedesc, sdt[i].desc, 573 sizeof(see->see_pes.pes_statedesc)); 574 575 /* 576 * state is ok again... send a normal event. 577 */ 578 if (see->see_evsent && see->see_edata->value_cur == state) 579 SME_SEND_NORMALEVENT(); 580 581 /* 582 * state has been changed... send event. 583 */ 584 if (see->see_evsent || see->see_edata->value_cur != state) { 585 /* 586 * save current drive state. 587 */ 588 see->see_evsent = see->see_edata->value_cur; 589 sysmon_penvsys_event(&see->see_pes, see->see_type); 590 } 591 592 /* 593 * Check if the system is running in low power and send the 594 * event to powerd (if running) or shutdown the system 595 * otherwise. 596 */ 597 if (!sysmon_low_power && sme_event_check_low_power()) { 598 struct penvsys_state pes; 599 600 /* 601 * Stop the callout and send the 'low-power' event. 602 */ 603 sysmon_low_power = true; 604 callout_stop(&sme->sme_callout); 605 pes.pes_type = PENVSYS_TYPE_BATTERY; 606 sysmon_penvsys_event(&pes, PENVSYS_EVENT_LOW_POWER); 607 } 608 break; 609 } 610 611 see->see_flags &= ~SME_EVENT_WORKING; 612 cv_broadcast(&sme_cv); 613 mutex_exit(&sme_mtx); 614 } 615 616 static bool 617 sme_event_check_low_power(void) 618 { 619 struct sysmon_envsys *sme; 620 envsys_data_t *edata; 621 bool battery, batterycap, batterycharge; 622 623 KASSERT(mutex_owned(&sme_mtx)); 624 625 battery = batterycap = batterycharge = false; 626 627 LIST_FOREACH(sme, &sysmon_envsys_list, sme_list) 628 if (sme->sme_class == SME_CLASS_ACADAPTER) 629 break; 630 631 /* 632 * No AC Adapter devices were found, do nothing. 633 */ 634 if (!sme) 635 return false; 636 637 /* 638 * If there's an AC Adapter connected, there's no need 639 * to continue... 640 */ 641 TAILQ_FOREACH(edata, &sme->sme_sensors_list, sensors_head) { 642 if (edata->units == ENVSYS_INDICATOR) { 643 if (edata->value_cur) 644 return false; 645 } 646 } 647 648 /* 649 * Check for battery devices and its state. 650 */ 651 LIST_FOREACH(sme, &sysmon_envsys_list, sme_list) { 652 if (sme->sme_class != SME_CLASS_BATTERY) 653 continue; 654 655 /* 656 * We've found a battery device... 657 */ 658 battery = true; 659 TAILQ_FOREACH(edata, &sme->sme_sensors_list, sensors_head) { 660 if (edata->units == ENVSYS_BATTERY_CAPACITY) { 661 batterycap = true; 662 if (!sme_battery_critical(edata)) 663 return false; 664 } else if (edata->units == ENVSYS_BATTERY_CHARGE) { 665 batterycharge = true; 666 if (edata->value_cur) 667 return false; 668 } 669 } 670 } 671 if (!battery || !batterycap || !batterycharge) 672 return false; 673 674 /* 675 * All batteries in low/critical capacity and discharging. 676 */ 677 return true; 678 } 679 680 static bool 681 sme_battery_critical(envsys_data_t *edata) 682 { 683 if (edata->value_cur == ENVSYS_BATTERY_CAPACITY_CRITICAL || 684 edata->value_cur == ENVSYS_BATTERY_CAPACITY_LOW) 685 return true; 686 687 return false; 688 } 689