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 e28c1908787..88d4851f717 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 6a712af68ff..d5e5bf8d716 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py @@ -27,15 +27,17 @@ from sonic_py_common.logger import Logger import os from functools import reduce - + from .utils import load_json_file, extract_RJ45_ports_index from . import utils from .device_data import DeviceDataManager - from .sfp import SFP, deinitialize_sdk_handle + 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" @@ -107,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): @@ -241,7 +247,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): @@ -249,14 +258,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): @@ -323,7 +338,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) @@ -339,7 +354,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 @@ -514,7 +530,10 @@ 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()) + biosComponent = DeviceDataManager.get_bios_component() + if not biosComponent: + biosComponent = ComponentBIOS() + self._component_list.append(biosComponent) self._component_list.extend(ComponentCPLD.get_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 37f04f9a1de..4009762d041 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' 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 b4610fe045f..a029f08fb8b 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/device_data.py @@ -263,3 +263,12 @@ def get_linecard_max_port_count(cls): if not sfp_data: return 0 return sfp_data.get('max_port_per_line_card', 0) + + @classmethod + def get_bios_component(cls): + if cls.get_platform_name() in ['x86_64-nvidia_sn2201-r0']: + from .component import ComponentBIOSSN2201 + # For SN2201, special chass is required for handle BIOS + # Currently, only fetching BIOS version is supported + return ComponentBIOSSN2201() + return None diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/sfp.py b/platform/mellanox/mlnx-platform-api/sonic_platform/sfp.py index 59f71bebc9a..5c43ae3d9e7 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/sfp.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/sfp.py @@ -276,6 +276,7 @@ QSFP_TYPE = "QSFP" OSFP_TYPE = "OSFP" QSFP_DD_TYPE = "QSFP_DD" +RJ45_TYPE = "RJ45" #variables for sdk REGISTER_NUM = 1 @@ -386,15 +387,18 @@ class SFP(SfpBase): 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): + def __init__(self, sfp_index, sfp_type=None, slot_id=0, linecard_port_count=0, lc_name=None): super(SFP, self).__init__() + 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) + if self._sfp_type != RJ45_TYPE: + # No thermal sensors for RJ45 ports so we do not need to initiallize them. + from .thermal import initialize_sfp_thermal + self._thermal_list = initialize_sfp_thermal(sfp_index) else: # For modular chassis # (slot_id % MAX_LC_CONUNT - 1) * MAX_PORT_COUNT + (sfp_index + 1) * (MAX_PORT_COUNT / LC_PORT_COUNT) max_linecard_count = DeviceDataManager.get_linecard_count() @@ -402,11 +406,11 @@ def __init__(self, sfp_index, slot_id=0, linecard_port_count=0, lc_name=None): self.index = (slot_id % max_linecard_count - 1) * max_linecard_port_count + sfp_index * (max_linecard_port_count / linecard_port_count) + 1 self.sdk_index = sfp_index - from .thermal import initialize_linecard_sfp_thermal - self._thermal_list = initialize_linecard_sfp_thermal(lc_name, slot_id, sfp_index) + if self._sfp_type != RJ45_TYPE: + from .thermal import initialize_linecard_sfp_thermal + self._thermal_list = initialize_linecard_sfp_thermal(lc_name, slot_id, sfp_index) self.slot_id = slot_id - self._sfp_type = None self._sfp_capability = None @property @@ -627,7 +631,8 @@ def reinit(self): Re-initialize this SFP object when a new SFP inserted :return: """ - self._sfp_type = None + if self.sfp_type != RJ45_TYPE: + self._sfp_type = None self._sfp_capability = None def get_presence(self): @@ -1043,7 +1048,7 @@ def get_transceiver_bulk_status(self): ] transceiver_dom_info_dict = dict.fromkeys(dom_info_dict_keys, 'N/A') - if self.sfp_type == OSFP_TYPE: + if self.sfp_type == OSFP_TYPE or self.sfp_type == RJ45_TYPE: pass elif self.sfp_type == QSFP_TYPE: @@ -1242,7 +1247,10 @@ def get_transceiver_threshold_info(self): ] transceiver_dom_threshold_info_dict = dict.fromkeys(dom_info_dict_keys, 'N/A') - if self.sfp_type == OSFP_TYPE: + if not self.dom_supported: + return transceiver_dom_threshold_info_dict + + if self.sfp_type == OSFP_TYPE or self.sfp_type == RJ45_TYPE: pass elif self.sfp_type == QSFP_TYPE: @@ -1810,6 +1818,9 @@ def get_tx_bias(self): for channel 0 to channel 4. Ex. ['110.09', '111.12', '108.21', '112.09'] """ + if not self.dom_supported: + return None + tx_bias_list = [] if self.sfp_type == QSFP_TYPE: offset = 0 @@ -1878,6 +1889,9 @@ def get_rx_power(self): power in mW for channel 0 to channel 4. Ex. ['1.77', '1.71', '1.68', '1.70'] """ + if not self.dom_supported: + return None + rx_power_list = [] if self.sfp_type == OSFP_TYPE: # OSFP not supported on our platform yet. @@ -1955,6 +1969,9 @@ def get_tx_power(self): for channel 0 to channel 4. Ex. ['1.86', '1.86', '1.86', '1.86'] """ + if not self.dom_supported: + return None + tx_power_list = [] if self.sfp_type == OSFP_TYPE: # OSFP not supported on our platform yet. @@ -2109,7 +2126,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() @@ -2117,12 +2134,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(cls, sdk_handle, log_port); + admin_state == SX_PORT_ADMIN_STATUS_UP @classmethod @@ -2346,3 +2370,125 @@ def get_error_description(self): else: error_description = "Unknow SFP module status ({})".format(oper_status) return error_description + + +class RJ45Port(SFP): + """class derived from SFP, representing RJ45 ports""" + + def __init__(self, sfp_index): + super(RJ45Port, self).__init__(sfp_index, RJ45_TYPE) + self._sfp_type = RJ45_TYPE + + @property + def sfp_type(self): + return self._sfp_type + + 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 + """ + return True + + @property + def dom_supported(self): + return False + + 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_reset_status(self): + return False + + 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: The error code returned by _get_error_code + + Returns: + The error description + """ + return False + 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 06948af3286..2d7efa1b070 100644 --- a/platform/mellanox/mlnx-platform-api/sonic_platform/sfp_event.py +++ b/platform/mellanox/mlnx-platform-api/sonic_platform/sfp_event.py @@ -115,6 +115,19 @@ class MockSxFd(object): SDK_SFP_STATE_DIS: str(SFP.SFP_STATUS_BIT_REMOVED), } +""" +RJ45 ports events definition +For RJ45, get_present always returns True. +In case an unplug / unknown event is reported for a port, error status is leveraged for representing them. +The following events will be used. +- Unknown: 2147483648 => 0x80000000 +- Unplug: 1073741824 => 0x40000000 +According to the error status design, the upper half (bits 16 ~ 31) are the events are encoded from bit 16 (0x0001000), +using those numbers will avoid conflict as much as possible +""" +RJ45_UNPLUG_EVENT = '1073741824' +RJ45_UNKNOWN_EVENT = '2147483648' + # system level event/error EVENT_ON_ALL_SFP = '-1' SYSTEM_NOT_READY = 'system_not_ready' @@ -134,7 +147,7 @@ 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 @@ -142,6 +155,9 @@ def __init__(self): self.rx_fd_p = new_sx_fd_t_p() self.user_channel_p = new_sx_user_channel_t_p() + self.RJ45_port_list = rj45_port_list + self.absent_ports_before_init = [] + def initialize(self): swid_cnt_p = None @@ -205,6 +221,30 @@ def initialize(self): if rc != SX_STATUS_SUCCESS: raise RuntimeError("sx_api_host_ifc_trap_id_register_set failed with rc {}, exiting...".format(rc)) + + if self.RJ45_port_list: + # Fetch the present state of each RJ45 port. + 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) + absent_port_list = [] + + for i in self.RJ45_port_list: + module_id_info = sx_mgmt_module_id_info_t() + module_id_info.slot_id = 0 + module_id_info.module_id = i + sx_mgmt_module_id_info_t_arr_setitem(module_id_info_list, 0, module_id_info) + + rc = sx_mgmt_phy_module_info_get(self.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) + if mod_info.module_state.oper_state not in [SX_PORT_MODULE_STATUS_PLUGGED, SX_PORT_MODULE_STATUS_PLUGGED_DISABLED]: + absent_port_list.append(i) + + self.absent_ports_before_init = absent_port_list + + delete_sx_mgmt_module_id_info_t_arr(module_id_info_list) + delete_sx_mgmt_phy_module_info_t_arr(module_info_list) except Exception as e: logger.log_error("sfp_event initialization failed due to {}, exiting...".format(repr(e))) if swid_cnt_p is not None: @@ -254,6 +294,15 @@ def check_sfp_status(self, port_change, error_dict, timeout): to repeat calling it with timeout = 0 in a loop until no new notification read (in this case it returns false). by doing so all the notifications in the fd can be retrieved through a single call to get_change_event. """ + if self.absent_ports_before_init: + # This is the first time this method is called. + # We should return the ports that are not present during initialization + for i in self.absent_ports_before_init: + error_dict[i + 1] = 'Not present' + port_change[i + 1] = RJ45_UNPLUG_EVENT + self.absent_ports_before_init = [] + return True + found = 0 try: @@ -284,7 +333,16 @@ def check_sfp_status(self, port_change, error_dict, timeout): # 3. and then the sfp module is removed # 4. sfp_event starts to try fetching the change event # in this case found is increased so that True will be returned - logger.log_info("unknown module state {}, maybe the port suffers two adjacent insertion/removal".format(module_state)) + # For RJ45 ports, we will report "unknown" event anyway since it's legal + reported = 0 + if self.RJ45_port_list: + for port in port_list: + if port in self.RJ45_port_list: + port_change[port+1] = sfp_state + reported += 1 + + if not reported: + logger.log_info("unknown module state {}, maybe the port suffers two adjacent insertion/removal".format(module_state)) found += 1 continue @@ -315,6 +373,32 @@ def check_sfp_status(self, port_change, error_dict, timeout): error_dict[port+1] = error_description found += 1 + if self.RJ45_port_list: + # Translate any plugin/plugout event into error dict for RJ45 ports + unknown_port = set() + unplug_port = set() + for index, event in port_change.items(): + if index in self.RJ45_port_list: + # Remove it from port event + # check if it's unknown event + if event == '0': # Remove event + unplug_port.add(index) + elif event != '1': + unknown_port.add(index) + # This is to leverage TRANSCEIVER_STATUS table to represent 'Not present' and 'Unknown' state + # The event should satisfies: + # - Vendor specific error bit + # - Non-blocking bit + # Currently, the 2 MSBs are reserved for this since they are most unlikely to be used in the near future + for index in unknown_port: + # Bit 31 for unknown + port_change[index] = RJ45_UNKNOWN_EVENT + error_dict[index] = 'Unknown' + for index in unplug_port: + # Bit 30 for not present + port_change[index] = RJ45_UNPLUG_EVENT + error_dict[index] = 'Not present' + return found != 0 def on_pmpe(self, fd_p): diff --git a/platform/mellanox/mlnx-platform-api/sonic_platform/utils.py b/platform/mellanox/mlnx-platform-api/sonic_platform/utils.py index 0650d9af1a1..4434771cc11 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() @@ -194,3 +204,50 @@ def _impl(*args, **kwargs): return return_value return _impl return wrapper + + +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 cfa2d822471..dae686f6d3d 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 2797d62a70f..5f0a30dbf51 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 7a9ebaf056a..1544ae35fb7 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 8213aa5a986..4cba90ac95f 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 0ad9537430b..8e5444d38ad 100644 --- a/platform/mellanox/mlnx-platform-api/tests/test_sfp.py +++ b/platform/mellanox/mlnx-platform-api/tests/test_sfp.py @@ -52,6 +52,7 @@ def test_sfp_index(self, mock_max_port): @mock.patch('sonic_platform.sfp.SFP._read_eeprom_specific_bytes', mock.MagicMock(return_value=None)) @mock.patch('sonic_platform.sfp.SFP._get_error_code') @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_sfp_event.py b/platform/mellanox/mlnx-platform-api/tests/test_sfp_event.py index ef4820ecfd8..ce3022df250 100644 --- a/platform/mellanox/mlnx-platform-api/tests/test_sfp_event.py +++ b/platform/mellanox/mlnx-platform-api/tests/test_sfp_event.py @@ -31,7 +31,7 @@ def setup_class(cls): os.environ["MLNX_PLATFORM_API_UNIT_TESTING"] = "1" @patch('select.select', MagicMock(return_value=([99], None, None))) - def test_check_sfp_status(self): + def test_check_sfp_status_xsfp(self): from sonic_platform.sfp_event import SDK_SFP_STATE_IN, SDK_SFP_STATE_OUT, SDK_SFP_STATE_ERR from sonic_platform.sfp_event import SDK_ERRORS_TO_ERROR_BITS, SDK_ERRORS_TO_DESCRIPTION, SDK_SFP_BLOCKING_ERRORS @@ -59,3 +59,46 @@ def executor(self, mock_module_state, mock_error_type, expect_status, descriptio if description: assert 1 in error_dict and error_dict[1] == description assert 2 in error_dict and error_dict[2] == description + + @patch('select.select', MagicMock(return_value=([99], None, None))) + def test_check_sfp_status_rj45(self): + from sonic_platform.sfp_event import sfp_event + from sonic_platform.sfp_event import SDK_SFP_STATE_IN, SDK_SFP_STATE_OUT + from sonic_platform.sfp_event import RJ45_UNPLUG_EVENT, RJ45_UNKNOWN_EVENT + + # Verify absent ports before initialization + event = sfp_event([0, 1, 2, 3]) + event.absent_ports_before_init = [0,1] + port_change = {} + error_dict = {} + found = event.check_sfp_status(port_change, error_dict, 0) + assert found + assert port_change == {1: RJ45_UNPLUG_EVENT, 2: RJ45_UNPLUG_EVENT} + assert error_dict == {1: 'Not present', 2: 'Not present'} + + # Unplug event + event.on_pmpe = MagicMock(return_value=(True, [2], SDK_SFP_STATE_OUT, None)) + port_change.clear() + error_dict.clear() + found = event.check_sfp_status(port_change, error_dict, 0) + assert found + assert port_change == {3: RJ45_UNPLUG_EVENT} + assert error_dict == {3: 'Not present'} + + # Plug event + event.on_pmpe = MagicMock(return_value=(True, [2], SDK_SFP_STATE_IN, None)) + port_change.clear() + error_dict.clear() + found = event.check_sfp_status(port_change, error_dict, 0) + assert found + assert port_change == {3: str(SDK_SFP_STATE_IN)} + assert error_dict == {} + + # Unknown event + event.on_pmpe = MagicMock(return_value=(True, [2], -1, None)) + port_change.clear() + error_dict.clear() + found = event.check_sfp_status(port_change, error_dict, 0) + assert found + assert port_change == {3: RJ45_UNKNOWN_EVENT} + assert error_dict == {3: 'Unknown'} diff --git a/platform/mellanox/mlnx-platform-api/tests/test_thermal.py b/platform/mellanox/mlnx-platform-api/tests/test_thermal.py index 89c940d8926..48bebf3d533 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 7a180bde7bc..fd0d4b66973 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