sonic-buildimage/platform/broadcom/sonic-platform-modules-mitac/ly1200-32x/opt/fan-ctrl/fan-ctrl
2018-08-11 09:09:03 +00:00

680 lines
17 KiB
Bash
Executable File

#!/bin/bash
#/*
#**********************************************************************
#*
#* @filename fan-ctrl
#*
#* @purpose system daemon for controlling system fan pwm
#*
#* @create 2017/06/21
#*
#* @author nixon.chu <nixon.chu@mic.com.tw>
#*
#* @history 2017/06/21: init version
#*
#**********************************************************************
#*/
DIR=$(dirname $0)
# include files
source ${DIR}/funcs.sh
#/*
#**********************************************************************
#*
#* CONSTANT VARIABLES
#*
#**********************************************************************
#*/
# process name/id
DAEMON_NAME=`basename $0`
DAEMON_PID="$$"
# define symbol for fan/temperature type sensor
SYMBOL_TEMP="TEMP_"
SYMBOL_FAN="FAN_"
# describe the structure of temperature sensor by id
STRUCT_TEMP_NAME=0
STRUCT_TEMP_CMD=1
STRUCT_TEMP_MAX=2
STRUCT_TEMP_SIZE=$(( ${STRUCT_TEMP_MAX} + 1 ))
# describe the structure of fan sensor by id
STRUCT_FAN_CMD=0
STRUCT_FAN_SIZE=$(( ${STRUCT_FAN_CMD} + 1 ))
# default fan zone configuration file
DEF_ZONE_CONF="${DIR}/fan-zone.conf"
# default fan zone thermal configuration file
DEF_TEMP_CONF="${DIR}/fan-zone-thermal.conf"
# default interval (sec)
DEF_INTERVAL=10
# default hysteresis (deg C)
DEF_HYSIS=3
# default debug mode (0: OFF, 1: ON)
DEF_DEBUG_MODE=0
# default log file
DEF_LOG_FILE="/var/log/syslog"
# default fan level
DEF_FAN_LEVEL=0
# default minimum pwm (38.25=255x15%)
DEF_MIN_PWM=39
# default zone id
DEF_ZONE_ID=0
# error codes
E_STATUS_GOOD=0
E_STATUS_FAULT=1
E_INVALID_ARGS=11
E_INVALID_CONF=12
# message levels
MSG_EMERG=0
MSG_ALERT=1
MSG_CRIT=2
MSG_ERR=3
MSG_WARNING=4
MSG_NOTICE=5
MSG_INFO=6
MSG_DEBUG=7
# default message level
DEF_MSG_LEVEL=$MSG_WARNING
#/*
#**********************************************************************
#*
#* GLOBAL VARIABLES
#*
#**********************************************************************
#*/
# temperature sensor group
GBL_TEMP_GRP=()
GBL_TEMP_NUM=${#GBL_TEMP_GRP[@]}
# fan sensor group
GBL_FAN_GRP=()
GBL_FAN_NUM=${#GBL_FAN_GRP[@]}
# fan level table
GBL_FAN_LEVEL=()
GBL_LEVEL_NUM=${#GBL_FAN_LEVEL[@]}
# load defaults
GBL_ZONE_CONF=$DEF_ZONE_CONF
GBL_TEMP_CONF=$DEF_TEMP_CONF
GBL_INTERVAL=$DEF_INTERVAL
GBL_HYSIS=$DEF_HYSIS
GBL_DEBUG_MODE=$DEF_DEBUG_MODE
GBL_LOG_FILE=$DEF_LOG_FILE
GBL_CUR_LEVEL=$DEF_FAN_LEVEL
GBL_MIN_PWM=$DEF_MIN_PWM
GBL_ZONE_ID=$DEF_ZONE_ID
GBL_MSG_LEVEL=$DEF_MSG_LEVEL
# temperature sensors' readings/statuses/properties (properties list have to be converted with fan level table)
GBL_TEMP_READINGS=()
GBL_TEMP_STATUSES=()
GBL_TEMP_PROPERTY=()
#/*
#**********************************************************************
#*
#* FUNCTIONS
#*
#**********************************************************************
#*/
#/*
#* FEATURE:
#* usage
#* PURPOSE:
#* show the usage of this daemon
#* PARAMETERS:
#*
#* RETURNS:
#* success, this function returns @E_INVALID_ARGS.
#*/
function usage() {
local dbg_mode
[ $GBL_DEBUG_MODE -eq 0 ] && dbg_mode="off" || dbg_mode="on"
echo -e "Usage: $0 [-z zone_file] [-t thermal_file] [-o log_file] [-d]" >&2
echo -e "" >&2
echo -e "Arguments:" >&2
echo -e " -z, --zone-config Fan zone configuration file (default: $DEF_ZONE_CONF)" >&2
echo -e " -t, --temp-config Fan zone thermal configuration file (default: $DEF_TEMP_CONF)" >&2
echo -e " -o, --log-file Log file (default: $DEF_LOG_FILE)" >&2
echo -e " -d, --debug Debug mode (default: $dbg_mode)" >&2
exit ${E_INVALID_ARGS}
}
#/*
#* FEATURE:
#* print_msg
#* PURPOSE:
#* print message by message level
#* PARAMETERS:
#* msg_lvl (IN) message level
#* msg (IN) message
#* RETURNS:
#*
#*/
function print_msg() {
local msg_lvl=$1
local msg=$2
[ $msg_lvl -le $GBL_MSG_LEVEL ] && echo "${msg}" >&2
}
#/*
#* FEATURE:
#* err_msg
#* PURPOSE:
#* log error message
#* PARAMETERS:
#* msg (IN) message
#* err_no (IN) error code
#* RETURNS:
#* success, this function returns non-zero error code.
#*/
function err_msg() {
local msg=$1
local err_no=$2
log_msg $MSG_ERROR "${msg}"
exit ${err_no}
}
#/*
#* FEATURE:
#* log_msg
#* PURPOSE:
#* log message
#* PARAMETERS:
#* msg_lvl (IN) message level
#* msg (IN) message
#* RETURNS:
#*
#*/
function log_msg() {
local msg_lvl=$1
local msg=$2
if [ $GBL_LOG_FILE == $DEF_LOG_FILE ]; then
`logger -t $DAEMON_NAME -p $msg_lvl $msg`
else
echo -e "`date +"%b %_d %T"` `hostname` $DAEMON_NAME[$DAEMON_PID]: ${msg}" >> ${GBL_LOG_FILE}
fi
print_msg $msg_lvl "${msg}"
}
#/*
#* FEATURE:
#* debug_temp_grp
#* PURPOSE:
#* debug function for showing the temperature sensor group configs
#* @GBL_TEMP_GRP[]: store sensor group configs (sensor name, command, and max_temp)
#* PARAMETERS:
#*
#* RETURNS:
#*
#*/
function debug_temp_grp() {
if [ $GBL_DEBUG_MODE -ne 0 ]; then
echo "********* Sensor Group *********"
for ((i=0; i<$GBL_TEMP_NUM; i++))
do
echo -en "`fetch_temp_struct $i $STRUCT_TEMP_NAME`\t"
echo -en "`fetch_temp_struct $i $STRUCT_TEMP_CMD`\t"
echo "`fetch_temp_struct $i $STRUCT_TEMP_MAX`"
done
fi
}
#/*
#* FEATURE:
#* debug_temp_readings
#* PURPOSE:
#* debug function for showing the temperature sensors' readings
#* @GBL_TEMP_READINGS[]: store current reading for temperature sensors
#* PARAMETERS:
#*
#* RETURNS:
#*
#*/
function debug_temp_readings() {
if [ $GBL_DEBUG_MODE -ne 0 ]; then
echo "********* Sensor Reading *********"
for ((i=0; i<$GBL_TEMP_NUM; i++))
do
echo -e "`fetch_temp_struct $i $STRUCT_TEMP_NAME`\t${GBL_TEMP_READINGS[$i]}"
done
fi
}
#/*
#* FEATURE:
#* debug_temp_property
#* PURPOSE:
#* debug function for showing the temperature sensors' properties
#* @GBL_TEMP_PROPERTY[]: store temperature sensors' properties (asserted temperature for each fan levels)
#* PARAMETERS:
#*
#* RETURNS:
#*
#*/
function debug_temp_property() {
local size base start_idx level
if [ $GBL_DEBUG_MODE -ne 0 ]; then
echo "********* Sensor Table *********"
for ((i=0; i<${GBL_TEMP_NUM}; i++))
do
# show array @GBL_TEMP_PROPERTY[]: format name(1) + fan levels(2-?)
size=$(( ${GBL_LEVEL_NUM}+1 ))
base=$i*${size}
start_idx=$(( $base+1 ))
level=${GBL_LEVEL_NUM}
echo -e "${GBL_TEMP_PROPERTY[$base]}\t\t${GBL_TEMP_PROPERTY[@]:$start_idx:$level}"
done
fi
}
#/*
#* FEATURE:
#* fetch_temp_struct
#* PURPOSE:
#* fetch the member value of specific temperature sensor
#* PARAMETERS:
#* id (IN) sensor id
#* member_id (IN) member id
#* RETURNS:
#* success, returns member value
#*/
function fetch_temp_struct() {
local id member_id index_base data
id=$1
member_id=$2
index_base=$(( $id * $STRUCT_TEMP_SIZE ))
data=${GBL_TEMP_GRP[$(($index_base+$member_id))]}
# remove unuseful characters, such as double quotes('"') and the start/end space(' ') of a string.
data=`echo $data | sed 's/^ *\| *$//g' | sed 's/^"\|\"$//g'`
echo $data
}
#/*
#* FEATURE:
#* fetch_temp_property
#* PURPOSE:
#* fetch the properties of specific temperature sensor
#* PARAMETERS:
#* name (IN) sensor name
#* RETURNS:
#* success, returns an asserted temperature list
#*/
function fetch_temp_property() {
local name size base start_idx length
name=$1
size=$(( $GBL_LEVEL_NUM+1 ))
for ((i=0; i<${GBL_TEMP_NUM}; i++))
do
base=$i*${size}
# find sensor by name
if [ ${GBL_TEMP_PROPERTY[${base}]} == "${name}" ]; then
start_idx=$(( $base+1 ))
length=$GBL_LEVEL_NUM
echo ${GBL_TEMP_PROPERTY[@]:$start_idx:$length}
break
fi
done
}
#/*
#* FEATURE:
#* debug_fan_speed
#* PURPOSE:
#* debug function for showing system current fan level and PWM
#* @GBL_CUR_LEVEL: system current/output fan level
#* PARAMETERS:
#*
#* RETURNS:
#*
#*/
function debug_fan_speed() {
if [ $GBL_DEBUG_MODE -ne 0 ]; then
# For human readable, the representation of fan level will start from 1.
echo "Current Level: $(( $GBL_CUR_LEVEL+1 )), PWM: ${GBL_FAN_LEVEL[$GBL_CUR_LEVEL]}"
fi
}
#/*
#* FEATURE:
#* arg_parse
#* PURPOSE:
#* parser for input arguments
#* PARAMETERS:
#* arg_lists[] (IN) argument list
#* RETURNS:
#*
#*/
function arg_parse() {
while [[ $# -ge 1 ]]
do
key="$1"
case $key in
-z|--zone-config)
[ $# -lt 2 ] && usage
GBL_ZONE_CONF=$2
[ ! -e $GBL_ZONE_CONF ] && usage
shift # past argument
;;
-t|--temp-config)
[ $# -lt 2 ] && usage
GBL_TEMP_CONF=$2
[ ! -e $GBL_TEMP_CONF ] && usage
shift # past argument
;;
-o|--log-file)
[ $# -lt 2 ] && usage
GBL_LOG_FILE=$2
shift # past argument
;;
-d|--debug)
GBL_DEBUG_MODE=1
;;
*)
usage # unknown option
;;
esac
shift # past argument or value
done
}
#/*
#* FEATURE:
#* valid_conf
#* PURPOSE:
#* check if configuration files are valid
#* PARAMETERS:
#*
#* RETURNS:
#* success, this function returns nothing
#* fail, returns @E_INVALID_CONF
#*/
function valid_conf() {
local list_1 list_2 match_num
list_1=("${!1}")
list_2=("${!2}")
# validate zone config
[ ${GBL_TEMP_NUM} -eq 0 ] && err_msg "config: status=invalid. reason=no TEMPERATURE SENSORS defined." "${E_INVALID_CONF}"
[ ${GBL_FAN_NUM} -eq 0 ] && err_msg "config: status=invalid. reason=no FAN defined." "${E_INVALID_CONF}"
# validate thermal config
[ ${GBL_LEVEL_NUM} -eq 0 ] && err_msg "config: status=invalid. reason=no FAN LEVELS defined." "${E_INVALID_CONF}"
[ ${#list_2[@]} -ne ${GBL_TEMP_NUM} ] && err_msg "config: status=invalid. reason=the number of temperature sensors is inconsistent." "${E_INVALID_CONF}"
# compare temperature sensor name between configuration files
match_num=0
for i in ${!list_1[@]}
do
for j in ${!list_2[@]}
do
[ "${list_1[$i]}" == "${list_2[$j]}" ] && match_num=$(( $match_num+1 ))
done
done
[ $match_num -ne ${GBL_TEMP_NUM} ] && err_msg "config: status=invalid. reason=the name of temperature sensors is inconsistent." "${E_INVALID_CONF}"
# validate pwm values
for pwm in ${GBL_FAN_LEVEL[@]}
do
expr $pwm + $GBL_MIN_PWM >/dev/null 2>&1
[ $? -ne 0 ] && err_msg "config: status=invalid. reason=the value of pwm is not integer." "$E_INVALID_CONF"
[ $pwm -lt $GBL_MIN_PWM ] && err_msg "config: status=invalid. reason=the value of fan level is less than MIN_PWM($GBL_MIN_PWM)." "$E_INVALID_CONF"
done
}
#/*
#* FEATURE:
#* initialize
#* PURPOSE:
#* load zone config and thermal config
#* PARAMETERS:
#*
#* RETURNS:
#*
#*/
function initialize() {
local data conf_interval conf_hysis conf_min_pwm conf_zone_id conf_msg_level temp_list_1 temp_list_2
log_msg $MSG_INFO "Initializing fan control service..."
# fetch temperature sensor name from configuration files
temp_list_1=(`cat ${GBL_ZONE_CONF} | grep "^${SYMBOL_TEMP}" | awk -F "," {'print $1'}`)
temp_list_2=(`cat ${GBL_TEMP_CONF} | grep "^${SYMBOL_TEMP}" | awk {'print $1'}`)
# calculate the number of temperature/fan sensors
GBL_TEMP_NUM=${#temp_list_1[@]}
GBL_FAN_NUM=(`cat ${GBL_ZONE_CONF} | grep "^${SYMBOL_FAN}" | awk {'print $2'} | wc -l`)
# calculate the number of fan levels
GBL_FAN_LEVEL=(`cat ${GBL_TEMP_CONF} | grep "^PWM" | awk -F "PWM" {'print $2'}`)
GBL_LEVEL_NUM=${#GBL_FAN_LEVEL[@]}
# check if the interval/hyteresis should be updated.
conf_interval=`cat ${GBL_ZONE_CONF} | grep "^INTERVAL=" | awk -F "=" {'print $2'} | tail`
[ ! -z ${conf_interval} ] && GBL_INTERVAL=${conf_interval}
conf_hysis=`cat ${GBL_ZONE_CONF} | grep "^HYTERESIS=" | awk -F "=" {'print $2'} | tail`
[ ! -z ${conf_hysis} ] && GBL_HYSIS=${conf_hysis}
conf_min_pwm=`cat ${GBL_ZONE_CONF} | grep "^MIN_PWM=" | awk -F "=" {'print $2'} | tail`
[ ! -z ${conf_min_pwm} ] && GBL_MIN_PWM=${conf_min_pwm}
conf_zone_id=`cat ${GBL_ZONE_CONF} | grep "^ZONE_ID=" | awk -F "=" {'print $2'} | tail`
[ ! -z ${conf_zone_id} ] && GBL_ZONE_ID=${conf_zone_id}
conf_msg_level=`cat ${GBL_ZONE_CONF} | grep "^MSG_LEVEL=" | awk -F "=" {'print $2'} | tail`
[ ! -z ${conf_msg_level} ] && GBL_MSG_LEVEL=${conf_msg_level}
# load zone config
for ((i=1; i<=${GBL_TEMP_NUM}; i++))
do
GBL_TEMP_STATUSES+=($E_STATUS_GOOD)
data=`cat ${GBL_ZONE_CONF} | grep "^${SYMBOL_TEMP}" | awk NR==$i`
# FIXME
GBL_TEMP_GRP+=("`echo $data | awk -F ", " {'print $1'}`")
GBL_TEMP_GRP+=("`echo $data | awk -F ", " {'print $2'}`")
GBL_TEMP_GRP+=("`echo $data | awk -F ", " {'print $3'}`")
done
debug_temp_grp
for ((i=1; i<=${GBL_FAN_NUM}; i++))
do
data=`cat ${GBL_ZONE_CONF} | grep "^${SYMBOL_FAN}" | awk NR==$i{'print $2'}`
GBL_FAN_GRP+=("`echo $data`")
done
# load thermal config
for ((i=0; i<${GBL_TEMP_NUM}; i++))
do
index=$(( $i * ${STRUCT_TEMP_SIZE} + ${STRUCT_TEMP_NAME} ))
sensor_name=${GBL_TEMP_GRP[$index]}
GBL_TEMP_PROPERTY+=(`cat ${GBL_TEMP_CONF} | grep "^${sensor_name}" | tail`)
done
debug_temp_property
# check if the config files is valid
valid_conf temp_list_1[@] temp_list_2[@]
}
#/*
#* FEATURE:
#* temperature_collection
#* PURPOSE:
#* collect all temperature sensor readlings
#* PARAMETERS:
#*
#* RETURNS:
#* success, returns a series of reading @GBL_TEMP_READINGS[]
#*/
function temperature_collection() {
local name cmd max_temp reading status pre_status
GBL_TEMP_READINGS=()
for ((i=0; i<${GBL_TEMP_NUM}; i++))
do
# read temperature sensor value & record sensor status
name=`fetch_temp_struct $i ${STRUCT_TEMP_NAME}`
cmd=`fetch_temp_struct $i ${STRUCT_TEMP_CMD}`
max_temp=`fetch_temp_struct $i ${STRUCT_TEMP_MAX}`
reading=`eval ${cmd} 2>/dev/null`
if [ $? -eq 0 ]; then
GBL_TEMP_READINGS+=($reading)
status=$E_STATUS_GOOD
else
GBL_TEMP_READINGS+=($max_temp)
status=$E_STATUS_FAULT
fi
# Compare previous status of temperature sensor, then update it.
pre_status=${GBL_TEMP_STATUSES[$i]}
if [ $pre_status -eq $E_STATUS_GOOD ] && [ $status -eq $E_STATUS_FAULT ]; then
# Good -> Fault
log_msg $MSG_WARN "[ZONE_${GBL_ZONE_ID}] Sensor $name: status=ERROR. reason=read sensor failed."
elif [ $pre_status -eq $E_STATUS_FAULT ] && [ $status -eq $E_STATUS_GOOD ]; then
# Fault -> Good
log_msg $MSG_WARN "[ZONE_${GBL_ZONE_ID}] Sensor $name: status=RECOVER. reason=reading is $reading deg C"
fi
GBL_TEMP_STATUSES[$i]=$status
done
debug_temp_readings
}
#/*
#* FEATURE:
#* fan_level_selection
#* PURPOSE:
#* select proper fan level by temperature sensor reading
#* PARAMETERS:
#* reading_list (IN) sensor reading list
#* RETURNS:
#* success, returns a fan level value
#*/
function fan_level_selection() {
local reading_list name reading temp_list highest_level pre_level next_level hysis_temp res res1
reading_list=("${!1}")
highest_level=0
pre_level=${GBL_CUR_LEVEL}
next_level=0
# search fan table by each temperature sensor reading
for ((i=0; i<${GBL_TEMP_NUM}; i++))
do
name=`fetch_temp_struct $i $STRUCT_TEMP_NAME`
reading=${reading_list[$i]}
temp_list=(`fetch_temp_property ${name}`)
for j in ${!temp_list[@]}
do
res=`echo $reading \<= ${temp_list[$j]} | bc -l`
if [ $res -eq 1 ] || [ $j -eq $((${#temp_list[@]}-1)) ]; then
if [ $j -gt $highest_level ]; then
highest_level=$j
fi
break
fi
done
done
if [ $highest_level -lt $pre_level ]; then
# Determine if require decreasing current fan level @assert_level
local pass_num
pass_num=0
for ((i=0; i<${GBL_TEMP_NUM}; i++))
do
name=`fetch_temp_struct $i $STRUCT_TEMP_NAME`
reading=${reading_list[$i]}
temp_list=(`fetch_temp_property ${name}`)
assert_level=$(( $pre_level-1 ))
hysis_temp=`echo ${temp_list[$assert_level]} - $GBL_HYSIS | bc`
res=`echo $reading \<= $hysis_temp | bc -l`
if [ $res -eq 1 ]; then
pass_num=$(( $pass_num+1 ))
else
break
fi
done
if [ $pass_num -eq ${GBL_TEMP_NUM} ]; then
# Decrease fan pwm: All sensor reading should less than its own hysteresis temperature
next_level=${highest_level}
else
# Keep current fan pwm
next_level=${pre_level}
fi
else
# Increase fan pwm
next_level=${highest_level}
fi
GBL_CUR_LEVEL=${next_level}
}
#/*
#* FEATURE:
#* apply_fan_level
#* PURPOSE:
#* convert given fan level to PWM, then output to fans
#* PARAMETERS:
#* fan_list (IN) fan devices list
#* fan_level (IN) fan level
#* RETURNS:
#* success, returns a pwm value
#*/
function apply_fan_level() {
local fan_level fan_list
fan_list=("${!1}")
fan_level=$2
for fan in ${fan_list[@]}
do
echo ${GBL_FAN_LEVEL[$fan_level]} > $fan
done
debug_fan_speed
}
#/*
#**********************************************************************
#*
#* MAIN
#*
#**********************************************************************
#*/
Platform_init
arg_parse $@
initialize
log_msg $MSG_INFO "Starting fan control service..."
while [ true ]; do
temperature_collection
fan_level_selection GBL_TEMP_READINGS[@]
apply_fan_level GBL_FAN_GRP[@] ${GBL_CUR_LEVEL}
sleep $GBL_INTERVAL
done
exit 0