/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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
* 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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* This file contains the environmental PICL plug-in module.
*/
/*
* Excalibur system contains up to two CPU and two PCI MAX1617 temperature
* devices, each consisting of two sensors: die and ambient. Each sensor is
* represented as a different minor device and the current temperature is read
* via an I2C_GET_TEMPERATURE ioctl call to the max1617 driver. Additionally,
* the MAX1617 device supports both a low and high temperature limit, which
* can trigger an alert condition, causing power supply to turn off.
*
* The environmental monitor defines the following thresholds per sensor:
*
* high_power_off high hard shutdown
* high_shutdown high soft shutdown limit
* high_warning high warning limit
* low_warning low warning limit
* low_shutdown low soft shutdown limit
* low_power_off low hard shutdown limit
*
* Above mentioned threshold values can be changed via "piclenvd.conf"
* configuration file.
*
* Environmental monitoring is done by the "envthr" thread. It periodically
* monitors both CPU die and CPU ambient temperatures and takes appropriate
* action depending upon the current temperature and threshold values for
* that sensor. If the temperature reaches the high_shutdown limit or the
* low_shutdown limit, and remains there for over shutdown_interval seconds,
* it forces a graceful system shutdown via tuneable shutdown_cmd string
* variable. Otherwise, if the temperature reaches the high_warning limit
* or the low_warning limit, it logs and prints a message on the console.
* This message will be printed at most at "warning_interval" seconds
* interval, which is also a tuneable variable.
*
* Excalibur system contains three fans: cpu, system and power supply. The
* cpu and system fans are under software control and their speed can be
* set to a value in the range 0 through 63. However, the software has no
* control over the power supply fan's speed (it's automatically controlled
* by the hardware), but it can turn it ON or OFF. When in EStar mode (i.e.
* the lowest power state), the environmental monitor turns off the power
* supply fan.
*
* Each fan is represented as a different minor device and the fan speed
* can be controlled by writing to the TDA8444 device driver. Note that
* these devices are read only and the driver caches the last speed set
* for each fan, thus allowing an interface to read the current fan speed
* also.
*
* The policy to control fan speed depends upon the sensor. For CPU die
* sensor, different policy is used depending upon whether the temperature
* is rising, falling or steady state. In case of CPU ambient sensor, only
* one policy (speed proportional to the current temperature) is used.
*
* The power state monitoring is done by the "pmthr" thread. It uses the
* PM_GET_STATE_CHANGE and PM_GET_STATE_CHANGE_WAIT ioctl commands to pick
* up any power state change events. It processes all queued power state
* change events and determines the curret lowest power state and saves it
* in cur_lpstate variable.
*
* Once the "envthr" and "pmthr" threads have been started, they are never
* killed. This is desirable so that we can do environmental monitoring
* to protect initialization of global state during reinit process against
* the "envthr" and "pmthr" trying to reference that state.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/sysmacros.h>
#include <limits.h>
#include <string.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/systeminfo.h>
#include "envd.h"
/*
* PICL plguin
*/
static void piclenvd_register(void);
static void piclenvd_init(void);
static void piclenvd_fini(void);
extern void env_picl_setup(void);
extern void env_picl_destroy(void);
#pragma init(piclenvd_register)
"SUNW_piclenvd",
};
/*
*/
0, 0, 0, 0
};
POLICY_LINEAR, 2,
0, 0, 0, 0
};
/*
* Dummy sensor threshold data structure for processing threshold tuneables
*/
/*
* Temperature related constants for fan speed adjustment
*/
/*
* tuneable variables
*/
int env_debug;
static int sensor_poll_interval;
static int warning_interval;
static int warning_duration;
static int shutdown_interval;
static int fan_slow_adjustment;
static int fan_incr_limit;
static int fan_decr_limit;
static int disable_piclenvd;
static int disable_warning;
static int disable_power_off;
static int disable_shutdown;
/*
* Temperature sensors
*/
};
/*
* Fan devices
*/
};
};
};
};
/*
* Linked list of devices advertising lpm-ranges
*/
/*
* Excalibur lpm to system-fan speed
* lpm values must be monotonically increasing (avoid divide-by-zero)
*/
/* {lpm, fspeed} */
{18, 12},
{25, 20},
{33, 26},
{44, 32},
{51, 39},
{63, 52},
{64, 63}
};
sizeof (excal_lpm_system_fan_tbl)/ sizeof (point_t),
};
/*
* Sensor to fan map
*/
typedef struct {
char *sensor_name;
char *fan_name;
};
/*
* Sensor to PM device map
*/
struct sensor_pmdev {
int sensor_id;
char *sensor_name;
char *pmdev_name;
char *speed_comp_name;
int speed_comp;
int full_power;
int cur_power;
};
};
/*
* Environmental thread variables
*/
/*
* Power management thread (pmthr) variables
*/
/*
* Miscellaneous variables and declarations
*/
static int fru_devfsadm_invoked = 0;
static int devfsadm_invoked = 0;
/*
*/
typedef struct {
int (*func)(char *, char *, void *, int, char *, int);
/* tuneable processing function */
static void process_env_conf_file(void);
{"low_power_off", process_threshold_tuneable,
&dummy_thresh.low_power_off, 0},
{"low_shutdown", process_threshold_tuneable,
&dummy_thresh.low_shutdown, 0},
{"low_warning", process_threshold_tuneable,
&dummy_thresh.low_warning, 0},
{"high_power_off", process_threshold_tuneable,
&dummy_thresh.high_power_off, 0},
{"high_shutdown", process_threshold_tuneable,
&dummy_thresh.high_shutdown, 0},
{"high_warning", process_threshold_tuneable,
&dummy_thresh.high_warning, 0},
sizeof (envd_cpu_fan.forced_speed)},
{"force_system_fan", process_int_tuneable,
sizeof (envd_system_fan.forced_speed)},
{"cpu_amb_low_power_off", process_threshold_tuneable,
{"cpu_amb_low_shutdown", process_threshold_tuneable,
{"cpu_amb_low_warning", process_threshold_tuneable,
{"cpu_amb_low_nominal", process_threshold_tuneable,
{"cpu_amb_high_power_off", process_threshold_tuneable,
{"cpu_amb_high_shutdown", process_threshold_tuneable,
{"cpu_amb_high_warning", process_threshold_tuneable,
{"cpu_amb_high_nominal", process_threshold_tuneable,
{"cpu_die_low_power_off", process_threshold_tuneable,
{"cpu_die_low_shutdown", process_threshold_tuneable,
{"cpu_die_low_warning", process_threshold_tuneable,
{"cpu_die_normal_target", process_threshold_tuneable,
{"cpu_die_high_power_off", process_threshold_tuneable,
{"cpu_die_high_shutdown", process_threshold_tuneable,
{"cpu_die_high_warning", process_threshold_tuneable,
{"cpu_die_other_target", process_threshold_tuneable,
sizeof (sensor_poll_interval)},
sizeof (warning_interval)},
sizeof (warning_duration)},
sizeof (disable_piclenvd)},
sizeof (disable_power_off)},
sizeof (disable_warning)},
sizeof (disable_shutdown)},
sizeof (shutdown_interval)},
sizeof (shutdown_cmd)},
sizeof (devfsadm_cmd)},
sizeof (fru_devfsadm_cmd)},
sizeof (fan_slow_adjustment)},
sizeof (fan_incr_limit)},
sizeof (fan_decr_limit)},
};
static void
{
return;
}
static table_t *
{
if (npoints == 0)
return (NULL);
return (NULL);
return (NULL);
}
return (tblp);
}
/*
* Temp-LPM Table format:
* temp, lpm, temp, lpm, ...
*/
static table_t *
{
int nentries;
int i;
return (NULL);
/*
* Table should have at least 2 points
* and all points should have x and y values
*/
if (env_debug)
return (NULL);
}
/* number of entries in the temp-lpm table */
return (tblp);
/* copy the tuples */
for (i = 1; i < nentries; ++i) {
if (env_debug)
return (NULL);
}
}
return (tblp);
}
/*
* function: calculates y for a given x based on a table of points
* for monotonically increasing x values.
* 'tbl' specifies the table to use, 'val' specifies the 'x', returns 'y'
*/
static int
{
int i;
int entries;
float newval;
return (xymap[0].y);
return (xymap[i].y);
break;
}
/*
* Use linear interpolation
*/
}
static int
{
int lpm;
int speed;
int maxspeed;
return (0);
maxspeed = 0;
continue;
if (env_debug)
if (env_debug)
}
return (maxspeed);
}
/*
* Callback function used by ptree_walk_tree_by_class
*/
static int
{
int err;
void *bufp;
if (err != PICL_SUCCESS)
return (PICL_WALK_CONTINUE);
if ((err != PICL_SUCCESS) ||
return (PICL_WALK_CONTINUE);
if (err != PICL_SUCCESS)
return (PICL_WALK_CONTINUE);
if (temp_lpm_tbl == NULL) {
return (PICL_WALK_CONTINUE);
}
return (PICL_WALK_TERMINATE);
}
/* add newdev to the list */
return (PICL_WALK_CONTINUE);
}
/*
* Find all devices advertising "lpm-ranges" property, parse and store
* the lpm tables for each device
*/
static int
{
int err;
if (err != PICL_SUCCESS)
return (err);
if (err == PICL_SUCCESS)
return (err);
}
/*
* Remove all lpm_devices and their tables.
*/
static void
delete_lpm_devices(void)
{
(void) pthread_rwlock_wrlock(&envd_rwlock);
if (lpm_devices == NULL) {
(void) pthread_rwlock_unlock(&envd_rwlock);
return;
}
devp = lpm_devices;
}
lpm_devices = NULL;
(void) pthread_rwlock_unlock(&envd_rwlock);
}
/*
* Translate observed (measured) temperature into expected (correct)
* temperature
*/
static int
{
float ftemp;
/* no map or can't map it */
} else {
/*
* Any point beyond the range specified by the map is
* extrapolated using either the first two or the last
* two entries in the map.
*/
break;
/*
* Interpolate/extrapolate the temperature using linear
* equation with map[i-1] and map[i] being the two ends
* of the line segment.
*/
if (denominator == 0) {
/*
* Infinite slope. Since the temperature reading
* resolution is 1C, force denominator to 1 to
* avoid divide by zero.
*/
denominator = 1;
}
}
return (new_temp);
}
/*
* Translate expected (correct) temperature into observed (measured)
* temperature
*/
static int
{
float ftemp;
/* no map or can't map it */
else {
/*
* Any point beyond the range specified by the map is
* extrapolated using either the first two or the last
* two entries in the map.
*/
break;
/*
* Interpolate/extrapolate the temperature using linear
* equation with map[i-1] and map[i] being the two ends
* of the line segment.
*/
if (denominator == 0) {
/*
* Infinite slope. Since the temperature reading
* resolution is 1C, force denominator to 1 to
* avoid divide by zero.
*/
denominator = 1;
}
}
if (threshp) {
}
return (new_temp);
}
/*
* Check if the specified FRU is present.
* Returns 1 if present; 0 otherwise.
*/
static int
{
int fru_present = 0;
/*
* Construct FRU device path by stripping minor
* node name from the path and use di_init() to
* see if the node exists.
*/
if (p != NULL)
*p = '\0';
fru_present = 1;
}
return (fru_present);
}
/*
* Get environmental segment from the specified FRU SEEPROM
*/
static int
{
return (EINVAL);
}
/*
* Verify we have the correct section and contents are valid
* For now, we don't verify the CRC.
*/
if (env_debug)
"Invalid section header tag:%x version:%x\n",
return (EINVAL);
}
/*
* Locate our environmental segment
*/
for (i = 0; i < segcnt; i++) {
return (errno);
}
if (env_debug > 1)
"Seg name: %x desc:%x off:%x len:%x\n",
break;
}
if (i >= segcnt) {
return (ENOENT);
}
/*
* Allocate memory to hold the environmental segment data.
*/
return (ENOMEM);
}
return (EIO);
}
*envseglenp = envseglen;
if (env_debug > 1) {
for (i = 0; i < envseglen; i++) {
(i & ~0xf), msgbuf);
}
}
return (0);
}
/*
* Get all environmental segments
*/
static fruenvseg_t *
get_fru_envsegs(void)
{
void *envsegbufp;
fruenvsegs = NULL;
continue;
break;
continue;
continue;
/* add this FRU to our list */
fruenvsegs = frup;
/*
* Now get the environmental segment from this FRU
*/
errno = 0;
if (env_debug > 1)
"fru SEEPROM: %s fd: %d errno:%d\n",
if (fru_devfsadm_invoked ||
fru_devfsadm_cmd[0] == '\0') {
continue;
}
/*
* FRU is present but no path exists as
* someone rebooted the system without
* "-r" option. Let's invoke "devfsadm"
* once to create seeprom nodes and try
* again so that we can monitor all
* accessible sensors properly and prevent
* any CPU overheating.
*/
if (env_debug)
"Invoking '%s' to create FRU nodes\n",
fru_devfsadm_invoked = 1;
(void) system(fru_devfsadm_cmd);
goto retry;
}
/*
* Read environmental segment from this FRU SEEPROM
*/
/*
* Validate envseg version number and header length
*/
hdrlen = sizeof (envseg_layout_t) -
sizeof (envseg_sensor_t) +
/*
* version mismatch or header not big enough
*/
if (envsegbufp != NULL)
(void) free(envsegbufp);
} else {
}
}
}
return (fruenvsegs);
}
/*
* Process environmental segment for all FRUs.
*/
static void
{
/*
* process it. Note that we read each SEEPROM once as it's
* a slow device.
*/
continue;
/*
* Locate our FRU environmental segment
*/
break;
continue;
/*
* Locate our sensor data record entry
*/
for (i = 0; i < sensorcnt; i++) {
if (env_debug > 1)
i, id);
break;
}
if (i >= sensorcnt)
continue;
/*
*/
offset);
mapentries * sizeof (envseg_map_t);
if (env_debug > 1)
"off:%x #maps:%x expected length:%x\n",
mapentries, length);
/* corrupted sensor record */
continue;
}
if (env_debug > 1) {
/* print threshold values */
"Thresholds: HPwrOff %d HShutDn %d HWarn %d\n",
"Thresholds: LWarn %d LShutDn %d LPwrOff %d\n",
/* print policy data */
" Policy type: %d #%d data: %x %x %x %x %x %x\n",
/* print map table */
for (i = 0; i < mapentries; i++) {
}
}
/*
* Copy threshold values
*/
/*
* Copy policy data
*/
for (i = 0; i < MAX_POLICY_ENTRIES; i++)
threshp->policy_data[i] =
/*
* Copy temperature mapping info (discard duplicate entries)
*/
if (sensorp->obs2exp_map) {
sensorp->obs2exp_cnt = 0;
}
if (mapentries > 0) {
int cnt;
sizeof (tempr_map_t));
continue;
}
for (i = 0, cnt = 0; i < mapentries; i++) {
/* ignore if duplicate entry */
if (cnt > 0 &&
continue;
}
cnt++;
}
}
"Measured --> Correct temperature table "
for (i = -128; i < 128; i++) {
xlate_obs2exp(sensorp, i));
if ((i &0x7) == 0x7)
}
if ((i & 0x7) != 0)
"Correct --> Measured temperature table "
for (i = -128; i < 128; i++) {
xlate_exp2obs(sensorp, i));
if ((i &0x7) == 0x7)
}
if ((i & 0x7) != 0)
}
}
/*
* Deallocate environmental segment list
*/
while (fruenvsegs) {
frup = fruenvsegs;
}
}
/*
* Lookup fan and return a pointer to env_fan_t data structure.
*/
{
int i;
return (fanp);
}
return (NULL);
}
/*
* Lookup sensor and return a pointer to env_sensor_t data structure.
*/
{
return (sensorp);
}
return (NULL);
}
/*
* Get current temperature
* Returns -1 on error, 0 if successful
*/
int
{
int retval = 0;
int expected_temp;
if (fd == -1)
retval = -1;
retval = -1;
}
if (env_debug > 1)
"sensor: %-13s temp:%d CORRECED to %d\n",
}
return (retval);
}
/*
* Get current fan speed
* Returns -1 on error, 0 if successful
*/
int
{
int fan_fd;
int retval = 0;
sizeof (fanspeed_t))
retval = -1;
return (retval);
}
/*
* Set fan speed
* Returns -1 on error, 0 if successful
*/
static int
{
int fan_fd;
int retval = 0;
sizeof (fanspeed_t))
retval = -1;
return (retval);
}
/*
* close all fan devices
*/
static void
envd_close_fans(void)
{
int i;
}
}
}
/*
* Close sensor devices
*/
static void
envd_close_sensors(void)
{
}
}
}
/*
* Open PM device
*/
static void
envd_open_pm(void)
{
if (pm_fd != -1)
}
/*
* Close PM device
*/
static void
envd_close_pm(void)
{
if (pm_fd != -1) {
pm_fd = -1;
}
}
/*
* Open fan devices and initialize per fan data structure.
* Returns #fans found.
*/
static int
envd_setup_fans(void)
{
int i, fd;
int fancnt = 0;
char *fan_name;
int sensor_cnt;
fanp->sensor_cnt = 0;
fanp->prev_speed = 0;
if (fd == -1) {
continue;
}
}
fancnt++;
/*
* Set initial speed and update cur_speed/prev_speed
*/
if (fanp->forced_speed >= 0) {
if (!disable_piclenvd)
/*
* The Fan driver does not know the current fan speed.
* variable speed fans under software control to 50%
* of the max speed and reread the fan to get the
* current speed.
*/
if (!disable_piclenvd) {
continue;
}
}
/*
* Process sensor_fan_map[] table and initialize sensors[]
* array for this fan.
*/
continue;
sensor_cnt++;
}
}
}
return (fancnt);
}
/*
* Adjust specified sensor target temperature and fan adjustment rate
*/
static void
{
float rate;
/*
* Look at current power state of all power managed devices
* associated with this sensor and look up the desired target
* temperature and pick the lowest one of those values. Also,
* calculate the rate of change based upon whether one or more
* of the associated power managed devices are not running at
* full power mode.
*/
return;
rate = 1.0;
if (index <= 0)
continue;
/* not running at full power */
if (env_debug > 1)
"pmdev: %-13s new_target:%d cur:%d power:%d/%d\n",
}
if (env_debug)
"sensor: %-13s new_target:%d cur:%d power:%d/%d\n",
}
/*
* Update current power level of all PM devices we are tracking and adjust
* the target temperature associated with the corresponding sensor.
*
* Returns 1 if one or more pmdev power level was adjusted; 0 otherwise.
*/
static int
{
int cur_power;
int updated = 0;
updated = 1;
}
}
}
return (updated);
}
/*
* Check if the specified sensor is present.
* Returns 1 if present; 0 otherwise.
*
* Note that we don't use ptree_get_node_by_path() here to detect
* if a temperature device is present as we don't want to make
* "devtree" a critical plugin.
*/
static int
{
int sensor_present = 0;
/*
* Construct temperature device path by stripping minor
* node name from the devfs_path and use di_init() to
* see if the node exists.
*/
if (p != NULL)
*p = '\0';
sensor_present = 1;
}
return (sensor_present);
}
/*
* Open temperature sensor devices and initialize per sensor data structure.
* Returns #sensors found.
*/
static int
envd_setup_sensors(void)
{
int sensorcnt = 0;
int sensor_present;
/* Don't reinitialize opened sensor */
} else {
/* Initialize sensor's initial state */
sensorp->warning_tstamp = 0;
sensorp->warning_start = 0;
sensorp->shutdown_tstamp = 0;
threshp->policy_data[0] : 0;
sizeof (path));
if (sensor_present && !devfsadm_invoked &&
devfsadm_cmd[0] != '\0') {
/*
* Sensor is present but no path
* exists as someone rebooted the
* system without "-r" option. Let's
* invoke "devfsadm" once to create
* max1617 sensors paths in /devices
* subtree and try again so that we
* can monitor all accessible sensors
* and prevent any CPU overheating.
*
* Note that this routine is always
* called in main thread context and
* serialized with respect to other
* plugins' initialization. Hence, it's
* safe to use system(3C) call here.
*/
devfsadm_invoked = 1;
(void) system(devfsadm_cmd);
goto retry;
}
if (sensor_present)
continue;
}
/*
* Set cur_temp field to the current temperature value
*/
}
}
sensorcnt++;
/*
* Set low_power_off and high_power_off limits
*/
if (threshp && !disable_power_off) {
if (env_debug > 1)
if (env_debug > 1)
&temp);
}
}
/*
* Locate "CPU Speed" component for any PM devices associated with
* the sensors.
*/
int i, ncomp;
/*
* Lookup speed component and get full and current power
* level for that component.
*/
for (i = 0; i < ncomp; i++) {
physpath[0] = '\0';
continue;
continue;
pmdevp->speed_comp = i;
/*
* Get full power and current power level
*/
&pmreq);
&pmreq);
if (sensorp) {
}
break;
}
if (env_debug > 1)
"sensor:%s %p pmdev:%s comp:%s %d power:%d/%d\n",
pmdevp->full_power);
}
return (sensorcnt);
}
/*
* Read all temperature sensors and take appropriate action based
* upon temperature threshols associated with each sensor. Possible
* actions are:
*
* temperature > high_shutdown
* temperature < low_shutdown
* on the system console provided the temperature has been
* in shutdown range for "shutdown_interval" seconds.
*
* high_warning < temperature <= high_shutdown
* low_warning > temperature >= low_shutdown
* once every "warning_interval" seconds.
*
* Note that the current temperature is recorded in the "cur_temp" field
* within each env_sensor_t structure.
*/
static void
monitor_sensors(void)
{
continue;
if (env_debug)
"sensor: %-13s temp prev_avg:%6.2f "
"cur:%d avg_temp:%6.2f power:%d/%d target:%d\n",
/*
* If this sensor already triggered system shutdown, don't
*/
continue;
/*
* Check for the temperature in warning and shutdown range
* and take appropriate action.
*/
/*
* Check if the temperature has been in warning
* range during last 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 warning_interval
* seconds.
*/
if (sensorp->warning_start == 0)
warning_duration) && (wtstamp == 0 ||
}
} else if (sensorp->warning_start != 0)
sensorp->warning_start = 0;
!disable_shutdown) {
if (sensorp->shutdown_tstamp == 0)
/*
* Shutdown the system if the temperature remains
* in the shutdown range for over shutdown_interval
* seconds.
*/
/* log error */
/* shutdown the system (only once) */
if (system_shutdown_started == B_FALSE) {
}
}
} else if (sensorp->shutdown_tstamp != 0)
sensorp->shutdown_tstamp = 0;
}
}
/*
* Adjust fan speed based upon the current temperature value of various
* sensors affected by the specified fan.
*/
static int
{
int i;
int av_ambient;
int amb_cnt;
/*
* Get current fan speed
*/
return (-1);
/*
* Calculate new fan speed for each sensor and pick the largest one.
*/
speed = 0;
av_ambient = 0;
amb_cnt = 0;
for (i = 0; i < fanp->sensor_cnt; i++) {
continue;
/*
* Note ambient temperatures to determine lpm for system fan
*/
av_ambient += temp;
amb_cnt++;
}
/*
* If the current temperature is above the warning
* threshold, use max fan speed.
*/
break;
break;
}
/*
* Try to achieve the desired target temperature.
* Calculate new fan speed based upon whether the
* temperature is rising, falling or steady state.
* Also take into consideration the current fan
* speed as well as the desired target temperature.
*/
float multiplier;
if (tempdiff > AVG_TEMP_HYSTERESIS) {
/*
* Temperature is rising. Increase fan
* speed 0.5% for every 1C above the
* (target - RISING_TEMP_MARGIN) limit.
* Also take into consideration temperature
* rising rate and the current fan speed.
*/
if (delta <= 0)
multiplier = 0;
else
2 : 1);
} else if (tempdiff < -AVG_TEMP_HYSTERESIS) {
/*
* Temperature is falling. Decrease fan
* speed 0.5% for every 1C below the
* (target + FALLING_TEMP_MARGIN) limit.
* Also take into consideration temperature
* falling rate and the current fan speed.
*/
if (delta >= 0)
multiplier = 0;
else
2 : 1);
} else {
/*
* Temperature is changing very slowly.
* Adjust fan speed by 0.4% for every 1C
*/
multiplier = 1.0;
}
/*
* Enforece some bounds on multiplier and the
* speed change.
*/
if (env_debug > 1)
"%4.2f x %4.2f speed %5.2f -> %5.2f\n",
/*
* Set fan speed linearly within the operating
* range specified by the policy_data[LOW_NOMINAL_LOC]
* and policy_data[HIGH_NOMINAL_LOC] threshold values.
* Fan speed is set to minimum value at LOW_NOMINAL
* and to maximum value at HIGH_NOMINAL value.
*/
if (env_debug > 1)
"sensor: %-8s policy: linear, cur_speed %5.2f"\
} else {
}
}
/*
* Adjust speed using lpm tables
*/
if (amb_cnt > 0) {
av_ambient = (av_ambient >= 0 ?
}
/*
* Record and update fan speed, if different.
*/
}
if (env_debug)
"fan: %-16s speed cur:%6.2f new:%6.2f\n",
return (0);
}
/*
* This is the environment thread, which monitors the current temperature
* and power managed state and controls system fan speed. Temperature is
* polled every sensor-poll_interval seconds duration.
*/
/*ARGSUSED*/
static void *
{
int to;
sensorp++) {
if (sensorp->obs2exp_map)
sensorp->obs2exp_cnt = 0;
}
/*
* Process environmental segment data, if present,
* in the FRU SEEPROM.
*/
/*
* Process tuneable parameters
*/
/*
* Setup temperature sensors and fail if we can't open
* at least one sensor.
*/
if (envd_setup_sensors() <= 0) {
return (NULL);
}
if (xwd < 0) {
xwd = -1;
}
/*
* Setup fan device (don't fail even if we can't access
* the fan as we can still monitor temeperature.
*/
(void) envd_setup_fans();
for (;;) {
(void) pthread_rwlock_rdlock(&envd_rwlock);
/*
* If no "pmthr" thread, then we need to update the
* current power level for all power managed deviecs
* so that we can determine correct target temperature.
*/
if (pmthr_exists == B_FALSE)
(void) update_pmdev_power();
if (xwd >= 0)
if (!disable_piclenvd) {
/*
* Monitor current temperature for all sensors
* (current temperature is recorded in the "cur_temp"
* field within each sensor data structure)
*/
/*
* Adjust CPU and system fan speed
*/
if (envd_cpu_fan.forced_speed < 0)
if (envd_system_fan.forced_speed < 0)
(void) adjust_fan_speed(&envd_system_fan,
/*
* Turn off power supply fan if in lowest power state.
*/
if (env_debug)
"fan: %-16s speed cur:%6.2f new:%6.2f "
(float)fan_speed, cur_lpstate);
}
(void) pthread_rwlock_unlock(&envd_rwlock);
/*
* Wait for sensor_poll_interval seconds before polling
* again. Note that we use our own envd_sleep() routine
* as sleep() in POSIX thread library gets affected by
* the wall clock time being set back.
*/
(void) envd_sleep(sensor_poll_interval);
}
/*NOTREACHED*/
return (NULL);
}
/*
* This is the power management thread, which monitors all power state
* change events and wakes up the "envthr" thread when the system enters
* or exits the lowest power state.
*/
/*ARGSUSED*/
static void *
{
cur_lpstate = 0;
for (;;) {
/*
* Get PM state change events to check if the system
* is in lowest power state and wake up the "envthr"
* thread when the power state changes.
*
* To minimize polling, we use the blocking interface
* to get the power state change event here.
*/
break;
continue;
}
/*
* Extract the lowest power state from the last queued
* state change events. We pick up queued state change
* events using the non-blocking interface and wake up
* the "envthr" thread only after consuming all the
* state change events queued at that time.
*/
do {
if (env_debug > 1) {
"pmstate event:0x%x flags:%x comp:%d "
"oldval:%d newval:%d path:%s\n",
}
/*
* Update current PM state for the components we are
* tracking. In case of CPU devices, PM state change
* event can be generated even before the state change
* takes effect, hence we need to get the current state
* for all CPU devices every time and recalculate the
* target temperature. We do this once after consuming
* all the queued events.
*/
(void) pthread_rwlock_rdlock(&envd_rwlock);
(void) update_pmdev_power();
(void) pthread_rwlock_unlock(&envd_rwlock);
}
/*
* We won't be able to monitor lowest power state any longer,
* hence reset it.
*/
cur_lpstate = 0;
return (NULL);
}
/*
* Process sensor threshold related tuneables
*/
static int
{
int retval = 0;
long val;
void *addr;
/*
* Tuneable entry can be in one of the following formats:
*
* threshold-keyword <int-value>
* threshold-keyword <int-value> <sensor-name> ...
*
* Convert threshold value into integer value and check for
* optional sensor name. If no sensor name is specified, then
* the tuneable applies to all sensors specified by the "flags".
* Otherwise, it is applicable to the specified sensors.
*
* Note that the dummy_thresh_addr is the address of the threshold
* to be changed and is converted into offset by subtracting the
* base dummy_thresh address. This offset is added to the base
* address of the threshold structure to be update to determine
* the final memory address to be modified.
*/
errno = 0;
retval = -1;
retval = -1;
int cnt = 0;
continue;
/*
* Convert dummy_thresh_addr into memory address
* for this sensor threshold values.
*/
(int)((char *)dummy_thresh_addr -
(char *)&dummy_thresh);
cnt++;
if (env_debug)
"line:%d %s = %d for sensor: '%s'\n",
}
if (cnt == 0)
"%s SKIPPED as no matching sensor found.\n",
} else {
/* apply threshold value to the specified sensors */
do {
"SUNW_piclenvd: file:%s line:%d %s SKIPPED"
" for '%s' as not a valid sensor.\n",
continue;
}
/*
* Convert dummy_thresh_addr into memory address
* for this sensor threshold values.
*/
(int)((char *)dummy_thresh_addr -
(char *)&dummy_thresh);
if (env_debug)
"line:%d %s = %d for sensor: '%s'\n",
}
return (retval);
}
/*
* Process integer tuneables
*/
static int
{
int retval = 0;
char *endp;
long val;
/*
* Convert input into integer value and ensure that there is
* no other token in the buffer.
*/
errno = 0;
retval = -1;
else {
switch (size) {
case 1:
retval = -1;
else
break;
case 2:
retval = -1;
else
break;
case 4:
break;
default:
retval = -1;
}
}
if (retval == -1)
else if (env_debug)
return (retval);
}
/*
* Process string tuneables
*
* String value must be within double quotes. Skip over initial white
* spaces before looking for string value.
*/
static int
{
int retval = 0;
char c, *p, *strend;
/* Skip over white spaces */
/*
* Parse srting and locate string end (handling escaped double quotes
* and other characters)
*/
if (buf[0] != '"')
else {
if (c == '"' || (c == '\\' && *++p == '\0'))
break;
}
retval = -1;
} else {
*strend = '\0';
if (env_debug)
}
return (retval);
}
/*
* Process configuration file
*/
static void
process_env_conf_file(void)
{
int skip_line = 0;
return;
return;
/*
* Blank lines or lines starting with "#" or "*" in the first
* column are ignored. All other lines are assumed to contain
* input in the following format:
*
* keyword value
*
* where the "value" can be a signed integer or string (in
* double quotes) depending upon the keyword.
*/
if (len <= 0)
continue;
/* skip long lines */
skip_line = 1;
continue;
} else if (skip_line) {
skip_line = 0;
continue;
} else
/* skip comments */
continue;
/*
* Skip over white space to get the keyword
*/
if (*tok == '\0')
continue; /* blank line */
/* Get possible location for value (within current line) */
/*
* Lookup the keyword and process value accordingly
*/
break;
}
}
}
}
/*
* Setup envrionmental monitor state and start threads to monitor
* temperature and power management state.
* Returns -1 on error, 0 if successful.
*/
static int
envd_setup(void)
{
int val;
int err;
if (pthread_attr_init(&thr_attr) != 0 ||
return (-1);
if (pm_fd == -1)
envd_open_pm();
/*
* Setup lpm devices
*/
lpm_devices = NULL;
if (env_debug)
err);
}
/*
* Initialize global state to initial startup values
*/
disable_piclenvd = 0;
disable_power_off = 0;
disable_shutdown = 0;
disable_warning = 0;
sizeof (fru_devfsadm_cmd));
sizeof (cpu_die_thresh_default));
sizeof (cpu_amb_thresh_default));
sizeof (cpu_die_thresh_default));
sizeof (cpu_amb_thresh_default));
}
/*
* Create a thread to monitor temperature and control fan
* speed.
*/
return (-1);
}
/*
* Create a thread to monitor PM state
*/
if (pmthr_exists == B_FALSE) {
} else
}
return (0);
}
/*
* Callback function used by ptree_walk_tree_by_class for the cpu class
*/
static int
{
int err;
int id;
/* Get CPU's ID, it is an int */
if (err != PICL_SUCCESS)
return (PICL_WALK_CONTINUE);
/* Get the pmdevp for the CPU */
break;
pmdevp++;
}
/* Return if didn't find the pmdevp for the cpu id */
return (PICL_WALK_CONTINUE);
/* Get the devfs-path property */
if (err != PICL_SUCCESS)
return (PICL_WALK_CONTINUE);
if ((err != PICL_SUCCESS) ||
return (PICL_WALK_CONTINUE);
return (PICL_WALK_CONTINUE);
if (err != PICL_SUCCESS)
return (PICL_WALK_CONTINUE);
return (PICL_WALK_CONTINUE);
}
/*
* Find the CPU's in the picl tree, set the devfs-path for pmdev_name
*/
static void
{
int err;
if (err != PICL_SUCCESS)
return;
}
static void
piclenvd_register(void)
{
}
static void
piclenvd_init(void)
{
/*
* Setup the names for the pm sensors, we do it just the first time
*/
if (pmdev_names_init == B_FALSE) {
(void) setup_pmdev_names();
}
/*
*/
(void) pthread_rwlock_wrlock(&envd_rwlock);
if (envd_setup() != 0) {
(void) pthread_rwlock_unlock(&envd_rwlock);
return;
}
(void) pthread_rwlock_unlock(&envd_rwlock);
/*
*/
}
static void
piclenvd_fini(void)
{
/*
* Delete the lpm device list. After this the lpm information
* will not be used in determining the fan speed, till the lpm
* device information is initialized by setup_lpm_devices called
* by envd_setup.
*/
/*
* 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.
*/
/*
* Since this is a critical plug-in, we know that it won't be
* unloaded and will be reinited again unless picld process is
* going away. Therefore, it's okay to let "envthr" and "pmthr"
* continue so that we can monitor the environment during SIGHUP
* handling also.
*/
}
/*VARARGS2*/
void
{
}
#ifdef __lint
/*
* Redefine sigwait to posix style external declaration so that LINT
* does not check against libc version of sigwait() and complain as
* it uses different number of arguments.
*/
#endif
/*
* sleep() in libpthread gets affected by time being set back, hence
* can cause the "envthr" not to wakeup for extended duration. For
* now, we implement our own sleep() routine below using alarm().
* This will work only if SIGALRM is masked off in all other threads.
* Note that SIGALRM signal is masked off in the main thread, hence
* in all threads, including the envthr, the one calling this routine.
*
* Note that SIGALRM and alarm() can't be used by any other thread
* in this manner.
*/
static unsigned int
{
int sig;
unsigned int unslept;
if (sleep_tm == 0)
return (0);
(void) sigemptyset(&alrm_mask);
return (unslept);
}