diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-24 23:16:56 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-24 23:16:56 -0500 |
| commit | a9391c90f07a564693bb1994f4f8fe6470efb3a2 (patch) | |
| tree | 73ac6d588491314615ef50dbffc8fdd07842019d /tests/installer-steps/test_grub_cmdline.py | |
| parent | 082d870d6c4c3a3cce85506505582b17ef19487b (diff) | |
| download | archsetup-a9391c90f07a564693bb1994f4f8fe6470efb3a2.tar.gz archsetup-a9391c90f07a564693bb1994f4f8fe6470efb3a2.zip | |
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.
Diffstat (limited to 'tests/installer-steps/test_grub_cmdline.py')
| -rw-r--r-- | tests/installer-steps/test_grub_cmdline.py | 42 |
1 files changed, 42 insertions, 0 deletions
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: |
