I Wrote a Fish Script to Check If My Laptop Will Survive the Next Suspend
After fixing my
laptop’s second-suspend freeze with
amdgpu.dcdebugmask=0x600, I had a lingering paranoia: what
happens after the next kernel update? What if the Limine drop-in gets
clobbered? What if I’m on a fresh install and forget to apply the
fix?
The answer is: I’d find out the hard way — black screen, hard reset, lost work. So I wrote a script.
The five things that must be true
The dcdebugmask fix works because a specific bitmask
reaches the amdgpu kernel module at boot time. That chain has multiple
failure points:
Any one of these five stages can fail independently:
- Config missing — you clone a fresh dotfiles repo and the drop-in doesn’t come with it
- Boot entry stale —
limine-updatewasn’t run after editing the config, so/boot/limine.confstill has the old value (or no value) - Wrong kernel param — a typo, or a different kernel tree that changed the parameter
- amdgpu not loaded — you switched to a kernel without amdgpu built, or the module blacklisted
- DMCUB errors persist — the firmware version changed
in a
linux-firmwareupdate and the mask no longer works
The script checks all five.
The script
#!/usr/bin/env fish
# check-suspend-fix — Verify amdgpu dcdebugmask=0x600 is active
set -l errors 0
set -l warnings 0
# [1/5] Kernel command line
if grep -q 'dcdebugmask=0x600' /proc/cmdline
echo " ✅ dcdebugmask=0x600 is ACTIVE in running kernel"
else
echo " ❌ dcdebugmask NOT PRESENT or wrong value"
set errors (math $errors + 1)
end
# [2/5] Limine drop-in config
set -l conf "/etc/limine-entry-tool.d/amdgpu-psr.conf"
if test -f "$conf" && grep -q 'dcdebugmask=0x600' "$conf"
echo " ✅ Drop-in config exists and correct"
else
echo " ❌ Config file MISSING or wrong"
set errors (math $errors + 1)
end
# [3/5] DMCUB firmware errors
set -l dmcub (sudo journalctl -b 0 -k --no-pager | grep -ci 'dmcub')
if test $dmcub -eq 0
echo " ✅ Zero DMCUB errors in kernel log"
else
echo " ⚠️ $dmcub DMCUB error(s) found"
set warnings (math $warnings + 1)
end
# [4/5] Suspend cycle count
set -l suspends (journalctl -b 0 --no-pager | grep -c 'PM: suspend entry')
if test $suspends -ge 2
echo " ✅ At least 2 suspend cycles — fix confirmed"
else
echo " ⚠️ <2 cycles — can't confirm second-suspend fix yet"
end
# [5/5] amdgpu module health
if lsmod | grep -q '^amdgpu'
echo " ✅ amdgpu module loaded"
else
echo " ❌ amdgpu NOT loaded"
set errors (math $errors + 1)
end
The full version is 100 lines including the summary banner, re-apply instructions on failure, and exit codes. You can read it on GitHub.
The output
Running it on a healthy system:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔬 AMDGPU Suspend Fix Health Check
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1/5] Kernel command line
✅ dcdebugmask=0x600 is ACTIVE in running kernel
[2/5] Limine drop-in config
✅ /etc/limine-entry-tool.d/amdgpu-psr.conf exists and contains 0x600
[3/5] DMCUB firmware errors (this boot)
✅ Zero DMCUB errors in kernel log
[4/5] Suspend/resume cycles this boot
ℹ️ No suspend cycles yet this boot
[5/5] amdgpu module health
✅ amdgpu module loaded
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ ALL CHECKS PASSED — suspend fix is healthy
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
And if the fix is missing — for instance, after a kernel update where
limine-update wasn’t re-run:
[1/5] Kernel command line
❌ Wrong value: dcdebugmask=0x10 (expected 0x600)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
❌ 1 error(s) — fix may not be applied
To re-apply:
echo 'KERNEL_CMDLINE[default]+=amdgpu.dcdebugmask=0x600' | sudo tee /etc/limine-entry-tool.d/amdgpu-psr.conf
sudo limine-update
sudo reboot
Why fish instead of bash
I use fish as my daily shell. Writing a fish script means:
- Local variables with
set -l— no accidental leakage to the parent shell - Integer arithmetic with
math— not$(( ))with its quoting traps - Clean conditionals with
test— no[vs[[confusion - No POSIX baggage — fish deliberately breaks compatibility for sanity
The trade-off is that fish scripts aren’t portable to systems without fish installed. Since this script only runs on my personal laptop (which has fish as the default shell), that’s fine.
Where it lives
~/.local/bin/check-suspend-fix # the script (in PATH)
~/.local/bin is in my PATH, so I can run it from
anywhere:
check-suspend-fix # verify the fix is active
Exit codes make it scriptable:
check-suspend-fix && echo "healthy" || echo "needs attention"
Exit 0 means all checks passed or only warnings (fix is active). Exit 1 means errors (fix is broken — re-apply needed).
What this pattern teaches
The pattern generalizes: for any system state you depend on, write a script that verifies it. Kernel parameters, service status, disk encryption, firewall rules, DNS configuration — if it can drift, check it.
This is what SRE teams call a “health check” or “synthetic test.” The difference is that I’m running it on a single laptop, not a Kubernetes cluster. Same principle, smaller blast radius.
Install it
# Download
curl -o ~/.local/bin/check-suspend-fix \
https://raw.githubusercontent.com/nurazhardotcom/dotfiles/main/.local/bin/check-suspend-fix
chmod +x ~/.local/bin/check-suspend-fix
# Run it
check-suspend-fix
If you don’t have fish, the logic ports to bash in about 20 lines —
grep /proc/cmdline, check a config file, scan
journalctl, count suspend cycles, check lsmod.
The checks are the important part, not the shell.
Written on a Ryzen 7 7730U running CachyOS with
amdgpu.dcdebugmask=0x600 confirmed active by this exact
script. Fish 4.8.1, linux-cachyos-lts 6.18.42.