#!/usr/bin/sh -e

# Copyright (c) 2018, Mellanox Technologies
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation are those
# of the authors and should not be interpreted as representing official policies,
# either expressed or implied, of the FreeBSD Project.

PROGNAME=bfvcheck

err () {
    echo "$PROGNAME: $@" >&2
}

echo "Beginning version check..."

# bfb file used to extract ATF/UEFI's expected version information
BOOTIMG_LOCATION=/lib/firmware/mellanox/boot/default.bfb

bfhcafw_status=0
mlxfw=$(bfhcafw find) || bfhcafw_status=$?

if [ "$bfhcafw_status" -ne 0 ]; then
    # FW package not found anywhere, so error out fw check
    # We keep going to check bootloader versions anyway.
    err "The firmware package could not be located."
    err "Firmware check will fail."
    mlxfw=false
fi

# If set to anything, the script will exit with an exit code of 1.
version_error=

# Compares two version strings, and prints a big red error if they aren't
# equal.
# $1 and $2 - the version strings to compare
# $3 - the name of the software we're testing the version of
compare_versions () {
    if [ "$1" != "$2" ]; then
        printf "\033[0;31mWARNING: %s VERSION DOES NOT MATCH RECOMMENDED!\033[0m\n" "$3"
        version_error=ERR
        echo ""
    fi
}

bfver_out="$( bfver --file $BOOTIMG_LOCATION )"

BUILD_ATF=$( echo "$bfver_out" | grep -m 1 "ATF" | cut -d':' -f 2,3 | \
    sed -e 's/[[:space:]]//' )
BUILD_UEFI=$( echo "$bfver_out" | grep -m 1 "UEFI" | cut -d':' -f 2 | \
    sed -e 's/[[:space:]]//' )

bfver_out="$( bfver )"

RUNTIME_ATF=$( echo "$bfver_out" | grep -m 1 "ATF" | cut -d':' -f 2,3 | \
    sed -e 's/[[:space:]]//' )
RUNTIME_UEFI=$( echo "$bfver_out" | grep -m 1 "UEFI" | cut -d':' -f 2 | \
    sed -e 's/[[:space:]]//' )


FW_UPDATED=no
mlxfw_out=""
MLXFW_SUCCESS=1
if [ -d /sys/module/mstflint_access ]; then
    # Use mst to access CX FW version
    cx_pcidev=$(lspci -nD 2> /dev/null | grep 15b3:a2d[26c] | awk '{print $1}' | head -1)
    PSID=$(mstflint -d $cx_pcidev q 2>&1 | grep -w "PSID:" | awk '{print $NF}')
    LOADED_FW=$(mstflint -d $cx_pcidev q 2>&1 | grep -w 'FW Version:' | awk '{print $NF}')
    RUNTIME_FW=$(mstflint -d $cx_pcidev q 2>&1 | grep -w 'FW Version(Running):' | awk '{print $NF}')
    if [ -z "$RUNTIME_FW" ]; then
        RUNTIME_FW=$LOADED_FW
    fi
    BUILD_FW=$( $mlxfw --list | grep -w ${PSID} | awk '{print $4}' )
    if [ -z "$BUILD_FW" ]; then
        BUILD_FW="ERROR"
        err "There was an error detecting the firmware version."
    fi
    if [ "$LOADED_FW" != "$RUNTIME_FW" ]; then
        FW_UPDATED=yes
    fi
else
    mlxfw_out=$( $mlxfw --query ) && MLXFW_SUCCESS=0 || MLXFW_SUCCESS=1

    if [ $MLXFW_SUCCESS -eq 0 ]; then
        # awk is used here to ease dealing with the spaces used in mlxfwmanager
        # output, and to make sure we pick the right FW line
        FIRMWARE_VERSIONS=$( echo "$mlxfw_out" | awk '($1 == "FW") && ($2 != "(Running)") { print $2 "," $3 }' )

        BUILD_FW=$( echo "$FIRMWARE_VERSIONS" | cut -d',' -f 2 )
        RUNTIME_FW=$( echo "$FIRMWARE_VERSIONS" |  cut -d',' -f 1 )
        LOADED_FW=$RUNTIME_FW

        # If FW (Running) line exists, user updated firmware, but did
        # not power cycle, so output message further down
        if echo "\"$mlxfw_out\"" | grep -q 'FW *(Running)' >/dev/null; then
            FIRMWARE_VERSIONS=$( echo "$mlxfw_out" | awk '($1 == "FW") && ($2 == "(Running)") { print $3 "," $4 }' )
            RUNTIME_FW=$( echo "$FIRMWARE_VERSIONS" |  cut -d',' -f 1 )
            FW_UPDATED=yes
        fi
    else
        err "There was an error detecting the firmware version."
        BUILD_FW=ERROR
        RUNTIME_FW=
    fi
fi

echo ""

cat << EOF
-RECOMMENDED VERSIONS-
ATF: $BUILD_ATF
UEFI: $BUILD_UEFI
FW: $BUILD_FW
EOF

cat << EOF

-INSTALLED VERSIONS-
ATF: $RUNTIME_ATF
UEFI: $RUNTIME_UEFI
FW: $RUNTIME_FW

EOF

compare_versions "$BUILD_ATF" "$RUNTIME_ATF" "ATF"
compare_versions "$BUILD_UEFI" "$RUNTIME_UEFI" "UEFI"

if [ "$BUILD_FW" = "N/A" ]; then
    BOARD_PN="$(lspci -s $(bfhcafw bus) -vv | grep '\[PN\]' | cut -d' ' -f4-)"
    cat << EOF
WARNING: The bundled firmware manager does not include firmware for
this board. Recommended firmware version cannot be determined.

Firmware Manager Path: $mlxfw
Part Number: $BOARD_PN

EOF
    version_error=ERR
else
    compare_versions "$BUILD_FW" "$RUNTIME_FW" "FW"
fi

if [ "$FW_UPDATED" = "yes" ]; then
    cat << EOF
WARNING: The firmware has been updated to $LOADED_FW, but the chassis
must be power cycled for changes to take effect.

EOF
fi

echo "Version check complete."

if [ -z "$version_error" ]  && [ $FW_UPDATED = "no" ]; then
    echo "No issues found."
    exit 0
fi

exit 1
