Diagram

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:

Diagram

Any one of these five stages can fail independently:

  1. Config missing — you clone a fresh dotfiles repo and the drop-in doesn’t come with it
  2. Boot entry stalelimine-update wasn’t run after editing the config, so /boot/limine.conf still has the old value (or no value)
  3. Wrong kernel param — a typo, or a different kernel tree that changed the parameter
  4. amdgpu not loaded — you switched to a kernel without amdgpu built, or the module blacklisted
  5. DMCUB errors persist — the firmware version changed in a linux-firmware update 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

Diagram

I use fish as my daily shell. Writing a fish script means:

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

Diagram

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.