Skip to content

Commit

Permalink
[Mellanox] platform api support firmware install (sonic-net#3931)
Browse files Browse the repository at this point in the history
support firmware install, including CPLD and BIOS.

CPLD: cpldupdate
BIOS: boot to onie and update BIOS in onie and then boot to SONiC
  • Loading branch information
stephenxs authored and tiantianlv committed Apr 24, 2020
1 parent af5acee commit c7d17f8
Show file tree
Hide file tree
Showing 7 changed files with 351 additions and 16 deletions.
1 change: 1 addition & 0 deletions files/build_templates/sonic_debian_extension.j2
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ sudo cp $files_path/$MLNX_SPC_FW_FILE $FILESYSTEM_ROOT/etc/mlnx/fw-SPC.mfa
sudo cp $files_path/$MLNX_SPC2_FW_FILE $FILESYSTEM_ROOT/etc/mlnx/fw-SPC2.mfa
sudo cp $files_path/$ISSU_VERSION_FILE $FILESYSTEM_ROOT/etc/mlnx/issu-version
sudo cp $files_path/$MLNX_FFB_SCRIPT $FILESYSTEM_ROOT/usr/bin/mlnx-ffb.sh
sudo cp $files_path/$ONIE_FW_UPDATE $FILESYSTEM_ROOT/usr/bin/onie-fw-update.sh
j2 platform/mellanox/mlnx-fw-upgrade.j2 | sudo tee $FILESYSTEM_ROOT/usr/bin/mlnx-fw-upgrade.sh
sudo chmod 755 $FILESYSTEM_ROOT/usr/bin/mlnx-fw-upgrade.sh

Expand Down
16 changes: 16 additions & 0 deletions platform/mellanox/mlnx-platform-api/sonic_platform/chassis.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
try:
from sonic_platform_base.chassis_base import ChassisBase
from sonic_platform_base.component_base import ComponentBase
from sonic_device_util import get_machine_info
from sonic_daemon_base.daemon_base import Logger
from os import listdir
from os.path import isfile, join
Expand Down Expand Up @@ -54,6 +55,11 @@ def __init__(self):

# Initialize SKU name
self.sku_name = self._get_sku_name()
mi = get_machine_info()
if mi is not None:
self.name = mi['onie_platform']
else:
self.name = self.sku_name

# move the initialization of each components to their dedicated initializer
# which will be called from platform
Expand Down Expand Up @@ -136,6 +142,16 @@ def initialize_components(self):
self._component_list.append(ComponentCPLD())


def get_name(self):
"""
Retrieves the name of the device
Returns:
string: The name of the device
"""
return self.name


##############################################
# SFP methods
##############################################
Expand Down
220 changes: 205 additions & 15 deletions platform/mellanox/mlnx-platform-api/sonic_platform/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,29 @@
#
# implementation of new platform api
#############################################################################

from __future__ import print_function
try:
from sonic_platform_base.component_base import ComponentBase
from sonic_device_util import get_machine_info
from glob import glob
import subprocess
import io
import os
import re
import sys
except ImportError as e:
raise ImportError(str(e) + "- required module not found")

#components definitions
COMPONENT_BIOS = "BIOS"
COMPONENT_CPLD = "CPLD"

BIOS_QUERY_VERSION_COMMAND = 'dmidecode -t 11'
CPLD_VERSION_FILE_PATTERN = '/var/run/hw-management/system/cpld[0-9]_version'
CPLD_VERSION_MAX_LENGTH = 4

class Component(ComponentBase):
def __init__(self):
self.name = None
self.image_ext_name = None


def get_name(self):
"""
Retrieves the name of the component
Expand Down Expand Up @@ -51,21 +55,56 @@ def _get_command_result(self, cmdline):
try:
proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, shell=True, stderr=subprocess.STDOUT)
stdout = proc.communicate()[0]
proc.wait()
rc = proc.wait()
result = stdout.rstrip('\n')
if rc != 0:
raise RuntimeError("Failed to execute command {}, return code {}, message {}".format(cmdline, rc, stdout))

except OSError as e:
raise RuntimeError("Failed to execute command {} due to {}".format(cmdline, repr(e)))

return result


def _check_file_validity(self, image_path):
# check whether the image file exists
if not os.path.isfile(image_path):
print("ERROR: File {} doesn't exist or is not a file".format(image_path))
return False

if self.image_ext_name is not None:
name_list = os.path.splitext(image_path)
if name_list[1] != self.image_ext_name:
print("ERROR: Extend name of file {} is wrong. Image for {} should have extend name {}".format(image_path, self.name, self.image_ext_name))
return False

return True



class ComponentBIOS(Component):
# To update BIOS requires the ONIE with version 5.2.0016 or upper
ONIE_VERSION_PARSE_PATTERN = '[0-9]{4}\.[0-9]{2}-([0-9]+)\.([0-9]+)\.([0-9]+)'
ONIE_VERSION_MAJOR_OFFSET = 1
ONIE_VERSION_MINOR_OFFSET = 2
ONIE_VERSION_RELEASE_OFFSET = 3
ONIE_REQUIRED_MAJOR = "5"
ONIE_REQUIRED_MINOR = "2"
ONIE_REQUIRED_RELEASE = "0016"

BIOS_VERSION_PARSE_PATTERN = 'OEM[\s]*Strings\n[\s]*String[\s]*1:[\s]*([0-9a-zA-Z_\.]*)'
BIOS_PENDING_UPDATE_PATTERN = '([0-9A-Za-z_]*.rom)[\s]*\|[\s]*bios_update'

ONIE_FW_UPDATE_CMD_ADD = "/usr/bin/onie-fw-update.sh add {}"
ONIE_FW_UPDATE_CMD_REMOVE = "/usr/bin/onie-fw-update.sh remove {}"
ONIE_FW_UPDATE_CMD_UPDATE = "/usr/bin/onie-fw-update.sh update"
ONIE_FW_UPDATE_CMD_SHOW = "/usr/bin/onie-fw-update.sh show-pending"

BIOS_QUERY_VERSION_COMMAND = 'dmidecode -t 11'

def __init__(self):
self.name = COMPONENT_BIOS
self.image_ext_name = ".rom"


def get_description(self):
Expand Down Expand Up @@ -99,20 +138,95 @@ def get_firmware_version(self):
By using regular expression 'OEM[\s]*Strings\n[\s]*String[\s]*1:[\s]*([0-9a-zA-Z_\.]*)'
we can extrace the version string which is marked with * in the above context
"""
bios_ver_str = self._get_command_result(BIOS_QUERY_VERSION_COMMAND)
try:
bios_ver_str = self._get_command_result(self.BIOS_QUERY_VERSION_COMMAND)
m = re.search(self.BIOS_VERSION_PARSE_PATTERN, bios_ver_str)
result = m.group(1)
except AttributeError as e:
raise RuntimeError("Failed to parse BIOS version by {} from {} due to {}".format(
self.BIOS_VERSION_PARSE_PATTERN, bios_ver_str, repr(e)))
except (AttributeError, RuntimeError) as e:
raise RuntimeError("Failed to parse BIOS version due to {}".format(repr(e)))

return result


def _check_onie_version(self):
# check ONIE version. To update ONIE requires version 5.2.0016 or later.
try:
machine_info = get_machine_info()
onie_version_string = machine_info['onie_version']
m = re.search(self.ONIE_VERSION_PARSE_PATTERN, onie_version_string)
onie_major = m.group(self.ONIE_VERSION_MAJOR_OFFSET)
onie_minor = m.group(self.ONIE_VERSION_MINOR_OFFSET)
onie_release = m.group(self.ONIE_VERSION_RELEASE_OFFSET)
except AttributeError as e:
print("ERROR: Failed to parse ONIE version by {} from {} due to {}".format(
self.ONIE_VERSION_PARSE_PATTERN, machine_conf, repr(e)))
return False

if onie_major < self.ONIE_REQUIRED_MAJOR or onie_minor < self.ONIE_REQUIRED_MINOR or onie_release < self.ONIE_REQUIRED_RELEASE:
print("ERROR: ONIE {}.{}.{} or later is required".format(self.ONIE_REQUIRED_MAJOR, self.ONIE_REQUIRED_MINOR, self.ONIE_REQUIRED_RELEASE))
return False

return True


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
"""
# check ONIE version requirement
if not self._check_onie_version():
return False

# check whether the file exists
if not self._check_file_validity(image_path):
return False

# do the real work
try:
# check whether there has already been some images pending
# if yes, remove them
result = self._get_command_result(self.ONIE_FW_UPDATE_CMD_SHOW)
pending_list = result.split("\n")
for pending in pending_list:
m = re.match(self.BIOS_PENDING_UPDATE_PATTERN, pending)
if m is not None:
pending_image = m.group(1)
self._get_command_result(self.ONIE_FW_UPDATE_CMD_REMOVE.format(pending_image))
print("WARNING: Image {} which is already pending to upgrade has been removed".format(pending_image))

result = subprocess.check_call(self.ONIE_FW_UPDATE_CMD_ADD.format(image_path).split())
if result:
return False
result = subprocess.check_call(self.ONIE_FW_UPDATE_CMD_UPDATE.split())
if result:
return False
except Exception as e:
print("ERROR: Installing BIOS failed due to {}".format(repr(e)))
return False

print("INFO: Reboot is required to finish BIOS installation.")
return True



class ComponentCPLD(Component):
CPLD_VERSION_FILE_PATTERN = '/var/run/hw-management/system/cpld[0-9]_version'
CPLD_VERSION_MAX_LENGTH = 4

CPLD_UPDATE_COMMAND = "cpldupdate --dev {} {}"
CPLD_INSTALL_SUCCESS_FLAG = "PASS!"

MST_DEVICE_PATTERN = "/dev/mst/mt[0-9]*_pciconf0"

def __init__(self):
self.name = COMPONENT_CPLD
self.image_ext_name = ".vme"


def get_description(self):
Expand All @@ -132,17 +246,93 @@ def get_firmware_version(self):
Returns:
A string containing the firmware version of the component
"""
cpld_version_file_list = glob(CPLD_VERSION_FILE_PATTERN)
cpld_version_file_list = glob(self.CPLD_VERSION_FILE_PATTERN)
cpld_version = ''
if cpld_version_file_list is not None and cpld_version_file_list:
if cpld_version_file_list:
cpld_version_file_list.sort()
for version_file in cpld_version_file_list:
version = self._read_generic_file(version_file, CPLD_VERSION_MAX_LENGTH)
if not cpld_version == '':
version = self._read_generic_file(version_file, self.CPLD_VERSION_MAX_LENGTH)
if cpld_version:
cpld_version += '.'
cpld_version += version.rstrip('\n')
else:
raise RuntimeError("Failed to get CPLD version files by matching {}".format(CPLD_VERSION_FILE_PATTERN))
raise RuntimeError("Failed to get CPLD version files by matching {}".format(self.CPLD_VERSION_FILE_PATTERN))

return cpld_version


def _get_mst_device(self):
mst_dev_list = glob(self.MST_DEVICE_PATTERN)
if mst_dev_list is None or len(mst_dev_list) != 1:
return None
return mst_dev_list


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
Details:
The command "cpldupdate" is provided to install CPLD. There are two ways to do it:
1. To burn CPLD via gpio, which is faster but only supported on new systems, like Anaconda, ...
2. To install CPLD via firmware, which is slower but supported on older systems.
This also requires the mst device designated.
"cpldupdate --dev <devname> <vme_file>" has the logic of testing whether to update via gpio is supported,
and if so then go this way, otherwise tries updating software via fw. So we take advantage of it to update the CPLD.
By doing so we don't have to mind whether to update via gpio supported, which belongs to hardware details.
So the procedure should be:
1. Test whether the file exists
2. Fetch the mst device name
3. Update CPLD via executing "cpldupdate --dev <devname> <vme_file>"
4. Check the result
"""
# check whether the image file exists
if not self._check_file_validity(image_path):
return False

mst_dev_list = self._get_mst_device()
if mst_dev_list is None:
print("ERROR: Failed to get mst device which is required for CPLD updating or multiple device files matched")
return False

cmdline = self.CPLD_UPDATE_COMMAND.format(mst_dev_list[0], image_path)
outputline = ""
success_flag = False
try:
proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, shell=True, stderr=subprocess.STDOUT)
while True:
out = proc.stdout.read(1)

if out == '' and proc.poll() != None:
break

if out != '':
sys.stdout.write(out)
sys.stdout.flush()
outputline += out

if (out == '\n' or out == '\r') and len(outputline):
m = re.search(self.CPLD_INSTALL_SUCCESS_FLAG, outputline)
if m and m.group(0) == self.CPLD_INSTALL_SUCCESS_FLAG:
success_flag = True

if proc.returncode:
print("ERROR: Upgrade CPLD failed, return code {}".format(proc.returncode))
success_flag = False

except OSError as e:
raise RuntimeError("Failed to execute command {} due to {}".format(cmdline, repr(e)))

if success_flag:
print("INFO: Success. Refresh or power cycle is required to finish CPLD installation.")
else:
print("ERROR: Failed to install CPLD")

return success_flag
2 changes: 1 addition & 1 deletion platform/mellanox/one-image.mk
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ $(SONIC_ONE_IMAGE)_DOCKERS += $(filter-out $(patsubst %-$(DBG_IMAGE_MARK).gz,%.g
else
$(SONIC_ONE_IMAGE)_DOCKERS = $(SONIC_INSTALL_DOCKER_IMAGES)
endif
$(SONIC_ONE_IMAGE)_FILES += $(MLNX_SPC_FW_FILE) $(MLNX_SPC2_FW_FILE) $(MLNX_FFB_SCRIPT) $(ISSU_VERSION_FILE)
$(SONIC_ONE_IMAGE)_FILES += $(MLNX_SPC_FW_FILE) $(MLNX_SPC2_FW_FILE) $(MLNX_FFB_SCRIPT) $(ISSU_VERSION_FILE) $(ONIE_FW_UPDATE)
SONIC_INSTALLERS += $(SONIC_ONE_IMAGE)
Loading

0 comments on commit c7d17f8

Please sign in to comment.