01 Jul 2013

Booting a full system with Ovmf

QEMU supports exporting a directory structure in the host machine filesystem as a block device containing a FAT filesystem. If you think about it, that's pretty much magic. This is *really* handy for kernel (U)EFI feature testing, since it means I can just throw a Linux kernel (with an EFI stub) into a directory and then be able to boot that from within the UEFI running in QEMU.

So, in order to test an x64 Linux kernel with my build of upstream Tianocore UEFI, here is what I do: First, I build a kernel with an EFI stub (to make it directly executable from UEFI).

    $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
    $ cd linux
    $ make defconfig
    $ make menuconfig
    *** Enable: Processor types and features -> EFI stub support 
    $ make -j8 bzImage

VoilĂ ! You now have a kernel image, loadable either as a plain old bzImage, or executable directly from within UEFI. You can copy this image into the EDK2 build output directory, which will make it appear within *fs0:* in the EFI shell. You need to also rename it, giving it a ".efi" file ending, in order for it to be executable by the shell.

    $ cp arch/x86/boot/bzImage ../edk2/Build/OvmfX64/DEBUG_GCC46/X64/bzImage.efi

So, if you now launch EDK2 in QEMU and simply call the kernel:

    Shell> bzImage.efi

... you should end up with the penguin and a whole bunch of normal kernel boot messages, up until the point where it crashes and burns horribly because you haven't told it where its root filesystem lives (silly!). Fortunately, this is easily done - you just pass paramaters to the kernel as if it were any other command in the EFI shell. However, you of course need to have some filesystem to point it to.

Now, in order to boot to a prompt, I still need a minimum of an initrd - but since I'm doing things like bootloader experiments, I probably want a full filesystem. So I generate a Debian disk image (since my workstation is an x64 Wheezy.

    $ dd if=/dev/zero of=x64-uefitest.img bs=1M count=4096
    $ /sbin/gdisk x64-uefitest.img
    *** Add one partition of type EFI System, and one Linux fs
    $ sudo su
    # /sbin/kpartx -a x64-uefitest.img 
    # mkfs.vfat /dev/mapper/loop0p1
    # mkfs.ext4 /dev/mapper/loop0p2
    # mount /dev/mapper/loop0p2 /mnt
    # debootstrap --include=grub-efi,efibootmgr jessie /mnt http://ftp.uk.debian.org/debian/
    # echo "x64-uefitest" > /mnt/etc/hostname
    *** Create fstab (/proc, /sys et. al. handled by udev?)
    # chroot /mnt /usr/bin/passwd
    # cp /etc/apt/sources.list /mnt/etc/apt
    # umount /mnt
    # kpartx -d x64-uefitest.img
    # exit

And then, from your EDK2 directory:

    ./OvmfPkg/build.sh -a X64 -t GCC46 qemu -hda fat:/work/linaro/uefi/edk2-github/Build/OvmfX64/DEBUG_GCC46/X64 -hdb /work/filesystems/x64-uefitest.img

This sets up your UEFI build output directory as the primary hard drive/filesystem (fs0: in UEFI, /dev/sda1 in Linux) and the image file as the secondary block device (EFI System partition becomes fs1:/sdb1, Linux root partition becomes sdb2). (When you start adding more QEMU parameters to the build/launch script, it strips the default ones - so the -hda ... parameter telling it to set up the virtual FAT partition must be explicitly specified.)

And once at the EFI shell prompt, explicitly change to the correct EFI System partition, since we now have two, and start the kernel:

    Shell> fs0:
    fs0:\> bzImage.efi console=ttyS0 root=/dev/sdb2 ext4

All done!

Update: actually, using efibootmgr (which you may need to install from within the guest with 'apt-get install efibootmgr'), you can make the system auto-boot on subsequent launches by executing:

    efibootmgr -c -l \\bzImage.efi -L "EFISTUB" -u "$(cat /proc/cmdline)"

Update again: doh! That auto-booting is only persistent over reboots - not over actually shutting QEMU down.

posted at: 13:37 | path: /uefi | permanent link to this entry