From a9391c90f07a564693bb1994f4f8fe6470efb3a2 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Fri, 24 Jul 2026 23:16:56 -0500 Subject: fix(installer): harden the lock path against the AMD-iGPU DPMS lockout An idle lock on this Strix Halo box wedged the whole session: hyprlock died and the compositor stayed locked with no prompt, recoverable only from a console. It's a documented AMD-integrated-Radeon failure (hyprlock#953, Hyprland#5822) -- a display power cycle via DPMS invalidates the GPU resources the lock client holds, so hyprlock loses its surface and exits without unlocking. No coredump, no OOM; the GPU pulls the rug out. Two installer changes, both scoped and tested: update_grub_cmdline adds amdgpu.runpm=0 on AMD machines only. Disabling GPU runtime power management keeps those resources valid across a display cycle -- the root fix. A no-op on Intel/NVIDIA, and it rides the existing merge so no boot-critical token is touched. configure_hyprlock_pam writes a complete PAM stack. The hyprlock package ships only `auth include login`, leaving account and session uninitialised so pam_end() crashes on cleanup -- a separate documented lockout cause. All three phases now resolve through login, inheriting the keyring the graphical login uses. CALL_SITES pins both new wirings. 372 unit tests, exit 0; each addition proven by reverting it. --- tests/installer-steps/test_grub_cmdline.py | 42 ++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'tests/installer-steps/test_grub_cmdline.py') diff --git a/tests/installer-steps/test_grub_cmdline.py b/tests/installer-steps/test_grub_cmdline.py index 8c0b5b0..4c2d202 100644 --- a/tests/installer-steps/test_grub_cmdline.py +++ b/tests/installer-steps/test_grub_cmdline.py @@ -67,6 +67,48 @@ class MergeGrubCmdline(unittest.TestCase): self.assertIn("zfs=zroot/ROOT/default", out) +def run_with_gpu(body, gpu): + # detect_gpu_vendors is stubbed to a chosen vendor list so the AMD-only + # amdgpu.runpm=0 addition can be exercised without real /sys reads. + stub = 'detect_gpu_vendors() { printf "%s\\n" %s; }\n' % ("%s", "'" + gpu + "'") + stub = 'detect_gpu_vendors() { printf "%s" "$GPU"; [ -n "$GPU" ] && echo; }\n' + script = ('logfile=/dev/null\nerror_warn() { echo "WARN: $1"; return 1; }\n' + + stub + EXTRACT + "\n" + body + "\n") + return subprocess.run(["bash", "-c", script], capture_output=True, text=True, + timeout=10, env={**os.environ, "GPU": gpu}) + + +class AmdRunpmCmdline(unittest.TestCase): + """amdgpu.runpm=0 is added on AMD machines only: the AMD iGPU invalidates + hyprlock's GPU resources on a display power cycle, wedging the lock + (hyprlock#953). Disabling GPU runtime PM keeps them valid. A no-op on Intel + or NVIDIA.""" + + def _file(self, gpu): + import tempfile + fd, path = tempfile.mkstemp(prefix="grub-runpm-") + os.close(fd) + with open(path, "w") as f: + f.write('GRUB_CMDLINE_LINUX_DEFAULT="rw quiet"\n') + self.addCleanup(os.remove, path) + run_with_gpu(f'update_grub_cmdline "{path}"', gpu) + with open(path) as f: + return f.read() + + def test_amd_gets_runpm_disabled(self): + self.assertIn("amdgpu.runpm=0", self._file("amd")) + + def test_intel_does_not(self): + self.assertNotIn("amdgpu.runpm", self._file("intel")) + + def test_no_gpu_detected_does_not(self): + self.assertNotIn("amdgpu.runpm", self._file("")) + + def test_amd_with_nvidia_still_gets_it(self): + # A hybrid box with an AMD part present still wants the fix. + self.assertIn("amdgpu.runpm=0", self._file("amd\nnvidia")) + + class UpdateGrubCmdline(unittest.TestCase): def run_update(self, grub_body): with tempfile.TemporaryDirectory() as d: -- cgit v1.2.3