Skip to content

Instantly share code, notes, and snippets.

@x1unix
Last active May 2, 2025 04:51
Show Gist options
  • Save x1unix/d47f69fafaed90e25d6a50968ade7d3f to your computer and use it in GitHub Desktop.
Save x1unix/d47f69fafaed90e25d6a50968ade7d3f to your computer and use it in GitHub Desktop.
ArchLinux on MacBookPro11,5 (MacBook Pro Mid 2015)

ArchLinux Setup Guide For MacBookPro11,5

Intro

This article is a collection of caveats necessary to get Arch Linux up and running on old Intel MacBooks with AMD GPUs.

Steps here are based on various articles and hours of painful debugging.

Article skips full Arch setup process, focusing only on MacBook-specific steps.

This article assumes that you’re using:

  • PipeWire as audio server.
  • Wayland with Hyprland as a compositor.

Sources

Information in this article is based on following sources:

Bootloader

Apple introduces certain limitations for non-macOS systems, such as inability to use Intel GPU.

The most convenient way to bypass them is using rEFInd bootloader. rEFInd handles MacOS version spoofing, SIP and other stuff out of the box.

rEFInd Installation

Install rEFInd by following instructions from ArchWiki.

Important

Don’t forget to add Linux boot configuration into rEFInd config file /boot/EFI/refind/refind.conf

Set Apple OS

Apple blocks access to integrated Intel GPU for any non-macOS operating system.

rEFInd provides a way to workaround this with spoof_osx_version config parameter.

Open /boot/EFI/refind/refind.conf and add a following parameter:

spoof_osx_version 12.7

Extra Boot Arguments

Add following options to your Arch Linux menu entry in rEFInd config:

data=writeback libata.force=1:noncq acpi_mask_gpe=0x06 acpi_osi=Darwin i915.modeset=1 amdgpu.modeset=0 amdgpu.runpm=1

Setting rEFInd as default bootloader

Unlike Grub or systemd-boot, rEFInd places itself into a non-standard directory inside EFI partition.

Use efibootmgr to add rEFInd as boot entry:

efibootmgr -c -L 'rEFInd Boot Manager' -l '\EFI\refind\refind_x64.efi'

Important

Remove old macOS boot entries using efibootmgr to avoid having long boot delays.

Network

Network Manager

NetworkManager won’t let you to connect to a WiFI without a proper dbus session and keychain provider.

The simplest and most reliable way is to use iwd instead:

pacman -S iwd
systemctl disable --now NetworkManager
systemctl enable --now systemd-resolved
systemctl enable --now systemd-networkd
systemctl enable --now iwd

Tip

Check this ArchWiki page for instructions how to connect to a WiFi network.

Broadcom

You might encounter issues related to missing Broadcom firmware.

In that case, installing broadcom-wl might help:

paru broadcom-wl-dmks

Then, create /etc/modprobe.d/broadcom-wl-dkms.conf:

blacklist brcm80211

GPU Power Management

Unfortunately AMD Cape-Verde gGPUs don’t support dynamic power management. This means, dGPU has to be shut down manually in order to save battery.

Udev Rules

Create /etc/udev/rules.d/30-amdgpu-pm.rules

ACTION=="add", SUBSYSTEM=="drm", DRIVERS=="amdgpu", ATTR{device/power/control}="auto"
ACTION=="add", SUBSYSTEM=="drm", DRIVERS=="amdgpu", ATTR{device/power_dpm_force_performance_level}="low"

Kernel Modules

Go to /etc/modprobe.d and create a couple of files.

amdgpu.conf

options amdgpu modeset=0
options amdgpu enable_psr=1
options amdgpu si_support=1
options amdgpu cik_support=1
options amdgpu runpm=1

radeon.conf

options radeon si_support=0
options radeon cik_support=0
options radeon runpm=1

Then run sudo mkinitcpio -P to apply changes.

Use integrated GPU on boot using gpu-switch

Install gpu-switch tool from AUR using Paru.

Then, use a following command to set iGPU as a primary GPU to drive display:

sudo gpu-switch -i

Note

Operation takes affect only after reboot.

Disable AMDGPU HDMI Audio

WirePlumber is using /dev/snd/controlC2 which is AMD GPU's HDMI Audio.

This prevents AMDGPU to put a device into D3 cold state.

Make ~/.config/wireplumber/wireplumber.conf.d/50-disable-dgpu-hdmi.conf:

# disable the HDA function on the Radeon R9 M370X (PCI 01:00.1)
monitor.alsa.rules = [
  {
    matches = [
      { "device.name" = "alsa_card.pci-0000_01_00.1" }
    ]
    actions = { update-props = { device.disabled = true } }
  }
]

After:

  • restart wireplumber: systemctl --user restart wireplumber
  • check if DIS-Audio is DynOff: sudo cat /sys/kernel/debug/vgaswitcheroo/switch

Turn Off Discrete AMD GPU

Both GPUs are controlled using Apple's GMUX. It is responsible for turning on and off discrete AMD GPU.

The vga-switcheroo kernel module is responsible for controlling a mux.

# set to integrated
sudo sh -c 'echo IDG > /sys/kernel/debug/vgaswitcheroo/switch'

# turn off dGPU
sudo sh -c 'echo OFF > /sys/kernel/debug/vgaswitcheroo/switch'

# print mux state.
# ensure that "DIS" is "OFF" and "DIS-Audio" is "DynOff"
sudo cat /sys/kernel/debug/vgaswitcheroo/switch

Output of /sys/kernel/vgaswitcheroo/switch should look like this:

0:IGD:+:Pwr:0000:00:02.0
1:DIS: :Off:0000:01:00.0
2:DIS-Audio: :DynOff:0000:01:00.1

Disable dGPU On Boot

Create oneshot systemd unit file /etc/systemd/system/dgpu-poweroff.service:

[Unit]
Description=Disable discrete GPU (vgaswitcheroo OFF)
Requires=sys-kernel-debug.mount
After=sys-kernel-debug.mount

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo OFF > /sys/kernel/debug/vgaswitcheroo/switch'

[Install]
WantedBy=multi-user.target suspend.target   # run at boot *and* after resume

Then, start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now dgpu-poweroff.service

Use iGPU for Hyprland

Find GPU PCI of integrated GPU:

$ lspci | grep VGA
00:02.0 VGA compatible controller: Intel Corporation Crystal Well Integrated Graphics Controller (rev 08)
01:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Venus XT [Radeon HD 8870M / R9 M270X/M370X] (rev 83)

Check what DRI card maps to PCI ID:

$ ls -l /dev/dri/by-path
total 0
lrwxrwxrwx 1 root root  8 Apr 23 03:14 pci-0000:00:02.0-card -> ../card1
lrwxrwxrwx 1 root root 13 Apr 23 03:14 pci-0000:00:02.0-render -> ../renderD128
lrwxrwxrwx 1 root root  8 Apr 23 03:14 pci-0000:01:00.0-card -> ../card0
lrwxrwxrwx 1 root root 13 Apr 23 03:14 pci-0000:01:00.0-render -> ../renderD129

In my case, card1 is Intel and card0 is AMD.

To make Intel GPU a preferred GPU for Hyprland, add following into a ~/.config/hypr/hyprland.conf:

# Prefer iGPU
env = AQ_DRM_DEVICES,/dev/dri/card1

Power Efficiency

Even with disabled AMD GPU, MacBook still consumes about 15–20 watts (which is a lot).

Although there are a lot of things that can be tweaked, the most basic thing to address is setting a correct CPU power profile.

Battery Monitoring

I recommend using battop tool to monitor power consumption during power optimization.

iGPU Tweaks

PSR (panel self-refresh) and FBC (frame-buffer compression) save ~0.3-0.5 W when the screen shows static content.

Create /etc/modprobe.d/i915.conf:

options i915 enable_psr=1
options i915 enable_fbc=1
options i915 enable_dc=1

Pefrormance Power Profile

Automatic Power Profiles

The auto-cpufreq daemon allows automatic CPU power profile configuration based on battery capacity and charging status.

Pros:

  • Automatic power profile management based on charging state.

Cons:

  • By default it will put CPU into a power save mode when not connected to a charger. This will cause a drastic performance decrease.
  • Written in Python and consumes 20MB of RAM which is quite a lot for a simple CPU frequency daemon.

Install auto-cpufreq daemon to automatically control CPU frequency:

paru auto-cpufreq
sudo systemctl enable --now auto-cpufreq

Important

Don't install auto-cpufreq-git as it's broken.

Switch Power Profiles Manually

power-profiles-deamon package provides DBus service and a tool to switch CPU profiles.

sudo pacman -S power-profiles-daemon
sudo systemctl enable --now power-profiles-daemon.service

Later, you can change power profile to power saver, performance or balanced:

powerprofilesctl set power-saver
powerprofilesctl set balanced
[Unit]
Description=Disable discrete GPU (vgaswitcheroo OFF)
Requires=sys-kernel-debug.mount
After=sys-kernel-debug.mount
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo OFF > /sys/kernel/debug/vgaswitcheroo/switch'
[Install]
WantedBy=multi-user.target suspend.target # run at boot *and* after resume
#!/usr/bin/env sh
set -e
VGASW=/sys/kernel/debug/vgaswitcheroo/switch
AMDGPU_PCI_ID='0000:01:00.0'
AMDGPU_HDA_PCI_ID='0000:01:00.1'
g_print() {
sudo cat "$VGASW"
}
g_write_mode() {
echo ":: Set mux mode to $1"
sudo sh -c "echo $1 > $VGASW"
}
g_setmode() {
if [ -z "$1" ]; then
echo "Empty value. Allowed: OFF|ON|IGD|DIS|DIDG|DDIS|MIGD|MDIS"
exit 100
fi
case "$1" in
OFF | ON | IGD | DIS | DIGD | DDIS | MIGD | MDIS)
g_write_mode "$1"
;;
*)
printf 'Bad GPU mode '$1'. Allowed values:\n%s\n' 'OFF | ON | IGD | DIS | DIGD | DDIS | MIGD | MDIS'
exit 1
;;
esac
echo "Set VGASWitcheroo mode to $1"
}
g_turnoff() {
g_setmode IGD
g_setmode OFF
g_print
}
g_turnon() {
g_setmode ON
g_print
}
g_powerstate() {
f="/sys/bus/pci/devices/$AMDGPU_PCI_ID"
echo ":: AMDGPU Power state:"
if [ ! -d "$f" ]; then
echo 'GPU device not found'
return 0
fi
acpi_state=$(cat "$f/power_state" | tr -d '\n')
dpm_state=$(cat "$f/power_dpm_state" | tr -d '\n')
pwctl_state=$(cat "$f/power/control" | tr -d '\n')
rt_status=$(cat "$f/power/runtime_status" | tr -d '\n')
echo "- ACPI State: $acpi_state"
echo "- DPM State: $dpm_state"
echo "- Power Control: $pwctl_state"
echo "- Runtime Status: $rt_status"
}
g_get_devfs_device() {
case "$1" in
"card")
readlink -f "/dev/dri/by-path/pci-$AMDGPU_PCI_ID-card" | tr -d '\n'
;;
"hda")
readlink -f "/dev/snd/by-path/pci-$AMDGPU_HDA_PCI_ID" | tr -d '\n'
;;
"renderer")
readlink -f "/dev/dri/by-path/pci-$AMDGPU_PCI_ID-render" | tr -d '\n'
;;
*)
echo "ERROR: g_get_devfs_device - bad type $1"
exit 2
;;
esac
}
g_consumers() {
dev_card="$(g_get_devfs_device 'card')"
dev_render="$(g_get_devfs_device 'renderer')"
dev_hda="$(g_get_devfs_device 'hda')"
echo ':: Consumers (GPU+SND)'
sudo fuser -v "$dev_card" "$dev_render" "$dev_hda"
}
g_status() {
echo ":: Devices"
echo "- GPU: $(g_get_devfs_device 'card')"
echo "- RND: $(g_get_devfs_device 'renderer')"
echo "- HDA: $(g_get_devfs_device 'hda')"
echo ":: Framebuffer:"
cat /sys/class/graphics/fb*/name
g_powerstate
echo ':: apple-gmux status:'
g_print
g_consumers
}
g_printenv() {
dev_card="$(g_get_devfs_device 'card')"
dev_render="$(g_get_devfs_device 'renderer')"
dev_hda="$(g_get_devfs_device 'hda')"
echo "AMDGPU_PCI_ID='$AMDGPU_PCI_ID'"
echo "AMDGPU_HDA_PCI_ID=$AMDGPU_HDA_PCI_ID"
echo "AMDGPU_DRI_CARD='$dev_card'"
echo "AMDGPU_DRI_RENDER='$dev_render'"
echo "AMDGPU_SND_CTRL='$dev_hda'"
echo "AMDGPU_DEV='/sys/bus/pci/devices/$AMDGPU_PCI_ID'"
echo "VGASW_DEV='/sys/kernel/debug/vgaswitcheroo/switch'"
}
case "$1" in
print | v)
g_status
;;
on)
g_turnon
;;
off)
g_turnoff
;;
e | env)
g_printenv
;;
w | write)
g_setmode "$2"
;;
*)
printf 'Usage: %s [v|w|on|off]\nTool to debug and control dGPU power management on MacBooks\n\n' "$0"
echo "off - Disable discrette gpu"
echo "on - Disable discrette gpu"
echo "v - Print current vgaswitcheroo switch status"
echo "w - Write a value into '$VGASW'"
echo "e - Dump paths"
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment