Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mac-manual/content/12-hardware.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ This is what the first release is qualified against on both reference Macs. <spa
| Fourth external display after HDMI unplug | <span class="status wip">known issue</span> | A stale DisplayPort link on the M2 Max after unplugging HDMI; replug or reboot |
| Text console between Plymouth and the greeter | <span class="status wip">known issue</span> | Cosmetic, a few seconds |

## Bluetooth after suspend

On BCM4378, BCM4387 and BCM4388 Macs, the Bluetooth controller can stop answering after suspend until its driver is reset. The package's `omarchy-bluetooth-resume-fix.service` runs after every suspend and resets only the Bluetooth driver when the kernel reports HCI command timeouts from the controller after that resume. A timeout means a command failed, not always that the controller is still stuck, so it may reset a controller that would have recovered by itself. With no timeout, or with the radio turned off, it does nothing. Read the result with `journalctl -u omarchy-bluetooth-resume-fix`.

To turn automatic recovery off, run `sudo systemctl mask omarchy-bluetooth-resume-fix.service`; updates keep that choice. This recovery does not make Bluetooth devices wake the Mac and does not cover a wedge while the Mac stays awake.

## What Asahi supports on your chip

Everything below the desktop is the Asahi Linux project's work, and the authoritative, current list of what each Apple chip supports is theirs:
Expand Down
1 change: 1 addition & 0 deletions omarchy-mac/ORIGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Extracted from Omarchy (MIT; see LICENSE), preserving the original helper and service names. Extraction baseline: `omacom/omarchy-mac` commit `350c46550b99688cdb5224408edd5870de2ca07b`.

- Wi-Fi recovery and behavioral tests: Scott Jones, `092ab7cf881742e790f58303b31cf7787802a8a9` (Reload brcmfmac after s2idle when Apple Silicon Wi-Fi wedges). Hardware restrictions and the journal cursor recovery algorithm are retained.
- Bluetooth resume recovery: @n0mahd, [omacom/omarchy-mac#498](https://github.com/omacom/omarchy-mac/pull/498), `b9a5b33e78f8f04dd9f6bf322aed3c5a42b51e68`. The suspend-entry journal cursor, timeout signature, Bluetooth-only rebind and bounded waits are retained. A vendor wants link (so installs and upgrades alike get it), platform rechecks and a watch tied to the current suspend cycle adapt it to this package. BCM4388 (`14e4:5f72`) follows Oliver Lukschander's report of the same wedge on an M2 Pro in that PR's review.

The network backend default follows Marcelo Alcantara's Apple Silicon integration in #9835, `4bc760378b5af60d730f52b7773c635e37331a81` and `2bd767f0e54a9138ada8b0e89b66e5080f2d1e33`. Package layout and setup tests are new work.

Expand Down
10 changes: 8 additions & 2 deletions omarchy-mac/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# omarchy-mac

Apple Silicon defaults and support services for Omarchy. Version: `0.1.3` (candidate). This add-on complements `omarchy` and `omarchy-settings`; it selects no kernel and contains no installer or repository trust configuration. It covers what stays on an installed Mac; installing one is the job of the [Omarchy Installer](https://github.com/omacom/omarchy-mac-installer), and boot support is the separate `omarchy-mac-boot` package beside this one.
Apple Silicon defaults and support services for Omarchy. Version: `0.1.4` (candidate). This add-on complements `omarchy` and `omarchy-settings`; it selects no kernel and contains no installer or repository trust configuration. It covers what stays on an installed Mac; installing one is the job of the [Omarchy Installer](https://github.com/omacom/omarchy-mac-installer), and boot support is the separate `omarchy-mac-boot` package beside this one.

## Build and stage

Expand All @@ -12,7 +12,7 @@ Runtime dependencies: `omarchy` (the `omarchy-hw-platform` detector and its Appl

Upgrade the matching runtime, settings and add-on candidates in one pacman transaction. Their transferred commands and user unit must have only one owner. A fresh Apple Silicon install takes two transactions before `omarchy-apply-system`: first one that installs the settings package, then one that installs the platform's default set (`omarchy-pkg-defaults aarch64-apple`: the base, aarch64 and Apple lists, this add-on included). The settings package's pacman platform guard checks only transactions that start after it is installed, so no Apple package may share a transaction with it; the runtime can go in either. The recipe must tag this package with `groups=('omarchy-platform-apple-silicon')`; the desktop's `docs/platform-guard.md` has the contract. The desktop's last hardware leaf runs this package's system setup through the dispatcher, and offline setup never downloads it: a Mac without it keeps Omarchy's generic setup.

Run `omarchy-mac-setup-system` as root on the target hardware (inside its target chroot for offline provisioning). An optional absolute root argument supports staging against the same target hardware without a bus. It exits without changes unless the runtime's Apple predicate `omarchy-hw-apple-silicon` holds (`omarchy-hw-platform` reports `aarch64-apple`, or `apple-silicon` on a runtime from before the platform rename), and fails if the detector does. It removes the Intel Mac Broadcom quirk (`options brcmfmac feature_disable=0x82000`, which breaks Apple Silicon Wi-Fi) from `/etc/modprobe.d/brcmfmac.conf` wherever an unguarded runtime migration appended it: only that exact line, with the comment block and blank line written right above it, goes; the file goes when nothing but blank lines is left, a link is left alone with the manual fix, and the initramfs rebuild it owes is recorded under `/var/lib/omarchy/migrations/1789172112-initramfs-pending`, which the runtime's migration finishes. It enables resume recovery only for BCM4378, BCM4387 and BCM4388 on Apple Silicon; Intel/T2 Macs are excluded. It is the only enabler of `speakersafetyd`, without which the kernel keeps the speakers muted: it applies the package's `80-omarchy-mac-audio.preset` once speakersafetyd is installed, so `/etc` presets that sort before it or replace it by name, masks and later explicit disables win, and image builders that apply presets get the same result. A live run also restarts a speakersafetyd left dead by a start-limit. No NetworkManager restart or driver reload occurs during setup. The backend applies when NetworkManager next starts; notch changes apply when appledrm next loads. A live run gives iwd a profile for each Wi-Fi network NetworkManager saved with its key before the switch, as NetworkManager itself does for a new connection, so saved networks keep connecting instead of reading as a wrong password. The greeter drop-in needs no setup: from the next `sddm` start it holds the greeter, for at most 10 s, until Apple's display controller has replaced the boot framebuffer.
Run `omarchy-mac-setup-system` as root on the target hardware (inside its target chroot for offline provisioning). An optional absolute root argument supports staging against the same target hardware without a bus. It exits without changes unless the runtime's Apple predicate `omarchy-hw-apple-silicon` holds (`omarchy-hw-platform` reports `aarch64-apple`, or `apple-silicon` on a runtime from before the platform rename), and fails if the detector does. It removes the Intel Mac Broadcom quirk (`options brcmfmac feature_disable=0x82000`, which breaks Apple Silicon Wi-Fi) from `/etc/modprobe.d/brcmfmac.conf` wherever an unguarded runtime migration appended it: only that exact line, with the comment block and blank line written right above it, goes; the file goes when nothing but blank lines is left, a link is left alone with the manual fix, and the initramfs rebuild it owes is recorded under `/var/lib/omarchy/migrations/1789172112-initramfs-pending`, which the runtime's migration finishes. It does not touch Bluetooth resume recovery, which the package enables itself (see Bluetooth resume recovery below). It is the only enabler of `speakersafetyd`, without which the kernel keeps the speakers muted: it applies the package's `80-omarchy-mac-audio.preset` once speakersafetyd is installed, so `/etc` presets that sort before it or replace it by name, masks and later explicit disables win, and image builders that apply presets get the same result. A live run also restarts a speakersafetyd left dead by a start-limit. No NetworkManager restart or driver reload occurs during setup. The backend applies when NetworkManager next starts; notch changes apply when appledrm next loads. A live run gives iwd a profile for each Wi-Fi network NetworkManager saved with its key before the switch, as NetworkManager itself does for a new connection, so saved networks keep connecting instead of reading as a wrong password. The greeter drop-in needs no setup: from the next `sddm` start it holds the greeter, for at most 10 s, until Apple's display controller has replaced the boot framebuffer.

Run `omarchy-mac-setup-user` as each target user with their HOME/XDG directories. It enables the vendor unit without a session bus. It writes nothing into the user's Hyprland files; a block an earlier version appended to `input.lua` or `looknfeel.lua` stays, as the user's. With a live bus, reached only through XDG_RUNTIME_DIR (which `sudo -i` clears), it reloads the user manager, reports the effective unit and starts the mapper. The mapper makes the microphone a typed stereo source, `omarchy_asahi_mic` ("MacBook Microphone"): pipewire-pulse's `module-remap-source` records asahi-audio's mono DSP source, which WirePlumber links to the left side and the mapper to the right, and offers an `Audio/Source` with front-left and front-right, the shape of a stock microphone, so the desktop's audio panel drives its volume, mute and level meter as it does any other. It makes that source the default input only if a brief sample finds signal, and hands a silent one back to the DSP source. A muted mapping is the user's microphone mute and is left alone. On upgrade from 0.1.2 the mapper replaces 0.1.2's virtual source of the same name (an `Audio/Source/Virtual`, which the panel gives no level meter) on its next start, keeping its gain, mute and selection. Provisioning and first run call setup; subsequent session starts only start the enabled unit, without reloading the user manager or repeating setup. The first desktop session does not depend on a bus existing during installation. Custom fragments, masks, activation links and headset or speaker policies are preserved; setup markers preserve explicit disables after initial setup. User gain state remains in `omarchy/asahi-mic-gain.json` under XDG_STATE_HOME. The desktop saves that state before restarting audio.

Expand All @@ -38,6 +38,12 @@ The logind drop-in `/usr/lib/systemd/logind.conf.d/20-omarchy-mac-sleep-key.conf

Vendor defaults use NetworkManager's `/usr/lib/NetworkManager/conf.d`, systemd's `/usr/lib/systemd` and `/usr/lib/tmpfiles.d`, modprobe's `/usr/lib/modprobe.d`, and WirePlumber's `/usr/share/wireplumber/wireplumber.conf.d`. Same-name `/etc` or user fragments retain precedence. iwd has no vendor directory, so an `iwd.service` drop-in lists `/usr/lib/omarchy-mac/iwd` after `/etc/iwd`; iwd loads the first `main.conf` it can read, so an `/etc/iwd/main.conf` replaces the Apple one whole. The Apple default keeps iwd off 6 GHz: on the BCM4388 a join the firmware makes to a 6 GHz access point reports connected and authorized, yet the access point drops everything the Mac sends and DHCP never completes, so a network that also offers 5 GHz joins there instead. The firmware's own roaming can still move a weak connection onto 6 GHz, and `omarchy network band 6` cannot reach 6 GHz. Like the backend, it applies when iwd next starts: setup restarts nothing, so an upgraded Mac keeps 6 GHz until it reboots. Setup reports effective live NetworkManager/module configuration and systemd fragments. Review those reports and any drop-ins when diagnosing overrides; custom policy is never normalized to the package default.

## Bluetooth resume recovery

The vendor `omarchy-bluetooth-resume-fix.service` rebinds `hci_bcm4377` on BCM4378, BCM4387 and BCM4388 Apple Silicon Macs when the controller times out HCI commands after suspend, the sign of a controller that can stop answering until the driver is rebound. A wants link in the package runs it after every suspend on every install and upgrade, with no setup step; its condition skips other Macs and chips. It watches the kernel log from this suspend's entry, including timeouts logged before the service starts: it waits for journald to hold the entry the kernel counted in `/sys/power/suspend_stats`, so an earlier suspend's timeouts are never read as this one's, and a suspend during the watch moves it to the newer resume. Without that entry it watches a bounded window from its own start. A timeout from the controller in the 20 s after resume rebinds the Bluetooth PCI function; a timeout shows the controller failed a command, not that it is still stuck, so a rebind may come when the controller would have recovered by itself. A controller with no timeout and a disabled radio are left alone. Hibernation is not covered.

Inspect a recovery with `journalctl -u omarchy-bluetooth-resume-fix`. Turn it off with `sudo systemctl mask omarchy-bluetooth-resume-fix.service`; `disable` cannot remove the package's wants link. Wedges while awake and waking the Mac with a Bluetooth keyboard are separate problems.

## Video decode in mpv

mpv has no vendor configuration directory and decodes in software unless told otherwise. The package ships its Apple default as `/usr/share/omarchy-mac/mpv/mpv.conf` and a systemd-tmpfiles rule that copies it to `/etc/mpv/mpv.conf` only when that file is missing. pacman's systemd hook applies the rule when the package is installed or upgraded, and boot applies it again. An existing `/etc/mpv/mpv.conf` is never overwritten, so edit it rather than delete it to change the system default; each user's `~/.config/mpv/mpv.conf` is read after it and wins, and `hwdec=no` there decodes in software.
Expand Down
196 changes: 196 additions & 0 deletions omarchy-mac/bin/omarchy-bluetooth-resume-fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#!/bin/bash

# omarchy:summary=Rebind hci_bcm4377 when Bluetooth times out after resume
# omarchy:group=system
# omarchy:hidden=true
# omarchy:requires-sudo=true
set -euo pipefail

support_status=0
/usr/lib/omarchy-mac/bluetooth-supported || support_status=$?
if (( support_status == 1 )); then
exit 0
elif (( support_status != 0 )); then
echo "Cannot determine Bluetooth recovery support" >&2
exit "$support_status"
fi

driver=${OMARCHY_BLUETOOTH_DRIVER_DIR:-/sys/bus/pci/drivers/hci_bcm4377}
controllers=${OMARCHY_BLUETOOTH_CLASS_DIR:-/sys/class/bluetooth}
stats=${OMARCHY_SUSPEND_STATS_DIR:-/sys/power/suspend_stats}
wait_before=20
wait_after=15
wait_journal=10
started=$(date '+%Y-%m-%d %H:%M:%S')
radio_enabled() {
local state name blocked
state=$(rfkill --noheadings --output DEVICE,SOFT list bluetooth 2>/dev/null) || return 2
while read -r name blocked; do
# shellcheck disable=SC2053 # HCI identifiers from sysfs contain no glob syntax.
if [[ $name == $controller ]]; then
[[ $blocked == "unblocked" ]]
return $?
fi
done <<<"$state"
return 1
}

bound_controller() {
local path
for path in "$driver"/*:*:*.*; do
if [[ -e $path ]]; then
basename "$path"
return 0
fi
done
return 1
}

if device=$(bound_controller); then
:
else
echo "No controller bound to hci_bcm4377, nothing to rebind"
exit 0
fi

controller_for_device() {
local path parent
for path in "$controllers"/hci*; do
parent=$(readlink -f "$path/device") || continue
# shellcheck disable=SC2053 # A PCI function identifier contains no glob syntax.
if [[ ${parent##*/} == $device ]]; then
basename "$path"
return 0
fi
done
return 1
}
if controller=$(controller_for_device); then
signature="$controller: command 0x[0-9a-f]+ tx timeout"
else
echo "Cannot identify the Bluetooth controller bound to $device" >&2
exit 1
fi

radio_status=0
radio_enabled || radio_status=$?
if (( radio_status == 1 )); then
echo "Bluetooth is disabled or absent, nothing to do"
exit 0
elif (( radio_status != 0 )); then
echo "Cannot read Bluetooth radio state" >&2
exit "$radio_status"
fi

# The kernel counts suspend attempts this boot, and logs one entry line for each.
suspend_attempts() {
local success fail
success=$(<"$stats/success") && fail=$(<"$stats/fail") || return 1
echo $((success + fail))
}

# Watch from this cycle's suspend entry: device-resume timeouts precede this
# service. Wait until journald holds the entry the kernel counted, or its latest
# entry is an earlier cycle's, with that cycle's timeouts.
anchor() {
local kernel seen waited=0
attempts=$(suspend_attempts) || attempts=
while true; do
kernel=$(journalctl -kqb --no-pager -o cat 2>/dev/null) || return 2
seen=$(grep -c 'PM: suspend entry' <<<"$kernel" || true)
if [[ -z $attempts ]] || (( seen >= attempts )); then
break
fi
if (( waited >= wait_journal )); then
echo "The journal has no entry for this suspend after ${wait_journal}s; watching from now instead"
cursor=
started=$(date '+%Y-%m-%d %H:%M:%S')
return 0
fi
waited=$((waited + 1))
sleep 1
done
cursor=$(journalctl -kqb -n 1 -g 'PM: suspend entry' --show-cursor 2>/dev/null |
sed -n 's/^-- cursor: //p') || cursor=
if [[ -z $cursor ]]; then
echo "No suspend marker in the journal; watching from now instead"
fi
}

timed_out() {
local journal
if [[ -n $cursor ]]; then
journal=$(journalctl -kqb --after-cursor "$cursor" --no-pager 2>/dev/null) || return 2
else
journal=$(journalctl -kqb --since "$started" --no-pager 2>/dev/null) || return 2
fi
[[ $journal =~ $signature ]]
}

observe_status=0
anchor || observe_status=$?
if (( observe_status != 0 )); then
echo "Cannot read the kernel journal for Bluetooth recovery" >&2
exit "$observe_status"
fi
elapsed=0
while (( elapsed < wait_before )); do
# A suspend during the watch starts a new cycle; this run follows it, since
# systemd won't start the unit again while it is still running.
if [[ -n $attempts ]] && now=$(suspend_attempts) && (( now > attempts )); then
echo "Suspended again while watching; following the latest resume"
anchor || observe_status=$?
if (( observe_status != 0 )); then
echo "Cannot read the kernel journal for Bluetooth recovery" >&2
exit "$observe_status"
fi
elapsed=0
fi
timed_out || observe_status=$?
if (( observe_status == 0 )); then
break
elif (( observe_status != 1 )); then
echo "Cannot read the kernel journal for Bluetooth recovery" >&2
exit "$observe_status"
fi
observe_status=0
elapsed=$((elapsed + 1))
sleep 1
done
if (( elapsed >= wait_before )); then
echo "No HCI command timeout from $controller in the ${wait_before}s after resume; leaving it alone"
exit 0
fi

# A user can turn the radio off while the service is watching the journal.
radio_status=0
radio_enabled || radio_status=$?
if (( radio_status == 1 )); then
echo "Bluetooth was disabled while watching, nothing to do"
exit 0
elif (( radio_status != 0 )); then
echo "Cannot read Bluetooth radio state before the rebind" >&2
exit "$radio_status"
fi
echo "HCI command timeout from $controller ${elapsed}s after resume ($device); rebinding hci_bcm4377"
if ! { printf '%s\n' "$device" >"$driver/unbind"; } 2>/dev/null; then
echo "Failed to unbind $device; a reboot is needed" >&2
exit 1
fi
sleep 1
if ! { printf '%s\n' "$device" >"$driver/bind"; } 2>/dev/null; then
echo "Failed to bind $device; a reboot is needed" >&2
exit 1
fi

elapsed=0
while (( elapsed < wait_after )); do
if controller_for_device >/dev/null; then
echo "Controller back ${elapsed}s after the rebind"
exit 0
fi
elapsed=$((elapsed + 1))
sleep 1
done
echo "Controller still missing ${wait_after}s after the rebind" >&2
exit 1
7 changes: 7 additions & 0 deletions omarchy-mac/lib/bluetooth-supported
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash
# BCM4378 and BCM4387 Bluetooth on Apple Silicon, as qualified by the source PR,
# and BCM4388, which wedges across resume the same way (omacom/omarchy-mac#498 review).
omarchy-hw-platform >/dev/null || exit 2
omarchy-hw-apple-silicon || exit 1
devices=$(lspci -nn) || exit 2
grep -E '14e4:(5f69|5f71|5f72)' <<<"$devices" >/dev/null
Loading
Loading