DellEMC: Z9332f - Component firmware upgrade platform API implementation (#8973)

This commit is contained in:
Arun Saravanan Balachandran 2021-10-21 11:34:37 +05:30 committed by Ying Xie
parent 517d81a57a
commit 4139e06260
5 changed files with 346 additions and 16 deletions

View File

@ -19,6 +19,12 @@
},
{
"name": "Switch CPLD 2"
},
{
"name": "SSD"
},
{
"name": "PCIe"
}
],
"fans": [

View File

@ -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

View File

@ -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

View File

@ -28,7 +28,7 @@ MAX_Z9332F_FANTRAY = 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",

View File

@ -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(4, 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 @@ class Component(ComponentBase):
"""
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 @@ class Component(ComponentBase):
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