x86 Platforms – Part 1: Boot Process and Yocto Integration

Share this post on:

The boot process for x86 platforms has evolved significantly, moving from the legacy BIOS (Basic Input Output System) to the more modern UEFI (Unified Extensible Firmware Interface). UEFI is an updated and enhanced version of EFI (Extensible Firmware Interface), designed to overcome the limitations of BIOS and improve boot speed, security, and support for larger storage devices. In this post, we will explore the key components of the x86 boot process, including UEFI firmware paths, EFI configuration in NVRAM, and how tools like efibootmgr help manage boot entries. We’ll also discuss the integration of these concepts with the Yocto Project for embedded development.

x86 Boot Process: BIOS vs. UEFI

Legacy BIOS Booting

BIOS (Basic Input Output System) was the standard for system firmware in x86 platforms prior to the mid-2000s. It initializes hardware and boots the operating system by loading a small piece of code from the Master Boot Record (MBR), which is located in the first sector of the boot device. The MBR contains:

  • A partition table.
  • A small bootloader (limited to 446 bytes).

BIOS typically uses a two-stage bootloader due to the size constraint of the MBR:

  • Stage 1: Fits within the 446-byte MBR space.
  • Stage 2: A larger bootloader loaded by Stage 1, usually stored outside of the filesystem (at a fixed offset), which loads the operating system kernel.

image source: https://www.bootlin.com

Limitations: BIOS is limited in its support for large storage devices (above 2TB), has no native secure boot capabilities, and lacks flexibility compared to UEFI.

UEFI Booting

UEFI (Unified Extensible Firmware Interface) is an updated and more advanced version of the original EFI standard. It was introduced to overcome BIOS limitations. UEFI is more modular and supports advanced features such as secure boot, faster boot times, and support for drives larger than 2TB using the GUID Partition Table (GPT). Unlike BIOS, UEFI stores its boot configuration in NVRAM and reads EFI applications from the EFI System Partition (ESP), a dedicated FAT-formatted partition that contains boot files.

image source: https://www.bootlin.com

UEFI replaces BIOS as the default firmware interface on most modern x86 systems, and it works hand-in-hand with the GUID Partition Table (GPT), which replaces the older MBR partitioning scheme. GPT supports larger drives and allows for more partitions, making it more robust and scalable than MBR. In addition, GPT stores multiple copies of partition data for redundancy, improving the reliability of the system.

GPT vs. MBR

  • MBR (Master Boot Record): Limited to four primary partitions and drives up to 2TB in size.
  • GPT (GUID Partition Table): Supports up to 128 partitions and much larger drive sizes (more than 2TB).

EFI Partitioning

EFI partitioning refers to the way the hard drive is set up to be compatible with the EFI/UEFI firmware.

image source: https://www.bootlin.com

With UEFI, the storage device uses EFI partitioning, which creates a dedicated EFI System Partition (ESP). This partition is formatted with a FAT filesystem and contains the files required for UEFI to boot the system. In contrast, BIOS uses the MBR to store the bootloader and partition table. The ESP is essential in UEFI systems, as it houses EFI-compliant applications, such as bootloaders, kernels, and drivers, allowing UEFI firmware to find and execute them at boot time.

EFI Configuration in NVRAM

UEFI firmware stores its boot configuration in non-volatile random-access memory (NVRAM), allowing it to persist even when the system is powered off. The NVRAM holds information about boot entries, which tell the firmware which operating system to boot, where the bootloader is located, and in what order to attempt booting from different devices. This configuration includes:

    • Boot entries: These specify the path to EFI applications (e.g., bootloaders, kernels) stored in the EFI System Partition (ESP) and any associated parameters like command-line arguments for the OS.
    • Boot order: The sequence in which UEFI will attempt to boot from different entries or devices.
    • Timeout settings: Defines how long the system waits for user input before proceeding with the default boot entry.

    If the NVRAM configuration is missing or corrupted, UEFI falls back on predefined paths in the ESP, ensuring the system remains bootable.

    Default EFI Firmware Paths

    In situations where there is no valid boot configuration in NVRAM, UEFI uses predefined paths on the EFI System Partition (ESP) to locate bootable EFI applications. These paths serve as a fallback mechanism to ensure the system can still boot.

    Some default paths UEFI firmware checks include:

    • x86 (64-bit): /efi/boot/bootx64.efi
    • x86 (32-bit): /efi/boot/bootia32.efi
    • ARM64 (aarch64): /efi/boot/bootaa64.efi
    • ARM: /efi/boot/bootarm.efi

    These paths are standardized and act as a fallback when no specific boot entries are configured in NVRAM. If an EFI application is present in one of these paths, the system will attempt to boot from it.

    Managing Boot Entries with efibootmgr

    The efibootmgr tool in Linux is used to manage UEFI boot entries stored in NVRAM. It allows you to list, create, modify, or delete boot entries without needing to access the firmware setup interface directly.

    Viewing Boot Entries

    Running efibootmgr -v will display detailed information about the current boot entries, including their paths, labels, and associated parameters.

    efibootmgr -v
    

    Example output:

    BootCurrent: 0001 
    Timeout: 1 seconds
    BootOrder: 0001, 0002, 0003
    Boot0001* Ubuntu HD(1,GPT,aa9b7f9d-b5d6-4c9f-82b6-5e7a94f34e3d,0x800,0x64000)/File(\EFI\ubuntu\shimx64.efi)
    Boot0002* Fedora HD(1,GPT,aa9b7f9d-b5d6-4c9f-82b6-5e7a94f34e3d,0x800,0x64000)/File(\EFI\fedora\grubx64.efi)
    Boot0003* UEFI: Built-in EFI Shell VenMedia(5023b95c-db26-429b-a648-bd47664c8012)..BO

    The belows are some key variables used for UEFI booting:

    • BootCurrent: Indicates the current boot entry used during the current boot.
    • BootOrder: Specifies the sequence in which the system will try to boot from the available entries.
    • Each BootXXXX entry represents a bootable EFI application, including the device, partition, and the EFI binary file path.
    • BootNext: Special EFI variable that allows specifying the boot entry that will be used for the next boot only, without changing the overall boot order. Once the system boots using the specified BootNext entry, the variable is cleared, and future boots will follow the normal sequence defined in BootOrder.
      • This can be useful for temporarily selecting a specific boot option, such as when switching between different operating systems (e.g. after an update) or kernels for a one-time boot.

    Creating a New Boot Entry

    To create a new boot entry with efibootmgr, you need to specify the device, partition, label, and the path to the EFI bootloader. This is useful when installing a new operating system or adding a custom boot option. Here’s an example:

    efibootmgr --create --disk /dev/sdaX --part 1 --label "system0" --loader \\EFI\\BOOT\\bootx64.efi --unicode "root=PARTUUID=<partuuid-of-part-1>"
    efibootmgr --create --disk /dev/sdaX --part 2 --label "system1" --loader \\EFI\\LINUX\\BZIMAGE.EFI --unicode "root=PARTUUID=<partuuid-of-part-2>"

    Here are some explanations of different flags:

    • --disk: Specifies the disk where the EFI System Partition (ESP) resides (e.g., /dev/sda).
    • --part: Defines the partition number of the ESP (e.g., partition 1).
    • --label: Gives a label to the boot entry (e.g., “MyLinux”).
    • --loader: Points to the EFI application to be loaded (e.g., \EFI\BOOT\bootx64.efi).

    This command creates two boot entries to boot two different UEFI-compliant binaries located at the specified path on the ESP. It could also be used at factory when flashing the system for the first time to create initial boot entries.

    Setting the Boot Order

    You can change the boot order with the following command, specifying the order in which entries should be tried. For example, to boot Fedora first:

    efibootmgr --bootorder 0002,0001,0003
    

    This sets Fedora (Boot0002) as the first option, followed by Ubuntu (Boot0001) and the EFI Shell (Boot0003). This is used for by system update tools (e.g. RAUC) to switch to the newly updated partition.

    Setting the Next Boot Entry

    If you need to specify which entry should be used for the next boot only, you can use the --bootnext option. For example, to boot into Ubuntu on the next reboot:

    efibootmgr --bootnext 0001

    This command temporarily overrides the boot order for the next boot, but after the system reboots, it will follow the normal boot order.

    The below is what the full UEFI boot process would look like:

    image source: https://joonas.fi/2021/02/uefi-pc-boot-process-and-uefi-with-qemu/

    The above diagram can be explained as follows:

    1. Power-On Initialization: The CPU starts by executing the motherboard’s UEFI firmware stored in NVRAM.
    2. UEFI Variables: The UEFI firmware checks variables such as BootOrder (the sequence of boot entries) and BootCurrent (the current boot entry).
      1. Boot Entries: Each boot entry (e.g., Boot0001, Boot0002) points to a specific bootloader on the drive’s EFI System Partition (ESP).
      2. ESP Partition: The EFI System Partition (ESP) contains bootloader files for different operating systems, such as Ubuntu and Windows, under specific directories (e.g., \EFI\ubuntu\... or \EFI\windows\...).
    3. Boot Process: The UEFI firmware selects the bootloader based on BootCurrent or the order defined in BootOrder, and then executes it.
    4. OS Loading: Once the selected bootloader runs, it loads the corresponding operating system (e.g., Ubuntu or Windows) from the specified partition.
    5. Fallback Mechanism: If the boot entry in BootOrder fails, the firmware attempts the next entry, or displays an error if none are successful.

    Yocto Integration with EFI Booting

    For embedded systems development, the Yocto Project provides flexibility in managing the boot process for x86 platforms, including EFI support. Yocto supports multiple bootloaders, such as GRUB and systemd-boot, which can be integrated as part of the build process.

    EFI Bootloader Configuration in Yocto

    Yocto allows you to configure which bootloader to use by setting the EFI_PROVIDER variable in the configuration files. For example, to use GRUB as the bootloader:

    EFI_PROVIDER = "grub-efi"
    

    Alternatively, to use systemd-boot:

    EFI_PROVIDER = "systemd-boot"
    

    This selection determines which EFI bootloader will be installed in the resulting image, providing flexibility for different system requirements.

    That being said, you might want to boot a UKI kernel image (which does not need any bootloader, and thus can be executed by the UEFI firmware itself). In this case, just set this variable empty:

    EFI_PROVIDER = ""
    

    Managing EFI Boot Files with Yocto

    One of the key tools in Yocto for creating bootable disk images is wic, which provides an easy way to define disk layouts and integrate UEFI with custom partitions. Yocto also enables the customization of files placed in the EFI System Partition (ESP). This is controlled by the IMAGE_EFI_BOOT_FILES or IMAGE_BOOT_FILES variables.  This flexibility allows developers to tailor the boot process for embedded systems with specific requirements. In our next blog post, we’ll discuss in detail how implementation for this would work.

    Conclusion

    The evolution of the x86 boot process from BIOS to UEFI has brought enhanced flexibility, improved performance, and better security features. UEFI’s reliance on standardized paths and NVRAM configuration provides a modern and adaptable booting framework for today’s systems. Tools like efibootmgr enable developers to manage boot entries efficiently from within Linux, and the Yocto Project integrates seamlessly with EFI to simplify the development of embedded Linux systems.

    By understanding how EFI firmware handles booting and utilizing the flexibility offered by Yocto and efibootmgr, developers can create robust, customized boot processes tailored to their specific needs.

    Share this post on:

    Leave a Reply

    Your email address will not be published. Required fields are marked *