diff --git a/bin/omarchy-hw-qualcomm-soc b/bin/omarchy-hw-qualcomm-soc new file mode 100755 index 00000000000..7bbe6c8b24b --- /dev/null +++ b/bin/omarchy-hw-qualcomm-soc @@ -0,0 +1,6 @@ +#!/bin/bash + +# omarchy:summary=Detect a Qualcomm SoC from the boot device tree. + +[[ -r /sys/firmware/devicetree/base/compatible ]] || exit 1 +tr '\0' '\n' /dev/null diff --git a/bin/omarchy-refresh-pacman b/bin/omarchy-refresh-pacman index 299d6c20d20..1f5921c7c6b 100755 --- a/bin/omarchy-refresh-pacman +++ b/bin/omarchy-refresh-pacman @@ -1,23 +1,34 @@ #!/bin/bash # omarchy:summary=Overwrite the package configuration for /etc/pacman with the Omarchy default of using its dedicated mirrors and repositories, then update all packages. +# omarchy:args=[stable|rc|edge] # omarchy:requires-sudo=true -sudo cp -f /etc/pacman.conf /etc/pacman.conf.bak -sudo cp -f /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak +set -e channel="${1:-stable}" +pacman_config=${OMARCHY_PACMAN_CONFIG:-/etc/pacman.conf} +mirrorlist=${OMARCHY_MIRRORLIST:-/etc/pacman.d/mirrorlist} -if [[ $channel != "stable" && $channel != "rc" && $channel != "edge" ]]; then - echo "Error: Invalid channel '$channel'. Must be one of: stable, rc, edge" +# Do not replace a working ARM repository setup with an unpublished channel. +if [[ $(uname -m) == "aarch64" && ( $channel == "stable" || $channel == "rc" ) ]]; then + echo "Omarchy $channel packages are not published for aarch64 yet. No repository configuration changed." >&2 + echo "Use 'omarchy refresh pacman edge' only if you intend to use edge packages and upgrade the system." >&2 exit 1 fi +staged=$(mktemp -d) +trap 'rm -rf -- "$staged"' EXIT +source "$OMARCHY_PATH/install/helpers/pacman.sh" +pacman_write_repository_config "$channel" "$staged/pacman.conf" "$staged/mirrorlist" + echo "Setting channel to $channel" echo -sudo cp -f "$OMARCHY_PATH/default/pacman/pacman-$channel.conf" /etc/pacman.conf -sudo cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" /etc/pacman.d/mirrorlist +sudo cp -f "$pacman_config" "$pacman_config.bak" +sudo cp -f "$mirrorlist" "$mirrorlist.bak" +sudo cp -f "$staged/pacman.conf" "$pacman_config" +sudo cp -f "$staged/mirrorlist" "$mirrorlist" # Allow user customization of /etc/pacman.conf before the upgrade runs omarchy-hook pre-refresh-pacman diff --git a/default/pacman/mirrorlist-aarch64 b/default/pacman/mirrorlist-aarch64 new file mode 100644 index 00000000000..c7185ed8f65 --- /dev/null +++ b/default/pacman/mirrorlist-aarch64 @@ -0,0 +1,2 @@ +# Arch Linux ARM publishes this endpoint with a $arch/$repo layout. +Server = http://mirror.archlinuxarm.org/$arch/$repo diff --git a/default/pacman/pacman-aarch64.conf b/default/pacman/pacman-aarch64.conf new file mode 100644 index 00000000000..b11e10a47c7 --- /dev/null +++ b/default/pacman/pacman-aarch64.conf @@ -0,0 +1,30 @@ +# See the pacman.conf(5) manpage for option and repository directives +# Arch Linux ARM provides the aarch64 repositories used below. + +[options] +Color +ILoveCandy +VerbosePkgLists +HoldPkg = pacman glibc +Architecture = auto +CheckSpace +ParallelDownloads = 5 +DownloadUser = alpm + +# By default, pacman accepts packages signed by keys that its local keyring +# trusts (see pacman-key and its man page), as well as unsigned packages. +SigLevel = Required DatabaseOptional +LocalFileSigLevel = Optional + +# pacman searches repositories in the order defined here. +[core] +Include = /etc/pacman.d/mirrorlist + +[extra] +Include = /etc/pacman.d/mirrorlist + +[alarm] +Include = /etc/pacman.d/mirrorlist + +[aur] +Include = /etc/pacman.d/mirrorlist diff --git a/etc/mkinitcpio.conf.d/thunderbolt_module.conf b/etc/mkinitcpio.conf.d/thunderbolt_module.conf index 9518f8b1201..eb0565bef59 100644 --- a/etc/mkinitcpio.conf.d/thunderbolt_module.conf +++ b/etc/mkinitcpio.conf.d/thunderbolt_module.conf @@ -1 +1,4 @@ -MODULES+=(thunderbolt) +# linux-aarch64 does not provide the thunderbolt module. +if [[ $(uname -m) == x86_64 ]]; then + MODULES+=(thunderbolt) +fi diff --git a/install/hardware/all.sh b/install/hardware/all.sh index 7b806ce8ea4..19b26b7eb78 100644 --- a/install/hardware/all.sh +++ b/install/hardware/all.sh @@ -40,6 +40,10 @@ run_logged "$OMARCHY_INSTALL/hardware/apple/fix-suspend-nvme.sh" run_logged "$OMARCHY_INSTALL/hardware/apple/fix-t2.sh" run_logged "$OMARCHY_INSTALL/hardware/apple/fix-brcmfmac-supplicant.sh" +run_logged "$OMARCHY_INSTALL/hardware/qualcomm/dtb-uki.sh" +run_logged "$OMARCHY_INSTALL/hardware/qualcomm/kernel-params.sh" +run_logged "$OMARCHY_INSTALL/hardware/qualcomm/firmware.sh" + run_logged "$OMARCHY_INSTALL/hardware/lenovo/fix-yoga-pro7-bass-speakers.sh" run_logged "$OMARCHY_INSTALL/hardware/fix-bcm43xx.sh" diff --git a/install/hardware/qualcomm/dtb-uki.sh b/install/hardware/qualcomm/dtb-uki.sh new file mode 100644 index 00000000000..a7728f8260b --- /dev/null +++ b/install/hardware/qualcomm/dtb-uki.sh @@ -0,0 +1,50 @@ +# Embed Qualcomm device trees so systemd-stub can select one from SMBIOS data. +# DeviceTreeAuto requires literal paths, so hardware setup refreshes the list. + +if omarchy-hw-qualcomm-soc; then + omarchy-pkg-add systemd-ukify + + dtb_dir=${OMARCHY_QUALCOMM_DTB_DIR:-/boot/dtbs/qcom} + uki_config=${OMARCHY_QUALCOMM_UKI_CONFIG:-/etc/kernel/uki.conf} + dtbs=() + for dtb in "$dtb_dir"/x1*.dtb "$dtb_dir"/hamoa*.dtb \ + "$dtb_dir"/glymur*.dtb "$dtb_dir"/sc8280xp*.dtb; do + if [[ -f $dtb && $dtb != *-el2.dtb ]]; then + dtbs+=("$dtb") + fi + done + + if ((${#dtbs[@]} == 0)); then + echo "No Windows-on-ARM device trees under $dtb_dir; the UKI gets no .dtbauto sections" >&2 + else + managed_begin="# BEGIN OMARCHY QUALCOMM DEVICE TREES" + managed_end="# END OMARCHY QUALCOMM DEVICE TREES" + + mkdir -p "$(dirname "$uki_config")" + uki_tmp=$(mktemp "${uki_config}.XXXXXX") + + if [[ -f $uki_config ]]; then + cp -p "$uki_config" "$uki_tmp" + awk -v begin="$managed_begin" -v end="$managed_end" ' + $0 == begin { managed = 1; next } + managed && $0 == end { managed = 0; next } + !managed { print } + ' "$uki_config" >"$uki_tmp" + else + chmod 0644 "$uki_tmp" + fi + + { + echo "$managed_begin" + echo "# Generated by Omarchy for Qualcomm Snapdragon devices." + echo "# systemd-stub selects a device tree from SMBIOS data." + echo "# Rerun 'omarchy apply hardware' after a kernel update adds new boards." + echo "[UKI]" + echo "DeviceTreeAuto=${dtbs[*]}" + echo "$managed_end" + } >>"$uki_tmp" + + mv -f "$uki_tmp" "$uki_config" + echo "Listed ${#dtbs[@]} device trees in $uki_config" + fi +fi diff --git a/install/hardware/qualcomm/firmware.sh b/install/hardware/qualcomm/firmware.sh new file mode 100644 index 00000000000..9c2b6cb6249 --- /dev/null +++ b/install/hardware/qualcomm/firmware.sh @@ -0,0 +1,29 @@ +# Install packaged Qualcomm firmware and vendor-signed blobs extracted from Windows. +# Keep the DSP driver disabled when starting it could disconnect the root disk. + +if omarchy-hw-qualcomm-soc; then + omarchy-pkg-add linux-firmware-qcom qcom-firmware-extract + + qcom-firmware-extract --install --no-rebuild || true + + modprobe_dir=${OMARCHY_QUALCOMM_MODPROBE_DIR:-/etc/modprobe.d} + root_source=$(findmnt -no SOURCE --nofsroot / 2>/dev/null || true) + + mkdir -p "$modprobe_dir" + if [[ -n $root_source ]] && lsblk -sno TRAN "$root_source" 2>/dev/null | grep -q '^usb$'; then + # Starting the DSPs resets USB-C, so keep them off when root is on USB. + cat >"$modprobe_dir/qualcomm-adsp-nofw.conf" <<'CONF' +# Generated by Omarchy for Qualcomm Snapdragon devices. +# Keep the DSP driver off while the root disk is attached over USB. +blacklist qcom_q6v5_pas +CONF + elif ! missing_firmware=$(qcom-firmware-extract --list-missing) || grep -q 'adsp' <<<"$missing_firmware"; then + cat >"$modprobe_dir/qualcomm-adsp-nofw.conf" <<'CONF' +# Generated by Omarchy for Qualcomm Snapdragon devices. +# Keep the DSP driver off until its signed firmware can be verified. +blacklist qcom_q6v5_pas +CONF + else + rm -f "$modprobe_dir/qualcomm-adsp-nofw.conf" + fi +fi diff --git a/install/hardware/qualcomm/kernel-params.sh b/install/hardware/qualcomm/kernel-params.sh new file mode 100644 index 00000000000..4536395f557 --- /dev/null +++ b/install/hardware/qualcomm/kernel-params.sh @@ -0,0 +1,13 @@ +# Add firmware workarounds required by current Snapdragon laptops. + +DROP_IN="/etc/limine-entry-tool.d/qualcomm-snapdragon.conf" + +if omarchy-hw-qualcomm-soc; then + mkdir -p /etc/limine-entry-tool.d + cat >"$DROP_IN" <<'CONF' +# Generated by Omarchy for Qualcomm Snapdragon devices. +# Keep display and I/O resources enabled until their drivers load. +# Disable pointer authentication and waiting for an unavailable TPM. +KERNEL_CMDLINE[default]+=" clk_ignore_unused pd_ignore_unused arm64.nopauth systemd.tpm2_wait=0" +CONF +fi diff --git a/install/hardware/vulkan.sh b/install/hardware/vulkan.sh index a2447d46ffa..e13de205b8c 100644 --- a/install/hardware/vulkan.sh +++ b/install/hardware/vulkan.sh @@ -15,6 +15,11 @@ for vendor in "${!VULKAN_DRIVERS[@]}"; do fi done +# Adreno GPUs are platform devices, so lspci never sees them. +if omarchy-hw-qualcomm-soc; then + PACKAGES+=(vulkan-freedreno) +fi + if (( ${#PACKAGES[@]} > 0 )); then omarchy-pkg-add "${PACKAGES[@]}" fi diff --git a/install/helpers/pacman.sh b/install/helpers/pacman.sh new file mode 100644 index 00000000000..7198695a311 --- /dev/null +++ b/install/helpers/pacman.sh @@ -0,0 +1,25 @@ +# Write repository files for both offline-install finalization and refreshes. +# Callers choose the destination so refreshes can stage files before using sudo. +pacman_write_repository_config() { + local channel=$1 config=$2 mirrorlist=$3 + local defaults="$OMARCHY_PATH/default/pacman" + + if [[ $channel != "stable" && $channel != "rc" && $channel != "edge" ]]; then + echo "Error: Invalid channel '$channel'. Must be one of: stable, rc, edge" >&2 + return 1 + fi + + if [[ $(uname -m) == "aarch64" ]]; then + cp -f "$defaults/pacman-aarch64.conf" "$config" || return 1 + cp -f "$defaults/mirrorlist-aarch64" "$mirrorlist" || return 1 + + # Only edge publishes an aarch64 Omarchy repository. Offline stable/RC + # installs retain ALARM without silently opting into an experimental channel. + if [[ $channel == "edge" ]]; then + printf "\n[omarchy]\nServer = https://pkgs.omarchy.org/edge/\$arch\n" >>"$config" || return 1 + fi + else + cp -f "$defaults/pacman-$channel.conf" "$config" || return 1 + cp -f "$defaults/mirrorlist-$channel" "$mirrorlist" || return 1 + fi +} diff --git a/install/omarchy-other.packages b/install/omarchy-other.packages index e5d56d566fe..1a7be9fec7b 100644 --- a/install/omarchy-other.packages +++ b/install/omarchy-other.packages @@ -75,3 +75,6 @@ t2fanrd # Framework 16 qmk-hid + +# Qualcomm Snapdragon support packages +qcom-firmware-extract diff --git a/install/post-install/pacman.sh b/install/post-install/pacman.sh index da84975b3ee..97a8a1d012d 100644 --- a/install/post-install/pacman.sh +++ b/install/post-install/pacman.sh @@ -1,7 +1,20 @@ -# Configure pacman after package installation completes. Offline target package -# installs use the live ISO's offline pacman.conf until this final restore. -cp -f "$OMARCHY_PATH/default/pacman/pacman-${OMARCHY_MIRROR:-stable}.conf" /etc/pacman.conf -cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-${OMARCHY_MIRROR:-stable}" /etc/pacman.d/mirrorlist +# Replace the installer's offline pacman configuration with online repositories. +pacman_config=${OMARCHY_PACMAN_CONFIG:-/etc/pacman.conf} +mirrorlist=${OMARCHY_MIRRORLIST:-/etc/pacman.d/mirrorlist} +source "$OMARCHY_PATH/install/helpers/pacman.sh" + +if [[ $(uname -m) == "aarch64" ]]; then + # Install the keyring before replacing the offline package source. + omarchy-pkg-add archlinuxarm-keyring +fi + +pacman_write_repository_config "${OMARCHY_MIRROR:-stable}" "$pacman_config" "$mirrorlist" || return 1 + +if [[ $(uname -m) == "aarch64" ]]; then + # Trust every installed keyring before the first signed sync. + pacman-key --init + pacman-key --populate +fi # Wait for CUPS to own the file, the way omarchy-settings does, so pacman does # not turn the override into a .pacnew during ISO package installation. diff --git a/install/user/mise-work.sh b/install/user/mise-work.sh index f77d5561e0e..17ea1e25df9 100644 --- a/install/user/mise-work.sh +++ b/install/user/mise-work.sh @@ -19,7 +19,12 @@ case ${OMARCHY_SETUP_CONTEXT:-runtime} in esac if [[ -n $NODE_PACKAGE_DIR ]]; then - NODE_TARBALL=$(find "$NODE_PACKAGE_DIR" -name "node-v*-linux-x64.tar.gz" -type f 2>/dev/null | head -n1) + # Node names its builds x64/arm64, not by uname + case "$(uname -m)" in + aarch64) _NODE_ARCH=arm64 ;; + *) _NODE_ARCH=x64 ;; + esac + NODE_TARBALL=$(find "$NODE_PACKAGE_DIR" -name "node-v*-linux-${_NODE_ARCH}.tar.gz" -type f 2>/dev/null | head -n1) if [[ -z $NODE_TARBALL ]]; then if [[ ${OMARCHY_SETUP_CONTEXT:-} == "provision-owner" ]]; then # A factory snapshot predating the bundled tarball may not have it staged. @@ -31,7 +36,7 @@ if [[ -n $NODE_PACKAGE_DIR ]]; then exit 1 fi else - NODE_VERSION=$(basename "$NODE_TARBALL" | sed 's/node-v\(.*\)-linux-x64.tar.gz/\1/') + NODE_VERSION=$(basename "$NODE_TARBALL" | sed "s/node-v\\(.*\\)-linux-${_NODE_ARCH}.tar.gz/\\1/") NODE_INSTALL_DIR="$HOME/.local/share/mise/installs/node/$NODE_VERSION" mkdir -p "$NODE_INSTALL_DIR" diff --git a/test/shell.d/pacman-aarch64-test.sh b/test/shell.d/pacman-aarch64-test.sh new file mode 100644 index 00000000000..c878051ad28 --- /dev/null +++ b/test/shell.d/pacman-aarch64-test.sh @@ -0,0 +1,132 @@ +#!/bin/bash + +# Each invocation has an isolated environment; exported stubs run in the child bash. +# shellcheck disable=SC2030,SC2031,SC2329 + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +scratch=$(mktemp -d) +trap 'rm -rf "$scratch"' EXIT +mkdir -p "$scratch/source/default" "$scratch/source/install/helpers" "$scratch/install/hardware" +cp -r "$ROOT/default/pacman" "$scratch/source/default/" +cp "$ROOT/install/helpers/pacman.sh" "$scratch/source/install/helpers/" +printf ':\n' >"$scratch/install/hardware/pacman.sh" + +run_setup() ( + architecture=$1 + export OMARCHY_MIRROR=$2 + export OMARCHY_PATH="$scratch/source" OMARCHY_INSTALL="$scratch/install" + export OMARCHY_PACMAN_CONFIG="$scratch/pacman.conf" OMARCHY_MIRRORLIST="$scratch/mirrorlist" + uname() { printf '%s\n' "$architecture"; } + omarchy-pkg-add() { printf 'add %s\n' "$*" >>"$scratch/keys"; } + pacman-key() { printf 'key %s\n' "$*" >>"$scratch/keys"; } + source "$ROOT/install/post-install/pacman.sh" +) + +run_setup aarch64 edge +grep -Fxq "Server = https://pkgs.omarchy.org/edge/\$arch" "$scratch/pacman.conf" || + fail "ARM edge installations retain the published Omarchy repository" +grep -Fxq '[alarm]' "$scratch/pacman.conf" || fail "ARM installations retain ALARM repositories" +if grep -Fxq '[multilib]' "$scratch/pacman.conf"; then + fail "ARM installations do not inherit x86 multilib" +fi +grep -Fxq 'add archlinuxarm-keyring' "$scratch/keys" || fail "ALARM keyring is installed" +grep -Fxq 'key --populate' "$scratch/keys" || fail "installed keyrings are trusted" + +for mirror in stable rc; do + run_setup aarch64 "$mirror" + if grep -Fxq '[omarchy]' "$scratch/pacman.conf"; then + fail "ARM $mirror does not select an unpublished repository or switch to edge" + fi +done + +run_setup x86_64 stable +cmp "$ROOT/default/pacman/pacman-stable.conf" "$scratch/pacman.conf" || + fail "x86 repository configuration remains unchanged" + +pass "ARM edge retains Omarchy packages without switching other channels" + +run_refresh() ( + export OMARCHY_TEST_ARCH=$1 + shift + export OMARCHY_PATH="$scratch/source" + export OMARCHY_PACMAN_CONFIG="$scratch/pacman.conf" OMARCHY_MIRRORLIST="$scratch/mirrorlist" + export OMARCHY_TEST_LOG="$scratch/refresh.log" + : >"$OMARCHY_TEST_LOG" + + uname() { printf '%s\n' "$OMARCHY_TEST_ARCH"; } + sudo() { + printf '%s\n' "$*" >>"$OMARCHY_TEST_LOG" + if [[ $1 == "cp" ]]; then + [[ ${OMARCHY_TEST_COPY_FAIL:-0} != "1" ]] || return 1 + command "$@" + elif [[ $* == "env OMARCHY_UPDATE_PACMAN=1 pacman -Syyuu --noconfirm" ]]; then + # Deliberately never invoke pacman from this test. + grep -Fxq 'hook pre-refresh-pacman' "$OMARCHY_TEST_LOG" || return 1 + else + return 1 + fi + } + omarchy-hook() { printf 'hook %s\n' "$*" >>"$OMARCHY_TEST_LOG"; } + export -f uname sudo omarchy-hook + bash "$ROOT/bin/omarchy-refresh-pacman" "$@" +) + +run_setup aarch64 edge +cp "$scratch/pacman.conf" "$scratch/expected-arm.conf" +cp "$scratch/mirrorlist" "$scratch/expected-arm-mirrorlist" +printf 'original config\n' >"$scratch/pacman.conf" +printf 'original mirrors\n' >"$scratch/mirrorlist" +run_refresh aarch64 edge +cmp "$scratch/expected-arm.conf" "$scratch/pacman.conf" || fail "ARM refresh matches installer repositories" +cmp "$scratch/expected-arm-mirrorlist" "$scratch/mirrorlist" || fail "ARM refresh preserves ALARM mirror layout" +grep -Fxq 'original config' "$scratch/pacman.conf.bak" || fail "refresh backs up the previous configuration" +grep -Fxq 'original mirrors' "$scratch/mirrorlist.bak" || fail "refresh backs up the previous mirrors" +grep -Fxq 'env OMARCHY_UPDATE_PACMAN=1 pacman -Syyuu --noconfirm' "$scratch/refresh.log" || + fail "refresh retains the guarded upgrade after its customization hook" +run_refresh aarch64 edge +[[ $(grep -Fxc '[omarchy]' "$scratch/pacman.conf") == "1" ]] || fail "repeated refresh does not duplicate repositories" +pass "ARM refresh shares installer selection, backs up files and remains idempotent" + +for channel in stable rc invalid ''; do + args=() + [[ -z $channel ]] || args+=("$channel") + if run_refresh aarch64 "${args[@]}" >"$scratch/error.out" 2>&1; then + fail "ARM refresh rejects unpublished or invalid channels" + fi + [[ ! -s $scratch/refresh.log ]] || fail "rejected refresh does not invoke sudo, hooks or upgrades" + cmp "$scratch/expected-arm.conf" "$scratch/pacman.conf" || fail "rejected refresh keeps the current repository" + cmp "$scratch/expected-arm-mirrorlist" "$scratch/mirrorlist" || fail "rejected refresh keeps the current mirrors" + if [[ $channel != "invalid" ]]; then + grep -Fq 'No repository configuration changed' "$scratch/error.out" || fail "unpublished channel has an actionable error" + fi +done +pass "unpublished ARM channels never remove a working repository or silently select edge" + +for channel in stable rc edge; do + run_refresh x86_64 "$channel" + cmp "$ROOT/default/pacman/pacman-$channel.conf" "$scratch/pacman.conf" || fail "x86 $channel configuration is unchanged" + cmp "$ROOT/default/pacman/mirrorlist-$channel" "$scratch/mirrorlist" || fail "x86 $channel mirrors are unchanged" +done +pass "all x86 channel templates are preserved" + +cp "$scratch/pacman.conf" "$scratch/before-error.conf" +cp "$scratch/mirrorlist" "$scratch/before-error-mirrorlist" +mv "$scratch/source/default/pacman/mirrorlist-aarch64" "$scratch/source/default/pacman/mirrorlist-aarch64.saved" +if run_refresh aarch64 edge >"$scratch/error.out" 2>&1; then + fail "refresh stops when a template cannot be staged" +fi +[[ ! -s $scratch/refresh.log ]] || fail "missing templates never reach privileged writes or an upgrade" +cmp "$scratch/before-error.conf" "$scratch/pacman.conf" || fail "staging failure preserves configuration" +cmp "$scratch/before-error-mirrorlist" "$scratch/mirrorlist" || fail "staging failure preserves mirrors" +mv "$scratch/source/default/pacman/mirrorlist-aarch64.saved" "$scratch/source/default/pacman/mirrorlist-aarch64" + +if OMARCHY_TEST_COPY_FAIL=1 run_refresh aarch64 edge >"$scratch/error.out" 2>&1; then + fail "refresh stops when a privileged copy fails" +fi +if grep -Eq 'hook|pacman -S' "$scratch/refresh.log"; then + fail "failed privileged copies never reach hooks or upgrades" +fi +pass "failed staging or privileged copies stop before a system upgrade" diff --git a/test/shell.d/snapdragon-hardware-test.sh b/test/shell.d/snapdragon-hardware-test.sh new file mode 100644 index 00000000000..7b294448a89 --- /dev/null +++ b/test/shell.d/snapdragon-hardware-test.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +firmware_setup="$ROOT/install/hardware/qualcomm/firmware.sh" +dtb_setup="$ROOT/install/hardware/qualcomm/dtb-uki.sh" +scratch=$(mktemp -d) +trap 'rm -rf "$scratch"' EXIT + +for script in "$firmware_setup" "$dtb_setup"; do + bash -n "$script" || fail "Snapdragon hardware scripts have valid syntax" +done + +( + omarchy-hw-qualcomm-soc() { return 0; } + omarchy-pkg-add() { :; } + qcom-firmware-extract() { + if [[ $1 == "--install" ]]; then + return 1 + else + return 0 + fi + } + findmnt() { + [[ $* == "-no SOURCE --nofsroot /" ]] || fail "Snapdragon firmware setup strips the Btrfs subvolume suffix" + printf '/dev/mapper/root\n' + } + lsblk() { + [[ ${!#} == "/dev/mapper/root" ]] || fail "Snapdragon firmware setup passes a resolvable root device to lsblk" + printf 'usb\n' + } + + OMARCHY_QUALCOMM_MODPROBE_DIR="$scratch/modprobe.d" + source "$firmware_setup" +) + +[[ -f $scratch/modprobe.d/qualcomm-adsp-nofw.conf ]] || + fail "Snapdragon firmware setup protects a USB-backed root disk" + +run_internal_firmware_setup() ( + omarchy-hw-qualcomm-soc() { return 0; } + omarchy-pkg-add() { :; } + findmnt() { printf '/dev/mapper/root\n'; } + lsblk() { printf 'nvme\n'; } + inspection_status=$1 + qcom-firmware-extract() { + [[ $1 == "--list-missing" ]] || return 0 + return "$inspection_status" + } + OMARCHY_QUALCOMM_MODPROBE_DIR="$scratch/modprobe.d" + source "$firmware_setup" +) + +run_internal_firmware_setup 1 +[[ -f $scratch/modprobe.d/qualcomm-adsp-nofw.conf ]] || + fail "Snapdragon setup keeps DSPs disabled when firmware inspection fails" +run_internal_firmware_setup 0 +[[ ! -f $scratch/modprobe.d/qualcomm-adsp-nofw.conf ]] || + fail "Snapdragon setup enables DSPs after firmware is verified on an internal root" + +mkdir -p "$scratch/dtbs" +: >"$scratch/dtbs/x1e80100-test.dtb" +: >"$scratch/dtbs/x1e80100-test-el2.dtb" +cat >"$scratch/uki.conf" <<'CONF' +[UKI] +SecureBootPrivateKey=/secure/db.key +PCRPrivateKey=/secure/pcr.key +CONF + +run_dtb_setup() ( + omarchy-hw-qualcomm-soc() { return 0; } + omarchy-pkg-add() { :; } + + OMARCHY_QUALCOMM_DTB_DIR="$scratch/dtbs" + OMARCHY_QUALCOMM_UKI_CONFIG="$scratch/uki.conf" + source "$dtb_setup" +) + +run_dtb_setup +first_uki_config=$(<"$scratch/uki.conf") +run_dtb_setup +[[ $(<"$scratch/uki.conf") == "$first_uki_config" ]] || + fail "Snapdragon DTB setup is idempotent" + +grep -Fq 'SecureBootPrivateKey=/secure/db.key' "$scratch/uki.conf" || + fail "Snapdragon DTB setup preserves Secure Boot settings" +grep -Fq 'PCRPrivateKey=/secure/pcr.key' "$scratch/uki.conf" || + fail "Snapdragon DTB setup preserves PCR settings" +[[ $(grep -Fc '# BEGIN OMARCHY QUALCOMM DEVICE TREES' "$scratch/uki.conf") == 1 ]] || + fail "Snapdragon DTB setup keeps one managed UKI block" +grep -Fq "DeviceTreeAuto=$scratch/dtbs/x1e80100-test.dtb" "$scratch/uki.conf" || + fail "Snapdragon DTB setup lists the matching device tree" +if grep -Fq 'x1e80100-test-el2.dtb' "$scratch/uki.conf"; then + fail "Snapdragon DTB setup excludes EL2-only device trees" +fi + +pass "Snapdragon setup tolerates missing firmware and preserves UKI settings"