#include <sys/errno.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ksynch.h>
#include <sys/uadmin.h>
#include <sys/acpi/acpi.h>
#include <sys/acpica.h>
#include <sys/sdt.h>
#include "tzmon.h"
#define TZMON_ENUM_TRIP_POINTS 1
#define TZMON_ENUM_DEV_LISTS 2
#define TZMON_ENUM_ALL (TZMON_ENUM_TRIP_POINTS | TZMON_ENUM_DEV_LISTS)
#define TZ_TASKQ_NAME_LEN 21
#define K_TO_C(temp) (((temp) - 2732) / 10)
static int tzmon_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd,
void *arg, void **result);
static int tzmon_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int tzmon_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static void tzmon_notify_zone(ACPI_HANDLE obj, UINT32 val, void *ctx);
static void tzmon_eval_int(ACPI_HANDLE obj, char *method, int *rv);
static thermal_zone_t *tzmon_alloc_zone();
static void tzmon_free_zone_list();
static void tzmon_discard_buffers(thermal_zone_t *tzp);
static void tzmon_enumerate_zone(ACPI_HANDLE obj, thermal_zone_t *tzp,
int enum_flag);
static ACPI_STATUS tzmon_zone_callback(ACPI_HANDLE obj, UINT32 nest,
void *ctx, void **rv);
static void tzmon_find_zones(void);
static void tzmon_monitor(void *ctx);
static void tzmon_set_power_device(ACPI_HANDLE dev, int on_off, char *tz_name);
static void tzmon_set_power(ACPI_BUFFER devlist, int on_off, char *tz_name);
static void tzmon_eval_zone(thermal_zone_t *tzp);
static void tzmon_do_shutdown(void);
extern void halt(char *);
static struct cb_ops tzmon_cb_ops = {
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
0,
D_NEW | D_MP,
};
static struct dev_ops tzmon_ops = {
DEVO_REV,
0,
tzmon_getinfo,
nulldev,
nulldev,
tzmon_attach,
tzmon_detach,
nodev,
&tzmon_cb_ops,
(struct bus_ops *)0,
NULL,
ddi_quiesce_not_needed,
};
extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
&mod_driverops,
"ACPI Thermal Zone Monitor",
&tzmon_ops,
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modldrv,
NULL,
};
static dev_info_t *tzmon_dip;
static thermal_zone_t *zone_list;
static int zone_count;
static kmutex_t zone_list_lock;
static kcondvar_t zone_list_condvar;
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}
static int
tzmon_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
if (cmd != DDI_ATTACH)
return (DDI_FAILURE);
if (tzmon_dip != NULL)
return (DDI_FAILURE);
if (AcpiSubsystemStatus() != AE_OK)
return (DDI_FAILURE);
mutex_init(&zone_list_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&zone_list_condvar, NULL, CV_DRIVER, NULL);
tzmon_find_zones();
mutex_enter(&zone_list_lock);
if (zone_count < 1) {
mutex_exit(&zone_list_lock);
mutex_destroy(&zone_list_lock);
cv_destroy(&zone_list_condvar);
return (DDI_FAILURE);
}
mutex_exit(&zone_list_lock);
if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR, 0,
DDI_PSEUDO, 0) == DDI_FAILURE) {
tzmon_free_zone_list();
mutex_destroy(&zone_list_lock);
cv_destroy(&zone_list_condvar);
return (DDI_FAILURE);
}
tzmon_dip = dip;
ddi_report_dev(dip);
return (DDI_SUCCESS);
}
static int
tzmon_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
int error;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
*result = tzmon_dip;
if (tzmon_dip == NULL)
error = DDI_FAILURE;
else
error = DDI_SUCCESS;
break;
case DDI_INFO_DEVT2INSTANCE:
*result = 0;
error = DDI_SUCCESS;
break;
default:
*result = NULL;
error = DDI_FAILURE;
}
return (error);
}
static int
tzmon_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
thermal_zone_t *tzp = zone_list;
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
while (tzp != NULL) {
AcpiOsFree(tzp->zone_name);
tzp = tzp->next;
}
tzmon_free_zone_list();
ddi_remove_minor_node(dip, NULL);
tzmon_dip = NULL;
mutex_destroy(&zone_list_lock);
cv_destroy(&zone_list_condvar);
return (DDI_SUCCESS);
}
static void
tzmon_notify_zone(ACPI_HANDLE obj, UINT32 val, void *ctx)
{
thermal_zone_t *tzp = (thermal_zone_t *)ctx;
switch (val) {
case 0x80:
tzmon_eval_zone(tzp);
break;
case 0x81:
tzmon_enumerate_zone(obj, tzp, TZMON_ENUM_TRIP_POINTS);
break;
case 0x82:
tzmon_enumerate_zone(obj, tzp, TZMON_ENUM_DEV_LISTS);
break;
case 0x83:
DTRACE_PROBE1(trt__change, char *, (char *)tzp->zone_name);
break;
default:
break;
}
}
static void
tzmon_eval_int(ACPI_HANDLE obj, char *method, int *rv)
{
if (acpica_eval_int(obj, method, rv) != AE_OK)
*rv = -1;
}
static thermal_zone_t *
tzmon_alloc_zone()
{
thermal_zone_t *tzp;
tzp = kmem_zalloc(sizeof (thermal_zone_t), KM_SLEEP);
mutex_init(&tzp->lock, NULL, MUTEX_DRIVER, NULL);
return (tzp);
}
static void
tzmon_free_zone_list()
{
thermal_zone_t *tzp = zone_list;
while (tzp != NULL) {
thermal_zone_t *next;
mutex_enter(&tzp->lock);
(void) AcpiRemoveNotifyHandler(tzp->obj, ACPI_DEVICE_NOTIFY,
tzmon_notify_zone);
if (tzp->taskq != NULL) {
tzp->polling_period = 0;
cv_broadcast(&zone_list_condvar);
mutex_exit(&tzp->lock);
ddi_taskq_destroy(tzp->taskq);
mutex_enter(&tzp->lock);
}
tzmon_discard_buffers(tzp);
mutex_exit(&tzp->lock);
mutex_destroy(&tzp->lock);
next = tzp->next;
kmem_free(tzp, sizeof (thermal_zone_t));
tzp = next;
}
}
static void
tzmon_discard_buffers(thermal_zone_t *tzp)
{
int level;
for (level = 0; level < TZ_NUM_LEVELS; level++) {
if (tzp->al[level].Pointer != NULL)
AcpiOsFree(tzp->al[level].Pointer);
}
if (tzp->psl.Pointer != NULL)
AcpiOsFree(tzp->psl.Pointer);
}
static void
tzmon_enumerate_zone(ACPI_HANDLE obj, thermal_zone_t *tzp, int enum_flag)
{
ACPI_STATUS status;
ACPI_BUFFER zone_name;
int level;
int instance = 0;
char abuf[5];
if (tzp == NULL) {
tzp = tzmon_alloc_zone();
mutex_enter(&zone_list_lock);
tzp->next = zone_list;
zone_list = tzp;
instance = zone_count;
zone_count++;
mutex_exit(&zone_list_lock);
mutex_enter(&tzp->lock);
tzp->obj = obj;
tzp->current_level = 0;
zone_name.Length = ACPI_ALLOCATE_BUFFER;
zone_name.Pointer = NULL;
status = AcpiGetName(obj, ACPI_FULL_PATHNAME, &zone_name);
ASSERT(status == AE_OK);
tzp->zone_name = zone_name.Pointer;
status = AcpiInstallNotifyHandler(obj, ACPI_DEVICE_NOTIFY,
tzmon_notify_zone, (void *)tzp);
ASSERT(status == AE_OK);
} else {
mutex_enter(&tzp->lock);
ASSERT(tzp->obj == obj);
if (enum_flag & TZMON_ENUM_DEV_LISTS)
tzmon_discard_buffers(tzp);
}
if (enum_flag & TZMON_ENUM_TRIP_POINTS) {
for (level = 0; level < TZ_NUM_LEVELS; level++) {
(void) snprintf(abuf, 5, "_AC%d", level);
tzmon_eval_int(obj, abuf, &tzp->ac[level]);
}
tzmon_eval_int(obj, "_CRT", &tzp->crt);
tzmon_eval_int(obj, "_HOT", &tzp->hot);
tzmon_eval_int(obj, "_PSV", &tzp->psv);
}
if (enum_flag & TZMON_ENUM_DEV_LISTS) {
for (level = 0; level < TZ_NUM_LEVELS; level++) {
if (tzp->ac[level] == -1) {
tzp->al[level].Length = 0;
tzp->al[level].Pointer = NULL;
} else {
(void) snprintf(abuf, 5, "_AL%d", level);
tzp->al[level].Length = ACPI_ALLOCATE_BUFFER;
tzp->al[level].Pointer = NULL;
if (AcpiEvaluateObjectTyped(obj, abuf, NULL,
&tzp->al[level], ACPI_TYPE_PACKAGE) !=
AE_OK) {
DTRACE_PROBE2(alx__missing, int, level,
char *, (char *)tzp->zone_name);
tzp->al[level].Length = 0;
tzp->al[level].Pointer = NULL;
}
}
}
tzp->psl.Length = ACPI_ALLOCATE_BUFFER;
tzp->psl.Pointer = NULL;
(void) AcpiEvaluateObjectTyped(obj, "_PSL", NULL, &tzp->psl,
ACPI_TYPE_PACKAGE);
}
tzmon_eval_int(obj, "_TC1", &tzp->tc1);
tzmon_eval_int(obj, "_TC2", &tzp->tc2);
tzmon_eval_int(obj, "_TSP", &tzp->tsp);
tzmon_eval_int(obj, "_TZP", &tzp->tzp);
if (tzp->tzp == 0) {
tzp->polling_period = 0;
} else {
if (tzp->tzp < 0)
tzp->polling_period = TZ_DEFAULT_PERIOD;
else
tzp->polling_period = tzp->tzp/10;
if (tzp->taskq == NULL) {
char taskq_name[TZ_TASKQ_NAME_LEN];
(void) snprintf(taskq_name, TZ_TASKQ_NAME_LEN,
"AcpiThermalMonitor%02d", instance);
tzp->taskq = ddi_taskq_create(tzmon_dip,
taskq_name, 1, TASKQ_DEFAULTPRI, 0);
if (tzp->taskq == NULL) {
tzp->polling_period = 0;
cmn_err(CE_WARN, "tzmon: could not create "
"monitor thread for thermal zone %s - "
"monitor by notify only",
(char *)tzp->zone_name);
} else {
(void) ddi_taskq_dispatch(tzp->taskq,
tzmon_monitor, tzp, DDI_SLEEP);
}
}
}
mutex_exit(&tzp->lock);
}
static ACPI_STATUS
tzmon_zone_callback(ACPI_HANDLE obj, UINT32 nest, void *ctx, void **rv)
{
ACPI_HANDLE tmpobj;
if (AcpiGetHandle(obj, "_TMP", &tmpobj) == AE_OK) {
tzmon_enumerate_zone(obj, NULL, TZMON_ENUM_ALL);
}
return (AE_OK);
}
static void
tzmon_find_zones()
{
ACPI_STATUS status;
int retval;
status = AcpiWalkNamespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT,
8, tzmon_zone_callback, NULL, NULL, (void **)&retval);
ASSERT(status == AE_OK);
}
static void
tzmon_monitor(void *ctx)
{
thermal_zone_t *tzp = (thermal_zone_t *)ctx;
clock_t ticks;
do {
tzmon_eval_zone(tzp);
mutex_enter(&tzp->lock);
ticks = drv_usectohz(tzp->polling_period * 1000000);
if (ticks > 0)
(void) cv_reltimedwait(&zone_list_condvar,
&tzp->lock, ticks, TR_CLOCK_TICK);
mutex_exit(&tzp->lock);
} while (ticks > 0);
}
static void
tzmon_set_power_device(ACPI_HANDLE dev, int on_off, char *tz_name)
{
ACPI_BUFFER rb;
ACPI_OBJECT *pr0;
ACPI_STATUS status;
int i;
rb.Length = ACPI_ALLOCATE_BUFFER;
rb.Pointer = NULL;
status = AcpiEvaluateObjectTyped(dev, "_PR0", NULL, &rb,
ACPI_TYPE_PACKAGE);
if (status != AE_OK) {
DTRACE_PROBE2(alx__error, int, 2, char *, tz_name);
return;
}
pr0 = ((ACPI_OBJECT *)rb.Pointer);
for (i = 0; i < pr0->Package.Count; i++) {
status = AcpiEvaluateObject(
pr0->Package.Elements[i].Reference.Handle,
on_off ? "_ON" : "_OFF", NULL, NULL);
if (status != AE_OK) {
DTRACE_PROBE2(alx__error, int, 4, char *, tz_name);
}
}
AcpiOsFree(rb.Pointer);
}
static void
tzmon_set_power(ACPI_BUFFER devlist, int on_off, char *tz_name)
{
ACPI_OBJECT *devs;
int i;
devs = ((ACPI_OBJECT *)devlist.Pointer);
if (devs->Type != ACPI_TYPE_PACKAGE) {
DTRACE_PROBE2(alx__error, int, 1, char *, tz_name);
return;
}
for (i = 0; i < devs->Package.Count; i++)
tzmon_set_power_device(
devs->Package.Elements[i].Reference.Handle, on_off,
tz_name);
}
static void
tzmon_eval_zone(thermal_zone_t *tzp)
{
int tmp, new_level, level;
mutex_enter(&tzp->lock);
tzmon_eval_int(tzp->obj, "_TMP", &tmp);
DTRACE_PROBE4(tz__temp, int, tmp, int, tzp->crt, int, tzp->hot,
char *, (char *)tzp->zone_name);
if (tzp->hot > 0 && tmp >= tzp->hot) {
cmn_err(CE_WARN,
"tzmon: Thermal zone (%s) is too hot (%d C); "
"initiating shutdown\n",
(char *)tzp->zone_name, K_TO_C(tmp));
tzmon_do_shutdown();
}
if (tzp->crt > 0 && tmp >= tzp->crt) {
cmn_err(CE_WARN,
"tzmon: Thermal zone (%s) is critically hot (%d C); "
"initiating rapid shutdown\n",
(char *)tzp->zone_name, K_TO_C(tmp));
mdboot(A_REBOOT, AD_HALT, NULL, B_FALSE);
}
for (level = 0, new_level = -1; level < TZ_NUM_LEVELS; level++) {
if (tzp->ac[level] >= 0 && (tmp >= tzp->ac[level])) {
new_level = level;
break;
}
}
if (tzp->current_level != new_level) {
if ((tzp->current_level >= 0) &&
(tzp->al[tzp->current_level].Length != 0))
tzmon_set_power(tzp->al[tzp->current_level], 0,
(char *)tzp->zone_name);
if ((new_level >= 0) &&
(tzp->al[new_level].Length != 0))
tzmon_set_power(tzp->al[new_level], 1,
(char *)tzp->zone_name);
tzp->current_level = new_level;
}
mutex_exit(&tzp->lock);
}
static void
tzmon_do_shutdown(void)
{
proc_t *initpp;
mutex_enter(&pidlock);
initpp = prfind(P_INITPID);
mutex_exit(&pidlock);
if (initpp == NULL) {
mdboot(A_REBOOT, AD_HALT, NULL, B_FALSE);
}
psignal(initpp, SIGPWR);
}