Skip to content

Fix suspend/sleep on T2 MacBooks#5129

Open
jzimdars wants to merge 2 commits intobasecamp:masterfrom
jzimdars:fix-t2-suspend
Open

Fix suspend/sleep on T2 MacBooks#5129
jzimdars wants to merge 2 commits intobasecamp:masterfrom
jzimdars:fix-t2-suspend

Conversation

@jzimdars
Copy link
Copy Markdown
Member

Summary

Fixes #1840

T2 MacBooks crash during suspend/resume due to three issues:

  • Default S3 (deep) sleep mode is not supported by T2 hardware — causes immediate crash/reboot on lid close
  • brcmfmac WiFi driver fails to enter D3 power state, causing an I/O error loop that retries every ~26 seconds until the system crashes
  • NVMe and T2 bridge PCI devices entering d3cold power state causes crashes during longer sleep periods (>20 min)

This PR adds to the T2 Mac installer (fix-apple-t2.sh) and includes a migration for existing installations:

  • Force s2idle (freeze) as the suspend mode via sleep.conf.d drop-in and mem_sleep_default=s2idle kernel parameter
  • Create omarchy-suspend-t2.service that runs Before=sleep.target to:
    • Dynamically discover and disable d3cold on Apple NVMe (106b:2005) and T2 Bridge (106b:1801) PCI devices
    • Unload brcmfmac and apple-bce modules before suspend
    • Reload modules on wake (ExecStop)
  • Migration for existing T2 Mac users, with guards against double-application and cleanup of any user-created suspend-t2.service

The d3cold fix uses dynamic PCI device discovery rather than hardcoded bus addresses, so it works across all T2 Mac models.

Validated on MacBookAir9,1 with linux-t2 6.17.7 — tested short (2 min) and long (82 min) suspend/resume cycles successfully.

Test plan

  • Fresh install on a T2 MacBook reaches working suspend/resume
  • Migration on existing T2 Mac applies all fixes correctly
  • Short suspend/resume cycle (close lid, reopen after 1-2 min)
  • Long suspend/resume cycle (close lid, reopen after 1+ hour)
  • WiFi reconnects after resume
  • Keyboard/trackpad work after resume
  • Non-T2 Macs are unaffected (migration exits early, installer block skipped)

🤖 Generated with Claude Code

T2 MacBooks crash during suspend because the hardware doesn't support
S3 (deep) sleep, the brcmfmac WiFi driver fails to enter D3 power
state, and NVMe/T2 bridge devices crash when entering d3cold during
longer sleep periods.

This adds three fixes to the T2 Mac installer and a migration for
existing installations:

- Force s2idle (freeze) as the suspend mode via sleep.conf and kernel
  cmdline parameter mem_sleep_default=s2idle
- Create a systemd service that dynamically disables d3cold on Apple
  NVMe and T2 Bridge PCI devices before suspend
- Unload brcmfmac and apple-bce modules before suspend and reload
  them on wake to prevent driver crashes

The d3cold fix uses dynamic PCI device discovery (vendor 106b, devices
2005/1801) rather than hardcoded bus addresses so it works across all
T2 Mac models.

Fixes basecamp#1840

Validated on MacBookAir9,1 with linux-t2 6.17.7.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 27, 2026 13:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses suspend/resume instability on Apple T2 MacBooks by forcing s2idle-style sleep behavior and adding a systemd hook to adjust PCI power states and reload problematic kernel modules around sleep, plus a migration to apply the fix to existing installs.

Changes:

  • Add a migration to apply T2 sleep/suspend fixes on existing systems and remove legacy suspend-t2.service.
  • Update the T2 hardware installer script to force mem_sleep_default=s2idle, create a sleep.conf.d drop-in, and install/enable a new omarchy-suspend-t2.service.
  • Introduce a suspend hook service that disables d3cold for specific Apple PCI devices and unloads/reloads brcmfmac and apple-bce around sleep.

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.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
migrations/1774617744.sh Adds a T2-only migration to force freeze/s2idle behavior, update Limine cmdline, install/enable the new suspend hook, and remove legacy service.
install/config/hardware/fix-apple-t2.sh Extends the T2 installer setup with s2idle kernel param, sleep.conf drop-in, and installs/enables the new suspend hook service.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread install/config/hardware/fix-apple-t2.sh Outdated
User=root
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -c 'for dev in /sys/bus/pci/devices/*/; do vendor=$(cat "$dev/vendor" 2>/dev/null); device=$(cat "$dev/device" 2>/dev/null); if [[ "$vendor" == "0x106b" ]] && [[ "$device" == "0x2005" || "$device" == "0x1801" ]]; then echo 0 > "$dev/d3cold_allowed" 2>/dev/null; fi; done; rmmod brcmfmac_wcc 2>/dev/null; rmmod brcmfmac 2>/dev/null; rmmod -f apple-bce 2>/dev/null'
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unit unloads apple-bce with rmmod -f. Forced module removal can be unsafe and may leave the system without keyboard/trackpad if the module is still in use; it also makes failures harder to diagnose because errors are suppressed. Prefer unloading cleanly (e.g., stopping any dependent services first and using modprobe -r/rmmod without -f), and only proceed if removal succeeds.

Suggested change
ExecStart=/bin/bash -c 'for dev in /sys/bus/pci/devices/*/; do vendor=$(cat "$dev/vendor" 2>/dev/null); device=$(cat "$dev/device" 2>/dev/null); if [[ "$vendor" == "0x106b" ]] && [[ "$device" == "0x2005" || "$device" == "0x1801" ]]; then echo 0 > "$dev/d3cold_allowed" 2>/dev/null; fi; done; rmmod brcmfmac_wcc 2>/dev/null; rmmod brcmfmac 2>/dev/null; rmmod -f apple-bce 2>/dev/null'
ExecStart=/bin/bash -c 'for dev in /sys/bus/pci/devices/*/; do vendor=$(cat "$dev/vendor" 2>/dev/null); device=$(cat "$dev/device" 2>/dev/null); if [[ "$vendor" == "0x106b" ]] && [[ "$device" == "0x2005" || "$device" == "0x1801" ]]; then echo 0 > "$dev/d3cold_allowed" 2>/dev/null; fi; done; rmmod brcmfmac_wcc 2>/dev/null; rmmod brcmfmac 2>/dev/null; if ! modprobe -r apple-bce 2>/dev/null; then echo "omarchy-suspend-t2: failed to unload apple-bce safely; aborting suspend helper." >&2; exit 1; fi'

Copilot uses AI. Check for mistakes.
Comment thread install/config/hardware/fix-apple-t2.sh Outdated
User=root
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -c 'for dev in /sys/bus/pci/devices/*/; do vendor=$(cat "$dev/vendor" 2>/dev/null); device=$(cat "$dev/device" 2>/dev/null); if [[ "$vendor" == "0x106b" ]] && [[ "$device" == "0x2005" || "$device" == "0x1801" ]]; then echo 0 > "$dev/d3cold_allowed" 2>/dev/null; fi; done; rmmod brcmfmac_wcc 2>/dev/null; rmmod brcmfmac 2>/dev/null; rmmod -f apple-bce 2>/dev/null'
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

T2 detection elsewhere in this script matches 106b:1801 and 106b:1802, but the d3cold-disable logic only targets device 0x1801. If some T2 models expose the bridge device as 0x1802, this service won’t apply the d3cold workaround on those machines. Consider including 0x1802 (or otherwise aligning the device-ID list with the detection logic).

Suggested change
ExecStart=/bin/bash -c 'for dev in /sys/bus/pci/devices/*/; do vendor=$(cat "$dev/vendor" 2>/dev/null); device=$(cat "$dev/device" 2>/dev/null); if [[ "$vendor" == "0x106b" ]] && [[ "$device" == "0x2005" || "$device" == "0x1801" ]]; then echo 0 > "$dev/d3cold_allowed" 2>/dev/null; fi; done; rmmod brcmfmac_wcc 2>/dev/null; rmmod brcmfmac 2>/dev/null; rmmod -f apple-bce 2>/dev/null'
ExecStart=/bin/bash -c 'for dev in /sys/bus/pci/devices/*/; do vendor=$(cat "$dev/vendor" 2>/dev/null); device=$(cat "$dev/device" 2>/dev/null); if [[ "$vendor" == "0x106b" ]] && [[ "$device" == "0x2005" || "$device" == "0x1801" || "$device" == "0x1802" ]]; then echo 0 > "$dev/d3cold_allowed" 2>/dev/null; fi; done; rmmod brcmfmac_wcc 2>/dev/null; rmmod brcmfmac 2>/dev/null; rmmod -f apple-bce 2>/dev/null'

Copilot uses AI. Check for mistakes.
Comment thread migrations/1774617744.sh
Comment on lines +16 to +22
T2_CONF="/etc/limine-entry-tool.d/t2-mac.conf"
if [[ -f "$T2_CONF" ]] && ! grep -q "mem_sleep_default=s2idle" "$T2_CONF"; then
sudo sed -i 's/pcie_ports=compat"/pcie_ports=compat mem_sleep_default=s2idle"/' "$T2_CONF"
fi

# Create and enable suspend service if not already present
if [[ ! -f /etc/systemd/system/omarchy-suspend-t2.service ]]; then
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only edits /etc/limine-entry-tool.d/t2-mac.conf and only via a fragile string replacement of pcie_ports=compat". On many installs the effective kernel cmdline comes from /etc/default/limine (which can override drop-ins), so this migration may not actually apply mem_sleep_default=s2idle. Update the effective Limine config (e.g., /etc/default/limine when present) and run limine-update (as other migrations do) so the new cmdline takes effect, and append the param more robustly if the expected substring isn’t present.

Suggested change
T2_CONF="/etc/limine-entry-tool.d/t2-mac.conf"
if [[ -f "$T2_CONF" ]] && ! grep -q "mem_sleep_default=s2idle" "$T2_CONF"; then
sudo sed -i 's/pcie_ports=compat"/pcie_ports=compat mem_sleep_default=s2idle"/' "$T2_CONF"
fi
# Create and enable suspend service if not already present
if [[ ! -f /etc/systemd/system/omarchy-suspend-t2.service ]]; then
add_t2_kernel_param() {
local file="$1"
local param="$2"
[[ -f "$file" ]] || return 0
if grep -q "$param" "$file"; then
return 0
fi
# Prefer to add after pcie_ports=compat if present
if grep -q "pcie_ports=compat" "$file"; then
sudo sed -i 's/pcie_ports=compat/& '"$param"'/g' "$file"
else
# Fall back to appending inside common cmdline variables if present
sudo sed -i 's/^\(LINUX_CMDLINE="[^"]*\)"/\1 '"$param"'"/' "$file" 2>/dev/null || true
sudo sed -i 's/^\(CMDLINE="[^"]*\)"/\1 '"$param"'"/' "$file" 2>/dev/null || true
fi
}
T2_CONF="/etc/limine-entry-tool.d/t2-mac.conf"
DEFAULT_LIMINE="/etc/default/limine"
add_t2_kernel_param "$T2_CONF" "mem_sleep_default=s2idle"
add_t2_kernel_param "$DEFAULT_LIMINE" "mem_sleep_default=s2idle"
# Apply Limine configuration if possible
if command -v limine-update >/dev/null 2>&1; then
sudo limine-update
fi
# Create and enable suspend service if not already present
if [[ ! -f /etc/systemd/system/omarchy-suspend-t2.service ]]; then
if [[ ! -f /etc/systemd/system/omarchy-suspend-t2.service ]]; then

Copilot uses AI. Check for mistakes.
Comment thread migrations/1774617744.sh Outdated
User=root
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -c 'for dev in /sys/bus/pci/devices/*/; do vendor=$(cat "$dev/vendor" 2>/dev/null); device=$(cat "$dev/device" 2>/dev/null); if [[ "$vendor" == "0x106b" ]] && [[ "$device" == "0x2005" || "$device" == "0x1801" ]]; then echo 0 > "$dev/d3cold_allowed" 2>/dev/null; fi; done; rmmod brcmfmac_wcc 2>/dev/null; rmmod brcmfmac 2>/dev/null; rmmod -f apple-bce 2>/dev/null'
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in the installer, the d3cold-disable loop targets only PCI device 0x1801 even though T2 detection for this migration allows 106b:1802. If the bridge device ID is 0x1802 on some machines, the workaround won’t be applied. Align the device list here with the detection logic (or document why 0x1802 should be excluded).

Suggested change
ExecStart=/bin/bash -c 'for dev in /sys/bus/pci/devices/*/; do vendor=$(cat "$dev/vendor" 2>/dev/null); device=$(cat "$dev/device" 2>/dev/null); if [[ "$vendor" == "0x106b" ]] && [[ "$device" == "0x2005" || "$device" == "0x1801" ]]; then echo 0 > "$dev/d3cold_allowed" 2>/dev/null; fi; done; rmmod brcmfmac_wcc 2>/dev/null; rmmod brcmfmac 2>/dev/null; rmmod -f apple-bce 2>/dev/null'
ExecStart=/bin/bash -c 'for dev in /sys/bus/pci/devices/*/; do vendor=$(cat "$dev/vendor" 2>/dev/null); device=$(cat "$dev/device" 2>/dev/null); if [[ "$vendor" == "0x106b" ]] && [[ "$device" == "0x2005" || "$device" == "0x1801" || "$device" == "0x1802" ]]; then echo 0 > "$dev/d3cold_allowed" 2>/dev/null; fi; done; rmmod brcmfmac_wcc 2>/dev/null; rmmod brcmfmac 2>/dev/null; rmmod -f apple-bce 2>/dev/null'

Copilot uses AI. Check for mistakes.
Comment thread install/config/hardware/fix-apple-t2.sh Outdated
WantedBy=sleep.target
EOF

chrootable_systemctl_enable omarchy-suspend-t2.service
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chrootable_systemctl_enable will run systemctl enable --now on non-chroot installs, which immediately starts omarchy-suspend-t2.service and executes ExecStart (unloading brcmfmac/apple-bce and toggling d3cold) during install. This service should be enabled without starting it immediately (or the helper should be avoided/extended here) so modules aren’t removed right away.

Suggested change
chrootable_systemctl_enable omarchy-suspend-t2.service
sudo systemctl enable omarchy-suspend-t2.service

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d933479b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread install/config/hardware/fix-apple-t2.sh Outdated
WantedBy=sleep.target
EOF

chrootable_systemctl_enable omarchy-suspend-t2.service
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enable suspend hook without starting it immediately

This call goes through chrootable_systemctl_enable, which runs systemctl enable --now when OMARCHY_CHROOT_INSTALL is not set (install/helpers/chroot.sh:3-7). Starting omarchy-suspend-t2.service immediately executes ExecStart and unloads brcmfmac/apple-bce outside any suspend flow, which can drop Wi‑Fi and internal keyboard/trackpad during install or first boot on T2 systems. This unit should be enabled only and allowed to start via sleep.target.

Useful? React with 👍 / 👎.

Comment thread install/config/hardware/fix-apple-t2.sh Outdated
User=root
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -c 'for dev in /sys/bus/pci/devices/*/; do vendor=$(cat "$dev/vendor" 2>/dev/null); device=$(cat "$dev/device" 2>/dev/null); if [[ "$vendor" == "0x106b" ]] && [[ "$device" == "0x2005" || "$device" == "0x1801" ]]; then echo 0 > "$dev/d3cold_allowed" 2>/dev/null; fi; done; rmmod brcmfmac_wcc 2>/dev/null; rmmod brcmfmac 2>/dev/null; rmmod -f apple-bce 2>/dev/null'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include PCI ID 0x1802 in the d3cold disable filter

The T2 detection logic in this script targets both 106b:1801 and 106b:1802, but the new suspend hook only disables d3cold_allowed for 0x1801 (plus NVMe 0x2005). On machines where the T2 bridge enumerates as 0x1802, the d3cold workaround is skipped, so the suspend/resume crash path this patch is addressing can still reproduce. Please include 0x1802 in this match (and in the duplicated migration unit text).

Useful? React with 👍 / 👎.

- Add PCI device ID 0x1802 to d3cold filter to match all T2 bridge
  variants (aligns with the T2 detection logic using 106b:180[12])
- Use plain systemctl enable instead of chrootable_systemctl_enable
  to avoid starting the service during install (which would unload
  WiFi and keyboard modules mid-install)
- Run limine-mkinitcpio in migration so kernel cmdline change takes
  effect without waiting for a kernel update

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dhh
Copy link
Copy Markdown
Member

dhh commented Apr 22, 2026

@fedesapuppo could you have a look at this one too? I don't have a T2 Mac to test.

@dhh dhh added the mac label Apr 22, 2026
@fedesapuppo
Copy link
Copy Markdown
Contributor

Hey!! Tested on my MacBookAir8,1 with linux-t2 6.18.13 (Jason validated on MacBookAir9,1 / 6.17.7, so different generation as well as kernel).

5 suspend cycles with the PR as-is: 1 success, 4 hard reboots. 🫣 Two failure modes:

  1. Kernel oops during ExecStart on rmmod -f apple-bce, when audio was (supposedly) in use before lid close:

RIP: 0010:iowrite32+0xd/0x60
apple_bce(C-) ... [last unloaded: brcmfmac]
note: pipewire[1215] exited with irqs disabled

  1. Instant reboot right after PM: suspend entry (s2idle), even when the service completed cleanly. Journal cuts off at the suspend entry line (T2 firmware doesn't retain pstore across reboots).

Tried a local variant keeping d3cold + brcmfmac unload/reload and dropping apple-bce handling. Failure #1 went away, but #2 still reproduced, so the s2idle path itself is unreliable here. I couldn't find a reliable solution for this yet.

Full logs attached! 📎 omarchy-pr-5129-logs.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants