/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* This file contains the environmental PICL plug-in module.
*/
/*
* This plugin sets up the PICLTREE for Chicago WS.
* It provides functionality to get/set temperatures and
* fan speeds.
*
* The environmental policy defaults to the auto mode
* as programmed by OBP at boot time.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysmacros.h>
#include <limits.h>
#include <string.h>
#include <strings.h>
#include <stdarg.h>
#include <alloca.h>
#include <unistd.h>
#include <sys/processor.h>
#include <syslog.h>
#include <errno.h>
#include <fcntl.h>
#include <picl.h>
#include <picltree.h>
#include <picldefs.h>
#include <pthread.h>
#include <signal.h>
#include <libdevinfo.h>
#include <sys/pm.h>
#include <sys/open.h>
#include <sys/time.h>
#include <sys/utsname.h>
#include <sys/systeminfo.h>
#include <note.h>
#include <sys/pic16f747.h>
#include "envd.h"
#include <sys/scsi/scsi.h>
#include <sys/scsi/generic/commands.h>
int debug_fd;
/*
* PICL plugin entry points
*/
static void piclenvd_register(void);
static void piclenvd_init(void);
static void piclenvd_fini(void);
/*
* Env setup routines
*/
extern void env_picl_setup(void);
extern void env_picl_destroy(void);
extern int env_picl_setup_tuneables(void);
static boolean_t has_fan_failed(env_fan_t *fanp);
/*
* PSU fan fault handling
*/
static boolean_t has_psufan_failed(void);
static int psufan_last_status = FAN_OK;
#pragma init(piclenvd_register)
/*
* Plugin registration information
*/
static picld_plugin_reg_t my_reg_info = {
PICLD_PLUGIN_VERSION,
PICLD_PLUGIN_CRITICAL,
"SUNW_piclenvd",
piclenvd_init,
piclenvd_fini,
};
#define REGISTER_INFORMATION_STRING_LENGTH 16
static char fan_rpm_string[REGISTER_INFORMATION_STRING_LENGTH] = {0};
static char fan_status_string[REGISTER_INFORMATION_STRING_LENGTH] = {0};
static int scsi_log_sense(env_disk_t *diskp, uchar_t page_code,
void *pagebuf, uint16_t pagelen, int page_control);
static int scsi_mode_select(env_disk_t *diskp, uchar_t page_code,
uchar_t *pagebuf, uint16_t pagelen);
static int get_disk_temp(env_disk_t *);
/*
* ES Segment stuff
*/
static es_sensor_blk_t sensor_ctl[MAX_SENSORS];
/*
* Default limits for sensors, in case ES segment is not present, or has
* inconsistent information
*/
static es_sensor_blk_t sensor_default_ctl[MAX_SENSORS] = {
{
CPU0_HIGH_POWER_OFF, CPU0_HIGH_SHUTDOWN, CPU0_HIGH_WARNING,
CPU0_LOW_WARNING, CPU0_LOW_SHUTDOWN, CPU0_LOW_POWER_OFF
},
{
CPU1_HIGH_POWER_OFF, CPU1_HIGH_SHUTDOWN, CPU1_HIGH_WARNING,
CPU1_LOW_WARNING, CPU1_LOW_SHUTDOWN, CPU1_LOW_POWER_OFF
},
{
ADT7462_HIGH_POWER_OFF, ADT7462_HIGH_SHUTDOWN, ADT7462_HIGH_WARNING,
ADT7462_LOW_WARNING, ADT7462_LOW_SHUTDOWN, ADT7462_LOW_POWER_OFF
},
{
MB_HIGH_POWER_OFF, MB_HIGH_SHUTDOWN, MB_HIGH_WARNING,
MB_LOW_WARNING, MB_LOW_SHUTDOWN, MB_LOW_POWER_OFF
},
{
LM95221_HIGH_POWER_OFF, LM95221_HIGH_SHUTDOWN, LM95221_HIGH_WARNING,
LM95221_LOW_WARNING, LM95221_LOW_SHUTDOWN, LM95221_LOW_POWER_OFF
},
{
FIRE_HIGH_POWER_OFF, FIRE_HIGH_SHUTDOWN, FIRE_HIGH_WARNING,
FIRE_LOW_WARNING, FIRE_LOW_SHUTDOWN, FIRE_LOW_POWER_OFF
},
{
LSI1064_HIGH_POWER_OFF, LSI1064_HIGH_SHUTDOWN, LSI1064_HIGH_WARNING,
LSI1064_LOW_WARNING, LSI1064_LOW_SHUTDOWN, LSI1064_LOW_POWER_OFF
},
{
FRONT_PANEL_HIGH_POWER_OFF, FRONT_PANEL_HIGH_SHUTDOWN,
FRONT_PANEL_HIGH_WARNING, FRONT_PANEL_LOW_WARNING,
FRONT_PANEL_LOW_SHUTDOWN, FRONT_PANEL_LOW_POWER_OFF
},
{
PSU_HIGH_POWER_OFF, PSU_HIGH_SHUTDOWN, PSU_HIGH_WARNING,
PSU_LOW_WARNING, PSU_LOW_SHUTDOWN, PSU_LOW_POWER_OFF
}
};
/*
* Env thread variables
*/
static boolean_t system_shutdown_started = B_FALSE;
static boolean_t system_temp_thr_created = B_FALSE;
static pthread_t system_temp_thr_id;
static pthread_attr_t thr_attr;
static boolean_t disk_temp_thr_created = B_FALSE;
static pthread_t disk_temp_thr_id;
static boolean_t fan_thr_created = B_FALSE;
static pthread_t fan_thr_id;
/*
* PM thread related variables
*/
static pthread_t pmthr_tid; /* pmthr thread ID */
static int pm_fd = -1; /* PM device file descriptor */
static boolean_t pmthr_created = B_FALSE;
static int cur_lpstate; /* cur low power state */
/*
* Envd plug-in verbose flag set by SUNW_PICLENVD_DEBUG environment var
* Setting the verbose tuneable also enables debugging for better
* control
*/
int env_debug = 0;
/*
* These are debug variables for keeping track of the total number
* of Fan and Temp sensor retries over the lifetime of the plugin.
*/
static int total_fan_retries = 0;
static int total_temp_retries = 0;
/*
* Fan devices
*/
static env_fan_t envd_system_fan0 = {
ENV_SYSTEM_FAN0, ENV_SYSTEM_FAN0_DEVFS, SYSTEM_FAN0_ID,
SYSTEM_FAN_SPEED_MIN, SYSTEM_FAN_SPEED_MAX, -1, -1,
};
static env_fan_t envd_system_fan1 = {
ENV_SYSTEM_FAN1, ENV_SYSTEM_FAN1_DEVFS, SYSTEM_FAN1_ID,
SYSTEM_FAN_SPEED_MIN, SYSTEM_FAN_SPEED_MAX, -1, -1,
};
static env_fan_t envd_system_fan2 = {
ENV_SYSTEM_FAN2, ENV_SYSTEM_FAN2_DEVFS, SYSTEM_FAN2_ID,
SYSTEM_FAN_SPEED_MIN, SYSTEM_FAN_SPEED_MAX, -1, -1,
};
static env_fan_t envd_system_fan3 = {
ENV_SYSTEM_FAN3, ENV_SYSTEM_FAN3_DEVFS, SYSTEM_FAN3_ID,
SYSTEM_FAN_SPEED_MIN, SYSTEM_FAN_SPEED_MAX, -1, -1,
};
static env_fan_t envd_system_fan4 = {
ENV_SYSTEM_FAN4, ENV_SYSTEM_FAN4_DEVFS, SYSTEM_FAN4_ID,
SYSTEM_FAN_SPEED_MIN, SYSTEM_FAN_SPEED_MAX, -1, -1,
};
/*
* Disk devices
*/
static env_disk_t envd_disk0 = {
ENV_DISK0, ENV_DISK0_DEVFS, DISK0_PHYSPATH, DISK0_NODE_PATH,
DISK0_ID, -1,
};
static env_disk_t envd_disk1 = {
ENV_DISK1, ENV_DISK1_DEVFS, DISK1_PHYSPATH, DISK1_NODE_PATH,
DISK1_ID, -1,
};
static env_disk_t envd_disk2 = {
ENV_DISK2, ENV_DISK2_DEVFS, DISK2_PHYSPATH, DISK2_NODE_PATH,
DISK2_ID, -1,
};
static env_disk_t envd_disk3 = {
ENV_DISK3, ENV_DISK3_DEVFS, DISK3_PHYSPATH, DISK3_NODE_PATH,
DISK3_ID, -1,
};
/*
* Sensors
*/
static env_sensor_t envd_sensor_cpu0 = {
SENSOR_CPU0, SENSOR_CPU0_DEVFS, CPU0_SENSOR_ID, -1, NULL,
};
static env_sensor_t envd_sensor_cpu1 = {
SENSOR_CPU1, SENSOR_CPU1_DEVFS, CPU1_SENSOR_ID, -1, NULL,
};
static env_sensor_t envd_sensor_adt7462 = {
SENSOR_ADT7462, SENSOR_ADT7462_DEVFS, ADT7462_SENSOR_ID, -1, NULL,
};
static env_sensor_t envd_sensor_mb = {
SENSOR_MB, SENSOR_MB_DEVFS, MB_SENSOR_ID, -1, NULL,
};
static env_sensor_t envd_sensor_lm95221 = {
SENSOR_LM95221, SENSOR_LM95221_DEVFS, LM95221_SENSOR_ID, -1, NULL,
};
static env_sensor_t envd_sensor_fire = {
SENSOR_FIRE, SENSOR_FIRE_DEVFS, FIRE_SENSOR_ID, -1, NULL,
};
static env_sensor_t envd_sensor_lsi1064 = {
SENSOR_LSI1064, SENSOR_LSI1064_DEVFS, LSI1064_SENSOR_ID, -1, NULL,
};
static env_sensor_t envd_sensor_front_panel = {
SENSOR_FRONT_PANEL, SENSOR_FRONT_PANEL_DEVFS, FRONT_PANEL_SENSOR_ID,
-1, NULL,
};
static env_sensor_t envd_sensor_psu = {
SENSOR_PSU, SENSOR_PSU_DEVFS, PSU_SENSOR_ID, -1, NULL,
};
/*
* The vendor-id and device-id are the properties associated with
* the SCSI controller. This is used to identify a particular controller
* like LSI1064.
*/
#define VENDOR_ID "vendor-id"
#define DEVICE_ID "device-id"
/*
* The implementation for SCSI disk drives to supply info. about
* temperature is not mandatory. Hence we first determine if the
* temperature page is supported. To do this we need to scan the list
* of pages supported.
*/
#define SUPPORTED_LPAGES 0
#define TEMPERATURE_PAGE 0x0D
#define LOGPAGEHDRSIZE 4
/*
* NULL terminated array of fans
*/
static env_fan_t *envd_fans[] = {
&envd_system_fan0,
&envd_system_fan1,
&envd_system_fan2,
&envd_system_fan3,
&envd_system_fan4,
NULL
};
/*
* NULL terminated array of disks
*/
static env_disk_t *envd_disks[] = {
&envd_disk0,
&envd_disk1,
&envd_disk2,
&envd_disk3,
NULL
};
/*
* NULL terminated array of temperature sensors
*/
#define N_ENVD_SENSORS 9
static env_sensor_t *envd_sensors[] = {
&envd_sensor_cpu0,
&envd_sensor_cpu1,
&envd_sensor_adt7462,
&envd_sensor_mb,
&envd_sensor_lm95221,
&envd_sensor_fire,
&envd_sensor_lsi1064,
&envd_sensor_front_panel,
&envd_sensor_psu,
NULL
};
#define NOT_AVAILABLE "NA"
/*
* Tuneables
*/
#define ENABLE 1
#define DISABLE 0
static int disk_high_warn_temperature = DISK_HIGH_WARN_TEMPERATURE;
static int disk_low_warn_temperature = DISK_LOW_WARN_TEMPERATURE;
static int disk_high_shutdown_temperature =
DISK_HIGH_SHUTDOWN_TEMPERATURE;
static int disk_low_shutdown_temperature = DISK_LOW_SHUTDOWN_TEMPERATURE;
static int disk_scan_interval = DISK_SCAN_INTERVAL;
static int sensor_scan_interval = SENSOR_SCAN_INTERVAL;
static int fan_scan_interval = FAN_SCAN_INTERVAL;
static int get_int_val(ptree_rarg_t *parg, void *buf);
static int set_int_val(ptree_warg_t *parg, const void *buf);
static int get_string_val(ptree_rarg_t *parg, void *buf);
static int set_string_val(ptree_warg_t *parg, const void *buf);
static int shutdown_override = 0;
static int sensor_warning_interval = SENSOR_WARNING_INTERVAL;
static int sensor_warning_duration = SENSOR_WARNING_DURATION;
static int sensor_shutdown_interval = SENSOR_SHUTDOWN_INTERVAL;
static int disk_warning_interval = DISK_WARNING_INTERVAL;
static int disk_warning_duration = DISK_WARNING_DURATION;
static int disk_shutdown_interval = DISK_SHUTDOWN_INTERVAL;
static int system_temp_monitor = 1; /* enabled */
static int fan_monitor = 1; /* enabled */
static int pm_monitor = 1; /* enabled */
/* Disable disk temperature monitoring until we have LSI fw support */
int disk_temp_monitor = 0;
static char shutdown_cmd[] = SHUTDOWN_CMD;
const char *iofru_devname = I2C_DEVFS "/" IOFRU_DEV;
env_tuneable_t tuneables[] = {
{"system_temp-monitor", PICL_PTYPE_INT, &system_temp_monitor,
&get_int_val, &set_int_val, sizeof (int)},
{"fan-monitor", PICL_PTYPE_INT, &fan_monitor,
&get_int_val, &set_int_val, sizeof (int)},
{"pm-monitor", PICL_PTYPE_INT, &pm_monitor,
&get_int_val, &set_int_val, sizeof (int)},
{"shutdown-override", PICL_PTYPE_INT, &shutdown_override,
&get_int_val, &set_int_val, sizeof (int)},
{"sensor-warning-duration", PICL_PTYPE_INT,
&sensor_warning_duration,
&get_int_val, &set_int_val,
sizeof (int)},
{"disk-scan-interval", PICL_PTYPE_INT,
&disk_scan_interval,
&get_int_val, &set_int_val,
sizeof (int)},
{"fan-scan-interval", PICL_PTYPE_INT,
&fan_scan_interval,
&get_int_val, &set_int_val,
sizeof (int)},
{"sensor-scan-interval", PICL_PTYPE_INT,
&sensor_scan_interval,
&get_int_val, &set_int_val,
sizeof (int)},
{"sensor_warning-interval", PICL_PTYPE_INT, &sensor_warning_interval,
&get_int_val, &set_int_val,
sizeof (int)},
{"sensor_shutdown-interval", PICL_PTYPE_INT, &sensor_shutdown_interval,
&get_int_val, &set_int_val,
sizeof (int)},
{"disk_warning-interval", PICL_PTYPE_INT, &disk_warning_interval,
&get_int_val, &set_int_val,
sizeof (int)},
{"disk_warning-duration", PICL_PTYPE_INT, &disk_warning_duration,
&get_int_val, &set_int_val,
sizeof (int)},
{"disk_shutdown-interval", PICL_PTYPE_INT, &disk_shutdown_interval,
&get_int_val, &set_int_val,
sizeof (int)},
{"shutdown-command", PICL_PTYPE_CHARSTRING, shutdown_cmd,
&get_string_val, &set_string_val,
sizeof (shutdown_cmd)},
{"monitor-disk-temp", PICL_PTYPE_INT, &disk_temp_monitor,
&get_int_val, &set_int_val, sizeof (int)},
{"disk-high-warn-temperature", PICL_PTYPE_INT,
&disk_high_warn_temperature, &get_int_val,
&set_int_val, sizeof (int)},
{"disk-low-warn-temperature", PICL_PTYPE_INT,
&disk_low_warn_temperature, &get_int_val,
&set_int_val, sizeof (int)},
{"disk-high-shutdown-temperature", PICL_PTYPE_INT,
&disk_high_shutdown_temperature, &get_int_val,
&set_int_val, sizeof (int)},
{"disk-low-shutdown-temperature", PICL_PTYPE_INT,
&disk_low_shutdown_temperature, &get_int_val,
&set_int_val, sizeof (int)},
{"verbose", PICL_PTYPE_INT, &env_debug,
&get_int_val, &set_int_val, sizeof (int)}
};
/*
* We use this to figure out how many tuneables there are
* This is variable because the publishing routine needs this info
* in piclenvsetup.c
*/
int ntuneables = (sizeof (tuneables)/sizeof (tuneables[0]));
/*
* Lookup fan and return a pointer to env_fan_t data structure.
*/
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);
}
/*
* Lookup sensor and return a pointer to env_sensor_t data structure.
*/
env_sensor_t *
sensor_lookup(char *name)
{
env_sensor_t *sensorp;
int i;
for (i = 0; i < N_ENVD_SENSORS; ++i) {
sensorp = envd_sensors[i];
if (strcmp(sensorp->name, name) == 0)
return (sensorp);
}
return (NULL);
}
/*
* Lookup disk and return a pointer to env_disk_t data structure.
*/
env_disk_t *
disk_lookup(char *name)
{
int i;
env_disk_t *diskp;
for (i = 0; (diskp = envd_disks[i]) != NULL; i++) {
if (strncmp(diskp->name, name, strlen(name)) == 0)
return (diskp);
}
return (NULL);
}
/*
* Get current temperature
* Returns -1 on error, 0 if successful
*/
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, PIC_GET_TEMPERATURE, temp) != 0) {
retval = -1;
sensorp->error++;
if (sensorp->error == MAX_SENSOR_RETRIES) {
envd_log(LOG_WARNING, ENV_SENSOR_ACCESS_FAIL,
sensorp->name, errno, strerror(errno));
}
total_temp_retries++;
(void) sleep(1);
} else if (sensorp->error != 0) {
if (sensorp->error >= MAX_SENSOR_RETRIES) {
envd_log(LOG_WARNING, ENV_SENSOR_ACCESS_OK,
sensorp->name);
}
sensorp->error = 0;
if (total_temp_retries && env_debug) {
envd_log(LOG_WARNING,
"Total retries for sensors = %d",
total_temp_retries);
}
}
return (retval);
}
/*
* Get current disk temperature
* Returns -1 on error, 0 if successful
*/
int
disk_temperature(env_disk_t *diskp, tempr_t *temp)
{
int retval = 0;
if (diskp == NULL)
retval = -1;
else
*temp = diskp->current_temp;
return (retval);
}
/*
* Get current fan speed
* This function returns a RPM value for fanspeed
* in fanspeedp.
* Returns -1 on error, 0 if successful
*/
int
get_fan_speed(env_fan_t *fanp, fanspeed_t *fanspeedp)
{
uint8_t tach;
int real_tach;
int retries;
if (fanp->fd == -1)
return (-1);
if (has_fan_failed(fanp)) {
*fanspeedp = 0;
return (0);
}
/* try to read the fan information */
for (retries = 0; retries < MAX_FAN_RETRIES; retries++) {
if (ioctl(fanp->fd, PIC_GET_FAN_SPEED, &tach) == 0)
break;
(void) sleep(1);
}
total_fan_retries += retries;
if (retries >= MAX_FAN_RETRIES)
return (-1);
if (total_fan_retries && env_debug) {
envd_log(LOG_WARNING, "total retries for fan = %d",
total_fan_retries);
}
real_tach = tach << 8;
*fanspeedp = TACH_TO_RPM(real_tach);
return (0);
}
/*
* Set fan speed
* This function accepts a percentage of fan speed
* from 0-100 and programs the HW monitor fans to the corresponding
* fanspeed value.
* Returns -1 on error, -2 on invalid args passed, 0 if successful
*/
int
set_fan_speed(env_fan_t *fanp, fanspeed_t fanspeed)
{
uint8_t speed;
if (fanp->fd == -1)
return (-1);
if (fanspeed < 0 || fanspeed > 100)
return (-2);
speed = fanspeed;
if (ioctl(fanp->fd, PIC_SET_FAN_SPEED, &speed) != 0)
return (-1);
return (0);
}
/*
* close all fan devices
*/
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;
}
}
}
/*
* Close sensor devices and freeup resources
*/
static void
envd_close_sensors(void)
{
env_sensor_t *sensorp;
int i;
for (i = 0; i < N_ENVD_SENSORS; ++i) {
sensorp = envd_sensors[i];
if (sensorp->fd != -1) {
(void) close(sensorp->fd);
sensorp->fd = -1;
}
}
}
/*
* Open fan devices and initialize per fan data structure.
*/
static int
envd_setup_fans(void)
{
int i, fd;
env_fan_t *fanp;
int fancnt = 0;
picl_nodehdl_t tnodeh;
for (i = 0; (fanp = envd_fans[i]) != NULL; i++) {
fanp->last_status = FAN_OK;
/* Make sure cpu0/1 present for validating cpu fans */
if (fanp->id == CPU0_FAN_ID) {
if (ptree_get_node_by_path(CPU0_PATH, &tnodeh) !=
PICL_SUCCESS) {
if (env_debug) {
envd_log(LOG_ERR,
"get node by path failed for %s\n",
CPU0_PATH);
}
fanp->present = B_FALSE;
continue;
}
}
if (fanp->id == CPU1_FAN_ID) {
if (ptree_get_node_by_path(CPU1_PATH, &tnodeh) !=
PICL_SUCCESS) {
if (env_debug) {
envd_log(LOG_ERR,
"get node by path failed for %s\n", CPU0_PATH);
}
fanp->present = B_FALSE;
continue;
}
}
if ((fd = open(fanp->devfs_path, O_RDWR)) == -1) {
envd_log(LOG_CRIT,
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 (fancnt == 0)
return (-1);
return (0);
}
static int
envd_setup_disks(void)
{
int ret, i, page_index, page_len;
picl_nodehdl_t tnodeh;
env_disk_t *diskp;
uint_t vendor_id;
uint_t device_id;
uchar_t log_page[256];
if (ptree_get_node_by_path(SCSI_CONTROLLER_NODE_PATH,
&tnodeh) != PICL_SUCCESS) {
if (env_debug) {
envd_log(LOG_ERR, "On-Board SCSI controller %s "
"not found in the system.\n",
SCSI_CONTROLLER_NODE_PATH);
}
return (-1);
}
if ((ret = ptree_get_propval_by_name(tnodeh, VENDOR_ID,
&vendor_id, sizeof (vendor_id))) != 0) {
if (env_debug) {
envd_log(LOG_ERR, "Error in getting vendor-id "
"for SCSI controller. ret = %d errno = 0x%d\n",
ret, errno);
}
return (-1);
}
if ((ret = ptree_get_propval_by_name(tnodeh, DEVICE_ID,
&device_id, sizeof (device_id))) != 0) {
if (env_debug) {
envd_log(LOG_ERR, "Error in getting device-id "
"for SCSI controller. ret = %d errno = 0x%d\n",
ret, errno);
}
return (-1);
}
/*
* We have found LSI1064 SCSi controller onboard.
*/
for (i = 0; (diskp = envd_disks[i]) != NULL; i++) {
if (ptree_get_node_by_path(diskp->nodepath,
&tnodeh) != PICL_SUCCESS) {
diskp->present = B_FALSE;
if (env_debug) {
envd_log(LOG_ERR,
"DISK %d: %s not found in the system.\n",
diskp->id, diskp->nodepath);
}
continue;
}
if ((diskp->fd = open(diskp->devfs_path, O_RDONLY)) == -1) {
diskp->present = B_FALSE;
if (env_debug) {
envd_log(LOG_ERR,
"Error in opening %s errno = 0x%x\n",
diskp->devfs_path, errno);
}
continue;
}
diskp->present = B_TRUE;
diskp->tpage_supported = B_FALSE;
diskp->smart_supported = B_FALSE;
diskp->warning_tstamp = 0;
diskp->shutdown_tstamp = 0;
diskp->high_warning = disk_high_warn_temperature;
diskp->low_warning = disk_low_warn_temperature;
diskp->high_shutdown = disk_high_shutdown_temperature;
diskp->low_shutdown = disk_low_shutdown_temperature;
/*
* Find out if the Temperature page is supported by the disk.
*/
if (scsi_log_sense(diskp, SUPPORTED_LPAGES, log_page,
sizeof (log_page), 1) == 0) {
page_len = ((log_page[2] << 8) & 0xFF00) | log_page[3];
for (page_index = LOGPAGEHDRSIZE;
page_index < page_len + LOGPAGEHDRSIZE;
page_index++) {
if (log_page[page_index] != TEMPERATURE_PAGE)
continue;
diskp->tpage_supported = B_TRUE;
if (env_debug) {
envd_log(LOG_ERR,
"tpage supported for %s\n",
diskp->nodepath);
}
}
}
/*
* If the temp log page failed, we can check if this is
* a SATA drive and attempt to read the temperature
* using the SMART interface.
*/
if (diskp->tpage_supported != B_TRUE) {
uchar_t iec_page[IEC_PAGE_SIZE];
if (env_debug)
envd_log(LOG_ERR, "Turning on SMART\n");
(void) memset(iec_page, 0, sizeof (iec_page));
iec_page[0] = IEC_PAGE; /* SMART PAGE */
iec_page[1] = 0xa; /* length */
/* Notification, only when requested */
iec_page[3] = REPORT_ON_REQUEST;
ret = scsi_mode_select(diskp, IEC_PAGE,
iec_page, sizeof (iec_page));
/*
* Since we know this is a SMART capable
* drive, we will try to set the page and
* determine if the drive is not capable
* of reading the TEMP page when we
* try to read the temperature and disable
* it then. We do not fail when reading
* or writing this page because we will
* determine the SMART capabilities
* when reading the temperature.
*/
if ((ret != 0) && (env_debug)) {
envd_log(LOG_ERR,
"Failed to set mode page");
}
diskp->smart_supported = B_TRUE;
diskp->tpage_supported = B_TRUE;
}
if (get_disk_temp(diskp) < 0) {
envd_log(LOG_ERR, " error reading temperature of:%s\n",
diskp->name);
} else if (env_debug) {
envd_log(LOG_ERR, "%s: temperature = %d\n",
diskp->name, diskp->current_temp);
}
}
return (0);
}
static int
envd_es_setup(void)
{
seeprom_scn_t scn_hdr;
seeprom_seg_t seg_hdr;
es_data_t *envseg;
es_sensor_t *sensorp;
int i, fd, id;
int envseg_len, esd_len;
char *envsegp;
/*
* Open the front io fru
*/
if ((fd = open(iofru_devname, O_RDONLY)) == -1) {
envd_log(LOG_ERR, ENV_FRU_OPEN_FAIL, iofru_devname, errno);
return (-1);
}
/*
* Read section header from the fru SEEPROM
*/
if (lseek(fd, SSCN_OFFSET, SEEK_SET) == (off_t)-1 ||
read(fd, &scn_hdr, sizeof (scn_hdr)) != sizeof (scn_hdr)) {
envd_log(LOG_ERR, ENV_FRU_BAD_ENVSEG, iofru_devname);
(void) close(fd);
return (-1);
}
if ((scn_hdr.sscn_tag != SSCN_TAG) ||
(GET_UNALIGN16(&scn_hdr.sscn_ver) != SSCN_VER)) {
envd_log(LOG_ERR, ENV_FRU_BAD_SCNHDR, scn_hdr.sscn_tag,
GET_UNALIGN16(&scn_hdr.sscn_ver));
(void) close(fd);
return (-1);
}
/*
* Locate environmental segment
*/
for (i = 0; i < scn_hdr.sscn_nsegs; i++) {
if (read(fd, &seg_hdr, sizeof (seg_hdr)) != sizeof (seg_hdr)) {
envd_log(LOG_ERR, ENV_FRU_BAD_ENVSEG, iofru_devname);
(void) close(fd);
return (-1);
}
if (env_debug) {
envd_log(LOG_INFO,
"Seg name: %x off:%x len:%x\n",
GET_UNALIGN16(&seg_hdr.sseg_name),
GET_UNALIGN16(&seg_hdr.sseg_off),
GET_UNALIGN16(&seg_hdr.sseg_len));
}
if (GET_UNALIGN16(&seg_hdr.sseg_name) == ENVSEG_NAME)
break;
}
if (i == scn_hdr.sscn_nsegs) {
envd_log(LOG_ERR, ENV_FRU_BAD_ENVSEG, iofru_devname);
(void) close(fd);
return (-1);
}
/*
* Read environmental segment
*/
envseg_len = GET_UNALIGN16(&seg_hdr.sseg_len);
if ((envseg = malloc(envseg_len)) == NULL) {
envd_log(LOG_ERR, ENV_FRU_NOMEM_FOR_SEG, envseg_len);
(void) close(fd);
return (-1);
}
if (lseek(fd, (off_t)GET_UNALIGN16(&seg_hdr.sseg_off),
SEEK_SET) == (off_t)-1 ||
read(fd, envseg, envseg_len) != envseg_len) {
envd_log(LOG_ERR, ENV_FRU_BAD_ENVSEG, iofru_devname);
free(envseg);
(void) close(fd);
return (-1);
}
/*
* Check environmental segment data for consistency
*/
esd_len = sizeof (*envseg) +
(envseg->esd_nsensors - 1) * sizeof (envseg->esd_sensors[0]);
if (envseg->esd_ver != ENVSEG_VERSION || envseg_len < esd_len) {
envd_log(LOG_ERR, ENV_FRU_BAD_ENVSEG, iofru_devname);
free(envseg);
(void) close(fd);
return (-1);
}
/*
* Process environmental segment data
*/
if (envseg->esd_nsensors > MAX_SENSORS) {
envd_log(LOG_ERR, ENV_FRU_BAD_ENVSEG, iofru_devname);
free(envseg);
(void) close(fd);
return (-1);
}
sensorp = &(envseg->esd_sensors[0]);
envsegp = (char *)envseg;
for (i = 0; i < envseg->esd_nsensors; i++) {
uint32_t ess_id;
(void) memcpy(&ess_id,
sensorp->ess_id, sizeof (sensorp->ess_id));
if (env_debug) {
envd_log(LOG_INFO, "\n Sensor Id %x offset %x",
ess_id, sensorp->ess_off);
}
if (ess_id >= MAX_SENSORS) {
envd_log(LOG_ERR, ENV_FRU_BAD_ENVSEG, iofru_devname);
free(envseg);
(void) close(fd);
return (-1);
}
(void) memcpy(&sensor_ctl[ess_id], &envsegp[sensorp->ess_off],
sizeof (es_sensor_blk_t));
sensorp++;
}
/*
* Match sensor/ES id and point to correct data based on IDs
*/
for (i = 0; i < N_ENVD_SENSORS; i++) {
id = envd_sensors[i]->id;
envd_sensors[i]->es = &sensor_ctl[id];
}
/*
* Cleanup and return
*/
free(envseg);
(void) close(fd);
return (0);
}
static void
envd_es_default_setup(void)
{
int i, id;
for (i = 0; i < N_ENVD_SENSORS; i++) {
id = envd_sensors[i]->id;
envd_sensors[i]->es = &sensor_default_ctl[id];
}
}
/*
* Open temperature sensor devices and initialize per sensor data structure.
*/
static int
envd_setup_sensors(void)
{
env_sensor_t *sensorp;
int sensorcnt = 0;
int i;
picl_nodehdl_t tnodeh;
for (i = 0; i < N_ENVD_SENSORS; i++) {
if (env_debug)
envd_log(LOG_ERR, "scanning sensor %d\n", i);
sensorp = envd_sensors[i];
/* Initialize sensor's initial state */
sensorp->shutdown_initiated = B_FALSE;
sensorp->warning_tstamp = 0;
sensorp->shutdown_tstamp = 0;
sensorp->error = 0;
/* Make sure cpu0/1 sensors are present */
if (sensorp->id == CPU0_SENSOR_ID) {
if (ptree_get_node_by_path(CPU0_PATH, &tnodeh) !=
PICL_SUCCESS) {
if (env_debug) {
envd_log(LOG_ERR,
"get node by path failed for %s\n",
CPU0_PATH);
}
sensorp->present = B_FALSE;
continue;
}
}
if (sensorp->id == CPU1_SENSOR_ID) {
if (ptree_get_node_by_path(CPU1_PATH, &tnodeh) !=
PICL_SUCCESS) {
if (env_debug) {
envd_log(LOG_ERR,
"get node by path failed for %s\n",
CPU1_PATH);
}
sensorp->present = B_FALSE;
continue;
}
}
sensorp->fd = open(sensorp->devfs_path, O_RDWR);
if (sensorp->fd == -1) {
if (env_debug) {
envd_log(LOG_ERR, ENV_SENSOR_OPEN_FAIL,
sensorp->name, sensorp->devfs_path,
errno, strerror(errno));
}
sensorp->present = B_FALSE;
continue;
}
/*
* Determine if the front panel is attached, we want the
* information if it exists, but should not shut down
* the system if it is removed.
*/
if (sensorp->id == FRONT_PANEL_SENSOR_ID) {
tempr_t temp;
int tries;
for (tries = 0; tries < MAX_SENSOR_RETRIES; tries++) {
if (ioctl(sensorp->fd, PIC_GET_TEMPERATURE,
&temp) == 0) {
break;
}
(void) sleep(1);
}
if (tries == MAX_SENSOR_RETRIES)
sensorp->present = B_FALSE;
}
sensorp->present = B_TRUE;
sensorcnt++;
}
if (sensorcnt == 0)
return (-1);
return (0);
}
/* ARGSUSED */
static void *
pmthr(void *args)
{
pm_state_change_t pmstate;
char physpath[PATH_MAX];
int pre_lpstate;
uint8_t estar_state;
int env_monitor_fd;
pmstate.physpath = physpath;
pmstate.size = sizeof (physpath);
cur_lpstate = 0;
pre_lpstate = 1;
pm_fd = open(PM_DEVICE, O_RDWR);
if (pm_fd == -1) {
envd_log(LOG_ERR, PM_THREAD_EXITING, errno, strerror(errno));
return (NULL);
}
for (;;) {
/*
* Get PM state change events to check if the system
* is in lowest power state and inform PIC which controls
* fan speeds.
*
* To minimize polling, we use the blocking interface
* to get the power state change event here.
*/
if (ioctl(pm_fd, PM_GET_STATE_CHANGE_WAIT, &pmstate) != 0) {
if (errno != EINTR)
break;
continue;
}
do {
if (env_debug) {
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 (pre_lpstate != cur_lpstate) {
pre_lpstate = cur_lpstate;
estar_state = (cur_lpstate & 0x1);
if (env_debug)
envd_log(LOG_ERR,
"setting PIC ESTAR SATE to %x\n",
estar_state);
env_monitor_fd = open(ENV_MONITOR_DEVFS, O_RDWR);
if (env_monitor_fd != -1) {
if (ioctl(env_monitor_fd, PIC_SET_ESTAR_MODE,
&estar_state) < 0) {
if (env_debug)
envd_log(LOG_ERR,
"unable to set ESTAR_MODE in PIC\n");
}
(void) close(env_monitor_fd);
} else {
if (env_debug)
envd_log(LOG_ERR,
"Failed to open %s\n",
ENV_MONITOR_DEVFS);
}
}
}
/*NOTREACHED*/
return (NULL);
}
/*
* This is env thread which monitors the current temperature when
* warning threshold is exceeded. The job is to make sure it does
* not execced/decrease shutdown threshold. If it does it will start
* forced shutdown to avoid reaching hardware poweroff via THERM interrupt.
*/
/*ARGSUSED*/
static void *
system_temp_thr(void *args)
{
char syscmd[BUFSIZ];
char msgbuf[BUFSIZ];
timespec_t to;
int ret, i;
env_sensor_t *sensorp;
pthread_mutex_t env_monitor_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t env_monitor_cv = PTHREAD_COND_INITIALIZER;
time_t ct;
tempr_t temp;
for (;;) {
/*
* Sleep for specified seconds before issuing IOCTL
* again.
*/
(void) pthread_mutex_lock(&env_monitor_mutex);
ret = pthread_cond_reltimedwait_np(&env_monitor_cv,
&env_monitor_mutex, &to);
to.tv_sec = sensor_scan_interval;
to.tv_nsec = 0;
if (ret != ETIMEDOUT) {
(void) pthread_mutex_unlock(&env_monitor_mutex);
continue;
}
(void) pthread_mutex_unlock(&env_monitor_mutex);
for (i = 0; i < N_ENVD_SENSORS; i++) {
sensorp = envd_sensors[i];
if (sensorp->present == B_FALSE)
continue;
if (get_temperature(sensorp, &temp) == -1)
continue;
sensorp->cur_temp = temp;
if (env_debug) {
envd_log(LOG_ERR,
"%s temp = %d",
sensorp->name, sensorp->cur_temp);
}
/*
* If this sensor already triggered system shutdown,
* don't log any more shutdown/warning messages for it.
*/
if (sensorp->shutdown_initiated)
continue;
/*
* Check for the temperature in warning and shutdown
* range and take appropriate action.
*/
if (SENSOR_TEMP_IN_WARNING_RANGE(sensorp->cur_temp,
sensorp)) {
/*
* Check if the temperature has been in
* warning range during last
* sensor_warning_duration interval. If so,
* the temperature is truly in warning range
* and we need to log a warning message, but
* no more than once every
* sensor_warning_interval seconds.
*/
time_t wtstamp = sensorp->warning_tstamp;
ct = (time_t)(gethrtime() / NANOSEC);
if (sensorp->warning_start == 0)
sensorp->warning_start = ct;
if (((ct - sensorp->warning_start) >=
sensor_warning_duration) &&
(wtstamp == 0 || (ct - wtstamp) >=
sensor_warning_interval)) {
envd_log(LOG_CRIT, ENV_WARNING_MSG,
sensorp->name, sensorp->cur_temp,
(int8_t)
sensorp->es->esb_low_warning,
(int8_t)
sensorp->es->esb_high_warning);
sensorp->warning_tstamp = ct;
}
} else if (sensorp->warning_start != 0)
sensorp->warning_start = 0;
if (!shutdown_override &&
SENSOR_TEMP_IN_SHUTDOWN_RANGE(sensorp->cur_temp,
sensorp)) {
ct = (time_t)(gethrtime() / NANOSEC);
if (sensorp->shutdown_tstamp == 0)
sensorp->shutdown_tstamp = ct;
/*
* Shutdown the system if the temperature
* remains in the shutdown range for over
* sensor_shutdown_interval seconds.
*/
if ((ct - sensorp->shutdown_tstamp) >=
sensor_shutdown_interval) {
/*
* Log error
*/
sensorp->shutdown_initiated = B_TRUE;
(void) snprintf(msgbuf, sizeof (msgbuf),
ENV_SHUTDOWN_MSG, sensorp->name,
sensorp->cur_temp,
(int8_t)
sensorp->es->esb_low_shutdown,
(int8_t)
sensorp->es->esb_high_shutdown);
envd_log(LOG_ALERT, msgbuf);
/*
* Shutdown the system (only once)
*/
if (system_shutdown_started ==
B_FALSE) {
(void) snprintf(syscmd,
sizeof (syscmd),
"%s \"%s\"", shutdown_cmd,
msgbuf);
envd_log(LOG_ALERT, syscmd);
system_shutdown_started =
B_TRUE;
(void) system(syscmd);
}
}
} else if (sensorp->shutdown_tstamp != 0)
sensorp->shutdown_tstamp = 0;
}
} /* end of forever loop */
/*NOTREACHED*/
return (NULL);
}
static int
scsi_log_sense(env_disk_t *diskp, uchar_t page_code, void *pagebuf,
uint16_t pagelen, int page_control)
{
struct uscsi_cmd ucmd_buf;
uchar_t cdb_buf[CDB_GROUP1];
struct scsi_extended_sense sense_buf;
int ret_val;
bzero(&cdb_buf, sizeof (cdb_buf));
bzero(&ucmd_buf, sizeof (ucmd_buf));
bzero(&sense_buf, sizeof (sense_buf));
cdb_buf[0] = SCMD_LOG_SENSE_G1;
/*
* For SATA we need to have the current threshold value set.
* For SAS drives we can use the current cumulative value.
* This is set for non-SMART drives, by passing a non-zero
* page_control.
*/
if (page_control)
cdb_buf[2] = (0x01 << 6) | page_code;
else
cdb_buf[2] = page_code;
cdb_buf[7] = (uchar_t)((pagelen & 0xFF00) >> 8);
cdb_buf[8] = (uchar_t)(pagelen & 0x00FF);
ucmd_buf.uscsi_cdb = (char *)cdb_buf;
ucmd_buf.uscsi_cdblen = sizeof (cdb_buf);
ucmd_buf.uscsi_bufaddr = (caddr_t)pagebuf;
ucmd_buf.uscsi_buflen = pagelen;
ucmd_buf.uscsi_rqbuf = (caddr_t)&sense_buf;
ucmd_buf.uscsi_rqlen = sizeof (struct scsi_extended_sense);
ucmd_buf.uscsi_flags = USCSI_RQENABLE | USCSI_READ | USCSI_SILENT;
ucmd_buf.uscsi_timeout = DEFAULT_SCSI_TIMEOUT;
ret_val = ioctl(diskp->fd, USCSICMD, ucmd_buf);
if ((ret_val == 0) && (ucmd_buf.uscsi_status == 0)) {
if (env_debug)
envd_log(LOG_ERR,
"log sense command for page_code 0x%x succeeded\n", page_code);
return (ret_val);
}
if (env_debug)
envd_log(LOG_ERR, "log sense command for %s failed. "
"page_code 0x%x ret_val = 0x%x "
"status = 0x%x errno = 0x%x\n", diskp->name, page_code,
ret_val, ucmd_buf.uscsi_status, errno);
return (1);
}
static int
get_disk_temp(env_disk_t *diskp)
{
int ret;
uchar_t tpage[256];
if (diskp->smart_supported == B_TRUE) {
smart_structure smartpage;
smart_attribute *temp_attrib = NULL;
uint8_t checksum;
uint8_t *index;
int i;
bzero(&smartpage, sizeof (smartpage));
ret = scsi_log_sense(diskp, GET_SMART_INFO,
&smartpage, sizeof (smartpage), 0);
if (ret != 0) {
diskp->current_temp = DISK_INVALID_TEMP;
diskp->ref_temp = DISK_INVALID_TEMP;
return (-1);
}
/*
* verify the checksum of the data. A 2's compliment
* of the result addition of the is stored in the
* last byte. The sum of all the checksum should be
* 0. If the checksum is bad, return an error for
* this iteration.
*/
index = (uint8_t *)&smartpage;
for (i = checksum = 0; i < 512; i++)
checksum += index[i];
if ((checksum != 0) && env_debug) {
envd_log(LOG_ERR,
"SMART checksum error! 0x%x\n", checksum);
/*
* We got bad data back from the drive, fail this
* time around and picl will retry again. If this
* continues to fail picl will give this drive a
* failed status.
*/
diskp->current_temp = DISK_INVALID_TEMP;
diskp->ref_temp = DISK_INVALID_TEMP;
return (-1);
}
/*
* Scan through the various SMART data and look for
* the complete drive temp.
*/
for (i = 0; (i < SMART_FIELDS) &&
(smartpage.attribute[i].id != 0) &&
(temp_attrib == NULL); i++) {
if (smartpage.attribute[i].id == HDA_TEMP) {
temp_attrib = &smartpage.attribute[i];
}
}
/*
* If we dont find any temp SMART attributes, this drive
* does not support this page, disable temp checking
* for this drive.
*/
if (temp_attrib == NULL) {
/*
* If the checksum is valid, the temp. attributes are
* not supported, disable this drive from temp.
* checking.
*/
if (env_debug)
envd_log(LOG_ERR,
"Temp ATTRIBUTE not supported\n");
diskp->smart_supported = B_FALSE;
diskp->tpage_supported = B_FALSE;
diskp->current_temp = DISK_INVALID_TEMP;
diskp->ref_temp = DISK_INVALID_TEMP;
return (-1);
}
if (env_debug) {
envd_log(LOG_ERR, "flags = 0x%x%x,curr = 0x%x,"
"data = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
temp_attrib->flags[0], temp_attrib->flags[1],
temp_attrib->raw_data[0], temp_attrib->raw_data[1],
temp_attrib->raw_data[2], temp_attrib->raw_data[3],
temp_attrib->raw_data[4], temp_attrib->raw_data[5],
temp_attrib->raw_data[6], temp_attrib->raw_data[7]);
}
if (temp_attrib->raw_data[1] != 0xFF) {
diskp->current_temp = temp_attrib->raw_data[2];
diskp->ref_temp = temp_attrib->raw_data[2];
} else {
diskp->ref_temp = DISK_INVALID_TEMP;
diskp->current_temp = DISK_INVALID_TEMP;
return (-1);
}
} else {
ret = scsi_log_sense(diskp, TEMPERATURE_PAGE, tpage,
sizeof (tpage), 1);
if (ret != 0) {
diskp->current_temp = DISK_INVALID_TEMP;
diskp->ref_temp = DISK_INVALID_TEMP;
return (-1);
}
/*
* For the current temperature verify that the parameter
* length is 0x02 and the parameter code is 0x00
* Temperature value of 255(0xFF) is considered INVALID.
*/
if ((tpage[7] == 0x02) && (tpage[4] == 0x00) &&
(tpage[5] == 0x00)) {
if (tpage[9] == 0xFF) {
diskp->current_temp = DISK_INVALID_TEMP;
return (-1);
} else {
diskp->current_temp = tpage[9];
}
}
/*
* For the reference temperature verify that the parameter
* length is 0x02 and the parameter code is 0x01
* Temperature value of 255(0xFF) is considered INVALID.
*/
if ((tpage[13] == 0x02) && (tpage[10] == 0x00) &&
(tpage[11] == 0x01)) {
if (tpage[15] == 0xFF) {
diskp->ref_temp = DISK_INVALID_TEMP;
} else {
diskp->ref_temp = tpage[15];
}
}
}
return (0);
}
/* ARGSUSED */
static void *
disk_temp_thr(void *args)
{
char syscmd[BUFSIZ];
char msgbuf[BUFSIZ];
timespec_t to;
int ret, i;
env_disk_t *diskp;
pthread_mutex_t env_monitor_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t env_monitor_cv = PTHREAD_COND_INITIALIZER;
pm_state_change_t pmstate;
int idle_time;
int disk_pm_fd;
time_t ct;
if ((disk_pm_fd = open(PM_DEVICE, O_RDWR)) == -1) {
envd_log(LOG_ERR, DISK_TEMP_THREAD_EXITING,
errno, strerror(errno));
return (NULL);
}
for (;;) {
/*
* Sleep for specified seconds before issuing IOCTL
* again.
*/
(void) pthread_mutex_lock(&env_monitor_mutex);
ret = pthread_cond_reltimedwait_np(&env_monitor_cv,
&env_monitor_mutex, &to);
to.tv_sec = disk_scan_interval;
to.tv_nsec = 0;
if (ret != ETIMEDOUT) {
(void) pthread_mutex_unlock(
&env_monitor_mutex);
continue;
}
(void) pthread_mutex_unlock(&env_monitor_mutex);
for (i = 0; (diskp = envd_disks[i]) != NULL; i++) {
if (diskp->present == B_FALSE)
continue;
if (diskp->tpage_supported == B_FALSE)
continue;
/*
* If the disk temperature is above the warning threshold
* continue monitoring until the temperature drops below
* warning threshold.
* if the temperature is in the NORMAL range monitor only
* when the disk is BUSY.
* We do not want to read the disk temperature if the disk is
* is idling. The reason for this is disk will never get into
* lowest power mode if we scan the disk temperature
* peridoically. To avoid this situation we first determine
* the idle_time of the disk. If the disk has been IDLE since
* we scanned the temperature last time we will not read the
* temperature.
*/
if (!DISK_TEMP_IN_WARNING_RANGE(diskp->current_temp, diskp)) {
pmstate.physpath = diskp->physpath;
pmstate.size = strlen(diskp->physpath);
pmstate.component = 0;
if ((idle_time =
ioctl(disk_pm_fd, PM_GET_TIME_IDLE,
&pmstate)) == -1) {
if (errno != EINTR) {
if (env_debug)
envd_log(LOG_ERR,
"ioctl PM_GET_TIME_IDLE failed for DISK0. errno=0x%x\n",
errno);
continue;
}
continue;
}
if (idle_time >= (disk_scan_interval/2)) {
if (env_debug) {
envd_log(LOG_ERR, "%s idle time = %d\n",
diskp->name, idle_time);
}
continue;
}
}
ret = get_disk_temp(diskp);
if (ret != 0)
continue;
if (env_debug) {
envd_log(LOG_ERR, "%s temp = %d ref. temp = %d\n",
diskp->name, diskp->current_temp, diskp->ref_temp);
}
/*
* If this disk already triggered system shutdown, don't
* log any more shutdown/warning messages for it.
*/
if (diskp->shutdown_initiated)
continue;
/*
* Check for the temperature in warning and shutdown range
* and take appropriate action.
*/
if (DISK_TEMP_IN_WARNING_RANGE(diskp->current_temp, diskp)) {
/*
* Check if the temperature has been in warning
* range during last disk_warning_duration interval.
* If so, the temperature is truly in warning
* range and we need to log a warning message,
* but no more than once every disk_warning_interval
* seconds.
*/
time_t wtstamp = diskp->warning_tstamp;
ct = (time_t)(gethrtime() / NANOSEC);
if (diskp->warning_start == 0)
diskp->warning_start = ct;
if (((ct - diskp->warning_start) >=
disk_warning_duration) && (wtstamp == 0 ||
(ct - wtstamp) >= disk_warning_interval)) {
envd_log(LOG_CRIT, ENV_WARNING_MSG,
diskp->name, diskp->current_temp,
diskp->low_warning,
diskp->high_warning);
diskp->warning_tstamp = ct;
}
} else if (diskp->warning_start != 0)
diskp->warning_start = 0;
if (!shutdown_override &&
DISK_TEMP_IN_SHUTDOWN_RANGE(diskp->current_temp, diskp)) {
ct = (time_t)(gethrtime() / NANOSEC);
if (diskp->shutdown_tstamp == 0)
diskp->shutdown_tstamp = ct;
/*
* Shutdown the system if the temperature remains
* in the shutdown range for over disk_shutdown_interval
* seconds.
*/
if ((ct - diskp->shutdown_tstamp) >=
disk_shutdown_interval) {
/* log error */
diskp->shutdown_initiated = B_TRUE;
(void) snprintf(msgbuf, sizeof (msgbuf),
ENV_SHUTDOWN_MSG, diskp->name,
diskp->current_temp, diskp->low_shutdown,
diskp->high_shutdown);
envd_log(LOG_ALERT, msgbuf);
/* shutdown the system (only once) */
if (system_shutdown_started == B_FALSE) {
(void) snprintf(syscmd, sizeof (syscmd),
"%s \"%s\"", shutdown_cmd, msgbuf);
envd_log(LOG_ALERT, syscmd);
system_shutdown_started = B_TRUE;
(void) system(syscmd);
}
}
} else if (diskp->shutdown_tstamp != 0)
diskp->shutdown_tstamp = 0;
}
} /* end of forever loop */
}
static void *
fan_thr(void *args)
{
char msgbuf[BUFSIZ];
timespec_t to;
int ret, i;
pthread_mutex_t env_monitor_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t env_monitor_cv = PTHREAD_COND_INITIALIZER;
env_fan_t *fanp;
#ifdef __lint
args = args;
#endif
for (;;) {
/*
* Sleep for specified seconds before issuing IOCTL
* again.
*/
(void) pthread_mutex_lock(&env_monitor_mutex);
ret = pthread_cond_reltimedwait_np(&env_monitor_cv,
&env_monitor_mutex, &to);
to.tv_sec = fan_scan_interval;
to.tv_nsec = 0;
if (ret != ETIMEDOUT) {
(void) pthread_mutex_unlock(&env_monitor_mutex);
continue;
}
(void) pthread_mutex_unlock(&env_monitor_mutex);
for (i = 0; (fanp = envd_fans[i]) != NULL; i++) {
if (fanp->present == B_FALSE)
continue;
if (has_fan_failed(fanp) == B_TRUE) {
if (fanp->last_status == FAN_FAILED)
continue;
fanp->last_status = FAN_FAILED;
(void) snprintf(msgbuf, sizeof (msgbuf),
ENV_FAN_FAILURE_WARNING_MSG, fanp->name,
fan_rpm_string, fan_status_string);
envd_log(LOG_ALERT, msgbuf);
} else {
if (fanp->last_status == FAN_OK)
continue;
fanp->last_status = FAN_OK;
(void) snprintf(msgbuf, sizeof (msgbuf),
ENV_FAN_OK_MSG, fanp->name);
envd_log(LOG_ALERT, msgbuf);
}
}
if (has_psufan_failed() == B_TRUE) {
if (psufan_last_status == FAN_FAILED)
continue;
psufan_last_status = FAN_FAILED;
(void) snprintf(msgbuf, sizeof (msgbuf),
ENV_FAN_FAILURE_WARNING_MSG, SENSOR_PSU,
fan_rpm_string, fan_status_string);
envd_log(LOG_ALERT, msgbuf);
} else {
if (psufan_last_status == FAN_OK)
continue;
psufan_last_status = FAN_OK;
(void) snprintf(msgbuf, sizeof (msgbuf),
ENV_FAN_OK_MSG, SENSOR_PSU);
envd_log(LOG_ALERT, msgbuf);
}
}
/*NOTREACHED*/
return (NULL);
}
/*
* Setup envrionmental monitor state and start threads to monitor
* temperature, fan, disk and power management state.
* Returns -1 on error, 0 if successful.
*/
static int
envd_setup(void)
{
if (getenv("SUNW_piclenvd_debug") != NULL)
env_debug = 1;
if (pthread_attr_init(&thr_attr) != 0 ||
pthread_attr_setscope(&thr_attr, PTHREAD_SCOPE_SYSTEM) != 0) {
return (-1);
}
/*
* If ES segment is not present or has inconsistent information, we
* use default values for sensor limits. For the sake of simplicity,
* we still store these limits internally in the 'es' member in the
* structure.
*/
if (envd_es_setup() < 0) {
envd_log(LOG_WARNING, ENV_DEFAULT_LIMITS);
envd_es_default_setup();
}
if (envd_setup_sensors() < 0) {
if (env_debug)
envd_log(LOG_ERR, "Failed to setup sensors\n");
system_temp_monitor = 0;
}
if (envd_setup_fans() < 0) {
if (env_debug)
envd_log(LOG_ERR, "Failed to setup fans\n");
fan_monitor = 0;
pm_monitor = 0;
}
/*
* Disable disk temperature monitoring until we have
* LSI fw support to read SATA disk temperature
*/
if (disk_temp_monitor) {
if (envd_setup_disks() < 0) {
if (env_debug)
envd_log(LOG_ERR, "Failed to setup disks\n");
disk_temp_monitor = 0;
}
}
/*
* Create a thread to monitor system temperatures
*/
if ((system_temp_monitor) && (system_temp_thr_created == B_FALSE)) {
if (pthread_create(&system_temp_thr_id, &thr_attr,
system_temp_thr, NULL) != 0) {
envd_log(LOG_ERR, ENVTHR_THREAD_CREATE_FAILED);
} else {
system_temp_thr_created = B_TRUE;
if (env_debug)
envd_log(LOG_ERR,
"Created thread to monitor system temperatures\n");
}
}
/*
* Create a thread to monitor fans
*/
if ((fan_monitor) && (fan_thr_created == B_FALSE)) {
if (pthread_create(&fan_thr_id, &thr_attr, fan_thr, NULL) != 0)
envd_log(LOG_ERR, ENVTHR_THREAD_CREATE_FAILED);
else {
fan_thr_created = B_TRUE;
if (env_debug) {
envd_log(LOG_ERR,
"Created thread to monitor system fans\n");
}
}
}
/*
* Create a thread to monitor PM state
*/
if ((pm_monitor) && (pmthr_created == B_FALSE)) {
if (pthread_create(&pmthr_tid, &thr_attr, pmthr, NULL) != 0)
envd_log(LOG_CRIT, PM_THREAD_CREATE_FAILED);
else {
pmthr_created = B_TRUE;
if (env_debug)
envd_log(LOG_ERR,
"Created thread to monitor system power state\n");
}
}
/*
* Create a thread to monitor disk temperature
*/
if ((disk_temp_monitor) && (disk_temp_thr_created == B_FALSE)) {
if (pthread_create(&disk_temp_thr_id, &thr_attr,
disk_temp_thr, NULL) != 0) {
envd_log(LOG_ERR, ENVTHR_THREAD_CREATE_FAILED);
} else {
disk_temp_thr_created = B_TRUE;
if (env_debug)
envd_log(LOG_ERR,
"Created thread for disk temperatures\n");
}
}
return (0);
}
static void
piclenvd_register(void)
{
picld_plugin_register(&my_reg_info);
}
static void
piclenvd_init(void)
{
(void) env_picl_setup_tuneables();
/*
* Do not allow disk temperature monitoring to be enabled
* via tuneables. Disk temperature monitoring is disabled
* until we have LSI fw support to read the temperature of
* SATA disks
*/
disk_temp_monitor = 0;
/*
* Setup the environmental data structures
*/
if (envd_setup() != 0) {
envd_log(LOG_CRIT, ENVD_PLUGIN_INIT_FAILED);
return;
}
/*
* Now setup/populate PICL tree
*/
env_picl_setup();
}
static void
piclenvd_fini(void)
{
/*
* Invoke env_picl_destroy() to remove any PICL nodes/properties
* (including volatile properties) we created. Once this call
* returns, there can't be any more calls from the PICL framework
* to get current temperature or fan speed.
*/
env_picl_destroy();
envd_close_sensors();
envd_close_fans();
}
/*VARARGS2*/
void
envd_log(int pri, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsyslog(pri, fmt, ap);
va_end(ap);
}
/*
* Tunables support functions
*/
static env_tuneable_t *
tuneable_lookup(picl_prophdl_t proph)
{
int i;
env_tuneable_t *tuneablep = NULL;
for (i = 0; i < ntuneables; i++) {
tuneablep = &tuneables[i];
if (tuneablep->proph == proph)
return (tuneablep);
}
return (NULL);
}
static int
get_string_val(ptree_rarg_t *parg, void *buf)
{
picl_prophdl_t proph;
env_tuneable_t *tuneablep;
proph = parg->proph;
tuneablep = tuneable_lookup(proph);
if (tuneablep == NULL)
return (PICL_FAILURE);
(void) memcpy(buf, tuneablep->value, tuneablep->nbytes);
return (PICL_SUCCESS);
}
static int
set_string_val(ptree_warg_t *parg, const void *buf)
{
picl_prophdl_t proph;
env_tuneable_t *tuneablep;
if (parg->cred.dc_euid != 0)
return (PICL_PERMDENIED);
proph = parg->proph;
tuneablep = tuneable_lookup(proph);
if (tuneablep == NULL)
return (PICL_FAILURE);
(void) memcpy(tuneables->value, buf, tuneables->nbytes);
return (PICL_SUCCESS);
}
static int
get_int_val(ptree_rarg_t *parg, void *buf)
{
picl_prophdl_t proph;
env_tuneable_t *tuneablep;
proph = parg->proph;
tuneablep = tuneable_lookup(proph);
if (tuneablep == NULL)
return (PICL_FAILURE);
(void) memcpy(buf, tuneablep->value, tuneablep->nbytes);
return (PICL_SUCCESS);
}
static int
set_int_val(ptree_warg_t *parg, const void *buf)
{
picl_prophdl_t proph;
env_tuneable_t *tuneablep;
if (parg->cred.dc_euid != 0)
return (PICL_PERMDENIED);
proph = parg->proph;
tuneablep = tuneable_lookup(proph);
if (tuneablep == NULL)
return (PICL_FAILURE);
(void) memcpy(tuneablep->value, buf, tuneablep->nbytes);
return (PICL_SUCCESS);
}
boolean_t
has_fan_failed(env_fan_t *fanp)
{
fanspeed_t fan_speed;
uchar_t status;
uint8_t tach;
int real_tach;
int ret, ntries;
if (fanp->fd == -1)
return (B_TRUE);
/*
* Read RF_FAN_STATUS bit of the fan fault register, retry if
* the PIC is busy, with a 1 second delay to allow it to update.
*/
for (ntries = 0; ntries < MAX_RETRIES_FOR_FAN_FAULT; ntries++) {
ret = ioctl(fanp->fd, PIC_GET_FAN_STATUS, &status);
if ((ret == 0) && ((status & 0x1) == 0))
break;
(void) sleep(1);
}
if (ntries > 0) {
if (env_debug) {
envd_log(LOG_ERR,
"%d retries attempted in reading fan status.\n",
ntries);
}
}
if (ntries == MAX_RETRIES_FOR_FAN_FAULT) {
(void) strncpy(fan_status_string, NOT_AVAILABLE,
sizeof (fan_status_string));
(void) strncpy(fan_rpm_string, NOT_AVAILABLE,
sizeof (fan_rpm_string));
return (B_TRUE);
}
if (env_debug)
envd_log(LOG_ERR, "fan status = 0x%x\n", status);
/*
* ST_FFAULT bit isn't implemented yet and we're reading only
* individual fan status
*/
if (status & 0x1) {
(void) snprintf(fan_status_string, sizeof (fan_status_string),
"0x%x", status);
if (ioctl(fanp->fd, PIC_GET_FAN_SPEED, &tach) != 0) {
(void) strncpy(fan_rpm_string, NOT_AVAILABLE,
sizeof (fan_rpm_string));
} else {
real_tach = tach << 8;
fan_speed = TACH_TO_RPM(real_tach);
(void) snprintf(fan_rpm_string, sizeof (fan_rpm_string),
"%d", fan_speed);
}
return (B_TRUE);
}
return (B_FALSE);
}
boolean_t
has_psufan_failed(void)
{
uchar_t status;
int ret, ntries;
if (envd_sensor_psu.fd == -1)
return (B_FALSE);
/*
* For psu, only fan fault is visible, no fan speed
*/
(void) strncpy(fan_rpm_string, NOT_AVAILABLE, sizeof (fan_rpm_string));
/*
* Read RF_FAN_STATUS bit of the fan fault register, retry if
* the PIC is busy, with a 1 second delay to allow it to update.
*/
for (ntries = 0; ntries < MAX_RETRIES_FOR_FAN_FAULT; ntries++) {
ret = ioctl(envd_sensor_psu.fd, PIC_GET_FAN_STATUS, &status);
if ((ret == 0) && ((status & 0x1) == 0))
break;
(void) sleep(1);
}
if (ntries > 0) {
if (env_debug) {
envd_log(LOG_ERR,
"%d retries attempted in reading fan status.\n",
ntries);
}
}
if (ntries == MAX_RETRIES_FOR_FAN_FAULT) {
(void) strncpy(fan_status_string, NOT_AVAILABLE,
sizeof (fan_status_string));
return (B_TRUE);
}
if (env_debug)
envd_log(LOG_ERR, "fan status = 0x%x\n", status);
if (status & 0x1) {
(void) snprintf(fan_status_string, sizeof (fan_status_string),
"0x%x", status);
return (B_TRUE);
}
return (B_FALSE);
}
static int
scsi_mode_select(env_disk_t *diskp, uchar_t page_code, uchar_t *pagebuf,
uint16_t pagelen)
{
struct uscsi_cmd ucmd_buf;
uchar_t cdb_buf[CDB_GROUP1];
struct scsi_extended_sense sense_buf;
int ret_val;
bzero(&cdb_buf, sizeof (cdb_buf));
bzero(&ucmd_buf, sizeof (ucmd_buf));
bzero(&sense_buf, sizeof (sense_buf));
cdb_buf[0] = SCMD_MODE_SELECT_G1;
cdb_buf[1] = 1<<PAGE_FMT;
cdb_buf[7] = (uchar_t)((pagelen & 0xFF00) >> 8);
cdb_buf[8] = (uchar_t)(pagelen & 0x00FF);
ucmd_buf.uscsi_cdb = (char *)cdb_buf;
ucmd_buf.uscsi_cdblen = sizeof (cdb_buf);
ucmd_buf.uscsi_bufaddr = (caddr_t)pagebuf;
ucmd_buf.uscsi_buflen = pagelen;
ucmd_buf.uscsi_rqbuf = (caddr_t)&sense_buf;
ucmd_buf.uscsi_rqlen = sizeof (struct scsi_extended_sense);
ucmd_buf.uscsi_flags = USCSI_RQENABLE | USCSI_WRITE | USCSI_SILENT;
ucmd_buf.uscsi_timeout = DEFAULT_SCSI_TIMEOUT;
ret_val = ioctl(diskp->fd, USCSICMD, ucmd_buf);
if (ret_val == 0 && ucmd_buf.uscsi_status == 0) {
return (ret_val);
}
if (env_debug)
envd_log(LOG_ERR, "mode select command for %s failed. "
"page_code 0x%x ret_val = 0x%x "
"status = 0x%x errno = 0x%x\n", diskp->name, page_code,
ret_val, ucmd_buf.uscsi_status, errno);
return (1);
}