Skip to content
1 change: 1 addition & 0 deletions install/hardware/all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
40 changes: 40 additions & 0 deletions install/hardware/lenovo/start-yoga-slim7x-remoteprocs.sh
Original file line number Diff line number Diff line change
@@ -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
68 changes: 68 additions & 0 deletions install/hardware/lenovo/yoga-slim7x.sh
Original file line number Diff line number Diff line change
@@ -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
161 changes: 161 additions & 0 deletions test/shell.d/yoga-slim7x-hardware-test.sh
Original file line number Diff line number Diff line change
@@ -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"