Fix intermittent suspend hang on T2 MacBooks (force synchronous device suspend)#6149
Fix intermittent suspend hang on T2 MacBooks (force synchronous device suspend)#6149andyhoops wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses intermittent hard hangs during suspend on T2 MacBooks by forcing synchronous device suspend (setting /sys/power/pm_async=0) via a boot-time systemd oneshot service, and ensures existing installations receive the fix via a migration.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Changes:
- Add a T2-gated hardware script that installs/enables a systemd oneshot service to set
pm_async=0at boot. - Wire the new script into the standard install/config stage.
- Add a migration to apply the same fix to existing installs on update.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
migrations/1782742549.sh |
Runs the new Apple T2 suspend fix for existing installs via migration. |
install/config/hardware/apple/fix-suspend-async.sh |
New T2-detection script that installs/enables a oneshot systemd unit to set pm_async=0. |
install/config/all.sh |
Invokes the new Apple suspend async fix during install/config. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Omarchy migration — apply the T2 synchronous device-suspend fix to existing | ||
| # installs. At submission time, RENAME this file to a fresh unix timestamp that | ||
| # is greater than the newest existing migration, e.g.: | ||
| # | ||
| # mv migration.sh "migrations/$(date +%s).sh" | ||
| # | ||
| # (Verify it sorts after the current latest in migrations/.) | ||
|
|
||
| echo "Fix intermittent suspend hang on T2 MacBooks (force synchronous device suspend)" | ||
|
|
||
| source $OMARCHY_PATH/install/config/hardware/apple/fix-suspend-async.sh |
There was a problem hiding this comment.
Done in 0fe6d3f — migration now leads with the echo like the others
| cat <<EOF | sudo tee /etc/systemd/system/omarchy-pm-async-suspend-fix.service >/dev/null | ||
| [Unit] | ||
| Description=Omarchy synchronous device-suspend fix for T2 MacBook | ||
|
|
||
| [Service] | ||
| Type=oneshot | ||
| ExecStart=/bin/bash -c 'echo 0 > /sys/power/pm_async' | ||
|
|
||
| [Install] | ||
| WantedBy=multi-user.target | ||
| EOF |
There was a problem hiding this comment.
Added ConditionPathExists=/sys/power/pm_async in 0fe6d3f, thanks.
| WantedBy=multi-user.target | ||
| EOF | ||
|
|
||
| chrootable_systemctl_enable omarchy-pm-async-suspend-fix.service |
There was a problem hiding this comment.
This script is also sourced by the migration, but chrootable_systemctl_enable is only defined when the installer loads install/helpers/all.sh. omarchy-migrate runs each migration in a fresh shell, so the function is unavailable on existing installations.
On a T2 Mac, this produces chrootable_systemctl_enable: command not found. The following systemctl daemon-reload can still succeed, causing the migration to exit successfully and be marked complete even though the service was never enabled or started.
Could you make this script self-contained for the migration path, or explicitly load the helper and ensure an enable failure propagates as a non-zero exit?
There was a problem hiding this comment.
Good catch, thank you — that's a real hole and it would have hit every existing
T2 install on omarchy update, silently. Fixed in 1faf6a0.
I went with your second option, loading the helper rather than inlining the
chroot branch, so the chroot logic stays in one place:
# The installer defines this helper via install/helpers/all.sh, but migrations
# run in a fresh shell, so pull it in directly when it isn't already defined.
# (OMARCHY_INSTALL is installer-only; OMARCHY_PATH is in the user environment.)
declare -F chrootable_systemctl_enable >/dev/null ||
source "$OMARCHY_PATH/install/helpers/chroot.sh"
if ! chrootable_systemctl_enable omarchy-pm-async-suspend-fix.service; then
echo "Failed to enable omarchy-pm-async-suspend-fix.service" >&2
exit 1
fi
sudo systemctl daemon-reloadTwo notes on the details:
- It sources via
$OMARCHY_PATH/install/helpers/chroot.sh, not
$OMARCHY_INSTALL.OMARCHY_INSTALLis exported only byinstall.sh, so on
the migration path it's empty — using it would have reintroduced the same class
of bug one level down. - The
declare -Fguard means the installer path is untouched:chroot.shdoes
export -f, so the function is already present inrun_logged's subshell and
nothing is re-sourced.
I verified both call paths with stubbed lspci/sudo/systemctl — bash $file
for the migration and bash -c "source $script" to match run_logged:
| case | before | after |
|---|---|---|
| migration, enable succeeds | (never enabled) | exit 0, enabled |
| migration, enable fails | exit 0, marked complete | exit 1, migration reports failure |
| installer, helper present, enable fails | exit 0 | exit 1 |
installer, OMARCHY_CHROOT_INSTALL=1 |
enable (no --now) |
unchanged |
| non-T2 hardware | no-op | no-op |
However, please note that I had to revert this machine to macOS — external
displays, BLE Bluetooth and the Thunderbolt dock were all broken under T2 Linux
in ways I couldn't fix at the config or distro level. The original
5/5-clean-vs-5/5-hang result on the MacBookPro16,2 still stands for the installer
path, but I can't re-test on the hardware now, so this last change is verified by
code reading and the simulation above rather than on a live T2 Mac. If you'd
rather have a hardware confirmation of the migration path before merging, the
co-tester call is still open on #1840 and I'm happy to wait on it.
Unrelated to this PR, but noticed while tracing it: the existing
install/config/hardware/apple/fix-suspend-nvme.sh has the same latent issue —
it calls chrootable_systemctl_enable and would break identically if it were
ever sourced from a migration. No migration does today, so it's harmless, but
happy to send a follow-up applying the same guard there if you want it.
chrootable_systemctl_enable is only defined once the installer has sourced install/helpers/all.sh. omarchy-migrate runs each migration with a fresh 'bash $file', so on existing installs the call failed with 'command not found' and the following daemon-reload still exited 0 - the migration was marked complete without the service ever being enabled. Source install/helpers/chroot.sh when the helper isn't already defined (via OMARCHY_PATH, since OMARCHY_INSTALL is installer-only), and exit non-zero if the enable fails so omarchy-migrate reports it.
Refs: #1840
Problem
On T2 MacBooks,
systemctl suspend/ lid-close intermittently hard-hangs themachine the instant it enters suspend — black screen, dead keyboard/trackpad/
Touch Bar, no wake; only a forced power-off recovers. This is the core of the
widely-reported #1840 across many T2 models (13"/15"/16", 2018–2020).
Root cause
An asynchronous device-suspend race. The kernel default
(
/sys/power/pm_async=1) suspends devices in parallel; on T2 hardware thisraces and deadlocks.
pm_tracelocalises the hang to the async suspend core(
drivers/base/power/main.c), not to any single driver — which is whyunloading individual drivers (brcmfmac, apple-bce) and PCI D3cold tweaks don't
help. Forcing synchronous device suspend (
pm_async=0) eliminates the race.The existing
install/config/hardware/apple/fix-suspend-nvme.shdoes not coverthese machines: it excludes 16,x/15,x models and targets a discrete NVMe at PCI
0000:01:00.0, whereas 16,2-class storage is the T2-bridged ANS2 controller ate6:00.0. This fix is independent of that one.Fix
Add
install/config/hardware/apple/fix-suspend-async.sh: on T2 Macs(
lspci 106b:180[12]), install + enable a oneshot service that writes0to/sys/power/pm_asyncat boot. Mirrors the structure of the existingfix-suspend-nvme.sh. No change to mem_sleep / logind.conf / sleep.conf /bootloader cmdline is required.
Testing
linux-t27.0.12: 5/5 clean suspend/resumecycles with the fix vs 5/5 hard hangs without (same
deepmode). Lidclose/reopen confirmed. Persists across reboot.
no-op on hardware that wasn't racing.
Changes
install/config/hardware/apple/fix-suspend-async.sh(new)install/config/all.sh— add one line after thefix-suspend-nvme.shentry:run_logged $OMARCHY_INSTALL/config/hardware/apple/fix-suspend-async.shmigrations/<unix-timestamp>.sh(new) — sources the script so existinginstalls get it on
omarchy update.Note for reviewers
Gated to T2 Macs (where it's proven).
pm_async=0is harmless on non-racinghardware, so the gate could be widened to all Apple MacBooks (
#1840includes acouple of non-T2 reports) if preferred. Co-testers from #1840 on 13"/15" models
would help confirm breadth.