From 443dbcdfb81fbd889432563652d56af951a8f3b2 Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 12:13:15 +0200 Subject: [PATCH] feat: Power-off fix for the Steam Machine; the kernel pin is optional Kernels >= 7.2 keep the firmware's S4/S5 wake bit on GPIO pin 18, so the Steam Machine starts again right after powering off. Steam Machine support now builds a DKMS module (patches/steamify-fremont-poweroff.c) that clears it before power-off, for every kernel. The kernel pin is no longer ticked by default. Module sources and patches move to patches/, embedded by the single-file build. --- .github/tools/bundle.sh | 17 ++++++ .github/workflows/bundle.yml | 4 +- AGENTS.md | 14 +++++ CHANGELOG.md | 17 ++++++ README.md | 12 ++-- TECHNICAL.md | 37 +++++++++++-- lib/cec.sh | 13 ++--- lib/common.sh | 3 + lib/fremont-poweroff.sh | 57 +++++++++++++++++++ lib/menu.sh | 10 ++-- lib/steam-machine.sh | 27 ++++++--- patches/README.md | 10 ++++ patches/cros-ec-cec-single-port.patch | 21 +++++++ patches/steamify-fremont-poweroff.c | 80 +++++++++++++++++++++++++++ steamify.sh | 4 +- ui/qml/Main.qml | 10 ++-- ui/steamify-app.sh | 2 +- 17 files changed, 297 insertions(+), 41 deletions(-) create mode 100644 lib/fremont-poweroff.sh create mode 100644 patches/README.md create mode 100644 patches/cros-ec-cec-single-port.patch create mode 100644 patches/steamify-fremont-poweroff.c diff --git a/.github/tools/bundle.sh b/.github/tools/bundle.sh index 1fddd13..5812723 100755 --- a/.github/tools/bundle.sh +++ b/.github/tools/bundle.sh @@ -31,6 +31,23 @@ version="v$(grep -oP '^VERSION=\K.*' "$entry"), $(git describe --always --dirty sed -e '1{/^#!/d}' -e '/^# Sourced by steamify.sh/d' "lib/$lib.sh" echo done + # patches/ embedded: replaces lib/common.sh's patch_file, which reads + # them from the checkout. + echo "# ----- patches/ -----" + echo "patch_file() {" + echo ' case "$1" in' + for f in patches/*; do + [[ "$f" == patches/README.md ]] && continue + grep -q '^STEAMIFY_PATCH_EOF$' "$f" && { echo "bundle: $f contains the heredoc delimiter" >&2; exit 1; } + echo " $(basename "$f")) cat << 'STEAMIFY_PATCH_EOF'" + cat "$f" + echo "STEAMIFY_PATCH_EOF" + echo " ;;" + done + echo ' *) return 1 ;;' + echo ' esac' + echo "}" + echo echo "# ----- steamify.sh -----" echo "main() {" # Everything after the source loop is the entry point's body. diff --git a/.github/workflows/bundle.yml b/.github/workflows/bundle.yml index f45e092..9700290 100644 --- a/.github/workflows/bundle.yml +++ b/.github/workflows/bundle.yml @@ -6,7 +6,7 @@ # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify.sh | bash # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/download/latest/steamify.sh | bash # Each release also has the app (v2): steamify-app.tar.gz (ui/, steamify.sh, -# lib/, the icon) and its launcher, run with: +# lib/, patches/, the icon) and its launcher, run with: # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.sh | bash # A push to feat/v2-ui (the app, not released yet) updates the pre-release # v2-ui-preview instead, whose launcher gets that build: @@ -36,7 +36,7 @@ jobs: .github/tools/bundle.sh dist/steamify.sh # The app: sources, not the single-file build (its backend mode runs # steamify.sh from its own folder). - tar -czf dist/steamify-app.tar.gz steamify.sh lib ui assets/steam-gaming-settings.svg LICENSE.md + tar -czf dist/steamify-app.tar.gz steamify.sh lib patches ui assets/steam-gaming-settings.svg LICENSE.md cp ui/steamify-app.sh dist/steamify-app.sh - name: Check diff --git a/AGENTS.md b/AGENTS.md index 376e54f..d4a2f48 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,10 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine - `steamify.sh` - the only entry point. Pre-flight checks, sources `lib/*.sh`, runs the menu and applies the plan. No component logic here. +- `patches/` - kernel module sources and patches the scripts build or + apply, never inline in the shell code. Read them with `patch_file + ` (`lib/common.sh`, from the checkout); the bundle embeds every file + in `patches/` and overrides `patch_file`. Add new ones to its README. - `lib/*.sh` - one file per responsibility, each defining functions only (no top-level side effects besides constants). See the table in `TECHNICAL.md`. @@ -204,6 +208,16 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine kernel only pairs that with a named lookup ("Port C") when the CEC driver registered first, which never happens since amdgpu loads from the initramfs. Without it `/dev/cec0` exists but stays at `f.f.f.f`. +- Power-off fix (`lib/fremont-poweroff.sh`, part of `machine`): kernels + >= 7.2 keep the firmware's S4/S5 wake bit on GPIO pin 18, so the Steam + Machine boots again right after powering off (Valve's kernel clears it at + probe, not for upstream). The DKMS module `steamify-fremont-poweroff` + (`patches/steamify-fremont-poweroff.c`) clears it in a power-off-prepare + handler, for every kernel. It made the kernel pin optional (`kpin` is in + `NO_PRESELECT`, no longer ticked with `machine`). `machine_repair` ticks + `machine` for setups without it. CachyOS's `linux-cachyos` is clang-built, + `-bore` GCC-built: let DKMS pick the compiler, never pass `LLVM=1`. Test + shutdown on the real machine for every new major kernel. - `Relogin=true` means a gamescope that fails to start is relaunched in a tight loop; keep that in mind when changing session handling. diff --git a/CHANGELOG.md b/CHANGELOG.md index b6b39fa..9d3d223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes, per version and per commit. Versions follow `steamify.sh`. Versions before 0.7.0 were numbered afterwards, one per merged pull request. +## 2.2.0 - 2026-09-27 + +- **feat: Power-off fix for the Steam Machine; the kernel pin is optional** + - With kernels 7.2 and newer the Steam Machine started again right after + shutting down: Linux 7.2 keeps the wake bit the firmware sets on GPIO + pin 18, which Valve's own kernel clears in a patch that won't reach + CachyOS. Steam Machine support now builds a small module with DKMS for + every kernel that clears it right before power-off. Tested with + CachyOS 7.2.8: stays off, and without the module it rebooted. + - The kernel pin is no longer ticked with Steam Machine support or on a + first run; it stays available (HDMI refresh boost needs it). An + existing pin is kept. Steam Machine support from before 2.2.0 is + ticked in the menu to add the fix. + - Module sources and patches live in `patches/` instead of inside the + shell code; the single-file build embeds them. The Steam Machine CEC + driver change is now `patches/cros-ec-cec-single-port.patch`. + ## 2.1.0 - 2026-09-26 - **feat(ui): Manage button for HDMI refresh boost** diff --git a/README.md b/README.md index 460fa37..f918868 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,13 @@ What Steamify can do on a Valve Steam Machine: TV's remote, and the TV turns on and off with the Steam Machine ([details](TECHNICAL.md#hdmi-cec)). - **Steam Machine support** - the front **LED bar** works, Steam's - **hardware settings** (fan, performance) work, and the power - button puts it to sleep like a console. - - **Pin the kernel to 7.1.6-1** - on by default: with newer CachyOS kernels - the Steam Machine reboots instead of shutting down. Untick it once CachyOS - fixes that. The kernel comes from its own + **hardware settings** (fan, performance) work, the power button puts it + to sleep like a console, and **shutting down really turns it off**, also + with the newest CachyOS kernel + ([details](TECHNICAL.md#power-off-fix-steam-machine)). + - **Pin the kernel to 7.1.6-1** - optional, off by default: stays on an + older kernel (HDMI refresh boost needs it). It used to be needed for + shutting down; the power-off fix above covers that now. The kernel comes from its own [release](https://github.com/theupriser/steamify-cachyos/releases/tag/kernel-7.1.6-1), checked against CachyOS's signature ([details](TECHNICAL.md#kernel-pin-steam-machine)). diff --git a/TECHNICAL.md b/TECHNICAL.md index 73d4285..22a2255 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -208,11 +208,36 @@ upset CEC for the TV's other devices Turning it off removes the packages again (`linuxconsole` only if the wizard installed it). +## Power-off fix (Steam Machine) + +With kernels 7.2 and newer a Steam Machine starts again right after +powering off. Linux 7.2 stopped clearing the S4/S5 wake bits at probe +(`pinctrl-amd: Don't clear S4 wake bits at probe`), and the firmware leaves +that bit set on GPIO pin 18 (`_SB.PCI0.GPP6`). Valve's own kernel +(`linux-neptune-72`) clears it at probe on Fremont, in a patch marked not +for upstream ("until the firmware is fixed"), so CachyOS and mainline won't +get it. + +Steam Machine support builds a small module with DKMS for every installed +kernel, `steamify-fremont-poweroff` (source in +[`patches/steamify-fremont-poweroff.c`](patches/steamify-fremont-poweroff.c)). +It only loads on Fremont (DMI board name) and touches one register: right +before power-off (a `SYS_OFF_MODE_POWER_OFF_PREPARE` handler, after the +drivers have shut down) it clears that pin's S4/S5 wake bit. On a kernel +that already clears it, it does nothing. It logs the pin's state at load: +`dmesg | grep 'GPIO 18'`; `/sys/kernel/debug/gpio` shows the S4/S5 column. +Tested on a Steam Machine with `linux-cachyos-bore` 7.2.8: it stayed off +three times in a row, and rebooted right away with the module unloaded. + +Steam Machine support set up before 2.2.0 has no power-off fix; the menu +ticks it, so a normal run adds it. + ## Kernel pin (Steam Machine) -With CachyOS kernels newer than 7.1.6 a Steam Machine reboots instead of -shutting down. The **Pin the kernel** sub-option (ticked along with Steam -Machine support) installs `linux-cachyos` and `linux-cachyos-headers` +With CachyOS kernels newer than 7.1.6 a Steam Machine rebooted instead of +shutting down; the power-off fix above handles that now, so the pin is +optional. The **Pin the kernel** sub-option (off by default; HDMI refresh +boost needs it) installs `linux-cachyos` and `linux-cachyos-headers` 7.1.6-1 and adds them to `IgnorePkg` in `/etc/pacman.conf`, so updates skip them. DKMS builds the LED driver for it; restart to boot it. @@ -356,8 +381,10 @@ immediately, which can turn into a loop - see | `lib/bios.sh` | Update BIOS (Steam Machine, opt-in): current/newest version, double confirmation, fwupd | | `lib/cec.sh` | HDMI-CEC: Valve's `cecd` and friends from its `holo` repository | | `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager; kernel pin | -| `lib/hdmi-refresh.sh` | HDMI refresh boost (Steam Machine, pinned kernel): EDID over DDC, calculated steps, live test, `drm.edid_firmware` | -| `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`) | +| `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) | +| `lib/hdmi-refresh.sh` | HDMI refresh boost (Steam Machine, pinned kernel): EDID over DDC, calculated steps, live test, per-display EDID hotplug script | +| `patches/` | Module sources and patches the scripts build or apply (`patch_file`); see its README | +| `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`), with `patches/` embedded | | `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` | The single-file version is generated: on every push to `main`, GitHub diff --git a/lib/cec.sh b/lib/cec.sh index d6f40c2..e8e4724 100644 --- a/lib/cec.sh +++ b/lib/cec.sh @@ -60,8 +60,8 @@ cec_reload_driver() { cec_driver_enable() { detect_valve_fremont || return 0 local tmp k - if ! pacman -Q dkms >/dev/null 2>&1; then - sudo pacman -S --needed --noconfirm dkms || { err "Installing dkms failed."; return 1; } + if ! pacman -Q dkms patch >/dev/null 2>&1; then + sudo pacman -S --needed --noconfirm dkms patch || { err "Installing dkms failed."; return 1; } fi install_kernel_headers || return 1 tmp="$(mktemp -d)" @@ -72,13 +72,8 @@ cec_driver_enable() { err "Downloading Valve's CEC driver failed (or its checksum didn't match)." return 1 fi - # amdgpu registers its HDMI notifier without a port name, which only - # matches the driver's named lookup ("Port C") if the driver loaded - # first; amdgpu loads from the initramfs, so it never does. With a - # single CEC port, look it up by device alone. - sed -i 's/cec_notifier_cec_adap_register(hdmi_dev, conns\[port_num\],/cec_notifier_cec_adap_register(hdmi_dev, conns[1] ? conns[port_num] : NULL,/' \ - "$tmp/cros-ec-cec.c" - grep -q 'conns\[1\] ? conns\[port_num\] : NULL' "$tmp/cros-ec-cec.c" || + # So it finds amdgpu's HDMI port (see the patch for why). + patch_file cros-ec-cec-single-port.patch | patch -s -d "$tmp" -p1 || { rm -rf "$tmp"; err "Patching Valve's CEC driver failed."; return 1; } echo 'obj-m += cros-ec-cec.o' >"$tmp/Makefile" printf '%s\n' "# Written by Steamify: Valve's cros_ec_cec, which knows the Steam Machine." \ diff --git a/lib/common.sh b/lib/common.sh index 4ad8495..dfcf60e 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -6,6 +6,9 @@ c_reset="\033[0m"; c_bold="\033[1m"; c_green="\033[32m"; c_yellow="\033[33m"; c_ info() { echo -e "${c_cyan}[INFO]${c_reset} $*"; } ok() { echo -e "${c_green}[OK]${c_reset} $*"; } +# patch_file : a file from patches/ (module sources, patches). The +# single-file build replaces this with one that has them embedded. +patch_file() { cat "$SCRIPT_DIR/patches/$1"; } warn() { echo -e "${c_yellow}[WARN]${c_reset} $*"; } err() { echo -e "${c_red}[ERROR] $*${c_reset}" >&2; } ask_yn() { diff --git a/lib/fremont-poweroff.sh b/lib/fremont-poweroff.sh new file mode 100644 index 0000000..991dbba --- /dev/null +++ b/lib/fremont-poweroff.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# Part of "Steam Machine support": the Steam Machine powers off instead of +# booting up again, on every kernel. Since Linux 7.2 ("pinctrl-amd: Don't +# clear S4 wake bits at probe") the S4/S5 wake bit the firmware leaves set +# on GPIO pin 18 stays set, and the machine starts again right after +# powering off (the reason for the kernel pin). Valve's kernel clears it at +# probe on Fremont, in a patch not meant for upstream, so CachyOS won't get +# it. A small module (patches/steamify-fremont-poweroff.c) clears it right +# before power-off; DKMS builds it for every kernel. On kernels that clear it themselves it does nothing. +# Sourced by steamify.sh; not meant to be run on its own. + +POWEROFF_DKMS_NAME=steamify-fremont-poweroff +POWEROFF_DKMS_VER=1 +POWEROFF_DKMS_SRC="/usr/src/$POWEROFF_DKMS_NAME-$POWEROFF_DKMS_VER" +POWEROFF_MODULES_LOAD=/etc/modules-load.d/steamify-fremont-poweroff.conf + +poweroff_fix_installed() { [[ -f "$POWEROFF_DKMS_SRC/dkms.conf" && -f "$POWEROFF_MODULES_LOAD" ]]; } + +poweroff_fix_enable() { + detect_valve_fremont || return 0 + local tmp k + if ! pacman -Q dkms >/dev/null 2>&1; then + sudo pacman -S --needed --noconfirm dkms || { err "Installing dkms failed."; return 1; } + fi + install_kernel_headers || return 1 + tmp="$(mktemp -d)" + patch_file "$POWEROFF_DKMS_NAME.c" > "$tmp/$POWEROFF_DKMS_NAME.c" + echo "obj-m += $POWEROFF_DKMS_NAME.o" >"$tmp/Makefile" + printf '%s\n' "# Written by Steamify: the Steam Machine powers off instead of booting up again." \ + "PACKAGE_NAME=\"$POWEROFF_DKMS_NAME\"" "PACKAGE_VERSION=\"$POWEROFF_DKMS_VER\"" \ + "BUILT_MODULE_NAME[0]=\"$POWEROFF_DKMS_NAME\"" 'DEST_MODULE_LOCATION[0]="/updates/dkms"' \ + 'AUTOINSTALL="yes"' >"$tmp/dkms.conf" + sudo dkms remove "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" --all >/dev/null 2>&1 + sudo rm -rf "$POWEROFF_DKMS_SRC" + sudo install -d "$POWEROFF_DKMS_SRC" && + sudo install -m644 "$tmp"/{"$POWEROFF_DKMS_NAME.c",Makefile,dkms.conf} "$POWEROFF_DKMS_SRC/" + rm -rf "$tmp" + sudo dkms add "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" >/dev/null || + { err "Adding the power-off fix to DKMS failed."; return 1; } + for k in /usr/lib/modules/*/build; do + k="$(basename "$(dirname "$k")")" + info "Building the power-off fix for $k..." + sudo dkms install "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" -k "$k" >/dev/null || + warn "Building the power-off fix for $k failed; with that kernel it may start again after shutting down." + done + echo "$POWEROFF_DKMS_NAME" | sudo tee "$POWEROFF_MODULES_LOAD" >/dev/null + sudo modprobe "$POWEROFF_DKMS_NAME" 2>/dev/null + return 0 +} + +poweroff_fix_disable() { + sudo rm -f "$POWEROFF_MODULES_LOAD" + sudo modprobe -r "$POWEROFF_DKMS_NAME" 2>/dev/null + [[ -d "$POWEROFF_DKMS_SRC" ]] || return 0 + sudo dkms remove "$POWEROFF_DKMS_NAME/$POWEROFF_DKMS_VER" --all >/dev/null 2>&1 + sudo rm -rf "$POWEROFF_DKMS_SRC" +} diff --git a/lib/menu.sh b/lib/menu.sh index 9fe7e97..b3f4aaa 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -16,7 +16,7 @@ declare -A PARENT=([boot]=gaming [kpin]=machine [hdmi]=machine [bios]=machine) # upset other devices on the TV, even on SteamOS), except on a Steam Machine, # which has CEC like on SteamOS. HDMI refresh boost needs someone at the # screen to confirm each step. -NO_PRESELECT=(boot cec hdmi) +NO_PRESELECT=(boot cec kpin hdmi) declare -A LABEL=( [gaming]="SteamOS conversion: boot into gaming mode, Steam on the desktop" @@ -27,7 +27,7 @@ declare -A LABEL=( [launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher" [cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)" [machine]="Steam Machine support: LED bar driver, hardware settings in Steam" - [kpin]="Pin the kernel to $PINNED_KERNEL_VER (fixes rebooting after shutdown)" + [kpin]="Pin the kernel to $PINNED_KERNEL_VER (optional, stays on an older kernel)" [hdmi]="HDMI refresh boost: highest refresh your HDMI display runs" [bios]="Update BIOS" ) @@ -72,6 +72,8 @@ detect_components() { done # HDMI-CEC set up by an older version: tick it, so a normal run fixes it. component_available cec && cec_repair && WANTED[cec]=1 + # Steam Machine support from before 2.2.0 lacks the power-off fix. + component_available machine && machine_repair && WANTED[machine]=1 # The terminal-only Steamify shortcut from before 2.0.1: tick it, so a # normal run replaces it with the app. launcher_repair && WANTED[launcher]=1 @@ -96,8 +98,8 @@ toggle_component() { if [[ "$c" == gaming && "${WANTED[gaming]}" == 0 ]]; then WANTED[single]=0; WANTED[boot]=0; fi # Where to boot to is part of the conversion, too. if [[ "$c" == boot && "${WANTED[boot]}" == 1 ]]; then WANTED[gaming]=1; fi - # The kernel pin is opt-out: ticked along with Steam Machine support. - if [[ "$c" == machine ]]; then WANTED[kpin]=${WANTED[machine]}; fi + # The kernel pin is opt-in (the power-off fix made it optional). + if [[ "$c" == machine && "${WANTED[machine]}" == 0 ]]; then WANTED[kpin]=0; fi if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi # HDMI refresh boost and the BIOS update sit under Steam Machine support; # the boost also needs the pinned kernel (newer kernels don't need it). diff --git a/lib/steam-machine.sh b/lib/steam-machine.sh index 3a5ed5d..8f3d7e7 100644 --- a/lib/steam-machine.sh +++ b/lib/steam-machine.sh @@ -1,6 +1,7 @@ #!/bin/bash # "Steam Machine support" menu item, only on Valve Fremont hardware: the -# front LED bar driver, LED access for Steam, and steamos-manager. +# front LED bar driver, LED access for Steam, steamos-manager, and the +# power-off fix (lib/fremont-poweroff.sh). # Sourced by steamify.sh; not meant to be run on its own. detect_valve_fremont() { @@ -14,8 +15,8 @@ detect_valve_fremont() { [[ "$vendor" == "OEM" && "$product" == "F7F" ]] } -# Kernel pinned on a Steam Machine: with newer linux-cachyos releases it -# reboots instead of shutting down. The packages are kept in +# Kernel pinned on a Steam Machine (optional): newer linux-cachyos releases +# rebooted it instead of shutting down, which the power-off fix now handles. The packages are kept in # PINNED_KERNEL_DIR, so re-applying (or reinstalling after an update slipped # through) needs no download. PINNED_KERNEL_VER="7.1.6-1" @@ -334,13 +335,13 @@ reload_powerdevil() { kernel_overview() { # Per installed kernel: headers (needed to build DKMS modules), the # in-kernel Steam controller driver, and, on a Steam Machine, whether the - # LED driver is built for it. Printed in the menu so it's easy to verify + # LED driver and the power-off fix are built for it. Printed in the menu so it's easy to verify # a kernel update or a newly added kernel got everything. local mark="${c_green}yes${c_reset}" miss="${c_red}no ${c_reset}" leds=false detect_valve_fremont && command -v dkms >/dev/null 2>&1 && leds=true - local kdir k pkg headers hid led running line + local kdir k pkg headers hid led off running line local legend="> = running; controller = Steam controller driver" - [[ "$leds" == true ]] && legend+=", LEDs = LED bar driver built" + [[ "$leds" == true ]] && legend+=", LEDs = LED bar driver built, power-off = power-off fix built" echo -e " ${c_bold}Kernels${c_reset} ($legend)" for kdir in /usr/lib/modules/*/; do k="$(basename "$kdir")" @@ -352,7 +353,8 @@ kernel_overview() { line="$(printf '%-23s %-18s headers %b controller %b' "$k" "$pkg" "$headers" "$hid")" if [[ "$leds" == true ]]; then led="$miss"; dkms status -k "$k" leds-valve-dkms 2>/dev/null | grep -q installed && led="$mark" - line+="$(printf ' LEDs %b' "$led")" + off="$miss"; dkms status -k "$k" "$POWEROFF_DKMS_NAME" 2>/dev/null | grep -q installed && off="$mark" + line+="$(printf ' LEDs %b power-off %b' "$led" "$off")" fi echo -e " ${running}${line}" done @@ -369,7 +371,13 @@ kernel_overview() { machine_available() { detect_valve_fremont; } machine_status() { - pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 && pacman -Qi steamos-manager >/dev/null 2>&1 + pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 && pacman -Qi steamos-manager >/dev/null 2>&1 && + poweroff_fix_installed +} +# Set up before 2.2.0: no power-off fix yet; the menu ticks it to add it. +machine_repair() { + pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 && pacman -Qi steamos-manager >/dev/null 2>&1 && + ! poweroff_fix_installed } machine_enable() { @@ -381,6 +389,8 @@ machine_enable() { ensure_aur_helper || { warn "Couldn't set up an AUR helper automatically. Install yay or paru, then run the wizard again."; return 1; } install_valve_led_driver || { warn "LED driver setup ran into a problem - see errors above."; return 1; } install_headers_boot_check + info "Installing the power-off fix (the machine stays off after shutting down)..." + poweroff_fix_enable || { err "Installing the power-off fix failed."; return 1; } # The LED files are root-only. Steam runs as the user; let it write them # in case it drives the bar directly (Valve's own privileged-write helper @@ -424,6 +434,7 @@ machine_disable() { sudo udevadm control --reload sudo modprobe -r leds-valve 2>/dev/null sudo pacman -Rns --noconfirm leds-valve-dkms-git 2>/dev/null + poweroff_fix_disable krevert machine reload_powerdevil sudo systemctl disable ensure-kernel-headers.service 2>/dev/null diff --git a/patches/README.md b/patches/README.md new file mode 100644 index 0000000..699c90d --- /dev/null +++ b/patches/README.md @@ -0,0 +1,10 @@ +# Patches and extra sources + +Files Steamify builds or applies, kept out of the shell code so they can be +read and reviewed on their own. `lib/*.sh` reads them with `patch_file +`; the single-file build (`.github/tools/bundle.sh`) embeds them. + +| File | Used by | What | +|---|---|---| +| `steamify-fremont-poweroff.c` | `lib/fremont-poweroff.sh` (Steam Machine support) | Kernel module (DKMS): clears GPIO 18's S4/S5 wake bit before power-off, so the Steam Machine stays off on kernels 7.2 and newer | +| `cros-ec-cec-single-port.patch` | `lib/cec.sh` (HDMI-CEC) | Makes Valve's Steam Machine CEC driver find amdgpu's HDMI port | diff --git a/patches/cros-ec-cec-single-port.patch b/patches/cros-ec-cec-single-port.patch new file mode 100644 index 0000000..3c21b3f --- /dev/null +++ b/patches/cros-ec-cec-single-port.patch @@ -0,0 +1,21 @@ +Steam Machine CEC: find amdgpu's HDMI port with a single CEC port. + +amdgpu registers its HDMI notifier without a port name, which only matches +the driver's named lookup ("Port C") if the CEC driver loaded first; amdgpu +loads from the initramfs, so it never does and /dev/cec0 stays at f.f.f.f. +With a single CEC port, look the notifier up by device alone. + +Applies to Valve's drivers/media/cec/platform/cros-ec/cros-ec-cec.c +(evlaV/linux-integration 10c8c880, see CEC_DRIVER_URL in lib/cec.sh). + +--- a/cros-ec-cec.c ++++ b/cros-ec-cec.c +@@ -485,7 +485,7 @@ + goto out_probe_adapter; + } + +- port->notify = cec_notifier_cec_adap_register(hdmi_dev, conns[port_num], ++ port->notify = cec_notifier_cec_adap_register(hdmi_dev, conns[1] ? conns[port_num] : NULL, + port->adap); + if (!port->notify) { + ret = -ENOMEM; diff --git a/patches/steamify-fremont-poweroff.c b/patches/steamify-fremont-poweroff.c new file mode 100644 index 0000000..f2c65b2 --- /dev/null +++ b/patches/steamify-fremont-poweroff.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Steam Machine (Fremont): power off instead of booting up again. + * + * Since Linux 7.2 ("pinctrl-amd: Don't clear S4 wake bits at probe") the + * S4/S5 wake bit the firmware leaves set on GPIO pin 18 (_SB.PCI0.GPP6) + * stays set, and the machine starts again right after powering off. + * Valve's kernel clears that bit at probe on Fremont (not for upstream); + * this does the same right before power-off, for kernels without it. + */ +#include +#include +#include +#include +#include +#include +#include + +#define FREMONT_GPP6_PIN 18 +#define WAKE_CNTRL_OFF_S4 15 + +static void __iomem *pin_reg; +static struct sys_off_handler *handler; + +static int fremont_poweroff_prepare(struct sys_off_data *data) +{ + u32 v = readl(pin_reg); + + if (v & BIT(WAKE_CNTRL_OFF_S4)) + writel(v & ~BIT(WAKE_CNTRL_OFF_S4), pin_reg); + return NOTIFY_DONE; +} + +static int __init fremont_poweroff_init(void) +{ + struct acpi_device *adev; + struct device *dev; + struct resource *res; + u32 v; + + if (!dmi_match(DMI_BOARD_NAME, "Fremont")) + return -ENODEV; + adev = acpi_dev_get_first_match_dev("AMDI0030", NULL, -1); + if (!adev) + return -ENODEV; + dev = bus_find_device_by_acpi_dev(&platform_bus_type, adev); + acpi_dev_put(adev); + if (!dev) + return -ENODEV; + res = platform_get_resource(to_platform_device(dev), IORESOURCE_MEM, 0); + put_device(dev); + if (!res || resource_size(res) < (FREMONT_GPP6_PIN + 1) * 4) + return -ENODEV; + /* pinctrl-amd owns the region; only this one register is touched. */ + pin_reg = ioremap(res->start + FREMONT_GPP6_PIN * 4, 4); + if (!pin_reg) + return -ENOMEM; + v = readl(pin_reg); + pr_info("GPIO %d register 0x%08x, S4/S5 wake %s\n", FREMONT_GPP6_PIN, v, + v & BIT(WAKE_CNTRL_OFF_S4) ? "set (cleared at power-off)" : "clear"); + handler = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF_PREPARE, + SYS_OFF_PRIO_DEFAULT, fremont_poweroff_prepare, NULL); + if (IS_ERR(handler)) { + iounmap(pin_reg); + return PTR_ERR(handler); + } + return 0; +} + +static void __exit fremont_poweroff_exit(void) +{ + unregister_sys_off_handler(handler); + iounmap(pin_reg); +} + +module_init(fremont_poweroff_init); +module_exit(fremont_poweroff_exit); +MODULE_DESCRIPTION("Steam Machine: clear GPIO 18's S4 wake bit before power-off"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("dmi:*:rnFremont:*"); diff --git a/steamify.sh b/steamify.sh index 4eb66f4..dd87858 100755 --- a/steamify.sh +++ b/steamify.sh @@ -15,11 +15,11 @@ set -uo pipefail # Release version, see CHANGELOG.md. -VERSION=2.1.0 +VERSION=2.2.0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -for lib in common state packages login-manager single-user steam-desktop steam-machine hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu backend; do +for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu backend; do # shellcheck source=/dev/null source "$SCRIPT_DIR/lib/$lib.sh" done diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index fc8ce49..94a04b5 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -67,10 +67,10 @@ ApplicationWindow { body: "Use Steam with your TV remote, and the TV turns on and off with the PC. Turn on CEC on the TV too (Sony: BRAVIA Sync, Samsung: Anynet+, LG: SimpLink).", changes: ["Valve's cecd and cec-audio-control", "HDMI CEC section in Steam's Display settings", "Volume buttons for the TV in the Quick Access menu"] }, machine: { label: "Steam Machine support", hint: "LED bar, fan and performance settings in Steam", - body: "The front LED bar works, Steam's hardware settings work, and the power button puts it to sleep like a console.", - changes: ["leds-valve driver for every kernel (DKMS)", "steamos-manager for Steam's settings", "Console-like power handling"] }, - kpin: { label: "Pin the kernel", hint: "Fixes rebooting after shutdown", - body: "Newer CachyOS kernels make the Steam Machine reboot instead of shutting down. Untick once CachyOS fixes that.", + body: "The front LED bar works, Steam's hardware settings work, the power button puts it to sleep like a console, and shutting down really turns it off on every kernel.", + changes: ["leds-valve driver for every kernel (DKMS)", "steamos-manager for Steam's settings", "Console-like power handling", "Power-off fix: stays off after shutting down (DKMS)"] }, + kpin: { label: "Pin the kernel", hint: "Optional: stay on an older kernel", + body: "Keeps the Steam Machine on CachyOS kernel 7.1.6. Not needed for shutting down any more (Steam Machine support fixes that on every kernel); HDMI refresh boost needs it, since newer kernels do HDMI 2.1 themselves.", changes: ["linux-cachyos from Steamify's release (signature checked)", "Kept in /var/cache/steamify/kernel", "Added to IgnorePkg"] }, hdmi: { label: "HDMI refresh boost", hint: "Higher refresh rates over HDMI", body: "The pinned kernel keeps many HDMI displays at 60 Hz. Turning this on shows which refresh rates your display can run at the desktop resolution; you pick them, and each one is tried for 15 seconds so you can check the picture before it's installed.", @@ -247,7 +247,7 @@ ApplicationWindow { if (id === "hdmi" && !hdmiChoice && hdmiSaved.length) { openHdmiList(); return; } if (id === "hdmi" && w.hdmi && !nowOn("hdmi") && !hdmiChoice) { startHdmi(); return; } if (id === "hdmi" && !w.hdmi) hdmiChoice = ""; - if (id === "machine") w.kpin = w.machine; + if (id === "machine" && !w.machine) w.kpin = false; if (id === "kpin" && w.kpin) w.machine = true; if (id === "hdmi" && w.hdmi) { w.machine = true; w.kpin = true; } if (!w.machine || !w.kpin) w.hdmi = false; diff --git a/ui/steamify-app.sh b/ui/steamify-app.sh index 6cafcec..35a267b 100755 --- a/ui/steamify-app.sh +++ b/ui/steamify-app.sh @@ -2,7 +2,7 @@ # Steamify CachyOS - get and start the app (v2), for `curl | bash`: # curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.sh | bash # Downloads the newest release's app package (steamify-app.tar.gz: the app, -# steamify.sh and lib/) to ~/.local/share/steamify/app, installs PySide6 if +# steamify.sh, lib/ and patches/) to ~/.local/share/steamify/app, installs PySide6 if # it's missing, adds a launcher entry, and starts the app. # STEAMIFY_RELEASE= takes that release instead of the newest (the # v2-ui-preview build's copy of this script sets it). STEAMIFY_BRANCH=