From 7fc5436d73373f8edf281854a863f979095c24be Mon Sep 17 00:00:00 2001 From: Aravind Mani <53524901+aravindmani-1@users.noreply.github.com> Date: Wed, 23 Aug 2023 00:11:19 +0530 Subject: [PATCH] [202012]Dell s6100 Fix sonic-mgmt platform test failures (#16207) ADO: 24709703 #### Why I did it sonic-mgmt platform testcases failed. #### How I did it Implement platform API 2.0. #### How to verify it Run sonic-mgmt tests. --- .../x86_64-dell_s6100_c2538-r0/platform.json | 12 ++ .../s6100/sonic_platform/chassis.py | 58 ++++++- .../s6100/sonic_platform/component.py | 112 +++++++++++- .../s6100/sonic_platform/fan_drawer.py | 10 ++ .../s6100/sonic_platform/module.py | 46 +++++ .../s6100/sonic_platform/psu.py | 33 ++++ .../s6100/sonic_platform/watchdog.py | 161 +++++++++++++++++- 7 files changed, 416 insertions(+), 16 deletions(-) diff --git a/device/dell/x86_64-dell_s6100_c2538-r0/platform.json b/device/dell/x86_64-dell_s6100_c2538-r0/platform.json index 8e9a0f17db..c3454198e6 100644 --- a/device/dell/x86_64-dell_s6100_c2538-r0/platform.json +++ b/device/dell/x86_64-dell_s6100_c2538-r0/platform.json @@ -15,6 +15,12 @@ }, { "name": "FPGA" + }, + { + "name": "SSD" + }, + { + "name": "ONIE" } ], "fans": [ @@ -463,6 +469,12 @@ }, { "name": "QSFP+ or later" + }, + { + "name": "SFP/SFP+/SFP28" + }, + { + "name": "SFP/SFP+/SFP28" } ] }, diff --git a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/chassis.py b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/chassis.py index b33db2e11b..6af22720f1 100755 --- a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/chassis.py +++ b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/chassis.py @@ -10,16 +10,17 @@ try: import os + import re + import time from sonic_platform_base.chassis_base import ChassisBase - from sonic_platform.sfp import Sfp - from sonic_platform.psu import Psu + from sonic_platform.component import Component + from sonic_platform.eeprom import Eeprom from sonic_platform.fan_drawer import FanDrawer from sonic_platform.module import Module + from sonic_platform.psu import Psu from sonic_platform.thermal import Thermal - from sonic_platform.component import Component - from sonic_platform.watchdog import Watchdog - from sonic_platform.eeprom import Eeprom - import time + from sonic_platform.watchdog import Watchdog, WatchdogTCO + from sonic_platform.sfp import Sfp except ImportError as e: raise ImportError(str(e) + "- required module not found") @@ -27,7 +28,7 @@ MAX_S6100_MODULE = 4 MAX_S6100_FANTRAY = 4 MAX_S6100_PSU = 2 MAX_S6100_THERMAL = 10 -MAX_S6100_COMPONENT = 3 +MAX_S6100_COMPONENT = 5 class Chassis(ChassisBase): @@ -64,6 +65,8 @@ class Chassis(ChassisBase): 'amber': 0x02, 'blinking amber': 0x08 } + _global_port_pres_dict = {} + def __init__(self): ChassisBase.__init__(self) @@ -75,6 +78,7 @@ class Chassis(ChassisBase): module = Module(i) self._module_list.append(module) self._sfp_list.extend(module._sfp_list) + #SFP ports sfp_port = 11 for index in range(64,66): @@ -101,12 +105,26 @@ class Chassis(ChassisBase): component = Component(i) self._component_list.append(component) - self._watchdog = Watchdog() + for i in self._sfp_list: + presence = i.get_presence() + if presence: + self._global_port_pres_dict[i.index] = '1' + else: + self._global_port_pres_dict[i.index] = '0' + + bios_ver = self.get_component(0).get_firmware_version() + bios_minor_ver = bios_ver.split("-")[-1] + if bios_minor_ver.isdigit() and (int(bios_minor_ver) >= 9): + self._watchdog = WatchdogTCO() + else: + self._watchdog = Watchdog() + self._transceiver_presence = self._get_transceiver_presence() def _get_reboot_reason_smf_register(self): # In S6100, mb_poweron_reason register will # Returns 0xaa or 0xcc on software reload + # Returns 0x88 on cold-reboot happened during software reload # Returns 0xff or 0xbb on power-cycle # Returns 0xdd on Watchdog # Returns 0xee on Thermal Shutdown @@ -236,6 +254,15 @@ class Chassis(ChassisBase): """ return self._eeprom.base_mac_addr() + def get_revision(self): + """ + Retrieves the hardware revision of the device + + Returns: + string: Revision value of device + """ + return self._eeprom.revision_str() + def get_system_eeprom_info(self): """ Retrieves the full content of system EEPROM information for the chassis @@ -246,6 +273,19 @@ class Chassis(ChassisBase): """ return self._eeprom.system_eeprom_info() + def get_module_index(self, module_name): + """ + Retrieves module index from the module name + + Args: + module_name: A string, prefixed by SUPERVISOR, LINE-CARD or FABRIC-CARD + Ex. SUPERVISOR0, LINE-CARD1, FABRIC-CARD5 + Returns: + An integer, the index of the ModuleBase object in the module_list + """ + module_index = re.match(r'IOM([1-4])', module_name).group(1) + return int(module_index) - 1 + def get_reboot_cause(self): """ Retrieves the cause of the previous reboot @@ -265,6 +305,8 @@ class Chassis(ChassisBase): return (ChassisBase.REBOOT_CAUSE_POWER_LOSS, None) elif ((smf_mb_reg_reason == 0xaa) or (smf_mb_reg_reason == 0xcc)): return (ChassisBase.REBOOT_CAUSE_NON_HARDWARE, None) + elif (smf_mb_reg_reason == 0x88): + return (ChassisBase.REBOOT_CAUSE_HARDWARE_OTHER, "CPU Reset") elif (smf_mb_reg_reason == 0xdd): return (ChassisBase.REBOOT_CAUSE_WATCHDOG, None) elif (smf_mb_reg_reason == 0xee): diff --git a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/component.py b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/component.py index 2e91648812..5b2b272861 100644 --- a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/component.py +++ b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/component.py @@ -10,13 +10,20 @@ ######################################################################## try: + import json import os + import re import subprocess + import tarfile from sonic_platform_base.component_base import ComponentBase except ImportError as e: raise ImportError(str(e) + "- required module not found") BIOS_QUERY_VERSION_COMMAND = "dmidecode -s system-version" +SSD_VERSION_COMMAND = "ssdutil -v" +SSD_UPGRADE_SCHEDULE = "/usr/local/bin/ssd_upgrade_schedule" +PCI_VERSION_COMMAND = "lspci -s 0:0.0" +ONIE_VERSION_COMMAND = "/usr/local/bin/onie_version" class Component(ComponentBase): @@ -29,9 +36,11 @@ class Component(ComponentBase): CHASSIS_COMPONENTS = [ ["BIOS", ("Performs initialization of hardware components during " "booting")], - ["CPLD", "Used for managing IO modules, SFP+ modules and system LEDs"], ["FPGA", ("Platform management controller for on-board temperature " - "monitoring, in-chassis power, Fan and LED control")] + "monitoring, in-chassis power, Fan and LED control")], + ["CPLD", "Used for managing IO modules, SFP+ modules and system LEDs"], + ["SSD", "Solid State Drive that stores data persistently"], + ["ONIE", "Open Network Install Environment"] ] MODULE_COMPONENT = [ "IOM{}-CPLD", @@ -125,6 +134,38 @@ class Component(ComponentBase): else: return 'NA' + def _get_ssd_version(self): + rv = 'NA' + ssd_ver = self._get_command_result(SSD_VERSION_COMMAND) + if not ssd_ver: + return rv + else: + version = re.search(r'Firmware\s*:(.*)',ssd_ver) + if version: + rv = version.group(1).strip() + return rv + + def _get_available_firmware_version(self, image_path): + if not os.path.isfile(image_path): + return False, "ERROR: File not found" + + try: + updater = tarfile.open(image_path, "r") + except tarfile.ReadError: + return False, "ERROR: Unable to extract firmware updater" + + try: + ver_info_fd = updater.extractfile("fw-component-version") + except KeyError: + updater.close() + return False, "ERROR: Version info not available" + + ver_info = json.load(ver_info_fd) + ver_info_fd.close() + updater.close() + + return True, ver_info + def get_name(self): """ Retrieves the name of the component @@ -216,10 +257,58 @@ class Component(ComponentBase): else: return bios_ver - elif self.index == 1: # SwitchCard CPLD - return self._get_cpld_version() - elif self.index == 2: # FPGA + elif self.index == 1: # FPGA return self._get_fpga_version() + elif self.index == 2: # SwitchCard CPLD + return self._get_cpld_version() + elif self.index == 3: #SSD + return self._get_ssd_version() + elif self.index == 4: # ONIE + try: + return subprocess.check_output(ONIE_VERSION_COMMAND, text=True).strip() + except (FileNotFoundError, subprocess.CalledProcessError): + return 'NA' + + def get_available_firmware_version(self, image_path): + """ + Retrieves the available firmware version of the component + + Note: the firmware version will be read from image + + Args: + image_path: A string, path to firmware image + + Returns: + A string containing the available firmware version of the component + """ + avail_ver = None + if self.index == 2: # SwitchCard CPLD + valid, version = self._get_available_firmware_version(image_path) + pci_ver = self._get_command_result(PCI_VERSION_COMMAND) + if valid: + if pci_ver: + board_ver = re.search(r"\(rev ([0-9]{2})\)$", pci_ver) + if board_ver: + board_ver = board_ver.group(1).strip() + board_type = 'B0' if board_ver == '02' else 'C0' + cpld_ver = self._get_cpld_version() + avail_ver = version.get(board_type) if board_type == 'B0' else cpld_ver + else: + print(version) + + elif self.index == 3: # SSD + valid, version = self._get_available_firmware_version(image_path) + ssd_ver = self._get_command_result(SSD_VERSION_COMMAND) + if valid: + if ssd_ver: + ssd_model = re.search(r'Device Model\s*:.*(3IE[3]{0,1})', ssd_ver) + if ssd_model: + ssd_model = ssd_model.group(1).strip() + avail_ver = version.get(ssd_model) + else: + print(version) + + return avail_ver if avail_ver else "NA" def install_firmware(self, image_path): """ @@ -232,3 +321,16 @@ class Component(ComponentBase): A boolean, True if install was successful, False if not """ return False + + def update_firmware(self,image_path): + """ + Updates firmware to the componenent + + Args: + image_path: A string, path to firmware image + + Returns: + A boolean, True if install was successful, False if not + + """ + return False diff --git a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/fan_drawer.py b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/fan_drawer.py index 41e870a639..2f38be9cd5 100644 --- a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/fan_drawer.py +++ b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/fan_drawer.py @@ -159,3 +159,13 @@ class FanDrawer(FanDrawerBase): return self.STATUS_LED_COLOR_AMBER else: return self.STATUS_LED_COLOR_OFF + + def get_maximum_consumed_power(self): + """ + Retrives the maximum power drawn by Fan Drawer + + Returns: + A float, with value of the maximum consumable power of the + component. + """ + return 54.0 diff --git a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/module.py b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/module.py index 923bb5c055..72ccac3c8e 100644 --- a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/module.py +++ b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/module.py @@ -198,3 +198,49 @@ class Module(ModuleBase): ‘0x26’:’01’, ‘0x27’:’REV01’, ‘0x28’:’AG9064-C2358-16G’} """ return self._eeprom.system_eeprom_info() + + def get_description(self): + """ + Retrieves the platform vendor's product description of the module + + Returns: + A string, providing the vendor's product description of the module. + """ + return self._eeprom.modelstr() + + def get_slot(self): + """ + Retrieves the platform vendor's slot number of the module + + Returns: + An integer, indicating the slot number in the chassis + """ + return self.index + + def get_oper_status(self): + """ + Retrieves the operational status of the module + + Returns: + A string, the operational status of the module from one of the + predefined status values: MODULE_STATUS_EMPTY, MODULE_STATUS_OFFLINE, + MODULE_STATUS_FAULT, MODULE_STATUS_PRESENT or MODULE_STATUS_ONLINE + """ + if self.get_presence(): + if self.get_status(): + return self.MODULE_STATUS_ONLINE + else: + return self.MODULE_STATUS_PRESENT + else: + return self.MODULE_STATUS_EMPTY + + def get_maximum_consumed_power(self): + """ + Retrives the maximum power drawn by this module + + Returns: + A float, with value of the maximum consumable power of the + module. + """ + return 97.23 + diff --git a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/psu.py b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/psu.py index 4e528b679e..8bfc0ddf2b 100644 --- a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/psu.py +++ b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/psu.py @@ -34,11 +34,13 @@ class Psu(PsuBase): self.psu_voltage_reg = "in30_input" self.psu_current_reg = "curr602_input" self.psu_power_reg = "power2_input" + self.psu_maxpower_reg = "power2_max" self.psu_temperature_reg = "temp14_input" elif self.index == 2: self.psu_voltage_reg = "in32_input" self.psu_current_reg = "curr702_input" self.psu_power_reg = "power4_input" + self.psu_maxpower_reg = "power4_max" self.psu_temperature_reg = "temp15_input" # Passing True to specify it is a PSU fan @@ -124,6 +126,19 @@ class Psu(PsuBase): return psu_serialno + def get_revision(self): + """ + Retrieves the hardware revision of the device + + Returns: + string: Revision value of device + """ + serial = self.get_serial() + if serial != "NA" and len(serial) == 28: + return serial[-3:] + else: + return "NA" + def get_status(self): """ Retrieves the operational status of the PSU @@ -195,6 +210,24 @@ class Psu(PsuBase): return psu_power + def get_maximum_supplied_power(self): + """ + Retrieves the maximum supplied power by PSU + + Returns: + A float number, the maximum power output in Watts. + e.g. 1200.1 + """ + psu_maxpower = self._get_pmc_register(self.psu_maxpower_reg) + if (psu_maxpower != 'ERR') and self.get_presence(): + # Converting the value returned by driver which is in + # microwatts to watts + psu_maxpower = float(psu_maxpower) / 1000000 + else: + psu_maxpower = 0.0 + + return psu_maxpower + def get_powergood_status(self): """ Retrieves the powergood status of PSU diff --git a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/watchdog.py b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/watchdog.py index 177315ef18..7090539baf 100644 --- a/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/watchdog.py +++ b/platform/broadcom/sonic-platform-modules-dell/s6100/sonic_platform/watchdog.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - ######################################################################## # # DELLEMC S6100 @@ -10,13 +8,30 @@ ######################################################################## try: + import array + import ctypes + import fcntl + import glob import os import struct - import ctypes from sonic_platform_base.watchdog_base import WatchdogBase except ImportError as e: raise ImportError(str(e) + "- required module not found") +# ioctl constants +IOC_WRITE = 0x40000000 +IOC_READ = 0x80000000 +IOC_SIZE_INT = 0x00040000 + +WATCHDOG_IOCTL_BASE = ord('W') + +WDIOC_SETOPTIONS = IOC_READ | IOC_SIZE_INT | (WATCHDOG_IOCTL_BASE << 8) | 4 +WDIOC_KEEPALIVE = IOC_READ | IOC_SIZE_INT | (WATCHDOG_IOCTL_BASE << 8) | 5 +WDIOC_SETTIMEOUT = IOC_READ | IOC_WRITE | IOC_SIZE_INT | (WATCHDOG_IOCTL_BASE << 8) | 6 + +WDIOS_DISABLECARD = 0x0001 +WDIOS_ENABLECARD = 0x0002 + class _timespec(ctypes.Structure): _fields_ = [ @@ -24,6 +39,7 @@ class _timespec(ctypes.Structure): ('tv_nsec', ctypes.c_long) ] + class Watchdog(WatchdogBase): """ Abstract base class for interfacing with a hardware watchdog module @@ -226,3 +242,142 @@ class Watchdog(WatchdogBase): return 0 + +class WatchdogTCO(WatchdogBase): + """ + Watchdog class for interfacing with iTCO watchdog + """ + + IDENTITY = "iTCO_wdt" + + def __init__(self): + + self.dev = None + self.dev_name = None + wd_sysfs_path = "/sys/class/watchdog" + + for dev_file in glob.glob("/dev/watchdog*"): + dev = os.path.basename(dev_file) + dev_identity = self._read_file("{}/{}/identity".format(wd_sysfs_path, dev)) + if dev_identity == self.IDENTITY: + self.dev_name = dev + break + + if self.dev_name is None: + raise RuntimeError("{} is not initialized".format(self.IDENTITY)) + + self.state_file = "{}/{}/state".format(wd_sysfs_path, self.dev_name) + self.timeout_file = "{}/{}/timeout".format(wd_sysfs_path, self.dev_name) + self.timeleft_file = "{}/{}/timeleft".format(wd_sysfs_path, self.dev_name) + + def __del__(self): + if self.dev is not None: + os.close(self.dev) + + def _ioctl(self, request, arg=0, mutate_flag=True): + """ + Perform ioctl on watchdog device + """ + self._open_wd_dev() + fcntl.ioctl(self.dev, request, arg, mutate_flag) + + def _open_wd_dev(self): + """ + Open watchdog device file + """ + if self.dev is None: + wd_dev = "/dev/{}".format(self.dev_name) + self.dev = os.open(wd_dev, os.O_RDWR) + + @staticmethod + def _read_file(file_path): + """ + Read a file + """ + try: + with open(file_path, "r") as fd: + read_str = fd.read() + except OSError: + return -1 + + return read_str.strip() + + def arm(self, seconds): + """ + Arm the hardware watchdog with a timeout of seconds. + If the watchdog is currently armed, calling this function will + simply reset the timer to the provided value. If the underlying + hardware does not support the value provided in , this + method should arm the watchdog with the *next greater* + available value. + + Returns: + An integer specifying the *actual* number of seconds the + watchdog was armed with. On failure returns -1. + """ + if seconds < 0 or seconds > 0x3ff: + return -1 + if seconds < 4: + seconds = 4 + + try: + timeout = int(self._read_file(self.timeout_file)) + if timeout != seconds: + buf = array.array('I', [seconds]) + self._ioctl(WDIOC_SETTIMEOUT, buf) + timeout = int(buf[0]) + + if self.is_armed(): + self._ioctl(WDIOC_KEEPALIVE) + else: + buf = array.array('h', [WDIOS_ENABLECARD]) + self._ioctl(WDIOC_SETOPTIONS, buf, False) + except OSError: + return -1 + else: + return timeout + + def disarm(self): + """ + Disarm the hardware watchdog + + Returns: + A boolean, True if watchdog is disarmed successfully, False + if not + """ + disarmed = True + if self.is_armed(): + try: + buf = array.array('h', [WDIOS_DISABLECARD]) + self._ioctl(WDIOC_SETOPTIONS, buf, False) + except OSError: + disarmed = False + + return disarmed + + def is_armed(self): + """ + Retrieves the armed state of the hardware watchdog. + + Returns: + A boolean, True if watchdog is armed, False if not + """ + state = self._read_file(self.state_file) + return state == "active" + + def get_remaining_time(self): + """ + If the watchdog is armed, retrieve the number of seconds + remaining on the watchdog timer + + Returns: + An integer specifying the number of seconds remaining on + their watchdog timer. If the watchdog is not armed, returns + -1. + + """ + timeleft = -1 + if self.is_armed(): + timeleft = int(self._read_file(self.timeleft_file)) + + return timeleft