Files
CISS.debian.installer/func/cdi_4100_base/4145_installation_firmware.sh
2025-10-13 17:13:09 +01:00

356 lines
10 KiB
Bash

#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2025-06-17; WEIDNER, Marc S.; <msw@coresecret.dev>
# 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.; <msw@coresecret.dev>
# 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
apt-get install -y --no-install-recommends --no-install-suggests ${ary_pkgs_resolved[*]} 2>&1 | tee -a ${var_logfile}
echo ExitCode of PIPESTATUS[0]: [\${PIPESTATUS[0]}] >> ${var_logfile}
"
guard_dir && return 0
}
#######################################
# 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
}
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh