#!/bin/bash # SPDX-Version: 3.0 # SPDX-CreationInfo: 2025-06-17; WEIDNER, Marc S.; # SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.installer.git # SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency # SPDX-FileCopyrightText: 2024-2025; WEIDNER, Marc S.; # SPDX-FileType: SOURCE # SPDX-License-Identifier: EUPL-1.2 OR LicenseRef-CCLA-1.0 # SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework. # SPDX-PackageName: CISS.debian.installer # SPDX-Security-Contact: security@coresecret.eu guard_sourcing ####################################### # Install microcode updates depending on architecture (amd64, arm64, intel64) and environment (Baremetal, VM). # Every 'apt-get install' command is invoked by adding 'export INITRD=No' # to suppress the 'update-initramfs'-Kernel-Hooks, according to the initramfs-tools manpage: # https://manpages.debian.org/testing/initramfs-tools-core/initramfs-tools.7.en.html # Globals: # TARGET # firmware_lookup # image # Arguments: # None # Returns: # 0: on success ####################################### installation_firmware() { ### Declare Arrays, HashMaps, and Variables. declare -a ary_bus=( "acpi" "hid" "i2c" "mdio" "of" "pci" "platform" "pnp" "serio" "spi" "usb" "virtio" ) \ ary_files=() ary_mods=() ary_pkgs_resolved=() declare var_kernel="${image#linux-image-}" var_fw_policy="${firmware_lookup:-missing}" \ dir_fw="${TARGET}/root/.ciss/cdi/log/firmware" \ var_alias="" var_bus="" var_file="" var_found="" var_fw="" var_mod="" var_pkgs="" var_re="" var_wc_alias="" declare -r var_logfile="/root/.ciss/cdi/log/4145_installation_firmware.log" declare -A hmp_alias_unique=() hmp_fw_present=() hmp_fw_unique=() hmp_module_unique=() hmp_want_pkgs=() declare -A hmp_fw_map=( ["^iwlwifi/.*\\.ucode$"]="firmware-iwlwifi" ["^ath10k/.*\\.bin$"]="firmware-atheros" ["^ath11k/.*\\.bin$"]="firmware-atheros" ["^brcm/.*\\.(bin|txt)$"]="firmware-brcm80211" ["^libertas/.*"]="firmware-libertas" ["^rtlwifi/.*\\.(bin|fw)$"]="firmware-realtek" ["^rtl_bt/.*\\.(bin|fw)$"]="firmware-realtek" ["^mediatek/.*"]="firmware-misc-nonfree" ["^rtl_nic/.*\\.fw$"]="firmware-realtek" ["^bnx2/.*\\.fw$"]="firmware-bnx2" ["^bnx2x/.*\\.fw$"]="firmware-bnx2x" ["^qed/.*\\.bin$"]="firmware-qlogic" ["^qla2xxx/.*\\.bin$"]="firmware-qlogic" ["^cxgb4/.*"]="firmware-chelsio" ["^netronome/.*"]="firmware-netronome" ["^ice/.*\\.pkg$"]="firmware-misc-nonfree" ["^amdgpu/.*\\.bin$"]="firmware-amd-graphics" ["^radeon/.*\\.bin$"]="firmware-amd-graphics" ["^i915/.*\\.(bin|dmc)$"]="firmware-misc-nonfree" ["^sof/.*"]="firmware-sof-signed" ["^mrvl/.*"]="firmware-misc-nonfree" ["^aspeed/.*"]="firmware-misc-nonfree" ) chroot_logger "${TARGET}${var_logfile}" if [[ ! -d "${TARGET}/lib/modules/${var_kernel}" ]]; then do_log "error" "file_only" "4145() Target modules-directory missing: ${TARGET}/lib/modules/${var_kernel}." return 0 fi ### Step 1: Collect all module aliases per bus (deterministic inputs): mkdir -p "${dir_fw}" : >| "${dir_fw}/4145_s1_mod_aliases_all.txt" for var_bus in "${ary_bus[@]}"; do : >| "${dir_fw}/4145_s1_mod_aliases_${var_bus}.txt" ### Safe enumeration without failing on unmatched globs. # shellcheck disable=SC2312 readarray -t ary_files < <(compgen -G "/sys/bus/${var_bus}/devices"/*/modalias) for var_file in "${ary_files[@]}"; do if [[ -r "${var_file}" ]]; then var_alias="" IFS= read -r var_alias < "${var_file}" || true if [[ -n "${var_alias}" ]]; then if [[ -z "${hmp_alias_unique[${var_alias}]:-}" ]]; then hmp_alias_unique["${var_alias}"]=1 printf '%s\n' "${var_alias}" >> "${dir_fw}/4145_s1_mod_aliases_all.txt" fi printf '%s\n' "${var_alias}" >> "${dir_fw}/4145_s1_mod_aliases_${var_bus}.txt" fi fi done done ### Step 2: Resolve modules from aliases against the "TARGET" kernel tree. : >| "${dir_fw}/4145_s2_alias_to_modules.txt" var_alias="" for var_alias in "${!hmp_alias_unique[@]}"; do var_wc_alias="$(wildcard_mod_alias "${var_alias}")" ### Resolve modules in the "TARGET" root (-d) and for the "var_kernel" (-S) while IFS= read -r var_mod; do if [[ -n "${var_mod}" ]]; then ary_mods+=("${var_mod}") fi done < <(modprobe -R "${var_wc_alias}" -S "${var_kernel}" -d "${TARGET}" 2>/dev/null || true) if [[ "${#ary_mods[@]}" -eq 0 ]]; then printf '%s\t%s\n' "${var_alias}" "-" >> "${dir_fw}/4145_s2_alias_to_modules.txt" else for var_mod in "${ary_mods[@]}"; do hmp_module_unique["${var_mod}"]=1 printf '%s\t%s\n' "${var_alias}" "${var_mod}" >> "${dir_fw}/4145_s2_alias_to_modules.txt" done fi done ### Step 3: Resolve modules to firmware filenames; mark present/missing in "TARGET". : >| "${dir_fw}/4145_s3_module_to_firmware.txt" : >| "${dir_fw}/4145_s3_firmware_present.txt" : >| "${dir_fw}/4145_s3_firmware_missing.txt" for var_mod in "${!hmp_module_unique[@]}"; do ### Query modinfo in the 'TARGET' base (-b) for the target kernel image (-k) while IFS= read -r var_fw; do if [[ -n "${var_fw}" ]]; then var_found="1" ### Normalize to the path relative to '/lib/firmware' ### modinfo may output "amdgpu/..." or "rtl_nic/..." already relative; handle both. if [[ "${var_fw}" == /lib/firmware/* ]]; then var_fw="${var_fw#/lib/firmware/}" fi hmp_fw_unique["${var_fw}"]=1 printf '%s\t%s\n' "${var_mod}" "${var_fw}" >> "${dir_fw}/4145_s3_module_to_firmware.txt" if [[ -e "${TARGET}/lib/firmware/${var_fw}" ]]; then hmp_fw_present["${var_fw}"]="yes" else hmp_fw_present["${var_fw}"]="no" fi fi done < <(modinfo -k "${var_kernel}" -b "${TARGET}" -F firmware "${var_mod}" 2>/dev/null || true) if [[ -z "${var_found}" ]]; then printf '%s\t-\n' "${var_mod}" >> "${dir_fw}/4145_s3_module_to_firmware.txt" fi done ### Emit present/missing lists. for var_fw in "${!hmp_fw_unique[@]}"; do if [[ "${hmp_fw_present[${var_fw}]:-no}" == "yes" ]]; then printf '%s\n' "${var_fw}" >> "${dir_fw}/4145_s3_firmware_present.txt" else printf '%s\n' "${var_fw}" >> "${dir_fw}/4145_s3_firmware_missing.txt" fi done ### Step 4: Read "${dir_fw}/4145_s3_firmware_missing.txt" and map to the package set using 'hmp_fw_map'. var_fw="" : >| "${dir_fw}/4145_s4_packages_resolved.txt" if [[ -s "${dir_fw}/4145_s3_firmware_missing.txt" ]]; then while IFS= read -r var_fw; do if [[ -z "${var_fw}" ]]; then continue fi declare var_matched="" ### Iterate through the explicit map first. for var_re in "${!hmp_fw_map[@]}"; do if [[ "${var_fw}" =~ ${var_re} ]]; then hmp_want_pkgs["${hmp_fw_map[${var_re}]}"]=1 var_matched="1" break fi done if [[ -z "${var_matched}" ]]; then ### Fallback heuristics by top-level directory. case "${var_fw}" in iwlwifi/*) hmp_want_pkgs["firmware-iwlwifi"]=1 ;; rtlwifi/*|rtl_*/*) hmp_want_pkgs["firmware-realtek"]=1 ;; amdgpu/*|radeon/*) hmp_want_pkgs["firmware-amd-graphics"]=1 ;; i915/*) hmp_want_pkgs["firmware-misc-nonfree"]=1 ;; ath10k/*|ath11k/*) hmp_want_pkgs["firmware-atheros"]=1 ;; brcm/*) hmp_want_pkgs["firmware-brcm80211"]=1 ;; bnx2/*) hmp_want_pkgs["firmware-bnx2"]=1 ;; bnx2x/*) hmp_want_pkgs["firmware-bnx2x"]=1 ;; qed/*|qla2xxx/*) hmp_want_pkgs["firmware-qlogic"]=1 ;; cxgb4/*) hmp_want_pkgs["firmware-chelsio"]=1 ;; netronome/*) hmp_want_pkgs["firmware-netronome"]=1 ;; sof/*) hmp_want_pkgs["firmware-sof-signed"]=1 ;; ice/*) hmp_want_pkgs["firmware-misc-nonfree"]=1 ;; *) do_log "warn" "file_only" "4145() No map entry for: '${var_fw}'." ;; esac fi done < "${dir_fw}/4145_s3_firmware_missing.txt" else do_log "info" "file_only" "4145() No missing firmware file found." fi ### Emit unique package list. for var_pkgs in "${!hmp_want_pkgs[@]}"; do printf '%s\n' "${var_pkgs}" done | sort -u >| "${dir_fw}/4145_s4_packages_resolved.txt" ### Step 5: Install packages according to policy (always|missing|never). : >| "${dir_fw}/4145_s5_installation_cmd.txt" : >| "${dir_fw}/4145_s5_installation_out.txt" if [[ "${var_fw_policy}" == "always" ]]; then do_log "info" "file_only" "4145() Policy=always: installing broad firmware sets." printf '%s\n' "firmware-linux" "firmware-misc-nonfree" >> "${dir_fw}/4145_s4_packages_resolved.txt" fi if [[ ! -s "${dir_fw}/4145_s4_packages_resolved.txt" ]]; then do_log "info" "file_only" "4145() Nothing to install." return 0 fi mapfile -t ary_pkgs_resolved < "${dir_fw}/4145_s4_packages_resolved.txt" if [[ "${var_fw_policy}" == "never" ]]; then do_log "info" "file_only" "4145() Policy=never: Packages would be: ${ary_pkgs_resolved[*]}." return 0 fi chroot_script "${TARGET}" " export INITRD=No [[ -r /root/ciss_xdg_tmp.sh ]] && . /root/ciss_xdg_tmp.sh apt-get install -y --no-install-recommends --no-install-suggests ${ary_pkgs_resolved[*]} 2>&1 | tee -a ${var_logfile} " guard_dir && return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f installation_firmware ####################################### # Helper: Wildcardize a module alias (bus-aware, conservative) # Arguments: # 1: Module alias # Returns: # 0: on success ####################################### wildcard_mod_alias() { ### Keep vendor/device exact, relax subfields, fall back to original if not matched. declare a="${1}" out="" case "${a}" in pci:*) if [[ "${a}" == *sv* ]]; then out="${a%%sv*}sv*sd*bc*sc*i*" elif [[ "${a}" == *bc* ]]; then out="${a%%bc*}bc*sc*i*" else out="${a}" fi ;; usb:*) if [[ "${a}" == *ic* ]]; then out="${a%%ic*}ic*isc*ip*in*" elif [[ "${a}" == *dc* ]]; then out="${a%%dc*}dc*dsc*dp*ic*isc*ip*in*" else out="${a}" fi ;; platform:*|acpi:*|of:*|i2c:*|spi:*|mdio:*|virtio:*|pnp:*|serio:*|hid:*) out="${a}" ;; *) out="${a}" ;; esac printf '%s\n' "${out}" return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f wildcard_mod_alias # vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh