Boot Modes
When a computer is first turned on, some built-in firmware typically runs, which then hands off the boot process to the bootloader, which is a piece of code that loads the operating system kernel from persistent storage and starts the kernel running. From that point, the operating system takes over control of the computer.
The exact way that the bootloader itself is loaded, and even which bootloaders are supported, varies from architecture to architecture. Moreover, on some architectures, the boot process can vary significantly between different devices, even if they have the same type of CPU. On PC-type devices with AMD or Intel processors, we normally only have to contend with two different boot modes: legacy (BIOS) and UEFI. However, on Arm-based systems, there is no one “standard” way to boot, and device vendors routinely implement their own approaches.
Basic Input-Output System (BIOS)
Historically, PC-type hardware used firmware called the Basic Input-Output System, or BIOS, to handle hardware initialization and the loading of the bootloader. While BIOS was reliable, it contained a lot of legacy features that are not applicable to today’s computer systems. Nevertheless, when emulating i386 or x86_64, QEMU defaults to using an open-source BIOS implementation, called SeaBIOS,1 to get the virtual machine up and running.
Unified Extensible Firmware Interface
As a general rule, today’s PC-type devices (the ones with AMD and Intel CPUs, at least) usually support the Unified Extensible Firmware Interface (UEFI), which provides a more flexible pre-boot environment compared to BIOS.2 Booting with UEFI requires the use of a UEFI-compatible bootloader and requires a different boot configuration to specify what order to use when multiple bootable devices are present on the system. Some systems may still provide a BIOS interface with UEFI firmware by including a Compatibility Support Module (CSM) as an optional feature.
Intel released an open-source reference implementation of UEFI firmware called TianoCore.3 Recent versions of QEMU include TianoCore support for the i386, x86_64, Arm (32-bit), and AArch64 (64-bit Arm) architectures. The PC System Flash interface allows TianoCore to be used instead of the legacy SeaBIOS implementation when starting a QEMU VM.4
The TianoCore code is split into two files: a read-only file containing the actual UEFI code, and a read/write file for storing UEFI variables (such as boot preferences). We can use the read-only file from its default location in the QEMU share directory; however, each VM that uses UEFI needs to have its own copy of the variables file.
Architecture | UEFI Code File | UEFI Variables File |
---|---|---|
i386 | edk2-i386-code.fd | edk2-i386-vars.fd |
x86_64 | edk2-x86_64-code.fd | edk2-i386-vars.fd |
Arm (32-bit) | edk2-arm-code.fd | edk2-arm-vars.fd |
AArch64 | edk2-aarch64-code.fd | edk2-arm-vars.fd |
Table 1 shows which files to use for each of the architectures for which UEFI support is included with QEMU. These files can be found in the QEMU share directory, which is typically /usr/share/qemu on Linux. On Mac, look in /opt/homebrew/share/qemu if you have an Apple M1 or successor CPU, or in /usr/local/share/qemu if you have an Intel CPU. The C:\Program Files\qemu\share directory is the QEMU share location on Windows hosts.
To use UEFI, first copy the UEFI variables file to the virtual machine directory. This step only needs to be done once. Then, simply include the two required TianoCore files as drives attached to the “pflash” interface. For example:
-drive if=pflash,format=raw,readonly=on,file=/usr/share/qemu/edk2-x86_64-code.fd
-drive if=pflash,format=raw,file=edk2-i386-vars.fd
The above QEMU arguments would enable UEFI boot support for an x86_64 VM running on a Linux host.
Other Boot Methods
When running a VM for an architecture other than PC or Arm with UEFI support, the boot methods might vary quite a bit. For example, many Arm devices that do not support UEFI instead can be booted with a custom version of Das U-Boot5 located either on primary storage or attached as some other kind of drive or device. Similarly, some physical x86_64 devices (Chromebooks, in particular) might use coreboot6 instead of UEFI. When trying to run virtual machines that do not support BIOS or UEFI, it will be necessary to do some research to determine what will be required to make the system boot.