#!/bin/sh
# Copyright (c) 2013 International Business Machines
# Common Public License Version 1.0 (see COPYRIGHT)
#
# Authors: Vasant Hegde <hegdevasant@linux.vnet.ibm.com>
#
# Simple script for code update on "KVM on Power" machines. This
# is a simple wrapper script to pass the image. The Linux kernel
# and FW does the real work during system reboot.
#
# This script has minimal dependencies so it can operate in a
# rescue environment.

#set -x

# Error codes
E_SUCCESS=0	# Success
E_UNSUPPORTED=1	# Firmware update is not supported
E_USAGE=3	# Usage error
E_PERM=4	# Permission error
E_IMAGE=5	# Image file error
E_SYS_FS=6	# Firmware update related sysfs file doesn't exist
E_MODULE=7	# Error loading module
E_OPAL=8	# OPAL call failed
E_USER=9	# User aborted operation
E_OVERWRITE=10	# Auto overwrite permanent side image
E_WRNTY=15	# Update Access Key Expired

# Firmware update related files
SYS_IMAGE_FILE=/sys/firmware/opal/image
SYS_VALIDATE_FLASH=/sys/firmware/opal/validate_flash
SYS_MANAGE_FLASH=/sys/firmware/opal/manage_flash
SYS_UPDATE_FLASH=/sys/firmware/opal/update_flash

# Code update status values
FLASH_SUCCESS=0			# Success
FLASH_PARAM_ERR=-1		# Parameter error
FLASH_BUSY=-2			# OPAL busy
FLASH_HW_ERR=-6			# Hardware error
FLASH_INTERNAL_ERR=-11		# Internal error
FLASH_NO_OP=-1099		# No operation initiated by user
FLASH_NO_AUTH=-9002		# Inband firmware update is not allowed

# Validate image status values
FLASH_IMG_READY=-1001		# Image ready for validation
FLASH_IMG_INCOMPLETE=-1002	# User copied < VALIDATE_BUF_SIZE

# Manage image status values
FLASH_ACTIVE_ERR=-9001		# Cannot overwrite active img

# Flash image status values
FLASH_IMG_READY=0		# Image ready for flash on reboot
FLASH_INVALID_IMG=-1003		# Flash image shorter than expected
FLASH_IMG_NULL_DATA=-1004	# Bad data
FLASH_IMG_BAD_LEN=-1005		# Bad length

# Validate image update result tokens
#
# T side will be updated
VALIDATE_TMP_UPDATE=0
#
# Partition does not have authority
VALIDATE_FLASH_AUTH=1
#
# Candidate image is not valid for this platform
VALIDATE_INVALID_IMG=2
#
# Current fixpack level is unknown
VALIDATE_CUR_UNKNOWN=3
#
# Current T side will be committed to P side before being replace
# with new image, and the new image is downlevel from current image
VALIDATE_TMP_COMMIT_DL=4
#
# Current T side will be committed to P side before being replaced
# with new image
VALIDATE_TMP_COMMIT=5
#
# T side will be updated with a downlevel image
VALIDATE_TMP_UPDATE_DL=6
#
# The candidate image's release date is later than the system's Update
# Access Key Expiration date - service warranty period has expired
VALIDATE_OUT_OF_WRNTY=7

error() {
	local exit_code=$1

	if [ $# -lt 1 ]; then
		echo "error(): usage." >&2
		return $E_USAGE
	fi

	shift;
	echo update_flash: $* >&2
	exit $exit_code
}

usage() {
	local exit_code;

	if [ "$1" == $E_SUCCESS ]; then
		exit_code=$E_SUCCESS
	else
		exit_code=$E_USAGE
	fi

	echo "USAGE: update_flash {-h | -s | -r | -c | [-v|-n] -f <image filename>}" >&2
	echo "	-h		Print this message." >&2
	echo "	-s		Determine if partition has access to" >&2
	echo "			perform flash image management." >&2
	echo "	-r		Reject temporary image." >&2
	echo "	-c		Commit temporary image." >&2
	echo "	-v		Validate the given image file." >&2
	echo "	-n		Do not overwrite Permanent side" >&2
	echo "			image automatically." >&2
	echo "	-f <filename>	Update with given image file. If possible," >&2
	echo "			the image is automatically validated prior" >&2
	echo "			to update." >&2
	echo "" >&2
	exit $exit_code
}

# Validate sysfs interface
validate_sysfs_file() {
	local file="$1"
	if [ -r "$file" ]; then
		return $E_SUCCESS
	fi

	error $E_SYS_FS "sysfs interface for firmware update does not exists."
}

# Copy image to sysfs file
copy_candidate_image() {
	local img_file=$1

	[ $# -eq 1 ] || error $E_USAGE "copy_candidate_image(): usage."

	[ -r "$img_file" ] || error $E_IMAGE "Cannot read ${img_file}."

	# Copy candidate image
	dd if=$img_file of=$SYS_IMAGE_FILE 2>/dev/null
	if [ $? -ne 0 ]; then
		echo "update_flash: Error copying firmware image."
		error $E_IMAGE "Please retry with valid firmware image."
	fi
}

echo_opal_return_status() {
	case "$1" in
	$FLASH_PARAM_ERR)
		error $E_OPAL "Parameter Error.";;
	$FLASH_BUSY)
		error $E_OPAL "OPAL Busy.";;
	$FLASH_HW_ERR)
		error $E_OPAL "Hardware error.";;
	$FLASH_INTERNAL_ERR)
		error $E_OPAL "OPAL internal error.";;
	$FLASH_NO_AUTH)
		error $E_PERM "System does not have authority to perform firmware update.";;
	$FLASH_IMG_INCOMPLETE)
		error $E_IMAGE "Invalid candidate image.";;
	$FLASH_ACTIVE_ERR)
		error $E_OVERWRITE "Cannot Overwrite the Active Firmware Image.";;
	$FLASH_INVALID_IMG)
		error $E_IMAGE "Invalid candidate image.";;
	$FLASH_IMG_NULL_DATA)
		error $E_IMAGE "Bad data value in flash list block.";;
	$FLASH_IMG_BAD_LEN)
		error $E_IMAGE "Bad length value in flash list block.";;
	*)	error $E_OPAL "Unknown return status.";;
	esac
}

# Determine if partition has access to perform flash image management
query_flash_support() {
	# Validate sysfs interface
	validate_sysfs_file $SYS_IMAGE_FILE

	# By default KVM on Power host is allowed to do firmware management
	echo "update_flash: Firmware image management is supported."

	exit $E_SUCCESS
}

echo_validate_buf() {
	local output="$1"
	local cur_t=$(echo "$output" | grep "^MI" | head -n 1 | awk ' { print $2 } ')
	local cur_p=$(echo "$output" | grep "^MI" | head -n 1 | awk ' { print $3 } ')
	local new_t=$(echo "$output" | grep "^MI" | tail -n 1 | awk ' { print $2 } ')
	local new_p=$(echo "$output" | grep "^MI" | tail -n 1 | awk ' { print $3 } ')

	echo "Projected Flash Update Results:"
	echo "Current T Image: $cur_t"
	echo "Current P Image: $cur_p"
	echo "New T Image:     $new_t"
	echo "New P Image:     $new_p"
}

echo_validate_return_status() {
	local output="$1"
	local rc=$(echo "$output" | head -n 1)
	local opal_buf=$(echo "$output" | tail -n +2)

	[ $# -eq 1 ] || error $E_USAGE "echo_validate_return_status(): usage."

	if [ $rc -lt 0 ]; then
		echo_opal_return_status $rc
	fi

	# Validation result
	case "$rc" in
	$VALIDATE_TMP_UPDATE)
		echo -n "info: Temporary side will be updated with a newer or"
		echo " identical image.";;
	$VALIDATE_FLASH_AUTH)
		error $E_OPAL "System does not have authority.";;
	$VALIDATE_INVALID_IMG)
		error $E_OPAL "Invalid candidate image for this platform.";;
	$VALIDATE_CUR_UNKNOWN)
		echo "info: Current fixpack level is unknown.";;
	$VALIDATE_TMP_COMMIT_DL)
		echo "info: Current Temporary image will be committed to"
		echo "Permanent side before being replaced with new image,"
		echo "and the new image is downlevel from current image.";;
	$VALIDATE_TMP_COMMIT)
		echo "info: Current Temporary side will be committed to"
		echo "Permanent side before being replaced with the new"
		echo "image.";;
	$VALIDATE_TMP_UPDATE_DL)
		echo "info: Temporary side will be updated with a downlevel image.";;
	*)	error $E_OPAL "Unknown return status."
	esac

	echo
	echo_validate_buf "$opal_buf"

	# Do not commit T side image to P side
	if [ $no_overwrite_opt -eq 1 ]; then
		if [ $rc -eq $VALIDATE_TMP_COMMIT_DL ] ||
			[ $rc -eq $VALIDATE_TMP_COMMIT ]; then
			echo ""
			echo "update_flash: Run without -n option to flash new image."
			exit $E_OVERWRITE
		fi
	fi
}

validate_flash() {
	local output=""

	# Validate candidate image
	echo 1 > $SYS_VALIDATE_FLASH 2>/dev/null

	# Display appropriate message, exiting if necessary
	output="$(cat $SYS_VALIDATE_FLASH)"
	echo_validate_return_status "$output"
}

validate_flash_from_file() {
	local img_file=$1

	[ $# -eq 1 ] || error $E_USAGE "validate_flash_from_file(): usage."

	# Validate sysfs interface
	validate_sysfs_file $SYS_VALIDATE_FLASH

	# Copy candiadate image
	copy_candidate_image $img_file

	# Validate candidate image
	validate_flash

	exit $E_SUCCESS
}

echo_update_return_status() {
	local rc="$1"

	[ $# -eq 1 ] || error $E_USAGE "echo_update_return_status(): usage."

	if [ $rc -lt 0 ]; then
		echo_opal_return_status $rc
	elif [ $rc -eq $FLASH_IMG_READY ]; then
		echo
		echo "FLASH: Image ready...rebooting the system..."
		echo "FLASH: This will take several minutes."
		echo "FLASH: Do not power off!"
	else
		error $E_SYS_FS "Unknown return status."
	fi
}

update_flash_from_file() {
	local img_file=$1
	local output=""

	[ $# -eq 1 ] || error $E_USAGE "update_flash_from_file(): usage."

	# Validate sysfs interface
	validate_sysfs_file $SYS_UPDATE_FLASH

	# Copy candidate image
	copy_candidate_image $img_file

	# Validate candidate image
	validate_flash

	# Update image
	echo 1 > $SYS_UPDATE_FLASH 2>/dev/null
	output="$(cat $SYS_UPDATE_FLASH)"
	echo_update_return_status "$output"

	# Reboot system, so that we can flash new image
	reboot -f

	exit $E_SUCCESS
}

echo_manage_return_status() {
	local is_commit=$1
	local output=$2
	local rc=$(echo $output)

	[ $# -eq 2 ] || error $E_USAGE "echo_manage_return_status(): usage."

	if [ $rc -lt 0 ]; then
		echo_opal_return_status $rc
	elif [ $rc -eq $FLASH_SUCCESS ]; then
		if [ $is_commit -eq 0 ]; then
			echo "Success: Rejected temporary firmware image."
		else
			echo "Success: Committed temporary firmware image."
		fi
	else
		error $E_OPAL "Unknown return status."
	fi
}

manage_flash() {
	local is_commit=$1
	local commit_str="1"
	local reject_str="0"
	local output=""

	[ $# -eq 1 ] || error $E_USAGE "manage_flash(): usage."

	# Validate sysfs interface
	validate_sysfs_file $SYS_MANAGE_FLASH

	# Commit operation
	if [ $is_commit -eq 1 ]; then
		echo $commit_str > $SYS_MANAGE_FLASH
	else
		echo $reject_str > $SYS_MANAGE_FLASH
	fi

	# Result
	output=$(cat $SYS_MANAGE_FLASH)
	echo_manage_return_status $is_commit "$output"

	exit $E_SUCCESS
}

file=""
check_opt=0
commit_opt=0
reject_opt=0
validate_opt=0
no_overwrite_opt=0
file_opt=0

# Only root user can perform firmware update
[ "`whoami`" == "root" ] || error $E_PERM "Must be root to execute this command."

# Parse command line options
while [ -n "$1" ]; do
	arg="$1"
	shift
	case "$arg" in
	  -q|-l|-D|-S) error $E_USAGE "The $arg option is not implemented.";;
	  -h) usage $E_SUCCESS;;
	  -s) check_opt=1;;
	  -c) commit_opt=1;;
	  -r) reject_opt=1;;
	  -v) validate_opt=1;;
	  -n) no_overwrite_opt=1;;
	  -f) file_opt=1; file="$1"; shift;;
	  *) error $E_USAGE "Unknown option ${arg}."
	esac
done

if [ -n "$file" ]; then
	if [ $commit_opt -eq 1 ] || [ $reject_opt -eq 1 ] ||
						[ $check_opt -eq 1 ]; then
		usage
	elif [ $validate_opt -eq 1 ] && [ $no_overwrite_opt -eq 1 ]; then
		usage
	elif [ $validate_opt -eq 1 ]; then
		validate_flash_from_file $file
	else
		update_flash_from_file $file
	fi
else
	if [ $check_opt -eq 1 ]; then
		if [ $commit_opt -eq 1 ] || [ $reject_opt -eq 1 ]; then
			usage
		else
			query_flash_support
		fi
	fi
	[ $commit_opt -eq 0 ] && [ $reject_opt -eq 0 ] && usage
	[ $commit_opt -eq 1 ] && [ $reject_opt -eq 1 ] && usage
	manage_flash $commit_opt
fi
