#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <limits.h>
#include <syslog.h>
#include <errno.h>
#include <fcntl.h>
#include <picl.h>
#include <picltree.h>
#include <pthread.h>
#include <sys/pm.h>
#include <sys/open.h>
#include <sys/time.h>
#include <sys/utsname.h>
#include <sys/systeminfo.h>
#include <sys/i2c/clients/max1617.h>
#include <sys/i2c/clients/i2c_client.h>
#include "envd.h"
static void piclenvd_register(void);
static void piclenvd_init(void);
static void piclenvd_fini(void);
extern void env_picl_setup();
#pragma init(piclenvd_register)
static picld_plugin_reg_t my_reg_info = {
PICLD_PLUGIN_VERSION_1,
PICLD_PLUGIN_CRITICAL,
"SUNW_piclenvd",
piclenvd_init,
piclenvd_fini,
};
int env_debug;
static int sensor_poll_interval = SENSOR_POLL_INTERVAL;
static int warning_interval = WARNING_INTERVAL;
static int shutdown_interval = SHUTDOWN_INTERVAL;
static char shutdown_cmd[128] = SHUTDOWN_CMD;
static int monitor_temperature = 0;
static sensor_thresh_t cpu_die_thresh = {
CPU_DIE_LOW_POWER_OFF, CPU_DIE_HIGH_POWER_OFF,
CPU_DIE_LOW_SHUTDOWN, CPU_DIE_HIGH_SHUTDOWN,
CPU_DIE_LOW_WARNING, CPU_DIE_HIGH_WARNING,
CPU_DIE_TARGET_TEMP
};
static sensor_thresh_t cpu_amb_thresh = {
CPU_AMB_LOW_POWER_OFF, CPU_AMB_HIGH_POWER_OFF,
CPU_AMB_LOW_SHUTDOWN, CPU_AMB_HIGH_SHUTDOWN,
CPU_AMB_LOW_WARNING, CPU_AMB_HIGH_WARNING,
CPU_AMB_TARGET_TEMP
};
static env_sensor_t cpu_die_sensor =
{ SENSOR_CPU_DIE, CPU_DIE_SENSOR_DEVFS, &cpu_die_thresh};
static env_sensor_t cpu_amb_sensor =
{ SENSOR_CPU_AMB, CPU_AMB_SENSOR_DEVFS, &cpu_amb_thresh};
static env_sensor_t *envd_sensors[] = {
&cpu_die_sensor,
&cpu_amb_sensor,
NULL
};
static env_fan_t envd_system_fan = {
ENV_SYSTEM_FAN, ENV_SYSTEM_FAN_DEVFS,
SYSTEM_FAN_SPEED_MIN, SYSTEM_FAN_SPEED_MAX,
};
static env_fan_t *envd_fans[] = {
&envd_system_fan,
NULL
};
static boolean_t envd_inited = B_FALSE;
static boolean_t system_shutdown_started;
static boolean_t envthr_created;
static pthread_t envthr_tid;
static pthread_attr_t thr_attr;
static pthread_t pmthr_tid;
static int pmthr_created;
static int pm_fd;
static int cur_lpstate;
static pthread_mutex_t lpstate_lock;
static pthread_cond_t lpstate_cond;
typedef struct {
char *name;
void *addr;
int type;
int size;
} env_tuneable_t;
#define KTYPE_INT 1
#define KTYPE_STRING 2
static env_tuneable_t env_tuneables[] = {
{"cpu_amb_low_shutdown", &cpu_amb_thresh.low_shutdown, KTYPE_INT,
sizeof (tempr_t)},
{"cpu_amb_low_warning", &cpu_amb_thresh.low_warning, KTYPE_INT,
sizeof (tempr_t)},
{"cpu_amb_target_temp", &cpu_amb_thresh.target_temp, KTYPE_INT,
sizeof (tempr_t)},
{"cpu_amb_high_shutdown", &cpu_amb_thresh.high_shutdown, KTYPE_INT,
sizeof (tempr_t)},
{"cpu_amb_high_warning", &cpu_amb_thresh.high_warning, KTYPE_INT,
sizeof (tempr_t)},
{"cpu_die_low_shutdown", &cpu_die_thresh.low_shutdown, KTYPE_INT,
sizeof (tempr_t)},
{"cpu_die_low_warning", &cpu_die_thresh.low_warning, KTYPE_INT,
sizeof (tempr_t)},
{"cpu_die_target_temp", &cpu_die_thresh.target_temp, KTYPE_INT,
sizeof (tempr_t)},
{"cpu_die_high_shutdown", &cpu_die_thresh.high_shutdown, KTYPE_INT,
sizeof (tempr_t)},
{"cpu_die_high_warning", &cpu_die_thresh.high_warning, KTYPE_INT,
sizeof (tempr_t)},
{"sensor_poll_interval", &sensor_poll_interval, KTYPE_INT,
sizeof (sensor_poll_interval)},
{"monitor_temperature", &monitor_temperature, KTYPE_INT,
sizeof (monitor_temperature)},
{"warning_interval", &warning_interval, KTYPE_INT,
sizeof (warning_interval)},
{"shutdown_interval", &shutdown_interval, KTYPE_INT,
sizeof (shutdown_interval)},
{"shutdown_cmd", &shutdown_cmd[0], KTYPE_STRING, sizeof (shutdown_cmd)},
{"env_debug", &env_debug, KTYPE_INT, sizeof (env_debug)},
{ NULL, NULL, 0, 0}
};
env_fan_t *
fan_lookup(char *name)
{
int i;
env_fan_t *fanp;
for (i = 0; (fanp = envd_fans[i]) != NULL; i++) {
if (strcmp(fanp->name, name) == 0)
return (fanp);
}
return (NULL);
}
env_sensor_t *
sensor_lookup(char *name)
{
int i;
env_sensor_t *sensorp;
for (i = 0; (sensorp = envd_sensors[i]) != NULL; i++) {
if (strcmp(sensorp->name, name) == 0)
return (sensorp);
}
return (NULL);
}
int
get_temperature(env_sensor_t *sensorp, tempr_t *temp)
{
int fd = sensorp->fd;
int retval = 0;
if (fd == -1)
retval = -1;
else if (ioctl(fd, I2C_GET_TEMPERATURE, temp) == -1) {
retval = -1;
if (sensorp->error == 0) {
sensorp->error = 1;
envd_log(LOG_WARNING, ENV_SENSOR_ACCESS_FAIL,
sensorp->name, errno, strerror(errno));
}
} else if (sensorp->error != 0) {
sensorp->error = 0;
envd_log(LOG_WARNING, ENV_SENSOR_ACCESS_OK, sensorp->name);
}
return (retval);
}
int
get_fan_speed(env_fan_t *fanp, fanspeed_t *fanspeedp)
{
int fan_fd;
int retval = 0;
fan_fd = fanp->fd;
if (fan_fd == -1 || read(fan_fd, fanspeedp, sizeof (fanspeed_t)) !=
sizeof (fanspeed_t))
retval = -1;
return (retval);
}
static int
set_fan_speed(env_fan_t *fanp, fanspeed_t fanspeed)
{
int fan_fd;
int retval = 0;
fan_fd = fanp->fd;
if (fan_fd == -1 || write(fan_fd, &fanspeed, sizeof (fanspeed)) !=
sizeof (fanspeed_t))
retval = -1;
return (retval);
}
static void
envd_close_fans(void)
{
int i;
env_fan_t *fanp;
for (i = 0; (fanp = envd_fans[i]) != NULL; i++) {
if (fanp->fd != -1) {
(void) close(fanp->fd);
fanp->fd = -1;
}
}
}
static void
envd_close_sensors(void)
{
int i;
env_sensor_t *sensorp;
for (i = 0; (sensorp = envd_sensors[i]) != NULL; i++) {
if (sensorp->fd != -1) {
(void) close(sensorp->fd);
sensorp->fd = -1;
}
}
}
static void
envd_close_pm(void)
{
if (pm_fd != -1) {
(void) close(pm_fd);
pm_fd = -1;
}
}
static int
envd_setup_fans(void)
{
int i, fd;
fanspeed_t speed;
env_fan_t *fanp;
char path[FILENAME_MAX];
int fancnt = 0;
for (i = 0; (fanp = envd_fans[i]) != NULL; i++) {
fanp->fd = -1;
fanp->cur_speed = 0;
fanp->prev_speed = 0;
(void) strcpy(path, "/devices");
(void) strlcat(path, fanp->devfs_path, sizeof (path));
fd = open(path, O_RDWR);
if (fd == -1) {
envd_log(LOG_WARNING, ENV_FAN_OPEN_FAIL, fanp->name,
fanp->devfs_path, errno, strerror(errno));
fanp->present = B_FALSE;
continue;
}
fanp->fd = fd;
fanp->present = B_TRUE;
fancnt++;
if (get_fan_speed(fanp, &speed) == -1) {
speed = fanp->speed_max/2;
(void) set_fan_speed(fanp, speed);
if (get_fan_speed(fanp, &speed) == -1)
continue;
}
fanp->cur_speed = speed;
fanp->prev_speed = speed;
}
return (fancnt);
}
static int
envd_setup_sensors(void)
{
int i;
tempr_t temp;
env_sensor_t *sensorp;
char path[FILENAME_MAX];
int sensorcnt = 0;
sensor_thresh_t *threshp;
for (i = 0; (sensorp = envd_sensors[i]) != NULL; i++) {
sensorp->fd = -1;
sensorp->shutdown_initiated = B_FALSE;
sensorp->warning_tstamp = 0;
sensorp->shutdown_tstamp = 0;
threshp = sensorp->temp_thresh;
sensorp->cur_temp = threshp->target_temp;
sensorp->error = 0;
(void) strcpy(path, "/devices");
(void) strlcat(path, sensorp->devfs_path, sizeof (path));
sensorp->fd = open(path, O_RDWR);
if (sensorp->fd == -1) {
envd_log(LOG_WARNING, ENV_SENSOR_OPEN_FAIL,
sensorp->name, sensorp->devfs_path, errno,
strerror(errno));
sensorp->present = B_FALSE;
continue;
}
sensorp->present = B_TRUE;
sensorcnt++;
if (monitor_temperature) {
(void) ioctl(sensorp->fd, MAX1617_SET_LOW_LIMIT,
&threshp->low_power_off);
(void) ioctl(sensorp->fd, MAX1617_SET_HIGH_LIMIT,
&threshp->high_power_off);
}
if (get_temperature(sensorp, &temp) == 0) {
sensorp->cur_temp = temp;
}
}
return (sensorcnt);
}
static void
monitor_sensors(void)
{
tempr_t temp;
int i;
env_sensor_t *sensorp;
sensor_thresh_t *threshp;
struct timeval ct;
char msgbuf[BUFSIZ];
char syscmd[BUFSIZ];
for (i = 0; (sensorp = envd_sensors[i]) != NULL; i++) {
if (get_temperature(sensorp, &temp) < 0)
continue;
sensorp->cur_temp = temp;
if (env_debug)
envd_log(LOG_INFO,
"sensor: %-13s temp cur:%3d target:%3d\n",
sensorp->name, temp,
sensorp->temp_thresh->target_temp);
if (!monitor_temperature)
continue;
if (sensorp->shutdown_initiated)
continue;
threshp = sensorp->temp_thresh;
if (TEMP_IN_WARNING_RANGE(temp, threshp)) {
(void) gettimeofday(&ct, NULL);
if ((ct.tv_sec - sensorp->warning_tstamp) >=
warning_interval) {
envd_log(LOG_WARNING, ENV_WARNING_MSG,
sensorp->name, temp,
threshp->low_warning,
threshp->high_warning);
sensorp->warning_tstamp = ct.tv_sec;
}
}
if (TEMP_IN_SHUTDOWN_RANGE(temp, threshp)) {
(void) gettimeofday(&ct, NULL);
if (sensorp->shutdown_tstamp == 0)
sensorp->shutdown_tstamp = ct.tv_sec;
if ((ct.tv_sec - sensorp->shutdown_tstamp) >=
shutdown_interval) {
sensorp->shutdown_initiated = B_TRUE;
(void) snprintf(msgbuf, sizeof (msgbuf),
ENV_SHUTDOWN_MSG, sensorp->name,
temp, threshp->low_shutdown,
threshp->high_shutdown);
envd_log(LOG_CRIT, msgbuf);
if (system_shutdown_started == B_FALSE) {
(void) snprintf(syscmd, sizeof (syscmd),
"%s \"%s\"", shutdown_cmd, msgbuf);
envd_log(LOG_CRIT, syscmd);
system_shutdown_started = B_TRUE;
(void) system(syscmd);
}
}
} else if (sensorp->shutdown_tstamp != 0)
sensorp->shutdown_tstamp = 0;
}
}
static void *
envthr(void *args)
{
int err;
fanspeed_t fan_speed;
struct timeval ct;
struct timespec to;
env_fan_t *pmfanp = &envd_system_fan;
tempr_t cpu_amb_temp, cpu_die_temp;
tempr_t cpu_amb_warning, cpu_die_warning;
(void) pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
(void) pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
cpu_amb_warning = cpu_amb_sensor.temp_thresh->high_warning;
cpu_die_warning = cpu_die_sensor.temp_thresh->high_warning;
for (;;) {
(void) gettimeofday(&ct, NULL);
monitor_sensors();
cpu_amb_temp = cpu_amb_sensor.cur_temp;
cpu_die_temp = cpu_die_sensor.cur_temp;
to.tv_sec = ct.tv_sec + sensor_poll_interval;
to.tv_nsec = 0;
for (;;) {
fan_speed = pmfanp->speed_max;
if (cur_lpstate && cpu_amb_temp < cpu_amb_warning &&
cpu_die_temp < cpu_die_warning)
fan_speed = pmfanp->speed_min;
if (env_debug)
envd_log(LOG_INFO,
"fan: %-16s speed cur:%3d new:%3d "
"low-power:%d\n", pmfanp->name,
(uint_t)pmfanp->cur_speed,
(uint_t)fan_speed, cur_lpstate);
if (fan_speed != pmfanp->cur_speed &&
set_fan_speed(pmfanp, fan_speed) == 0)
pmfanp->cur_speed = fan_speed;
pthread_mutex_lock(&lpstate_lock);
err = pthread_cond_timedwait(&lpstate_cond,
&lpstate_lock, &to);
pthread_mutex_unlock(&lpstate_lock);
if (err == ETIMEDOUT)
break;
}
}
return (NULL);
}
static void *
pmthr(void *args)
{
pm_state_change_t pmstate;
char physpath[PATH_MAX];
int prev_lpstate;
(void) pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
(void) pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
pmstate.physpath = physpath;
pmstate.size = sizeof (physpath);
cur_lpstate = 0;
prev_lpstate = 0;
for (;;) {
if (ioctl(pm_fd, PM_GET_STATE_CHANGE_WAIT, &pmstate) != 0) {
if (errno != EINTR)
break;
continue;
}
do {
if (env_debug > 1) {
envd_log(LOG_INFO,
"pmstate event:0x%x flags:%x comp:%d "
"oldval:%d newval:%d path:%s\n",
pmstate.event, pmstate.flags,
pmstate.component, pmstate.old_level,
pmstate.new_level, pmstate.physpath);
}
cur_lpstate =
(pmstate.flags & PSC_ALL_LOWEST) ? 1 : 0;
} while (ioctl(pm_fd, PM_GET_STATE_CHANGE, &pmstate) == 0);
if (cur_lpstate != prev_lpstate) {
prev_lpstate = cur_lpstate;
pthread_mutex_lock(&lpstate_lock);
pthread_cond_signal(&lpstate_cond);
pthread_mutex_unlock(&lpstate_lock);
}
}
if (cur_lpstate != 0) {
prev_lpstate = cur_lpstate;
cur_lpstate = 0;
pthread_mutex_lock(&lpstate_lock);
pthread_cond_signal(&lpstate_cond);
pthread_mutex_unlock(&lpstate_lock);
}
envd_log(LOG_ERR, PM_THREAD_EXITING, errno, strerror(errno));
return (NULL);
}
static char *
parse_string_val(char *buf)
{
char *p, c;
if (buf[0] != '"')
return (NULL);
for (p = buf+1; (c = *p) != '\0'; p++)
if (c == '"' || (c == '\\' && *++p == '\0'))
break;
return ((*p == '"') ? p : NULL);
}
static void
process_env_conf_file(void)
{
int line, len, val, toklen;
char buf[BUFSIZ];
FILE *fp;
env_tuneable_t *tunep;
char nmbuf[SYS_NMLN];
char fname[PATH_MAX];
char *tok, *valuep, *strend;
char tokdel[] = " \t\n\r";
int skip_line = 0;
if (sysinfo(SI_PLATFORM, nmbuf, sizeof (nmbuf)) == -1)
return;
(void) snprintf(fname, sizeof (fname), PICLD_PLAT_PLUGIN_DIRF, nmbuf);
(void) strlcat(fname, ENV_CONF_FILE, sizeof (fname));
fp = fopen(fname, "r");
if (fp == NULL)
return;
for (line = 1; fgets(buf, sizeof (buf), fp) != NULL; line++) {
len = strlen(buf);
if (len <= 0)
continue;
if (buf[len-1] != '\n') {
skip_line = 1;
continue;
} else if (skip_line) {
skip_line = 0;
continue;
} else
buf[len-1] = '\0';
if (buf[0] == '*' || buf[0] == '#')
continue;
tok = buf + strspn(buf, tokdel);
if (*tok == '\0')
continue;
toklen = strcspn(tok, tokdel);
tok[toklen] = '\0';
valuep = tok + toklen + 1;
if (valuep > buf+len)
valuep = buf + len;
for (tunep = &env_tuneables[0]; tunep->name != NULL; tunep++) {
if (strcmp(tunep->name, tok) != 0)
continue;
switch (tunep->type) {
case KTYPE_INT:
errno = 0;
val = strtol(valuep, &valuep, 0);
if (errno != 0 || strtok(valuep, tokdel)) {
envd_log(LOG_INFO,
ENV_CONF_INT_EXPECTED,
fname, line, tok);
break;
}
if (tunep->size == sizeof (int8_t) &&
val == (int8_t)val)
*(int8_t *)tunep->addr = (int8_t)val;
else if (tunep->size == sizeof (short) &&
val == (short)val)
*(short *)tunep->addr = (short)val;
else if (tunep->size == sizeof (int))
*(int *)tunep->addr = (int)val;
else {
envd_log(LOG_INFO,
ENV_CONF_INT_EXPECTED,
fname, line, tok);
break;
}
if (env_debug)
envd_log(LOG_INFO, "SUNW_piclenvd: "
"file:%s line:%d %s = %d\n",
fname, line, tok, val);
break;
case KTYPE_STRING:
valuep += strspn(valuep, tokdel);
strend = parse_string_val(valuep);
if (strend == NULL || *valuep != '"' ||
strtok(strend+1, tokdel) != NULL ||
(strend-valuep) > tunep->size) {
envd_log(LOG_INFO,
ENV_CONF_STRING_EXPECTED,
fname, line, tok,
tunep->size);
break;
}
*strend = '\0';
if (env_debug)
envd_log(LOG_INFO, "piclenvd: file:%s"
" line:%d %s = \"%s\"\n",
fname, line, tok, valuep+1);
(void) strcpy(tunep->addr, (caddr_t)valuep+1);
break;
default:
envd_log(LOG_INFO,
ENV_CONF_UNSUPPORTED_TYPE,
fname, line,
tunep->type, tunep->name);
}
break;
}
if (tunep->name == NULL)
envd_log(LOG_INFO, ENV_CONF_UNSUPPORTED_KEYWORD,
fname, line, tok);
}
(void) fclose(fp);
}
static int
envd_setup(void)
{
if (envd_inited == B_FALSE) {
system_shutdown_started = B_FALSE;
envthr_created = B_FALSE;
pmthr_created = B_FALSE;
if (pthread_attr_init(&thr_attr) != 0 ||
pthread_attr_setscope(&thr_attr, PTHREAD_SCOPE_SYSTEM) != 0)
return (-1);
if (pthread_mutex_init(&lpstate_lock, NULL) != 0 ||
pthread_cond_init(&lpstate_cond, NULL) != 0)
return (-1);
process_env_conf_file();
if (envd_setup_sensors() <= 0)
return (-1);
(void) envd_setup_fans();
if (envthr_created == B_FALSE && pthread_create(&envthr_tid,
&thr_attr, envthr, (void *)NULL) != 0) {
envd_close_fans();
envd_close_sensors();
envd_log(LOG_CRIT, ENV_THREAD_CREATE_FAILED);
return (-1);
}
envthr_created = B_TRUE;
}
envd_inited = B_TRUE;
if (pmthr_created == B_FALSE) {
pm_fd = open(PM_DEVICE, O_RDONLY);
if (pm_fd == -1 || pthread_create(&pmthr_tid, &thr_attr,
pmthr, (void *)NULL) != 0) {
envd_close_pm();
envd_log(LOG_CRIT, PM_THREAD_CREATE_FAILED);
} else
pmthr_created = B_TRUE;
}
return (0);
}
static void
piclenvd_register(void)
{
picld_plugin_register(&my_reg_info);
}
static void
piclenvd_init(void)
{
if (envd_setup() != 0) {
envd_log(LOG_CRIT, ENVD_PLUGIN_INIT_FAILED);
return;
}
env_picl_setup();
}
static void
piclenvd_fini(void)
{
void *exitval;
if (envthr_created) {
(void) pthread_cancel(envthr_tid);
(void) pthread_join(envthr_tid, &exitval);
envthr_created = B_FALSE;
}
if (pmthr_created) {
(void) pthread_cancel(pmthr_tid);
(void) pthread_join(pmthr_tid, &exitval);
pmthr_created = B_FALSE;
}
envd_close_pm();
envd_close_fans();
envd_close_sensors();
envd_inited = B_FALSE;
}
void
envd_log(int pri, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsyslog(pri, fmt, ap);
va_end(ap);
}