Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sonic_platform]support install firmware #18

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -381,6 +381,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
149 changes: 144 additions & 5 deletions platform/mellanox/mlnx-platform-api/sonic_platform/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
#
# implementation of new platform api
#############################################################################

from __future__ import print_function
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
try:
from sonic_platform_base.component_base import ComponentBase
from glob import glob
import subprocess
import io
import re
import sys
except ImportError as e:
raise ImportError(str(e) + "- required module not found")

Expand All @@ -23,6 +24,26 @@
CPLD_VERSION_FILE_PATTERN = '/var/run/hw-management/system/cpld[0-9]_version'
CPLD_VERSION_MAX_LENGTH = 4
stephenxs marked this conversation as resolved.
Show resolved Hide resolved

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

MST_DEVICE_PATTERN = "/dev/mst/mt[0-9]*_pciconf0"
MACHINE_CONF_FILE = "/host/machine.conf"
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
MACHINE_CONF_MAX_SIZE = 2048

BIOS_VERSION_PARSE_PATTERN = 'OEM[\s]*Strings\n[\s]*String[\s]*1:[\s]*([0-9a-zA-Z_\.]*)'
ONIE_VERSION_PARSE_PATTERN = 'onie_version=[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
# To update BIOS requires the ONIE with version 5.2.0016 or upper
ONIE_REQUIRED_MAJOR = "5"
ONIE_REQUIRED_MINOR = "2"
ONIE_REQUIRED_RELEASE = "0016"

ONIE_FW_UPDATE_CMD_ADD = "/usr/bin/onie-fw-update add {}"
ONIE_FW_UPDATE_CMD_UPDATE = "/usr/bin/onie-fw-update update"
stephenxs marked this conversation as resolved.
Show resolved Hide resolved

class Component(ComponentBase):
def get_name(self):
"""
Expand Down Expand Up @@ -60,10 +81,16 @@ def _get_command_result(self, cmdline):
return result

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephenxs do we need also check Exit Code here?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's OK here.
But it is required in some other place. I added.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.



class ComponentBIOS(Component):
BIOS_VERSION_PARSE_PATTERN = 'OEM[\s]*Strings\n[\s]*String[\s]*1:[\s]*([0-9a-zA-Z_\.]*)'
def _does_file_exist(self, image_path):
image = glob(image_path)
if not image:
return False, "Failed to get the image file via {} or multiple files matched".format(image_path)
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
if len(image) != 1:
return False, "Multiple files {} matched {}".format(image, image_path)
return True, ""


class ComponentBIOS(Component):
def __init__(self):
self.name = COMPONENT_BIOS

Expand Down Expand Up @@ -101,15 +128,63 @@ def get_firmware_version(self):
"""
bios_ver_str = self._get_command_result(BIOS_QUERY_VERSION_COMMAND)
try:
m = re.search(self.BIOS_VERSION_PARSE_PATTERN, bios_ver_str)
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
m = re.search(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)))
BIOS_VERSION_PARSE_PATTERN, bios_ver_str, repr(e)))

return result


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. To update ONIE requires version 5.2.0016 or later.
machine_conf = self._read_generic_file(MACHINE_CONF_FILE, MACHINE_CONF_MAX_SIZE)
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
try:
m = re.search(ONIE_VERSION_PARSE_PATTERN, machine_conf)
onie_major = m.group(ONIE_VERSION_MAJOR_OFFSET)
onie_minor = m.group(ONIE_VERSION_MINOR_OFFSET)
onie_release = m.group(ONIE_VERSION_RELEASE_OFFSET)
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
except AttributeError as e:
print("Failed to parse ONIE version by {} from {} due to {}".format(
BIOS_VERSION_PARSE_PATTERN, machine_conf, repr(e)))
return False
if onie_major < ONIE_REQUIRED_MAJOR or onie_minor < ONIE_REQUIRED_MINOR or onie_release < ONIE_REQUIRED_RELEASE:
print("ONIE {}.{}.{} or later is required".format(ONIE_REQUIRED_MAJOR, ONIE_REQUIRED_MINOR, ONIE_REQUIRED_RELEASE))
return False

# check whether the file exists
image_exists, message = self._does_file_exist(image_path)
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
if not image_exists:
print(message)
return False

# do the real work.
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
try:
result = subprocess.call(ONIE_FW_UPDATE_CMD_ADD.format(image_path).split())
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
if result:
return False
result = subprocess.call(ONIE_FW_UPDATE_CMD_UPDATE.split())
stephenxs marked this conversation as resolved.
Show resolved Hide resolved

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephenxs why not to use _get_command_result here?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to print the output to screen. get command result returns output for parsing or comparing. they're different.
all other cases fall into this.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I'm not going to change the way we capture output when upgrading CPLD. current way works and I don't have more time on it :(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephenxs ok. Let's leave it as is.

if result:
return False
except Exception as e:
print("Installing BIOS failed due to {}".format(repr(e)))
return False

print("Reboot via \"/sbin/reboot\" is required to finish BIOS installation.")
print("Please don't try installing a new sonic image before BIOS installation finishing")
return True


class ComponentCPLD(Component):
def __init__(self):
self.name = COMPONENT_CPLD
Expand Down Expand Up @@ -146,3 +221,67 @@ def get_firmware_version(self):

return cpld_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

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 file exists
image_exists, message = self._does_file_exist(image_path)
if not image_exists:
print(message)
return False

mst_dev_list = glob(MST_DEVICE_PATTERN)
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
if not mst_dev_list or not len(mst_dev_list) == 1:
print("Failed to get mst device which is required for CPLD updating or multiple device files matched")
return False

cmdline = 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(CPLD_INSTALL_SUCCESS_FLAG, outputline)
if m and m.group(0) == CPLD_INSTALL_SUCCESS_FLAG:
success_flag = True

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

if success_flag:
print("Success. Refresh or power cycle is required to finish CPLD installation.")
stephenxs marked this conversation as resolved.
Show resolved Hide resolved
else:
print("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)
150 changes: 150 additions & 0 deletions platform/mellanox/onie-fw-update
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/bin/sh

# Copyright (C) 2019 Mellanox Technologies Ltd.
# Copyright (C) 2019 Michael Shych <michaelsh@mellanox.com>
#
# SPDX-License-Identifier: GPL-2.0

this_script=${ONIE_FWPKG_PROGRAM_NAME:-$(basename $(realpath $0))}

onie_mount=/mnt/onie-boot
os_boot=/host
onie_partition=
onie_entry=0

export ONIE_FWPKG_PROGRAM_NAME=$(basename $(realpath $0))

usage()
{
cat <<EOF
update
The 'update' command will reboot system to ONIE update mode
and ONIE will perform automatically update of previously
added (i.e. pending) FW (ONIE itself, BIOS or CPLD) image.

EOF
}

enable_onie_access()
{
onie_partition=$(fdisk -l | grep "ONIE boot" | awk '{print $1}')
if [ ! -d $onie_mount ]; then
mkdir /mnt/onie-boot
fi
mount $onie_partition /mnt/onie-boot
if [ ! -e /lib/onie ]; then
ln -s /mnt/onie-boot/onie/tools/lib/onie /lib/onie
fi
PATH=/sbin:/usr/sbin:/bin:/usr/bin:$onie_mount/onie/tools/bin/
export PATH
}

clean_onie_access()
{
rm -f /lib/onie
umount $onie_partition
}

# ONIE entry must exist in grub config
find_onie_menuentry()
{
onie_entry="$(cat $os_boot/grub/grub.cfg | grep -e 'menuentry' | cat -n | awk '$0~/ONIE/ {print $1-1}')"
entries_num="$(echo "$onie_entry" | grep -E '^[0-9]+$' | wc -l)"
if [ $entries_num -eq 1 ] && [ $onie_entry -ge 1 ]; then
return 0
fi
return 1
}

change_grub_boot_order()
{
find_onie_menuentry
rc=$?
if [ $rc -eq 0 ]; then
grub-reboot --boot-directory=$os_boot $onie_entry
else
echo "ERROR: ONIE entry wasn't found in grub config"
return 1
fi

grub-editenv $onie_mount/grub/grubenv set onie_mode=update
return 0
}

show_pending()
{
curr_dir=$(pwd)
cd $onie_mount/onie/update/pending || return 0
num=$(find . -type f | wc -l)
if [ $num -ge 1 ]; then
echo "Number of FW update pending files are: "$num
ls -l * | awk {'print $9" "$5" "$7"-"$6" "$8'}
else
echo "There is no pending files for FW update."
fi
cd $curr_dir

return $num
}

system_reboot()
{
echo "Reboot will be done after 5 sec."
sleep 5
/sbin/reboot
}

# Process command arguments
cmd=$1
# optional argument
name="$2"

if [ -z "$cmd" ] ; then
# Default to 'show' if no command is specified.
cmd="show"
fi

case "$cmd" in
add | remove)
[ -z "$name" ] && {
echo "ERROR: This command requires a firmware update file name."
echo "Run '$this_script help' for complete details."
exit 1
}
;;
update)
enable_onie_access
show_pending
rc=$?
if [ $rc -ne 0 ]; then
change_grub_boot_order
rc=$?
clean_onie_access
if [ $rc -eq 0 ]; then
system_reboot

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephenxs You need to update the script, because it will do the reboot regardless the user action.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. will remove it.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nazariig just FYI, I just remember that after a BIOS upgrade scheduled, the boot sequence of grub will be updated to 2 which will not be recognized by sonic_installer. As a result, any attempt trying to reboot switch via a SONiC command, like "sudo reboot", won't work. The only way to reboot the system is to use /sbin/reboot.
This hint has been printed to the screen after a BIOS upgrade successfully scheduled.

fi
exit $rc
else
echo "ERROR: NO FW images for update."
echo "Run: $this_script add <image> before update."
clean_onie_access
exit 1
fi
;;
purge | show | show-results | show-log | show-pending | help)
;;
*)
echo "Unknown command: $cmd"
exit 1
;;
esac

enable_onie_access
$onie_mount/onie/tools/bin/onie-fwpkg "$@"
rc=$?
if [ $cmd = "help" ]; then
usage
fi
clean_onie_access

exit $rc
7 changes: 7 additions & 0 deletions platform/mellanox/onie-fw-update.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# bios update tool

ONIE_FW_UPDATE= onie-fw-update
$(ONIE_FW_UPDATE)_PATH = platform/mellanox/
SONIC_COPY_FILES += $(ONIE_FW_UPDATE)

export ONIE_FW_UPDATE
1 change: 1 addition & 0 deletions platform/mellanox/rules.mk
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ include $(PLATFORM_PATH)/libsaithrift-dev.mk
include $(PLATFORM_PATH)/docker-ptf-mlnx.mk
include $(PLATFORM_PATH)/mlnx-ffb.mk
include $(PLATFORM_PATH)/issu-version.mk
include $(PLATFORM_PATH)/onie-fw-update.mk

SONIC_ALL += $(SONIC_ONE_IMAGE) \
$(DOCKER_FPM)
Expand Down