feat: HDMI refresh boost only for the display it was set up for

The EDID override moves off the kernel command line to a hotplug script
(boot unit + udev rule) that loads it only while the display it was
tested on is connected, identified over DDC; other displays get their
own EDID.
This commit is contained in:
theupriser committed 2026-09-26 16:04:02 +02:00
1 parent 2149adf995
commit 8840e24d1a
5 files changed
+156 -48

No files matched your search

+7 -2
View File
@@ -186,8 +186,13 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
<output>=<w>x<h>:<rates>`; without `--hdmi` the backend refuses it. debugfs is root-only
(glob it under sudo), and `edid_override` takes exactly `reset` with no
newline. Build the EDID from the DDC read, not from sysfs: a live override
replaces the kernel's copy. The kernel command-line value holds a path, so
`sed` uses `|` as its delimiter.
replaces the kernel's copy. The override is never on the kernel command
line (that applied it to any display on the port): `steamify-edid-hotplug`
(boot unit + udev drm hotplug rule) loads it only while the display whose
DDC ID is in `/etc/steamify/hdmi-edid.conf` is connected, else resets.
It records the state in `/run/steamify-edid` before its own
`trigger_hotplug`, whose event runs it again. Pre-2.1.0 command-line
setups are removed by `hdmi_remove_boot_param`.
- Steam Machine CEC driver (`cec_driver_enable`, `lib/cec.sh`): mainline
`cros_ec_cec` lacks Fremont, so HDMI-CEC builds Valve's copy (evlaV
`linux-integration`, pinned commit + SHA-256) with DKMS for every kernel.
+10
View File
@@ -5,6 +5,16 @@ 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.1.0 - 2026-09-26
- **feat: HDMI refresh boost only for the display it was set up for**
- The EDID override was on the kernel command line, so any display on that
HDMI port got the first display's timings. It's now loaded at boot and
at every hotplug only when the connected display is the one it was
tested on (its ID read over DDC); another display, or unplugging, puts
the port back on the display's own EDID. No initramfs or boot loader
change any more. An existing setup is moved over when re-applied (`a`).
## 2.0.3 - 2026-09-26
- **fix: HDMI-CEC on the Steam Machine**
+17 -9
View File
@@ -259,16 +259,24 @@ ticked by default, run from the desktop in Konsole):
to each step, lowest first. Each one needs a "y" within 15 s
(`WIZARD_HDMI_CONFIRM_SECONDS` for tests); anything else switches back
and stops.
5. Makes it permanent with only the confirmed steps:
`/usr/lib/firmware/edid/steamify-<connector>.bin`, in the initramfs via
`/etc/mkinitcpio.conf.d/90-steamify-edid.conf` (amdgpu loads from there),
and `drm.edid_firmware=<connector>:edid/steamify-<connector>.bin` on the
kernel command line: `/etc/default/limine` (`limine-mkinitcpio`),
`/etc/sdboot-manage.conf` or `/etc/default/grub`.
5. Keeps only the confirmed steps, for that display only:
`/usr/lib/firmware/edid/steamify-<connector>.bin`, and the display's ID
(manufacturer, model, serial, date: EDID bytes 8-17) in
`/etc/steamify/hdmi-edid.conf`. `steamify-edid.service` (at boot, before
the login manager) and a udev rule (`90-steamify-edid.rules`, every drm
hotplug) run `/usr/local/bin/steamify-edid-hotplug`, which reads the
connected display's ID over DDC (the real display, even while an override
is loaded) and loads the file through debugfs only when it matches.
Another display, or no display, resets the port to the display's own
EDID, so a different monitor never gets the boosted timings. The state
per port is kept in `/run/steamify-edid`, so the hotplug the script
triggers itself doesn't loop.
The display's manufacturer, model and serial are stored; re-applying with
another display connected tests that one instead. Turning it off removes
the files and the parameter and rebuilds. Untick it before removing the
Versions before 2.1.0 used `drm.edid_firmware=` on the kernel command line
(and the initramfs), which applied to any display on that port; re-applying
moves such a setup over to the hotplug script. Re-applying with another
display connected tests that one instead. Turning it off removes the files,
the unit and the rule. Untick it before removing the
kernel pin: newer kernels read the EDID themselves and can do HDMI 2.1.
## BIOS updates (Steam Machine)
+121 -36
View File
@@ -1,8 +1,8 @@
#!/bin/bash
# "HDMI refresh boost" menu item, Steam Machine (Fremont) only: finds the
# highest refresh rate the display runs at the desktop resolution over HDMI,
# tests it live and makes it permanent with an EDID override
# (drm.edid_firmware). Two things keep displays below what they can do:
# tests it live and keeps it with an EDID override that's only loaded while
# that display is connected (see hdmi_install_hotplug). Two things keep displays below what they can do:
# - Monitors put their fast modes in an extra EDID block (HDMI Forum EEODB);
# the pinned kernel (7.1.6) only reads the first extension. Newer kernels
# do, and do HDMI 2.1, so it's only offered with the pinned kernel.
@@ -14,7 +14,14 @@
# black). Sourced by steamify.sh; not meant to be run on its own.
HDMI_FW_DIR=/usr/lib/firmware/edid
# Before 2.1.0 the override was on the kernel command line; only removed now.
HDMI_INITRAMFS_CONF=/etc/mkinitcpio.conf.d/90-steamify-edid.conf
# "<output> <display id>" per line: which display each EDID file is for.
HDMI_MAP=/etc/steamify/hdmi-edid.conf
HDMI_HOTPLUG=/usr/local/bin/steamify-edid-hotplug
HDMI_UNIT_NAME=steamify-edid.service
HDMI_UNIT="/etc/systemd/system/$HDMI_UNIT_NAME"
HDMI_UDEV_RULE=/etc/udev/rules.d/90-steamify-edid.rules
# No answer within this time switches back; longer for scripted tests.
HDMI_CONFIRM_SECONDS="${WIZARD_HDMI_CONFIRM_SECONDS:-15}"
# amdgpu's HDMI 2.0 limit; the display's own limit (from its EDID) may be lower.
@@ -48,10 +55,14 @@ hdmi_cmdline_param() {
hdmi_available() {
# Stays available while on, so it can be turned off after the pin is gone.
detect_valve_fremont || return 1
hdmi_status || { pinned_kernel_installed && [[ -n "$(hdmi_connectors)" && -n "$(hdmi_boot_file)" ]]; }
hdmi_status || { pinned_kernel_installed && [[ -n "$(hdmi_connectors)" ]]; }
}
hdmi_status() { [[ -n "$(hdmi_cmdline_param)" ]] && compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null; }
hdmi_status() {
# The command line: set up by an older version, re-applying moves it over.
{ [[ -f "$HDMI_UNIT" ]] || [[ -n "$(hdmi_cmdline_param)" ]]; } &&
compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null
}
hdmi_edid_tool() {
# hdmi_edid_tool <command> ...: EDID parsing and building (python, as
@@ -322,23 +333,16 @@ hdmi_confirm() {
[[ "$reply" =~ ^[Yy]$ ]]
}
hdmi_boot_param() {
# hdmi_boot_param <value|"">: set or remove our drm.edid_firmware= in
# the boot loader's command line, then rebuild the initramfs and entries.
local f value="$1"
f="$(hdmi_boot_file)" || return 1
[[ -n "$f" ]] || { err "No supported boot loader (Limine, systemd-boot with sdboot-manage, GRUB) found."; return 1; }
hdmi_remove_boot_param() {
# Removes the drm.edid_firmware= older versions put on the kernel command
# line (it applied to whatever display was on that port), then rebuilds
# the initramfs and boot entries.
local f
[[ -n "$(hdmi_cmdline_param)" ]] || { sudo rm -f "$HDMI_INITRAMFS_CONF"; return 0; }
f="$(hdmi_boot_file)"
backup_file "$f"
sudo sed -i -E 's/drm\.edid_firmware=[^ "]*steamify-[^ "]* ?//' "$f"
if [[ -n "$value" ]]; then
case "$f" in
# | as delimiter: the value holds a path.
/etc/default/limine) sudo sed -i -E "0,\|^KERNEL_CMDLINE\[default\]\+?=\"|s||&$value |" "$f" ;;
/etc/sdboot-manage.conf) sudo sed -i -E "0,\|^LINUX_OPTIONS=\"|s||&$value |" "$f" ;;
/etc/default/grub) sudo sed -i -E "0,\|^GRUB_CMDLINE_LINUX_DEFAULT=\"|s||&$value |" "$f" ;;
esac
grep -qF "$value" "$f" || { err "Couldn't add $value to $f."; return 1; }
fi
sudo rm -f "$HDMI_INITRAMFS_CONF"
info "Rebuilding the initramfs and boot entries..."
case "$f" in
/etc/default/limine) sudo limine-mkinitcpio ;;
@@ -347,6 +351,87 @@ hdmi_boot_param() {
esac
}
hdmi_install_hotplug() {
# The EDID file of an output is only loaded while the display it was
# tested on is connected: a udev rule runs the script at every hotplug
# (and a unit at boot, before the login manager). The script reads the
# display's ID over DDC, which shows the real display even while an
# override is loaded, and loads the file or resets to the display's own
# EDID. Another display on that port gets its own EDID, and unplugging
# resets it, so the next display never starts on the wrong one.
local f out map=""
for f in "$HDMI_FW_DIR"/steamify-*.bin; do
[[ -e "$f" ]] || continue
out="${f##*/steamify-}"; out="${out%.bin}"
map+="$out $(state_get hdmi "$out")"$'\n'
done
[[ -n "$map" ]] || return 1
sudo mkdir -p "$(dirname "$HDMI_MAP")"
printf '# Written by steamify: HDMI refresh boost, <output> <display id>.\n%s' "$map" |
sudo tee "$HDMI_MAP" >/dev/null || return 1
sudo tee "$HDMI_HOTPLUG" > /dev/null << 'EOF'
#!/bin/bash
# Steamify CachyOS, HDMI refresh boost: load an output's EDID override only
# while the display it was made for is connected. Runs as root (debugfs).
MAP=/etc/steamify/hdmi-edid.conf FW=/usr/lib/firmware/edid RUN=/run/steamify-edid
modprobe i2c-dev 2>/dev/null
mkdir -p "$RUN"
# Hotplug events come in bursts, and the link needs a moment before DDC works.
sleep 1
for c in /sys/class/drm/card*-HDMI-A-*; do
[ -e "$c" ] || continue
out="${c##*/}"; out="${out#card*-}"
want="$(awk -v o="$out" '$1 == o { print $2 }' "$MAP")"
[ -n "$want" ] && [ -f "$FW/steamify-$out.bin" ] || continue
new=reset
if [ "$(cat "$c/status")" = connected ]; then
bus="$(basename "$(readlink -f "$c/ddc")")"
for i in 1 2 3 4 5; do
# Bytes 8-17 of block 0: manufacturer, product, serial, date.
id="$(i2ctransfer -y "${bus#i2c-}" w1@0x50 0 r18@0x50 2>/dev/null | tr -d ' ' | sed 's/0x//g')"
[ ${#id} -eq 36 ] && break
sleep 1
done
[ "${id:16}" = "$want" ] && new=override
fi
[ "$(cat "$RUN/$out" 2>/dev/null || echo reset)" = "$new" ] && continue
for d in /sys/kernel/debug/dri/*/"$out"; do [ -e "$d/edid_override" ] && break; done
[ -e "$d/edid_override" ] || continue
if [ "$new" = override ]; then cat "$FW/steamify-$out.bin" > "$d/edid_override"
else printf reset > "$d/edid_override"; fi
# Before the hotplug below, whose own event runs this again.
echo "$new" > "$RUN/$out"
echo 1 > "$d/trigger_hotplug"
done
exit 0
EOF
sudo chmod 755 "$HDMI_HOTPLUG"
# StartLimitIntervalSec=0: a burst of hotplugs must not get it rate-limited.
sudo tee "$HDMI_UNIT" > /dev/null << EOF
[Unit]
Description=Steamify CachyOS: HDMI refresh boost EDID for the connected display
StartLimitIntervalSec=0
Wants=sys-kernel-debug.mount
After=sys-kernel-debug.mount
Before=display-manager.service sddm.service plasmalogin.service
[Service]
Type=oneshot
ExecStart=$HDMI_HOTPLUG
[Install]
WantedBy=graphical.target
EOF
# restart, not start: a hotplug during a run must still be looked at.
printf '%s\n' '# Written by steamify: HDMI refresh boost, check the display at every hotplug.' \
"ACTION==\"change\", SUBSYSTEM==\"drm\", ENV{HOTPLUG}==\"1\", RUN+=\"/usr/bin/systemctl --no-block restart $HDMI_UNIT_NAME\"" |
sudo tee "$HDMI_UDEV_RULE" >/dev/null || return 1
sudo systemctl daemon-reload
sudo udevadm control --reload
sudo systemctl enable "$HDMI_UNIT_NAME" >/dev/null 2>&1 || { err "Enabling $HDMI_UNIT_NAME failed."; return 1; }
sudo systemctl restart "$HDMI_UNIT_NAME"
}
hdmi_tune() {
# hdmi_tune <card-connector> <tmpdir>: find and confirm the highest rate
# at the desktop resolution. Sets HDMI_RESULT to "<w> <h> <hz>..." (the
@@ -499,6 +584,12 @@ hdmi_enable() {
[[ -f "$HDMI_FW_DIR/steamify-${conn#card*-}.bin" ]] || continue
[[ "$(state_get hdmi "${conn#card*-}")" == "$(hdmi_edid_tool id "/sys/class/drm/$conn/edid")" ]] || stale=true
done
if [[ "$stale" == false && -n "$(hdmi_cmdline_param)" ]]; then
info "Moving the EDID override off the kernel command line, so it only applies to this display..."
hdmi_remove_boot_param && hdmi_install_hotplug || { err "Moving it failed."; return 1; }
ok "HDMI refresh boost now only applies to the display it was set up for."
return 0
fi
if [[ "$stale" == false ]]; then
ok "Already set up for the connected display(s). Untick and tick it again to re-test."
return 0
@@ -508,7 +599,7 @@ hdmi_enable() {
fi
sudo pacman -S --needed --noconfirm i2c-tools python >/dev/null 2>&1 || { err "Installing i2c-tools failed."; return 1; }
local tmp conn out param="" files=() rc=0 res
local tmp conn out files=() rc=0 res
tmp="$(mktemp -d)"
if [[ ${#HDMI_CHOICE[@]} -gt 0 ]]; then
# Picked and tested in the app.
@@ -524,35 +615,29 @@ hdmi_enable() {
sudo install -Dm644 "$tmp/$out.final" "$HDMI_FW_DIR/steamify-$out.bin" || { rc=1; continue; }
state_set hdmi "$out" "$(hdmi_edid_tool id "$tmp/$out.orig")"
state_set hdmi "$out-mode" "${res#*|}"
param+="${param:+,}$out:edid/steamify-$out.bin"
files+=("$HDMI_FW_DIR/steamify-$out.bin")
done
rm -rf "$tmp" "$HDMI_CACHE"
[[ -n "$param" ]] || return $rc
# amdgpu loads from the initramfs (the kms hook), before the root
# filesystem: the EDID file has to be in there too.
sudo mkdir -p "$(dirname "$HDMI_INITRAMFS_CONF")"
printf '# Written by steamify: EDID override for HDMI refresh boost.\nFILES+=(%s)\n' "${files[*]}" |
sudo tee "$HDMI_INITRAMFS_CONF" >/dev/null
if ! hdmi_boot_param "drm.edid_firmware=$param"; then
[[ ${#files[@]} -gt 0 ]] || return $rc
if ! hdmi_install_hotplug; then
# Nothing half-done left behind; the live EDID lasts until a restart.
sudo rm -f "$HDMI_INITRAMFS_CONF" "${files[@]}"
for conn in $(hdmi_connectors); do hdmi_override_live "$conn" reset 2>/dev/null; done
state_clear hdmi
hdmi_disable >/dev/null 2>&1
err "Making it permanent failed; the display is back on its own EDID."
return 1
fi
ok "HDMI refresh boost is set up; it's active now and after every restart."
ok "HDMI refresh boost is set up for this display; another display gets its own settings."
return $rc
}
hdmi_disable() {
local f conn
# Drop-in and files first: the rebuild below leaves them out.
sudo rm -f "$HDMI_INITRAMFS_CONF"
sudo systemctl disable "$HDMI_UNIT_NAME" >/dev/null 2>&1
sudo rm -f "$HDMI_UDEV_RULE" "$HDMI_UNIT" "$HDMI_HOTPLUG" "$HDMI_MAP"
sudo rm -rf /run/steamify-edid
sudo systemctl daemon-reload
sudo udevadm control --reload
for f in "$HDMI_FW_DIR"/steamify-*.bin; do [[ -e "$f" ]] && sudo rm -f "$f"; done
hdmi_boot_param "" || return 1
hdmi_remove_boot_param || return 1
for conn in $(hdmi_connectors); do hdmi_override_live "$conn" reset 2>/dev/null; done
state_clear hdmi
ok "HDMI refresh boost removed; the display uses its own EDID again."
+1 -1
View File
@@ -15,7 +15,7 @@
set -uo pipefail
# Release version, see CHANGELOG.md.
VERSION=2.0.3
VERSION=2.1.0
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"