#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-2.0-only

get_host_vendor() {
    if grep -aiq 'vendor_id.*amd' /proc/cpuinfo 2>/dev/null; then
        echo 'amd'
    elif grep -aiq 'vendor_id.*intel' /proc/cpuinfo 2>/dev/null; then
        echo 'intel'
    fi
}

get_host_ucode() {
    local vendor="$1" family model stepping
    family="$(grep -am1 'cpu family' /proc/cpuinfo | sed 's/.*: //')"
    model="$(grep -am1 'model[[:space:]]*:' /proc/cpuinfo | sed 's/.*: //')"
    stepping="$(grep -am1 'stepping' /proc/cpuinfo | sed 's/.*: //')"

    case "$vendor" in
        amd)
            # 21 = 15h
            if (( family >= 21 )); then
                printf 'amd-ucode/microcode_amd_fam%xh.bin' "$family"
            else
                printf 'amd-ucode/microcode_amd.bin'
            fi
            ;;
        intel)
            printf 'intel-ucode/%02x-%02x-%02x' "$family" "$model" "$stepping"
            ;;
    esac
}

build() {
    local arch vendor fwpath ucode_file
    local vendors=(intel amd)
    local ucode_dest='/kernel/x86/microcode'
    local -A ucode_dir=([intel]=intel-ucode [amd]=amd-ucode)
    local -A ucode_img=([intel]=intel-ucode.img [amd]=amd-ucode.img)
    local -A ucode_bin=([intel]=GenuineIntel.bin [amd]=AuthenticAMD.bin)
    local -A ucode_glob=([intel]='??-??-??' [amd]='*.bin')
    local -a ucode_files

    arch="$(uname -m)"
    if [[ "$arch" != @(i?86|x86_64) ]]; then
        warning "architecture '%s' not supported, skipping hook" "$arch"
        return
    fi

    # mkinitcpio_autodetect is assigned in install/autodetect
    # shellcheck disable=SC2154
    if (( mkinitcpio_autodetect )); then
        vendor="$(get_host_vendor)"
        ucode_file="$(get_host_ucode "$vendor")"
        # _d_fwpath is assigned in mkinitcpio
        # shellcheck disable=SC2154
        for fwpath in "${_d_fwpath[@]}"; do
            if [[ -f "${fwpath}/${ucode_file}" ]]; then
                quiet "adding microcode file: '%s'" "${fwpath}/${ucode_file}"
                add_file_early "${fwpath}/${ucode_file}" "${ucode_dest}/${ucode_bin[$vendor]}" 644
                return
            fi
        done
        # if no host-specific microcode is found, don't continue and add all microcode
        return
    fi

    for vendor in "${vendors[@]}"; do
        # find the uncompiled update files and compile them
        # _d_fwpath is assigned in mkinitcpio
        # shellcheck disable=SC2154
        for fwpath in "${_d_fwpath[@]}"; do
            if [[ -e "${EARLYROOT}/${ucode_dest}/${ucode_bin[$vendor]}" ]]; then
                break
            fi
            if [[ ! -d "${fwpath}/${ucode_dir[$vendor]}" ]]; then
                continue
            fi
            mapfile -t ucode_files < <(LC_ALL=C.UTF-8 find "${fwpath}/${ucode_dir[$vendor]}" -maxdepth 1 -name "${ucode_glob[$vendor]}")
            if ! (( ${#ucode_files[@]} )); then
                continue
            fi
            quiet "adding microcode files: '%s'" "${ucode_files[*]}"
            cat "${ucode_files[@]}" | add_file_early - "${ucode_dest}/${ucode_bin[$vendor]}" 644
            break
        done

        # as a last resort, find and extract a precompiled image in /boot
        if [[ ! -e "${EARLYROOT}/${ucode_dest}/${ucode_bin[$vendor]}" && -r "/boot/${ucode_img[$vendor]}" ]]; then
            quiet "extracting precompiled microcode image: '/boot/%s'" "${ucode_img[$vendor]}"
            LC_ALL=C.UTF-8 bsdtar -xOf "/boot/${ucode_img[$vendor]}" "${ucode_dest#/}/${ucode_bin[$vendor]}" | \
                add_file_early - "${ucode_dest}/${ucode_bin[$vendor]}" 644
        fi
    done
}

help() {
    cat <<HELPEOF
This hook adds early microcode update files for Intel and AMD processors. If
individual microcode files exist in /lib/firmware/, they are included in a
uncompressed initramfs image which is prepended to the main initramfs image.
If individual microcode files are not available, but /boot/amd-ucode.img or
/boot/intel-ucode.img exist, they are extracted and used instead.

If the autodetect hook runs before this hook, it will only add early microcode
update files for the processor of the system the image is built on.
HELPEOF
}

# vim: set ft=sh ts=4 sw=4 et:
