Last active
March 2, 2026 11:04
-
-
Save stratomancer/c9488cdc80dfedf9e33213e17aa2dd61 to your computer and use it in GitHub Desktop.
Revisions
-
stratomancer revised this gist
Mar 2, 2026 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ # Linux Mint on MacBookPro 11,5: force Intel iGPU + power off Radeon dGPU (repro guide) 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. -
stratomancer created this gist
Mar 2, 2026 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,393 @@ # Linux Mint on MacBookPro 11,5: force Intel iGPU + power off Radeon dGPU (repro guide) This is a reproducible setup for **Linux Mint (UEFI)** 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: 1) **rEFInd** as the first-stage boot manager. 2) A small UEFI app that calls Apple’s private `apple_set_os` protocol (spoofs macOS vendor/version) **and then chainloads GRUB/shim**. 3) A **systemd oneshot service** that runs on every boot to switch to Intel and power off the dGPU via `vga_switcheroo`. --- ## 0) Assumptions / notes - 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.efi` first, so it works with Secure Boot setups too (assuming shim is properly installed). --- ## 1) Install required packages ```bash sudo apt update sudo apt install -y git make gcc gnu-efi efibootmgr pciutils tlp ``` (You may already have some of these.) --- ## 2) Confirm your GPUs and that GMUX exists ```bash 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 200 ``` You should see something like: - Intel iGPU: `00:02.0` (i915) - AMD dGPU: `01:00.0` (radeon) - `apple_gmux: Found gmux ...` --- ## 3) Build the EFI wrapper: `apple_set_os_grub_chain.efi` ### 3.1 Get the apple_set_os source ```bash 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-os ``` ### 3.2 Add the chainloading variant source Create a new file `apple_set_os_grub_chain.c`: ```bash 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; } EOF ``` ### 3.3 Update the Makefile to build both EFI binaries ```bash perl -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;' Makefile ``` ### 3.4 Build ```bash make clean make -j"$(nproc)" ls -la *.efi ``` You should have: - `apple_set_os.efi` - `apple_set_os_grub_chain.efi` --- ## 4) Install the wrapper into the ESP This guide installs it to: **`/boot/efi/EFI/tools/apple_set_os_grub_chain.efi`** ```bash 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.efi ``` Confirm your GRUB/shim path exists (Mint often uses `EFI/ubuntu` if installed in Ubuntu-compatible mode): ```bash sudo find /boot/efi/EFI -maxdepth 3 -type f -iname '*.efi' | sed -n '1,200p' ``` --- ## 5) Configure rEFInd to boot through the wrapper ### 5.1 Install rEFInd (if not already installed) Mint package (simple): ```bash sudo apt install -y refind sudo refind-install ``` ### 5.2 Add a rEFInd menu entry Edit: - `/boot/efi/EFI/refind/refind.conf` Append: ```conf # 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**. --- ## 6) Power off the Radeon dGPU after boot (persistent) Create a systemd service: ```bash 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.service ``` --- ## 7) Verification checklist ### 7.1 Verify internal panel is on Intel ```bash for 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 50 ``` Expected: - Intel `card?-eDP-?` shows `connected` - `fbcon: i915drmfb (fb0) is primary device` ### 7.2 Verify dGPU is off ```bash sudo cat /sys/kernel/debug/vgaswitcheroo/switch ``` Expected: - `DIS` shows `Off` - `IGD` shows `+` and `Pwr` ### 7.3 Use TLP to verify state ```bash sudo tlp-stat -s sudo tlp-stat -g ``` TLP will show the `vgaswitcheroo` status. --- ## 8) Rollback / recovery ### Remove the dGPU-off service ```bash 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-reload ``` ### Remove the wrapper ```bash sudo rm -f /boot/efi/EFI/tools/apple_set_os_grub_chain.efi ``` ### Remove the rEFInd menu entry Edit `/boot/efi/EFI/refind/refind.conf` and delete the `menuentry "Apple Set OS -> GRUB" { ... }` stanza. ### If you can’t boot 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. --- ## 9) Notes / tuning - On this hardware, `vgaswitcheroo` shows `DIS: Off` even if PCI runtime PM still reads `active`. 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)