1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * Solaris x86 ACPI ThermalZone Monitor 29 */ 30 31 #pragma ident "%Z%%M% %I% %E% SMI" 32 33 #include <sys/errno.h> 34 #include <sys/conf.h> 35 #include <sys/modctl.h> 36 #include <sys/open.h> 37 #include <sys/stat.h> 38 #include <sys/ddi.h> 39 #include <sys/sunddi.h> 40 #include <sys/ksynch.h> 41 #include <sys/uadmin.h> 42 #include <sys/acpi/acpi.h> 43 #include <sys/acpica.h> 44 #include <sys/sdt.h> 45 46 #include "tzmon.h" 47 48 49 #define TZMON_ENUM_TRIP_POINTS 1 50 #define TZMON_ENUM_DEV_LISTS 2 51 #define TZMON_ENUM_ALL (TZMON_ENUM_TRIP_POINTS | TZMON_ENUM_DEV_LISTS) 52 53 54 /* cb_ops or dev_ops forward declarations */ 55 static int tzmon_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, 56 void *arg, void **result); 57 static int tzmon_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); 58 static int tzmon_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); 59 60 /* other forward declarations */ 61 static void tzmon_notify_zone(ACPI_HANDLE obj, UINT32 val, void *ctx); 62 static void tzmon_eval_int(ACPI_HANDLE obj, char *method, int *rv); 63 static thermal_zone_t *tzmon_alloc_zone(); 64 static void tzmon_free_zone_list(); 65 static void tzmon_discard_buffers(thermal_zone_t *tzp); 66 static void tzmon_enumerate_zone(ACPI_HANDLE obj, thermal_zone_t *tzp, 67 int enum_flag); 68 static ACPI_STATUS tzmon_zone_callback(ACPI_HANDLE obj, UINT32 nest, 69 void *ctx, void **rv); 70 static void tzmon_find_zones(void); 71 static void tzmon_monitor(void *ctx); 72 static void tzmon_set_power_device(ACPI_HANDLE dev, int on_off); 73 static void tzmon_set_power(ACPI_BUFFER devlist, int on_off); 74 static void tzmon_eval_zone(thermal_zone_t *tzp); 75 static void tzmon_do_shutdown(void); 76 77 extern void halt(char *); 78 79 static struct cb_ops tzmon_cb_ops = { 80 nodev, /* no open routine */ 81 nodev, /* no close routine */ 82 nodev, /* not a block driver */ 83 nodev, /* no print routine */ 84 nodev, /* no dump routine */ 85 nodev, /* no read routine */ 86 nodev, /* no write routine */ 87 nodev, /* no ioctl routine */ 88 nodev, /* no devmap routine */ 89 nodev, /* no mmap routine */ 90 nodev, /* no segmap routine */ 91 nochpoll, /* no chpoll routine */ 92 ddi_prop_op, 93 0, /* not a STREAMS driver */ 94 D_NEW | D_MP, /* safe for multi-thread/multi-processor */ 95 }; 96 97 static struct dev_ops tzmon_ops = { 98 DEVO_REV, /* devo_rev */ 99 0, /* devo_refcnt */ 100 tzmon_getinfo, /* devo_getinfo */ 101 nulldev, /* devo_identify */ 102 nulldev, /* devo_probe */ 103 tzmon_attach, /* devo_attach */ 104 tzmon_detach, /* devo_detach */ 105 nodev, /* devo_reset */ 106 &tzmon_cb_ops, /* devo_cb_ops */ 107 (struct bus_ops *)0, /* devo_bus_ops */ 108 NULL, /* devo_power */ 109 }; 110 111 extern struct mod_ops mod_driverops; 112 113 static struct modldrv modldrv = { 114 &mod_driverops, 115 "ACPI Thermal Zone Monitor %I%", 116 &tzmon_ops, 117 }; 118 119 static struct modlinkage modlinkage = { 120 MODREV_1, /* MODREV_1 indicated by manual */ 121 (void *)&modldrv, 122 NULL, /* termination of list of linkage structures */ 123 }; 124 125 /* globals for this module */ 126 static dev_info_t *tzmon_dip; 127 static thermal_zone_t *zone_list; 128 static int zone_count; 129 static kmutex_t zone_list_lock; 130 static kcondvar_t zone_list_condvar; 131 132 133 /* 134 * _init, _info, and _fini support loading and unloading the driver. 135 */ 136 int 137 _init(void) 138 { 139 return (mod_install(&modlinkage)); 140 } 141 142 143 int 144 _info(struct modinfo *modinfop) 145 { 146 return (mod_info(&modlinkage, modinfop)); 147 } 148 149 150 int 151 _fini(void) 152 { 153 return (mod_remove(&modlinkage)); 154 } 155 156 157 static int 158 tzmon_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 159 { 160 if (cmd != DDI_ATTACH) 161 return (DDI_FAILURE); 162 163 if (tzmon_dip != NULL) 164 return (DDI_FAILURE); 165 166 /* 167 * Check to see if ACPI CA services are available 168 */ 169 if (AcpiSubsystemStatus() != AE_OK) 170 return (DDI_FAILURE); 171 172 mutex_init(&zone_list_lock, NULL, MUTEX_DRIVER, NULL); 173 cv_init(&zone_list_condvar, NULL, CV_DRIVER, NULL); 174 175 tzmon_find_zones(); 176 mutex_enter(&zone_list_lock); 177 if (zone_count < 1) { 178 mutex_exit(&zone_list_lock); 179 mutex_destroy(&zone_list_lock); 180 cv_destroy(&zone_list_condvar); 181 return (DDI_FAILURE); 182 } 183 mutex_exit(&zone_list_lock); 184 185 if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR, 0, 186 DDI_PSEUDO, 0) == DDI_FAILURE) { 187 tzmon_free_zone_list(); 188 mutex_destroy(&zone_list_lock); 189 cv_destroy(&zone_list_condvar); 190 return (DDI_FAILURE); 191 } 192 193 tzmon_dip = dip; 194 195 ddi_report_dev(dip); 196 197 return (DDI_SUCCESS); 198 } 199 200 201 /*ARGSUSED*/ 202 static int 203 tzmon_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) 204 { 205 int error; 206 207 switch (infocmd) { 208 case DDI_INFO_DEVT2DEVINFO: 209 *result = tzmon_dip; 210 if (tzmon_dip == NULL) 211 error = DDI_FAILURE; 212 else 213 error = DDI_SUCCESS; 214 break; 215 case DDI_INFO_DEVT2INSTANCE: 216 *result = 0; 217 error = DDI_SUCCESS; 218 break; 219 default: 220 *result = NULL; 221 error = DDI_FAILURE; 222 } 223 224 return (error); 225 } 226 227 228 static int 229 tzmon_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 230 { 231 if (cmd != DDI_DETACH) 232 return (DDI_FAILURE); 233 234 /* discard zone list assets */ 235 tzmon_free_zone_list(); 236 237 ddi_remove_minor_node(dip, NULL); 238 tzmon_dip = NULL; 239 240 mutex_destroy(&zone_list_lock); 241 cv_destroy(&zone_list_condvar); 242 243 return (DDI_SUCCESS); 244 } 245 246 247 /* 248 * tzmon_notify_zone 249 * Thermal zone notification handler. 250 */ 251 static void 252 tzmon_notify_zone(ACPI_HANDLE obj, UINT32 val, void *ctx) 253 { 254 thermal_zone_t *tzp = (thermal_zone_t *)ctx; 255 256 switch (val) { 257 case 0x80: /* Thermal Zone status changed */ 258 tzmon_eval_zone(tzp); 259 break; 260 case 0x81: /* Thermal Zone trip points changed */ 261 tzmon_enumerate_zone(obj, tzp, TZMON_ENUM_TRIP_POINTS); 262 break; 263 case 0x82: /* Device Lists changed */ 264 tzmon_enumerate_zone(obj, tzp, TZMON_ENUM_DEV_LISTS); 265 break; 266 case 0x83: /* Thermal Relationship Table changed */ 267 /* not handling _TRT objects, so not handling this event */ 268 cmn_err(CE_CONT, "?tzmon: thermal relationship table changed"); 269 break; 270 default: 271 break; 272 } 273 } 274 275 276 /* 277 * tzmon_eval_int 278 * Evaluate the object/method as an integer. 279 */ 280 static void 281 tzmon_eval_int(ACPI_HANDLE obj, char *method, int *rv) 282 { 283 284 if (acpica_eval_int(obj, method, rv) != AE_OK) 285 *rv = -1; 286 } 287 288 289 /* 290 * tzmon_alloc_zone 291 * Allocate memory for the zone structure and initialize it lock mutex. 292 */ 293 static thermal_zone_t * 294 tzmon_alloc_zone() 295 { 296 thermal_zone_t *tzp; 297 298 tzp = kmem_zalloc(sizeof (thermal_zone_t), KM_SLEEP); 299 mutex_init(&tzp->lock, NULL, MUTEX_DRIVER, NULL); 300 301 return (tzp); 302 } 303 304 305 /* 306 * tzmon_free_zone_list 307 * Free the zone list, either because attach failed or detach initiated. 308 */ 309 static void 310 tzmon_free_zone_list() 311 { 312 thermal_zone_t *tzp = zone_list; 313 314 while (tzp != NULL) { 315 thermal_zone_t *next; 316 317 mutex_enter(&tzp->lock); 318 319 /* 320 * Remove the notify handler for the zone. Not much to 321 * do if this fails (since we are on our way out), so 322 * just ignore failure. 323 */ 324 (void) AcpiRemoveNotifyHandler(tzp->obj, ACPI_DEVICE_NOTIFY, 325 tzmon_notify_zone); 326 327 /* Shut down monitor thread, if running */ 328 if (tzp->taskq != NULL) { 329 tzp->polling_period = 0; 330 cv_broadcast(&zone_list_condvar); 331 332 /* Drop mutex to allow the thread to run */ 333 mutex_exit(&tzp->lock); 334 ddi_taskq_destroy(tzp->taskq); 335 mutex_enter(&tzp->lock); 336 } 337 338 tzmon_discard_buffers(tzp); 339 mutex_exit(&tzp->lock); 340 mutex_destroy(&tzp->lock); 341 342 next = tzp->next; 343 kmem_free(tzp, sizeof (thermal_zone_t)); 344 tzp = next; 345 } 346 } 347 348 349 static void 350 tzmon_discard_buffers(thermal_zone_t *tzp) 351 { 352 int level; 353 354 for (level = 0; level < TZ_NUM_LEVELS; level++) { 355 if (tzp->al[level].Pointer != NULL) 356 AcpiOsFree(tzp->al[level].Pointer); 357 } 358 359 if (tzp->psl.Pointer != NULL) 360 AcpiOsFree(tzp->psl.Pointer); 361 } 362 363 364 /* 365 * tzmon_enumerate_zone 366 * Enumerates the contents of a thermal zone and updates passed-in 367 * thermal_zone or creates a new one if tzp is NULL. Newly-created 368 * zones are linked into the global zone_list. 369 */ 370 static void 371 tzmon_enumerate_zone(ACPI_HANDLE obj, thermal_zone_t *tzp, int enum_flag) 372 { 373 ACPI_STATUS status; 374 int level; 375 char abuf[5]; 376 377 /* 378 * Newly-created zones and existing zones both require 379 * some individual attention. 380 */ 381 if (tzp == NULL) { 382 /* New zone required */ 383 tzp = tzmon_alloc_zone(); 384 mutex_enter(&zone_list_lock); 385 tzp->next = zone_list; 386 zone_list = tzp; 387 zone_count++; 388 mutex_exit(&zone_list_lock); 389 mutex_enter(&tzp->lock); 390 tzp->obj = obj; 391 392 /* 393 * Set to a low level. Will get set to the actual 394 * current power level when the thread monitor polls 395 * the current temperature. 396 */ 397 tzp->current_level = 0; 398 399 status = AcpiInstallNotifyHandler(obj, ACPI_DEVICE_NOTIFY, 400 tzmon_notify_zone, (void *)tzp); 401 ASSERT(status == AE_OK); 402 } else { 403 /* Existing zone - toss out allocated items */ 404 mutex_enter(&tzp->lock); 405 ASSERT(tzp->obj == obj); 406 407 if (enum_flag & TZMON_ENUM_DEV_LISTS) 408 tzmon_discard_buffers(tzp); 409 } 410 411 if (enum_flag & TZMON_ENUM_TRIP_POINTS) { 412 for (level = 0; level < TZ_NUM_LEVELS; level++) { 413 (void) snprintf(abuf, 5, "_AC%d", level); 414 tzmon_eval_int(obj, abuf, &tzp->ac[level]); 415 416 } 417 418 tzmon_eval_int(obj, "_CRT", &tzp->crt); 419 tzmon_eval_int(obj, "_HOT", &tzp->hot); 420 tzmon_eval_int(obj, "_PSV", &tzp->psv); 421 } 422 423 if (enum_flag & TZMON_ENUM_DEV_LISTS) { 424 for (level = 0; level < TZ_NUM_LEVELS; level++) { 425 if (tzp->ac[level] == -1) { 426 tzp->al[level].Length = 0; 427 tzp->al[level].Pointer = NULL; 428 } else { 429 (void) snprintf(abuf, 5, "_AL%d", level); 430 tzp->al[level].Length = ACPI_ALLOCATE_BUFFER; 431 tzp->al[level].Pointer = NULL; 432 if (AcpiEvaluateObject(obj, abuf, NULL, 433 &tzp->al[level]) != AE_OK) { 434 cmn_err(CE_WARN, "tzmon: error " 435 "evaluating _AL object"); 436 437 tzp->al[level].Length = 0; 438 tzp->al[level].Pointer = NULL; 439 } 440 } 441 } 442 443 tzp->psl.Length = ACPI_ALLOCATE_BUFFER; 444 tzp->psl.Pointer = NULL; 445 (void) AcpiEvaluateObject(obj, "_PSL", NULL, &tzp->psl); 446 } 447 448 tzmon_eval_int(obj, "_TC1", &tzp->tc1); 449 tzmon_eval_int(obj, "_TC2", &tzp->tc2); 450 tzmon_eval_int(obj, "_TSP", &tzp->tsp); 451 tzmon_eval_int(obj, "_TZP", &tzp->tzp); 452 453 if (tzp->tzp == 0) { 454 tzp->polling_period = 0; 455 } else { 456 if (tzp->tzp < 0) 457 tzp->polling_period = TZ_DEFAULT_PERIOD; 458 else 459 tzp->polling_period = tzp->tzp/10; 460 461 /* start monitor thread if needed */ 462 if (tzp->taskq == NULL) { 463 tzp->taskq = ddi_taskq_create(tzmon_dip, 464 "AcpiThermalMonitor", 1, TASKQ_DEFAULTPRI, 0); 465 if (tzp->taskq == NULL) { 466 tzp->polling_period = 0; 467 cmn_err(CE_WARN, "tzmon: could not create" 468 " monitor thread - monitor by notify only"); 469 } else { 470 (void) ddi_taskq_dispatch(tzp->taskq, 471 tzmon_monitor, tzp, DDI_SLEEP); 472 } 473 } 474 } 475 476 mutex_exit(&tzp->lock); 477 } 478 479 480 /* 481 * tzmon_zone_callback 482 * Enumerate the thermal zone if it has a _TMP (current thermal zone 483 * operating temperature) method. 484 */ 485 /*ARGSUSED*/ 486 static ACPI_STATUS 487 tzmon_zone_callback(ACPI_HANDLE obj, UINT32 nest, void *ctx, void **rv) 488 { 489 ACPI_HANDLE tmpobj; 490 491 /* 492 * We get both ThermalZone() and Scope(\_TZ) objects here; 493 * look for _TMP (without which a zone is invalid) to pick 494 * between them (and ignore invalid zones) 495 */ 496 if (AcpiGetHandle(obj, "_TMP", &tmpobj) == AE_OK) { 497 tzmon_enumerate_zone(obj, NULL, TZMON_ENUM_ALL); 498 } 499 500 return (AE_OK); 501 } 502 503 504 /* 505 * tzmon_find_zones 506 * Find all of the thermal zones by calling a ACPICA function that 507 * walks the ACPI namespace and invokes a callback for each thermal 508 * object found. 509 */ 510 static void 511 tzmon_find_zones() 512 { 513 ACPI_STATUS status; 514 int retval; 515 516 status = AcpiWalkNamespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT, 517 8, tzmon_zone_callback, NULL, (void **)&retval); 518 519 ASSERT(status == AE_OK); 520 } 521 522 523 /* 524 * tzmon_monitor 525 * Run as a separate thread, this wakes according to polling period and 526 * checks particular objects in the thermal zone. One instance per 527 * thermal zone. 528 */ 529 static void 530 tzmon_monitor(void *ctx) 531 { 532 thermal_zone_t *tzp = (thermal_zone_t *)ctx; 533 clock_t ticks; 534 535 do { 536 /* Check out the zone */ 537 tzmon_eval_zone(tzp); 538 539 /* Go back to sleep */ 540 mutex_enter(&tzp->lock); 541 ticks = drv_usectohz(tzp->polling_period * 1000000); 542 if (ticks > 0) 543 (void) cv_timedwait(&zone_list_condvar, &tzp->lock, 544 ddi_get_lbolt() + ticks); 545 mutex_exit(&tzp->lock); 546 } while (ticks > 0); 547 } 548 549 550 /* 551 * tzmon_set_power_device 552 */ 553 static void 554 tzmon_set_power_device(ACPI_HANDLE dev, int on_off) 555 { 556 ACPI_BUFFER rb; 557 ACPI_OBJECT *pr0; 558 ACPI_STATUS status; 559 int i; 560 561 rb.Length = ACPI_ALLOCATE_BUFFER; 562 rb.Pointer = NULL; 563 status = AcpiEvaluateObject(dev, "_PR0", NULL, &rb); 564 if (status != AE_OK) { 565 cmn_err(CE_NOTE, "tzmon: can not set power"); 566 return; 567 } 568 569 pr0 = ((ACPI_OBJECT *)rb.Pointer); 570 if (pr0->Type != ACPI_TYPE_PACKAGE) { 571 cmn_err(CE_NOTE, "tzmon: can not set power"); 572 AcpiOsFree(rb.Pointer); 573 return; 574 } 575 576 for (i = 0; i < pr0->Package.Count; i++) { 577 status = AcpiEvaluateObject( 578 pr0->Package.Elements[i].Reference.Handle, 579 on_off ? "_ON" : "_OFF", NULL, NULL); 580 if (status != AE_OK) { 581 cmn_err(CE_WARN, "tz_set_pwr_dev: failed to set %d\n", 582 i); 583 } 584 } 585 586 AcpiOsFree(rb.Pointer); 587 } 588 589 590 /* 591 * tzmon_set_power 592 * Turn on or turn off all devices in the supplied list. 593 */ 594 static void 595 tzmon_set_power(ACPI_BUFFER devlist, int on_off) 596 { 597 ACPI_OBJECT *devs; 598 int i; 599 600 devs = ((ACPI_OBJECT *)devlist.Pointer); 601 if (devs->Type != ACPI_TYPE_PACKAGE) { 602 cmn_err(CE_NOTE, "tzmon: can not set power"); 603 return; 604 } 605 606 for (i = 0; i < devs->Package.Count; i++) 607 tzmon_set_power_device( 608 devs->Package.Elements[i].Reference.Handle, on_off); 609 } 610 611 612 /* 613 * tzmon_eval_zone 614 * Evaluate the current conditions within the thermal zone. 615 */ 616 static void 617 tzmon_eval_zone(thermal_zone_t *tzp) 618 { 619 int tmp, new_level, level; 620 621 mutex_enter(&tzp->lock); 622 623 /* get the current temperature from ACPI */ 624 tzmon_eval_int(tzp->obj, "_TMP", &tmp); 625 DTRACE_PROBE1(tzmon_tz_temp, int, tmp); 626 627 /* _HOT handling */ 628 if (tzp->hot > 0 && tmp >= tzp->hot) { 629 cmn_err(CE_WARN, "tzmon: temp %d is above _HOT (%d)\n", 630 tmp, tzp->hot); 631 632 tzmon_do_shutdown(); 633 } 634 635 /* _CRT handling */ 636 if (tzp->crt > 0 && tmp >= tzp->crt) { 637 cmn_err(CE_WARN, "tzmon: temp %d is above _CRT (%d)\n", 638 tmp, tzp->crt); 639 640 /* shut down (fairly) immediately */ 641 mdboot(A_REBOOT, AD_HALT, NULL, B_FALSE); 642 } 643 644 /* 645 * use the temperature to determine whether the thermal zone 646 * is at a new active cooling threshold level 647 */ 648 for (level = 0, new_level = -1; level < TZ_NUM_LEVELS; level++) { 649 if (tzp->ac[level] >= 0 && (tmp >= tzp->ac[level])) { 650 new_level = level; 651 break; 652 } 653 } 654 655 /* 656 * if the active cooling threshold has changed, turn off the 657 * devices associated with the old one and turn on the new one 658 */ 659 if (tzp->current_level != new_level) { 660 if ((tzp->current_level >= 0) && 661 (tzp->al[tzp->current_level].Length != 0)) 662 tzmon_set_power(tzp->al[tzp->current_level], 0); 663 664 if ((new_level >= 0) && 665 (tzp->al[new_level].Length != 0)) 666 tzmon_set_power(tzp->al[new_level], 1); 667 668 tzp->current_level = new_level; 669 } 670 671 mutex_exit(&tzp->lock); 672 } 673 674 675 /* 676 * tzmon_do_shutdown 677 * Initiates shutdown by sending a SIGPWR signal to init. 678 */ 679 static void 680 tzmon_do_shutdown(void) 681 { 682 proc_t *initpp; 683 684 mutex_enter(&pidlock); 685 initpp = prfind(P_INITPID); 686 mutex_exit(&pidlock); 687 688 /* if we can't find init, just halt */ 689 if (initpp == NULL) { 690 mdboot(A_REBOOT, AD_HALT, NULL, B_FALSE); 691 } 692 693 /* graceful shutdown with inittab and all getting involved */ 694 psignal(initpp, SIGPWR); 695 } 696