diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9fc270f3b..106fe6aac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,9 +25,16 @@ jobs: -w /workspace \ archlinux:base-devel bash -lc ' set -euo pipefail - pacman -Syu --noconfirm git jq + pacman -Syu --noconfirm git jq python-yaml xz zstd ./bin/sync-upstream self-test ./bin/sync-rebuilds --self-test ./bin/omarchy-pkgs self-test ./bin/omarchy-release self-test + shopt -s nullglob + for test in pkgbuilds/*/test.sh; do + bash "$test" + done + if [[ -d test ]]; then + runuser -u nobody -- python -m unittest discover -s test -v + fi ' diff --git a/pkgbuilds/qcom-firmware-extract/.omarchy/package.json b/pkgbuilds/qcom-firmware-extract/.omarchy/package.json new file mode 100644 index 000000000..2a9719d3f --- /dev/null +++ b/pkgbuilds/qcom-firmware-extract/.omarchy/package.json @@ -0,0 +1,3 @@ +{ + "source": "local" +} diff --git a/pkgbuilds/qcom-firmware-extract/PKGBUILD b/pkgbuilds/qcom-firmware-extract/PKGBUILD new file mode 100644 index 000000000..f550fa2d0 --- /dev/null +++ b/pkgbuilds/qcom-firmware-extract/PKGBUILD @@ -0,0 +1,24 @@ +# Maintainer: Jimmy Van Veen + +# Copy vendor-signed Qualcomm firmware named by the running device tree. + +pkgname=qcom-firmware-extract +pkgver=1 +pkgrel=4 +pkgdesc="Copy vendor-signed Qualcomm Snapdragon firmware from the machine's Windows installation" +arch=('any') +url="https://github.com/omacom/omarchy-pkgs" +license=('GPL-2.0-or-later') +depends=('bash' 'coreutils' 'diffutils' 'findutils' 'util-linux' 'xz' 'zstd') +optdepends=('limine-mkinitcpio-hook: rebuild the boot image after installing firmware') +options=('!debug') +source=( + 'qcom-firmware-extract' + 'README.package.md' +) +sha256sums=('SKIP' 'SKIP') + +package() { + install -Dm755 qcom-firmware-extract "${pkgdir}/usr/bin/qcom-firmware-extract" + install -Dm644 README.package.md "${pkgdir}/usr/share/doc/${pkgname}/README.md" +} diff --git a/pkgbuilds/qcom-firmware-extract/README.package.md b/pkgbuilds/qcom-firmware-extract/README.package.md new file mode 100644 index 000000000..816e0f513 --- /dev/null +++ b/pkgbuilds/qcom-firmware-extract/README.package.md @@ -0,0 +1,55 @@ +# qcom-firmware-extract + +Copies the vendor-signed Qualcomm firmware a Snapdragon laptop needs, including +the GPU zap shader and audio/compute DSP images, from the owner's own Windows +installation on the same machine into `/usr/lib/firmware/updates/`. + +Omarchy ships no vendor-signed firmware: the live ISO and every package carry +only files from linux-firmware. The files this tool copies never leave the +machine and are never downloaded. It is a temporary measure until laptop +vendors contribute complete firmware sets to linux-firmware. + +## How it works + +The device tree names every firmware file the kernel will ask for +(`firmware-name` properties). The tool keeps the names that are missing under +`/usr/lib/firmware{,/updates}`, finds each one by file name in +`Windows/System32/DriverStore/FileRepository` on any NTFS partition it can +mount read-only. If Windows carries several variants and linux-firmware has a +companion image from the same device-tree node, the sibling image's hash +selects the compatible variant. Identical duplicates are accepted; differing +variants without a unique companion match are skipped. The result is +installed under `/usr/lib/firmware/updates/`. The GPU zap shader is added to the +initramfs through `/etc/mkinitcpio.conf.d/qcom-firmware.conf`; the DSP images +are loaded from the root filesystem. What was installed, from where, and its +checksum is recorded in `/var/lib/omarchy/qcom-firmware/manifest`. + +Nothing is model-specific. A laptop whose firmware linux-firmware already +ships gets nothing copied; a machine without a device tree exits at once. + +## When it runs + +- **Installer, live session:** `qcom-firmware-extract --stage DIR` right + after the disk is chosen, before anything is written. A full-disk install + destroys the Windows partition the files come from, so this is the only + moment they can be read. The stage is copied into the target. +- **Installer, hardware setup:** `qcom-firmware-extract --install --no-rebuild` + from `install/hardware/qualcomm/firmware.sh`, using the stage + (or a Windows partition still on disk). The installer builds the boot image + once afterwards. +- **Installed system:** `sudo qcom-firmware-extract` scans the disks again, + or `sudo qcom-firmware-extract -d /path/to/FileRepository` takes any driver + store you can mount (a Windows install of the *same model*: the files are + tied to the vendor's signing keys). It rebuilds the boot image; reboot + afterwards. + +BitLocker volumes cannot be read; turn BitLocker off in Windows first. + +## Retirement + +When linux-firmware ships a machine's vendor directory the tool copies nothing +and still configures its GPU firmware for early display. When every supported machine is covered, drop the +package and prune the `/usr/lib/firmware/updates` entries listed in the +manifest. + +Derived from Canonical's `qcom-firmware-extract` (GPL-2+). diff --git a/pkgbuilds/qcom-firmware-extract/qcom-firmware-extract b/pkgbuilds/qcom-firmware-extract/qcom-firmware-extract new file mode 100755 index 000000000..f8ef7fb41 --- /dev/null +++ b/pkgbuilds/qcom-firmware-extract/qcom-firmware-extract @@ -0,0 +1,367 @@ +#!/bin/bash +# +# Copy missing vendor-signed Qualcomm firmware from a Windows driver store. +# Required filenames come from the running device tree. +# +# Modes +# --stage DIR Live ISO, before the disk is touched: scan the Windows +# partitions and save the missing files under DIR (the +# installer copies DIR into the target). Missing firmware +# is reported without aborting staging. +# --install Installed system or the installer's chroot: take the +# files from the stage (--stage-dir, default +# /var/lib/omarchy/firmware-stage), else from a Windows +# partition still on disk, write them under +# /usr/lib/firmware/updates, record a manifest, add the +# zap shader to the initramfs, and rebuild the boot image +# (unless --no-rebuild). This is the default mode. +# --list-missing Print the device-tree firmware names not present under +# /usr/lib/firmware, one per line. Needs no root. +# -d DIR Use DIR as the driver store (a Windows/System32/ +# DriverStore/FileRepository directory, or any directory +# tree holding the files) instead of scanning partitions. +# +# Files land in /usr/lib/firmware/updates to avoid package ownership conflicts. +# +# Derived from Canonical's qcom-firmware-extract (Tobias Heider, GPL-2+): +# https://salsa.debian.org/debian/qcom-firmware-extract +# SPDX-License-Identifier: GPL-2.0-or-later + +set -euo pipefail + +DT_ROOT=${QCOM_FW_DT_ROOT:-/sys/firmware/devicetree/base} +FW_ROOT=${QCOM_FW_FIRMWARE_ROOT:-/usr/lib/firmware} +# Destination prefix for the installed-system paths (tests only). +ROOT=${QCOM_FW_ROOT:-} +STATE_DIR="$ROOT/var/lib/omarchy/qcom-firmware" +MANIFEST="$STATE_DIR/manifest" +INITCPIO_CONF="$ROOT/etc/mkinitcpio.conf.d/qcom-firmware.conf" +DEFAULT_STAGE_DIR="$ROOT/var/lib/omarchy/firmware-stage" +DRIVERSTORE_PATH="Windows/System32/DriverStore/FileRepository" + +mode=install +stage_dir="" +driverstore="" +rebuild=yes + +usage() { + sed -n '2,/^# SPDX/{/^# SPDX/d;s/^# \{0,1\}//p}' "$0" +} + +while (($#)); do + case $1 in + --stage) mode=stage; stage_dir=${2:?--stage needs a directory}; shift ;; + --stage-dir) stage_dir=${2:?--stage-dir needs a directory}; shift ;; + --install) mode=install ;; + --list-missing) mode=list ;; + --no-rebuild) rebuild=no ;; + -d) driverstore=${2:?-d needs a directory}; shift ;; + -h | --help) usage; exit 0 ;; + *) echo "qcom-firmware-extract: unknown option: $1" >&2; usage >&2; exit 2 ;; + esac + shift +done + +log() { printf 'qcom-firmware-extract: %s\n' "$*"; } +warn() { printf 'qcom-firmware-extract: %s\n' "$*" >&2; } + +# --------------------------------------------------------------------------- +# 1. What does this machine ask for, and what is already there? +# --------------------------------------------------------------------------- + +# Prints " " for every firmware-name entry in the +# device tree. A property can hold several NUL-separated strings. +dt_firmware_names() { + local prop node name + [[ -d $DT_ROOT ]] || return 0 + find "$DT_ROOT" -name firmware-name -type f -print0 2>/dev/null | sort -z | + while IFS= read -r -d '' prop; do + node=${prop%/firmware-name} + node=${node#"$DT_ROOT"/} + tr '\0' '\n' <"$prop" | while IFS= read -r name; do + [[ -n $name ]] && printf '%s %s\n' "$node" "$name" + done + done +} + +# The kernel searches every directory for plain firmware before trying zstd +# and xz. gzip is not an automatic firmware-loader fallback. +firmware_file() { + local name=$1 dir ext + for ext in '' .zst .xz; do + for dir in "$FW_ROOT/updates" "$FW_ROOT"; do + if [[ -f "$dir/$name$ext" ]]; then + printf '%s\n' "$dir/$name$ext" + return 0 + fi + done + done + return 1 +} + +declare -a missing_nodes=() missing_names=() +while read -r node name; do + firmware_file "$name" >/dev/null && continue + missing_nodes+=("$node") + missing_names+=("$name") +done < <(dt_firmware_names) + +if [[ $mode == list ]]; then + ((${#missing_names[@]})) && printf '%s\n' "${missing_names[@]}" + exit 0 +fi + +if [[ ! -d $DT_ROOT ]]; then + log "no device tree on this machine, nothing to do" + exit 0 +fi +if [[ $mode != install ]] && ((${#missing_names[@]} == 0)); then + log "every firmware file the device tree names is already installed" + exit 0 +fi + +# Root is needed to write under /usr/lib/firmware (and, below, to mount +# partitions); staging a -d driver store into a user directory needs neither. +if ((EUID != 0)) && [[ $mode == install && -z $ROOT ]]; then + warn "must run as root" + exit 1 +fi + +# --------------------------------------------------------------------------- +# 2. Where can they come from? +# --------------------------------------------------------------------------- + +declare -a sources=() # directories to search, best first +declare -a mounts=() # temporary mounts to undo +cleanup() { + local m + for m in "${mounts[@]-}"; do + [[ -n $m ]] || continue + umount "$m" 2>/dev/null || true + rmdir "$m" 2>/dev/null || true + done +} +trap cleanup EXIT + +# Mount Windows driver stores read-only, preferring internal disks. +scan_windows_partitions() { + local dev fstype tran mp bitlocker="" + if ((EUID != 0)); then + warn "must run as root to read the Windows partitions" + exit 1 + fi + local -a internal=() external=() + while read -r dev fstype tran; do + case $fstype in + ntfs) ;; + BitLocker) bitlocker+=" $dev"; continue ;; + *) continue ;; + esac + if [[ $tran == usb ]]; then external+=("$dev"); else internal+=("$dev"); fi + done < <(lsblk -rpno PATH,FSTYPE,TRAN 2>/dev/null | awk 'NF>=2') + + for dev in "${internal[@]}" "${external[@]}"; do + mp=$(mktemp -d /run/qcom-firmware-extract.XXXXXX) + if mount -t ntfs3 -o ro,nosuid,nodev,noexec "$dev" "$mp" 2>/dev/null; then + mounts+=("$mp") + if [[ -d "$mp/$DRIVERSTORE_PATH" ]]; then + log "Windows driver store found on $dev" + sources+=("$mp/$DRIVERSTORE_PATH") + fi + else + warn "could not mount $dev read-only (ntfs3); skipping it" + rmdir "$mp" 2>/dev/null || true + fi + done + [[ -n $bitlocker ]] && warn "BitLocker volume(s) skipped:$bitlocker; turn BitLocker off in Windows to let the firmware be read" + return 0 +} + +firmware_sha256() { + local file=$1 + case "$file" in + *.zst) zstd -qdc -- "$file" ;; + *.xz) xz -dc -- "$file" ;; + *) cat -- "$file" ;; + esac | sha256sum | cut -d' ' -f1 +} + +# Match ambiguous DTBs to an installed sibling firmware blob from the same node. +matching_companion_candidate() { + local node=$1 + shift + local -a candidates=("$@") + local dt_node dt_name installed installed_hash candidate sibling matched compatible + local -a matches=() + + for candidate in "${candidates[@]}"; do + matched=0 + compatible=1 + while read -r dt_node dt_name; do + [[ $dt_node == "$node" ]] || continue + installed=$(firmware_file "$dt_name") || continue + installed_hash=$(firmware_sha256 "$installed") || return 1 + sibling=$(find "${candidate%/*}" -maxdepth 1 -type f \ + -iname "$(basename "$dt_name")" -print -quit 2>/dev/null) + [[ -n $sibling ]] || continue + if [[ $(sha256sum "$sibling" | cut -d' ' -f1) == "$installed_hash" ]]; then + matched=1 + else + compatible=0 + break + fi + done < <(dt_firmware_names) + if ((matched && compatible)); then + matches+=("$candidate") + fi + done + + unique_candidate "${matches[@]}" +} + +# Duplicate copies are safe only when their contents agree. +unique_candidate() { + (($#)) || return 1 + local first=$1 candidate + shift + for candidate in "$@"; do + cmp -s "$first" "$candidate" || return 1 + done + printf '%s\n' "$first" +} + +# Require a companion match when several firmware variants exist. +find_in_sources() { + local base=$1 node=$2 candidate + local -a candidates=() + while IFS= read -r -d '' candidate; do + candidates+=("$candidate") + done < <(find "${sources[@]}" -type f -iname "$base" -print0 2>/dev/null) + + ((${#candidates[@]})) || return 1 + if unique_candidate "${candidates[@]}"; then + return 0 + fi + + if matching_companion_candidate "$node" "${candidates[@]}"; then + return 0 + fi + + warn "refusing ambiguous $base: no unique variant matches its installed companion firmware" + return 1 +} + +# --------------------------------------------------------------------------- +# 3. Stage: live session, before the disk is written +# --------------------------------------------------------------------------- + +if [[ $mode == stage ]]; then + if [[ -f "$stage_dir/manifest" ]]; then + log "already staged in $stage_dir" + exit 0 + fi + if [[ -n $driverstore ]]; then sources=("$driverstore"); else scan_windows_partitions; fi + mkdir -p "$stage_dir" + staged=0 + : >"$stage_dir/manifest.partial" + for i in "${!missing_names[@]}"; do + node=${missing_nodes[$i]} + name=${missing_names[$i]} + src="" + if ((${#sources[@]})); then + src=$(find_in_sources "$(basename "$name")" "$node") || true + fi + if [[ -z $src ]]; then + warn "not found in the driver store: $name" + continue + fi + install -Dm644 "$src" "$stage_dir/$name" + printf '%s %s %s\n' "$name" "$(sha256sum "$src" | cut -d' ' -f1)" "$src" >>"$stage_dir/manifest.partial" + staged=$((staged + 1)) + done + mv "$stage_dir/manifest.partial" "$stage_dir/manifest" + log "staged $staged of ${#missing_names[@]} firmware files in $stage_dir" + exit 0 +fi + +# --------------------------------------------------------------------------- +# 4. Install: from the stage, else from a Windows partition still on disk +# --------------------------------------------------------------------------- + +[[ -n $stage_dir ]] || stage_dir=$DEFAULT_STAGE_DIR +if [[ -n $driverstore ]]; then + sources=("$driverstore") +elif [[ -f "$stage_dir/manifest" ]]; then + log "using the firmware staged by the installer in $stage_dir" + sources=("$stage_dir") +elif ((${#missing_names[@]})); then + scan_windows_partitions +fi + +mkdir -p "$STATE_DIR" +touch "$MANIFEST" +installed=0 +for i in "${!missing_names[@]}"; do + node=${missing_nodes[$i]} + name=${missing_names[$i]} + src="" + if ((${#sources[@]})); then + src=$(find_in_sources "$(basename "$name")" "$node") || true + fi + if [[ -z $src ]]; then + warn "not found: $name" + continue + fi + install -Dm644 "$src" "$FW_ROOT/updates/$name" + sum=$(sha256sum "$src" | cut -d' ' -f1) + # Keep one line per firmware name. + grep -v "^$name " "$MANIFEST" >"$MANIFEST.new" || true + printf '%s %s %s %s\n' "$name" "$sum" "$src" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >>"$MANIFEST.new" + mv "$MANIFEST.new" "$MANIFEST" + log "installed $name" + installed=$((installed + 1)) +done + +# Include existing GPU zap shaders in the initramfs for the early DRM driver. +declare -a initramfs_files=() +while read -r node name; do + [[ ${node##*/} == zap-shader || ${node##*/} == zap-shader@* ]] || continue + if file=$(firmware_file "$name"); then + initramfs_files+=("${file#"$ROOT"}") + fi +done < <(dt_firmware_names) +initramfs_changed=0 +if ((${#initramfs_files[@]})); then + initramfs_config=$( + echo "# Generated by qcom-firmware-extract for early DRM firmware loading." + printf 'FILES+=(%s)\n' "$(printf '%q ' "${initramfs_files[@]}" | sed 's/ $//')" + ) + if [[ ! -f $INITCPIO_CONF || $(<"$INITCPIO_CONF") != "$initramfs_config" ]]; then + mkdir -p "$(dirname "$INITCPIO_CONF")" + printf '%s\n' "$initramfs_config" >"$INITCPIO_CONF" + initramfs_changed=1 + fi +elif [[ -f $INITCPIO_CONF ]]; then + rm -f "$INITCPIO_CONF" + initramfs_changed=1 +fi + +still_missing=$(( ${#missing_names[@]} - installed )) +if ((installed == 0)); then + if ((still_missing > 0 && ${#sources[@]} == 0)); then + warn "no Windows installation found to copy from; run again with -d when one is available" + fi + log "nothing installed; $still_missing firmware file(s) still missing" + ((initramfs_changed)) || exit 0 +else + log "installed $installed firmware file(s), $still_missing still missing" +fi + +if [[ $rebuild == yes ]]; then + if command -v limine-update >/dev/null 2>&1; then + limine-update + else + mkinitcpio -P + fi + log "reboot to load the added firmware" +fi diff --git a/pkgbuilds/qcom-firmware-extract/test.sh b/pkgbuilds/qcom-firmware-extract/test.sh new file mode 100644 index 000000000..6d26721c5 --- /dev/null +++ b/pkgbuilds/qcom-firmware-extract/test.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +set -euo pipefail + +package_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +extractor="$package_dir/qcom-firmware-extract" +scratch=$(mktemp -d) +trap 'rm -rf "$scratch"' EXIT + +# macOS' install(1) gives -D a different meaning. Prefer GNU coreutils when +# this focused test runs on a contributor's Mac; Arch uses GNU install already. +test_bin="$scratch/bin" +mkdir -p "$test_bin" +if command -v ginstall >/dev/null 2>&1; then + ln -s "$(command -v ginstall)" "$test_bin/install" +fi + +dt_root="$scratch/device-tree" +firmware_root="$scratch/firmware" +driver_store="$scratch/DriverStore" +stage="$scratch/stage" +node="$dt_root/remoteproc@0" +firmware_path="qcom/x1e80100/LENOVO/83ED" + +mkdir -p "$node" "$firmware_root/$firmware_path" \ + "$driver_store/wrong" "$driver_store/matching" +printf '%s\0%s\0' \ + "$firmware_path/qccdsp8380.mbn" \ + "$firmware_path/cdsp_dtbs.elf" >"$node/firmware-name" + +# Make the incompatible Windows firmware newer than the matching variant. +printf 'installed-cdsp' >"$firmware_root/$firmware_path/qccdsp8380.mbn" +printf 'other-cdsp' >"$driver_store/wrong/qccdsp8380.mbn" +printf 'wrong-dtb' >"$driver_store/wrong/cdsp_dtbs.elf" +printf 'installed-cdsp' >"$driver_store/matching/qccdsp8380.mbn" +printf 'matching-dtb' >"$driver_store/matching/cdsp_dtbs.elf" +touch -t 203001010000 "$driver_store/wrong/cdsp_dtbs.elf" +touch -t 202001010000 "$driver_store/matching/cdsp_dtbs.elf" + +QCOM_FW_DT_ROOT="$dt_root" \ + QCOM_FW_FIRMWARE_ROOT="$firmware_root" \ + PATH="$test_bin:$PATH" \ + bash "$extractor" --stage "$stage" -d "$driver_store" + +[[ $(<"$stage/$firmware_path/cdsp_dtbs.elf") == matching-dtb ]] || { + echo "not ok - extractor did not select the DTB matching the installed DSP image" >&2 + exit 1 +} + +echo "ok - extractor selects an ambiguous DTB by its companion firmware hash" + +run_extractor() { + QCOM_FW_DT_ROOT="$dt_root" \ + QCOM_FW_FIRMWARE_ROOT="$firmware_root" \ + QCOM_FW_ROOT="$scratch/root" \ + PATH="$test_bin:$PATH" \ + bash "$extractor" "$@" +} + +# A different remote processor must not supply the matching companion. +mkdir -p "$dt_root/remoteproc@1" "$driver_store/unrelated" +printf '%s\0' "$firmware_path/qcadsp8380.mbn" >"$dt_root/remoteproc@1/firmware-name" +printf 'installed-adsp' >"$firmware_root/$firmware_path/qcadsp8380.mbn" +printf 'installed-adsp' >"$driver_store/unrelated/qcadsp8380.mbn" +printf 'unrelated-dtb' >"$driver_store/unrelated/cdsp_dtbs.elf" +printf 'missing-cdsp' >"$firmware_root/$firmware_path/qccdsp8380.mbn" +run_extractor --stage "$scratch/unmatched" -d "$driver_store" +[[ ! -e $scratch/unmatched/$firmware_path/cdsp_dtbs.elf ]] +echo "ok - companion matching stays within the exact device-tree node" + +# Firmware not found in Windows must not stop other files from being staged. +printf '%s\0%s\0%s\0' "$firmware_path/not-in-windows.mbn" \ + "$firmware_path/cdsp_dtbs.elf" "$firmware_path/qccdsp8380.mbn" >"$node/firmware-name" +printf 'installed-cdsp' >"$firmware_root/$firmware_path/qccdsp8380.mbn" +run_extractor --stage "$scratch/partial" -d "$driver_store" +[[ $(<"$scratch/partial/$firmware_path/cdsp_dtbs.elf") == matching-dtb ]] +run_extractor --install --no-rebuild --stage-dir "$scratch/partial" +[[ $(<"$firmware_root/updates/$firmware_path/cdsp_dtbs.elf") == matching-dtb ]] +echo "ok - missing firmware does not abort staging or installation" + +# With no companion, only byte-identical duplicates are safe to select. +printf '%s\0' "$firmware_path/duplicate.mbn" >"$node/firmware-name" +printf 'one' >"$driver_store/wrong/duplicate.mbn" +printf 'two' >"$driver_store/matching/duplicate.mbn" +run_extractor --stage "$scratch/ambiguous" -d "$driver_store" +[[ ! -e $scratch/ambiguous/$firmware_path/duplicate.mbn ]] +printf 'one' >"$driver_store/matching/duplicate.mbn" +run_extractor --stage "$scratch/identical" -d "$driver_store" +[[ $(<"$scratch/identical/$firmware_path/duplicate.mbn") == one ]] +echo "ok - differing variants require a unique match" + +# A packaged zap shader still needs an initramfs entry when nothing is missing. +printf '%s\0' "$firmware_path/qccdsp8380.mbn" >"$node/firmware-name" +mkdir -p "$dt_root/gpu@0/zap-shader" +printf '%s\0' "$firmware_path/qcdxkmsuc8380.mbn" >"$dt_root/gpu@0/zap-shader/firmware-name" +zap="$firmware_path/qcdxkmsuc8380.mbn" +config="$scratch/root/etc/mkinitcpio.conf.d/qcom-firmware.conf" +for compression in zstd xz; do + if [[ $compression == zstd ]]; then suffix=zst; else suffix=xz; fi + printf 'zap' | "$compression" -c >"$firmware_root/$zap.$suffix" + run_extractor --install --no-rebuild -d "$driver_store" + [[ -z $(run_extractor --list-missing) ]] + grep -Fq "$zap.$suffix" "$config" + rm "$firmware_root/$zap.$suffix" +done +echo "ok - zstd and xz firmware are loadable and included in the initramfs" + +printf 'zap' | gzip >"$firmware_root/$zap.gz" +[[ $(run_extractor --list-missing) == "$zap" ]] +run_extractor --install --no-rebuild -d "$driver_store" +[[ ! -e $config ]] +printf 'zap' >"$driver_store/matching/qcdxkmsuc8380.mbn" +run_extractor --install --no-rebuild -d "$driver_store" +cmp "$driver_store/matching/qcdxkmsuc8380.mbn" "$firmware_root/updates/$zap" +grep -Fq "updates/$zap" "$config" +echo "ok - gzip does not hide missing firmware or prevent extraction" + +# Compressed updates must not take precedence over a plain packaged file. +mv "$firmware_root/updates/$zap" "$firmware_root/$zap" +printf 'update' | zstd -c >"$firmware_root/updates/$zap.zst" +run_extractor --install --no-rebuild -d "$driver_store" +grep -Fq "$firmware_root/$zap" "$config" +if grep -Fq "updates/$zap" "$config"; then + echo "not ok - firmware selection differs from the kernel search order" >&2 + exit 1 +fi +printf 'update' >"$firmware_root/updates/$zap" +run_extractor --install --no-rebuild -d "$driver_store" +grep -Fq "updates/$zap" "$config" +echo "ok - plain firmware is preferred before compressed directory overrides" + +limine-update() { printf 'rebuild\n' >>"$QCOM_FW_ROOT/rebuilds"; } +export -f limine-update +run_extractor --install -d "$driver_store" +[[ ! -e $scratch/root/rebuilds ]] +rm "$scratch/root/etc/mkinitcpio.conf.d/qcom-firmware.conf" +run_extractor --install -d "$driver_store" +[[ $(<"$scratch/root/rebuilds") == rebuild ]] +echo "ok - configuration-only changes rebuild the initramfs once"