From d5aa0d438219d5c84bbc4ad3bfb6fdfa46ef425c Mon Sep 17 00:00:00 2001 From: Stephen Sun <5379172+stephenxs@users.noreply.github.com> Date: Thu, 5 Dec 2019 03:40:42 +0800 Subject: [PATCH] [Mellanox]support led for fan/psu and fan's direction (#3795) --- .../sonic_platform/chassis.py | 19 ++- .../mlnx-platform-api/sonic_platform/fan.py | 132 +++++++++++++++--- .../mlnx-platform-api/sonic_platform/psu.py | 116 ++++++++++++++- 3 files changed, 243 insertions(+), 24 deletions(-) diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py b/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py index 44ef898128..d34245390c 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py @@ -14,6 +14,7 @@ try: from sonic_daemon_base.daemon_base import Logger from os import listdir from os.path import isfile, join + from glob import glob import sys import io import re @@ -33,6 +34,10 @@ EEPROM_CACHE_FILE = 'syseeprom_cache' HWMGMT_SYSTEM_ROOT = '/var/run/hw-management/system/' +MST_DEVICE_NAME_PATTERN = '/dev/mst/mt[0-9]*_pciconf0' +MST_DEVICE_RE_PATTERN = '/dev/mst/mt([0-9]*)_pciconf0' +SPECTRUM1_CHIP_ID = '52100' + #reboot cause related definitions REBOOT_CAUSE_ROOT = HWMGMT_SYSTEM_ROOT @@ -87,11 +92,21 @@ class Chassis(ChassisBase): num_of_fan, num_of_drawer = self._extract_num_of_fans_and_fan_drawers() multi_rotor_in_drawer = num_of_fan > num_of_drawer + # Fan's direction isn't supported on spectrum 1 devices for now + mst_dev_list = glob(MST_DEVICE_NAME_PATTERN) + if not mst_dev_list: + raise RuntimeError("Can't get chip type due to {} not found".format(MST_DEVICE_NAME_PATTERN)) + m = re.search(MST_DEVICE_RE_PATTERN, mst_dev_list[0]) + if m.group(1) == SPECTRUM1_CHIP_ID: + has_fan_dir = False + else: + has_fan_dir = True + for index in range(num_of_fan): if multi_rotor_in_drawer: - fan = Fan(index, index/2) + fan = Fan(has_fan_dir, index, index/2) else: - fan = Fan(index, index) + fan = Fan(has_fan_dir, index, index) self._fan_list.append(fan) diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/fan.py b/platform/mellanox/mlnx-platform-api/sonic_platform/fan.py index 8b057e4123..818aa0f011 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/fan.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/fan.py @@ -15,23 +15,28 @@ try: except ImportError as e: raise ImportError (str(e) + "- required module not found") -LED_ON = 1 -LED_OFF = 0 +LED_ON = '1' +LED_OFF = '0' PWM_MAX = 255 FAN_PATH = "/var/run/hw-management/thermal/" LED_PATH = "/var/run/hw-management/led/" +# fan_dir isn't supported on Spectrum 1. It is supported on Spectrum 2 and later switches +FAN_DIR = "/var/run/hw-management/system/fan_dir" class Fan(FanBase): """Platform-specific Fan class""" - def __init__(self, fan_index, drawer_index = 1, psu_fan = False): + + STATUS_LED_COLOR_ORANGE = "orange" + + def __init__(self, has_fan_dir, fan_index, drawer_index = 1, psu_fan = False): # API index is starting from 0, Mellanox platform index is starting from 1 self.index = fan_index + 1 self.drawer_index = drawer_index + 1 self.is_psu_fan = psu_fan - + self.fan_min_speed_path = "fan{}_min".format(self.index) if not self.is_psu_fan: self.fan_speed_get_path = "fan{}_speed_get".format(self.index) @@ -48,6 +53,45 @@ class Fan(FanBase): self.fan_orange_led_path = "led_fan{}_orange".format(self.drawer_index) self.fan_pwm_path = "pwm1" self.fan_led_cap_path = "led_fan{}_capability".format(self.drawer_index) + if has_fan_dir: + self.fan_dir = FAN_DIR + else: + self.fan_dir = None + + + def get_direction(self): + """ + Retrieves the fan's direction + + Returns: + A string, either FAN_DIRECTION_INTAKE or FAN_DIRECTION_EXHAUST + depending on fan direction + + Notes: + What Mellanox calls forward: + Air flows from fans side to QSFP side, for example: MSN2700-CS2F + which means intake in community + What Mellanox calls reverse: + Air flow from QSFP side to fans side, for example: MSN2700-CS2R + which means exhaust in community + According to hw-mgmt: + 1 stands for forward, in other words intake + 0 stands for reverse, in other words exhaust + """ + if not self.fan_dir or self.is_psu_fan: + return self.FAN_DIRECTION_NOT_APPLICABLE + + try: + with open(os.path.join(self.fan_dir), 'r') as fan_dir: + fan_dir_bits = int(fan_dir.read()) + fan_mask = 1 << self.index - 1 + if fan_dir_bits & fan_mask: + return self.FAN_DIRECTION_INTAKE + else: + return self.FAN_DIRECTION_EXHAUST + except (ValueError, IOError) as e: + raise RuntimeError("Failed to read fan direction status to {}".format(repr(e))) + def get_status(self): """ @@ -68,6 +112,7 @@ class Fan(FanBase): return status == 1 + def get_presence(self): """ Retrieves the presence status of fan @@ -89,7 +134,8 @@ class Fan(FanBase): status = 0 return status == 1 - + + def _get_min_speed_in_rpm(self): speed = 0 try: @@ -99,7 +145,8 @@ class Fan(FanBase): speed = 0 return speed - + + def _get_max_speed_in_rpm(self): speed = 0 try: @@ -110,6 +157,7 @@ class Fan(FanBase): return speed + def get_speed(self): """ Retrieves the speed of fan @@ -129,6 +177,7 @@ class Fan(FanBase): return speed + def get_target_speed(self): """ Retrieves the expected speed of fan @@ -151,6 +200,7 @@ class Fan(FanBase): return speed + def set_speed(self, speed): """ Set fan speed to expected value @@ -176,7 +226,8 @@ class Fan(FanBase): status = False return status - + + def _get_led_capability(self): cap_list = None try: @@ -188,6 +239,7 @@ class Fan(FanBase): return cap_list + def set_status_led(self, color): """ Set led to expected color @@ -208,32 +260,70 @@ class Fan(FanBase): return False status = False try: - if color == 'green': + if color == self.STATUS_LED_COLOR_GREEN: with open(os.path.join(LED_PATH, self.fan_green_led_path), 'w') as fan_led: - fan_led.write(str(LED_ON)) - elif color == 'red': + fan_led.write(LED_ON) + status = True + elif color == self.STATUS_LED_COLOR_RED: # Some fan don't support red led but support orange led, in this case we set led to orange - if 'red' in led_cap_list: + if self.STATUS_LED_COLOR_RED in led_cap_list: led_path = os.path.join(LED_PATH, self.fan_red_led_path) - elif 'orange' in led_cap_list: + elif self.STATUS_LED_COLOR_ORANGE in led_cap_list: led_path = os.path.join(LED_PATH, self.fan_orange_led_path) else: return False with open(led_path, 'w') as fan_led: - fan_led.write(str(LED_ON)) + fan_led.write(LED_ON) + status = True + elif color == self.STATUS_LED_COLOR_OFF: + if self.STATUS_LED_COLOR_GREEN in led_cap_list: + with open(os.path.join(LED_PATH, self.fan_green_led_path), 'w') as fan_led: + fan_led.write(str(LED_OFF)) + if self.STATUS_LED_COLOR_RED in led_cap_list: + with open(os.path.join(LED_PATH, self.fan_red_led_path), 'w') as fan_led: + fan_led.write(str(LED_OFF)) + if self.STATUS_LED_COLOR_ORANGE in led_cap_list: + with open(os.path.join(LED_PATH, self.fan_orange_led_path), 'w') as fan_led: + fan_led.write(str(LED_OFF)) - elif color == 'off': - with open(os.path.join(LED_PATH, self.fan_green_led_path), 'w') as fan_led: - fan_led.write(str(LED_OFF)) - - with open(os.path.join(LED_PATH, self.fan_red_led_path), 'w') as fan_led: - fan_led.write(str(LED_OFF)) + status = True else: status = False except (ValueError, IOError): - status = False + status = False + return status + + def get_status_led(self): + """ + Gets the state of the fan status LED + + Returns: + A string, one of the predefined STATUS_LED_COLOR_* strings above + """ + led_cap_list = self._get_led_capability() + if led_cap_list is None: + return self.STATUS_LED_COLOR_OFF + + try: + with open(os.path.join(LED_PATH, self.fan_green_led_path), 'r') as fan_led: + if LED_OFF != fan_led.read().rstrip('\n'): + return self.STATUS_LED_COLOR_GREEN + if self.STATUS_LED_COLOR_RED in led_cap_list: + with open(os.path.join(LED_PATH, self.fan_red_led_path), 'r') as fan_led: + if LED_OFF != fan_led.read().rstrip('\n'): + return self.STATUS_LED_COLOR_RED + if self.STATUS_LED_COLOR_ORANGE in led_cap_list: + with open(os.path.join(LED_PATH, self.fan_orange_led_path), 'r') as fan_led: + if LED_OFF != fan_led.read().rstrip('\n'): + return self.STATUS_LED_COLOR_RED + except (ValueError, IOError) as e: + raise RuntimeError("Failed to read led status for fan {} due to {}".format(self.index, repr(e))) + + return self.STATUS_LED_COLOR_OFF + + def get_speed_tolerance(self): """ Retrieves the speed tolerance of the fan @@ -243,4 +333,4 @@ class Fan(FanBase): considered tolerable """ # The tolerance value is fixed as 20% for all the Mellanox platform - return 20 \ No newline at end of file + return 20 diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/psu.py b/platform/mellanox/mlnx-platform-api/sonic_platform/psu.py index 0789f67e4f..0e4c3fd50f 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/psu.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/psu.py @@ -16,6 +16,9 @@ try: except ImportError as e: raise ImportError (str(e) + "- required module not found") +LED_ON = '1' +LED_OFF = '0' + # Global logger class instance logger = Logger() @@ -25,6 +28,8 @@ PSU_CURRENT = "current" PSU_VOLTAGE = "voltage" PSU_POWER = "power" +LED_PATH = "/var/run/hw-management/led/" + # SKUs with unplugable PSUs: # 1. don't have psuX_status and should be treated as always present # 2. don't have voltage, current and power values @@ -50,6 +55,9 @@ psu_profile_list = [ class Psu(PsuBase): """Platform-specific Psu class""" + + STATUS_LED_COLOR_ORANGE = "orange" + def __init__(self, psu_index, sku): global psu_list PsuBase.__init__(self) @@ -90,10 +98,16 @@ class Psu(PsuBase): psu_presence = os.path.join(self.psu_path, psu_presence) self.psu_presence = psu_presence - fan = Fan(psu_index, psu_index, True) + fan = Fan(sku, psu_index, psu_index, True) if fan.get_presence(): self._fan = fan + self.psu_green_led_path = "led_psu_green" + self.psu_red_led_path = "led_psu_red" + self.psu_orange_led_path = "led_psu_orange" + self.psu_led_cap_path = "led_psu_capability" + + def _read_generic_file(self, filename, len): """ Read a generic file, returns the contents of the file @@ -106,6 +120,7 @@ class Psu(PsuBase): logger.log_info("Fail to read file {} due to {}".format(filename, repr(e))) return result + def get_powergood_status(self): """ Retrieves the operational status of power supply unit (PSU) defined @@ -117,6 +132,7 @@ class Psu(PsuBase): return status == 1 + def get_presence(self): """ Retrieves the presence status of power supply unit (PSU) defined @@ -130,6 +146,7 @@ class Psu(PsuBase): status = self._read_generic_file(self.psu_presence, 0) return status == 1 + def get_voltage(self): """ Retrieves current PSU voltage output @@ -144,6 +161,7 @@ class Psu(PsuBase): else: return None + def get_current(self): """ Retrieves present electric current supplied by PSU @@ -169,3 +187,99 @@ class Psu(PsuBase): return float(power) / 1000000 else: return None + + + def _get_led_capability(self): + cap_list = None + try: + with open(os.path.join(LED_PATH, self.psu_led_cap_path), 'r') as psu_led_cap: + caps = psu_led_cap.read() + cap_list = caps.split() + except (ValueError, IOError): + status = 0 + + return cap_list + + + def set_status_led(self, color): + """ + Sets the state of the PSU status LED + + Args: + color: A string representing the color with which to set the + PSU status LED + + Returns: + bool: True if status LED state is set successfully, False if not + + Notes: + Only one led for all PSUs. + """ + led_cap_list = self._get_led_capability() + if led_cap_list is None: + return False + + status = False + try: + if color == self.STATUS_LED_COLOR_GREEN: + with open(os.path.join(LED_PATH, self.psu_green_led_path), 'w') as psu_led: + psu_led.write(LED_ON) + status = True + elif color == self.STATUS_LED_COLOR_RED: + # Some fan don't support red led but support orange led, in this case we set led to orange + if self.STATUS_LED_COLOR_RED in led_cap_list: + led_path = os.path.join(LED_PATH, self.psu_red_led_path) + elif self.STATUS_LED_COLOR_ORANGE in led_cap_list: + led_path = os.path.join(LED_PATH, self.psu_orange_led_path) + else: + return False + with open(led_path, 'w') as psu_led: + psu_led.write(LED_ON) + status = True + elif color == self.STATUS_LED_COLOR_OFF: + if self.STATUS_LED_COLOR_GREEN in led_cap_list: + with open(os.path.join(LED_PATH, self.psu_green_led_path), 'w') as psu_led: + psu_led.write(str(LED_OFF)) + if self.STATUS_LED_COLOR_RED in led_cap_list: + with open(os.path.join(LED_PATH, self.psu_red_led_path), 'w') as psu_led: + psu_led.write(str(LED_OFF)) + if self.STATUS_LED_COLOR_ORANGE in led_cap_list: + with open(os.path.join(LED_PATH, self.psu_orange_led_path), 'w') as psu_led: + psu_led.write(str(LED_OFF)) + + status = True + else: + status = False + except (ValueError, IOError): + status = False + + return status + + + def get_status_led(self): + """ + Gets the state of the PSU status LED + + Returns: + A string, one of the predefined STATUS_LED_COLOR_* strings above + """ + led_cap_list = self._get_led_capability() + if led_cap_list is None: + return self.STATUS_LED_COLOR_OFF + + try: + with open(os.path.join(LED_PATH, self.psu_green_led_path), 'r') as psu_led: + if LED_OFF != psu_led.read().rstrip('\n'): + return self.STATUS_LED_COLOR_GREEN + if self.STATUS_LED_COLOR_RED in led_cap_list: + with open(os.path.join(LED_PATH, self.psu_red_led_path), 'r') as psu_led: + if LED_OFF != psu_led.read().rstrip('\n'): + return self.STATUS_LED_COLOR_RED + if self.STATUS_LED_COLOR_ORANGE in led_cap_list: + with open(os.path.join(LED_PATH, self.psu_orange_led_path), 'r') as psu_led: + if LED_OFF != psu_led.read().rstrip('\n'): + return self.STATUS_LED_COLOR_RED + except (ValueError, IOError) as e: + raise RuntimeError("Failed to read led status for psu due to {}".format(repr(e))) + + return self.STATUS_LED_COLOR_OFF