Intel Graphics on a 2011 MacBook Pro in Linux

TerminalOne of the headaches of running Linux on a 2011 MacBook Pro is the bad battery life, heat generation, and the nearly incessant fan noise. As it turns out, this is largely caused by using the dedicated ATI high-power graphics card all the time, as it’s not as easy to get graphics-switching configured on Linux. On OSX, this feature comes out of the box, which is why battery life and heat-generation is so good. I finally got fed up with the heat, noise, and short battery life and took the plunge into configuring onboard Intel Graphics on my MacBook Pro.

What You’re Getting Into

This post is not for the faint of heart or for the faint of terminal. This post is also not for people who want graphics-switching; this post only addresses getting onboard Intel Graphics to work instead of the ATI discrete card. Perhaps I will update it at a future point with any success I’ve had in getting radeon to play nicely with the Intel Graphics and vga_switcheroo. However, for now, this is only about disabling ATI and enabling Intel Graphics for maximum battery life and minimal heat generation. This tutorial is primarily targeting Debian-based distributions, so my apologies if you’re running something else: you’ll just have to improvise. Gird your loins, gentlemen, this is going to be interesting.

Recompiling Your Kernel

Chances are, your kernel is not gonna work here. You need to have the Intel i915 driver compiled directly into your kernel (not just available as a module), so we’re going to go through a kernel compile. Let’s get to it.

We need to download all the packages we’ll need. Since you’re going to compile a kernel, we’ll need all the build tools for doing that:

sudo apt-get install build-essential kernel-package libncurses5-dev git

Great, now, let’s download the deb files for kernel 3.8.0 out of Raring Ringtail (as of time of writing not released). It’s important to get a kernel version of at least 3.3 due to built-in EFI booting support (if you want that, it’s not necessarily required).

wget http://mirrors.us.kernel.org/ubuntu//pool/main/l/linux/linux-source-3.8.0_3.8.0-19.29_all.deb
wget http://mirrors.us.kernel.org/ubuntu//pool/main/l/linux/linux-headers-3.8.0-19_3.8.0-19.29_all.deb
wget http://mirrors.us.kernel.org/ubuntu//pool/main/l/linux/linux-headers-3.8.0-19-generic_3.8.0-19.29_$(dpkg --print-architecture).deb

Now, we’re going to install these in a very specific order so as to not break dependencies in the package manager:

sudo dpkg -i linux-source-3.8.0_3.8.0-19.29_all.deb
sudo dpkg -i linux-headers-3.8.0-19_3.8.0-19.29_all.deb
sudo dpkg -i linux-headers-3.8.0-19-generic_3.8.0-19.29_$(dpkg --print-architecture).deb

Cool. Hopefully no error messages have appeared up to this point. Now that we have our kernel source and headers, it’s time to make some changes in our kernel’s compilation options. First, we’ll change into the kernel source directory, extract the kernel source, change into that directory, then copy over the existing config file for our kernel version:

cd /usr/src/linux-source-3.8.0/
sudo tar jxvf ./linux-source-3.8.0.tar.bz2
cd ./linux-source-3.8.0
sudo cp /usr/src/linux-headers-3.8.0-19-generic/.config ./
sudo vim .config

Time to edit that configuration file. I use vim but use whatever you’d like. Open up that new .config file in the current directory as root and find the following lines:

CONFIG_DRM_I915=m
CONFIG_DRM_I915_KMS=y

Since we need this module hard-compiled into our kernel, not as a module, modify that first line to replace the m with a y:

CONFIG_DRM_I915=y
CONFIG_DRM_I915_KMS=y

For my specific kernel configuration file, the line number is 4469.

Great. We’ve made the changes we need to make, so let’s start this compilation. I’m assuming you’re on a quad-core i7 MacBook Pro, so let’s compile with 8 threads for the best concurrency. This is going to take a while.

sudo make-kpkg --initrd --jobs 8 --append_to_version +igd kernel_image

Get yourself a coffee and take a break. This should take some time.

All done? No errors? If so, awesome! You should now see a deb file at ../linux-image-3.8.8+igd_3.8.8+igd-10.00.Custom_$ARCH.deb. (Replace $ARCH with either i386 or amd64 as is applicable.)

One more compilation to go, though this one’s going to be a lot shorter. It may not be strictly necessary to create a new headers deb file since we already have one installed for our kernel version, but making things match is always nice. Let’s generate that headers deb:

sudo make-kpkg --initrd --jobs 8 --append_to_version +igd kernel_headers

After that completes, we’re ready to go with our new kernel. Let’s install it now. First the headers package, then the kernel itself:

sudo dpkg -i /usr/src/linux-source-3.8.0/linux-source-3.8.0/linux-headers-3.8.8+igd_3.8.8+igd-10.00.Custom_amd64.deb
sudo dpkg -i /usr/src/linux-source-3.8.0/linux-source-3.8.0/linux-image-3.8.8+igd_3.8.8+igd-10.00.Custom_amd64.deb

Rock on! The kernel has been downloaded, patched, compiled, and now installed. Next, we’ll configure the boot loader to launch our kernel in EFI mode with some tweaks, hacks, and special configuration options.

Setting Up EFI Booting

This tutorial assumes that you already have some method of booting your Linux distribution on your MacBook Pro. If not… well, why are you reading this? If you need help along these lines, look into installing rEFInd. You’ll also need to at least be able to boot Linux in BIOS-emulation mode. If you’re already booting in EFI to Linux, some parts of the next section won’t apply exactly, but again, improvise.

First, let’s make sure that we have GRUB installed with EFI support. If you’re running a 32-bit operating system, you’ll need to install grub-efi. If you’re running a 64-bit operating system, you’ll need grub-efi-amd64. Please note that this will most likely wipe out BIOS GRUB, so you may need to boot into OSX if something doesn’t go as planned in order to fix things.

32-bit

sudo apt-get install grub-efi

64-bit

sudo apt-get install grub-efi-amd64

Now that it’s been installed, it’s time to make some good use of it.

First, we’ll mount our EFI partition in /boot/efi to make it easy to work with. (You may wish to do this in your fstab configuration to make things easier to tweak in the future.)

sudo mkdir /boot/efi
sudo mount -t vfat /dev/sda1 /boot/efi

If your EFI partition is somewhere other than /dev/sda1 for some reason, modify that line above accordingly.

Let’s change into the GRUB lib directory and make ourselves a nice EFI binary. If you already have a file at /EFI/BOOT/BOOTX64.EFI, you may want to compile the binary elsewhere to preserve the original EFI binary. (This file is a Windows 7+ EFI binary, but isn’t necessarily required as Windows usually installs multiple EFI binaries.)

cd /usr/lib/grub/*-efi/
test ! -d /boot/efi/EFI/BOOT && sudo mkdir /boot/efi/EFI/BOOT
sudo grub-mkimage -O "$(basename $(pwd))" -d . -o /boot/efi/EFI/BOOT/BOOTX64.EFI \
    -p "" part_gpt part_msdos ntfs ntfscomp hfsplus fat ext2 normal chain boot \
    configfile linux multiboot

Now, let’s copy all GRUB extension files to that directory so that our new shiny GRUB binary can load them at runtime.

sudo cp *.mod *.lst /boot/efi/EFI/BOOT/

Okay, so now we’ve successfully installed GRUB’s EFI binary. The last thing to do is to create our own GRUB configuration file for our boot options. Before we do that, you’ll need to get your root filesystem’s UUID to make booting happen properly. The easiest way to do this is with this script:

blkid -o value "$(df -P / | tail -1 | cut -d ' ' -f 1)" | sed '1q;'

That will spit out some strange looking value like “XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.” This is the UUID of your root filesystem. If you have a separate partitions for /boot, you’ll have to adjust the configuration file accordingly. In addition to the UUID, it’s important to note which partition your Linux root exists at. For me, since my root ext4 partition lives at /dev/sda4, I’ll be using a root of (hd0,gpt4).

Let’s wrap this all up by writing our GRUB configuration file. Open up /boot/efi/EFI/BOOT/grub.cfg in your favorite text editor as root and insert the following configuration:

menuentry 'Elementary OS Luna (Linux 3.8.0)' {
    insmod gzio
    insmod part_gpt
    insmod ext2

    set gfxpayload=keep

    # Switch gmux to IGD
    outb 0x728 1
    outb 0x710 2
    outb 0x740 2
    
    # Power down ATI 
    outb 0x750 0
    
    # Set root to /dev/sdXX
    set root='(hdX,gptX)'
    search --no-floppy --fs-uuid --set=root XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
 
    echo 'Loading Linux 3.7.0...'
    linux /boot/initrd.img-3.8.8+igd root=UUID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX ro \
        i915.lvds_channel_mode=2 i915.modeset=1 i915.lvds_use_ssc=0 \
        i915.i915_enable_rc6=1 acpi_backlight=vendor vt_handoff=7 quiet splash 
    
    echo 'Loading initial ramdisk...'
    initrd /boot/initrd.img-3.8.8+igd
}

Make sure that the following things have been updated to fit your installation in the above configuration file:

  1. If you’re running something other than Elementary OS, you can rename the boot entry by updating that string of text on the first line.
  2. Your root filesystem UUID. Remember this? It has the following format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. Replace all occurrences above with your UUID.
  3. Your hard drive number and partition number. See the line that looks like set root='(hdX,gptX)'? Replace that with the zero-indexed hard drive number and the one-indexed partition number. For my install, since my root partition is at /dev/sda4, this will mean that the line should look like set root='(hd0,gpt4').

Ok, so after you’ve quadruple-checked the configuration file to make sure that everything needing updating is good to go, we’ll proceed.

A Bit of an Explanation

One quick glance and you’ll realize that this isn’t your grandmother’s GRUB setup. We’re doing some pretty devious things here.

  1. First, we load up some modules we need for our custom implementation. Nothing too scary here.
  2. Next, we set the GFX payload to “keep” in order to preserve any previously defined graphics configuration. This isn’t so important here, but it could be if you customize things in the future.
  3. Now, we get super-devious. We’re going to change some low-level port memory values. Creepy, huh? These all send messages to Apple’s gmux device on the MacBook Pro.
    1. First, we modify the gmux switch select.
    2. Next, we switch display modes.
    3. Then, we switch the gmux DDC.
    4. Finally, we disable power to the ATI discrete card.
  4. Having accomplished most of the creepy stuff, we now set the root to our target boot filesystem.
  5. Next, we search for available kernels using our filesystem UUID.
  6. We then load linux. There are a few configuration variables here to make sure that things work properly specifically for our MacBook.
  7. Finally, we setup the initial ramdisk, and we’re on our happy way.

Black Screen?

After the outb 0x710 command, the display on your MacBook Pro will go black until the kernel gets to a point where it has loaded enough to enable the Intel Graphics kernel module. If you never see anything, the best thing to do would be to go back, edit that GRUB configuration file, and comment out all outb commands.

One Last Step

Unfortunately, it seems that the ATI device is powered on again after a resume from suspend or hibernate. However, we’re definitely not letting something like this stop us! Native code to the rescue!

Create a file called disable-ati.c in your home directory and fill it with the following:

#include <stdio.h>
#include <sys/io.h>

#define PORT_SWITCH_DISPLAY 0x710
#define PORT_SWITCH_SELECT 0x728
#define PORT_SWITCH_DDC 0x740
#define PORT_DISCRETE_POWER 0x750

static int gmux_switch_to_igd()
{
    outb(1, PORT_SWITCH_SELECT);
    outb(2, PORT_SWITCH_DISPLAY);
    outb(2, PORT_SWITCH_DDC);
    return 0;
}

static void mbp_gpu_power(int state)
{
    outb(state, PORT_DISCRETE_POWER);
}

int main(int argc, char **argv)
{
    if (iopl(3) < 0) {
        perror ("No IO permissions");
        return 1;
    }
    mbp_gpu_power(0);
    gmux_switch_to_igd();
    return 0;
}

Next, compile it with gcc:

sudo gcc -O2 -o /usr/local/sbin/disable-ati disable-ati.c

Now, we need to make sure that this script is called on resume to power down that nasty discrete card. Create a new file at /etc/pm/sleep.d/10-disable-ati and fill it with the following Bash code:

#!/bin/sh
#
# turn off the ATI card

case "${1}" in
    hibernate|suspend)
        # this is called when going to hibernate or to sleep
            ;;
    resume|thaw)
        # this is called when waking up
        /usr/local/sbin/disable-ati
            ;;
esac

Phew! Now, everything should be good. You should be able to boot into Linux, display everything using the Intel Graphics card, and have a LOT longer battery life and a LOT less heat. This has been a crazy-long process, but hopefully it'll make someone else's experience a little bit better and shorter.

Troubleshooting

  • I can't see the GRUB EFI bootloader!

    Bummer. Umm... are you running rEFInd as mentioned before? If not, then you're going to have a lot of problems. Make sure you're running that as your default EFI boot loader. How to do that is outside the scope of what we're talking about here.

  • My screen goes black and Linux never seems to boot.

    Open up that /EFI/BOOT/grub.cfg file and comment out all creepy outb calls. Chances are, there's something wrong there that is causing Linux to never even boot in the first place and you can't see it because your display is off.

  • X/lightdm/my display manager won't start!

    Make sure that you've completely nuked fglrx from orbit. That means:

    sudo apt-get remove fglrx*

    Anything less won't do.

  • I don't want to use GRUB!

    Neither do I, buddy. If you find a good workaround which only depends on rEFInd, let me know.

Hybrid-Graphics Switching?

Okay, now we're visiting awesome town. I did mention at the top of this article that hybrid graphics is well outside of the scope of what we're talking about here, but I did want to mention something. Don't get too excited, but it just may be possible to enable hybrid graphics in this mode. As far as I know, I believe that hybrid graphics requires a few basic changes:

  1. Don't disable the ATI card with that last outb 0x750 0 call in GRUB, and disable that resume script. Obviously, if you want to use the ATI card, it should be on.
  2. You need to use the radeon driver and not fglrx. All bets seem to indicate that fglrx absolutely can not be installed in order for this to work.
  3. You need vga_switcheroo enabled and a pretty new kernel.

    There was a patch in 2012 which seems to have updated everything in the kernel to make this a real possibility. Add a parameter to the Linux kernel in the GRUB configuration to set modeset=1 and vga_switcheroo should then be available.

If you're able to get hybrid graphics working, let me know in the comments below. I may just try doing this next to see if I can make it work. (First thing is to completely clone my MacBook with CloneZilla in the event that something goes wrong ;))

14 thoughts on “Intel Graphics on a 2011 MacBook Pro in Linux

  1. thx4 realy nice workshop.
    you’ve tested the vgaswitcheroo???
    plz let me know – if you have successfully:)
    if i modprobe my radeon module [open source driver] my mbp 8.3 is frozzzen.

    grzz marie

  2. Hi,

    i try to compil your disable-ati.c program but i need a file name for the first two

    #include at the beginning of the program. Or maybe I miss something

    Thanks for your help your tutorial is just what i am looking for.

  3. Looks like a good instruction, I’ll maybe try it, thanks!

    Honestly, I was looking the opposite: I need to enable ATI graphics to be able to connect external monitor. Are you aware of how this could be done?

    Currently I have this in grub.cfg:


    outb 0×728 1
    outb 0×710 2
    outb 0×740 2
    outb 0×750 0

    as without these lines Linux doesn’t even boots. As far as I know these lines disables ATI graphics. Can I just disable Intel graphics?

    Thanks in advance,
    Artem

    • Do you have the same laptop model?

      I had no problems whatsoever using both the radeon and the fglrx drivers for the discrete ATI card. In fact, a regular installation does just that, it installs radeon and uses that for the display driver. You’ll get better performance from fglrx, though, so download that from ATI and enjoy.

  4. Hi,

    Thanks for the tips. It also works on later Macbook Pros with and nVidia card.

    I’d like to package a small helper based on the code you quote to turn on/off the GPU. Can you license this code?

    Kind regards, Thibaut.

  5. Hey,

    Thanks for sharing this. I just did the same thign on Ubuntu 14.04 on my MacBook Pro and it worked like a charm with Ubuntu’s latest

    One change I had was to name the bootx64.efi all lowercase (otherwise it wasn’t being found properly).

    Also – I made 2 GRUB menu items – called one – ATI GPU, and another Intel GPU – and now at boot I can choose which type of GPU I actually want ;)

    If you figure out a way to be able to “switch” around the GPU’s that are active, that’d be great.

    The vga_switcharoo does not show up when I run on IGD, but it does otherwise, however, any commands sent to it never switch the GPU, just disable the inactive one.

  6. Hi,
    Running gentoo on an MBP 13, early 2011. If you have refind installed, you may get rid of grub and boot straight into your linux kernel from the refind boot menu.
    You need to make sure your kernel has the right EFI options turned on.
    I mount /dev/sda1 to /efi which contains
    EFI/ refind_linux.conf vmlinuz-3.17.3-gentoo vmlinuz-3.17.4-gentoo
    and
    /efi/EFI/BOOT/
    bootia32.efi bootx64.efi icons keys refind.conf

    I removed MacOSx completely and wiped the HFS+ partition which I now use as an extra partition for linux.

    This is a slightly different setup from what can be found on the refind web page. But it works great.

    Hope this helps those who want a 100% linux MBP.

    • If you boot straight via efi, you cannot issue the outb commands however. That way, the discrete chip is active by default. And while vgaswitcheroo could be used to switch to the intel chip, this does not work (the screen will just go blank). As far as I can tell, there’s absolutely no way to switch to the integrated intel graphics when the gmux was set to use the radeon chip on boot (something about the intel driver not being able to get the lvds parameters or similar probably), at least I never managed this.
      That said… you CAN switch those gmux bits when booting via refind/efi stub. By using the efi shell which lets you poke at random memory, including i/o addresses just like outb does… At some point you’ll lose the screen though so you have to type in the rest, exit the shell and select the right boot option without seeing anything – but it does work and switching seems too that way with some gotchas (suspend/resume didn’t seem to work right, I guess suspend/resume scripts invoking vgaswitcheroo could fix this). Oh and something else, quite surprisingly you must not switch off IGD if you’re using DIS otherwise the kernel spits out some errors and power consumption is noticeably higher compared if you just leave it powered on. Wasn’t really a practical solution though in any case so I gave up on that anyway.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>