#!/bin/bash -efu
# SPDX-License-Identifier: GPL-3.0-or-later

. sh-functions

# to be sure that microcode loading supported by kernel
if [ -f "$KERNEL_CONFIG" ]; then
	grep -E -qs "^CONFIG_MICROCODE(|_INTEL|_AMD)=[my]" "$KERNEL_CONFIG" ||
		fatal "Microcode loading support is missing in kernel"
fi

if ! IUCODE_BIN="$(type -P iucode_tool)"; then
	message "Utility not found: iucode_tool"
	exit 0
fi

cpu_vendor="${UCODE_CPU_VENDOR-}"
cpu_family="${UCODE_CPU_FAMILY-}"
iucode_scan_system='--scan-system'

[ -z "${UCODE_ALL_MICROCODE-}" ] ||
	iucode_scan_system=

if [ -z "$cpu_vendor" ]; then
	cpu="$(sed -r -n \
		-e '1,/^processor[[:cntrl:]]\:.*/ d' \
		-e '/^model[[:space:]]+[[:cntrl:]]\:.*/,$ d' \
		-e 's/.*[[:cntrl:]]\:[[:space:]](.*)/\1/ip' \
		"$PROCFS_PATH/cpuinfo"
	)"
	if [ -n "$cpu" ]; then
		cpu_vendor="${cpu%%
*}"
		cpu_family="${cpu##*
}"
	fi
fi

prep_amd_ucode()
{
	local fw_path fw_file='' fw

	set -- \
		"amd-ucode/microcode_amd_fam$(printf %x "$cpu_family")h.bin" \
		"amd-ucode/microcode_amd.bin"

	for fw in "$@"; do
		for fw_path in ${FIRMWARE_DIRS:-/lib/firmware}; do
			if [ -f "$fw_path/$fw" ]; then
				fw_file="$fw_path/$fw"
				break 2
			fi
		done
	done

	if [ -z "$fw_file" ]; then
		message "firmware file not found for cpu model \`$cpu_vendor' and family \`$cpu_family'. Microcode update unavailable."
		return 0
	fi

	mkdir "$WORKDIR"/ucode ||
		return
	mkdir -p -- "$WORKDIR"/ucode/kernel/x86/microcode
	cp ${VERBOSE:+-v} -- "$fw_file" "$WORKDIR"/ucode/kernel/x86/microcode/"$cpu_vendor".bin
}

prep_intel_ucode()
{
	# microcode loading supported only for CPUs >= PentiumPro
	[ -n "$cpu_family" ] && [ "$cpu_family" -ge 6 ] || return 1

	[ -x "$IUCODE_BIN" ] ||
		return 1

	local fw_path
	for fw_path in ${FIRMWARE_DIRS:-/lib/firmware}; do
		if [ -d "$fw_path"/intel-ucode ]; then
			"$IUCODE_BIN" \
					--quiet \
					$iucode_scan_system \
					--write-earlyfw="$WORKDIR"/ucode.cpio \
					"$fw_path"/intel-ucode
			[ -f "$WORKDIR"/ucode.cpio ] ||
				continue
			mkdir "$WORKDIR"/ucode &&
				cpio -D "$WORKDIR"/ucode --quiet -id < "$WORKDIR"/ucode.cpio
			return
		fi
	done
}

case "$cpu_vendor" in
	AuthenticAMD) prep_amd_ucode
		;;
	GenuineIntel) prep_intel_ucode
		;;
	*) exit 0
		;;
esac
rc=$?

[ "$rc" -eq 0 ] ||
	exit $rc

if [ -d "$WORKDIR/ucode" ]; then
	cd "$WORKDIR/ucode"

	find . -type f -execdir touch -c -m --date="1970-01-01 00:00:00 +0000" '{}' '+'

	# shellcheck disable=SC2185
	find -O2 . -mindepth 1 -print0 |
		sort -z |
		cpio --null --quiet --create --format=newc --reproducible > "$WORKDIR"/ucode.cpio

	rm -rf -- "$WORKDIR/ucode"
fi
