Skip to content

Instantly share code, notes, and snippets.

@delucamd
Last active March 25, 2026 12:06
Show Gist options
  • Select an option

  • Save delucamd/b5b29465b4275a9ff9f2b725aa9866e3 to your computer and use it in GitHub Desktop.

Select an option

Save delucamd/b5b29465b4275a9ff9f2b725aa9866e3 to your computer and use it in GitHub Desktop.
Disabling NVIDIA GPU on MacBook Pro

Technical Guide: Disabling NVIDIA GPU on MacBook Pro 11,3 (Linux)

Target System: MacBook Pro Retina 15" (Late 2013, Model 11,3)
OS: Linux (Mint/Ubuntu/Debian based)
Objective: Completely power down the dedicated NVIDIA GT 750M to save battery and reduce heat, relying solely on the Integrated Intel Iris Pro graphics.


Phase 0: Install rEFInd

The MacBook Pro firmware decides which hardware to expose based on what OS it thinks it is booting.

If it thinks it's Windows/Linux: It often forces the NVIDIA card ON (for compatibility) and hides the Integrated GPU or the GMUX controls.

If it thinks it's macOS: It exposes the GMUX controller and leaves the power management logic intact.

1. Install the Package:

sudo apt install refind

During installation, it will ask: "Do you want to install rEFInd to the ESP?"

Select YES.

2. Configure the Spoof

sudo nano /boot/efi/EFI/refind/refind.conf

Ctrl+W to search for spoof_osx_version.

Remove the # to uncomment it.

spoof_osx_version 10.9

Save and Exit.

Phase 1: The EFI Firmware Fix

Critical: Tell the system to use the iris chip instead of nvidia. Injects a variable into the Mac's firmware to reset GPU power preferences. If you ever reset your PRAM (Command+Option+P+R), you must run efi-inject.sh again.

1. efi-verify.sh

Use this to check if the firmware variable is currently set.

#!/bin/bash
# VERIFY EFI GPU PREFERENCES
# Checks if the gpu-power-prefs variable exists in NVRAM.

EFI_VAR="/sys/firmware/efi/efivars/gpu-power-prefs-fa4ce28d-b62f-4c99-9cc3-0c68ec427f89"

echo "Checking EFI Firmware..."
if [ -f "$EFI_VAR" ]; then
    echo "STATUS: FOUND"
    echo "The GPU Power Preference variable is present."
    
    # Optional: Read the value (Hex dump)
    # Valid/Safe values are usually 01 00 00 00 or similar minimal headers.
    hexdump -C "$EFI_VAR"
else
    echo "STATUS: MISSING"
    echo "WARNING: The variable is not set. If you reset PRAM, the system may freeze on boot."
    echo "Run ./efi-inject.sh immediately."
fi

2. efi-inject.sh

Use this to inject the variable. Run this as sudo.

#!/bin/bash
# RESTORE EFI GPU PREFERENCES
# Injects the 'gpu-power-prefs' variable to prevent boot hang on MBP 11,3.

# 1. The Target Variable (UUID: fa4ce28d-b62f-4c99-9cc3-0c68ec427f89)
EFI_VAR="/sys/firmware/efi/efivars/gpu-power-prefs-fa4ce28d-b62f-4c99-9cc3-0c68ec427f89"

# 2. Check if efivarfs is mounted (Required to write)
if ! mount | grep -q "efivarfs"; then
    echo "Mounting efivarfs..."
    mount -t efivarfs efivarfs /sys/firmware/efi/efivars
fi

# 3. Unlock the variable (if it exists, it's often write-protected)
if [ -f "$EFI_VAR" ]; then
    echo "Variable exists. Unlocking..."
    chattr -i "$EFI_VAR"
fi

# 4. Inject the Safe Value
# The header (0x07 0x00 0x00 0x00) + Clean Data (0x00 0x00 0x00 0x00)
# This clears any "Force Discrete" flags set by macOS.
echo "Injecting safe GPU preferences..."
printf "\x07\x00\x00\x00\x00\x00\x00\x00" > "$EFI_VAR"

# 5. Verify write
if [ $? -eq 0 ]; then
    echo "SUCCESS: Variable written."
    # Lock it again to prevent accidental changes
    chattr +i "$EFI_VAR"
else
    echo "ERROR: Failed to write to EFI. Are you running as root?"
fi

Phase 2: Boot Configuration (GRUB)

We need to load the standard open-source driver (nouveau) so the system recognizes the card and creates the "switch" file. We also force Active State Power Management (ASPM) to allow the hardware to sleep.

  1. Edit GRUB Config:

    sudo nano /etc/default/grub
  2. Modify the Line: Find GRUB_CMDLINE_LINUX_DEFAULT. Change it to match this exactly:

    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash pcie_aspm=force"
    

    (Note: Do NOT add nouveau.modeset=0. We need the driver to load.)

  3. Update GRUB:

    sudo update-grub
  4. Reboot: Restart your computer.


Phase 3: The Automation Script (rc.local)

The OS will boot with the card ON. We need a script to flip the hardware switch to OFF automatically after logging in.

  1. Create/Edit rc.local:

    sudo nano /etc/rc.local

    (If the file is new, make sure it starts with #!/bin/bash).

  2. Paste the Kill Logic:

    #!/bin/bash
    # IRIS KEEPER: NVIDIA POWER CUT
    # Description: Turns off discrete GPU on MBP 11,3 via vgaswitcheroo
    
    # 1. Wait for desktop and drivers to fully load
    sleep 10
    
    # 2. Force the switch to OFF
    if [ -f /sys/kernel/debug/vgaswitcheroo/switch ]; then
        echo OFF > /sys/kernel/debug/vgaswitcheroo/switch
    fi
    
    # 3. Optional: Cut the Audio too (if not already handled)
    if [ -d /sys/bus/pci/devices/0000:01:00.1 ]; then
        echo auto > /sys/bus/pci/devices/0000:01:00.1/power/control
    fi
    
    exit 0
  3. Make Executable:

    sudo chmod +x /etc/rc.local
  4. Enable the Service (Systemd systems): On newer versions of Linux (like Ubuntu 20.04+ and Mint). Systemd considers rc.local "legacy," so while the service exists, it isn't configured to start automatically by default.

    We need to manually add the "Install" instruction to the service file so systemctl enable works.

    1. Edit the Service Unit: Open the systemd service file for rc-local:
    sudo nano /lib/systemd/system/rc-local.service
    1. Add the Install Section: Scroll to the very bottom of the file. If you don't see an [Install] section, paste this block at the end:
    [Install]
    WantedBy=multi-user.target

    Note: If the file is completely empty (unlikely), paste this standard configuration:

    [Unit]
    Description=/etc/rc.local Compatibility
    ConditionFileIsExecutable=/etc/rc.local
    
    [Service]
    Type=forking
    ExecStart=/etc/rc.local start
    TimeoutSec=0
    RemainAfterExit=yes
    GuessMainPID=no
    
    [Install]
    WantedBy=multi-user.target
    1. Reload and Enable: Now that we've told systemd when to run it (multi-user.target), we can enable it.

    Reload systemd manager configuration:

    sudo systemctl daemon-reload

    Enable the service:

    sudo systemctl enable rc-local

    (This should now output Created symlink ... without error).

    Start it immediately (to test):

    sudo systemctl start rc-local

    Check status:

    sudo systemctl status rc-local

    Target: You want to see Active: active (exited).

    Note: If it says failed, check that your /etc/rc.local file is executable (sudo chmod +x /etc/rc.local) and has #!/bin/bash at the top.

    1. Ensure rc-local is active: (some modern distros disable it by default):
    sudo systemctl enable rc-local
    sudo systemctl start rc-local

Phase 4: Verification Checks

Use these commands to confirm the fix is working.

1. The Switch Check

Confirm the software switch is in the "OFF" position.

sudo cat /sys/kernel/debug/vgaswitcheroo/switch
  • Success: 0:DIS: :Off:0000:01:00.0
  • Failure: 0:DIS: :Pwr:0000:01:00.0

2. The Sensor Check

Confirm the hardware is cold (Sensor should be missing or N/A).

sensors
  • Success: No nouveau adapter listed, or temp is N/A.
  • Failure: nouveau-pci-0100 ... temp1: +45.0°C

Appendix: The Toolbox (Recovery & HDMI)

Since the HDMI port is wired to the NVIDIA card, you need a quick way to turn the card back ON if you want to use an external monitor.

1. gpu-restore.sh (Turn NVIDIA Back On)

Use this if you need to plug in an HDMI cable or if the system isn't detecting screens correctly.

#!/bin/bash
# RESTORE NVIDIA GPU (For HDMI/DP Output)

echo "Turning NVIDIA Card ON..."
if [ -f /sys/kernel/debug/vgaswitcheroo/switch ]; then
    echo ON | sudo tee /sys/kernel/debug/vgaswitcheroo/switch
    echo "Success. NVIDIA card is powered up."
    echo "You may need to log out/in for xrandr to detect the new ports."
else
    echo "Error: Switcheroo file not found."
fi

2. gpu-verify.sh (Quick Status Check)

A simplified version of the monitor script to quickly answer: "Is it on or off?"

#!/bin/bash
# CHECK GPU POWER STATE

echo "--- SWITCH STATUS ---"
if [ -f /sys/kernel/debug/vgaswitcheroo/switch ]; then
    grep "DIS" /sys/kernel/debug/vgaswitcheroo/switch
else
    echo "vgaswitcheroo: NOT FOUND"
fi

echo "--- POWER SENSORS ---"
# Check if nouveau is reporting temp (Alive) or if it's gone (Dead)
if sensors | grep -q "nouveau"; then
    TEMP=$(sensors | grep -A 2 "nouveau" | grep "temp1" | awk '{print $2}')
    echo "NVIDIA Sensor: ALIVE ($TEMP)"
else
    echo "NVIDIA Sensor: DEAD (No data = Good for battery)"
fi

3. gpu-audit.sh

Check to see if iris is actually being used by the system.

#!/bin/bash
# =================================================================
#  IRIS KEEPER: SYSTEM AUDIT TOOL (V3 - ROBUST)
# =================================================================
#  Checks the health of the MacBook Pro 11,3 graphics fix.
# =================================================================

echo "--- STARTING SYSTEM AUDIT ---"
echo "Date: $(date)"
echo "-----------------------------------------------------------"

# 1. CHECK FIRMWARE (EFI)
echo -n "[1/5] Checking EFI Firmware Fix... "

# Method: Find the variable by name, ignore path strictness
# We search for 'gpu-power-prefs' inside the efivars folder
if [ ! -d "/sys/firmware/efi/efivars" ]; then
    # Try to mount if missing
    mount -t efivarfs efivarfs /sys/firmware/efi/efivars >/dev/null 2>&1
fi

EFI_FOUND=$(find /sys/firmware/efi/efivars -name "*gpu-power-prefs-*" 2>/dev/null)

if [ -n "$EFI_FOUND" ]; then
    echo "PASS"
else
    echo "FAIL (Variable not found)"
    echo "      -> The system is working, but the tool can't see the file."
    echo "      -> If you reboot and get a black screen, run restore-screen.sh."
fi

# 2. CHECK BOOT PARAMETERS (GRUB)
echo -n "[2/5] Checking Boot Parameters...  "
if grep -q 'acpi_osi=Darwin' /proc/cmdline; then
    echo "PASS"
else
    echo "FAIL (Missing acpi_osi=Darwin)"
    echo "      -> Edit /etc/default/grub -> GRUB_CMDLINE_LINUX_DEFAULT."
fi

# 3. CHECK ACTIVE DISPLAY (Hardware ID Check)
echo -n "[3/5] Checking Active Display...   "

# Find the connector that is "connected" (Internal Screen)
STATUS_FILE=$(grep -l "^connected$" /sys/class/drm/*/status 2>/dev/null | grep -E "eDP|LVDS|DP" | head -n 1)

if [ -n "$STATUS_FILE" ]; then
    # Resolve the full physical path of the connector directory
    CONN_DIR=$(dirname "$STATUS_FILE")
    PHYS_PATH=$(readlink -f "$CONN_DIR")
    
    # Go up directories until we find the PCI ID (vendor file)
    # Structure: .../pci0000:00/0000:00:02.0/drm/card0/card0-eDP-1/
    # We look for the folder containing the file 'vendor'
    
    PCI_DIR="$PHYS_PATH"
    while [ "$PCI_DIR" != "/" ]; do
        if [ -f "$PCI_DIR/vendor" ]; then
            break
        fi
        PCI_DIR=$(dirname "$PCI_DIR")
    done
    
    if [ -f "$PCI_DIR/vendor" ]; then
        VENDOR_ID=$(cat "$PCI_DIR/vendor")
        # Intel is 0x8086. NVIDIA is 0x10de.
        if [[ "$VENDOR_ID" == "0x8086" ]]; then
            echo "PASS (Intel Chip Active)"
        elif [[ "$VENDOR_ID" == "0x10de" ]]; then
            echo "FAIL (NVIDIA Chip Active)"
        else
            echo "WARN (Unknown Vendor: $VENDOR_ID)"
        fi
    else
        echo "FAIL (Could not trace hardware path)"
    fi
else
    echo "FAIL (No connected screen found?)"
fi

# 4. CHECK BACKLIGHT
echo -n "[4/5] Checking Brightness Keys...  "
if lsmod | grep -q "apple_gmux"; then
    echo "PASS (apple_gmux loaded)"
else
    echo "FAIL (apple_gmux missing)"
fi

# 5. CHECK 3D ACCELERATION
echo -n "[5/5] Checking 3D Acceleration...  "
if command -v glxinfo >/dev/null; then
    if glxinfo -B 2>/dev/null | grep -iE "Intel|Iris" >/dev/null; then
        echo "PASS"
    else
        RENDERER=$(glxinfo -B 2>/dev/null | grep "Device:")
        echo "FAIL (Renderer: $RENDERER)"
    fi
else
    echo "SKIP (glxinfo missing)"
fi

echo "-----------------------------------------------------------"
echo "AUDIT COMPLETE"

4. The Power Monitor Script (gpu-monitor.sh)

Create this file to watch your battery draw in real-time.

#!/bin/bash
# IRIS KEEPER: Power Monitor (Extended)
# Dependency: bc (sudo apt install bc)

while true; do
    clear
    echo "=== IRIS POWER MONITOR ==="
    echo "Time: $(date +%T)"
    echo "--------------------------"

    # 1. Check Software Switch (vgaswitcheroo)
    if [ -f /sys/kernel/debug/vgaswitcheroo/switch ]; then
        SWITCH_STATE=$(grep "DIS" /sys/kernel/debug/vgaswitcheroo/switch)
        echo "Switcheroo: FOUND"
        echo "State:      $SWITCH_STATE"
    else
        echo "Switcheroo: NOT FOUND"
    fi
    echo "--------------------------"

    # 2. Check Hardware Link (PCIe Bridge)
    if command -v lspci &> /dev/null; then
        # Check bridge 00:01.0 (The gateway to the GPU)
        LINK_INFO=$(sudo lspci -vv -s 00:01.0 2>/dev/null | grep -E "LnkSta:|Power")
        
        SPEED=$(echo "$LINK_INFO" | grep -o "Speed [^,]*")
        WIDTH=$(echo "$LINK_INFO" | grep -o "Width [^,]*")
        
        if [[ -z "$SPEED" ]]; then
            echo "PCIe Link:  [Reading Error]"
        else
            echo "PCIe Link:  $SPEED, $WIDTH"
        fi
    fi
    echo "--------------------------"

    # 3. Check Battery Metrics
    if [ -d /sys/class/power_supply/BAT0 ]; then
        STATUS=$(cat /sys/class/power_supply/BAT0/status)
        
        # Raw values are usually in micro-units (uV, uA)
        VOLTAGE_RAW=$(cat /sys/class/power_supply/BAT0/voltage_now)
        CURRENT_RAW=$(cat /sys/class/power_supply/BAT0/current_now)
        
        # Convert to human readable (Volts, Amps)
        VOLTS=$(echo "scale=2; $VOLTAGE_RAW / 1000000" | bc)
        AMPS=$(echo "scale=3; $CURRENT_RAW / 1000000" | bc)
        
        # Calculate Watts: (uV * uA) / 10^12
        WATTS=$(echo "scale=2; ($VOLTAGE_RAW * $CURRENT_RAW) / 1000000000000" | bc)
        
        echo "Battery:    $STATUS"
        echo "Voltage:    $VOLTS V"
        echo "Current:    $AMPS A"
        echo "Power Draw: $WATTS W"
    else
        echo "Battery:    NOT DETECTED"
    fi
    
    echo "=========================="
    echo "Press [CTRL+C] to exit"
    sleep 2
done

Target Power Draw:

  • Idle (Desktop): ~10W - 15W
  • Load: > 20W
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment