Skip to content

Instantly share code, notes, and snippets.

@stratomancer
Last active March 2, 2026 11:04
Show Gist options
  • Select an option

  • Save stratomancer/c9488cdc80dfedf9e33213e17aa2dd61 to your computer and use it in GitHub Desktop.

Select an option

Save stratomancer/c9488cdc80dfedf9e33213e17aa2dd61 to your computer and use it in GitHub Desktop.

Revisions

  1. stratomancer revised this gist Mar 2, 2026. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion MacBookPro-force-Intel-GPU.md
    Original 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 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:
    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.
  2. stratomancer created this gist Mar 2, 2026.
    393 changes: 393 additions & 0 deletions MacBookPro-force-Intel-GPU.md
    Original 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)