This is a guide for improving the power consumption on Linux Mint 22.2 by disabling the Radeon GPU and enabling the Intel GPU on a 2015 MacBook Pro 11,5 (Intel Haswell iGPU + AMD Radeon dGPU + Apple GMUX). It is based on the work of Bruno Bierbaumer https://github.com/0xbb/apple_set_os.efi and credits belong to Andreas Heider who originally discovered this hack: https://lists.gnu.org/archive/html/grub-devel/2013-12/msg00442.html
I simply adapted it to work on Linux Mint 22.2.
Goal:
- Boot in a way that exposes/keeps the Intel iGPU usable (some Mac firmware disables or hides it for non-macOS boots).
- Ensure the internal panel (eDP) is driven by i915.
- Power off the Radeon dGPU so it stops draining the battery.
The configuration uses:
- rEFInd as the first-stage boot manager.
- A small UEFI app that calls Apple’s private
apple_set_osprotocol (spoofs macOS vendor/version) and then chainloads GRUB/shim. - A systemd oneshot service that runs on every boot to switch to Intel and power off the dGPU via
vga_switcheroo.
- You are booting in UEFI mode.
- Your EFI System Partition (ESP) is mounted at
/boot/efi. - You have sudo privileges.
- Secure Boot: this guide chainloads
shimx64.efifirst, so it works with Secure Boot setups too (assuming shim is properly installed).
sudo apt update
sudo apt install -y git make gcc gnu-efi efibootmgr pciutils tlp(You may already have some of these.)
lspci -nnk | awk 'BEGIN{IGNORECASE=1} /(vga|3d|display)/{p=1} p{print} /^$/{p=0}'
dmesg | egrep -i 'apple_gmux|gmux|i915|radeon|amdgpu' | tail -n 200You should see something like:
- Intel iGPU:
00:02.0(i915) - AMD dGPU:
01:00.0(radeon) apple_gmux: Found gmux ...
mkdir -p /tmp/apple-set-os-chain
cd /tmp/apple-set-os-chain
rm -rf apple-set-os
git clone --depth 1 https://github.com/kekrby/apple-set-os.git
cd apple-set-osCreate a new file apple_set_os_grub_chain.c:
cat > apple_set_os_grub_chain.c <<'EOF'
// apple_set_os_grub_chain
// 1) Call Apple's apple_set_os protocol to set OS vendor/version
// 2) Chainload GRUB/shim from the same ESP/device
#include <efibind.h>
#include <efidef.h>
#include <efidevp.h>
#include <eficon.h>
#include <efiprot.h>
#include <efiapi.h>
#include <efierr.h>
#define APPLE_SET_OS_VENDOR "Apple Inc."
#define APPLE_SET_OS_VERSION "Mac OS X 10.9"
static EFI_GUID APPLE_SET_OS_GUID = { 0xc5c5da95, 0x7d5c, 0x45e6, { 0xb2, 0xf1, 0x3f, 0xd5, 0x2b, 0xb1, 0x00, 0x77 } };
static EFI_GUID EFI_LOADED_IMAGE_GUID = EFI_LOADED_IMAGE_PROTOCOL_GUID;
static EFI_GUID EFI_DEVICE_PATH_GUID = EFI_DEVICE_PATH_PROTOCOL_GUID;
typedef struct _APPLE_SET_OS_INTERFACE {
UINT64 Version;
EFI_STATUS (EFIAPI *SetOsVersion) (IN CHAR8 *Version);
EFI_STATUS (EFIAPI *SetOsVendor) (IN CHAR8 *Vendor);
} APPLE_SET_OS_INTERFACE;
static UINTN str_len16(const CHAR16 *s) {
UINTN n = 0;
while (s && s[n]) n++;
return n;
}
static void mem_copy(void *dst, const void *src, UINTN n) {
UINT8 *d = (UINT8 *)dst;
const UINT8 *s = (const UINT8 *)src;
while (n--) *d++ = *s++;
}
static void say(SIMPLE_TEXT_OUTPUT_INTERFACE *ConOut, const CHAR16 *msg) {
if (ConOut && msg) ConOut->OutputString(ConOut, (CHAR16 *)msg);
}
static UINTN device_path_size(EFI_DEVICE_PATH_PROTOCOL *dp) {
UINTN sz = 0;
if (!dp) return 0;
while (!IsDevicePathEnd(dp)) {
sz += DevicePathNodeLength(dp);
dp = NextDevicePathNode(dp);
}
sz += END_DEVICE_PATH_LENGTH;
return sz;
}
static EFI_DEVICE_PATH_PROTOCOL *build_full_device_path(EFI_BOOT_SERVICES *BS,
EFI_DEVICE_PATH_PROTOCOL *device_dp,
const CHAR16 *file_path) {
// Build full device path: [device_dp without END] + FILEPATH node + END
UINTN dev_sz = device_path_size(device_dp);
if (dev_sz < END_DEVICE_PATH_LENGTH) return NULL;
UINTN file_path_bytes = (str_len16(file_path) + 1) * sizeof(CHAR16);
UINTN file_node_sz = sizeof(FILEPATH_DEVICE_PATH) + file_path_bytes - sizeof(CHAR16);
UINTN total = (dev_sz - END_DEVICE_PATH_LENGTH) + file_node_sz + END_DEVICE_PATH_LENGTH;
EFI_DEVICE_PATH_PROTOCOL *out = NULL;
if (EFI_ERROR(BS->AllocatePool(EfiLoaderData, total, (VOID **)&out)) || !out)
return NULL;
mem_copy(out, device_dp, dev_sz - END_DEVICE_PATH_LENGTH);
FILEPATH_DEVICE_PATH *fp = (FILEPATH_DEVICE_PATH *)((UINT8 *)out + (dev_sz - END_DEVICE_PATH_LENGTH));
fp->Header.Type = MEDIA_DEVICE_PATH;
fp->Header.SubType = MEDIA_FILEPATH_DP;
SetDevicePathNodeLength(&fp->Header, file_node_sz);
mem_copy(fp->PathName, file_path, file_path_bytes);
EFI_DEVICE_PATH_PROTOCOL *end = (EFI_DEVICE_PATH_PROTOCOL *)((UINT8 *)fp + file_node_sz);
SetDevicePathEndNode(end);
return out;
}
static EFI_STATUS try_chainload(EFI_HANDLE Image, EFI_SYSTEM_TABLE *SystemTable, const CHAR16 *path) {
EFI_STATUS Status;
EFI_LOADED_IMAGE *LoadedImage = NULL;
Status = SystemTable->BootServices->HandleProtocol(Image, &EFI_LOADED_IMAGE_GUID, (VOID **) &LoadedImage);
if (EFI_ERROR(Status) || LoadedImage == NULL)
return Status;
EFI_DEVICE_PATH_PROTOCOL *DeviceDp = NULL;
Status = SystemTable->BootServices->HandleProtocol(LoadedImage->DeviceHandle, &EFI_DEVICE_PATH_GUID, (VOID **) &DeviceDp);
if (EFI_ERROR(Status) || DeviceDp == NULL)
return Status;
EFI_DEVICE_PATH_PROTOCOL *FullDp = build_full_device_path(SystemTable->BootServices, DeviceDp, path);
if (FullDp == NULL)
return EFI_OUT_OF_RESOURCES;
EFI_HANDLE ChildImage = NULL;
Status = SystemTable->BootServices->LoadImage(FALSE, Image, FullDp, NULL, 0, &ChildImage);
SystemTable->BootServices->FreePool(FullDp);
if (EFI_ERROR(Status))
return Status;
say(SystemTable->ConOut, L"Starting: ");
say(SystemTable->ConOut, path);
say(SystemTable->ConOut, L"\r\n");
Status = SystemTable->BootServices->StartImage(ChildImage, NULL, NULL);
return Status;
}
EFI_STATUS
efi_main(EFI_HANDLE Image, EFI_SYSTEM_TABLE *SystemTable)
{
EFI_STATUS Status;
APPLE_SET_OS_INTERFACE *SetOs = NULL;
say(SystemTable->ConOut, L"apple_set_os_grub_chain started\r\n");
// Apple protocol: set vendor/version to "Mac OS X" values
Status = SystemTable->BootServices->LocateProtocol(&APPLE_SET_OS_GUID, NULL, (VOID **) &SetOs);
if (!EFI_ERROR(Status) && SetOs != NULL) {
if (SetOs->Version != 0)
SetOs->SetOsVersion((CHAR8 *) APPLE_SET_OS_VERSION);
SetOs->SetOsVendor((CHAR8 *) APPLE_SET_OS_VENDOR);
}
// Try common distro paths on ESP
const CHAR16 *targets[] = {
L"\\EFI\\ubuntu\\shimx64.efi",
L"\\EFI\\ubuntu\\grubx64.efi",
L"\\EFI\\linuxmint\\shimx64.efi",
L"\\EFI\\linuxmint\\grubx64.efi",
L"\\EFI\\debian\\grubx64.efi",
L"\\EFI\\BOOT\\BOOTX64.EFI",
NULL
};
for (int i = 0; targets[i] != NULL; i++) {
Status = try_chainload(Image, SystemTable, targets[i]);
if (!EFI_ERROR(Status))
return Status;
}
say(SystemTable->ConOut, L"No GRUB target could be chainloaded.\r\n");
return EFI_NOT_FOUND;
}
EOFperl -0777 -i -pe 's/^TARGET\t=\s*apple_set_os\.efi\s*$/TARGETS\t= apple_set_os.efi apple_set_os_grub_chain.efi\n\n# Backward compatible single-target alias\nTARGET\t= apple_set_os.efi/m;
s/^all:\s*\$\(TARGET\)\s*$/all: \$\(TARGETS\)/m;
s/^clean:\n\t.*$/clean:\n\trm -f \$\(TARGETS\) *.so *.o/m;' Makefilemake clean
make -j"$(nproc)"
ls -la *.efiYou should have:
apple_set_os.efiapple_set_os_grub_chain.efi
This guide installs it to: /boot/efi/EFI/tools/apple_set_os_grub_chain.efi
sudo mkdir -p /boot/efi/EFI/tools
sudo cp -f apple_set_os_grub_chain.efi /boot/efi/EFI/tools/apple_set_os_grub_chain.efi
sudo chmod 0644 /boot/efi/EFI/tools/apple_set_os_grub_chain.efiConfirm your GRUB/shim path exists (Mint often uses EFI/ubuntu if installed in Ubuntu-compatible mode):
sudo find /boot/efi/EFI -maxdepth 3 -type f -iname '*.efi' | sed -n '1,200p'Mint package (simple):
sudo apt install -y refind
sudo refind-installEdit:
/boot/efi/EFI/refind/refind.conf
Append:
# Chainload: set Apple OS vendor/version then boot GRUB
menuentry "Apple Set OS -> GRUB" {
icon /EFI/refind/icons/os_linux.png
loader /EFI/tools/apple_set_os_grub_chain.efi
}
Reboot and select Apple Set OS -> GRUB.
Create a systemd service:
sudo tee /etc/systemd/system/apple-set-os-dgpu-off.service >/dev/null <<'UNIT'
[Unit]
Description=Power off Radeon dGPU on dual-GPU Mac after boot (vga_switcheroo)
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'mountpoint -q /sys/kernel/debug || mount -t debugfs none /sys/kernel/debug'
ExecStart=/bin/sh -c 'test -e /sys/kernel/debug/vgaswitcheroo/switch && echo IGD > /sys/kernel/debug/vgaswitcheroo/switch || true'
ExecStart=/bin/sh -c 'test -e /sys/kernel/debug/vgaswitcheroo/switch && echo OFF > /sys/kernel/debug/vgaswitcheroo/switch || true'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now apple-set-os-dgpu-off.servicefor s in /sys/class/drm/card*-eDP-*/status; do
[ -r "$s" ] && echo "$(basename "$(dirname "$s")") : $(cat "$s")"
done | sort
dmesg | egrep -i 'fbcon:.*primary device|i915' | tail -n 50Expected:
- Intel
card?-eDP-?showsconnected fbcon: i915drmfb (fb0) is primary device
sudo cat /sys/kernel/debug/vgaswitcheroo/switchExpected:
DISshowsOffIGDshows+andPwr
sudo tlp-stat -s
sudo tlp-stat -gTLP will show the vgaswitcheroo status.
sudo systemctl disable --now apple-set-os-dgpu-off.service
sudo rm -f /etc/systemd/system/apple-set-os-dgpu-off.service
sudo systemctl daemon-reloadsudo rm -f /boot/efi/EFI/tools/apple_set_os_grub_chain.efiEdit /boot/efi/EFI/refind/refind.conf and delete the menuentry "Apple Set OS -> GRUB" { ... } stanza.
Use the Mac’s firmware boot picker (Option key) to boot your normal “Ubuntu” shim entry, or boot rEFInd and pick the default Mint/Ubuntu loader directly.
- On this hardware,
vgaswitcherooshowsDIS: Offeven if PCI runtime PM still readsactive. The important indicator is the switcheroo state and the fact that Intel eDP is connected. - For additional idle power tuning, use:
sudo tlp-stat -w(warnings)sudo tlp-stat -e(PCIe runtime PM)sudo tlp-stat -d(SATA ALPM)