[device/celestica]: Add thermalctld support on Haliburton platform APIs (#6493)

- Removed the old function for detecting a faulty fan.
- Removed the old function for detecting excess temperature.
- Implement thermal_manager APIs based on ThermalManagerBase
- Implement thermal_conditions APIs based on ThermalPolicyConditionBase
- Implement thermal_actions APIs based on ThermalPolicyActionBase
- Implement thermal_info APIs based on ThermalPolicyInfoBase
- Add thermal_policy.json
This commit is contained in:
Wirut Getbamrung 2021-05-03 23:14:35 +07:00 committed by GitHub
parent e1ff8b6ad6
commit cfda77b3de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1141 additions and 220 deletions

View File

@ -1,13 +1,11 @@
# Configuration file generated by pwmconfig, changes will be lost # Configuration file generated by pwmconfig, changes will be lost
INTERVAL=2 INTERVAL=2
DEVPATH=hwmon3=devices/pci0000:00/0000:00:13.0/i2c-0/i2c-8/i2c-23/23-004d hwmon2=devices/pci0000:00/0000:00:13.0/i2c-0/i2c-8/i2c-11/11-001a FCTEMPS=23-004d/pwm1=/sys/bus/i2c/devices/11-001a/hwmon/hwmon*/temp1_input 23-004d/pwm2=/sys/bus/i2c/devices/11-001a/hwmon/hwmon*/temp1_input 23-004d/pwm4=/sys/bus/i2c/devices/11-001a/hwmon/hwmon*/temp1_input
DEVNAME=hwmon3=emc2305 hwmon2=max6699 FCFANS=23-004d/pwm1=23-004d/fan1_input 23-004d/pwm2=23-004d/fan2_input 23-004d/pwm4=23-004d/fan4_input
FCTEMPS=hwmon3/device/pwm1=hwmon2/temp1_input hwmon3/device/pwm2=hwmon2/temp1_input hwmon3/device/pwm4=hwmon2/temp1_input MINTEMP=23-004d/pwm1=27 23-004d/pwm2=27 23-004d/pwm4=27
FCFANS=hwmon3/device/pwm1=hwmon3/device/fan1_input hwmon3/device/pwm2=hwmon3/device/fan2_input hwmon3/device/pwm4=hwmon3/device/fan4_input MAXTEMP=23-004d/pwm1=46 23-004d/pwm2=46 23-004d/pwm4=46
MINTEMP=hwmon3/device/pwm1=27 hwmon3/device/pwm2=27 hwmon3/device/pwm4=27 MINSTART=23-004d/pwm1=102 23-004d/pwm2=102 23-004d/pwm4=102
MAXTEMP=hwmon3/device/pwm1=46 hwmon3/device/pwm2=46 hwmon3/device/pwm4=46 MINSTOP=23-004d/pwm1=102 23-004d/pwm2=102 23-004d/pwm4=102
MINSTART=hwmon3/device/pwm1=102 hwmon3/device/pwm2=102 hwmon3/device/pwm4=102 MINPWM=23-004d/pwm1=102 23-004d/pwm2=102 23-004d/pwm4=102
MINSTOP=hwmon3/device/pwm1=102 hwmon3/device/pwm2=102 hwmon3/device/pwm4=102 MAXPWM=23-004d/pwm1=255 23-004d/pwm2=255 23-004d/pwm4=255
MINPWM=hwmon3/device/pwm1=102 hwmon3/device/pwm2=102 hwmon3/device/pwm4=102 THYST=23-004d/pwm1=2 23-004d/pwm2=2 23-004d/pwm4=2
MAXPWM=hwmon3/device/pwm1=255 hwmon3/device/pwm2=255 hwmon3/device/pwm4=255
THYST=hwmon3/device/pwm1=2 hwmon3/device/pwm2=2 hwmon3/device/pwm4=2

View File

@ -1,13 +1,11 @@
# Configuration file generated by pwmconfig, changes will be lost # Configuration file generated by pwmconfig, changes will be lost
INTERVAL=2 INTERVAL=2
DEVPATH=hwmon3=devices/pci0000:00/0000:00:13.0/i2c-0/i2c-8/i2c-23/23-004d hwmon2=devices/pci0000:00/0000:00:13.0/i2c-0/i2c-8/i2c-11/11-001a FCTEMPS=23-004d/pwm1=/sys/bus/i2c/devices/11-001a/hwmon/hwmon*/temp3_input 23-004d/pwm2=/sys/bus/i2c/devices/11-001a/hwmon/hwmon*/temp3_input 23-004d/pwm4=/sys/bus/i2c/devices/11-001a/hwmon/hwmon*/temp3_input
DEVNAME=hwmon3=emc2305 hwmon2=max6697 FCFANS=23-004d/pwm1=23-004d/fan1_input 23-004d/pwm2=23-004d/fan2_input 23-004d/pwm4=23-004d/fan4_input
FCTEMPS=hwmon3/device/pwm1=hwmon2/temp3_input hwmon3/device/pwm2=hwmon2/temp3_input hwmon3/device/pwm4=hwmon2/temp3_input MINTEMP=23-004d/pwm1=29 23-004d/pwm2=29 23-004d/pwm4=29
FCFANS=hwmon3/device/pwm1=hwmon3/device/fan1_input hwmon3/device/pwm2=hwmon3/device/fan2_input hwmon3/device/pwm4=hwmon3/device/fan4_input MAXTEMP=23-004d/pwm1=46 23-004d/pwm2=46 23-004d/pwm4=46
MINTEMP=hwmon3/device/pwm1=29 hwmon3/device/pwm2=29 hwmon3/device/pwm4=29 MINSTART=23-004d/pwm1=102 23-004d/pwm2=102 23-004d/pwm4=102
MAXTEMP=hwmon3/device/pwm1=46 hwmon3/device/pwm2=46 hwmon3/device/pwm4=46 MINSTOP=23-004d/pwm1=102 23-004d/pwm2=102 23-004d/pwm4=102
MINSTART=hwmon3/device/pwm1=102 hwmon3/device/pwm2=102 hwmon3/device/pwm4=102 MINPWM=23-004d/pwm1=102 23-004d/pwm2=102 23-004d/pwm4=102
MINSTOP=hwmon3/device/pwm1=102 hwmon3/device/pwm2=102 hwmon3/device/pwm4=102 MAXPWM=23-004d/pwm1=255 23-004d/pwm2=255 23-004d/pwm4=255
MINPWM=hwmon3/device/pwm1=102 hwmon3/device/pwm2=102 hwmon3/device/pwm4=102 THYST=23-004d/pwm1=2 23-004d/pwm2=2 23-004d/pwm4=2
MAXPWM=hwmon3/device/pwm1=255 hwmon3/device/pwm2=255 hwmon3/device/pwm4=255
THYST=hwmon3/device/pwm1=2 hwmon3/device/pwm2=2 hwmon3/device/pwm4=2

View File

@ -1,2 +1,2 @@
__all__ = ["platform", "chassis"] from . import chassis
from sonic_platform import * from . import platform

View File

@ -6,18 +6,12 @@
# #
############################################################################# #############################################################################
import sys
import re
import os
import subprocess
import json
try: try:
import sys
from sonic_platform_base.sonic_sfp.sfputilhelper import SfpUtilHelper from sonic_platform_base.sonic_sfp.sfputilhelper import SfpUtilHelper
from sonic_platform_base.chassis_base import ChassisBase from sonic_platform_base.chassis_base import ChassisBase
from sonic_py_common import device_info from .common import Common
from .event import SfpEvent from .event import SfpEvent
from .helper import APIHelper
except ImportError as e: except ImportError as e:
raise ImportError(str(e) + "- required module not found") raise ImportError(str(e) + "- required module not found")
@ -38,11 +32,10 @@ class Chassis(ChassisBase):
def __init__(self): def __init__(self):
ChassisBase.__init__(self) ChassisBase.__init__(self)
self._api_helper = APIHelper() self._api_common = Common()
self.sfp_module_initialized = False self.sfp_module_initialized = False
self.__initialize_eeprom() self.__initialize_eeprom()
self.is_host = self._api_helper.is_host() self.is_host = self._api_common.is_host()
if not self.is_host: if not self.is_host:
self.__initialize_fan() self.__initialize_fan()
@ -81,8 +74,9 @@ class Chassis(ChassisBase):
def __initialize_thermals(self): def __initialize_thermals(self):
from sonic_platform.thermal import Thermal from sonic_platform.thermal import Thermal
airflow = self.__get_air_flow()
for index in range(0, NUM_THERMAL): for index in range(0, NUM_THERMAL):
thermal = Thermal(index) thermal = Thermal(index, airflow)
self._thermal_list.append(thermal) self._thermal_list.append(thermal)
def __initialize_eeprom(self): def __initialize_eeprom(self):
@ -95,17 +89,11 @@ class Chassis(ChassisBase):
component = Component(index) component = Component(index)
self._component_list.append(component) self._component_list.append(component)
def __is_host(self): def __get_air_flow(self):
return os.system(HOST_CHK_CMD) == 0 air_flow_path = '/usr/share/sonic/device/{}/fan_airflow'.format(
self._api_common.platform) if self.is_host else '/usr/share/sonic/platform/fan_airflow'
def __read_txt_file(self, file_path): air_flow = self._api_common.read_txt_file(air_flow_path)
try: return air_flow or 'B2F'
with open(file_path, 'r') as fd:
data = fd.read()
return data.strip()
except IOError:
pass
return None
def get_base_mac(self): def get_base_mac(self):
""" """
@ -116,6 +104,14 @@ class Chassis(ChassisBase):
""" """
return self._eeprom.get_mac() return self._eeprom.get_mac()
def get_serial_number(self):
"""
Retrieves the hardware serial number for the chassis
Returns:
A string containing the hardware serial number for this chassis.
"""
return self._eeprom.get_serial()
def get_system_eeprom_info(self): def get_system_eeprom_info(self):
""" """
Retrieves the full content of system EEPROM information for the chassis Retrieves the full content of system EEPROM information for the chassis
@ -141,7 +137,7 @@ class Chassis(ChassisBase):
reboot_cause = self.REBOOT_CAUSE_HARDWARE_OTHER reboot_cause = self.REBOOT_CAUSE_HARDWARE_OTHER
hw_reboot_cause = self._component_list[0].get_register_value( hw_reboot_cause = self._component_list[0].get_register_value(
RESET_REGISTER) RESET_REGISTER)
sw_reboot_cause = self.__read_txt_file( sw_reboot_cause = self._api_common.read_txt_file(
self._reboot_cause_path) or "Unknown" self._reboot_cause_path) or "Unknown"
if hw_reboot_cause == "0x55": if hw_reboot_cause == "0x55":
@ -151,8 +147,12 @@ class Chassis(ChassisBase):
reboot_cause = self.REBOOT_CAUSE_POWER_LOSS reboot_cause = self.REBOOT_CAUSE_POWER_LOSS
elif hw_reboot_cause == "0x33": elif hw_reboot_cause == "0x33":
reboot_cause = self.REBOOT_CAUSE_WATCHDOG reboot_cause = self.REBOOT_CAUSE_WATCHDOG
elif hw_reboot_cause == "0x88":
reboot_cause = self.REBOOT_CAUSE_THERMAL_OVERLOAD_CPU
elif hw_reboot_cause == "0x99":
reboot_cause = self.REBOOT_CAUSE_THERMAL_OVERLOAD_ASIC
else: else:
reboot_cause = self.REBOOT_CAUSE_HARDWARE_OTHER reboot_cause = self.REBOOT_CAUSE_NON_HARDWARE
description = 'Unknown reason' description = 'Unknown reason'
return (reboot_cause, description) return (reboot_cause, description)
@ -251,6 +251,14 @@ class Chassis(ChassisBase):
index, len(self._sfp_list))) index, len(self._sfp_list)))
return sfp return sfp
##############################################################
################## ThermalManager methods ####################
##############################################################
def get_thermal_manager(self):
from .thermal_manager import ThermalManager
return ThermalManager
############################################################## ##############################################################
###################### Device methods ######################## ###################### Device methods ########################
############################################################## ##############################################################
@ -261,13 +269,13 @@ class Chassis(ChassisBase):
Returns: Returns:
string: The name of the device string: The name of the device
""" """
return self._api_helper.hwsku return self._api_common.hwsku
def get_presence(self): def get_presence(self):
""" """
Retrieves the presence of the Chassis Retrieves the presence of the PSU
Returns: Returns:
bool: True if Chassis is present, False if not bool: True if PSU is present, False if not
""" """
return True return True
@ -285,7 +293,7 @@ class Chassis(ChassisBase):
Returns: Returns:
string: Serial number of device string: Serial number of device
""" """
return self._eeprom.get_serial() return self.get_serial_number()
def get_status(self): def get_status(self):
""" """
@ -294,3 +302,4 @@ class Chassis(ChassisBase):
A boolean value, True if device is operating properly, False if not A boolean value, True if device is operating properly, False if not
""" """
return True return True

View File

@ -0,0 +1,283 @@
import os
import imp
import yaml
import subprocess
from sonic_py_common import device_info
class Common:
DEVICE_PATH = '/usr/share/sonic/device/'
PMON_PLATFORM_PATH = '/usr/share/sonic/platform/'
CONFIG_DIR = 'sonic_platform_config'
OUTPUT_SOURCE_IPMI = 'ipmitool'
OUTPUT_SOURCE_GIVEN_LIST = 'value_list'
OUTPUT_SOURCE_GIVEN_VALUE = 'value'
OUTPUT_SOURCE_GIVEN_CLASS = 'class'
OUTPUT_SOURCE_SYSFS = 'sysfs_value'
OUTPUT_SOURCE_FUNC = 'function'
OUTPUT_SOURCE_GIVEN_TXT_FILE = 'txt_file'
OUTPUT_SOURCE_GIVEN_VER_HEX_FILE = 'hex_version_file'
OUTPUT_SOURCE_GIVEN_VER_HEX_ADDR = 'hex_version_getreg'
SET_METHOD_IPMI = 'ipmitool'
NULL_VAL = 'N/A'
HOST_CHK_CMD = "docker > /dev/null 2>&1"
REF_KEY = '$ref:'
def __init__(self, conf=None):
self._main_conf = conf
(self.platform, self.hwsku) = device_info.get_platform_and_hwsku()
def run_command(self, command):
status = False
output = ""
try:
p = subprocess.Popen(
command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
raw_data, err = p.communicate()
if err == '':
status, output = True, raw_data.strip()
except Exception:
pass
return status, output
def _clean_input(self, input, config):
cleaned_input = input
ai = config.get('avaliable_input')
if ai and input not in ai:
return None
input_translator = config.get('input_translator')
if type(input_translator) is dict:
cleaned_input = input_translator.get(input)
elif type(input_translator) is str:
cleaned_input = eval(input_translator.format(input))
return cleaned_input
def _clean_output(self, index, output, config):
output_translator = config.get('output_translator')
if type(output_translator) is dict:
output = output_translator.get(output)
elif type(output_translator) is str:
output = eval(output_translator.format(output))
elif type(output_translator) is list:
output = eval(output_translator[index].format(output))
return output
def _ipmi_get(self, index, config):
argument = config.get('argument')
cmd = config['command'].format(
config['argument'][index]) if argument else config['command']
status, output = self.run_command(cmd)
return output if status else None
def _sysfs_read(self, index, config):
sysfs_path = config.get('sysfs_path')
argument = config.get('argument', '')
if self.REF_KEY in argument:
argument = self._main_conf[argument.split(":")[1]]
if type(argument) is list:
sysfs_path = sysfs_path.format(argument[index])
content = ""
try:
content = open(sysfs_path)
content = content.readline().rstrip()
except IOError as e:
print("Error: unable to open file: %s" % str(e))
return False
return content
def _sysfs_write(self, index, config, input):
sysfs_path = config.get('sysfs_path')
argument = config.get('argument', '')
if self.REF_KEY in argument:
argument = self._main_conf[argument.split(":")[1]]
if type(argument) is list:
sysfs_path = sysfs_path.format(argument[index])
write_offset = int(config.get('write_offset', 0))
output = ""
try:
open_file = open(sysfs_path, "r+")
open_file.seek(write_offset)
open_file.write(input)
open_file.close()
except IOError as e:
print("Error: unable to open file: %s" % str(e))
return False, output
return True, output
def _ipmi_set(self, index, config, input):
arg = config['argument'][index].format(input)
return self.run_command(config['command'].format(arg))
def _hex_ver_decode(self, hver, num_of_bits, num_of_points):
ver_list = []
c_bit = 0
bin_val = bin(int(hver, 16))[2:].zfill(num_of_bits)
bit_split = num_of_bits / (num_of_points + 1)
for x in range(0, num_of_points+1):
split_bin = bin_val[c_bit:c_bit+bit_split]
ver_list.append(str(int(split_bin, 2)))
c_bit += bit_split
return '.'.join(ver_list)
def _get_class(self, config):
"""
Retreives value of expected attribute
Returns:
A value of the attribute of object
"""
path = config['host_path'] if self.is_host() else config['pmon_path']
module = imp.load_source(config['class'], path)
class_ = getattr(module, config['class'])
return class_
def get_reg(self, path, reg_addr):
cmd = "echo {1} > {0}; cat {0}".format(path, reg_addr)
status, output = self.run_command(cmd)
return output if status else None
def read_txt_file(self, path):
with open(path, 'r') as f:
output = f.readline()
return output.strip('\n')
def write_txt_file(self, file_path, value):
try:
with open(file_path, 'w') as fd:
fd.write(str(value))
except Exception:
return False
return True
def is_host(self):
return os.system(self.HOST_CHK_CMD) == 0
def load_json_file(self, path):
"""
Retrieves the json object from json file path
Returns:
A json object
"""
with open(path, 'r') as f:
json_data = yaml.safe_load(f)
return json_data
def get_config_path(self, config_name):
"""
Retrieves the path to platform api config directory
Args:
config_name: A string containing the name of config file.
Returns:
A string containing the path to json file
"""
return os.path.join(self.DEVICE_PATH, self.platform, self.CONFIG_DIR, config_name) if self.is_host() else os.path.join(self.PMON_PLATFORM_PATH, self.CONFIG_DIR, config_name)
def get_output(self, index, config, default):
"""
Retrieves the output for each function base on config
Args:
index: An integer containing the index of device.
config: A dict object containing the configuration of specified function.
default: A string containing the default output of specified function.
Returns:
A string containing the output of specified function in config
"""
output_source = config.get('output_source')
if output_source == self.OUTPUT_SOURCE_IPMI:
output = self._ipmi_get(index, config)
elif output_source == self.OUTPUT_SOURCE_GIVEN_VALUE:
output = config["value"]
elif output_source == self.OUTPUT_SOURCE_GIVEN_CLASS:
output = self._get_class(config)
elif output_source == self.OUTPUT_SOURCE_GIVEN_LIST:
output = config["value_list"][index]
elif output_source == self.OUTPUT_SOURCE_SYSFS:
output = self._sysfs_read(index, config)
elif output_source == self.OUTPUT_SOURCE_FUNC:
func_conf = self._main_conf[config['function'][index]]
output = self.get_output(index, func_conf, default)
elif output_source == self.OUTPUT_SOURCE_GIVEN_TXT_FILE:
path = config.get('path')
output = self.read_txt_file(path)
elif output_source == self.OUTPUT_SOURCE_GIVEN_VER_HEX_FILE:
path = config.get('path')
hex_ver = self.read_txt_file(path)
output = self._hex_ver_decode(
hex_ver, config['num_of_bits'], config['num_of_points'])
elif output_source == self.OUTPUT_SOURCE_GIVEN_VER_HEX_ADDR:
path = config.get('path')
addr = config.get('reg_addr')
hex_ver = self.get_reg(path, addr)
output = self._hex_ver_decode(
hex_ver, config['num_of_bits'], config['num_of_points'])
else:
output = default
return self._clean_output(index, output, config) or default
def set_output(self, index, input, config):
"""
Sets the output of specified function on config
Args:
config: A dict object containing the configuration of specified function.
index: An integer containing the index of device.
input: A string containing the input of specified function.
Returns:
bool: True if set function is successfully, False if not
"""
cleaned_input = self._clean_input(input, config)
if not cleaned_input:
return False
set_method = config.get('set_method')
if set_method == self.SET_METHOD_IPMI:
output = self._ipmi_set(index, config, cleaned_input)[0]
elif set_method == self.OUTPUT_SOURCE_SYSFS:
output = self._sysfs_write(index, config, cleaned_input)[0]
else:
output = False
return output
def get_event(self, timeout, config, sfp_list):
"""
Returns a nested dictionary containing all devices which have
experienced a change at chassis level
"""
event_class = self._get_class(config)
return event_class(sfp_list).get_event(timeout)

View File

@ -6,7 +6,6 @@
# #
############################################################################# #############################################################################
import json
import os.path import os.path
import shutil import shutil
import shlex import shlex
@ -49,7 +48,7 @@ class Component(ComponentBase):
rc = process.poll() rc = process.poll()
if rc != 0: if rc != 0:
return False return False
except: except Exception:
return False return False
return True return True

View File

@ -8,11 +8,9 @@
############################################################################# #############################################################################
try: try:
import glob
import os import os
import sys import sys
import re import re
from array import array
if sys.version_info.major == 3: if sys.version_info.major == 3:
from io import StringIO from io import StringIO
@ -52,7 +50,7 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
value = match.group(3).rstrip('\0') value = match.group(3).rstrip('\0')
_eeprom_info_dict[idx] = value _eeprom_info_dict[idx] = value
except: except Exception:
pass pass
return _eeprom_info_dict return _eeprom_info_dict
@ -61,7 +59,7 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
sys.stdout = StringIO() sys.stdout = StringIO()
try: try:
self.read_eeprom_db() self.read_eeprom_db()
except: except Exception:
decode_output = sys.stdout.getvalue() decode_output = sys.stdout.getvalue()
sys.stdout = original_stdout sys.stdout = original_stdout
return self.__parse_output(decode_output) return self.__parse_output(decode_output)
@ -73,7 +71,7 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
if not os.path.exists(CACHE_ROOT): if not os.path.exists(CACHE_ROOT):
try: try:
os.makedirs(CACHE_ROOT) os.makedirs(CACHE_ROOT)
except: except Exception:
pass pass
# #
@ -82,7 +80,7 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
# #
try: try:
self.set_cache_name(os.path.join(CACHE_ROOT, CACHE_FILE)) self.set_cache_name(os.path.join(CACHE_ROOT, CACHE_FILE))
except: except Exception:
pass pass
e = self.read_eeprom() e = self.read_eeprom()
@ -91,7 +89,7 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
try: try:
self.update_cache(e) self.update_cache(e)
except: except Exception:
pass pass
self.decode_eeprom(e) self.decode_eeprom(e)
@ -112,3 +110,6 @@ class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
def get_mac(self): def get_mac(self):
return self._eeprom.get('0x24', "Undefined.") return self._eeprom.get('0x24', "Undefined.")
def get_pn(self):
return self._eeprom.get('0x21', "Undefined.")

View File

@ -6,12 +6,12 @@
# #
############################################################################# #############################################################################
import json
import math import math
import os.path import os.path
try: try:
from sonic_platform_base.fan_base import FanBase from sonic_platform_base.fan_base import FanBase
from .common import Common
except ImportError as e: except ImportError as e:
raise ImportError(str(e) + "- required module not found") raise ImportError(str(e) + "- required module not found")
@ -67,25 +67,10 @@ class Fan(FanBase):
self.STATUS_LED_COLOR_RED: "amber", self.STATUS_LED_COLOR_RED: "amber",
self.STATUS_LED_COLOR_OFF: "off" self.STATUS_LED_COLOR_OFF: "off"
} }
self._api_common = Common()
FanBase.__init__(self) FanBase.__init__(self)
def __read_txt_file(self, file_path):
try:
with open(file_path, 'r') as fd:
data = fd.read()
return data.strip()
except IOError:
pass
return ""
def __write_txt_file(self, file_path, value):
try:
with open(file_path, 'w') as fd:
fd.write(str(value))
except:
return False
return True
def __search_file_by_name(self, directory, file_name): def __search_file_by_name(self, directory, file_name):
for dirpath, dirnames, files in os.walk(directory): for dirpath, dirnames, files in os.walk(directory):
for name in files: for name in files:
@ -105,7 +90,8 @@ class Fan(FanBase):
if not self.is_psu_fan: if not self.is_psu_fan:
fan_direction_file = (FAN_PATH + fan_direction_file = (FAN_PATH +
self.fan_e1031_direction.format(self.fan_tray_index+1)) self.fan_e1031_direction.format(self.fan_tray_index+1))
raw = self.__read_txt_file(fan_direction_file).strip('\r\n') raw = self._api_common.read_txt_file(
fan_direction_file).strip('\r\n')
direction = self.FAN_DIRECTION_INTAKE if str( direction = self.FAN_DIRECTION_INTAKE if str(
raw).upper() == "F2B" else self.FAN_DIRECTION_EXHAUST raw).upper() == "F2B" else self.FAN_DIRECTION_EXHAUST
@ -126,8 +112,8 @@ class Fan(FanBase):
fan_speed_sysfs_name = "fan{}_input".format(self.fan_index+1) fan_speed_sysfs_name = "fan{}_input".format(self.fan_index+1)
fan_speed_sysfs_path = self.__search_file_by_name( fan_speed_sysfs_path = self.__search_file_by_name(
self.psu_hwmon_path, fan_speed_sysfs_name) self.psu_hwmon_path, fan_speed_sysfs_name)
fan_speed_rpm = self.__read_txt_file(fan_speed_sysfs_path) or 0 fan_speed_rpm = self._api_common.read_txt_file(
fan_speed_raw = float(fan_speed_rpm)/PSU_FAN_MAX_RPM * 100 fan_speed_sysfs_path) or 0
speed = math.ceil(float(fan_speed_rpm) * 100 / PSU_FAN_MAX_RPM) speed = math.ceil(float(fan_speed_rpm) * 100 / PSU_FAN_MAX_RPM)
elif self.get_presence(): elif self.get_presence():
chip = self.emc2305_chip_mapping[self.fan_index] chip = self.emc2305_chip_mapping[self.fan_index]
@ -136,7 +122,7 @@ class Fan(FanBase):
sysfs_path = "%s%s/%s" % ( sysfs_path = "%s%s/%s" % (
EMC2305_PATH, device, EMC2305_FAN_INPUT) EMC2305_PATH, device, EMC2305_FAN_INPUT)
sysfs_path = sysfs_path.format(fan_index[self.fan_tray_index]) sysfs_path = sysfs_path.format(fan_index[self.fan_tray_index])
raw = self.__read_txt_file(sysfs_path).strip('\r\n') raw = self._api_common.read_txt_file(sysfs_path).strip('\r\n')
pwm = int(raw, 10) if raw else 0 pwm = int(raw, 10) if raw else 0
speed = math.ceil(float(pwm * 100 / EMC2305_MAX_PWM)) speed = math.ceil(float(pwm * 100 / EMC2305_MAX_PWM))
@ -163,7 +149,7 @@ class Fan(FanBase):
sysfs_path = "%s%s/%s" % ( sysfs_path = "%s%s/%s" % (
EMC2305_PATH, device, EMC2305_FAN_TARGET) EMC2305_PATH, device, EMC2305_FAN_TARGET)
sysfs_path = sysfs_path.format(fan_index[self.fan_tray_index]) sysfs_path = sysfs_path.format(fan_index[self.fan_tray_index])
raw = self.__read_txt_file(sysfs_path).strip('\r\n') raw = self._api_common.read_txt_file(sysfs_path).strip('\r\n')
pwm = int(raw, 10) if raw else 0 pwm = int(raw, 10) if raw else 0
target = math.ceil(float(pwm) * 100 / EMC2305_MAX_PWM) target = math.ceil(float(pwm) * 100 / EMC2305_MAX_PWM)
@ -202,7 +188,7 @@ class Fan(FanBase):
sysfs_path = "%s%s/%s" % ( sysfs_path = "%s%s/%s" % (
EMC2305_PATH, device, EMC2305_FAN_PWM) EMC2305_PATH, device, EMC2305_FAN_PWM)
sysfs_path = sysfs_path.format(fan_index[self.fan_tray_index]) sysfs_path = sysfs_path.format(fan_index[self.fan_tray_index])
return self.__write_txt_file(sysfs_path, int(pwm)) return self._api_common.write_txt_file(sysfs_path, int(pwm))
return False return False
@ -220,7 +206,7 @@ class Fan(FanBase):
fan_led_file = (FAN_PATH + fan_led_file = (FAN_PATH +
self.fan_e1031_led.format(self.fan_tray_index+1)) self.fan_e1031_led.format(self.fan_tray_index+1))
set_status_led = self.__write_txt_file( set_status_led = self._api_common.write_txt_file(
fan_led_file, self.fan_e1031_led_col_map[color]) if self.get_presence() else False fan_led_file, self.fan_e1031_led_col_map[color]) if self.get_presence() else False
return set_status_led return set_status_led
@ -244,14 +230,55 @@ class Fan(FanBase):
""" """
fan_direction_file = (FAN_PATH + fan_direction_file = (FAN_PATH +
self.fan_e1031_presence.format(self.fan_tray_index+1)) self.fan_e1031_presence.format(self.fan_tray_index+1))
present_str = self.__read_txt_file(fan_direction_file) or '1' present_str = self._api_common.read_txt_file(fan_direction_file) or '1'
return int(present_str) == 0 if not self.is_psu_fan else True return int(present_str) == 0 if not self.is_psu_fan else True
def get_model(self):
"""
Retrieves the model number (or part number) of the device
Returns:
string: Model/part number of device
"""
if self.is_psu_fan:
return NULL_VAL
model = NULL_VAL
return model
def get_serial(self):
"""
Retrieves the serial number of the device
Returns:
string: Serial number of device
"""
if self.is_psu_fan:
return NULL_VAL
serial = NULL_VAL
return serial
def get_status(self): def get_status(self):
""" """
Retrieves the operational status of the device Retrieves the operational status of the device
Returns: Returns:
A boolean value, True if device is operating properly, False if not A boolean value, True if device is operating properly, False if not
""" """
return self.get_presence() and self.get_speed() > 0 status = 1
if self.is_psu_fan:
fan_fault_sysfs_name = "fan1_fault"
fan_fault_sysfs_path = self.__search_file_by_name(
self.psu_hwmon_path, fan_fault_sysfs_name)
status = self._api_common.read_txt_file(fan_fault_sysfs_path)
elif self.get_presence():
chip = self.emc2305_chip_mapping[self.fan_index]
device = chip['device']
fan_index = chip['index_map']
sysfs_path = "%s%s/%s" % (
EMC2305_PATH, device, 'fan{}_fault')
sysfs_path = sysfs_path.format(fan_index[self.fan_tray_index])
status = self._api_common.read_txt_file(sysfs_path)
return False if int(status) != 0 else True

View File

@ -7,7 +7,6 @@
############################################################################# #############################################################################
import os.path import os.path
import sonic_platform
try: try:
from sonic_platform_base.psu_base import PsuBase from sonic_platform_base.psu_base import PsuBase

View File

@ -8,7 +8,6 @@
import os import os
import time import time
import subprocess
from ctypes import create_string_buffer from ctypes import create_string_buffer
try: try:
@ -159,7 +158,7 @@ class Sfp(SfpBase):
raw = sysfsfile_eeprom.read(num_bytes) raw = sysfsfile_eeprom.read(num_bytes)
for n in range(0, num_bytes): for n in range(0, num_bytes):
eeprom_raw[n] = hex(ord(raw[n]))[2:].zfill(2) eeprom_raw[n] = hex(ord(raw[n]))[2:].zfill(2)
except: except Exception:
pass pass
finally: finally:
if sysfsfile_eeprom: if sysfsfile_eeprom:
@ -469,7 +468,6 @@ class Sfp(SfpBase):
A Boolean, True if tx_disable is enabled, False if disabled A Boolean, True if tx_disable is enabled, False if disabled
""" """
tx_disable = False tx_disable = False
tx_fault = False
status_control_raw = self.__read_eeprom_specific_bytes( status_control_raw = self.__read_eeprom_specific_bytes(
SFP_STATUS_CONTROL_OFFSET, SFP_STATUS_CONTROL_WIDTH) SFP_STATUS_CONTROL_OFFSET, SFP_STATUS_CONTROL_WIDTH)
if status_control_raw: if status_control_raw:
@ -602,7 +600,7 @@ class Sfp(SfpBase):
# Write to eeprom # Write to eeprom
sysfsfile_eeprom.seek(SFP_STATUS_CONTROL_OFFSET) sysfsfile_eeprom.seek(SFP_STATUS_CONTROL_OFFSET)
sysfsfile_eeprom.write(buffer[0]) sysfsfile_eeprom.write(buffer[0])
except: except Exception:
#print("Error: unable to open file: %s" % str(e)) #print("Error: unable to open file: %s" % str(e))
return False return False
finally: finally:

View File

@ -6,16 +6,90 @@
# #
############################################################################# #############################################################################
import os
import re
import os.path
try: try:
import os
from sonic_platform_base.thermal_base import ThermalBase from sonic_platform_base.thermal_base import ThermalBase
from .common import Common
except ImportError as e: except ImportError as e:
raise ImportError(str(e) + "- required module not found") raise ImportError(str(e) + "- required module not found")
THERMAL_INFO = {
0: {
"F2B_max": 55.000,
"F2B_max_crit": 58.000,
"B2F_max": 55.000,
"B2F_max_crit": 58.000,
"postion": "asic",
"name": "Inlet ambient sensor (Rear to Front)",
"ss_index": 1,
"i2c_path": "i2c-11/11-001a/hwmon", # U7273
},
1: {
"F2B_max": 85.000,
"F2B_max_crit": 90.000,
"B2F_max": 85.000,
"B2F_max_crit": 90.000,
"postion": "asic",
"name": "Helix shutdown sensor (Rear to Front)",
"ss_index": 2,
"i2c_path": "i2c-11/11-001a/hwmon", # Q7000
},
2: {
"F2B_max": 55.000,
"F2B_max_crit": 58.000,
"B2F_max": 55.000,
"B2F_max_crit": 58.000,
"postion": "asic",
"name": "Inlet ambient sensor (Front to Rear, right)",
"ss_index": 3,
"i2c_path": "i2c-11/11-001a/hwmon", # Q7001
},
3: {
"F2B_max": 85.000,
"F2B_max_crit": 90.000,
"B2F_max": 85.000,
"B2F_max_crit": 90.000,
"postion": "asic",
"name": "Helix shutdown sensor (Front to Rear)",
"ss_index": 4,
"i2c_path": "i2c-11/11-001a/hwmon", # Q7751
},
4: {
"F2B_max": 55.000,
"F2B_max_crit": 58.000,
"B2F_max": 55.000,
"B2F_max_crit": 58.000,
"postion": "cpu",
"name": "Inlet ambient sensor (Front to Rear, left)",
"ss_index": 5,
"i2c_path": "i2c-11/11-001a/hwmon" # Q7752
},
5: {
"F2B_max": 90.000,
"F2B_max_crit": 95.000,
"B2F_max": 90.000,
"B2F_max_crit": 95.000,
"postion": "cpu",
"name": "CPU errata sensor (Front to Rear)",
"ss_index": 2,
"i2c_path": "i2c-3/3-001a/hwmon" # Q5
},
6: {
"F2B_max": 90.000,
"F2B_max_crit": 95.000,
"B2F_max": 90.000,
"B2F_max_crit": 95.000,
"postion": "cpu",
"name": "CPU errata sensor (Rear to Front)",
"ss_index": 3,
"i2c_path": "i2c-3/3-001a/hwmon" # Q6
}
}
NULL_VAL = "N/A"
I2C_ADAPTER_PATH = "/sys/class/i2c-adapter"
class Thermal(ThermalBase): class Thermal(ThermalBase):
"""Platform-specific Thermal class""" """Platform-specific Thermal class"""
@ -24,57 +98,33 @@ class Thermal(ThermalBase):
CPUBOARD_SS_PATH = "/sys/class/i2c-adapter/i2c-3/3-001a/hwmon/hwmon1" CPUBOARD_SS_PATH = "/sys/class/i2c-adapter/i2c-3/3-001a/hwmon/hwmon1"
SS_CONFIG_PATH = "/usr/share/sonic/device/x86_64-cel_e1031-r0/sensors.conf" SS_CONFIG_PATH = "/usr/share/sonic/device/x86_64-cel_e1031-r0/sensors.conf"
def __init__(self, thermal_index): def __init__(self, thermal_index, airflow):
ThermalBase.__init__(self) ThermalBase.__init__(self)
self.index = thermal_index self.index = thermal_index
self._api_common = Common()
# Add thermal name self._airflow = airflow
self.THERMAL_NAME_LIST.append("Rear panel-Inlet ambient sensor") self._thermal_info = THERMAL_INFO[self.index]
self.THERMAL_NAME_LIST.append("Rear panel-Helix shutdown sensor") self._hwmon_path = self._get_hwmon_path()
self.THERMAL_NAME_LIST.append( self._ss_index = self._thermal_info["ss_index"]
"Front panel-Inlet ambient sensor (right)")
self.THERMAL_NAME_LIST.append("Front panel-Helix shutdown sensor")
self.THERMAL_NAME_LIST.append(
"Front panel-Inlet ambient sensor (left)")
self.THERMAL_NAME_LIST.append("CPU board temperature sensor : 1")
self.THERMAL_NAME_LIST.append("CPU board temperature sensor : 2")
# Set hwmon path self.name = self.get_name()
self.ss_index, self.hwmon_path = self.__get_ss_info(self.index) self.postion = self._thermal_info["postion"]
self.ss_key = self.THERMAL_NAME_LIST[self.index]
def __get_ss_info(self, index): def _get_hwmon_path(self):
if self.index <= 4: hwmon_path = os.path.join(I2C_ADAPTER_PATH, self._thermal_info["i2c_path"])
ss_path = self.MAINBOARD_SS_PATH hwmon_dir = os.listdir(hwmon_path)[0]
ss_index = index+1 return os.path.join(hwmon_path, hwmon_dir)
else:
ss_path = self.CPUBOARD_SS_PATH
ss_index = index-3
return ss_index, ss_path
def __read_txt_file(self, file_path): def _get_temp(self, temp_file):
try: temp_file_path = os.path.join(self._hwmon_path, temp_file)
with open(file_path, 'r') as fd: raw_temp = self._api_common.read_txt_file(temp_file_path)
data = fd.read()
return data.strip()
except IOError:
raise IOError("Unable to open %s file !" % file_path)
def __get_temp(self, temp_file):
temp_file_path = os.path.join(self.hwmon_path, temp_file)
raw_temp = self.__read_txt_file(temp_file_path)
temp = float(raw_temp)/1000 temp = float(raw_temp)/1000
return "{:.3f}".format(temp) return float("{:.3f}".format(temp))
def __set_threshold(self, file_name, temperature): def _set_threshold(self, file_name, temperature):
temp_file_path = os.path.join(self.hwmon_path, file_name) temp_file_path = os.path.join(self._hwmon_path, file_name)
try: return self._api_common.write_txt_file(temp_file_path, str(temperature))
with open(temp_file_path, 'w') as fd:
fd.write(str(temperature))
return True
except IOError:
return False
def get_temperature(self): def get_temperature(self):
""" """
@ -83,8 +133,8 @@ class Thermal(ThermalBase):
A float number of current temperature in Celsius up to nearest thousandth A float number of current temperature in Celsius up to nearest thousandth
of one degree Celsius, e.g. 30.125 of one degree Celsius, e.g. 30.125
""" """
temp_file = "temp{}_input".format(self.ss_index) temp_file = "temp{}_input".format(self._ss_index)
return self.__get_temp(temp_file) return self._get_temp(temp_file)
def get_high_threshold(self): def get_high_threshold(self):
""" """
@ -93,8 +143,17 @@ class Thermal(ThermalBase):
A float number, the high threshold temperature of thermal in Celsius A float number, the high threshold temperature of thermal in Celsius
up to nearest thousandth of one degree Celsius, e.g. 30.125 up to nearest thousandth of one degree Celsius, e.g. 30.125
""" """
temp_file = "temp{}_max".format(self.ss_index) max_crit_key = '{}_max'.format(self._airflow)
return self.__get_temp(temp_file) return self._thermal_info.get(max_crit_key, None)
def get_low_threshold(self):
"""
Retrieves the low threshold temperature of thermal
Returns:
A float number, the low threshold temperature of thermal in Celsius
up to nearest thousandth of one degree Celsius, e.g. 30.125
"""
return 0.0
def set_high_threshold(self, temperature): def set_high_threshold(self, temperature):
""" """
@ -105,8 +164,8 @@ class Thermal(ThermalBase):
Returns: Returns:
A boolean, True if threshold is set successfully, False if not A boolean, True if threshold is set successfully, False if not
""" """
temp_file = "temp{}_max".format(self.ss_index) temp_file = "temp{}_max".format(self._ss_index)
is_set = self.__set_threshold(temp_file, int(temperature*1000)) is_set = self._set_threshold(temp_file, int(temperature*1000))
file_set = False file_set = False
if is_set: if is_set:
try: try:
@ -115,7 +174,7 @@ class Thermal(ThermalBase):
f.seek(0) f.seek(0)
ss_found = False ss_found = False
for idx, val in enumerate(content): for idx, val in enumerate(content):
if self.ss_key in val: if self.name in val:
ss_found = True ss_found = True
elif ss_found and temp_file in val: elif ss_found and temp_file in val:
content[idx] = " set {} {}\n".format( content[idx] = " set {} {}\n".format(
@ -128,13 +187,43 @@ class Thermal(ThermalBase):
return is_set & file_set return is_set & file_set
def set_low_threshold(self, temperature):
"""
Sets the low threshold temperature of thermal
Args :
temperature: A float number up to nearest thousandth of one degree Celsius,
e.g. 30.125
Returns:
A boolean, True if threshold is set successfully, False if not
"""
return False
def get_high_critical_threshold(self):
"""
Retrieves the high critical threshold temperature of thermal
Returns:
A float number, the high critical threshold temperature of thermal in Celsius
up to nearest thousandth of one degree Celsius, e.g. 30.125
"""
max_crit_key = '{}_max_crit'.format(self._airflow)
return self._thermal_info.get(max_crit_key, None)
def get_low_critical_threshold(self):
"""
Retrieves the low critical threshold temperature of thermal
Returns:
A float number, the low critical threshold temperature of thermal in Celsius
up to nearest thousandth of one degree Celsius, e.g. 30.125
"""
return 0.0
def get_name(self): def get_name(self):
""" """
Retrieves the name of the thermal device Retrieves the name of the thermal device
Returns: Returns:
string: The name of the thermal device string: The name of the thermal device
""" """
return self.THERMAL_NAME_LIST[self.index] return self._thermal_info["name"]
def get_presence(self): def get_presence(self):
""" """
@ -142,10 +231,26 @@ class Thermal(ThermalBase):
Returns: Returns:
bool: True if PSU is present, False if not bool: True if PSU is present, False if not
""" """
temp_file = "temp{}_input".format(self.ss_index) temp_file = "temp{}_input".format(self._ss_index)
temp_file_path = os.path.join(self.hwmon_path, temp_file) temp_file_path = os.path.join(self._hwmon_path, temp_file)
return os.path.isfile(temp_file_path) return os.path.isfile(temp_file_path)
def get_model(self):
"""
Retrieves the model number (or part number) of the device
Returns:
string: Model/part number of device
"""
return NULL_VAL
def get_serial(self):
"""
Retrieves the serial number of the device
Returns:
string: Serial number of device
"""
return NULL_VAL
def get_status(self): def get_status(self):
""" """
Retrieves the operational status of the device Retrieves the operational status of the device
@ -155,10 +260,10 @@ class Thermal(ThermalBase):
if not self.get_presence(): if not self.get_presence():
return False return False
fault_file = "temp{}_fault".format(self.ss_index) fault_file = "temp{}_fault".format(self._ss_index)
fault_file_path = os.path.join(self.hwmon_path, fault_file) fault_file_path = os.path.join(self._hwmon_path, fault_file)
if not os.path.isfile(fault_file_path): if not os.path.isfile(fault_file_path):
return True return True
raw_txt = self.__read_txt_file(fault_file_path) raw_txt = self._api_common.read_txt_file(fault_file_path)
return int(raw_txt) == 0 return int(raw_txt) == 0

View File

@ -0,0 +1,78 @@
from sonic_platform_base.sonic_thermal_control.thermal_action_base import ThermalPolicyActionBase
from sonic_platform_base.sonic_thermal_control.thermal_json_object import thermal_json_object
from .thermal_infos import ChassisInfo
from .common import Common
@thermal_json_object('thermal_control.control')
class ControlThermalAlgoAction(ThermalPolicyActionBase):
"""
Action to control the thermal control algorithm
"""
# JSON field definition
JSON_FIELD_STATUS = 'status'
def __init__(self):
self.status = True
def load_from_json(self, json_obj):
"""
Construct ControlThermalAlgoAction via JSON. JSON example:
{
"type": "thermal_control.control"
"status": "true"
}
:param json_obj: A JSON object representing a ControlThermalAlgoAction action.
:return:
"""
if ControlThermalAlgoAction.JSON_FIELD_STATUS in json_obj:
status_str = json_obj[ControlThermalAlgoAction.JSON_FIELD_STATUS].lower(
)
if status_str == 'true':
self.status = True
elif status_str == 'false':
self.status = False
else:
raise ValueError('Invalid {} field value, please specify true of false'.
format(ControlThermalAlgoAction.JSON_FIELD_STATUS))
else:
raise ValueError('ControlThermalAlgoAction '
'missing mandatory field {} in JSON policy file'.
format(ControlThermalAlgoAction.JSON_FIELD_STATUS))
def execute(self, thermal_info_dict):
"""
Disable thermal control algorithm
:param thermal_info_dict: A dictionary stores all thermal information.
:return:
"""
if ChassisInfo.INFO_NAME in thermal_info_dict:
chassis_info_obj = thermal_info_dict[ChassisInfo.INFO_NAME]
chassis = chassis_info_obj.get_chassis()
thermal_manager = chassis.get_thermal_manager()
if self.status:
thermal_manager.start_thermal_control_algorithm()
else:
thermal_manager.stop_thermal_control_algorithm()
@thermal_json_object('switch.power_cycling')
class SwitchPolicyAction(ThermalPolicyActionBase):
"""
Base class for thermal action. Once all thermal conditions in a thermal policy are matched,
all predefined thermal action will be executed.
"""
def execute(self, thermal_info_dict):
"""
Take action when thermal condition matches. For example, power cycle the switch.
:param thermal_info_dict: A dictionary stores all thermal information.
:return:
"""
thermal_overload_position_path = '/tmp/thermal_overload_position'
thermal_overload_position = Common().read_txt_file(
thermal_overload_position_path)
cmd = 'bash /usr/share/sonic/platform/thermal_overload_control.sh {}'.format(
thermal_overload_position)
Common().run_command(cmd)

View File

@ -0,0 +1,78 @@
from sonic_platform_base.sonic_thermal_control.thermal_condition_base import ThermalPolicyConditionBase
from sonic_platform_base.sonic_thermal_control.thermal_json_object import thermal_json_object
class FanCondition(ThermalPolicyConditionBase):
def get_fan_info(self, thermal_info_dict):
from .thermal_infos import FanInfo
if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo):
return thermal_info_dict[FanInfo.INFO_NAME]
else:
return None
@thermal_json_object('fan.any.absence')
class AnyFanAbsenceCondition(FanCondition):
def is_match(self, thermal_info_dict):
fan_info_obj = self.get_fan_info(thermal_info_dict)
return len(fan_info_obj.get_absence_fans()) > 0 if fan_info_obj else False
@thermal_json_object('fan.any.fault')
class AnyFanFaultCondition(FanCondition):
def is_match(self, thermal_info_dict):
fan_info_obj = self.get_fan_info(thermal_info_dict)
return len(fan_info_obj.get_fault_fans()) > 0 if fan_info_obj else False
@thermal_json_object('fan.all.presence')
class AllFanPresenceCondition(FanCondition):
def is_match(self, thermal_info_dict):
fan_info_obj = self.get_fan_info(thermal_info_dict)
return len(fan_info_obj.get_absence_fans()) == 0 if fan_info_obj else False
@thermal_json_object('fan.all.good')
class AllFanGoodCondition(FanCondition):
def is_match(self, thermal_info_dict):
fan_info_obj = self.get_fan_info(thermal_info_dict)
return len(fan_info_obj.get_fault_fans()) == 0 if fan_info_obj else False
class ThermalCondition(ThermalPolicyConditionBase):
def get_thermal_info(self, thermal_info_dict):
from .thermal_infos import ThermalInfo
if ThermalInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[ThermalInfo.INFO_NAME], ThermalInfo):
return thermal_info_dict[ThermalInfo.INFO_NAME]
else:
return None
@thermal_json_object('thermal.over.high_threshold')
class ThermalOverHighCriticalCondition(ThermalCondition):
def is_match(self, thermal_info_dict):
thermal_info_obj = self.get_thermal_info(thermal_info_dict)
if thermal_info_obj:
return thermal_info_obj.is_over_high_threshold()
else:
return False
@thermal_json_object('thermal.over.high_critical_threshold')
class ThermalOverHighCriticalCondition(ThermalCondition):
def is_match(self, thermal_info_dict):
thermal_info_obj = self.get_thermal_info(thermal_info_dict)
if thermal_info_obj:
return thermal_info_obj.is_over_high_critical_threshold()
else:
return False
@thermal_json_object('thermal.all.good')
class ThermalGoodCondition(ThermalCondition):
def is_match(self, thermal_info_dict):
thermal_info_obj = self.get_thermal_info(thermal_info_dict)
if thermal_info_obj:
return not thermal_info_obj.is_over_threshold()
else:
return False

View File

@ -0,0 +1,165 @@
from sonic_platform_base.sonic_thermal_control.thermal_info_base import ThermalPolicyInfoBase
from sonic_platform_base.sonic_thermal_control.thermal_json_object import thermal_json_object
from .common import Common
import time
@thermal_json_object('fan_info')
class FanInfo(ThermalPolicyInfoBase):
"""
Fan information needed by thermal policy
"""
# Fan information name
INFO_NAME = 'fan_info'
def __init__(self):
self._absence_fans = set()
self._presence_fans = set()
self._fault_fans = set()
self._status_changed = False
def collect(self, chassis):
"""
Collect absence and presence fans.
:param chassis: The chassis object
:return:
"""
self._status_changed = False
for fan in chassis.get_all_fans():
presence = fan.get_presence()
status = fan.get_status()
if presence and fan not in self._presence_fans:
self._presence_fans.add(fan)
self._status_changed = True
if fan in self._absence_fans:
self._absence_fans.remove(fan)
elif not presence and fan not in self._absence_fans:
self._absence_fans.add(fan)
self._status_changed = True
if fan in self._presence_fans:
self._presence_fans.remove(fan)
if not status and fan not in self._fault_fans:
self._fault_fans.add(fan)
self._status_changed = True
elif status and fan in self._fault_fans:
self._fault_fans.remove(fan)
self._status_changed = True
def get_absence_fans(self):
"""
Retrieves absence fans
:return: A set of absence fans
"""
return self._absence_fans
def get_presence_fans(self):
"""
Retrieves presence fans
:return: A set of presence fans
"""
return self._presence_fans
def get_fault_fans(self):
"""
Retrieves fault fans
:return: A set of fault fans
"""
return self._fault_fans
def is_status_changed(self):
"""
Retrieves if the status of fan information changed
:return: True if status changed else False
"""
return self._status_changed
@thermal_json_object('thermal_info')
class ThermalInfo(ThermalPolicyInfoBase):
"""
Thermal information needed by thermal policy
"""
# Fan information name
INFO_NAME = 'thermal_info'
def collect(self, chassis):
"""
Collect thermal sensor temperature change status
:param chassis: The chassis object
:return:
"""
self._over_high_threshold = False
self._over_high_critical_threshold = False
self._thermal_overload_position = 'cpu'
# Calculate average temp within the device
temp = 0
num_of_thermals = chassis.get_num_thermals()
for index in range(num_of_thermals):
thermal = chassis.get_thermal(index)
temp = thermal.get_temperature()
high_threshold = thermal.get_high_threshold()
high_critical_threshold = thermal.get_high_critical_threshold()
if high_threshold and temp > high_threshold:
self._over_high_threshold = True
if high_critical_threshold and temp > high_critical_threshold:
self._thermal_overload_position = thermal.postion
self._over_high_critical_threshold = True
def is_over_threshold(self):
"""
Retrieves if the temperature is over any threshold
:return: True if the temperature is over any threshold else False
"""
return self._over_high_threshold or self._over_high_critical_threshold
def is_over_high_critical_threshold(self):
"""
Retrieves if the temperature is over high critical threshold
:return: True if the temperature is over high critical threshold else False
"""
thermal_overload_position_path = '/tmp/thermal_overload_position'
if self._over_high_critical_threshold:
Common().write_txt_file(thermal_overload_position_path,
self._thermal_overload_position)
time.sleep(1)
return self._over_high_critical_threshold
def is_over_high_threshold(self):
"""
Retrieves if the temperature is over high threshold
:return: True if the temperature is over high threshold else False
"""
return self._over_high_threshold
@thermal_json_object('chassis_info')
class ChassisInfo(ThermalPolicyInfoBase):
"""
Chassis information needed by thermal policy
"""
INFO_NAME = 'chassis_info'
def __init__(self):
self._chassis = None
def collect(self, chassis):
"""
Collect platform chassis.
:param chassis: The chassis object
:return:
"""
self._chassis = chassis
def get_chassis(self):
"""
Retrieves platform chassis object
:return: A platform chassis object.
"""
return self._chassis

View File

@ -0,0 +1,47 @@
from sonic_platform_base.sonic_thermal_control.thermal_manager_base import ThermalManagerBase
from .common import Common
from .thermal_actions import *
from .thermal_conditions import *
from .thermal_infos import *
class ThermalManager(ThermalManagerBase):
FSC_ALGORITHM_CMD = ' supervisorctl {} fancontrol'
@classmethod
def start_thermal_control_algorithm(cls):
"""
Start vendor specific thermal control algorithm. The default behavior of this function is a no-op.
:return:
"""
return cls._enable_fancontrol_service(True)
@classmethod
def stop_thermal_control_algorithm(cls):
"""
Stop thermal control algorithm
Returns:
bool: True if set success, False if fail.
"""
return cls._enable_fancontrol_service(False)
@classmethod
def deinitialize(cls):
"""
Destroy thermal manager, including any vendor specific cleanup. The default behavior of this function
is a no-op.
:return:
"""
return cls._enable_fancontrol_service(True)
@classmethod
def _enable_fancontrol_service(cls, enable):
"""
Control thermal by fcs algorithm
Args:
enable: Bool, indicate enable the algorithm or not
Returns:
bool: True if set success, False if fail.
"""
cmd = 'start' if enable else 'stop'
return Common().run_command(cls.FSC_ALGORITHM_CMD.format(cmd))

View File

@ -4,11 +4,8 @@
# Watchdog contains an implementation of SONiC Platform Base API # Watchdog contains an implementation of SONiC Platform Base API
# #
############################################################################# #############################################################################
import ctypes
import fcntl import fcntl
import os import os
import subprocess
import time
import array import array
try: try:

View File

@ -0,0 +1,75 @@
#!/bin/bash
#
# Copyright 2020-present Celestica. All Rights Reserved.
#
# This program file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
#
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
SETREG_FILE=/sys/devices/platform/e1031.smc/setreg
TOVERREG=0x115
CPUOVER=0xa1
ASICOVER=0xa2
prog="$0"
command="$1"
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
usage() {
echo "Usage: thermal_overload_control.sh [option] <command>"
echo
echo "Options:"
echo " -h, --help : to print this message."
echo
echo "Commands:"
echo
echo " cpu: To enabling CPU thermal overload handler"
echo
echo " asic : To enabling ASIC thermal overload handler"
echo
}
cpu_overload() {
logger "Enable CPU thermal overload control"
set_reg=`echo ${TOVERREG} ${CPUOVER} > ${SETREG_FILE}`
}
asic_overload() {
logger "Enable ASIC thermal overload control"
set_reg=`echo ${TOVERREG} ${ASICOVER} > ${SETREG_FILE}`
}
if [ $# -lt 1 ]; then
usage
exit -1
fi
case "$command" in
-h | --help)
usage
;;
cpu)
cpu_overload
;;
asic)
asic_overload
;;
*)
usage
exit -1
;;
esac
exit $?

View File

@ -0,0 +1,93 @@
{
"thermal_control_algorithm": {
"run_at_boot_up": "true"
},
"info_types": [
{
"type": "chassis_info"
},
{
"type": "fan_info"
},
{
"type": "thermal_info"
}
],
"policies": [
{
"name": "any fan absence",
"conditions": [
{
"type": "fan.any.absence"
}
],
"actions": [
{
"type": "thermal_control.control",
"status": "false"
}
]
},
{
"name": "any fan broken",
"conditions": [
{
"type": "fan.any.fault"
}
],
"actions": [
{
"type": "thermal_control.control",
"status": "false"
}
]
},
{
"name": "any thermal over threshold",
"conditions": [
{
"type": "thermal.over.high_threshold"
}
],
"actions": [
{
"type": "thermal_control.control",
"status": "false"
}
]
},
{
"name": "temp over high critical threshold",
"conditions": [
{
"type": "thermal.over.high_critical_threshold"
}
],
"actions": [
{
"type": "switch.power_cycling"
}
]
},
{
"name": "all fan presence / thermal no warning",
"conditions": [
{
"type": "fan.all.presence"
},
{
"type": "fan.all.good"
},
{
"type": "thermal.all.good"
}
],
"actions": [
{
"type": "thermal_control.control",
"status": "true"
}
]
}
]
}

View File

@ -53,9 +53,12 @@ start)
echo max6699 0x1a > /sys/bus/i2c/devices/i2c-3/new_device echo max6699 0x1a > /sys/bus/i2c/devices/i2c-3/new_device
echo max6699 0x1a > /sys/bus/i2c/devices/i2c-11/new_device echo max6699 0x1a > /sys/bus/i2c/devices/i2c-11/new_device
## TODO: fix dps200 driver cause kernel panic issue
# https://github.com/Azure/sonic-buildimage/issues/6602
# Attach PSUs # Attach PSUs
echo dps200 0x5a > /sys/bus/i2c/devices/i2c-12/new_device # echo dps200 0x5a > /sys/bus/i2c/devices/i2c-12/new_device
echo dps200 0x5b > /sys/bus/i2c/devices/i2c-13/new_device # echo dps200 0x5b > /sys/bus/i2c/devices/i2c-13/new_device
# Attach fans # Attach fans
echo emc2305 0x4d > /sys/bus/i2c/devices/i2c-23/new_device echo emc2305 0x4d > /sys/bus/i2c/devices/i2c-23/new_device

View File

@ -1,12 +1,15 @@
depmod -a depmod -a
sudo chmod +x /usr/local/bin/udev_prefix.sh sudo chmod +x /usr/local/bin/udev_prefix.sh
sudo chmod +x /usr/local/bin/popmsg.sh sudo chmod +x /usr/local/bin/popmsg.sh
sudo chmod +x /usr/local/bin/reload_udev.sh sudo chmod +x /usr/local/bin/reload_udev.sh
/usr/local/bin/platform_api_mgnt.sh install
/etc/init.d/fancontrol.sh install
/usr/local/bin/reload_udev.sh
systemctl enable platform-modules-haliburton.service systemctl enable platform-modules-haliburton.service
systemctl enable fancontrol.service systemctl enable fancontrol.service
systemctl start platform-modules-haliburton.service systemctl start platform-modules-haliburton.service
systemctl start fancontrol.service systemctl start fancontrol.service
/usr/local/bin/platform_api_mgnt.sh install
/usr/local/bin/reload_udev.sh

View File

@ -7,70 +7,35 @@
# Default-Start: 2 3 4 5 # Default-Start: 2 3 4 5
# Default-Stop: # Default-Stop:
# Short-Description: fancontrol # Short-Description: fancontrol
# Description: fan speed regulator # Description: fancontrol configuration selector
### END INIT INFO ### END INIT INFO
. /lib/lsb/init-functions . /lib/lsb/init-functions
[ -f /etc/default/rcS ] && . /etc/default/rcS [ -f /etc/default/rcS ] && . /etc/default/rcS
PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin
DAEMON=/usr/local/bin/fancontrol
DESC="fan speed regulator"
NAME="fancontrol"
PIDFILE=/var/run/fancontrol.pid
PLATFORMPATH=/sys/devices/platform/e1031.smc
MAIN_CONF=/usr/share/sonic/device/x86_64-cel_e1031-r0/fancontrol MAIN_CONF=/usr/share/sonic/device/x86_64-cel_e1031-r0/fancontrol
DEVPATH=/sys/devices/pci0000:00/0000:00:13.0/i2c-0/i2c-8/i2c-23/23-004d PLATFORM_PATH=/sys/devices/platform/e1031.smc
test -x $DAEMON || exit 0 init() {
FANDIR=$(cat ${PLATFORM_PATH}/fan1_dir)
for i in 1 2 3
do
j=$i
[ $i -eq 3 ] && j=4
FANFAULT=$(cat ${DEVPATH}/fan${j}_fault)
[ $FANFAULT = 1 ] && continue
FANDIR=$(cat ${PLATFORMPATH}/fan${i}_dir)
done
CONF=${MAIN_CONF}-${FANDIR} CONF=${MAIN_CONF}-${FANDIR}
echo $FANDIR > /usr/share/sonic/device/x86_64-cel_e1031-r0/fan_airflow
}
install() {
find /var/lib/docker/overlay*/ -path */sbin/fancontrol -exec cp /usr/local/bin/fancontrol {} \;
}
case "$1" in case "$1" in
start) start)
if [ -f $CONF ] ; then init
if $DAEMON --check $CONF 1>/dev/null 2>/dev/null ; then cp $CONF $MAIN_CONF
log_daemon_msg "Starting $DESC" "$NAME\n"
start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON $CONF
log_end_msg $?
else
log_failure_msg "Not starting fancontrol, broken configuration file; please re-run pwmconfig."
fi
else
if [ "$VERBOSE" != no ]; then
log_warning_msg "Not starting fancontrol; run pwmconfig first."
fi
fi
;; ;;
stop) install)
log_daemon_msg "Stopping $DESC" "$NAME" install
start-stop-daemon --stop --quiet --pidfile $PIDFILE --oknodo --startas $DAEMON $CONF
rm -f $PIDFILE
log_end_msg $?
;;
restart)
$0 stop
sleep 3
$0 start
;;
force-reload)
if start-stop-daemon --stop --test --quiet --pidfile $PIDFILE --startas $DAEMON $CONF ; then
$0 restart
fi
;;
status)
status_of_proc $DAEMON $NAME $CONF && exit 0 || exit $?
;; ;;
*) *)
log_success_msg "Usage: /etc/init.d/fancontrol {start|stop|restart|force-reload|status}" log_success_msg "Usage: /etc/init.d/fancontrol {start} | {install}"
exit 1 exit 1
;; ;;
esac esac