Since 2012, at the last three jobs I’ve held as a software engineer, I’ve always used Linux natively on my work desktop or laptop. At some companies this was a choice, and at one it was mandatory. Most development shops give engineers the option of either a Windows PC or a Mac upon hiring them. However, my most recent shop did not. I considered simply running Linux in a virtual machine, however VirtualBox proved to be so slow that it was unusable, and I had some EFI booting issues with a demo of VMWare Fusion.
I probably could have worked through those issues, but instead I decided to take the leap and attempt to dual boot into Linux natively. A considerably amount of work has been done in the open source community to get modern Apple hardware working under Linux, much of it documented under the mbp-2016-linux project on GitHub. I was able to leverage quite a bit of that work to get a mostly working development environment. Although some things are still broken, I’m confident I can work through those issues, and hope this post can help other engineers who want to use modern MacBook Pros as powerful Linux development machines.
I’d barely had anything on the macOS partition of this fresh laptop, so I decided to go all in, shrinking it down as small as possible. With the macOS partition down to 60GB, I proceeded to boot into a Linux LiveCD and format the new empty partition as an LUKS encryption container. Within that container I created logical volumes for the root partition and swap. I won’t go into the details of that particular layout as partition setups will vary from person to person, however I will say that my
/boot folder was on the encrypted volume, and I relied on Grub’s builtin LUKS module to decrypt that partition and load the Linux kernel.
Device Start End Sectors Size Type /dev/nvme0n1p1 6 76805 76800 300M EFI System /dev/nvme0n1p2 76806 14715615 14638810 55.9G unknown /dev/nvme0n1p3 14715616 122105358 107389743 409.7G Linux filesystem /dev/nvme0n1p4 122105359 122138126 32768 128M Apple boot
I was surprised to discover that MacBooks don’t seem to have Secure Boot enabled. It was pretty trivial to simply plug in a USB flash drive containing a Ubuntu Live CD image, hold down the Alt key during the bootup process, and see the UEFI boot options for the USB stick. In my setup, I installed a Gentoo Linux image, but used Ubuntu installation media since it supported UEFI and Gentoo’s minimal USB media didn’t. The Gentoo Live CD does support UEFI, but at the time of this writing, it was pretty old and I wanted my best shot at Apple driver/hardware support. After booting, I came to my first challenge.
Keyboard and Mouse
On almost any modern day machine, whether it’s an x86/PC or an ARM laptop, the input devices (typically a keyboard and touchpad) are almost always attached internally to the Universal Serial Bus (USB). All of them except Apple, which on their modern laptops, decided to attach all their input devices, and that new fancy touch bar, to the Serial Peripheral Interface (SPI) bus. Luckily, there is a fork of cb22’s MacBook SPI kernel module, adapted by roadrunner2, which will enable the keyboard, touchpad and touchbar on the current generation of MacBooks. For these modules to work, the following kernel options must be enabled:
# menuconfig: Device Drivers -> SPI Support CONFIG_SPI=y # menuconfig: Device Drivers -> Multifunction device drivers -> # Intel Low Power Subsystem support in PCI mode CONFIG_X86_INTEL_LPSS=y CONFIG_MFD_INTEL_LPSS_PCI=y
I misread the instruction and didn’t realize I needed to enable the
LPSS options, listed above, as well. Without them, the touchbar was operational, but the keyboard and touchpad were not.
Bluetooth on these MacBooks is on a serial bus using the Broadcom protocol. Specifically, the
CONFIG_BT_HCIUART_BCM kernel option must be enabled. It first appeared in the mainline kernel starting in version 4.16-rc1. It might be difficult to get this option to show up in
make menucofnig as it has several dependencies which must be enabled in the right order.
# menuconfig: Device Drivers -> GPIO Support CONFIG_GPIOLIB=y # menuconfig: Device Drivers -> Character devices -> Serial drivers -> # Support for serial ports on Intel LPSS platforms CONFIG_SERIAL_8250_LPSS=y # menuconfig: Device Drivers -> Character devices -> Serial drivers -> # Support for Synopsys DesignWare 8250 quirks CONFIG_SERIAL_8250_DW=y # menuconfig: Device Drivers -> Character devices -> Serial device bus CONFIG_SERIAL_DEV_BUS=y # menuconfig: Device Drivers -> Character devices -> Serial device bus -> # Serial device TTY port controller CONFIG_SERIAL_DEV_CTRL_TTYPORT=y # Enabled with SERIAL_DEV_BUS + BT_HCIUART CONFIG_BT_HCIUART_SERDEV=y # menuconfig: Networking support -> Bluetooth subsystem support -> # Bluetooth device drivers -> Broadcom protocol support CONFIG_BT_HCIUART_BCM=y
With all these options enabled in a vanilla 4.16-rc2 kernel, Bluetooth worked right out of the box. Getting this particular Bluetooth adapter working on a Mac started over a year ago, with the most recent fixes committed and released to the mainline Linux tree less than a month before my attempt. Many of the problems I encountered, and debugging I attempted, involved reading the Bluetooth issue on the mbp-2016-linux Github project.
At first I didn’t encounter any of the audio sync and stuttering issues documented in the above issue while using A2DP headphones. Over time I didn’t get any real stuttering, but I did start to get sync issues with delays up to a second. While working in the office, I only use Bluetooth for listening to music and for my mouse. There isn’t any noticeable mouse lag, and the audio delay is acceptable for my limited use case of music immersion.
The display was one of the more puzzling issues. X11 worked out of the box using the open source
amdgpu drivers, however my USB-C connected monitor simply wouldn’t display anything when connected after X11 started. I attempted to initialize the external display with
xrandr, but the screen refused to turn on. Nothing appearing in the Xorg logs. Switching to the virtual console (via
Ctrl+Alt+F1) and back occasionally would cause the monitor to display. Oddly enough, there were two connected screens according to
Screen 0: minimum 320 x 200, current 6720 x 2160, maximum 16384 x 16384 eDP connected primary 2880x1800+3840+0 (normal left inverted right x axis y axis) 331mm x 207mm 2880x1800 60.00*+ 1920x1200 59.95 1920x1080 60.00 1600x1200 59.95 1680x1050 60.00 1400x1050 60.00 1280x1024 59.95 1440x900 59.99 1280x960 59.99 1280x854 59.95 1280x800 59.96 1280x720 59.97 1152x768 59.95 1024x768 59.95 800x600 59.96 848x480 59.94 720x480 59.94 640x480 59.94 DisplayPort-0 disconnected (normal left inverted right x axis y axis) DisplayPort-1 disconnected (normal left inverted right x axis y axis) DisplayPort-2 connected 3840x2160+0+0 (normal left inverted right x axis y axis) 600mm x 340mm 3840x2160 60.00*+ 4096x2304 60.00 3200x1800 60.00 2560x1440 60.00 640x480 59.94 DisplayPort-3 connected (normal left inverted right x axis y axis) 2560x2880 60.00
I discovered that the third display always needed to be disabled using
xrandr --output DisplayPort-3 --off. I’m not sure if it’s an issue with the
amdgpu drivers or the Apple hardware, but with the third output enabled, I’d occasionally get artifacts where windows on my monitor would occupy a lower resolution bounding box than the available display area:
I suspect the 3rd Display Port screen is somehow being overlapped onto the 2nd Display Port in some weird buggy driver voodoo. This still didn’t solve the problem of the 3rd Display Port not turning back on after waking up from the sleep state, or not being enabled when X starts. Eventually I saw the following in the kernel logs:
[13221.797955] [drm:amdgpu_atombios_dp_link_train [amdgpu]] *ERROR* channel eq failed: 5 tries [13221.797971] [drm:amdgpu_atombios_dp_link_train [amdgpu]] *ERROR* channel eq failed
Those messages led me to a forum post1 where a user suggested the video adapter was having difficulty making the signal connection at the maximum/preferred refresh rate. That user suggested either lowering and raising the resolution or refresh rate to try to re-enable the monitor. This seemed consistent with my monitor turning on after debugging with random mode switches or switching back and fourth from the virtual console. Eventually I created the following script to reliably enable the external monitor:
#!/bin/sh xrandr --output DisplayPort-2 --mode 640x480 xrandr --output DisplayPort-2 --mode 3840x2160
Even though I’m dropping and increase the resolution, I use the i3 tiling window manager, keeping all my windows in the same positions and proportions even after the mode switch.
One of the most daunting challenges involved power/battery. If I ran
acpi, I didn’t get any battery information. I had no battery sensors appearing in
/proc either. If I disconnected the USB-C power connector while X was running, the laptop immediately and abruptly shutdown. If I started the Mac on battery, it would boot, get past the Grub screen, begin to load Gentoo, and then cut off once it tried to go into graphical mode by starting X.
I couldn’t find anyone else who seemed to have this issue. I suspected it was a problem with the
amdgpu drivers. I attempted to follow guides to blacklist the AMD graphics and force use of the Intel integrated graphics, but they left me with a non-functional X server. After disabling
xdm while booting my machine, I encountered the same cut-off problem while the power was unplugged. I then realized it had nothing to do with X or the AMD graphics drivers.
The problem was that I had cloned the image from a previous install which had TLP services running. TLP is a power management tool for Linux laptops. I suspect TLP misinterpreted the MacBook’s missing battery status as the laptop running low on power and immediately forced a shutdown. Disabling the TLP service fixed the issue.
As of writing, Wi-Fi still doesn’t work. The adapter shows up, but it cannot access the 5Ghz frequency and only connect to access points at very close range, rendering it effectively useless. The USB-C Ethernet adapter attached to the back of my monitor works perfectly though. Suspend/resume doesn’t work either. During the week before I solved the above TLP/power issue, the MacBook Pro running Linux was effectively an overpriced desktop with shitty drivers. Now I can take it to meetings, even if running offline.
My immediate supervisor had misgiving about me running Linux in dual boot mode and would prefer I simply run Linux in VMWare Fusion as a virtual machine. I realize a good hypervisor can reduce the performance hit, however I’d still be limited in accessing the full resources of the hardware and I’d still have to deal with managing two conflicting sets of keybindings. Furthermore, running Linux and being part of the testing community could help contribute to better hardware support. My previous trial of getting hardware working on an MSI laptop involved testing firmware and helping others with similar Wi-Fi adapters in several other laptops.
Overall, getting Linux running on the MacBook Pro 14,3 only took about one full Sunday, and much of that time was waiting on the kernel to recompile, rebooting and testing. Although frustrating at times, there is something enjoyable in the challenge in figuring out the pieces and making it all work. Even though I didn’t do any of the driver work, there is a certain sanctification in assembling the efforts of other developers into a working system. It’s like building an Ikea bookshelf.
It helps me as a developer appreciate all the work that goes into hardware and drivers, as well as increase my full understanding of the underlying technology I use. If I purchase a Wi-Fi dongle, I believe this MacBook pro will be fully suitable for my role as a developer, using an operating system I love to work with.