From b55a2aec979259fb16d71460dcf03aaca4ab6abf Mon Sep 17 00:00:00 2001 From: Arun Saravanan Balachandran Date: Thu, 14 Oct 2021 10:19:51 +0530 Subject: [PATCH] DellEMC: Z9332f - Component firmware upgrade platform API implementation --- .../platform.json | 6 + .../common/onie_stage_fwpkg | 70 +++++ .../debian/platform-modules-z9332f.install | 1 + .../z9332f/sonic_platform/chassis.py | 2 +- .../z9332f/sonic_platform/component.py | 283 +++++++++++++++++- 5 files changed, 346 insertions(+), 16 deletions(-) create mode 100755 platform/broadcom/sonic-platform-modules-dell/common/onie_stage_fwpkg diff --git a/device/dell/x86_64-dellemc_z9332f_d1508-r0/platform.json b/device/dell/x86_64-dellemc_z9332f_d1508-r0/platform.json index e8add067ca00..2c603dd722fa 100644 --- a/device/dell/x86_64-dellemc_z9332f_d1508-r0/platform.json +++ b/device/dell/x86_64-dellemc_z9332f_d1508-r0/platform.json @@ -19,6 +19,12 @@ }, { "name": "Switch CPLD 2" + }, + { + "name": "SSD" + }, + { + "name": "PCIe" } ], "fans": [ diff --git a/platform/broadcom/sonic-platform-modules-dell/common/onie_stage_fwpkg b/platform/broadcom/sonic-platform-modules-dell/common/onie_stage_fwpkg new file mode 100755 index 000000000000..1ceaf32fe8c1 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-dell/common/onie_stage_fwpkg @@ -0,0 +1,70 @@ +#!/bin/bash + +ONIE_PATH="/mnt/onie-boot" +ONIE_PENDING_DIR="${ONIE_PATH}/onie/update/pending" + +unset FWPKG + +function stage_fwpkg() +{ + local name=$(basename ${FWPKG}) + local pending="${ONIE_PENDING_DIR}/$name" + + # Exit if not superuser + if [[ "$EUID" -ne 0 ]]; then + echo "ERROR: This command must be run as root" >&2 + exit 1 + fi + + # Mount ONIE partition if not already mounted + if ! grep -qs ${ONIE_PATH} /proc/mounts; then + mkdir -p ${ONIE_PATH} + mount LABEL=ONIE-BOOT ${ONIE_PATH} || { + echo "ERROR: Failed to mount ONIE partition" + exit 1 + } + fi + + [ -f "$pending" ] && { + echo "INFO: Firmware update package ${name} is already staged" + exit 2 + } + + ${ONIE_PATH}/onie/tools/bin/onie-fwpkg add ${FWPKG} || { + echo "ERROR: onie-fwpkg add for ${name} failed" + exit 1 + } +} + +SCRIPT=$0 + +function show_help_and_exit() +{ + echo "Usage ${SCRIPT} [options]" + echo " This script will stage ONIE firmware update package." + echo " " + echo " Available options:" + echo " -h, -? : getting this help" + echo " -o [fwpkg] : stages the firmware update package" + + exit 0 +} + + +function parse_options() +{ + while getopts ":h?a:" opt; do + case $opt in + a ) + FWPKG=$(realpath $OPTARG) + stage_fwpkg + ;; + h|\? ) + show_help_and_exit + ;; + esac + done +} + +parse_options $@ +exit 0 diff --git a/platform/broadcom/sonic-platform-modules-dell/debian/platform-modules-z9332f.install b/platform/broadcom/sonic-platform-modules-dell/debian/platform-modules-z9332f.install index 9915b08b72ee..6d12d4e1732a 100644 --- a/platform/broadcom/sonic-platform-modules-dell/debian/platform-modules-z9332f.install +++ b/platform/broadcom/sonic-platform-modules-dell/debian/platform-modules-z9332f.install @@ -9,3 +9,4 @@ common/platform_reboot usr/share/sonic/device/x86_64-dellemc_z9332f_d1508-r0 common/pcisysfs.py usr/bin common/fw-updater usr/local/bin common/onie_mode_set usr/local/bin +common/onie_stage_fwpkg usr/local/bin diff --git a/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/chassis.py b/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/chassis.py index fac058f45207..0b1f6036782c 100755 --- a/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/chassis.py +++ b/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/chassis.py @@ -28,7 +28,7 @@ MAX_Z9332F_FAN = 2 MAX_Z9332F_PSU = 2 MAX_Z9332F_THERMAL = 14 -MAX_Z9332F_COMPONENT = 6 # BIOS,FPGA,BMC,BB CPLD and 2 Switch CPLDs +MAX_Z9332F_COMPONENT = 8 # BIOS,FPGA,BMC,BB CPLD,2 Switch CPLDs,SSD and PCIe media_part_num_list = set([ \ "8T47V","XTY28","MHVPK","GF76J","J6FGD","F1KMV","9DN5J","H4DHD","6MCNV","0WRX0","X7F70","5R2PT","WTRD1","WTRD1","WTRD1","WTRD1","5250G","WTRD1","C5RNH","C5RNH","FTLX8571D3BCL-FC", diff --git a/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/component.py b/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/component.py index 01bcdeffe688..7c992be940de 100644 --- a/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/component.py +++ b/platform/broadcom/sonic-platform-modules-dell/z9332f/sonic_platform/component.py @@ -10,10 +10,14 @@ ######################################################################## try: + import json + import os + import re import subprocess + import tarfile + import tempfile from sonic_platform_base.component_base import ComponentBase import sonic_platform.hwaccess as hwaccess - except ImportError as e: raise ImportError(str(e) + "- required module not found") @@ -24,8 +28,8 @@ def get_bios_version(): def get_fpga_version(): val = hwaccess.pci_get_value('/sys/bus/pci/devices/0000:09:00.0/resource0', 0) - return '{}.{}'.format((val >> 8) & 0xff, val & 0xff) - + return '{}.{}'.format((val >> 16) & 0xffff, val & 0xffff) + def get_bmc_version(): return subprocess.check_output( ['cat', '/sys/class/ipmi/ipmi0/device/bmc/firmware_revision'] @@ -43,6 +47,31 @@ def get_cpld1_version(): def get_cpld2_version(): return get_cpld_version(36, 0x31) +def get_ssd_version(): + val = 'NA' + try: + ssd_ver = subprocess.check_output(['ssdutil','-v'], text=True) + except Exception: + return val + else: + version = re.search(r'Firmware\s*:(.*)',ssd_ver) + if version: + val = version.group(1).strip() + + return val + +def get_pciephy_version(): + val = 'NA' + try: + pcie_ver = subprocess.check_output('bcmcmd "pciephy fw version"', shell=True, text=True) + except Exception: + return val + else: + version = re.search(r'PCIe FW loader version:\s(.*)', pcie_ver) + if version: + val = version.group(1).strip() + + return val class Component(ComponentBase): @@ -77,17 +106,83 @@ class Component(ComponentBase): ['Switch CPLD 2', 'Used for managing QSFP-DD/QSFP28/SFP port transceivers', get_cpld2_version - ] + ], + + ['SSD', + 'Solid State Drive that stores data persistently', + get_ssd_version + ], + ['PCIe', + 'ASIC PCIe firmware', + get_pciephy_version + ] ] - def __init__(self, component_index = 0): + def __init__(self, component_index=0): ComponentBase.__init__(self) self.index = component_index self.name = self.CHASSIS_COMPONENTS[self.index][0] self.description = self.CHASSIS_COMPONENTS[self.index][1] self.version = self.CHASSIS_COMPONENTS[self.index][2]() + @staticmethod + def _get_available_firmware_version(image_path): + if not os.path.isfile(image_path): + return False, "ERROR: File not found" + + with tempfile.TemporaryDirectory() as tmpdir: + cmd = "sed -e '1,/^exit_marker$/d' {} | tar -x -C {} installer/onie-update.tar.xz".format(image_path, tmpdir) + try: + subprocess.check_call(cmd, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, shell=True) + except subprocess.CalledProcessError: + return False, "ERROR: Unable to extract firmware updater" + + try: + updater = tarfile.open(os.path.join(tmpdir, "installer/onie-update.tar.xz"), "r") + except tarfile.ReadError: + return False, "ERROR: Unable to extract firmware updater" + + try: + ver_info_fd = updater.extractfile("firmware/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() + + ver_info = ver_info.get("x86_64-dellemc_z9332f_d1508-r0") + if ver_info: + return True, ver_info + else: + return False, "ERROR: Version info not available" + + @staticmethod + def _stage_firmware_package(image_path): + stage_msg = None + cmd = "onie_stage_fwpkg -a {}".format(image_path) + try: + subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT, text=True) + except subprocess.CalledProcessError as e: + if e.returncode != 2: + return False, e.output.strip() + else: + stage_msg = e.output.strip() + + cmd = "onie_mode_set -o update" + try: + subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT, text=True) + except subprocess.CalledProcessError as e: + return False, e.output.strip() + + if stage_msg: + return True, stage_msg + else: + return True, "INFO: Firmware upgrade staged" + def get_name(self): """ Retrieves the name of the component @@ -112,16 +207,6 @@ def get_firmware_version(self): """ return self.version - def install_firmware(self, image_path): - """ - Installs firmware to the component - Args: - image_path: A string, path to firmware image - Returns: - A boolean, True if install was successful, False if not - """ - return False - def get_presence(self): """ Retrieves the presence of the component @@ -170,3 +255,171 @@ def is_replaceable(self): bool: True if it is replaceable. """ return False + + 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 + valid, version = self._get_available_firmware_version(image_path) + if valid: + avail_ver = version.get(self.name) + if avail_ver: + avail_ver = avail_ver.get("version") + else: + print(version) + + return avail_ver if avail_ver else "NA" + + def get_firmware_update_notification(self, image_path): + """ + Retrieves a notification on what should be done in order to complete + the component firmware update + + Args: + image_path: A string, path to firmware image + + Returns: + A string containing the component firmware update notification if required. + By default 'None' value will be used, which indicates that no actions are required + """ + valid, version = self._get_available_firmware_version(image_path) + if valid: + avail_ver = version.get(self.name) + if avail_ver: + avail_ver = avail_ver.get("version") + if avail_ver and avail_ver != self.get_firmware_version(): + return "Cold reboot is required to perform firmware upgrade" + else: + print(version) + + return None + + def install_firmware(self, image_path): + """ + Installs firmware to the component + + This API performs firmware installation only: this may/may not be the same as firmware update. + In case platform component requires some extra steps (apart from calling Low Level Utility) + to load the installed firmware (e.g, reboot, power cycle, etc.) - this must be done manually by user + + Note: in case immediate actions are required to complete the component firmware update + (e.g., reboot, power cycle, etc.) - will be done automatically by API and no return value provided + + Args: + image_path: A string, path to firmware image + + Returns: + A boolean, True if install was successful, False if not + """ + valid, version = self._get_available_firmware_version(image_path) + if valid: + avail_ver = version.get(self.name) + if avail_ver: + avail_ver = avail_ver.get("version") + if avail_ver and avail_ver != self.get_firmware_version(): + status, msg = self._stage_firmware_package(image_path) + print(msg) + if status: + return True + else: + return False + + print("INFO: Firmware version up-to-date") + return True + else: + print(version) + + return False + + def update_firmware(self, image_path): + """ + Updates firmware of the component + + This API performs firmware update: it assumes firmware installation and loading in a single call. + In case platform component requires some extra steps (apart from calling Low Level Utility) + to load the installed firmware (e.g, reboot, power cycle, etc.) - this will be done automatically by API + + Args: + image_path: A string, path to firmware image + + Raises: + RuntimeError: update failed + """ + valid, version = self._get_available_firmware_version(image_path) + if valid: + avail_ver = version.get(self.name) + if avail_ver: + avail_ver = avail_ver.get("version") + if avail_ver and avail_ver != self.get_firmware_version(): + status, msg = self._stage_firmware_package(image_path) + if status: + print(msg) + subprocess.call("reboot") + else: + raise RuntimeError(msg) + + print("INFO: Firmware version up-to-date") + return None + else: + raise RuntimeError(version) + + def auto_update_firmware(self, image_path, boot_type): + """ + Updates firmware of the component + + This API performs firmware update automatically based on boot_type: it assumes firmware installation + and/or creating a loading task during the reboot, if needed, in a single call. + In case platform component requires some extra steps (apart from calling Low Level Utility) + to load the installed firmware (e.g, reboot, power cycle, etc.) - this will be done automatically during the reboot. + The loading task will be created by API. + + Args: + image_path: A string, path to firmware image + boot_type: A string, reboot type following the upgrade + - none/fast/warm/cold + + Returns: + Output: A return code + return_code: An integer number, status of component firmware auto-update + - return code of a positive number indicates successful auto-update + - status_installed = 1 + - status_updated = 2 + - status_scheduled = 3 + - return_code of a negative number indicates failed auto-update + - status_err_boot_type = -1 + - status_err_image = -2 + - status_err_unknown = -3 + + Raises: + RuntimeError: auto-update failure cause + """ + valid, version = self._get_available_firmware_version(image_path) + if valid: + avail_ver = version.get(self.name) + if avail_ver: + avail_ver = avail_ver.get("version") + if avail_ver and avail_ver != self.get_firmware_version(): + if boot_type != "cold": + return -1 + + status, msg = self._stage_firmware_package(image_path) + if status: + print(msg) + return 3 + else: + raise RuntimeError(msg) + + print("INFO: Firmware version up-to-date") + return 1 + else: + print(version) + return -2