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.
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.
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]
23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
28 * Solaris x86 ACPI ThermalZone Monitor
32 #include <sys/errno.h>
34 #include <sys/modctl.h>
38 #include <sys/sunddi.h>
39 #include <sys/ksynch.h>
40 #include <sys/uadmin.h>
41 #include <sys/acpi/acpi.h>
42 #include <sys/acpica.h>
48 #define TZMON_ENUM_TRIP_POINTS 1
49 #define TZMON_ENUM_DEV_LISTS 2
50 #define TZMON_ENUM_ALL (TZMON_ENUM_TRIP_POINTS | TZMON_ENUM_DEV_LISTS)
53 * TZ_TASKQ_NAME_LEN is precisely the length of the string "AcpiThermalMonitor"
54 * plus a two-digit instance number plus a NULL. If the taskq name is changed
55 * (particularly if it is lengthened), then this value needs to change.
57 #define TZ_TASKQ_NAME_LEN 21
60 * Kelvin to Celsius conversion
61 * The formula for converting degrees Kelvin to degrees Celsius is
62 * C = K - 273.15 (we round to 273.2). The unit for thermal zone
63 * temperatures is tenths of a degree Kelvin. Use tenth of a degree
64 * to convert, then make a whole number out of it.
66 #define K_TO_C(temp) (((temp) - 2732) / 10)
69 /* cb_ops or dev_ops forward declarations */
70 static int tzmon_getinfo(dev_info_t
*dip
, ddi_info_cmd_t infocmd
,
71 void *arg
, void **result
);
72 static int tzmon_attach(dev_info_t
*dip
, ddi_attach_cmd_t cmd
);
73 static int tzmon_detach(dev_info_t
*dip
, ddi_detach_cmd_t cmd
);
75 /* other forward declarations */
76 static void tzmon_notify_zone(ACPI_HANDLE obj
, UINT32 val
, void *ctx
);
77 static void tzmon_eval_int(ACPI_HANDLE obj
, char *method
, int *rv
);
78 static thermal_zone_t
*tzmon_alloc_zone();
79 static void tzmon_free_zone_list();
80 static void tzmon_discard_buffers(thermal_zone_t
*tzp
);
81 static void tzmon_enumerate_zone(ACPI_HANDLE obj
, thermal_zone_t
*tzp
,
83 static ACPI_STATUS
tzmon_zone_callback(ACPI_HANDLE obj
, UINT32 nest
,
84 void *ctx
, void **rv
);
85 static void tzmon_find_zones(void);
86 static void tzmon_monitor(void *ctx
);
87 static void tzmon_set_power_device(ACPI_HANDLE dev
, int on_off
, char *tz_name
);
88 static void tzmon_set_power(ACPI_BUFFER devlist
, int on_off
, char *tz_name
);
89 static void tzmon_eval_zone(thermal_zone_t
*tzp
);
90 static void tzmon_do_shutdown(void);
92 extern void halt(char *);
94 static struct cb_ops tzmon_cb_ops
= {
95 nodev
, /* no open routine */
96 nodev
, /* no close routine */
97 nodev
, /* not a block driver */
98 nodev
, /* no print routine */
99 nodev
, /* no dump routine */
100 nodev
, /* no read routine */
101 nodev
, /* no write routine */
102 nodev
, /* no ioctl routine */
103 nodev
, /* no devmap routine */
104 nodev
, /* no mmap routine */
105 nodev
, /* no segmap routine */
106 nochpoll
, /* no chpoll routine */
108 0, /* not a STREAMS driver */
109 D_NEW
| D_MP
, /* safe for multi-thread/multi-processor */
112 static struct dev_ops tzmon_ops
= {
113 DEVO_REV
, /* devo_rev */
115 tzmon_getinfo
, /* devo_getinfo */
116 nulldev
, /* devo_identify */
117 nulldev
, /* devo_probe */
118 tzmon_attach
, /* devo_attach */
119 tzmon_detach
, /* devo_detach */
120 nodev
, /* devo_reset */
121 &tzmon_cb_ops
, /* devo_cb_ops */
122 NULL
, /* devo_bus_ops */
123 NULL
, /* devo_power */
124 ddi_quiesce_not_needed
, /* devo_quiesce */
127 extern struct mod_ops mod_driverops
;
129 static struct modldrv modldrv
= {
131 "ACPI Thermal Zone Monitor",
135 static struct modlinkage modlinkage
= {
136 MODREV_1
, /* MODREV_1 indicated by manual */
138 NULL
, /* termination of list of linkage structures */
141 /* globals for this module */
142 static dev_info_t
*tzmon_dip
;
143 static thermal_zone_t
*zone_list
;
144 static int zone_count
;
145 static kmutex_t zone_list_lock
;
146 static kcondvar_t zone_list_condvar
;
150 * _init, _info, and _fini support loading and unloading the driver.
155 return (mod_install(&modlinkage
));
160 _info(struct modinfo
*modinfop
)
162 return (mod_info(&modlinkage
, modinfop
));
169 return (mod_remove(&modlinkage
));
174 tzmon_attach(dev_info_t
*dip
, ddi_attach_cmd_t cmd
)
176 if (cmd
!= DDI_ATTACH
)
177 return (DDI_FAILURE
);
179 if (tzmon_dip
!= NULL
)
180 return (DDI_FAILURE
);
183 * Check to see if ACPI CA services are available
185 if (AcpiSubsystemStatus() != AE_OK
)
186 return (DDI_FAILURE
);
188 mutex_init(&zone_list_lock
, NULL
, MUTEX_DRIVER
, NULL
);
189 cv_init(&zone_list_condvar
, NULL
, CV_DRIVER
, NULL
);
192 mutex_enter(&zone_list_lock
);
193 if (zone_count
< 1) {
194 mutex_exit(&zone_list_lock
);
195 mutex_destroy(&zone_list_lock
);
196 cv_destroy(&zone_list_condvar
);
197 return (DDI_FAILURE
);
199 mutex_exit(&zone_list_lock
);
201 if (ddi_create_minor_node(dip
, ddi_get_name(dip
), S_IFCHR
, 0,
202 DDI_PSEUDO
, 0) == DDI_FAILURE
) {
203 tzmon_free_zone_list();
204 mutex_destroy(&zone_list_lock
);
205 cv_destroy(&zone_list_condvar
);
206 return (DDI_FAILURE
);
213 return (DDI_SUCCESS
);
219 tzmon_getinfo(dev_info_t
*dip
, ddi_info_cmd_t infocmd
, void *arg
, void **result
)
224 case DDI_INFO_DEVT2DEVINFO
:
226 if (tzmon_dip
== NULL
)
231 case DDI_INFO_DEVT2INSTANCE
:
245 tzmon_detach(dev_info_t
*dip
, ddi_detach_cmd_t cmd
)
247 thermal_zone_t
*tzp
= zone_list
;
249 if (cmd
!= DDI_DETACH
)
250 return (DDI_FAILURE
);
252 /* free allocated thermal zone name(s) */
253 while (tzp
!= NULL
) {
254 AcpiOsFree(tzp
->zone_name
);
258 /* discard zone list assets */
259 tzmon_free_zone_list();
261 ddi_remove_minor_node(dip
, NULL
);
264 mutex_destroy(&zone_list_lock
);
265 cv_destroy(&zone_list_condvar
);
267 return (DDI_SUCCESS
);
273 * Thermal zone notification handler.
276 tzmon_notify_zone(ACPI_HANDLE obj
, UINT32 val
, void *ctx
)
278 thermal_zone_t
*tzp
= (thermal_zone_t
*)ctx
;
281 case 0x80: /* Thermal Zone status changed */
282 tzmon_eval_zone(tzp
);
284 case 0x81: /* Thermal Zone trip points changed */
285 tzmon_enumerate_zone(obj
, tzp
, TZMON_ENUM_TRIP_POINTS
);
287 case 0x82: /* Device Lists changed */
288 tzmon_enumerate_zone(obj
, tzp
, TZMON_ENUM_DEV_LISTS
);
290 case 0x83: /* Thermal Relationship Table changed */
291 /* not handling _TRT objects, so not handling this event */
292 DTRACE_PROBE1(trt__change
, char *, (char *)tzp
->zone_name
);
302 * Evaluate the object/method as an integer.
305 tzmon_eval_int(ACPI_HANDLE obj
, char *method
, int *rv
)
308 if (acpica_eval_int(obj
, method
, rv
) != AE_OK
)
315 * Allocate memory for the zone structure and initialize it lock mutex.
317 static thermal_zone_t
*
322 tzp
= kmem_zalloc(sizeof (thermal_zone_t
), KM_SLEEP
);
323 mutex_init(&tzp
->lock
, NULL
, MUTEX_DRIVER
, NULL
);
330 * tzmon_free_zone_list
331 * Free the zone list, either because attach failed or detach initiated.
334 tzmon_free_zone_list()
336 thermal_zone_t
*tzp
= zone_list
;
338 while (tzp
!= NULL
) {
339 thermal_zone_t
*next
;
341 mutex_enter(&tzp
->lock
);
344 * Remove the notify handler for the zone. Not much to
345 * do if this fails (since we are on our way out), so
346 * just ignore failure.
348 (void) AcpiRemoveNotifyHandler(tzp
->obj
, ACPI_DEVICE_NOTIFY
,
351 /* Shut down monitor thread, if running */
352 if (tzp
->taskq
!= NULL
) {
353 tzp
->polling_period
= 0;
354 cv_broadcast(&zone_list_condvar
);
356 /* Drop mutex to allow the thread to run */
357 mutex_exit(&tzp
->lock
);
358 ddi_taskq_destroy(tzp
->taskq
);
359 mutex_enter(&tzp
->lock
);
362 tzmon_discard_buffers(tzp
);
363 mutex_exit(&tzp
->lock
);
364 mutex_destroy(&tzp
->lock
);
367 kmem_free(tzp
, sizeof (thermal_zone_t
));
374 tzmon_discard_buffers(thermal_zone_t
*tzp
)
378 for (level
= 0; level
< TZ_NUM_LEVELS
; level
++) {
379 if (tzp
->al
[level
].Pointer
!= NULL
)
380 AcpiOsFree(tzp
->al
[level
].Pointer
);
383 if (tzp
->psl
.Pointer
!= NULL
)
384 AcpiOsFree(tzp
->psl
.Pointer
);
389 * tzmon_enumerate_zone
390 * Enumerates the contents of a thermal zone and updates passed-in
391 * thermal_zone or creates a new one if tzp is NULL. Newly-created
392 * zones are linked into the global zone_list.
395 tzmon_enumerate_zone(ACPI_HANDLE obj
, thermal_zone_t
*tzp
, int enum_flag
)
398 ACPI_BUFFER zone_name
;
404 * Newly-created zones and existing zones both require
405 * some individual attention.
408 /* New zone required */
409 tzp
= tzmon_alloc_zone();
410 mutex_enter(&zone_list_lock
);
411 tzp
->next
= zone_list
;
415 * It is exceedingly unlikely that instance will exceed 99.
416 * However, if it does, this will cause problems when
417 * creating the taskq for this thermal zone.
419 instance
= zone_count
;
421 mutex_exit(&zone_list_lock
);
422 mutex_enter(&tzp
->lock
);
426 * Set to a low level. Will get set to the actual
427 * current power level when the thread monitor polls
428 * the current temperature.
430 tzp
->current_level
= 0;
432 /* Get the zone name in case we need to display it later */
433 zone_name
.Length
= ACPI_ALLOCATE_BUFFER
;
434 zone_name
.Pointer
= NULL
;
436 status
= AcpiGetName(obj
, ACPI_FULL_PATHNAME
, &zone_name
);
437 ASSERT(status
== AE_OK
);
439 tzp
->zone_name
= zone_name
.Pointer
;
441 status
= AcpiInstallNotifyHandler(obj
, ACPI_DEVICE_NOTIFY
,
442 tzmon_notify_zone
, (void *)tzp
);
443 ASSERT(status
== AE_OK
);
445 /* Existing zone - toss out allocated items */
446 mutex_enter(&tzp
->lock
);
447 ASSERT(tzp
->obj
== obj
);
449 if (enum_flag
& TZMON_ENUM_DEV_LISTS
)
450 tzmon_discard_buffers(tzp
);
453 if (enum_flag
& TZMON_ENUM_TRIP_POINTS
) {
454 for (level
= 0; level
< TZ_NUM_LEVELS
; level
++) {
455 (void) snprintf(abuf
, 5, "_AC%d", level
);
456 tzmon_eval_int(obj
, abuf
, &tzp
->ac
[level
]);
460 tzmon_eval_int(obj
, "_CRT", &tzp
->crt
);
461 tzmon_eval_int(obj
, "_HOT", &tzp
->hot
);
462 tzmon_eval_int(obj
, "_PSV", &tzp
->psv
);
465 if (enum_flag
& TZMON_ENUM_DEV_LISTS
) {
466 for (level
= 0; level
< TZ_NUM_LEVELS
; level
++) {
467 if (tzp
->ac
[level
] == -1) {
468 tzp
->al
[level
].Length
= 0;
469 tzp
->al
[level
].Pointer
= NULL
;
471 (void) snprintf(abuf
, 5, "_AL%d", level
);
472 tzp
->al
[level
].Length
= ACPI_ALLOCATE_BUFFER
;
473 tzp
->al
[level
].Pointer
= NULL
;
474 if (AcpiEvaluateObjectTyped(obj
, abuf
, NULL
,
475 &tzp
->al
[level
], ACPI_TYPE_PACKAGE
) !=
477 DTRACE_PROBE2(alx__missing
, int, level
,
478 char *, (char *)tzp
->zone_name
);
480 tzp
->al
[level
].Length
= 0;
481 tzp
->al
[level
].Pointer
= NULL
;
486 tzp
->psl
.Length
= ACPI_ALLOCATE_BUFFER
;
487 tzp
->psl
.Pointer
= NULL
;
488 (void) AcpiEvaluateObjectTyped(obj
, "_PSL", NULL
, &tzp
->psl
,
492 tzmon_eval_int(obj
, "_TC1", &tzp
->tc1
);
493 tzmon_eval_int(obj
, "_TC2", &tzp
->tc2
);
494 tzmon_eval_int(obj
, "_TSP", &tzp
->tsp
);
495 tzmon_eval_int(obj
, "_TZP", &tzp
->tzp
);
498 tzp
->polling_period
= 0;
501 tzp
->polling_period
= TZ_DEFAULT_PERIOD
;
503 tzp
->polling_period
= tzp
->tzp
/10;
505 /* start monitor thread if needed */
506 if (tzp
->taskq
== NULL
) {
507 char taskq_name
[TZ_TASKQ_NAME_LEN
];
509 (void) snprintf(taskq_name
, TZ_TASKQ_NAME_LEN
,
510 "AcpiThermalMonitor%02d", instance
);
511 tzp
->taskq
= ddi_taskq_create(tzmon_dip
,
512 taskq_name
, 1, TASKQ_DEFAULTPRI
, 0);
513 if (tzp
->taskq
== NULL
) {
514 tzp
->polling_period
= 0;
515 cmn_err(CE_WARN
, "tzmon: could not create "
516 "monitor thread for thermal zone %s - "
517 "monitor by notify only",
518 (char *)tzp
->zone_name
);
520 (void) ddi_taskq_dispatch(tzp
->taskq
,
521 tzmon_monitor
, tzp
, DDI_SLEEP
);
526 mutex_exit(&tzp
->lock
);
531 * tzmon_zone_callback
532 * Enumerate the thermal zone if it has a _TMP (current thermal zone
533 * operating temperature) method.
537 tzmon_zone_callback(ACPI_HANDLE obj
, UINT32 nest
, void *ctx
, void **rv
)
542 * We get both ThermalZone() and Scope(\_TZ) objects here;
543 * look for _TMP (without which a zone is invalid) to pick
544 * between them (and ignore invalid zones)
546 if (AcpiGetHandle(obj
, "_TMP", &tmpobj
) == AE_OK
) {
547 tzmon_enumerate_zone(obj
, NULL
, TZMON_ENUM_ALL
);
556 * Find all of the thermal zones by calling a ACPICA function that
557 * walks the ACPI namespace and invokes a callback for each thermal
566 status
= AcpiWalkNamespace(ACPI_TYPE_THERMAL
, ACPI_ROOT_OBJECT
,
567 8, tzmon_zone_callback
, NULL
, NULL
, (void **)&retval
);
569 ASSERT(status
== AE_OK
);
575 * Run as a separate thread, this wakes according to polling period and
576 * checks particular objects in the thermal zone. One instance per
580 tzmon_monitor(void *ctx
)
582 thermal_zone_t
*tzp
= (thermal_zone_t
*)ctx
;
586 /* Check out the zone */
587 tzmon_eval_zone(tzp
);
589 /* Go back to sleep */
590 mutex_enter(&tzp
->lock
);
591 ticks
= drv_usectohz(tzp
->polling_period
* 1000000);
593 (void) cv_reltimedwait(&zone_list_condvar
,
594 &tzp
->lock
, ticks
, TR_CLOCK_TICK
);
595 mutex_exit(&tzp
->lock
);
601 * tzmon_set_power_device
604 tzmon_set_power_device(ACPI_HANDLE dev
, int on_off
, char *tz_name
)
611 rb
.Length
= ACPI_ALLOCATE_BUFFER
;
613 status
= AcpiEvaluateObjectTyped(dev
, "_PR0", NULL
, &rb
,
615 if (status
!= AE_OK
) {
616 DTRACE_PROBE2(alx__error
, int, 2, char *, tz_name
);
620 pr0
= ((ACPI_OBJECT
*)rb
.Pointer
);
621 for (i
= 0; i
< pr0
->Package
.Count
; i
++) {
622 status
= AcpiEvaluateObject(
623 pr0
->Package
.Elements
[i
].Reference
.Handle
,
624 on_off
? "_ON" : "_OFF", NULL
, NULL
);
625 if (status
!= AE_OK
) {
626 DTRACE_PROBE2(alx__error
, int, 4, char *, tz_name
);
630 AcpiOsFree(rb
.Pointer
);
636 * Turn on or turn off all devices in the supplied list.
639 tzmon_set_power(ACPI_BUFFER devlist
, int on_off
, char *tz_name
)
644 devs
= ((ACPI_OBJECT
*)devlist
.Pointer
);
645 if (devs
->Type
!= ACPI_TYPE_PACKAGE
) {
646 DTRACE_PROBE2(alx__error
, int, 1, char *, tz_name
);
650 for (i
= 0; i
< devs
->Package
.Count
; i
++)
651 tzmon_set_power_device(
652 devs
->Package
.Elements
[i
].Reference
.Handle
, on_off
,
659 * Evaluate the current conditions within the thermal zone.
662 tzmon_eval_zone(thermal_zone_t
*tzp
)
664 int tmp
, new_level
, level
;
666 mutex_enter(&tzp
->lock
);
668 /* get the current temperature from ACPI */
669 tzmon_eval_int(tzp
->obj
, "_TMP", &tmp
);
670 DTRACE_PROBE4(tz__temp
, int, tmp
, int, tzp
->crt
, int, tzp
->hot
,
671 char *, (char *)tzp
->zone_name
);
674 if (tzp
->hot
> 0 && tmp
>= tzp
->hot
) {
676 "tzmon: Thermal zone (%s) is too hot (%d C); "
677 "initiating shutdown\n",
678 (char *)tzp
->zone_name
, K_TO_C(tmp
));
684 if (tzp
->crt
> 0 && tmp
>= tzp
->crt
) {
686 "tzmon: Thermal zone (%s) is critically hot (%d C); "
687 "initiating rapid shutdown\n",
688 (char *)tzp
->zone_name
, K_TO_C(tmp
));
690 /* shut down (fairly) immediately */
691 mdboot(A_REBOOT
, AD_HALT
, NULL
, B_FALSE
);
695 * use the temperature to determine whether the thermal zone
696 * is at a new active cooling threshold level
698 for (level
= 0, new_level
= -1; level
< TZ_NUM_LEVELS
; level
++) {
699 if (tzp
->ac
[level
] >= 0 && (tmp
>= tzp
->ac
[level
])) {
706 * if the active cooling threshold has changed, turn off the
707 * devices associated with the old one and turn on the new one
709 if (tzp
->current_level
!= new_level
) {
710 if ((tzp
->current_level
>= 0) &&
711 (tzp
->al
[tzp
->current_level
].Length
!= 0))
712 tzmon_set_power(tzp
->al
[tzp
->current_level
], 0,
713 (char *)tzp
->zone_name
);
715 if ((new_level
>= 0) &&
716 (tzp
->al
[new_level
].Length
!= 0))
717 tzmon_set_power(tzp
->al
[new_level
], 1,
718 (char *)tzp
->zone_name
);
720 tzp
->current_level
= new_level
;
723 mutex_exit(&tzp
->lock
);
729 * Initiates shutdown by sending a SIGPWR signal to init.
732 tzmon_do_shutdown(void)
736 mutex_enter(&pidlock
);
737 initpp
= prfind(P_INITPID
);
738 mutex_exit(&pidlock
);
740 /* if we can't find init, just halt */
741 if (initpp
== NULL
) {
742 mdboot(A_REBOOT
, AD_HALT
, NULL
, B_FALSE
);
745 /* graceful shutdown with inittab and all getting involved */
746 psignal(initpp
, SIGPWR
);