#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# This validates the user-initiated fw upload mechanism of the firmware
# loader. It verifies that one or more firmware devices can be created
# for a device driver. It also verifies the data transfer, the
# cancellation support, and the error flows.
set -e

TEST_REQS_FW_UPLOAD="yes"
TEST_DIR=$(dirname $0)

progress_states="preparing transferring  programming"
errors="hw-error
	timeout
	device-busy
	invalid-file-size
	read-write-error
	flash-wearout"
error_abort="user-abort"
fwname1=fw1
fwname2=fw2
fwname3=fw3

source $TEST_DIR/fw_lib.sh

check_mods
check_setup
verify_reqs

trap "upload_finish" EXIT

upload_finish() {
	local fwdevs="$fwname1 $fwname2 $fwname3"

	for name in $fwdevs; do
		if [ -e "$DIR/$name" ]; then
			echo -n "$name" > "$DIR"/upload_unregister
		fi
	done
}

upload_fw() {
	local name="$1"
	local file="$2"

	echo 1 > "$DIR"/"$name"/loading
	cat "$file" > "$DIR"/"$name"/data
	echo 0 > "$DIR"/"$name"/loading
}

verify_fw() {
	local name="$1"
	local file="$2"

	echo -n "$name" > "$DIR"/config_upload_name
	if ! cmp "$file" "$DIR"/upload_read > /dev/null 2>&1; then
		echo "$0: firmware compare for $name did not match" >&2
		exit 1
	fi

	echo "$0: firmware upload for $name works" >&2
	return 0
}

inject_error() {
	local name="$1"
	local status="$2"
	local error="$3"

	echo 1 > "$DIR"/"$name"/loading
	echo -n "inject":"$status":"$error" > "$DIR"/"$name"/data
	echo 0 > "$DIR"/"$name"/loading
}

await_status() {
	local name="$1"
	local expected="$2"
	local status
	local i

	let i=0
	while [ $i -lt 50 ]; do
		status=$(cat "$DIR"/"$name"/status)
		if [ "$status" = "$expected" ]; then
			return 0;
		fi
		sleep 1e-03
		let i=$i+1
	done

	echo "$0: Invalid status: Expected $expected, Actual $status" >&2
	return 1;
}

await_idle() {
	local name="$1"

	await_status "$name" "idle"
	return $?
}

expect_error() {
	local name="$1"
	local expected="$2"
	local error=$(cat "$DIR"/"$name"/error)

	if [ "$error" != "$expected" ]; then
		echo "Invalid error: Expected $expected, Actual $error" >&2
		return 1
	fi

	return 0
}

random_firmware() {
	local bs="$1"
	local count="$2"
	local file=$(mktemp -p /tmp uploadfwXXX.bin)

	dd if=/dev/urandom of="$file" bs="$bs" count="$count" > /dev/null 2>&1
	echo "$file"
}

test_upload_cancel() {
	local name="$1"
	local status

	for status in $progress_states; do
		inject_error $name $status $error_abort
		if ! await_status $name $status; then
			exit 1
		fi

		echo 1 > "$DIR"/"$name"/cancel

		if ! await_idle $name; then
			exit 1
		fi

		if ! expect_error $name "$status":"$error_abort"; then
			exit 1
		fi
	done

	echo "$0: firmware upload cancellation works"
	return 0
}

test_error_handling() {
	local name=$1
	local status
	local error

	for status in $progress_states; do
		for error in $errors; do
			inject_error $name $status $error

			if ! await_idle $name; then
				exit 1
			fi

			if ! expect_error $name "$status":"$error"; then
				exit 1
			fi

		done
	done
	echo "$0: firmware upload error handling works"
}

test_fw_too_big() {
	local name=$1
	local fw_too_big=`random_firmware 512 5`
	local expected="preparing:invalid-file-size"

	upload_fw $name $fw_too_big
	rm -f $fw_too_big

	if ! await_idle $name; then
		exit 1
	fi

	if ! expect_error $name $expected; then
		exit 1
	fi

	echo "$0: oversized firmware error handling works"
}

echo -n "$fwname1" > "$DIR"/upload_register
echo -n "$fwname2" > "$DIR"/upload_register
echo -n "$fwname3" > "$DIR"/upload_register

test_upload_cancel $fwname1
test_error_handling $fwname1
test_fw_too_big $fwname1

fw_file1=`random_firmware 512 4`
fw_file2=`random_firmware 512 3`
fw_file3=`random_firmware 512 2`

upload_fw $fwname1 $fw_file1
upload_fw $fwname2 $fw_file2
upload_fw $fwname3 $fw_file3

verify_fw ${fwname1} ${fw_file1}
verify_fw ${fwname2} ${fw_file2}
verify_fw ${fwname3} ${fw_file3}

echo -n "$fwname1" > "$DIR"/upload_unregister
echo -n "$fwname2" > "$DIR"/upload_unregister
echo -n "$fwname3" > "$DIR"/upload_unregister

exit 0