diff --git a/install/hardware/all.sh b/install/hardware/all.sh index 19b26b7eb78..05c3d8a2673 100644 --- a/install/hardware/all.sh +++ b/install/hardware/all.sh @@ -45,6 +45,7 @@ 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/lenovo/yoga-slim7x.sh" run_logged "$OMARCHY_INSTALL/hardware/fix-bcm43xx.sh" run_logged "$OMARCHY_INSTALL/hardware/fix-surface-keyboard.sh" diff --git a/install/hardware/lenovo/start-yoga-slim7x-remoteprocs.sh b/install/hardware/lenovo/start-yoga-slim7x-remoteprocs.sh new file mode 100644 index 00000000000..ec7a5c94197 --- /dev/null +++ b/install/hardware/lenovo/start-yoga-slim7x-remoteprocs.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +set -euo pipefail + +remoteproc_root=${OMARCHY_YOGA_REMOTEPROC_ROOT:-/sys/class/remoteproc} +attempts=${OMARCHY_YOGA_REMOTEPROC_ATTEMPTS:-30} +sleep_seconds=${OMARCHY_YOGA_REMOTEPROC_SLEEP:-1} + +for ((attempt = 1; attempt <= attempts; attempt++)); do + adsp_started=0 + shopt -s nullglob + + for state_file in "$remoteproc_root"/remoteproc*/state; do + remoteproc=${state_file%/state} + [[ -r $remoteproc/firmware && -r $state_file ]] || continue + + firmware=$(tr -d '\0\n' <"$remoteproc/firmware") + case "$firmware" in + *qcadsp*) kind=ADSP ;; + *qccdsp*) kind=CDSP ;; + *) continue ;; + esac + + if [[ $(<$state_file) == running ]]; then + echo "Yoga Slim 7x: $kind is already running" + [[ $kind == ADSP ]] && adsp_started=1 + elif [[ -w $state_file ]] && printf 'start\n' >"$state_file"; then + echo "Yoga Slim 7x: started $kind" + [[ $kind == ADSP ]] && adsp_started=1 + else + echo "Yoga Slim 7x: could not start $kind ($firmware)" >&2 + fi + done + + ((adsp_started)) && exit 0 + sleep "$sleep_seconds" +done + +echo "Yoga Slim 7x: audio DSP did not appear or could not be started" >&2 +exit 1 diff --git a/install/hardware/lenovo/yoga-slim7x.sh b/install/hardware/lenovo/yoga-slim7x.sh new file mode 100644 index 00000000000..2c9ed5c7312 --- /dev/null +++ b/install/hardware/lenovo/yoga-slim7x.sh @@ -0,0 +1,68 @@ +# Lenovo Yoga Slim 7x (14Q8X9 / 83ED) board-specific setup. + +yoga_slim7x_compatible="" +compatible_path=${OMARCHY_YOGA_COMPATIBLE_PATH:-/sys/firmware/devicetree/base/compatible} +modules_load_dir=${OMARCHY_YOGA_MODULES_LOAD_DIR:-/etc/modules-load.d} +mkinitcpio_dir=${OMARCHY_YOGA_MKINITCPIO_DIR:-/etc/mkinitcpio.conf.d} +limine_config_dir=${OMARCHY_YOGA_LIMINE_CONFIG_DIR:-/etc/limine-entry-tool.d} +systemd_dir=${OMARCHY_YOGA_SYSTEMD_DIR:-/etc/systemd/system} + +if [[ -r $compatible_path ]]; then + yoga_slim7x_compatible=$(tr '\0' '\n' <"$compatible_path") +fi + +if omarchy-hw-qualcomm-soc && + { grep -qi '^lenovo,yoga-slim7x$' <<<"$yoga_slim7x_compatible" || + omarchy-hw-match 'Yoga Slim 7x' || omarchy-hw-match '83ED'; }; then + echo "Detected Lenovo Yoga Slim 7x, applying board-specific support..." + + # The Yoga exposes its CPU performance domains through SCMI. + mkdir -p "$modules_load_dir" + echo "scmi-cpufreq" >"$modules_load_dir/yoga-slim7x.conf" + + # Initialize the internal keyboard and display before disk unlock. + mkdir -p "$mkinitcpio_dir" + cat >"$mkinitcpio_dir/yoga-slim7x-initramfs.conf" <<'CONF' +MODULES+=(i2c-hid-of qrtr ps883x pmic_glink_altmode) + +# The board-signed zap shader is included by qcom-firmware-extract. +for firmware in \ + qcom/gen70500_sqe.fw \ + qcom/gen70500_gmu.bin; do + for suffix in '' .zst .xz; do + for directory in "${OMARCHY_YOGA_FIRMWARE_ROOT:-/usr/lib/firmware}/updates" \ + "${OMARCHY_YOGA_FIRMWARE_ROOT:-/usr/lib/firmware}"; do + if [[ -f $directory/$firmware$suffix ]]; then + FILES+=("$directory/$firmware$suffix") + break 2 + fi + done + done +done +unset firmware directory suffix +CONF + + mkdir -p "$limine_config_dir" + cat >"$limine_config_dir/yoga-slim7x.conf" <<'CONF' +KERNEL_CMDLINE[default]+=" initcall_blacklist=simpledrm_platform_driver_init" +# Keep Plymouth and the keyboard hooks on the laptop console, not the serial port. +KERNEL_CMDLINE[default]+=" console=tty0" +CONF + + # Start the board's DSP remote processors. + mkdir -p "$systemd_dir" + cat >"$systemd_dir/yoga-slim7x-remoteprocs.service" <<'UNIT' +[Unit] +Description=Start the Lenovo Yoga Slim 7x DSPs +ConditionPathExists=!/etc/modprobe.d/qualcomm-adsp-nofw.conf + +[Service] +Type=oneshot +ExecStart=/bin/bash /usr/share/omarchy/install/hardware/lenovo/start-yoga-slim7x-remoteprocs.sh +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target +UNIT + systemctl enable yoga-slim7x-remoteprocs.service +fi diff --git a/test/shell.d/yoga-slim7x-hardware-test.sh b/test/shell.d/yoga-slim7x-hardware-test.sh new file mode 100644 index 00000000000..125210b0527 --- /dev/null +++ b/test/shell.d/yoga-slim7x-hardware-test.sh @@ -0,0 +1,161 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +setup="$ROOT/install/hardware/lenovo/yoga-slim7x.sh" +starter="$ROOT/install/hardware/lenovo/start-yoga-slim7x-remoteprocs.sh" +scratch=$(mktemp -d) +trap 'rm -rf "$scratch"' EXIT + +for script in "$setup" "$starter"; do + bash -n "$script" || fail "Yoga Slim 7x hardware scripts have valid syntax" +done + +matching="$scratch/matching" +mkdir -p "$matching" +printf 'qcom,x1e80100\0lenovo,yoga-slim7x\0' >"$matching/compatible" +( + omarchy-hw-qualcomm-soc() { return 0; } + omarchy-hw-match() { return 1; } + systemctl() { printf '%s\n' "$*" >>"$matching/systemctl.log"; } + + OMARCHY_YOGA_COMPATIBLE_PATH="$matching/compatible" \ + OMARCHY_YOGA_MODULES_LOAD_DIR="$matching/modules-load.d" \ + OMARCHY_YOGA_MKINITCPIO_DIR="$matching/mkinitcpio.conf.d" \ + OMARCHY_YOGA_LIMINE_CONFIG_DIR="$matching/limine-entry-tool.d" \ + OMARCHY_YOGA_SYSTEMD_DIR="$matching/systemd" \ + source "$setup" +) + +grep -Fxq 'scmi-cpufreq' "$matching/modules-load.d/yoga-slim7x.conf" || + fail "Yoga Slim 7x setup loads its SCMI CPU-frequency driver" +( + firmware_root="$scratch/firmware" + mkdir -p "$firmware_root/qcom" "$firmware_root/updates/qcom" + touch "$firmware_root/qcom/gen70500_sqe.fw.zst" \ + "$firmware_root/qcom/gen70500_gmu.bin.xz" + MODULES=() FILES=() + OMARCHY_YOGA_FIRMWARE_ROOT="$firmware_root" \ + source "$matching/mkinitcpio.conf.d/yoga-slim7x-initramfs.conf" + [[ ${MODULES[*]} == "i2c-hid-of qrtr ps883x pmic_glink_altmode" ]] || + fail "the generated config loads the keyboard and display modules" + (( ${#FILES[@]} == 2 )) || fail "GPU microcode is included without duplicating the board zap shader" + [[ ${FILES[0]} == "$firmware_root/qcom/gen70500_sqe.fw.zst" ]] || + fail "zstd display firmware is included" + [[ ${FILES[1]} == "$firmware_root/qcom/gen70500_gmu.bin.xz" ]] || + fail "xz display firmware is included" + touch "$firmware_root/updates/qcom/gen70500_sqe.fw" + FILES=() + OMARCHY_YOGA_FIRMWARE_ROOT="$firmware_root" \ + source "$matching/mkinitcpio.conf.d/yoga-slim7x-initramfs.conf" + [[ ${FILES[0]} == "$firmware_root/updates/qcom/gen70500_sqe.fw" ]] || + fail "plain extracted firmware takes precedence over compressed packaged firmware" +) +( + declare -A KERNEL_CMDLINE=([default]="root=/dev/mapper/root quiet splash") + source "$matching/limine-entry-tool.d/yoga-slim7x.conf" + [[ " ${KERNEL_CMDLINE[default]} " == *" console=tty0 "* ]] || + fail "Yoga Slim 7x uses the laptop console for graphical disk unlock" + [[ " ${KERNEL_CMDLINE[default]} " == *" initcall_blacklist=simpledrm_platform_driver_init "* ]] || + fail "Yoga Slim 7x defers display ownership to the native driver" + [[ ${KERNEL_CMDLINE[default]} == "root=/dev/mapper/root quiet splash "* ]] || + fail "Yoga Slim 7x preserves the existing boot parameters" +) +grep -Fq 'ConditionPathExists=!/etc/modprobe.d/qualcomm-adsp-nofw.conf' \ + "$matching/systemd/yoga-slim7x-remoteprocs.service" || + fail "Yoga Slim 7x skips DSP startup when the generic firmware leaf blacklists it" +if grep -Fq 'systemd-udev-settle.service' "$matching/systemd/yoga-slim7x-remoteprocs.service"; then + fail "Yoga Slim 7x DSP startup does not wait for all udev devices" +fi +grep -Fxq 'enable yoga-slim7x-remoteprocs.service' "$matching/systemctl.log" || + fail "Yoga Slim 7x enables its remote processor service" + +nonmatching="$scratch/nonmatching" +mkdir -p "$nonmatching" +printf 'qcom,x1e80100\0hp,elitebook-ultra-g1q\0' >"$nonmatching/compatible" +( + omarchy-hw-qualcomm-soc() { return 0; } + omarchy-hw-match() { return 1; } + systemctl() { fail "nonmatching Qualcomm hardware does not enable Yoga services"; } + + OMARCHY_YOGA_COMPATIBLE_PATH="$nonmatching/compatible" \ + OMARCHY_YOGA_MODULES_LOAD_DIR="$nonmatching/modules-load.d" \ + OMARCHY_YOGA_MKINITCPIO_DIR="$nonmatching/mkinitcpio.conf.d" \ + OMARCHY_YOGA_LIMINE_CONFIG_DIR="$nonmatching/limine-entry-tool.d" \ + OMARCHY_YOGA_SYSTEMD_DIR="$nonmatching/systemd" \ + source "$setup" +) + +[[ ! -e $nonmatching/modules-load.d/yoga-slim7x.conf ]] || + fail "nonmatching Qualcomm hardware does not get Yoga CPU setup" +[[ ! -e $nonmatching/mkinitcpio.conf.d/yoga-slim7x-initramfs.conf ]] || + fail "nonmatching Qualcomm hardware does not get Yoga initramfs setup" +[[ ! -e $nonmatching/limine-entry-tool.d/yoga-slim7x.conf ]] || + fail "nonmatching Qualcomm hardware does not get Yoga boot parameters" +[[ ! -e $nonmatching/systemd/yoga-slim7x-remoteprocs.service ]] || + fail "nonmatching Qualcomm hardware does not get Yoga services" + +( + omarchy-hw-qualcomm-soc() { return 0; } + omarchy-hw-match() { [[ $1 == "83ED" ]]; } + systemctl() { :; } + OMARCHY_YOGA_COMPATIBLE_PATH="$scratch/no-compatible" \ + OMARCHY_YOGA_MODULES_LOAD_DIR="$nonmatching/modules-load.d" \ + OMARCHY_YOGA_MKINITCPIO_DIR="$nonmatching/mkinitcpio.conf.d" \ + OMARCHY_YOGA_LIMINE_CONFIG_DIR="$nonmatching/limine-entry-tool.d" \ + OMARCHY_YOGA_SYSTEMD_DIR="$nonmatching/systemd" \ + source "$setup" +) +[[ -f $nonmatching/modules-load.d/yoga-slim7x.conf ]] || + fail "Lenovo DMI matching works without a device-tree compatible property" + +remoteprocs="$scratch/remoteproc" +mkdir -p "$remoteprocs/remoteproc0" "$remoteprocs/remoteproc1" "$remoteprocs/remoteproc2" +printf 'qcom/x1e80100/LENOVO/83ED/qcadsp8380.mbn\n' >"$remoteprocs/remoteproc0/firmware" +printf 'offline\n' >"$remoteprocs/remoteproc0/state" +printf 'qcom/x1e80100/LENOVO/83ED/qccdsp8380.mbn\n' >"$remoteprocs/remoteproc1/firmware" +printf 'offline\n' >"$remoteprocs/remoteproc1/state" +printf 'unrelated.mbn\n' >"$remoteprocs/remoteproc2/firmware" +printf 'offline\n' >"$remoteprocs/remoteproc2/state" + +OMARCHY_YOGA_REMOTEPROC_ROOT="$remoteprocs" \ + OMARCHY_YOGA_REMOTEPROC_ATTEMPTS=1 \ + OMARCHY_YOGA_REMOTEPROC_SLEEP=0 \ + bash "$starter" + +[[ $(<"$remoteprocs/remoteproc0/state") == start ]] || + fail "Yoga Slim 7x helper starts the audio DSP by firmware identity" +[[ $(<"$remoteprocs/remoteproc1/state") == start ]] || + fail "Yoga Slim 7x helper starts the compute DSP by firmware identity" +[[ $(<"$remoteprocs/remoteproc2/state") == offline ]] || + fail "Yoga Slim 7x helper leaves unrelated remote processors alone" + +run_starter() { + OMARCHY_YOGA_REMOTEPROC_ROOT="$remoteprocs" \ + OMARCHY_YOGA_REMOTEPROC_ATTEMPTS=1 \ + OMARCHY_YOGA_REMOTEPROC_SLEEP=0 \ + bash "$starter" +} +printf 'running\n' >"$remoteprocs/remoteproc0/state" +printf 'running\n' >"$remoteprocs/remoteproc1/state" +run_starter +[[ $(<"$remoteprocs/remoteproc0/state") == running ]] || fail "running ADSP is not restarted" +[[ $(<"$remoteprocs/remoteproc1/state") == running ]] || fail "running CDSP is not restarted" + +printf 'unrelated.mbn\n' >"$remoteprocs/remoteproc0/firmware" +if run_starter; then fail "missing ADSP must time out, even when CDSP is running"; fi +printf 'qcadsp8380.mbn\n' >"$remoteprocs/remoteproc0/firmware" +printf 'offline\n' >"$remoteprocs/remoteproc0/state" +( + printf() { + [[ $1 != "start\n" ]] || return 1 + # shellcheck disable=SC2059 # Forward the caller's format unchanged. + builtin printf "$@" + } + export -f printf + if run_starter; then fail "a failed ADSP start must be reported"; fi +) + +pass "Yoga Slim 7x adds only its board-specific keyboard, display, CPU and DSP setup"