diff --git a/bin/omarchy-hw-nvidia b/bin/omarchy-hw-nvidia old mode 100644 new mode 100755 index b50ca6e4..7d180593 --- a/bin/omarchy-hw-nvidia +++ b/bin/omarchy-hw-nvidia @@ -2,4 +2,15 @@ # omarchy:summary=Detect whether the computer has an NVIDIA GPU. -lspci | grep -qi 'nvidia' &>/dev/null +# Read the cached sysfs IDs rather than lspci, which reads PCI config space and +# resumes runtime-suspended GPUs. +pci_devices_path="${OMARCHY_PCI_DEVICES_PATH:-/sys/bus/pci/devices}" + +shopt -s nullglob + +for device in "$pci_devices_path"/*; do + [[ $(< "$device/vendor") == "0x10de" ]] || continue + [[ $(< "$device/class") == 0x03* ]] && exit 0 +done + +exit 1 diff --git a/bin/omarchy-hw-nvidia-gsp b/bin/omarchy-hw-nvidia-gsp index 06af2a3e..d3c584f9 100755 --- a/bin/omarchy-hw-nvidia-gsp +++ b/bin/omarchy-hw-nvidia-gsp @@ -2,5 +2,19 @@ # omarchy:summary=Detect whether the computer has an NVIDIA GPU with GSP firmware (Turing or newer). -# GTX 16xx, RTX 20xx-50xx, RTX Pro, Quadro RTX, datacenter A/H/T/L series. -lspci | grep -i 'nvidia' | grep -qE "GTX 16[0-9]{2}|RTX [2-5][0-9]{3}|RTX PRO [0-9]{4}|Quadro RTX|RTX A[0-9]{4}|A[1-9][0-9]{2}|H[1-9][0-9]{2}|T4|L[0-9]+" +# Turing is the first generation with GSP firmware, and the first to use device +# IDs at 0x1e00 or above; Maxwell, Pascal, and Volta all sit below that line. +# +# Read the cached sysfs IDs rather than lspci, which reads PCI config space and +# resumes runtime-suspended GPUs. +pci_devices_path="${OMARCHY_PCI_DEVICES_PATH:-/sys/bus/pci/devices}" + +shopt -s nullglob + +for device in "$pci_devices_path"/*; do + [[ $(< "$device/vendor") == "0x10de" ]] || continue + [[ $(< "$device/class") == 0x03* ]] || continue + (( $(< "$device/device") >= 0x1e00 )) && exit 0 +done + +exit 1 diff --git a/bin/omarchy-hw-nvidia-without-gsp b/bin/omarchy-hw-nvidia-without-gsp index 177e60b6..f951ebf1 100755 --- a/bin/omarchy-hw-nvidia-without-gsp +++ b/bin/omarchy-hw-nvidia-without-gsp @@ -2,5 +2,22 @@ # omarchy:summary=Detect whether the computer has an NVIDIA GPU without GSP firmware (Maxwell/Pascal/Volta). -# GTX 9xx/10xx, GT 10xx, Quadro P/M/GV, MX series, Titan X/Xp/V, Tesla V100. -lspci | grep -i 'nvidia' | grep -qE "GTX (9[0-9]{2}|10[0-9]{2})|GT 10[0-9]{2}|Quadro [PM][0-9]{3,4}|Quadro GV100|MX *[0-9]+|Titan (X|Xp|V)|Tesla V100" +# Bounded at both ends, because the callers install the 580xx driver on a match +# and it supports exactly this span. GSP firmware arrived with Turing, which is +# also where device IDs cross 0x1e00. Maxwell opens at 0x1340, one ID after the +# last Kepler part; anything older needs a legacy driver we don't package. +# +# Read the cached sysfs IDs rather than lspci, which reads PCI config space and +# resumes runtime-suspended GPUs. +pci_devices_path="${OMARCHY_PCI_DEVICES_PATH:-/sys/bus/pci/devices}" + +shopt -s nullglob + +for device in "$pci_devices_path"/*; do + [[ $(< "$device/vendor") == "0x10de" ]] || continue + [[ $(< "$device/class") == 0x03* ]] || continue + device_id=$(< "$device/device") + (( device_id >= 0x1340 && device_id < 0x1e00 )) && exit 0 +done + +exit 1 diff --git a/default/hypr/nvidia.lua b/default/hypr/nvidia.lua index 3f6c8daf..663fe79e 100644 --- a/default/hypr/nvidia.lua +++ b/default/hypr/nvidia.lua @@ -1,9 +1,13 @@ local paths = require("default.hypr.paths") +local nvidia = paths.omarchy_path .. "/bin/omarchy-hw-nvidia" local nvidia_gsp = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-gsp" local nvidia_without_gsp = paths.omarchy_path .. "/bin/omarchy-hw-nvidia-without-gsp" -if o.shell_succeeds("lspci | grep -qi nvidia") then +-- These detectors read cached sysfs IDs rather than shelling out to lspci. +-- lspci reads PCI config space, which resumes a runtime-suspended GPU, and on a +-- hybrid laptop that wake alone outlasts Hyprland's 1.5s config reload budget. +if o.shell_succeeds(o.shell_quote(nvidia)) then if o.shell_succeeds(o.shell_quote(nvidia_gsp)) then hl.env("NVD_BACKEND", "direct") hl.env("LIBVA_DRIVER_NAME", "nvidia") diff --git a/test/shell.d/hw-nvidia-test.sh b/test/shell.d/hw-nvidia-test.sh new file mode 100755 index 00000000..da461ce4 --- /dev/null +++ b/test/shell.d/hw-nvidia-test.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +tmp_dir=$(mktemp -d) +trap 'rm -rf "$tmp_dir"' EXIT + +# Each argument is a PCI device as "vendor:device:class", in sysfs's own format. +write_pci_devices() { + rm -rf "$tmp_dir/devices" + mkdir -p "$tmp_dir/devices" + + local index=0 + local spec + for spec in "$@"; do + local slot + slot=$(printf '0000:%02x:00.0' "$index") + mkdir -p "$tmp_dir/devices/$slot" + printf '%s\n' "${spec%%:*}" >"$tmp_dir/devices/$slot/vendor" + printf '%s\n' "$(cut -d: -f2 <<<"$spec")" >"$tmp_dir/devices/$slot/device" + printf '%s\n' "${spec##*:}" >"$tmp_dir/devices/$slot/class" + index=$((index + 1)) + done +} + +hw_nvidia() { + OMARCHY_PCI_DEVICES_PATH="$tmp_dir/devices" "$ROOT/bin/omarchy-hw-$1" +} + +assert_detects() { + local description="$1" nvidia="$2" gsp="$3" without_gsp="$4" + + local command + for command in nvidia gsp without-gsp; do + local expected + case $command in + nvidia) expected=$nvidia ;; + gsp) expected=$gsp ;; + without-gsp) expected=$without_gsp ;; + esac + + local detector=nvidia + [[ $command == "nvidia" ]] || detector="nvidia-$command" + + local actual=no + hw_nvidia "$detector" && actual=yes + + [[ $actual == "$expected" ]] || + fail "$description" "omarchy-hw-$detector: expected $expected, got $actual" + done + + pass "$description" +} + +# AMD Cezanne integrated graphics. +write_pci_devices 0x1002:0x15e7:0x030000 +assert_detects "a machine without an NVIDIA GPU detects nothing" no no no + +# NVIDIA GA106M [RTX 3060 Mobile] alongside AMD Cezanne, the pair from issue #6660. +write_pci_devices 0x1002:0x15e7:0x030000 0x10de:0x2560:0x030200 +assert_detects "a hybrid Ampere laptop detects a GSP GPU" yes yes no + +# NVIDIA TU117M [GTX 1650 Mobile], the first generation with GSP firmware. +write_pci_devices 0x10de:0x1f91:0x030000 +assert_detects "Turing is the oldest generation with GSP firmware" yes yes no + +# NVIDIA GV100 [TITAN V], the newest generation without GSP firmware. +write_pci_devices 0x10de:0x1d81:0x030000 +assert_detects "Volta is the newest generation without GSP firmware" yes no yes + +# NVIDIA GP104 [GTX 1080]. +write_pci_devices 0x10de:0x1b80:0x030000 +assert_detects "Pascal detects a GPU without GSP firmware" yes no yes + +# NVIDIA GM108M [GeForce 830M], the oldest part the 580xx driver supports. +write_pci_devices 0x10de:0x1340:0x030000 +assert_detects "Maxwell detects a GPU without GSP firmware" yes no yes + +# NVIDIA GK110 [GTX 780]. Kepler predates GSP but also predates 580xx, so +# claiming it here would install a driver that cannot drive it. +write_pci_devices 0x10de:0x1004:0x030000 +assert_detects "Kepler is too old for either driver" yes no no + +# NVIDIA GF100 [GTX 470], older still. +write_pci_devices 0x10de:0x06cd:0x030000 +assert_detects "Fermi is too old for either driver" yes no no + +# NVIDIA GB203 [RTX 5080], newer than every other device ID here. +write_pci_devices 0x10de:0x2c02:0x030000 +assert_detects "Blackwell detects a GSP GPU" yes yes no + +# The GA106 audio function carries the NVIDIA vendor ID but is not a GPU. +write_pci_devices 0x10de:0x228e:0x040300 +assert_detects "a non-display NVIDIA function is not a GPU" no no no + +write_pci_devices +assert_detects "a machine with no PCI devices detects nothing" no no no