#!/bin/bash
# SPDX-FileCopyrightText: Copyright (c) 2019-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: LicenseRef-NvidiaProprietary
#
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
# property and proprietary rights in and to this material, related
# documentation and any modifications thereto. Any use, reproduction,
# disclosure or distribution of this material and related documentation
# without an express license agreement from NVIDIA CORPORATION or
# its affiliates is strictly prohibited.

# This script runs on target to check if need to update QSPI partitions for Nano
# If the running system version match the bootloader debian package,
# then check the VER in QSPI, if the VER is less then running system version,
# install the package to update QSPI.

BOOT_CTRL_CONF="/etc/nv_boot_control.conf"
BL_PACKAGE_NAME="nvidia-l4t-bootloader"
IGX_BL_PACKAGE_NAME="nvidia-igx-bootloader"
TNSPEC_NAME="TegraPlatformSpec"
COMPAT_SPEC_NAME="TegraPlatformCompatSpec"
SYS_VAR_DIR="/sys/firmware/efi/efivars"
NVIDIA_GUID="781e084c-a330-417c-b678-38e696380cb9"
TMP_FILE="/tmp/spec_var.bin"
SYS_VER_VAR="SystemFwVersions-781e084c-a330-417c-b678-38e696380cb9"
ESP_MOUNT_POINT="/boot/efi"
FS_TABLE="/etc/fstab"
EFI_PART_TYPE_GUID="C12A7328-F81F-11D2-BA4B-00A0C93EC93B"

orig_name=""
unify_name=""
# When create image, user may flash device with symlink board
# config name or orignal/legacy name, so the nv_boot_control.conf
# may have different board name informaton.
# Unify it to "jetson-*"

# Make sure to put the unify_name in the first line in the table.
jetson_agx_orin_devkit=(
	"unify_name=jetson-agx-orin-devkit"
	"orig_name=p3737-0000-p3701-0000"
)

holoscan_devkit=(
	"unify_name=holoscan-devkit"
	"orig_name=p3711-0000-p3701-0002"
)

jetson_orin_nano_devkit=(
	"unify_name=jetson-orin-nano-devkit"
	"orig_name=jetson-orin-nano-devkit-nvme"
	"orig_name=p3768-0000-p3767-0000-a0"
	"orig_name=p3768-0000-p3767-0000-a0-nvme"
)

jetson_orin_nano_devkit_super=(
	"unify_name=jetson-orin-nano-devkit-super"
	"orig_name=jetson-orin-nano-devkit-super-nvme"
	"orig_name=p3768-0000-p3767-0000-super"
	"orig_name=p3768-0000-p3767-0000-super-nvme"
)

igx_orin_devkit=(
	"unify_name=igx-orin-devkit"
)

igx_orin_safety=(
	"unify_name=igx-orin-safety"
	"orig_name=p3740-0002+p3701-0008-safety"
	"orig_name=p3740-0002-p3701-0008-safety-baseos"
)

jetson_agx_orin_devkit_maxn=(
	"unify_name=jetson-agx-orin-devkit-maxn"
	"orig_name=p3737-0000-p3701-0000-maxn"
)

jetson_agx_orin_devkit_industrial=(
    "unify_name=jetson-agx-orin-devkit-industrial"
    "orig_name=p3737-0000-p3701-0008"
)

jetson_agx_orin_devkit_industrial_maxn=(
    "unify_name=jetson-agx-orin-devkit-industrial-maxn"
    "orig_name=p3737-0000-p3701-0008-maxn"
)

t23x_board_conf=(
	"board_conf=jetson_agx_orin_devkit"
	"board_conf=holoscan_devkit"
	"board_conf=jetson_orin_nano_devkit"
	"board_conf=jetson_orin_nano_devkit_super"
	"board_conf=igx_orin_devkit"
	"board_conf=igx_orin_safety"
	"board_conf=jetson_agx_orin_devkit_maxn"
	"board_conf=jetson_agx_orin_devkit_industrial"
	"board_conf=jetson_agx_orin_devkit_industrial_maxn"
)

comp_board=""
compatible_board_name() {
	local boards=("${!1}")
	local board_name="$2"

	orig_name=""
	unify_name=""

	for i in "${!boards[@]}"; do
		eval "${boards[$i]}"

		if [[ "${i}" == "0" ]]; then
			# get the unify_name in first loop
			# check if the board_name is the unify_name
			if [[ "${board_name}" == "${unify_name}" ]]; then
				comp_board="${unify_name}"
				break;
			fi

			continue
		fi

		# get the orig_name in next loops.
		# change the orig_name to unify_name for comp_board
		if [[ "${board_name}" == "${orig_name}" ]]; then
			comp_board="${unify_name}"
			break
		fi
	done
}

generate_compatible_board_name () {
	local confs=("${!1}")
	local orig_board_name=${2}

	for i in "${!confs[@]}"; do
		eval "${confs[$i]}"
		compatible_board_name "${board_conf}"[@] "${orig_board_name}"
		if [[ "${comp_board}" != "" ]]; then
			break;
		fi
	done

	# cannot get compatible board name, use original board name.
	if [[ "${comp_board}" == "" ]]; then
		echo "Warning: Cannot get compatible board name."
		comp_board="${orig_board_name}"
	fi
}

board_id=""
board_ver=""
board_sku=""
board_rev=""
board_fuselevel=""
chip_rev=""
compatible_spec=""

update_jetson_agx_orin_spec () {
    if [[ "${board_sku}" == "0000" ]]; then
        if [[ "${board_ver}" == "000" || "${board_ver}" == "TS"* || "${board_ver}" == "EB"*
            || ($((board_ver)) -gt 0 && $((board_ver)) -lt 300) ]]; then
            board_ver="000"
        else
            board_ver="300"
        fi
    elif [[ "${board_sku}" == "0004" || "${board_sku}" == "0005" || "${board_sku}" == "0008" ]]; then
        board_ver=""
    fi

    board_rev=""
    chip_rev=""
}

update_jetson_orin_nano_spec () {
    if [[ "${board_sku}" == "0000" || "${board_sku}" == "0002" ]]; then
        if [[ "${board_ver}" != "TS"* && "${board_ver}" != "EB"* ]]; then
            board_ver="000"
        fi
    else
        board_ver=""
    fi
    board_rev=""
    chip_rev=""
}

# Parse all fields in spec, and return all of them:
# ${BOARDID}-${FAB}-${BOARDSKU}-${BOARDREV}-${fuselevel}-${chiprev}-${target_board}-${target_rootdev}
parse_spec_info() {
	local _spec="${1}"
	local _board_id="${2}"
	local _board_ver="${3}"
	local _board_sku="${4}"
	local _board_rev="${5}"
	local _board_fuselevel="${6}"
	local _chip_rev="${7}"
	local _target_board="${8}"
	local _target_rootdev="${9}"

	local _id
	_id=$( echo "${_spec}" | awk -F"-" '{print $1}' )
	local _ver
	_ver=$( echo "${_spec}" | awk -F"-" '{print $2}' )
	local _sku
	_sku=$( echo "${_spec}" | awk -F"-" '{print $3}' )
	local _rev
	_rev=$( echo "${_spec}" | awk -F"-" '{print $4}' )
	local _fuselevel
	_fuselevel=$( echo "${_spec}" | awk -F"-" '{print $5}' )
	local _cp_rev
	_cp_rev=$( echo "${_spec}" | awk -F"-" '{print $6}' )

	local tmp_spec
	tmp_spec="${_id}-${_ver}-${_sku}-${_rev}-${_fuselevel}-${_cp_rev}"
	local boot_info
	boot_info=${_spec//"${tmp_spec}-"/}
	local count
	count=$( echo "${boot_info}" | awk '{print split($0,a,"-");}' );
	local _rootdev
	_rootdev=$( echo "${boot_info}" | awk '{split($0,a,"-"); print a['${count}']}' );

	local _board
	if [ "${_rootdev}" != "" ]; then
		_board=${boot_info//"-${_rootdev}"/}
	else
		_board=${boot_info%?}
	fi

	# Return all fields of spec
	eval "${_board_id}=${_id}"
	eval "${_board_ver}=${_ver}"
	eval "${_board_sku}=${_sku}"
	eval "${_board_rev}=${_rev}"
	eval "${_board_fuselevel}=${_fuselevel}"
	eval "${_chip_rev}=${_cp_rev}"
	eval "${_target_board}=${_board}"
	eval "${_target_rootdev}=${_rootdev}"
}

generate_compatible_spec_info () {
	local spec_str=${1}
	local target_board
	local target_rootdev

	parse_spec_info "${spec_str}" "board_id" "board_ver" "board_sku" "board_rev" \
			"board_fuselevel" "chip_rev" "target_board" "target_rootdev"

	case "${chipid}" in
		0x23)
			generate_compatible_board_name t23x_board_conf[@] "${target_board}"
			;;
		*)
			echo "Error: Unrecognized chip ID: ${chipid}."
			echo "Exiting..."
			exit 1
	esac

	case "${board_id}" in
		3701)
			# Jetson-agx-orin
			update_jetson_agx_orin_spec
		;;
		3767)
			# Jetson-Orin-Nano
			update_jetson_orin_nano_spec
		;;
		*)
			echo "Error: Unsupported board ID: ${board_id}."
			echo "Exiting..."
			exit 1
	esac

	compatible_spec="${board_id}-${board_ver}-${board_sku}-${board_rev}-${board_fuselevel}-${chip_rev}-${comp_board}-"
	echo "${compatible_spec}"
}

sys_ver=""

read_sys_ver() {
	local sys_rel=""
	local sys_maj_ver=""
	local sys_min_ver=""
	local installed_deb_ver=""
	local deb_version=""

	# 1. Get installed bootloader package's version
	# Sample: ii  nvidia-l4t-bootloader  35.3.1-20230314154120  arm64  NVIDIA Bootloader Package
	# installed_deb_ver=35.3.1-20230314154120
	installed_deb_ver="$(dpkg -l | grep "${BL_PACKAGE_NAME}" | awk '/'${BL_PACKAGE_NAME}'/ {print $3}')"
	if [ "${installed_deb_ver}" == "" ]; then
		return 1
	fi

	# 2. Get main deb_version
	deb_version="$(echo "${installed_deb_ver}" | cut -d "-" -f 1)"

	# 3. Use deb_version as sys_version
	# Sample: sys_rel=35, sys_maj_ver=3, sys_min_ver=1
	sys_rel="$(echo "${deb_version}" | cut -d "." -f 1)"
	sys_maj_ver="$(echo "${deb_version}" | cut -d "." -f 2)"
	sys_min_ver="$(echo "${deb_version}" | cut -d "." -f 3)"

	# 4. Convert the sys_version as a decimal number to compare with bsp_version
	# It's hex value is 0xRRRRMMmm
	# R=Release
	# M=Major number
	# m=minor number
	# E.g. 2294529=0x230301=35.3.1
	sys_ver=$(( (sys_rel<<16) | (sys_maj_ver<<8) | sys_min_ver ))

	return 0
}

# It's hex value is 0xRRRRMMmm
# R=Release
# M=Major number
# m=minor number
# E.g. 2294529=0x230301=35.3.1
bsp_ver=""

# Read bsp version from ESRT, the version is a decimal number
read_bsp_ver_from_esrt() {
	local esrt_ver_path="/sys/firmware/efi/esrt/entries/entry0/fw_version"
	bsp_ver=$(cat "${esrt_ver_path}")
}

auto_update_bootloader () {
	# Read system version
	if ! read_sys_ver; then
		echo "Error: Read system version failed."
		return 1
	fi

	# Read qspi version from ESRT
	read_bsp_ver_from_esrt
	if [ "${bsp_ver}" = "" ]; then
		echo "Error: Failed to read bsp version from ESRT."
		return 1
	fi

	echo "Info: System version(deb version): ${sys_ver}, BSP version(QSPI version): ${bsp_ver}"
	# Reconfigure bootloader package to update the bootloader if sys_ver is larger than bsp_ver.
	if [[ $((sys_ver)) > $((bsp_ver)) ]]; then
		echo "Info: Starting bootloader dpkg reconfigure."
		if ! dpkg-reconfigure "${BL_PACKAGE_NAME}"; then
			echo "Error: Procedure for bootloader dpkg reconfig FAILED."
			return 1
		fi
	fi
}

# Get the version of input slot A/B
# get_bl_version $slot $version
get_bl_version () {
	local slot="${1}"
	local version="${2}"
	local sys_ver_var=""
	local ver_high=""
	local ver_low=""
	local ver_branch=""
	local ver_major=""
	local ver_minor=""
	local ver=""

	sys_ver_var="${SYS_VAR_DIR}/${SYS_VER_VAR}"
	if [ ! -f "${sys_ver_var}" ]; then
		echo "Error: ${sys_ver_var} does not exist."
		return 1
	fi

	# The contents of the UEFI variable "SystemFwVersion":
	# 0600 0000    0003 2400    0103 2400
	# [attribute]  [A version]  [B version]
	# Slot A version: 0003 2400 => 0x00240300 => 0x24.0x03.0x00 => 36.3.0
	# Slot B version: 0103 2400 => 0x00240301 => 0x24.0x03.0x01 => 36.3.1
	# Get the high 8 bytes and the low 8 bytes of the version
	if [ "${slot}" = "A" ]; then
		ver_high=$(xxd ${sys_ver_var} | awk '{print $5}')
		ver_low=$(xxd ${sys_ver_var} | awk '{print $4}')
	elif [ "${slot}" = "B" ];then
		ver_high=$(xxd ${sys_ver_var} | awk '{print $7}')
		ver_low=$(xxd ${sys_ver_var} | awk '{print $6}')
	else
		echo "Error: Unsupport slot ${slot}."
		return 1
	fi

	# Version format: branch.major.minor
	# Get each field of the versions
	ver_branch="${ver_high:2:2}${ver_high:0:2}"
	ver_major="${ver_low:2:2}"
	ver_minor="${ver_low:0:2}"

	ver="${ver_branch}${ver_major}${ver_minor}"

	eval "${version}=${ver}"

	return 0
}

update_sync_state () {
	local state="${1}"

	sed -i '/TEGRA_BOOT_SLOT_SYNC/d' "${BOOT_CTRL_CONF}"
	sed -i '$a\TEGRA_BOOT_SLOT_SYNC '"${state}"'' "${BOOT_CTRL_CONF}"
}

# Check and sync up the two bootloader slots
# Use TEGRA_BOOT_SLOT_SYNC to track state:
# in-sync: the version of the two slots are the same.
# not-support: current slot version is older, not support rollback.
# pre-sync: capsule update has triggered to sync bootloader slots, need a reboot.
# failed: sync bootloader failed, will not trigger capsule update at subsequent boot.
auto_sync_up_bootloader () {
	local sync_up_state=""
	local capsule_status=""
	local curr_slot=""
	local curr_version=""
	local non_curr_slot=""
	local non_curr_version=""

	# Skip auto sync up bootloader if the bootloader package is not installed.
	if ! dpkg -l | grep -w "${IGX_BL_PACKAGE_NAME}"; then
		echo "Info: The bootloader package ${IGX_BL_PACKAGE_NAME} is not installed. Skip auto sync."
		return 0
	fi

	curr_slot=$( nvbootctrl dump-slots-info | grep "Current bootloader slot" | awk '{print $4}' )
	if [ "${curr_slot}" != "A" ] && [ "${curr_slot}" != "B" ]; then
		echo "Error: Failed to get Current boot slot."
		return 1
	fi
	echo "Info: Current slot: ${curr_slot}"

	capsule_status=$( nvbootctrl dump-slots-info | grep "Capsule update status" | awk '{print $4}' )
	if [ "${capsule_status}" = "" ]; then
		echo "Error: Failed to get Capsule update status."
		return 1
	fi
	echo "Info: Capsule update status: ${capsule_status}"

	# Get current slot version
	if ! get_bl_version "${curr_slot}" "curr_version"; then
		echo "Error: Failed to get current slot version."
		return 1
	fi

	sync_up_state=$( awk '/TEGRA_BOOT_SLOT_SYNC/ {print $2}' "${BOOT_CTRL_CONF}" )
	echo "Info: Boot slot sync status: ${sync_up_state}"

	# Get non-current slot version
	if [ "${curr_slot}" = "A" ]; then
		non_curr_slot="B"
	else
		non_curr_slot="A"
	fi
	if ! get_bl_version "${non_curr_slot}" "non_curr_version"; then
		echo "Error: Failed to get non-current slot version."
		return 1
	fi

	echo "Info: Current slot version: ${curr_version}, non-current slot version: ${non_curr_version}."

	if [ $((curr_version)) -eq $((non_curr_version)) ]; then
		# Set in-sync to TEGRA_BOOT_SLOT_SYNC.
		echo "Info: Current slot version is equal to non-current slot version."
		update_sync_state "in-sync"
	elif [ $((curr_version)) -lt $((non_curr_version)) ]; then
		# Set not-support to TEGRA_BOOT_SLOT_SYNC.
		echo "Info: Current slot version is older than non-current slot version."
		update_sync_state "not-support"
	elif [ $((curr_version)) -gt $((non_curr_version)) ]; then
		# Sync up the other slot.
		# Is previous sync-up request failed or not.
		if [ "${sync_up_state}" = "failed" ]; then
			echo "Info: Previous bootloader sync failed, no need to sync this time."
			return 0
		fi

		# Sync have not tried, trigger a capsule update.
		if [ "${sync_up_state}" != "pre-sync" ]; then
			echo "Info: Starting bootloader dpkg reconfigure."
			if ! dpkg-reconfigure "${IGX_BL_PACKAGE_NAME}"; then
				echo "Error: Procedure for igx bootloader dpkg reconfig FAILED."
				return 1
			fi

			# Set pre-sync to TEGRA_BOOT_SLOT_SYNC.
			update_sync_state "pre-sync"

			# Reboot the target.
			echo "Rebooting system in 3 seconds..."
			sleep 3
			reboot
		fi

		# Sync tried and Capsule update failed.
		if [ "${sync_up_state}" = "pre-sync" ] && [ "${capsule_status}" != "1" ]; then
			echo "Info: Sync bootloader slot ${non_curr_slot} failed."

			# Set failed to TEGRA_BOOT_SLOT_SYNC.
			update_sync_state "failed"
		fi
	fi
}

auto_update_qspi () {
	# Only support auto update QSPI for Orin Nano SD board.
	if [[ (("${comp_board}" == "jetson-orin-nano-devkit" || "${comp_board}" == "jetson-orin-nano-devkit-super")
		&& "${board_sku}" == "0005") ]]; then
		auto_update_bootloader
	fi

	# Auto update the QSPI for IGX board.
	if [[ "${comp_board}" == "igx-orin-devkit" ]]; then
		auto_sync_up_bootloader
	fi
}

# Read board_id, board_sku, board_ver and board_rev from EEPROM
read_eeprom_board_info () {
	local i2c=0
	local pn
	local val
	local ret

	# Get i2c controller and slave address based on chip id,
	# slave address is fixed to 0x50
	case ${chipid} in
		0x23)
			i2c=0
			;;
		*)
			echo "Error: Unrecognized chip ID: ${chipid}."
			exit 1
			;;
	esac

	# Read product part number from EEPROM address 25 to 41.
	# if read EEPROM failed return 1
	for reg in {25..41}
	do
		val=$(i2cget -f -y "${i2c}" 0x50 "${reg}")
		ret=$?
		if [ ${ret} -ne 0 ]; then
			echo "Warning: Read i2c-${i2c} slave addr: 0x50 failed."
			return 1
		fi
		pn+=$(echo "${val}" | xxd -r)
	done

	# Parse board info
	board_id=${pn:0:4}
	board_sku=${pn:5:4}
	board_ver=${pn:10:3}
	board_rev=${pn:14:3}
	return 0
}

# Generate board_spec by combining info from EEPROM and TNSPEC
# Return generated board_spec
generate_board_spec() {
	local tnspec="${1}"
	local board_spec_gen="${2}"

	local tnspec_id
	local tnspec_ver
	local tnspec_sku
	local tnspec_rev
	local tnspec_fuselevel
	local tnspec_chip_rev
	local tnspec_board
	local tnspec_rootdev

	# Get spec info from TNSPEC
	parse_spec_info "${tnspec}" "tnspec_id" "tnspec_ver" "tnspec_sku" "tnspec_rev" \
					"tnspec_fuselevel" "tnspec_chip_rev" "tnspec_board" "tnspec_rootdev"

	# Generate board_spec where
	# board_id,board_ver,board_sku and board_rev are read from EEPROM
	# board_fuselevel,chip_rev,target_board and target_rootdev are from BOOT_CTRL_CONF
	eval "${board_spec_gen}=${board_id}-${board_ver}-${board_sku}-${board_rev}-${tnspec_fuselevel}-${tnspec_chip_rev}-${tnspec_board}-${tnspec_rootdev}"
}

update_partition_name()
{
	# Update partition name
	local name="${1}"
	local partlabel_node="/dev/disk/by-partlabel/${name}"

	# Return directly if the partlabel node does not exist
	if [ ! -e "${partlabel_node}" ]; then
		return 0
	fi

	# Change partition name:
	# kernel -> A_kernel
	# kernel_b -> B_kernel
	# kernel-dtb -> A_kernel-dtb
	# kernel-dtb_b -> B_kernel-dtb
	local updated_name=
	local part_node=
	local part_num=
	local disk_node=

	case "${name}" in
		kernel|kernel-dtb) updated_name="A_${name}"; ;;
		kernel_b|kernel-dtb_b) updated_name="B_${name::-2}"; ;;
		*) return 0; ;;
	esac
	part_node="$(readlink -f "${partlabel_node}")"
	part_num="$(partx -r "${part_node}" -o nr | tail -n 1)"
	disk_node="$(lsblk -slp "${part_node}" -o NAME,TYPE | grep "disk" | cut -d\  -f 1)"
	if ! parted "${disk_node}" name "${part_num}" "${updated_name}"; then
		echo "Warning: Failed to change the name of ${name} partition(${part_node})."
	fi
	return 0
}

# Update partitions names for t234 devices
update_t234_partition_names()
{
	# Update names for kernel and kernel-dtb partitions
	# on both chain for t234 devices

	# Return directly if not t234 devices.
	if [ "${chipid}" != "0x23" ]; then
		return 0
	fi

	# Update its name if either of kernel/kernel_b/kernel-dtb/kernel-dtb
	# partition exists
	update_partition_name "kernel"
	update_partition_name "kernel_b"
	update_partition_name "kernel-dtb"
	update_partition_name "kernel-dtb_b"
}

# Update /dev/disk/by-partlabel if error symbol link found
update_partlabel() {
	local dependecy_table
	local mount_dev_name
	local partlabel_path="/dev/disk/by-partlabel"

	mount_dev_name=$( df / | tail -n 1 | awk '{print $1}' )

	# Find the boot disk device name according to mount_dev_name
	dependecy_table=$( lsblk -sl "${mount_dev_name}" -o NAME,TYPE )

	echo "${dependecy_table}" | while read -r line
	do
		name=$( echo "${line}" | awk '{print $1}' )
		type=$( echo "${line}" | awk '{print $2}' )

		if [ "${type}" == "disk" ]; then
			boot_dev=${name}

			# Check partlabel link, regenerate if error link found
			label_dev_table=$( lsblk -o PARTLABEL,NAME -lp | grep "${boot_dev}" )

			echo "${label_dev_table}" | while read -r line;
			do
				partlabel_name=$( echo "${line}" | awk '{print $1}' )
				partlabel_dev=$( echo "${line}" | awk '{print $2}' )
				if [ "${partlabel_name}" == "" ] || [ "${partlabel_dev}" == "" ]; then
					continue
				fi

				current_device=$( readlink -f ${partlabel_path}/"${partlabel_name}" )
				if [ "${current_device}" == "" ] || [ "${partlabel_dev}" == "${current_device}" ]; then
					continue
				fi

				ln -snf "${partlabel_dev}" ${partlabel_path}/"${partlabel_name}"
				echo "Restore link: ${partlabel_name} to ${partlabel_dev}"
			done
			break;
		fi
	done
}

# Check spec variable, return 0 if it is found; return -1 if it is not found.
is_spec_variable_found() {
	local _spec_name="${1}"
	local _spec_var="${SYS_VAR_DIR}/${_spec_name}-${NVIDIA_GUID}"

	if [ -r "${_spec_var}" ]; then
		return 0;
	fi

	echo "Info: Spec variable ${_spec_name} is not found."
	return 1;
}

# Check the board name and write spec variable:
# If it is an eMMC only board, mount esp partition and write variable to esp.
# Otherwise write variable to /sys/firmware/efi/efivars.
write_spec_variable() {
	local _spec_name="${1}"
	local _spec_val="${2}"
	local _sys_var_path="${SYS_VAR_DIR}/${_spec_name}-${NVIDIA_GUID}"

	# Write attribute field and data field to temp file
	if [ -f "${TMP_FILE}" ]; then
		rm -f "${TMP_FILE}"
	fi
	printf "\x07\x00\x00\x00" > "${TMP_FILE}"
	printf "%s" "${_spec_val}" >> "${TMP_FILE}"
	echo "Info: Write ${_spec_name} with ${_spec_val}."

	# Write variable to sys variable directory
	dd if="${TMP_FILE}" of="${_sys_var_path}" > /dev/null 2>&1
	chattr +i "${_sys_var_path}"

	# Clean up the tmp fie
	rm -f "${TMP_FILE}"
}

active_storage=""
# Find the active boot storage device, maybe eMMC/SD or NVMe or
# USB storage, and save the active boot storage device to BOOT_CTRL_CONF
# TEGRA_BOOT_STORAGE mmcblkN/nvmeN/sdXN
#
# TEGRA_BOOT_STORAGE is deprecated and it is not used in this script or
# any other script in L4T. It will be removed in the JP7 release.
find_and_save_active_boot_storage_node () {
	local active_storage_current=""
	local current_boot=""

	# Check the utilities are installed
	if [ ! -f "$(command -v efibootmgr)" ]; then
		echo "Error: efibootmgr is not installed."
		exit 1
	fi
	if [ ! -f "$(command -v efibootdump)" ]; then
		echo "Error: efibootdump is not installed."
		exit 1
	fi

	# Get current boot from the BootCurrent UEFI variable
	current_boot=$(efibootmgr | grep -w "BootCurrent" | awk '{print $2}')
	if [ "${current_boot}" = "" ]; then
		echo "Error: Failed to get BootCurrent by efibootmgr."
		exit 1
	fi

	# Read qspi version from ESRT
	# JP6 DP release version is r36.2 => 0x242000 => 2359808
	local jp6_dp_ver="2359808"
	read_bsp_ver_from_esrt
	if [ "${bsp_ver}" = "" ]; then
		echo "Error: Failed to read bsp version from ESRT."
		return 1
	fi

	# Check current boot media according to the strings
	# "/eMMC", "/NVMe", "/USB" and "/SD" in the Bootxxxx output.
	NVME0N1_EUI="UNDEFINED"
	NVME1N1_EUI="UNDEFINED"
	if [ -e /sys/block/nvme0n1 ] ; then
		NVME0N1_EUI=$(nvme id-ns /dev/nvme0n1 | grep eui64 | awk '{print $3}' | sed -E 's|.{2}|&-|g; s|-$||')
	fi
	if [ -e /sys/block/nvme1n1 ] ; then
		NVME1N1_EUI=$(nvme id-ns /dev/nvme1n1 | grep eui64 | awk '{print $3}' | sed -E 's|.{2}|&-|g; s|-$||')
	fi

	if efibootdump "Boot${current_boot}" | grep -w "\/eMMC" > /dev/null 2>&1; then
		echo "Info: Boot from eMMC."
		if [[ $((bsp_ver)) = $((jp6_dp_ver)) ]]; then
			active_storage="mmcblk3"
		else
			active_storage="mmcblk0"
		fi
	elif efibootdump "Boot${current_boot}" | grep -wi "\/NVMe(.*,${NVME0N1_EUI}" > /dev/null 2>&1; then
		echo "Info: Boot from NVMe - nvme0n1"
		active_storage="nvme0n1"
	elif efibootdump "Boot${current_boot}" | grep -wi "\/NVMe(.*,${NVME1N1_EUI}" > /dev/null 2>&1; then
		echo "Info: Boot from NVMe - nvme1n1"
		active_storage="nvme1n1"
	elif efibootdump "Boot${current_boot}" | grep -w "\/SD" > /dev/null 2>&1; then
		echo "Info: Boot from SD."
		if efibootmgr -v | grep -w "\/eMMC" > /dev/null 2>&1; then
			# If eMMC exists together with the SD.
			if [[ $((bsp_ver)) = $((jp6_dp_ver)) ]]; then
				active_storage="mmcblk4"
			else
				active_storage="mmcblk1"
			fi
		else
			active_storage="mmcblk0"
		fi
	elif efibootdump "Boot${current_boot}" | grep -w "\/USB" > /dev/null 2>&1; then
		echo "Info: Boot from USB storage."
		active_storage="sd."
	else
		echo "Error: Failed to find known boot media."
		exit 1
	fi

	# Check the device exist or not
	if ! lsblk -lo name | grep "${active_storage}" > /dev/null 2>&1; then
		echo "Error: Active boot storage ${active_storage} not found."
		exit 1
	fi
	echo "Info: Active boot storage: ${active_storage}"

	# Save the active boot storage device to BOOT_CTRL_CONF
	active_storage_current=$( awk '/TEGRA_BOOT_STORAGE/ {print $2}' "${BOOT_CTRL_CONF}" )
	if [ "${active_storage_current}" != "${active_storage}" ]; then
		if [ "${active_storage_current}" != "" ]; then
			sed -i '/TEGRA_BOOT_STORAGE*/d' "${BOOT_CTRL_CONF}"
		fi
		sed -i "2 a TEGRA_BOOT_STORAGE ${active_storage}" "${BOOT_CTRL_CONF}"
	fi
}

# Install mtdblock and its dependent modules
install_mtdblock () {
	local kernel_ver=
	local mtd_dir=
	kernel_ver="$(uname -r)"
	mtd_dir="/lib/modules/${kernel_ver}/kernel/drivers/mtd"

	# mtdblock has dependency on mtd and mtd_blkdevs
	if [ -f "${mtd_dir}/mtd.ko" ] \
		&&  [ -f "${mtd_dir}/mtd_blkdevs.ko" ] \
		&&  [ -f "${mtd_dir}/mtdblock.ko" ]; then
		echo "Info: Installing mtdblock."
		modprobe mtdblock
	fi
}

# Uninstall mtdblock and its dependent modules
uninstall_mtdblock () {
	echo "Info: Uninstalling mtdblock."
	modprobe -r mtdblock
}

# mount the esp on active media to the mount point /boot/efi.
mount_esp () {
	local active_storage="${1}"
	local esp_uuid=""
	local num_of_esp=""

	# Return error if there are multiple esps on the active storage
	num_of_esp=$(lsblk -lo name,parttype | grep "${active_storage}" | grep -iwc "${EFI_PART_TYPE_GUID}")
	if [[ $((num_of_esp)) -gt 1 ]]; then
		echo "Error: Multiple esp partitions are found on active storage ${active_storage}."
		return 1
	fi

	# Find the uuid of active boot media's esp partition
	esp_uuid=$(lsblk -o name,parttype,uuid | grep "${active_storage}" | grep -iw "${EFI_PART_TYPE_GUID}" | awk '{print $3}')
	if [ "${esp_uuid}" == "" ]; then
		echo "Error: Failed to find the uuid of esp."
		return 1
	fi
	# Check whether the active boot media's esp is mounted to /boot/efi, return if mounted.
	if lsblk -lo name,mountpoint,uuid | grep -w "${esp_uuid}" | grep -w "${ESP_MOUNT_POINT}" > /dev/null 2>&1; then
		echo "Info: The esp partition uuid=${esp_uuid} is mounted to ${ESP_MOUNT_POINT}."
		return 0
	fi

	# The active boot media's esp is not mounted, mount it to the /boot/efi
	# Create esp mount point if does not exist
	if [ ! -d "${ESP_MOUNT_POINT}" ]; then
		mkdir -p "${ESP_MOUNT_POINT}"
	fi

	# Unmount the esp in case it is mounted to other directory
	umount /dev/disk/by-uuid/"${esp_uuid}" > /dev/null 2>&1

	# Mount the esp to /boot/efi
	if ! mount UUID="${esp_uuid}" "${ESP_MOUNT_POINT}"; then
		echo "Error: Failed to mount esp ${esp_uuid} to ${ESP_MOUNT_POINT}."
		return 2
	fi
	echo "Info: Mount esp uuid=${esp_uuid} to ${ESP_MOUNT_POINT}."

	# Configure the /etc/fstab to mount esp automatically at boot time.
	sed -i '/UUID='"${esp_uuid}"'/d' "${FS_TABLE}"
	sed -i '$a\UUID='"${esp_uuid}"' '"${ESP_MOUNT_POINT}"' vfat defaults 0 1' "${FS_TABLE}"

	return 0
}

# Check esp is mount or not, if it's not mounted,
# mount the esp on active media to the mount point /boot/efi.
check_and_mount_esp () {
	# Check whether the /boot/efi is mount or not.
	if ! mountpoint -q "${ESP_MOUNT_POINT}"; then
		echo "Info: The esp is not mounted to ${ESP_MOUNT_POINT}."

		# Find the active boot storage device
		find_and_save_active_boot_storage_node

		if [ "${active_storage}" != "" ]; then
			if ! mount_esp "${active_storage}"; then
				echo "Error: Failed to mount esp on ${active_storage}."
				exit 1
			fi
		else
			echo "Error: Active boot storage is not found."
			exit 1
		fi
	else
		echo "Info: The esp is already mounted to ${ESP_MOUNT_POINT}."
	fi
}

update_compat_spec_only=""
update_spec_variables=""
verify_boot_status=""

update_partlabel

while [ $# -gt 0 ]; do
	case "${1}" in
		-c|--update-compat-spec) update_compat_spec_only="1" ;;
		-f|--boot-control-file) BOOT_CTRL_CONF="${2}"; shift ;;
		-l|--update-spec-vars) update_spec_variables="1"; shift ;;
		-v|--verify-boot-status) verify_boot_status="1"; shift ;;
		*) echo "Terminating... wrong option: $*" >&2 ; exit 1 ;;
	esac
	shift
done

if [ ! -r "${BOOT_CTRL_CONF}" ]; then
	echo "Error: Cannot open ${BOOT_CTRL_CONF} for reading."
	echo "Cannot install package. Exiting..."
	exit 1
fi

chipid=$( awk '/TEGRA_CHIPID/ {print $2}' "${BOOT_CTRL_CONF}" )
tnspec=$( awk '/TNSPEC/ {print $2}' "${BOOT_CTRL_CONF}" )
compatible_spec_current=$( awk '/COMPATIBLE_SPEC/ {print $2}' "${BOOT_CTRL_CONF}" )
compatible_spec=""
board_spec=""

update_t234_partition_names

# if read EEPROM board info successfully, generate board_spec and replace TNSPEC
# if read failed, use tnspec in /etc/nv_boot_control.conf as board_spec
if read_eeprom_board_info; then
	# Generate board_spec by combining info from EEPROM and TNSPEC
	generate_board_spec "${tnspec}" "board_spec"

	# Replace TNSPEC with board_spec
	if [ "${tnspec}" != "${board_spec}" ]; then
		if [ "${tnspec}" != "" ]; then
			sed -i '/TNSPEC*/d' "${BOOT_CTRL_CONF}"
		fi
		sed -i "1i TNSPEC ${board_spec}" "${BOOT_CTRL_CONF}"
	fi
else
	board_spec=${tnspec}
fi

generate_compatible_spec_info "${board_spec}"

if [ "${compatible_spec_current}" != "${compatible_spec}" ]; then
	if [ "${compatible_spec_current}" != "" ]; then
		sed -i '/COMPATIBLE_SPEC*/d' "${BOOT_CTRL_CONF}"
	fi
	sed -i "1 a COMPATIBLE_SPEC ${compatible_spec}" "${BOOT_CTRL_CONF}"
fi

# If update_compat_spec_only is set, update the compatible spec in BOOT_CTRL_CONF only
if [ "${update_spec_variables}" != "1" ] && [ "${update_compat_spec_only}" = "1" ]; then
	exit 0;
fi

# Dump BOOT_CTRL_CONF to output of syslog
cat "${BOOT_CTRL_CONF}"

# Check tnspec variable, if it is not found, create the variable and write with ${board_spec}.
if ! is_spec_variable_found "${TNSPEC_NAME}"; then
	write_spec_variable "${TNSPEC_NAME}" "${board_spec}"
fi

# Always write the compatible spec variable.
if is_spec_variable_found "${COMPAT_SPEC_NAME}"; then
	chattr -i "${SYS_VAR_DIR}/${COMPAT_SPEC_NAME}-${NVIDIA_GUID}"
fi
write_spec_variable "${COMPAT_SPEC_NAME}" "${compatible_spec}"

# Check esp is mounted or not, mount the esp if not mount.
check_and_mount_esp

# If update_spec_variables is set, update the tnspec and compatible spec variables then exit.
if [ "${update_spec_variables}" = "1" ]; then
	exit 0;
fi

# Update QSPI automatically for devkit if BSP version in QSPI is older than SDcard.
auto_update_qspi

# If verify_boot_status is set, call nvbootctrl to verify the boot status.
if [ "${verify_boot_status}" = "1" ]; then
	# IGX does not support rootfs A/B, verify only bootloader.
	if [[ "${compatible_spec}" == *"igx-orin-devkit-"
		|| "${compatible_spec}" == *"igx-orin-safety-" ]]; then
		echo "Info: Verifying bootloader status."
		nvbootctrl verify-bl
	else
		# Verify both bootloader and rootfs.
		echo "Info: Verifying bootloader and rootfs status."
		nvbootctrl verify
	fi
fi
