diff --git a/device/mellanox/x86_64-nvidia_sn2201-r0/platform.json b/device/mellanox/x86_64-nvidia_sn2201-r0/platform.json index f600f26293ce..83250c75f74e 100644 --- a/device/mellanox/x86_64-nvidia_sn2201-r0/platform.json +++ b/device/mellanox/x86_64-nvidia_sn2201-r0/platform.json @@ -90,9 +90,6 @@ { "name": "Ambient CPU Board Temp" }, - { - "name": "Ambient Switch Board Temp" - }, { "name": "CPU Pack Temp" }, diff --git a/device/mellanox/x86_64-nvidia_sn2201-r0/platform_components.json b/device/mellanox/x86_64-nvidia_sn2201-r0/platform_components.json index e28c19087875..88d4851f7176 100644 --- a/device/mellanox/x86_64-nvidia_sn2201-r0/platform_components.json +++ b/device/mellanox/x86_64-nvidia_sn2201-r0/platform_components.json @@ -4,6 +4,7 @@ "component": { "ONIE": { }, "SSD": { }, + "BIOS": { }, "CPLD1": { }, "CPLD2": { } } diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py b/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py index f68d1033f019..dc48e11da272 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py @@ -27,14 +27,17 @@ from sonic_py_common.logger import Logger import os from functools import reduce - + from .utils import extract_RJ45_ports_index from . import utils from .device_data import DeviceDataManager + from .sfp import SFP, RJ45Port, deinitialize_sdk_handle except ImportError as e: raise ImportError (str(e) + "- required module not found") MAX_SELECT_DELAY = 3600 +RJ45_TYPE = "RJ45" + DMI_FILE = '/sys/firmware/dmi/entries/2-0/raw' DMI_HEADER_LEN = 15 DMI_PRODUCT_NAME = "Product Name" @@ -106,6 +109,10 @@ def __init__(self): self.sfp_initialized_count = 0 self.sfp_event = None self.reboot_cause_initialized = False + + # Build the RJ45 port list from platform.json and hwsku.json + self.RJ45_port_list = extract_RJ45_ports_index() + logger.log_info("Chassis loaded successfully") def __del__(self): @@ -242,7 +249,10 @@ def initialize_single_sfp(self, index): if not self._sfp_list[index]: from .sfp import SFP - self._sfp_list[index] = SFP(index) + if self.RJ45_port_list and index in self.RJ45_port_list: + self._sfp_list[index] = RJ45Port(index) + else: + self._sfp_list[index] = SFP(index) self.sfp_initialized_count += 1 def initialize_sfp(self): @@ -250,14 +260,20 @@ def initialize_sfp(self): from .sfp import SFP sfp_count = self.get_num_sfps() for index in range(sfp_count): - sfp_module = SFP(index) + if self.RJ45_port_list and index in self.RJ45_port_list: + sfp_module = RJ45Port(index) + else: + sfp_module = SFP(index) self._sfp_list.append(sfp_module) self.sfp_initialized_count = sfp_count elif self.sfp_initialized_count != len(self._sfp_list): from .sfp import SFP for index in range(len(self._sfp_list)): if self._sfp_list[index] is None: - self._sfp_list[index] = SFP(index) + if self.RJ45_port_list and index in self.RJ45_port_list: + self._sfp_list[index] = RJ45Port(index) + else: + self._sfp_list[index] = SFP(index) self.sfp_initialized_count = len(self._sfp_list) def get_num_sfps(self): @@ -324,7 +340,7 @@ def get_change_event(self, timeout=0): # Initialize SFP event first if not self.sfp_event: from .sfp_event import sfp_event - self.sfp_event = sfp_event() + self.sfp_event = sfp_event(self.RJ45_port_list) self.sfp_event.initialize() wait_for_ever = (timeout == 0) @@ -340,7 +356,8 @@ def get_change_event(self, timeout=0): status = self.sfp_event.check_sfp_status(port_dict, error_dict, timeout) if status: - self.reinit_sfps(port_dict) + if port_dict: + self.reinit_sfps(port_dict) result_dict = {'sfp':port_dict} if error_dict: result_dict['sfp_error'] = error_dict @@ -515,8 +532,8 @@ def initialize_components(self): from .component import ComponentONIE, ComponentSSD, ComponentBIOS, ComponentCPLD self._component_list.append(ComponentONIE()) self._component_list.append(ComponentSSD()) - self._component_list.append(ComponentBIOS()) - self._component_list.extend(ComponentCPLD.get_component_list()) + self._component_list.append(DeviceDataManager.get_bios_component()) + self._component_list.extend(DeviceDataManager.get_cpld_component_list()) def get_num_components(self): """ diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/component.py b/platform/mellanox/mlnx-platform-api/sonic_platform/component.py index d2820090798b..6f482f497194 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/component.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/component.py @@ -29,6 +29,7 @@ import glob import tempfile import subprocess + from sonic_py_common import device_info if sys.version_info[0] > 2: import configparser else: @@ -136,7 +137,17 @@ class ONIEUpdater(object): ONIE_IMAGE_INFO_COMMAND = '/bin/bash {} -q -i' + # Upgrading fireware from ONIE is not supported from the beginning on some platforms, like SN2700. + # There is a logic to check the ONIE version in order to know whether it is supported. + # If it is not supported, we will not proceed and print some error message. + # For SN2201, upgrading fireware from ONIE is supported from day one so we do not need to check it. + PLATFORM_ALWAYS_SUPPORT_UPGRADE = ['x86_64-nvidia_sn2201-r0'] + BIOS_UPDATE_FILE_EXT = '.rom' + + + def __init__(self): + self.platform = device_info.get_platform() def __add_prefix(self, image_path): if self.BIOS_UPDATE_FILE_EXT not in image_path: @@ -336,6 +347,9 @@ def update_firmware(self, image_path, allow_reboot=True): raise def is_non_onie_firmware_update_supported(self): + if self.platform in self.PLATFORM_ALWAYS_SUPPORT_UPGRADE: + return True + current_version = self.get_onie_version() _, _, major1, minor1, release1, _ = self.parse_onie_version(current_version) version1 = int("{}{}{}".format(major1, minor1, release1)) @@ -698,6 +712,37 @@ def update_firmware(self, image_path): self.__install_firmware(image_path) +class ComponentBIOSSN2201(Component): + COMPONENT_NAME = 'BIOS' + COMPONENT_DESCRIPTION = 'BIOS - Basic Input/Output System' + + BIOS_VERSION_COMMAND = 'dmidecode -t0' + + def __init__(self): + super(ComponentBIOSSN2201, self).__init__() + + self.name = self.COMPONENT_NAME + self.description = self.COMPONENT_DESCRIPTION + + def get_firmware_version(self): + cmd = self.BIOS_VERSION_COMMAND + + try: + output = subprocess.check_output(cmd.split(), + stderr=subprocess.STDOUT, + universal_newlines=True).rstrip('\n') + except subprocess.CalledProcessError as e: + raise RuntimeError("Failed to get {} version: {}".format(self.name, str(e))) + + match = re.search('Version: (.*)', output) + if match: + version = match.group(1) + else: + version = 'Unknown version' + + return version + + class ComponentCPLD(Component): COMPONENT_NAME = 'CPLD{}' COMPONENT_DESCRIPTION = 'CPLD - Complex Programmable Logic Device' @@ -744,7 +789,7 @@ def __get_mst_device(self): return mst_dev_list[0] - def __install_firmware(self, image_path): + def _install_firmware(self, image_path): if not self._check_file_validity(image_path): return False @@ -830,9 +875,9 @@ def install_firmware(self, image_path): burn_firmware = mpfa.get_metadata().get('firmware', 'burn') print("INFO: Processing {} burn file: firmware install".format(self.name)) - return self.__install_firmware(os.path.join(mpfa.get_path(), burn_firmware)) + return self._install_firmware(os.path.join(mpfa.get_path(), burn_firmware)) else: - return self.__install_firmware(image_path) + return self._install_firmware(image_path) def update_firmware(self, image_path): with MPFAManager(image_path) as mpfa: @@ -845,11 +890,11 @@ def update_firmware(self, image_path): refresh_firmware = mpfa.get_metadata().get('firmware', 'refresh') print("INFO: Processing {} burn file: firmware install".format(self.name)) - if not self.__install_firmware(os.path.join(mpfa.get_path(), burn_firmware)): + if not self._install_firmware(os.path.join(mpfa.get_path(), burn_firmware)): return print("INFO: Processing {} refresh file: firmware update".format(self.name)) - self.__install_firmware(os.path.join(mpfa.get_path(), refresh_firmware)) + self._install_firmware(os.path.join(mpfa.get_path(), refresh_firmware)) @classmethod def get_component_list(cls): @@ -862,3 +907,19 @@ def get_component_list(cls): component_list.append(cls(cpld_idx)) return component_list + + +class ComponentCPLDSN2201(ComponentCPLD): + CPLD_FIRMWARE_UPDATE_COMMAND = 'cpldupdate --gpio {} --uncustomized --print-progress' + + def _install_firmware(self, image_path): + cmd = self.CPLD_FIRMWARE_UPDATE_COMMAND.format(image_path) + + try: + print("INFO: Installing {} firmware update: path={}".format(self.name, image_path)) + subprocess.check_call(cmd.split(), universal_newlines=True) + except subprocess.CalledProcessError as e: + print("ERROR: Failed to update {} firmware: {}".format(self.name, str(e))) + return False + + return True diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py b/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py index 17fe88037e06..94ed64d7d380 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py @@ -158,8 +158,7 @@ 'thermal': { "capability": { "comex_amb": False, - "cpu_amb": True, - "swb_amb": True + "cpu_amb": True } } }, @@ -281,3 +280,21 @@ def get_cpu_thermal_threshold(cls): return None, None return thermal_data.get('cpu_threshold', (None, None)) + + @classmethod + def get_bios_component(cls): + from .component import ComponentBIOS, ComponentBIOSSN2201 + if cls.get_platform_name() in ['x86_64-nvidia_sn2201-r0']: + # For SN2201, special chass is required for handle BIOS + # Currently, only fetching BIOS version is supported + return ComponentBIOSSN2201() + return ComponentBIOS() + + @classmethod + def get_cpld_component_list(cls): + from .component import ComponentCPLD, ComponentCPLDSN2201 + if cls.get_platform_name() in ['x86_64-nvidia_sn2201-r0']: + # For SN2201, special chass is required for handle BIOS + # Currently, only fetching BIOS version is supported + return ComponentCPLDSN2201.get_component_list() + return ComponentCPLD.get_component_list() diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/sfp.py b/platform/mellanox/mlnx-platform-api/sonic_platform/sfp.py index efaf3479b6ed..3ee8b348ce00 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/sfp.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/sfp.py @@ -80,6 +80,8 @@ '18' # QSFP-DD Double Density 8X Pluggable Transceiver ] +RJ45_TYPE = "RJ45" + #variables for sdk REGISTER_NUM = 1 DEVICE_ID = 1 @@ -235,7 +237,44 @@ def __exit__(self, exc_type, exc_val, exc_tb): deinitialize_sdk_handle(self.sdk_handle) -class SFP(SfpOptoeBase): +class NvidiaSFPCommon(SfpOptoeBase): + def __init__(self, sfp_index): + super(NvidiaSFPCommon, self).__init__() + self.index = sfp_index + 1 + self.sdk_index = sfp_index + + @property + def sdk_handle(self): + if not SFP.shared_sdk_handle: + SFP.shared_sdk_handle = initialize_sdk_handle() + if not SFP.shared_sdk_handle: + logger.log_error('Failed to open SDK handle') + return SFP.shared_sdk_handle + + @classmethod + def _get_module_info(self, sdk_handle, sdk_index): + """ + Get error code of the SFP module + + Returns: + The error code fetch from SDK API + """ + module_id_info_list = new_sx_mgmt_module_id_info_t_arr(1) + module_info_list = new_sx_mgmt_phy_module_info_t_arr(1) + + module_id_info = sx_mgmt_module_id_info_t() + module_id_info.slot_id = 0 + module_id_info.module_id = sdk_index + sx_mgmt_module_id_info_t_arr_setitem(module_id_info_list, 0, module_id_info) + + rc = sx_mgmt_phy_module_info_get(sdk_handle, module_id_info_list, 1, module_info_list) + assert SX_STATUS_SUCCESS == rc, "sx_mgmt_phy_module_info_get failed, error code {}".format(rc) + + mod_info = sx_mgmt_phy_module_info_t_arr_getitem(module_info_list, 0) + return mod_info.module_state.oper_state, mod_info.module_state.error_type + + +class SFP(NvidiaSFPCommon): """Platform-specific SFP class""" shared_sdk_handle = None SFP_MLNX_ERROR_DESCRIPTION_LONGRANGE_NON_MLNX_CABLE = 'Long range for non-Mellanox cable or module' @@ -250,13 +289,11 @@ class SFP(SfpOptoeBase): SFP_MLNX_ERROR_BIT_PCIE_POWER_SLOT_EXCEEDED = 0x00080000 SFP_MLNX_ERROR_BIT_RESERVED = 0x80000000 - def __init__(self, sfp_index, slot_id=0, linecard_port_count=0, lc_name=None): - super(SFP, self).__init__() + def __init__(self, sfp_index, sfp_type=None, slot_id=0, linecard_port_count=0, lc_name=None): + super(SFP, self).__init__(sfp_index) + self._sfp_type = sfp_type if slot_id == 0: # For non-modular chassis - self.index = sfp_index + 1 - self.sdk_index = sfp_index - from .thermal import initialize_sfp_thermal self._thermal_list = initialize_sfp_thermal(sfp_index) else: # For modular chassis @@ -281,6 +318,7 @@ def get_mst_pci_device(self): logger.log_error("Failed to find mst PCI device rc={} err.msg={}".format(e.returncode, e.output)) return device_name + ''' @property def sdk_handle(self): if not SFP.shared_sdk_handle: @@ -288,6 +326,7 @@ def sdk_handle(self): if not SFP.shared_sdk_handle: logger.log_error('Failed to open SDK handle') return SFP.shared_sdk_handle + ''' def reinit(self): """ @@ -512,7 +551,7 @@ def is_cpu(cls, port): @classmethod - def is_port_admin_status_up(cls, sdk_handle, log_port): + def _fetch_port_status(cls, sdk_handle, log_port): oper_state_p = new_sx_port_oper_state_t_p() admin_state_p = new_sx_port_admin_state_t_p() module_state_p = new_sx_port_module_state_t_p() @@ -520,12 +559,19 @@ def is_port_admin_status_up(cls, sdk_handle, log_port): assert rc == SXD_STATUS_SUCCESS, "sx_api_port_state_get failed, rc = %d" % rc admin_state = sx_port_admin_state_t_p_value(admin_state_p) + oper_state = sx_port_oper_state_t_p_value(oper_state_p) delete_sx_port_oper_state_t_p(oper_state_p) delete_sx_port_admin_state_t_p(admin_state_p) delete_sx_port_module_state_t_p(module_state_p) - return admin_state == SX_PORT_ADMIN_STATUS_UP + return oper_state, admin_state + + + @classmethod + def is_port_admin_status_up(cls, sdk_handle, log_port): + _, admin_state = cls._fetch_port_status(sdk_handle, log_port); + admin_state == SX_PORT_ADMIN_STATUS_UP @classmethod @@ -663,27 +709,6 @@ def is_replaceable(self): """ return True - def _get_error_code(self): - """ - Get error code of the SFP module - - Returns: - The error code fetch from SDK API - """ - module_id_info_list = new_sx_mgmt_module_id_info_t_arr(1) - module_info_list = new_sx_mgmt_phy_module_info_t_arr(1) - - module_id_info = sx_mgmt_module_id_info_t() - module_id_info.slot_id = 0 - module_id_info.module_id = self.sdk_index - sx_mgmt_module_id_info_t_arr_setitem(module_id_info_list, 0, module_id_info) - - rc = sx_mgmt_phy_module_info_get(self.sdk_handle, module_id_info_list, 1, module_info_list) - assert SX_STATUS_SUCCESS == rc, "sx_mgmt_phy_module_info_get failed, error code {}".format(rc) - - mod_info = sx_mgmt_phy_module_info_t_arr_getitem(module_info_list, 0) - return mod_info.module_state.oper_state, mod_info.module_state.error_type - @classmethod def _get_error_description_dict(cls): return {0: cls.SFP_ERROR_DESCRIPTION_POWER_BUDGET_EXCEEDED, @@ -704,12 +729,12 @@ def get_error_description(self): Get error description Args: - error_code: The error code returned by _get_error_code + error_code: The error code returned by _get_module_info Returns: The error description """ - oper_status, error_code = self._get_error_code() + oper_status, error_code = self._get_module_info(self.sdk_handle, self.sdk_index) if oper_status == SX_PORT_MODULE_STATUS_INITIALIZING: error_description = self.SFP_STATUS_INITIALIZING elif oper_status == SX_PORT_MODULE_STATUS_PLUGGED: @@ -727,3 +752,271 @@ def get_error_description(self): else: error_description = "Unknow SFP module status ({})".format(oper_status) return error_description + + +class RJ45Port(NvidiaSFPCommon): + """class derived from SFP, representing RJ45 ports""" + + def __init__(self, sfp_index): + super(RJ45Port, self).__init__(sfp_index) + self.sfp_type = RJ45_TYPE + + @classmethod + def _get_presence(cls, sdk_handle, sdk_index): + """Class level method to get low power mode. + + Args: + sdk_handle: SDK handle + sdk_index (integer): SDK port index + slot_id (integer): Slot ID + + Returns: + [boolean]: True if low power mode is on else off + """ + oper_status, _ = cls._get_module_info(sdk_handle, sdk_index) + return print(oper_status == SX_PORT_MODULE_STATUS_PLUGGED) + + def get_presence(self): + """ + Retrieves the presence of the device + For RJ45 ports, it always return True + + Returns: + bool: True if device is present, False if not + """ + if utils.is_host(): + # To avoid performance issue, + # call class level method to avoid initialize the whole sonic platform API + get_presence_code = 'from sonic_platform import sfp;\n' \ + 'with sfp.SdkHandleContext() as sdk_handle:' \ + 'print(sfp.RJ45Port._get_presence(sdk_handle, {}))'.format(self.sdk_index) + presence_cmd = "docker exec pmon python3 -c \"{}\"".format(get_presence_code) + try: + output = subprocess.check_output(presence_cmd, shell=True, universal_newlines=True) + return 'True' in output + except subprocess.CalledProcessError as e: + print("Error! Unable to get presence for {}, rc = {}, err msg: {}".format(self.sdk_index, e.returncode, e.output)) + return False + else: + oper_status, _ = self._get_module_info(self.sdk_handle, self.sdk_index); + return (oper_status == SX_PORT_MODULE_STATUS_PLUGGED) + + def get_transceiver_info(self): + """ + Retrieves transceiver info of this port. + For RJ45, all fields are N/A + + Returns: + A dict which contains following keys/values : + ================================================================================ + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + type |1*255VCHAR |type of SFP + vendor_rev |1*255VCHAR |vendor revision of SFP + serial |1*255VCHAR |serial number of the SFP + manufacturer |1*255VCHAR |SFP vendor name + model |1*255VCHAR |SFP model name + connector |1*255VCHAR |connector information + encoding |1*255VCHAR |encoding information + ext_identifier |1*255VCHAR |extend identifier + ext_rateselect_compliance |1*255VCHAR |extended rateSelect compliance + cable_length |INT |cable length in m + mominal_bit_rate |INT |nominal bit rate by 100Mbs + specification_compliance |1*255VCHAR |specification compliance + vendor_date |1*255VCHAR |vendor date + vendor_oui |1*255VCHAR |vendor OUI + application_advertisement |1*255VCHAR |supported applications advertisement + ================================================================================ + """ + transceiver_info_keys = ['manufacturer', + 'model', + 'vendor_rev', + 'serial', + 'vendor_oui', + 'vendor_date', + 'connector', + 'encoding', + 'ext_identifier', + 'ext_rateselect_compliance', + 'cable_type', + 'cable_length', + 'specification_compliance', + 'nominal_bit_rate', + 'application_advertisement'] + transceiver_info_dict = dict.fromkeys(transceiver_info_keys, 'N/A') + transceiver_info_dict['type'] = self.sfp_type + + return transceiver_info_dict + + def get_lpmode(self): + """ + Retrieves the lpmode (low power mode) status of this SFP + + Returns: + A Boolean, True if lpmode is enabled, False if disabled + """ + return False + + def reset(self): + """ + Reset SFP and return all user module settings to their default state. + + Returns: + A boolean, True if successful, False if not + + refer plugins/sfpreset.py + """ + return False + + def set_lpmode(self, lpmode): + """ + Sets the lpmode (low power mode) of SFP + + Args: + lpmode: A Boolean, True to enable lpmode, False to disable it + Note : lpmode can be overridden by set_power_override + + Returns: + A boolean, True if lpmode is set successfully, False if not + """ + return False + + def get_error_description(self): + """ + Get error description + + Args: + error_code: Always false on SN2201 + + Returns: + The error description + """ + return False + + def get_transceiver_bulk_status(self): + """ + Retrieves transceiver bulk status of this SFP + + Returns: + A dict which contains following keys/values : + ======================================================================== + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + RX LOS |BOOLEAN |RX lost-of-signal status, + | |True if has RX los, False if not. + TX FAULT |BOOLEAN |TX fault status, + | |True if has TX fault, False if not. + Reset status |BOOLEAN |reset status, + | |True if SFP in reset, False if not. + LP mode |BOOLEAN |low power mode status, + | |True in lp mode, False if not. + TX disable |BOOLEAN |TX disable status, + | |True TX disabled, False if not. + TX disabled channel |HEX |disabled TX channles in hex, + | |bits 0 to 3 represent channel 0 + | |to channel 3. + Temperature |INT |module temperature in Celsius + Voltage |INT |supply voltage in mV + TX bias |INT |TX Bias Current in mA + RX power |INT |received optical power in mW + TX power |INT |TX output power in mW + ======================================================================== + """ + transceiver_dom_info_dict = {} + + dom_info_dict_keys = ['temperature', 'voltage', + 'rx1power', 'rx2power', + 'rx3power', 'rx4power', + 'rx5power', 'rx6power', + 'rx7power', 'rx8power', + 'tx1bias', 'tx2bias', + 'tx3bias', 'tx4bias', + 'tx5bias', 'tx6bias', + 'tx7bias', 'tx8bias', + 'tx1power', 'tx2power', + 'tx3power', 'tx4power', + 'tx5power', 'tx6power', + 'tx7power', 'tx8power' + ] + transceiver_dom_info_dict = dict.fromkeys(dom_info_dict_keys, 'N/A') + + return transceiver_dom_info_dict + + + def get_transceiver_threshold_info(self): + """ + Retrieves transceiver threshold info of this SFP + + Returns: + A dict which contains following keys/values : + ======================================================================== + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + temphighalarm |FLOAT |High Alarm Threshold value of temperature in Celsius. + templowalarm |FLOAT |Low Alarm Threshold value of temperature in Celsius. + temphighwarning |FLOAT |High Warning Threshold value of temperature in Celsius. + templowwarning |FLOAT |Low Warning Threshold value of temperature in Celsius. + vcchighalarm |FLOAT |High Alarm Threshold value of supply voltage in mV. + vcclowalarm |FLOAT |Low Alarm Threshold value of supply voltage in mV. + vcchighwarning |FLOAT |High Warning Threshold value of supply voltage in mV. + vcclowwarning |FLOAT |Low Warning Threshold value of supply voltage in mV. + rxpowerhighalarm |FLOAT |High Alarm Threshold value of received power in dBm. + rxpowerlowalarm |FLOAT |Low Alarm Threshold value of received power in dBm. + rxpowerhighwarning |FLOAT |High Warning Threshold value of received power in dBm. + rxpowerlowwarning |FLOAT |Low Warning Threshold value of received power in dBm. + txpowerhighalarm |FLOAT |High Alarm Threshold value of transmit power in dBm. + txpowerlowalarm |FLOAT |Low Alarm Threshold value of transmit power in dBm. + txpowerhighwarning |FLOAT |High Warning Threshold value of transmit power in dBm. + txpowerlowwarning |FLOAT |Low Warning Threshold value of transmit power in dBm. + txbiashighalarm |FLOAT |High Alarm Threshold value of tx Bias Current in mA. + txbiaslowalarm |FLOAT |Low Alarm Threshold value of tx Bias Current in mA. + txbiashighwarning |FLOAT |High Warning Threshold value of tx Bias Current in mA. + txbiaslowwarning |FLOAT |Low Warning Threshold value of tx Bias Current in mA. + ======================================================================== + """ + transceiver_dom_threshold_info_dict = {} + + dom_info_dict_keys = ['temphighalarm', 'temphighwarning', + 'templowalarm', 'templowwarning', + 'vcchighalarm', 'vcchighwarning', + 'vcclowalarm', 'vcclowwarning', + 'rxpowerhighalarm', 'rxpowerhighwarning', + 'rxpowerlowalarm', 'rxpowerlowwarning', + 'txpowerhighalarm', 'txpowerhighwarning', + 'txpowerlowalarm', 'txpowerlowwarning', + 'txbiashighalarm', 'txbiashighwarning', + 'txbiaslowalarm', 'txbiaslowwarning' + ] + transceiver_dom_threshold_info_dict = dict.fromkeys(dom_info_dict_keys, 'N/A') + + return transceiver_dom_threshold_info_dict + + def get_reset_status(self): + """ + Retrieves the reset status of SFP + + Returns: + A Boolean, True if reset enabled, False if disabled + + for QSFP, originally I would like to make use of Initialization complete flag bit + which is at Page a0 offset 6 bit 0 to test whether reset is complete. + However as unit testing was carried out I find this approach may fail because: + 1. we make use of ethtool to read data on I2C bus rather than to read directly + 2. ethtool is unable to access I2C during QSFP module being reset + In other words, whenever the flag is able to be retrived, the value is always be 1 + As a result, it doesn't make sense to retrieve that flag. Just treat successfully + retrieving data as "data ready". + for SFP it seems that there is not flag indicating whether reset succeed. However, + we can also do it in the way for QSFP. + """ + return False + + def read_eeprom(self, offset, num_bytes): + return None + + def reinit(self): + """ + Nothing to do for RJ45. Just provide it to avoid exception + :return: + """ + return diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/sfp_event.py b/platform/mellanox/mlnx-platform-api/sonic_platform/sfp_event.py index 4749a6fbe710..eec88e1786df 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/sfp_event.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/sfp_event.py @@ -50,6 +50,7 @@ class MockSxFd(object): SDK_SFP_STATE_OUT = 0x2 SDK_SFP_STATE_ERR = 0x3 SDK_SFP_STATE_DIS = 0x4 +SDK_SFP_STATE_UNKNOWN = 0x5 # SFP status used in this file only, will not expose to XCVRD # STATUS_ERROR will be mapped to different status according to the error code @@ -134,13 +135,17 @@ class sfp_event: SX_OPEN_TIMEOUT = 5 SELECT_TIMEOUT = 1 - def __init__(self): + def __init__(self, rj45_port_list=None): self.swid = 0 self.handle = None # Allocate SDK fd and user channel structures self.rx_fd_p = new_sx_fd_t_p() self.user_channel_p = new_sx_user_channel_t_p() + if rj45_port_list: + self.RJ45_port_set = set(rj45_port_list) + else: + self.RJ45_port_set = set() def initialize(self): swid_cnt_p = None @@ -340,6 +345,7 @@ def on_pmpe(self, fd_p): status = False else: status = True + unknown = False pmpe_t = recv_info_p.event_info.pmpe port_list_size = pmpe_t.list_size logical_port_list = pmpe_t.log_port_list @@ -354,8 +360,11 @@ def on_pmpe(self, fd_p): logger.log_info("Receive PMPE disable event on module {}: status {}".format(module_id, module_state)) elif module_state == SDK_SFP_STATE_IN or module_state == SDK_SFP_STATE_OUT: logger.log_info("Receive PMPE plug in/out event on module {}: status {}".format(module_id, module_state)) + elif module_state == SDK_SFP_STATE_UNKNOWN: + unknown = True else: logger.log_error("Receive PMPE unknown event on module {}: status {}".format(module_id, module_state)) + for i in range(port_list_size): logical_port = sx_port_log_id_t_arr_getitem(logical_port_list, i) rc = sx_api_port_device_get(self.handle, 1 , 0, port_attributes_list, port_cnt_p) @@ -369,6 +378,14 @@ def on_pmpe(self, fd_p): if label_port is not None: label_port_list.append(label_port) + if unknown: + SFP_ports_with_unknown_event = set(label_port_list) - self.RJ45_port_set + if SFP_ports_with_unknown_event: + logger.log_error("Receive PMPE unknown event on module {}: status {}".format(module_id, module_state)) + else: + # For RJ45 ports, we treat unknown as disconnect + module_state = SDK_SFP_STATE_DIS + delete_uint32_t_p(pkt_size_p) delete_uint8_t_arr(pkt) delete_sx_receive_info_t_p(recv_info_p) diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/utils.py b/platform/mellanox/mlnx-platform-api/sonic_platform/utils.py index 22ef4bb1f27d..b35ef313dfcd 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/utils.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/utils.py @@ -16,8 +16,18 @@ # import functools import subprocess +import json +import sys +import os +from sonic_py_common import device_info from sonic_py_common.logger import Logger +HWSKU_JSON = 'hwsku.json' + +PORT_INDEX_KEY = "index" +PORT_TYPE_KEY = "port_type" +RJ45_PORT_TYPE = "RJ45" + logger = Logger() @@ -206,4 +216,51 @@ def run_command(command): process = subprocess.Popen(command, shell=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return process.communicate()[0].strip() except Exception: - return None \ No newline at end of file + return None + + +def load_json_file(filename, log_func=logger.log_error): + # load 'platform.json' or 'hwsku.json' file + data = None + try: + with open(filename) as fp: + try: + data = json.load(fp) + except json.JSONDecodeError: + if log_func: + log_func("failed to decode Json file.") + return data + except Exception as e: + if log_func: + log_func("error occurred while parsing json file: {}".format(sys.exc_info()[1])) + return None + + +def extract_RJ45_ports_index(): + # Cross check 'platform.json' and 'hwsku.json' to extract the RJ45 port index if exists. + hwsku_path = device_info.get_path_to_hwsku_dir() + platform_file = device_info.get_path_to_port_config_file() + platform_dict = load_json_file(platform_file)['interfaces'] + hwsku_file = os.path.join(hwsku_path, HWSKU_JSON) + hwsku_dict = load_json_file(hwsku_file)['interfaces'] + port_name_to_index_map_dict = {} + RJ45_port_index_list = [] + + # Compose a interface name to index mapping from 'platform.json' + for i, (key, value) in enumerate(platform_dict.items()): + if PORT_INDEX_KEY in value: + index_raw = value[PORT_INDEX_KEY] + # The index could be "1" or "1, 1, 1, 1" + index = index_raw.split(',')[0] + port_name_to_index_map_dict[key] = index + + if not bool(port_name_to_index_map_dict): + return None + + # Check if "port_type" specified as "RJ45", if yes, add the port index to the list. + for i, (key, value) in enumerate(hwsku_dict.items()): + if key in port_name_to_index_map_dict and PORT_TYPE_KEY in value and value[PORT_TYPE_KEY] == RJ45_PORT_TYPE: + RJ45_port_index_list.append(int(port_name_to_index_map_dict[key])-1) + + return RJ45_port_index_list if bool(RJ45_port_index_list) else None + diff --git a/platform/mellanox/mlnx-platform-api/tests/test_chassis.py b/platform/mellanox/mlnx-platform-api/tests/test_chassis.py index a7eff5b08625..7b153813460b 100644 --- a/platform/mellanox/mlnx-platform-api/tests/test_chassis.py +++ b/platform/mellanox/mlnx-platform-api/tests/test_chassis.py @@ -28,9 +28,11 @@ modules_path = os.path.dirname(test_path) sys.path.insert(0, modules_path) +import sonic_platform.chassis from sonic_platform.chassis import Chassis from sonic_platform.device_data import DeviceDataManager +sonic_platform.chassis.extract_RJ45_ports_index = mock.MagicMock(return_value=[]) class TestChassis: """Test class to test chassis.py. The test cases covers: diff --git a/platform/mellanox/mlnx-platform-api/tests/test_eeprom.py b/platform/mellanox/mlnx-platform-api/tests/test_eeprom.py index 2797d62a70f3..5f0a30dbf519 100644 --- a/platform/mellanox/mlnx-platform-api/tests/test_eeprom.py +++ b/platform/mellanox/mlnx-platform-api/tests/test_eeprom.py @@ -30,11 +30,11 @@ from sonic_platform.chassis import Chassis from sonic_platform.eeprom import Eeprom, EepromContentVisitor - class TestEeprom: @patch('os.path.exists', MagicMock(return_value=True)) @patch('os.path.islink', MagicMock(return_value=True)) @patch('sonic_platform.eeprom.Eeprom.get_system_eeprom_info') + @patch('sonic_platform.chassis.extract_RJ45_ports_index', MagicMock(return_value=[])) def test_chassis_eeprom(self, mock_eeprom_info): mock_eeprom_info.return_value = { hex(Eeprom._TLV_CODE_PRODUCT_NAME): 'MSN3420', @@ -102,7 +102,3 @@ def test_eeprom_content_visitor(self): v.visit_tlv('tlv3', Eeprom._TLV_CODE_VENDOR_EXT, 4, 'ext2') assert content[hex(Eeprom._TLV_CODE_PRODUCT_NAME)] == 'MSN3420' assert content[hex(Eeprom._TLV_CODE_VENDOR_EXT)] == ['ext1', 'ext2'] - - - - diff --git a/platform/mellanox/mlnx-platform-api/tests/test_led.py b/platform/mellanox/mlnx-platform-api/tests/test_led.py index 7a9ebaf056a5..1544ae35fb70 100644 --- a/platform/mellanox/mlnx-platform-api/tests/test_led.py +++ b/platform/mellanox/mlnx-platform-api/tests/test_led.py @@ -35,6 +35,7 @@ class TestLed: @mock.patch('sonic_platform.led.Led._wait_files_ready', mock.MagicMock(return_value=True)) + @mock.patch('sonic_platform.chassis.extract_RJ45_ports_index', mock.MagicMock(return_value=True)) def test_chassis_led(self): chassis = Chassis() assert chassis._led is None diff --git a/platform/mellanox/mlnx-platform-api/tests/test_module.py b/platform/mellanox/mlnx-platform-api/tests/test_module.py index 8213aa5a986a..4cba90ac95f4 100644 --- a/platform/mellanox/mlnx-platform-api/tests/test_module.py +++ b/platform/mellanox/mlnx-platform-api/tests/test_module.py @@ -26,6 +26,7 @@ modules_path = os.path.dirname(test_path) sys.path.insert(0, modules_path) +import sonic_platform.chassis from sonic_platform import utils from sonic_platform.chassis import ModularChassis from sonic_platform.device_data import DeviceDataManager @@ -37,6 +38,7 @@ class TestModule: def setup_class(cls): DeviceDataManager.get_linecard_sfp_count = mock.MagicMock(return_value=2) DeviceDataManager.get_linecard_count = mock.MagicMock(return_value=2) + sonic_platform.chassis.extract_RJ45_ports_index = mock.MagicMock(return_value=[]) def test_chassis_get_num_sfp(self): chassis = ModularChassis() diff --git a/platform/mellanox/mlnx-platform-api/tests/test_sfp.py b/platform/mellanox/mlnx-platform-api/tests/test_sfp.py index dcb3953ded88..b932856fa38c 100644 --- a/platform/mellanox/mlnx-platform-api/tests/test_sfp.py +++ b/platform/mellanox/mlnx-platform-api/tests/test_sfp.py @@ -53,8 +53,10 @@ def test_sfp_index(self, mock_max_port): assert sfp.index == 5 @mock.patch('sonic_platform.sfp.SFP.read_eeprom', mock.MagicMock(return_value=None)) - @mock.patch('sonic_platform.sfp.SFP._get_error_code') + @mock.patch('sonic_platform.sfp.SFP.shared_sdk_handle', mock.MagicMock(return_value=2)) + @mock.patch('sonic_platform.sfp.SFP._get_module_info') @mock.patch('sonic_platform.chassis.Chassis.get_num_sfps', mock.MagicMock(return_value=2)) + @mock.patch('sonic_platform.chassis.extract_RJ45_ports_index', mock.MagicMock(return_value=[])) def test_sfp_get_error_status(self, mock_get_error_code): chassis = Chassis() diff --git a/platform/mellanox/mlnx-platform-api/tests/test_thermal.py b/platform/mellanox/mlnx-platform-api/tests/test_thermal.py index 9c1526479a9b..a7fdc4d0bafe 100644 --- a/platform/mellanox/mlnx-platform-api/tests/test_thermal.py +++ b/platform/mellanox/mlnx-platform-api/tests/test_thermal.py @@ -28,9 +28,11 @@ modules_path = os.path.dirname(test_path) sys.path.insert(0, modules_path) +import sonic_platform.chassis from sonic_platform.chassis import Chassis from sonic_platform.device_data import DeviceDataManager +sonic_platform.chassis.extract_RJ45_ports_index = mock.MagicMock(return_value=[]) class TestThermal: @mock.patch('os.path.exists', mock.MagicMock(return_value=True)) @@ -334,4 +336,4 @@ def test_get_cooling_level(self, mock_read_file): mock_read_file.side_effect = ValueError('') with pytest.raises(RuntimeError): - Thermal.get_cooling_level() \ No newline at end of file + Thermal.get_cooling_level() diff --git a/platform/mellanox/mlnx-ssd-fw-update.sh b/platform/mellanox/mlnx-ssd-fw-update.sh index 07ec9ce103ea..819f02ddd3a8 100755 --- a/platform/mellanox/mlnx-ssd-fw-update.sh +++ b/platform/mellanox/mlnx-ssd-fw-update.sh @@ -21,9 +21,8 @@ #= Global variable # #= #===== -VERSION="1.5" +VERSION="1.6" #===== -SWITCH_SSD_DEV="/dev/sda" UTIL_TITLE="This is MLNX SSD firmware update utility to read and write SSD FW. Version ${VERSION}" DEPENDECIES=("smartctl" "sha256sum" "tar" "/bin/bash" "gpg" "sed" "realpath" "dirname") TRUE="0" @@ -37,6 +36,7 @@ DEBUG_MSG="DEBUG" # remove all instance after script is ready. #===== PKG_EXTRACTED=$FALSE LOGGER_UTIL=$FALSE +SSD_DEV_NAME="" SSD_FW_VER="" SSD_DEVICE_MODEL="" SSD_SERIAL="" @@ -230,7 +230,7 @@ function get_ssd_fw_version() { [ $1 ] || { LOG_MSG_AND_EXIT "Wrong usage - ${FUNCNAME[0]}()"; } local device_fw_version - device_fw_version=$(smartctl -i $SWITCH_SSD_DEV | grep -Po "Firmware Version: +\K[^,]+") + device_fw_version=$(smartctl -i $SSD_DEV_NAME | grep -Po "Firmware Version: +\K[^,]+") LOG_MSG "device_fw_version: $device_fw_version" ${DEBUG_MSG} eval $1='$device_fw_version' } @@ -242,7 +242,7 @@ function get_ssd_device_model() { [ $1 ] || { LOG_MSG_AND_EXIT "Wrong usage - ${FUNCNAME[0]}()"; } local device_model_name - device_model_name=$(smartctl -i $SWITCH_SSD_DEV | grep -Po "Device Model: +\K[^,]+") + device_model_name=$(smartctl -i $SSD_DEV_NAME | grep -E "Device Model:|Model Number:" | awk '{$1=$2="";print $0}'| sed 's/^ *//g') LOG_MSG "device_model_name: $device_model_name" ${DEBUG_MSG} eval $1='$device_model_name' } @@ -254,7 +254,7 @@ function get_ssd_size() { [ $1 ] || { LOG_MSG_AND_EXIT "Wrong usage - ${FUNCNAME[0]}()"; } local device_size - device_size=$(smartctl -i $SWITCH_SSD_DEV | grep -Po "User Capacity:.+bytes \[\K[^ ]+") + device_size=$(smartctl -i $SSD_DEV_NAME | grep -E "User Capacity:|Size/Capacity" | awk -F '\[|\]' '{print $2}' | awk '{print $1}') LOG_MSG "device_size: $device_size" ${DEBUG_MSG} eval $1='$device_size' } @@ -266,16 +266,56 @@ function get_ssd_serial() { [ $1 ] || { LOG_MSG_AND_EXIT "Wrong usage - ${FUNCNAME[0]}()"; } local device_serial - device_serial=$(smartctl -i $SWITCH_SSD_DEV | grep -Po "Serial Number: +\K[^,]+") + device_serial=$(smartctl -i $SSD_DEV_NAME | grep -Po "Serial Number: +\K[^,]+") LOG_MSG "device_serial: $device_serial" ${DEBUG_MSG} eval $1='$device_serial' } #==============================================================================# -#= This function check if given argument is valid and return boolean result. # +# This function check SSD device name # +# +function get_ssd_device_name() { + [ $1 ] || { LOG_MSG_AND_EXIT "Wrong usage - ${FUNCNAME[0]}()"; } + + non_rem_mount_disks="" + non_rem_mount_disks_count=0 + mount_parts=$(cat /proc/partitions | grep -v "^major" | grep -v ram | awk '{{print $4}}') + for blk_dev_name in ${mount_parts} + do + blk_dev_link=$(find /sys/bus /sys/class /sys/block/ -name ${blk_dev_name}) + for first_blk_dev_link in ${blk_dev_link} + do + if ls -l ${first_blk_dev_link} | grep -q virtual; then + continue + fi + if [ -e ${first_blk_dev_link}/removable ] ; then + if [ "0" = $(cat ${first_blk_dev_link}/removable) ] ; then + let non_rem_mount_disks_count=${non_rem_mount_disks_count}+1 + if [ "1" == "${non_rem_mount_disks_count}" ] ; then + non_rem_mount_disks="${blk_dev_name}" + else + non_rem_mount_disks="${non_rem_mount_disks} ${blk_dev_name}" + fi + fi + fi + break + done + done + if [ "1" == "${non_rem_mount_disks_count}" ] ; then + device_name="/dev/${non_rem_mount_disks}" + else + $1="/dev/sda" + fi + LOG_MSG "device_name: $device_name" ${DEBUG_MSG} + eval $1='$device_name' +} + +#==============================================================================# +#= This function check if given argument. # #= function get_ssd_info() { LOG_MSG "func: ${FUNCNAME[0]}()" ${DEBUG_MSG} + get_ssd_device_name SSD_DEV_NAME get_ssd_fw_version SSD_FW_VER get_ssd_device_model SSD_DEVICE_MODEL get_ssd_serial SSD_SERIAL