Test-driving Linux on an Intel-based Macintosh

Introduction

I do have strange friends. Take Mark Smith, for example. Mark is essentially a "Windows Internals" guy -- he's OS-agnostic at best. In particular, he is certainly not a "Macintosh" person. However, he recently purchased an Intel-based Macintosh because he felt like running Linux (an operating system that he does not normally use) on it.

Then, there is Benjamin Reed, who is decidedly a Linux guy, although he owns and uses Macintosh computers, albeit with relentless complaints.

Somehow, they think of me (Amit) as a Mac OS X person — this is not true.

So it was this unlikely team that decided to see if we can get Linux to run on an Intel-based Macintosh.

We did succeed, and since Ben is somewhat of a Linux distribution connoisseur, we thought of releasing a prototype distribution for Intel-based Macintoshes. However, as far as "first-to-announce" is concerned, we were beaten to the punch by the Mactel Linux people, who announced their successful attempt yesterday.

Nevertheless, here is our technical description of how we ran Linux on a 17-inch iMac. Moreover, we are releasing a test-drive mini-distribution as an HFS+ bootable disk image. Simply burn it to a CD and boot with the option key pressed.

The Intel chip... For days, it's been trapped inside a Mac, inside a pretty little box, dutifully performing pretty little tasks when it could have been doing so much more. Starting today, the Intel chip will be set free, and get to live life in a Mac... running Linux. Imagine the possibilities.

The Extensible Firmware Interface (EFI)

Andrew Fish, an Intel engineer, invented EFI at his desk in the late 1990s, calling it Intel Boot Initiative (IBI) at that time. He offered his 26 page unsolicited white paper to his management. The paper was meant to be a response to major operating system and hardware companies rejecting legacy BIOS as the firmware for enterprise class Itanium® Processor Platforms.

Andrew said of EFI:

"EFI is an interface specification and it really is more about how to write an operating system loader and an Option ROM than it is about how to make a BIOS that works. The Intel® Platform Innovation Framework for EFI (Framework for short) is Intel's next generation firmware architecture from the ground up. The core chunks of this code are available under an Open Source license at www.TianoCore.org. Tiano was the developer code name while Framework was the marketing name."

We downloaded the EFI Development Kit (EDK), along with the EFI Shell, from TianoCore.org. Given the EDK, we built a nice development environment for EFI. It is interesting to note that the EFI shell built from the TianoCore sources has FAT support, whereas the EFI shell provided in Intel's pre-built sample EFI implementation does not. This is most easily observed by attempting to change to the EFI fat partition (fs0: normally). Attempts to access the file system through the pre-built shell will hang, whereas the TianoCore shell has full FAT support.

Elilo — the EFI-capable Linux Boot Loader

Elilo is an EFI-capable variant of Lilo, a Linux boot loader. Specifically, elilo is an EFI application (a Windows PE executable) that arranges for the Linux kernel to bootstrap from an EFI environment. As originally written, elilo expects a Linux-based EFI development environment. Since we used the Windows/TianoCore build environment, we first had to port elilo to this environment.

Let us look at the key modifications we made to elilo.

Elilo Modifications

Elilo makes extensive use of the EFI shell libraries, which are rather different across the Linux-based (older) and Windows-based (newer) EFI development kits. We contained most of the differences in a single "compatibility" header that we wrote: win_compat.h. Thereafter, we could mechanically replace EFI Foundation includes as follows:

#ifdef EDK_LINUX #include <efi.h> #include <efilib.h> #else #include "win_compat.h" #include <eficommon.h> #include <efiapi.h> #endif

Stack checking for EFI binaries is also stricter in the Windows EFI build environment — we had to move quite a few stack allocations to the heap.

Elilo also has a hardcoded limit (2 MB) on the size of the compressed kernel image (the so-called bzImage) it loads. Because we were generating compressed kernel images with large "ramdisks", we increased the limit appropriately.

The final substantive change we made to elilo was its parsing of its configuration file (elilo.conf). Elilo was unable to deal with either Mac-OS-X-style (\r) or Windows-style (\r\n) end-of-line (EOL) characters. Lines could only be terminated with a single newline (\n). We generalized this so that we could more easily generate configuration files on systems other than Linux.

Elilo Considerations

Many EFI systems, even though they may contain complete EFI implementations, are not exclusively EFI in that they contain legacy BIOS facilities. In fact, by default, elilo on such systems will attempt to load and jump to a kernel in "legacy" mode. Specifically, after loading the kernel into memory, it will switch to real mode before jumping to the kernel's start address, relying on the BIOS to take care of kernel requests, and allowing the kernel to make the switch back into protected mode. The Intel-based Macintosh is a completely legacy-free system — there is no BIOS at all. The legacy-free option to elilo is required to successfully boot a linux kernel on the Intel-based Macintosh.

The Linux Kernel

By default, a "stock" Linux kernel is not an EFI kernel. This means that when it receives control from the bootloader (be it an EFI bootloader or a legacy bootloader), it expects to be in real mode. As a first step, the kernel switches to protected mode before it uncompresses itself and starts running. Since we have no legacy facilities on the Intel-based Macintosh, we don't start the kernel in real mode. The legacy-free option to elilo ensures that the Linux kernel starts executing in protected mode. In particular, the EFI CONFIG_* kernel configuration options require selection. We started with the 2.6.15.3 kernel. After selecting the appropriate EFI options, our first (meaningful) experiment went as follows:

"Ok. Booting the kernel..."

A dark, dark place...

Many seconds passed as we watched the screen. Nothing. Did elilo hang? Did the kernel hang? We added a bunch of debugging output to elilo. It definitely thought it was passing control to the kernel. We compiled the ATI Radeon driver into the kernel (artificially adding the X1600 card's vendor-id and device-id reported by the Mac OS X ioreg utility). The goal was to enable framebuffer support. Still, nothing.

Unlike most PCs where we could get a serial port, a parallel port, or a simple-minded speaker beep to give us feedback to let us know the kernel was running, we had a harder time on the Macintosh. All we needed was a single bit of information coming back from the machine — anything we could make the computer do that we could perceive. We were stymied until Ben suggested that we make it reboot. We went to the kernel entry point, loaded a null interrupt descriptor table (lidt), and issued an interrupt (int 3), causing a "triple fault". Consequently, the machine would reboot. We ran elilo again. Seconds later, the screen went blank, as we heard the chime of a Macintosh booting up.

A place only slightly brighter than a dark, dark place...

Many hours of placing reboots at significant places in the kernel led us to the knowledge that the Radeon card was indeed getting detected. We had even refined our reboot technique by adding variable mdelay() calls before reboots: "Okay, if it reboots in 10 seconds, it took this path; if it reboots in 20 seconds, it took that one, if it doesn't reboot, it took the third path.". We even used the delay technique to check values of certain variables. For example, a significant find was when the machine delayed for 128 seconds before rebooting, one second for each detected megabyte of the graphics card's VRAM.

We also discovered that we were getting a valid physical address for the framebuffer, and that the driver was successfully mapping it into virtual memory. However, we were unable to produce anything on the screen by writing to the framebuffer. Finally, we simply tried writing blue pixels one-by-one starting from the mapped address, and looping forever. The blue screen of life. As quickly as it came, it went, as we plowed over memory past the end of the framebuffer.

A much brighter place...

After a little more tweaking, we discovered that the portion of the framebuffer mapped to the screen did not start until 64 KB into the framebuffer. We tried a few things to get the Radeon framebuffer device to work, but failed to make headway. However, since we were writing pixels to the screen, we might as well paint characters ourselves. We grabbed a font from Amit's Unix on the Game Boy Advance project, modified it to our needs, and wrote our own text-printing function by hijacking the kernel's earlyprintk() facility. We also commented out the Radeon's mode-switching functionality so it would stay in linear mode (and not switch to tiled mode).

We finally had words, and consequently, actual kernel messages. Interestingly, we also found that the linear framebuffer on the 17-inch iMac screen is not 1440x900, but actually 1472x900 — that is, the bytes-per-row value corresponds to 32 pixels wider than what you would expect. This is a point not lost on someone trying to write their own framebuffer primitives.

The true bytes-per-row count can be determined by looking at the output of CGDisplayBytesPerRow() on Mac OS X.

A light at the end of the tunnel...

With kernel messages coming out, debugging proceeded at a much faster pace. Although no exactly matching Linux drivers exist yet for the internal Ethernet interface, we used a Linksys 200M USB-based Ethernet adapter to network-enable the Macintosh. With functional networking, we could configure Linux to provide us an equivalent of a remote dmesg. We loaded the Knoppix Linux distribution onto an external USB disk, and were able to chroot to it.

We contemplated releasing screenshots similar to the ones released by the "Mac-Tel Linux" folks, but decided to wait until we could provide something interesting for others to try out. Now that our "first-to-market" incentive is no longer relevant, we are simply releasing what we have at this point.

Comparing Approaches

It was interesting to see the work of the Mactel-Linux folks. Our approach was similar to theirs on certain counts, but we solved some problems differently. Like them, we used elilo. However, they used the Linux EFI development environment to produce elilo builds, whereas we used the TianoCore EDK on Windows. Also, according to their post, they modified the vesafb driver to inherit the bootloader's framebuffer, whereas we use the Radeon driver to find the framebuffer address and pass that to a modified vesafb driver.

Download


ALL INFORMATION AND SOFTWARE ON THIS WEB SITE IS PROVIDED BY THE AUTHOR "AS IS", AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS INFORMATION OR SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Questions and Further Discussion

Please only use the following forum thread for reporting problems, asking questions, or for further discussion on this topic:

Forum Thread: Test-driving Linux on an Intel-based Macintosh

Release Notes

Take a test drive! Screenshots are nice, but nothing beats first-hand experience. Here is a bootable disc image that will let you trivially run Linux on your Intel-based Macintosh.

At a high level, simply download the image for your machine, burn it to a an optical disc, reboot while holding down the option key, wait for the boot chooser to appear, select the optical disc's icon, and watch Linux boot. Once it has booted completely, you can hit <enter> to get a shell. The distribution we placed in the ramdisk is sufficient to poke around to satisfy your curiosity. For the more enterprising, you can also mount and chroot to an external drive containing a full(er) Linux distribution — if you so desire.

Because the current version of the kernel (2.6.15.4) cannot properly detect the Intel Macintosh monitor, we are resorting to using hardcoded screen resolutions into the kernel. This necessitates two separate disc images — one for the 17-inch iMac, and another for the 20-inch one. Of course, this will eventually be "fixed".

Instructions

$ hdiutil burn linux-imac-17.dmg ... $ hdiutil burn linux-imac-20.dmg

$ cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 14 model name : Genuine Intel(R) CPU 1400 @ 1.83GHz stepping : 8 cpu MHz : 1833.346 cache size : 2048 KB physical id : 0 siblings : 2 core id : 0 cpu cores : 2 fdiv_bug : no hlt_bug : no f00f_bug : no coma_bug : no fpu : yes fpu_exception : yes cpuid level : 6 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx pni monitor vmx est tm2 xtpr bogomips : 3678.02 processor : 1 ...

Source / Patches

Linux Kernel and Elilo source diffs (raw and unclean)