diff --git a/device/mellanox/x86_64-mlnx_msn2010-r0/plugins/fanutil.py b/device/mellanox/x86_64-mlnx_msn2010-r0/plugins/fanutil.py new file mode 120000 index 00000000000..82ea06ef915 --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2010-r0/plugins/fanutil.py @@ -0,0 +1 @@ +../../x86_64-mlnx_msn2700-r0/plugins/fanutil.py \ No newline at end of file diff --git a/device/mellanox/x86_64-mlnx_msn2010-r0/plugins/thermalutil.py b/device/mellanox/x86_64-mlnx_msn2010-r0/plugins/thermalutil.py new file mode 120000 index 00000000000..cef21ffaccc --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2010-r0/plugins/thermalutil.py @@ -0,0 +1 @@ +../../x86_64-mlnx_msn2700-r0/plugins/thermalutil.py \ No newline at end of file diff --git a/device/mellanox/x86_64-mlnx_msn2100-r0/plugins/fanutil.py b/device/mellanox/x86_64-mlnx_msn2100-r0/plugins/fanutil.py new file mode 120000 index 00000000000..82ea06ef915 --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2100-r0/plugins/fanutil.py @@ -0,0 +1 @@ +../../x86_64-mlnx_msn2700-r0/plugins/fanutil.py \ No newline at end of file diff --git a/device/mellanox/x86_64-mlnx_msn2100-r0/plugins/thermalutil.py b/device/mellanox/x86_64-mlnx_msn2100-r0/plugins/thermalutil.py new file mode 120000 index 00000000000..cef21ffaccc --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2100-r0/plugins/thermalutil.py @@ -0,0 +1 @@ +../../x86_64-mlnx_msn2700-r0/plugins/thermalutil.py \ No newline at end of file diff --git a/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/fanutil.py b/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/fanutil.py new file mode 120000 index 00000000000..82ea06ef915 --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/fanutil.py @@ -0,0 +1 @@ +../../x86_64-mlnx_msn2700-r0/plugins/fanutil.py \ No newline at end of file diff --git a/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/psuutil.py b/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/psuutil.py deleted file mode 100644 index 954045dde59..00000000000 --- a/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/psuutil.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python - -############################################################################# -# Mellanox -# -# Module contains an implementation of SONiC PSU Base API and -# provides the PSUs status which are available in the platform -# -############################################################################# - -import os.path - -try: - from sonic_psu.psu_base import PsuBase -except ImportError as e: - raise ImportError (str(e) + "- required module not found") - -class PsuUtil(PsuBase): - """Platform-specific PSUutil class""" - - def __init__(self): - PsuBase.__init__(self) - - self.psu_path = "" - for index in range(0, 100): - hwmon_path = "/sys/devices/platform/mlxplat/mlxreg-hotplug/hwmon/hwmon{}/".format(index) - if os.path.exists(hwmon_path): - self.psu_path = hwmon_path - break - self.psu_presence = "psu{}" - self.psu_oper_status = "pwr{}" - - def get_num_psus(self): - """ - Retrieves the number of PSUs available on the device - - :return: An integer, the number of PSUs available on the device - """ - return 2 - - def get_psu_status(self, index): - """ - Retrieves the oprational status of power supply unit (PSU) defined - by 1-based index - - :param index: An integer, 1-based index of the PSU of which to query status - :return: Boolean, True if PSU is operating properly, False if PSU is faulty - """ - if index is None: - return False - - status = 0 - try: - with open(self.psu_path + self.psu_oper_status.format(index), 'r') as power_status: - status = int(power_status.read()) - except IOError: - return False - - return status == 1 - - def get_psu_presence(self, index): - """ - Retrieves the presence status of power supply unit (PSU) defined - by 1-based index - - :param index: An integer, 1-based index of the PSU of which to query status - :return: Boolean, True if PSU is plugged, False if not - """ - if index is None: - return False - - status = 0 - try: - with open(self.psu_path + self.psu_presence.format(index), 'r') as presence_status: - status = int(presence_status.read()) - except IOError: - return False - - return status == 1 diff --git a/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/psuutil.py b/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/psuutil.py new file mode 120000 index 00000000000..9f724238a8d --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/psuutil.py @@ -0,0 +1 @@ +../../x86_64-mlnx_msn2700-r0/plugins/psuutil.py \ No newline at end of file diff --git a/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/thermalutil.py b/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/thermalutil.py new file mode 120000 index 00000000000..cef21ffaccc --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2410-r0/plugins/thermalutil.py @@ -0,0 +1 @@ +../../x86_64-mlnx_msn2700-r0/plugins/thermalutil.py \ No newline at end of file diff --git a/device/mellanox/x86_64-mlnx_msn2700-r0/plugins/fanutil.py b/device/mellanox/x86_64-mlnx_msn2700-r0/plugins/fanutil.py new file mode 100644 index 00000000000..6a4110ebbfe --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2700-r0/plugins/fanutil.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python + +############################################################################# +# Mellanox +# +# Module contains an implementation of SONiC PSU Base API and +# provides the PSUs status which are available in the platform +# +############################################################################# + + +try: + import os.path + import syslog + import subprocess + from glob import glob + from sonic_fan.fan_base import FanBase +except ImportError as e: + raise ImportError (str(e) + "- required module not found") + +def log_err(msg): + syslog.openlog("fanutil") + syslog.syslog(syslog.LOG_ERR, msg) + syslog.closelog() + +class FanUtil(FanBase): + """Platform-specific FanUtil class""" + + PWM_MAX = 255 + MAX_FAN_PER_DRAWER = 2 + GET_HWSKU_CMD = "sonic-cfggen -d -v DEVICE_METADATA.localhost.hwsku" + sku_with_unpluggable_fan = ['ACS-MSN2010', 'ACS-MSN2100'] + FAN_BASE_DIR_PATTERN = "/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-2/2-0048/hwmon/hwmon*" + FAN_HOTPLUG_STATUS_BASE_DIR_PATTERN = "/sys/devices/platform/mlxplat/mlxreg-hotplug/hwmon/hwmon*" + + def __init__(self): + FanBase.__init__(self) + + self.sku_name = self._get_sku_name() + + if self.sku_name in self.sku_with_unpluggable_fan: + self.fan_status = None + self.unpluggable_fan = True + else: + fan_hotplug_status_base_list = glob(self.FAN_HOTPLUG_STATUS_BASE_DIR_PATTERN) + if len(fan_hotplug_status_base_list) != 1: + raise RuntimeError("Can't find hotplug hwmon path for fan info from {}".format(self.FAN_HOTPLUG_STATUS_BASE_DIR_PATTERN)) + self.fan_status = os.path.join(fan_hotplug_status_base_list[0], "fan{}") + self.unpluggable_fan = False + fan_base_dir_list = glob(self.FAN_BASE_DIR_PATTERN) + if len(fan_base_dir_list) != 1: + raise RuntimeError("Can't find hwmon path for fan info from {}".format(self.FAN_BASE_DIR_PATTERN)) + + self.fan_get_speed = os.path.join(fan_base_dir_list[0], "fan{}_input") + self.fan_set_speed = os.path.join(fan_base_dir_list[0], "pwm1") + + self.num_of_fan, self.num_of_drawer = self._extract_num_of_fans_and_fan_drawers() + + def _get_sku_name(self): + p = subprocess.Popen(self.GET_HWSKU_CMD, shell=True, stdout=subprocess.PIPE) + out, err = p.communicate() + return out.rstrip('\n') + + def _extract_num_of_fans_and_fan_drawers(self): + # So far we don't have files representing the number of fans and drawers + # The only way to retrieve the number is to count files. + # for number of fans, we get it via couting the speed files. + # for number of draws, we get it via couting the status files. + list_of_fan_speed = glob(self.fan_get_speed.format("*")) + num_of_fan = len(list_of_fan_speed) + list_of_fan_status = glob(self.fan_status) + num_of_drawer = len(list_of_fan_status) + + return num_of_fan, num_of_drawer + + def _convert_fan_index_to_drawer_index(self, index): + return (index + self.MAX_FAN_PER_DRAWER - 1) / self.MAX_FAN_PER_DRAWER + + def _read_file(self, file_pattern, index = None): + """ + Reads the file of the fan + + :param file_pattern: The filename convention + :param index: An integer, 1-based index of the fan of which to query status + :return: int + """ + return_value = 0 + try: + with open(os.path.join(file_pattern.format(index)), 'r') as file_to_read: + return_value = int(file_to_read.read()) + except IOError: + log_err("Read file {} failed".format(file_pattern.format(index))) + return return_value + + return return_value + + def get_num_fans(self): + """ + Retrieves the number of FANs supported on the device + + :return: An integer, the number of FANs supported on the device + """ + return self.num_of_fan + + def get_status(self, index): + """ + Retrieves the operational status of FAN defined + by index 1-based + + :param index: An integer, 1-based index of the PSU of which to query status + :return: Boolean, + - True if FAN is running with some speed + - False if FAN has stopped running + """ + if not self.get_presence(index): + return False + + return self.get_speed(index) != 0 + + def get_presence(self, index): + """ + Retrieves the presence status of a FAN defined + by 1-based index + + :param index: An integer, 1-based index of the FAN of which to query status + :return: Boolean, True if FAN is plugged, False if not + """ + if index > self.num_of_fan: + raise RuntimeError("index ({}) shouldn't be greater than number of fans ({})".format(index, self.num_of_fan)) + + if self.unpluggable_fan: + return True + + draw_index = self._convert_fan_index_to_drawer_index(index) + presence = self._read_file(self.fan_status, draw_index) + + return presence != 0 + + def get_speed(self, index): + """ + Retrieves the speed of a Front FAN in the tray in revolutions per minute defined + by 1-based index + + :param index: An integer, 1-based index of the FAN of which to query speed + :return: integer, denoting front FAN speed + """ + speed = self._read_file(self.fan_get_speed, self.num_of_fan - index + 1) + + return speed + + def set_speed(self, val): + """ + Sets the speed of all the FANs to a value denoted by the duty-cycle percentage val + + :param val: An integer, <0-100> denoting FAN duty cycle percentage + :return: Boolean, True if operation is successful, False if not + """ + status = True + pwm = int(round(self.PWM_MAX*val/100.0)) + + try: + with open(os.path.join(self.fan_set_speed), 'w') as fan_pwm: + fan_pwm.write(str(pwm)) + except (ValueError, IOError): + log_err("Read file {} failed".format(self.fan_set_speed)) + status = False + + return status diff --git a/device/mellanox/x86_64-mlnx_msn2700-r0/plugins/psuutil.py b/device/mellanox/x86_64-mlnx_msn2700-r0/plugins/psuutil.py index 954045dde59..ccc945ed9d0 100644 --- a/device/mellanox/x86_64-mlnx_msn2700-r0/plugins/psuutil.py +++ b/device/mellanox/x86_64-mlnx_msn2700-r0/plugins/psuutil.py @@ -8,27 +8,85 @@ # ############################################################################# -import os.path - try: + import os.path + import syslog + import subprocess + from glob import glob from sonic_psu.psu_base import PsuBase except ImportError as e: raise ImportError (str(e) + "- required module not found") +def log_err(msg): + syslog.openlog("psuutil") + syslog.syslog(syslog.LOG_ERR, msg) + syslog.closelog() + + class PsuUtil(PsuBase): """Platform-specific PSUutil class""" + MAX_PSU_FAN = 1 + MAX_NUM_PSU = 2 + GET_HWSKU_CMD = "sonic-cfggen -d -v DEVICE_METADATA.localhost.hwsku" + # for spectrum1 switches with plugable PSUs, the output voltage file is psuX_volt + # for spectrum2 switches the output voltage file is psuX_volt_out2 + psu_sku_lookup = {'ACS-MSN2410' : 0, 'ACS-MSN2700' : 0, 'Mellanox-SN2700' : 0, 'Mellanox-SN2700-D48C8' : 0, 'LS-SN2700' : 0, 'ACS-MSN2740' : 1} + psu_dir_pattern_list = [ + [ + # for 2410, 2700 + "/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-10/10-0059/hwmon/hwmon*", + "/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-10/10-0058/hwmon/hwmon*" + ], + [ + # for 2740 + "/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-4/4-0059/hwmon/hwmon*", + "/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-4/4-0058/hwmon/hwmon*" + ] + ] + PSU_STATUS_DIR_PATTERN = "/sys/devices/platform/mlxplat/mlxreg-hotplug/hwmon/hwmon*/" + + def _init_one_psu(self, index): + psu_dir_temp_list = glob(self.psu_dir_pattern[index]) + if not len(psu_dir_temp_list) == 1: + log_err("Can't find hwmon path for psu power related values") + self.psu_current.append("") + self.psu_power.append("") + self.psu_voltage.append("") + self.fan_speed.append("") + else: + psu_dir_path = psu_dir_temp_list[0] + self.psu_current.append(os.path.join(psu_dir_path, "curr2_input")) + self.psu_power.append(os.path.join(psu_dir_path, "power2_input")) + self.psu_voltage.append(os.path.join(psu_dir_path, "in2_input")) + self.fan_speed.append(os.path.join(psu_dir_path, "fan1_input")) + def __init__(self): PsuBase.__init__(self) + self.sku_name = self._get_sku_name() + self.psu_path = "" - for index in range(0, 100): - hwmon_path = "/sys/devices/platform/mlxplat/mlxreg-hotplug/hwmon/hwmon{}/".format(index) - if os.path.exists(hwmon_path): - self.psu_path = hwmon_path - break - self.psu_presence = "psu{}" - self.psu_oper_status = "pwr{}" + hwmon_path_list = glob(self.PSU_STATUS_DIR_PATTERN) + if not len(hwmon_path_list) == 1: + raise RuntimeError("Can't find hwmon path for psu presence status from {}".format(self.PSU_STATUS_DIR_PATTERN)) + psu_status_path = hwmon_path_list[0] + self.psu_presence = os.path.join(psu_status_path, "psu{}") + self.psu_oper_status = os.path.join(psu_status_path, "pwr{}") + + self.psu_dir = [] + self.psu_current = [] + self.psu_power = [] + self.psu_voltage = [] + self.fan_speed = [] + self.psu_dir_pattern = self.psu_dir_pattern_list[self.psu_sku_lookup[self.sku_name]] + for i in range(self.MAX_NUM_PSU): + self._init_one_psu(i) + + def _get_sku_name(self): + p = subprocess.Popen(self.GET_HWSKU_CMD, shell=True, stdout=subprocess.PIPE) + out, err = p.communicate() + return out.rstrip('\n') def get_num_psus(self): """ @@ -36,7 +94,26 @@ def get_num_psus(self): :return: An integer, the number of PSUs available on the device """ - return 2 + return self.MAX_NUM_PSU + + def _read_file(self, file_pattern, index): + """ + Reads the file of the PSU + + :param file_pattern: The filename convention + :param index: An integer, 1-based index of the PSU of which to query status + :return: int + """ + return_value = 0 + try: + with open(file_pattern.format(index), 'r') as file_to_read: + return_value = int(file_to_read.read()) + except IOError: + log_err("Read file {} failed".format(self.psu_path + file_pattern.format(index))) + self._init_one_psu(index) + return 0 + + return return_value def get_psu_status(self, index): """ @@ -48,13 +125,10 @@ def get_psu_status(self, index): """ if index is None: return False + if index > self.MAX_NUM_PSU: + raise RuntimeError("index ({}) shouldn't be greater than {}".format(index, self.MAX_NUM_PSU)) - status = 0 - try: - with open(self.psu_path + self.psu_oper_status.format(index), 'r') as power_status: - status = int(power_status.read()) - except IOError: - return False + status = self._read_file(self.psu_oper_status, index) return status == 1 @@ -67,13 +141,80 @@ def get_psu_presence(self, index): :return: Boolean, True if PSU is plugged, False if not """ if index is None: - return False + raise RuntimeError("index shouldn't be None") + if index > self.MAX_NUM_PSU: + raise RuntimeError("index ({}) shouldn't be greater than {}".format(index, self.MAX_NUM_PSU)) - status = 0 - try: - with open(self.psu_path + self.psu_presence.format(index), 'r') as presence_status: - status = int(presence_status.read()) - except IOError: - return False + status = self._read_file(self.psu_presence, index) return status == 1 + + def get_output_voltage(self, index): + """ + Retrieves the ouput volatage in milli volts of a power supply unit (PSU) defined + by 1-based index + :param index: An integer, 1-based index of the PSU of which to query o/p volatge + :return: An integer, value of o/p voltage in mV if PSU is good, else zero + """ + if index is None: + raise RuntimeError("index shouldn't be None") + + if not self.get_psu_presence(index) or not self.get_psu_status(index): + return 0 + + voltage = self._read_file(self.psu_voltage[index - 1], index) + + return voltage + + def get_output_current(self, index): + """ + Retrieves the output current in milli amperes of a power supply unit (PSU) defined + by 1-based index + :param index: An integer, 1-based index of the PSU of which to query o/p current + :return: An integer, value of o/p current in mA if PSU is good, else zero + """ + if index is None: + raise RuntimeError("index shouldn't be None") + + if not self.get_psu_presence(index) or not self.get_psu_status(index): + return 0 + + current = self._read_file(self.psu_current[index - 1], index) + + return current + + def get_output_power(self, index): + """ + Retrieves the output power in micro watts of a power supply unit (PSU) defined + by 1-based index + :param index: An integer, 1-based index of the PSU of which to query o/p power + :return: An integer, value of o/p power in micro Watts if PSU is good, else zero + """ + if index is None: + raise RuntimeError("index shouldn't be None") + + if not self.get_psu_presence(index) or not self.get_psu_status(index): + return 0 + + power = self._read_file(self.psu_power[index - 1], index) / 1000.0 + + return power + + def get_fan_speed(self, index, fan_index): + """ + Retrieves the speed of fan, in rpm, denoted by 1-based of a power + supply unit (PSU) defined by 1-based index + :param index: An integer, 1-based index of the PSU of which to query fan speed + :param fan_index: An integer, 1-based index of the PSU-fan of which to query speed + :return: An integer, value of PSU-fan speed in rpm if PSU-fan is good, else zero + """ + if index is None: + raise RuntimeError("index shouldn't be None") + if fan_index > self.MAX_PSU_FAN: + raise RuntimeError("fan_index ({}) shouldn't be greater than {}".format(fan_index, self.MAX_PSU_FAN)) + if not self.get_psu_presence(index) or not self.get_psu_status(index): + return 0 + + fan_speed = self._read_file(self.fan_speed[index - 1], index) + + return fan_speed diff --git a/device/mellanox/x86_64-mlnx_msn2700-r0/plugins/thermalutil.py b/device/mellanox/x86_64-mlnx_msn2700-r0/plugins/thermalutil.py new file mode 100644 index 00000000000..27fe945be8e --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2700-r0/plugins/thermalutil.py @@ -0,0 +1,435 @@ +#!/usr/bin/env python + +############################################################################# +# Mellanox +# +# Module contains an implementation of SONiC Thermal Base API and +# provides the thermal sensor status which are available in the platform +# +############################################################################# + +try: + from os.path import join, dirname, basename + from glob import glob + import syslog + import subprocess + from sonic_thermal.thermal_base import ThermalBase +except ImportError as e: + raise ImportError (str(e) + "- required module not found") + +def log_info(msg): + syslog.openlog("thermalutil") + syslog.syslog(syslog.LOG_INFO, msg) + syslog.closelog() + + +THERMAL_DEV_CATEGORY_CPU_CORE = "cpu_core" +THERMAL_DEV_CATEGORY_CPU_PACK = "cpu_pack" +THERMAL_DEV_CATEGORY_MODULE = "module" +THERMAL_DEV_CATEGORY_PSU = "psu" +THERMAL_DEV_CATEGORY_AMBIENT = "ambient" +THERMAL_PATH_INDEX_PORT_AMBIENT = "port ambient path" +THERMAL_PATH_INDEX_FAN_AMBIENT = "fan ambient path" + +THERMAL_DEV_ASIC_AMBIENT = "asic_amb" +THERMAL_DEV_FAN_AMBIENT = "fan_amb" +THERMAL_DEV_PORT_AMBIENT = "port_amb" + +THERMAL_API_BASE_DIR = "base_dir" +THERMAL_API_GET_TEMPERATURE = "get_temperature" +THERMAL_API_GET_HIGH_THRESHOLD = "get_high_threshold" +THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD = "get_high_critical_threshold" + +THERMAL_API_INVALID_HIGH_THRESHOLD = 0.0 + +HW_MGMT_THERMAL_ROOT = "/var/run/hw-management/thermal/" + +thermal_api_handler_cpu_core = { + THERMAL_API_GET_TEMPERATURE:"/sys/devices/platform/coretemp.0/hwmon/hwmon*/temp{}_input", + THERMAL_API_GET_HIGH_THRESHOLD:"/sys/devices/platform/coretemp.0/hwmon/hwmon*/temp{}_max", + THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD:"/sys/devices/platform/coretemp.0/hwmon/hwmon*/temp{}_crit" +} +thermal_api_handler_cpu_pack = { + THERMAL_API_GET_TEMPERATURE:"/sys/devices/platform/coretemp.0/hwmon/hwmon*/temp1_input", + THERMAL_API_GET_HIGH_THRESHOLD:"/sys/devices/platform/coretemp.0/hwmon/hwmon*/temp1_max", + THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD:"/sys/devices/platform/coretemp.0/hwmon/hwmon*/temp1_crit" +} +thermal_api_handler_module = { + THERMAL_API_GET_TEMPERATURE:"/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-2/2-0048/hwmon/hwmon*/temp{}_input", + THERMAL_API_GET_HIGH_THRESHOLD:"/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-2/2-0048/hwmon/hwmon*/temp{}_crit", + THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD:"/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-2/2-0048/hwmon/hwmon*/temp{}_emergency" +} +thermal_api_handler_psu = { + THERMAL_API_GET_TEMPERATURE:"/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-10/10-00{}/hwmon/hwmon*/temp1_input", + THERMAL_API_GET_HIGH_THRESHOLD:"/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-10/10-00{}/hwmon/hwmon*/temp1_max", + THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD:None +} +thermal_ambient_apis = { + THERMAL_DEV_ASIC_AMBIENT : "/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-2/2-0048/hwmon/hwmon*/temp1_input", + THERMAL_DEV_PORT_AMBIENT : "/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-7/7-00{}/hwmon/hwmon*/temp1_input", + THERMAL_DEV_FAN_AMBIENT : "/sys/devices/platform/mlxplat/i2c_mlxcpld.1/i2c-1/i2c-{}/{}-00{}/hwmon/hwmon*/temp1_input" +} +thermal_psu_indexes = ["59", "58"] +thermal_cpu_core_index_start = 2 +thermal_module_index_start = 2 + +thermal_ambient_name = { + THERMAL_DEV_ASIC_AMBIENT : "Ambient ASIC Temp", + THERMAL_DEV_PORT_AMBIENT : "Ambient Port Side Temp", + THERMAL_DEV_FAN_AMBIENT : "Ambient Fan Side Temp" +} +thermal_api_handlers = { + THERMAL_DEV_CATEGORY_CPU_CORE : thermal_api_handler_cpu_core, + THERMAL_DEV_CATEGORY_CPU_PACK : thermal_api_handler_cpu_pack, + THERMAL_DEV_CATEGORY_MODULE : thermal_api_handler_module, + THERMAL_DEV_CATEGORY_PSU : thermal_api_handler_psu +} +thermal_name = { + THERMAL_DEV_CATEGORY_CPU_CORE : "CPU Core {} Temp", + THERMAL_DEV_CATEGORY_CPU_PACK : "CPU Pack Temp", + THERMAL_DEV_CATEGORY_MODULE : "xSFP module {} Temp", + THERMAL_DEV_CATEGORY_PSU : "PSU-{} Temp" +} + +thermal_device_categories_all = [ + THERMAL_DEV_CATEGORY_CPU_CORE, + THERMAL_DEV_CATEGORY_CPU_PACK, + THERMAL_DEV_CATEGORY_MODULE, + THERMAL_DEV_CATEGORY_PSU, + THERMAL_DEV_CATEGORY_AMBIENT +] + +thermal_device_categories_singleton = [ + THERMAL_DEV_CATEGORY_CPU_PACK, + THERMAL_DEV_CATEGORY_AMBIENT +] +thermal_api_names = [ + THERMAL_API_GET_TEMPERATURE, + THERMAL_API_GET_HIGH_THRESHOLD +] + +hwsku_dict_thermal = {'ACS-MSN2700': 0, 'LS-SN2700':0, 'ACS-MSN2740': 3, 'ACS-MSN2100': 1, 'ACS-MSN2410': 2, 'ACS-MSN2010': 4, 'Mellanox-SN2700': 0, 'Mellanox-SN2700-D48C8': 0} +thermal_profile_list = [ + # 2700 + { + THERMAL_DEV_CATEGORY_CPU_CORE:(0, 2), + THERMAL_DEV_CATEGORY_MODULE:(1, 32), + THERMAL_DEV_CATEGORY_PSU:(1, 2), + THERMAL_DEV_CATEGORY_CPU_PACK:(0,1), + THERMAL_DEV_CATEGORY_AMBIENT:(0, + [ + THERMAL_DEV_ASIC_AMBIENT, + THERMAL_DEV_PORT_AMBIENT, + THERMAL_DEV_FAN_AMBIENT + ] + ), + THERMAL_PATH_INDEX_PORT_AMBIENT: "4a", + THERMAL_PATH_INDEX_FAN_AMBIENT: ("17", "17", "49") + }, + # 2100 + { + THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4), + THERMAL_DEV_CATEGORY_MODULE:(1, 16), + THERMAL_DEV_CATEGORY_PSU:(0, 0), + THERMAL_DEV_CATEGORY_CPU_PACK:(0,0), + THERMAL_DEV_CATEGORY_AMBIENT:(0, + [ + THERMAL_DEV_ASIC_AMBIENT, + THERMAL_DEV_PORT_AMBIENT, + THERMAL_DEV_FAN_AMBIENT, + ] + ), + THERMAL_PATH_INDEX_PORT_AMBIENT: "4a", + THERMAL_PATH_INDEX_FAN_AMBIENT: ("7", "7", "4b") + }, + # 2410 + { + THERMAL_DEV_CATEGORY_CPU_CORE:(0, 2), + THERMAL_DEV_CATEGORY_MODULE:(1, 56), + THERMAL_DEV_CATEGORY_PSU:(1, 2), + THERMAL_DEV_CATEGORY_CPU_PACK:(0,1), + THERMAL_DEV_CATEGORY_AMBIENT:(0, + [ + THERMAL_DEV_ASIC_AMBIENT, + THERMAL_DEV_PORT_AMBIENT, + THERMAL_DEV_FAN_AMBIENT, + ] + ), + THERMAL_PATH_INDEX_PORT_AMBIENT: "4a", + THERMAL_PATH_INDEX_FAN_AMBIENT: ("17", "17", "49") + }, + # 2740 + { + THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4), + THERMAL_DEV_CATEGORY_MODULE:(1, 32), + THERMAL_DEV_CATEGORY_PSU:(1, 2), + THERMAL_DEV_CATEGORY_CPU_PACK:(0,0), + THERMAL_DEV_CATEGORY_AMBIENT:(0, + [ + THERMAL_DEV_ASIC_AMBIENT, + THERMAL_DEV_PORT_AMBIENT, + THERMAL_DEV_FAN_AMBIENT, + ] + ), + THERMAL_PATH_INDEX_PORT_AMBIENT: "48", + THERMAL_PATH_INDEX_FAN_AMBIENT: ("6", "6", "49") + }, + # 2010 + { + THERMAL_DEV_CATEGORY_CPU_CORE:(0, 4), + THERMAL_DEV_CATEGORY_MODULE:(1, 22), + THERMAL_DEV_CATEGORY_PSU:(0, 0), + THERMAL_DEV_CATEGORY_CPU_PACK:(0,0), + THERMAL_DEV_CATEGORY_AMBIENT:(0, + [ + THERMAL_DEV_ASIC_AMBIENT, + THERMAL_DEV_PORT_AMBIENT, + THERMAL_DEV_FAN_AMBIENT, + ] + ), + THERMAL_PATH_INDEX_PORT_AMBIENT: "4a", + THERMAL_PATH_INDEX_FAN_AMBIENT: ("7", "7", "4b") + } +] + + +class Thermal(object): + def __init__(self, category, index, has_index, file_index = None): + """ + index should be a string for category ambient and int for other categories + """ + if category == THERMAL_DEV_CATEGORY_AMBIENT: + self.name = thermal_ambient_name[index] + self.index = index + elif has_index: + self.name = thermal_name[category].format(index) + self.index = index + else: + self.name = thermal_name[category] + self.index = 0 + + self.category = category + self.temperature = self._get_file_from_api(THERMAL_API_GET_TEMPERATURE, file_index) + self.high_threshold = self._get_file_from_api(THERMAL_API_GET_HIGH_THRESHOLD, file_index) + self.high_critical_threshold = self._get_file_from_api(THERMAL_API_GET_HIGH_CRITICAL_THRESHOLD, file_index) + + def _get_real_hwmon_path(self, hwmon_pattern): + dirpattern = dirname(hwmon_pattern) + filename = basename(hwmon_pattern) + hwmon_list = glob(dirpattern) + if len(hwmon_list) != 1: + log_info("unable to get real path from hwmon path pattern {}".format(hwmon_pattern)) + return hwmon_pattern + return join(hwmon_list[0], filename) + + def get_name(self): + """ + Retrieves the name of the device + + Returns: + string: The name of the device + """ + return self.name + + def _read_generic_file(self, filename, len): + """ + Read a generic file, returns the contents of the file + """ + result = None + try: + with open(filename, 'r') as fileobj: + result = fileobj.read() + except Exception as e: + log_info("Fail to read file {} due to {}".format(filename, repr(e))) + return result + + def _get_file_from_api(self, api_name, index): + if self.category == THERMAL_DEV_CATEGORY_AMBIENT: + if api_name == THERMAL_API_GET_TEMPERATURE: + filename = thermal_ambient_apis[self.index] + else: + return None, None + else: + handler = thermal_api_handlers[self.category][api_name] + if self.category in thermal_device_categories_singleton: + filename = handler + else: + if handler: + filename = handler.format(index) + else: + return None, None + return self._get_real_hwmon_path(filename), filename + + def get_temperature(self): + """ + Retrieves current temperature reading from thermal + + Returns: + A float number of current temperature in Celsius up to nearest thousandth + of one degree Celsius, e.g. 30.125 + """ + value_str = self._read_generic_file(self.temperature[0], 0) + if value_str is None: + # Probably the sensor was replugged and the path has been changed + self.temperature = self._get_real_hwmon_path(self.temperature[0]), self.temperature[1] + return None + value_float = float(value_str) + if self.category == THERMAL_DEV_CATEGORY_MODULE and value_float == THERMAL_API_INVALID_HIGH_THRESHOLD: + return None + return value_float / 1000.0 + + def get_high_threshold(self): + """ + Retrieves the high threshold temperature of thermal + + Returns: + A float number, the high threshold temperature of thermal in Celsius + up to nearest thousandth of one degree Celsius, e.g. 30.125 + """ + if self.high_threshold[0] is None: + return None + value_str = self._read_generic_file(self.high_threshold[0], 0) + if value_str is None: + self.high_threshold = self._get_real_hwmon_path(self.high_threshold[0]), self.high_threshold[1] + return None + value_float = float(value_str) + if self.category == THERMAL_DEV_CATEGORY_MODULE and value_float == THERMAL_API_INVALID_HIGH_THRESHOLD: + return None + return value_float / 1000.0 + + 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 + """ + if self.high_critical_threshold[0] is None: + return None + value_str = self._read_generic_file(self.high_critical_threshold[0], 0) + if value_str is None: + self.high_critical_threshold = self._get_real_hwmon_path(self.high_critical_threshold[0]), self.high_critical_threshold[1] + return None + value_float = float(value_str) + if self.category == THERMAL_DEV_CATEGORY_MODULE and value_float == THERMAL_API_INVALID_HIGH_THRESHOLD: + return None + return value_float / 1000.0 + + +class ThermalUtil(ThermalBase): + """Platform-specific Thermalutil class""" + + MAX_PSU_FAN = 1 + MAX_NUM_PSU = 2 + GET_HWSKU_CMD = "sonic-cfggen -d -v DEVICE_METADATA.localhost.hwsku" + number_of_thermals = 0 + thermal_list = [] + + def _get_sku_name(self): + p = subprocess.Popen(self.GET_HWSKU_CMD, shell=True, stdout=subprocess.PIPE) + out, err = p.communicate() + return out.rstrip('\n') + + def __init__(self): + sku = self._get_sku_name() + # create thermal objects for all categories of sensors + tp_index = hwsku_dict_thermal[sku] + thermal_profile = thermal_profile_list[tp_index] + for category in thermal_device_categories_all: + if category == THERMAL_DEV_CATEGORY_AMBIENT: + count, ambient_list = thermal_profile[category] + for ambient in ambient_list: + path_pattern = thermal_ambient_apis[ambient] + if ambient == THERMAL_DEV_PORT_AMBIENT: + # Generate the real path + param1 = thermal_profile[THERMAL_PATH_INDEX_PORT_AMBIENT] + path_pattern = path_pattern.format(param1) + thermal_ambient_apis[THERMAL_DEV_PORT_AMBIENT] = path_pattern + elif ambient == THERMAL_DEV_FAN_AMBIENT: + param1, param2, param3 = thermal_profile[THERMAL_PATH_INDEX_FAN_AMBIENT] + path_pattern = path_pattern.format(param1, param2, param3) + thermal_ambient_apis[THERMAL_DEV_FAN_AMBIENT] = path_pattern + thermal = Thermal(category, ambient, True) + self.thermal_list.append(thermal) + else: + start, count = 0, 0 + if category in thermal_profile: + start, count = thermal_profile[category] + if count == 0: + continue + if count == 1: + thermal = Thermal(category, 0, False) + self.thermal_list.append(thermal) + else: + for index in range(count): + if category == THERMAL_DEV_CATEGORY_PSU: + fileindex = thermal_psu_indexes[index] + elif category == THERMAL_DEV_CATEGORY_MODULE or category == THERMAL_DEV_CATEGORY_CPU_CORE: + fileindex = index + 2 + else: + fileindex = None + thermal = Thermal(category, start + index, True, fileindex) + self.thermal_list.append(thermal) + self.number_of_thermals = len(self.thermal_list) + + def get_num_thermals(self): + """ + Retrieves the number of thermal sensors supported on the device + + :return: An integer, the number of thermal sensors supported on the device + """ + return self.number_of_thermals + + def get_name(self, index): + """ + Retrieves the human-readable name of a thermal sensor by 1-based index + + Returns: + :param index: An integer, 1-based index of the thermal sensor of which to query status + :return: String, + A string representing the name of the thermal sensor. + """ + if index >= self.number_of_thermals: + raise RuntimeError("index ({}) shouldn't be greater than {}".format(index, self.number_of_thermals)) + return self.thermal_list[index].get_name() + + def get_temperature(self, index): + """ + Retrieves current temperature reading from thermal sensor by 1-based index + + :param index: An integer, 1-based index of the thermal sensor of which to query status + :return: Float, + A float number of current temperature in Celsius up to nearest thousandth + of one degree Celsius, e.g. 30.125 + """ + if index >= self.number_of_thermals: + raise RuntimeError("index ({}) shouldn't be greater than {}".format(index, self.number_of_thermals)) + return self.thermal_list[index].get_temperature() + + def get_high_threshold(self, index): + """ + Retrieves the high threshold temperature of thermal by 1-based index + Actions should be taken if the temperature becomes higher than the threshold. + + :param index: An integer, 1-based index of the thermal sensor of which to query status + :return: A float number, the high threshold temperature of thermal in Celsius + up to nearest thousandth of one degree Celsius, e.g. 30.125 + """ + if index >= self.number_of_thermals: + raise RuntimeError("index ({}) shouldn't be greater than {}".format(index, self.number_of_thermals)) + return self.thermal_list[index].get_high_threshold() + + def get_high_critical_threshold(self, index): + """ + Retrieves the high critical threshold temperature of thermal by 1-based index + Actions should be taken immediately if the temperature becomes higher than the high critical + threshold otherwise the device will be damaged. + + :param index: An integer, 1-based index of the thermal sensor of which to query status + :return: A float number, the high critical threshold temperature of thermal in Celsius + up to nearest thousandth of one degree Celsius, e.g. 30.125 + """ + if index >= self.number_of_thermals: + raise RuntimeError("index ({}) shouldn't be greater than {}".format(index, self.number_of_thermals)) + return self.thermal_list[index].get_high_critical_threshold() diff --git a/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/fanutil.py b/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/fanutil.py new file mode 120000 index 00000000000..82ea06ef915 --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/fanutil.py @@ -0,0 +1 @@ +../../x86_64-mlnx_msn2700-r0/plugins/fanutil.py \ No newline at end of file diff --git a/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/psuutil.py b/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/psuutil.py deleted file mode 100644 index 8c47a008324..00000000000 --- a/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/psuutil.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python - -############################################################################# -# Mellanox -# -# Module contains an implementation of SONiC PSU Base API and -# provides the PSUs status which are available in the platform -# -############################################################################# - -import os.path - -try: - from sonic_psu.psu_base import PsuBase -except ImportError as e: - raise ImportError (str(e) + "- required module not found") - -class PsuUtil(PsuBase): - """Platform-specific PSUutil class""" - - def __init__(self): - PsuBase.__init__(self) - self.psu_path = "" - for index in range(0, 100): - hwmon_path = "/sys/devices/platform/mlxplat/mlxreg-hotplug/hwmon/hwmon{}/".format(index) - if os.path.exists(hwmon_path): - self.psu_path = hwmon_path - break - self.psu_presence = "psu{}" - self.psu_oper_status = "pwr{}" - - def get_num_psus(self): - """ - Retrieves the number of PSUs available on the device - - :return: An integer, the number of PSUs available on the device - """ - return 2 - - def get_psu_status(self, index): - """ - Retrieves the oprational status of power supply unit (PSU) defined - by 1-based index - - :param index: An integer, 1-based index of the PSU of which to query status - :return: Boolean, True if PSU is operating properly, False if PSU is faulty - """ - if index is None: - return False - - status = 0 - try: - with open(self.psu_path + self.psu_oper_status.format(index), 'r') as power_status: - status = int(power_status.read()) - except IOError: - return False - - return status == 1 - - def get_psu_presence(self, index): - """ - Retrieves the presence status of power supply unit (PSU) defined - by 1-based index - - :param index: An integer, 1-based index of the PSU of which to query status - :return: Boolean, True if PSU is plugged, False if not - """ - if index is None: - return False - - status = 0 - try: - with open(self.psu_path + self.psu_presence.format(index), 'r') as presence_status: - status = int(presence_status.read()) - except IOError: - return False - - return status == 1 diff --git a/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/psuutil.py b/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/psuutil.py new file mode 120000 index 00000000000..9f724238a8d --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/psuutil.py @@ -0,0 +1 @@ +../../x86_64-mlnx_msn2700-r0/plugins/psuutil.py \ No newline at end of file diff --git a/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/thermalutil.py b/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/thermalutil.py new file mode 120000 index 00000000000..cef21ffaccc --- /dev/null +++ b/device/mellanox/x86_64-mlnx_msn2740-r0/plugins/thermalutil.py @@ -0,0 +1 @@ +../../x86_64-mlnx_msn2700-r0/plugins/thermalutil.py \ No newline at end of file