Test-driving Linux on an Intel-based Macintosh
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 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:
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.
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 188.8.131.52 kernel. After selecting the appropriate EFI options, our first (meaningful) experiment went as follows:
- We blessed the
GraphicsConsole.efiprogram, which is technically an EFI driver, causing it to be "booted" by Apple's firmware implementation.
- From the graphics console, we launched the EFI shell we built.
- Then, we ran elilo, providing it the kernel we built.
"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
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.
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
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:
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 (184.108.40.206) 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".
- Download the HFS+ bootable disc image by clicking on the applicable download link (17-inch or 20-inch).
- Burn the disc image to a writable optical disc. For example, you can do it from the Mac OS X Disk Utility application, or from the Mac OS X command line:
$ hdiutil burn linux-imac-17.dmg
$ hdiutil burn linux-imac-20.dmg
- Hold down the option key. You'll hear the CD spin up, and the CD icon will appear.
- Select the CD icon.
- Watch Linux boot (Notice the two penguins for the Duo dual processor).
<Enter>for a command shell.
- Enjoy the Busy Box distribution. Be sure to look around in the
- Imagine the possibilities...
$ 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