x86 Platforms – Part 2: UEFI Bootloader Management and Integration with Yocto

Share this post on:

In this part, we continue to explore bootloader management in UEFI systems, focusing on integrating GRUB and systemd-boot. We will also cover advanced boot mechanisms such as Linux EFI stub and Unified Kernel Images (UKI). Additionally, we will discuss using Yocto’s wic tool to create bootable images with an EFI System Partition (ESP) and a root filesystem partition.

Bootloader Integration in UEFI Systems

In UEFI-based systems, bootloaders like GRUB and systemd-boot play a crucial role in the system’s boot process by loading the operating system. Once installed, the bootloader binary’s location can be either explicitly configured in the UEFI firmware’s settings using NVRAM (Non-Volatile Random-Access Memory) entries, or it can simply be placed in a fallback path within the EFI System Partition (ESP).

When a bootloader is configured via NVRAM, the UEFI firmware is directed to a specific location to load the bootloader. This approach offers more control, allowing for multiple operating systems or kernel configurations to be managed. However, if the bootloader is placed in a fallback path, such as \EFI\BOOT\bootx64.efi for x86_64 systems, the firmware will automatically load it without needing NVRAM configuration. The fallback method is especially useful for system recovery or when NVRAM settings are unavailable or corrupted.

Both approaches – NVRAM configuration and fallback paths – ensure flexibility in managing bootloader locations on UEFI-based systems, making it easier to handle multi-boot setups or system recovery.

systemd-boot in UEFI Systems

systemd-boot is a simple UEFI boot manager that directly loads EFI-compliant binaries, including the Linux kernel when compiled with the EFI stub.

  • Installation: systemd-boot can be registered in UEFI firmware or placed in the fallback path at \EFI\BOOT\bootx64.efi on the ESP.
  • Configuration: systemd-boot uses simple text-based configuration files:
    • /boot/loader/loader.conf: Defines bootloader behavior (e.g., default entry, timeout).
    • /boot/loader/entries/*.conf: Defines boot entries with kernel images and boot options. Each file corresponds to a boot entry.
      • Systemd-boot requires both a loader.conf for global settings and one or more boot entry files for each kernel option.

Example loader.conf:

default default boot-5.10.conf
timeout 5

Example boot entry (boot-5.10.conf):

title   MyLinux 5.10
linux /vmlinuz-5.10
initrd /initramfs-5.10.img
options root=/dev/sda2 rw quiet splash

Example boot entry (boot-4.19.conf):

title   MyLinux 4.19
linux /vmlinuz-4.19
initrd /initramfs-4.19.img
options root=/dev/sda2 rw quiet splash

File loader.conf  has the following key variables:

  • default boot-5.10.conf: Specifies the default boot entry by referencing its .conf file in /boot/loader/entries/.
  • timeout 5: Sets the timeout before the default boot entry is chosen automatically.
  • console-mode keep: Controls the console resolution mode during boot.

Key Variables in Boot Entry Files (boot-5.10.conf, etc.) are:

  • title: The display name for this boot entry.
  • linux: The path to the kernel image, relative to the ESP. It must have been compiled with EFI stub support (CONFIG_EFI_STUB=y), turning the kernel into an EFI-compliant binary.
  • initrd: The path to the initial RAM disk relative to the ESP.
  • options: Kernel boot parameters, such as the root filesystem and boot options like quiet or splash.

Systemd-boot requires both a loader.conf for global settings and one or more boot entry files for each kernel option.

GRUB in UEFI Systems

GRUB is a feature-rich bootloader supporting multi-OS booting, multiple filesystems, and complex configurations. GRUB functions as both a boot manager and bootloader.

  • Installation: GRUB can be installed in UEFI’s NVRAM or placed in the ESP’s fallback path (\EFI\BOOT\bootx64.efi).
  • Configuration Files:
    • /boot/grub/grub.cfg: The main configuration file for defining boot options. The structure of this file is essential for GRUB’s functionality, as it provides the boot menu and ensures the correct kernel is loaded
    • /boot/grub/grubenv: Stores grub’s persistent settings (e.g., environment variables, boot logic, …).

Example grub.cfg:

set default=0
set timeout=5

menuentry "MyLinux 5.10" {
set root=(hd0,1)
linux /vmlinuz-5.10 root=/dev/sda2 ro quiet
initrd /initramfs-5.10.img
}

menuentry "MyLinux 4.19" {
set root=(hd0,1)
linux /vmlinuz-4.19 root=/dev/sda2 ro quiet
initrd /initramfs-4.19.img
}

Key Variables in grub.cfg includes:

  • set timeout=5: Defines how long GRUB waits before booting the default option.
  • set default=0: Specifies the default boot option (first entry in the list).
  • menuentry: Each menuentry block defines a bootable kernel option.
  • linux: The path to the kernel image and additional kernel parameters (like the root partition).
  • initrd: Specifies the path to the initial RAM disk (initramfs).

This configuration presents two boot options for the user, one for Linux kernel 5.10 and another for kernel 4.19.

Managing GRUB’s grubenv Outside the ESP

While grub.cfg must reside in the ESP, the grubenv file can be stored outside the ESP. This setup is useful in scenarios where the ESP is updated separately from the rest of the system. To configure GRUB to load grubenv from a different partition, use the --file argument in the load_env and save_env commands in grub.cfg:

load_env --file=(hd0,2)/grubenv
[...]
save_env --file=(hd0,2)/grubenv

This method allows GRUB to load and save the environment file from a separate partition.

Direct Kernel Booting with EFI Stub

The Linux kernel can be compiled with EFI stub support (CONFIG_EFI_STUB=y), enabling it to act as an EFI-compliant binary that can be booted directly by UEFI firmware or by a simple boot manager like systemd-boot. It can be achieved thhough the following steps:

  1. Compile the Kernel with EFI Stub Support:
    CONFIG_EFI_STUB=y
  2. Place the Kernel in the ESP: Copy the kernel to the ESP (typically /efi/boot/bootx64.efi).
  3. Set Up Kernel Command Line with efibootmgr: When creating a UEFI boot entry, you can specify kernel parameters directly. However, paths must be properly formatted according to EFI standards, using backslashes and being relative to the ESP.

Specifying Kernel Command Line Options with efibootmgr

When setting up a boot entry with efibootmgr, it’s essential to follow the correct formatting for paths to match EFI standards by using backslashes and ensuring the paths are relative to the ESP. For example, the path to the initramfs should be specified relative to the ESP and use backslashes. Here’s how you can configure the efibootmgr command:

efibootmgr --create --disk /dev/sda --part 1 --label "MyLinux" --loader '\EFI\arch\vmlinuz-linux' \
--unicode "root=/dev/sda2 rw initrd=\EFI\arch\initramfs-linux.img"

This example assumes the kernel and initramfs are located under /EFI/arch in the ESP. Proper path formatting ensures compatibility with UEFI standards.

Limitations of Direct Kernel Booting

While direct kernel booting simplifies the boot process, it has some limitations:

  • No Boot Menu: Users cannot select between multiple kernels or configurations.
  • Parameter Management: Kernel command-line arguments must be handled via UEFI configuration, which can be cumbersome.

Furthermore, some UEFI firmwares do not pass command-line parameters from the NVRAM boot entries to the EFI binaries (even though you can sometime contact the CoM vendor for customization). In such cases, kernel parameters specified in boot entries are ignored, making it difficult to control boot behavior.

One solution might be to compile the kernel with command line set in CONFIG_CMDLINE. However, you’ll need to recompile your kernel every time need be to change a parameter in command line. A solution to this problem is to combine the kernel and its parameters into a Unified Kernel Image (UKI). The resulting .efi file can then be used to create a new boot entry that includes all necessary parameters, bypassing the firmware limitation.

Unified Kernel Image (UKI) Booting

A Unified Kernel Image (UKI) consolidates the Linux kernel, initramfs, and other components into a single EFI binary, simplifying the boot process and enhancing security (especially for systems using Secure Boot). A UKI image has the following core components:

  1. UEFI Boot Stub: Forms the initial executable program of the UKI. This binary is architecture-specific and can be provided by the systemd-boot package (e.g., linuxx64.efi.stub, linuxia32.efi.stub for x86_64 and x86_32, respectively).The UEFI boot stub binary is recommended to avoid frequent kernel rebuilds since it can fetch kernel parameters from the EFI configuration.
  2. Linux Kernel: Must be compiled with CONFIG_EFI_STUB=y and is stored in the .linux section of the UKI.
  3. Optional Components:
    • OS Information (.osrel section): Derived from /etc/os-release.
    • Kernel Command Line (.cmdline section): Stores kernel boot parameters.
    • Initrd (.initrd section): Initial RAM disk loaded during boot.
    • Microcode Initrd (.ucode section): Microcode updates for the CPU.

There are two methods to create a UKI image: using objcopy or ukify tool.

Creating a UKI with objcopy

You can create a UKI using objcopy, merging the kernel, initramfs, and other components into a single EFI binary.

Example process:

MICRO_CODE_1="microcode.cpio"
MICRO_CODE_2="acpi_override.cpio"
INITRD="core-image-initramfs.cpio.gz"
KERNEL="bzImage"

# Create a combined initrd
cat ${MICRO_CODE_1} ${MICRO_CODE_2} ${INITRD} > /path/to/initrd

# Use objcopy to create the UKI
objcopy \
--add-section .osrel=/usr/lib/os-release --change-section-vma .osrel=0x20000 \
--add-section .cmdline=/path/to/cmdline-file --change-section-vma .cmdline=0x30000 \
--add-section .linux=/path/to/${KERNEL} --change-section-vma .linux=0x2000000 \
--add-section .initrd=/path/to/initrd --change-section-vma .initrd=0x3000000 \
/path/to/linuxx64.efi.stub /boot/efi/EFI/Linux/linux.efi

This process creates a unified kernel image with all necessary boot components, which can be placed in the ESP for direct booting by UEFI.

CONFIG_EFI_STUB vs. linux<arch>efi.stub (Systemd Stub)

CONFIG_EFI_STUB and linux<arch>.efi.stub (the systemd boot stub) serve similar purposes but are not mutually exclusive. Both can be used independently or in combination, depending on the needs of the system. Here’s a breakdown of their relationship:

  • CONFIG_EFI_STUB: This option allows the Linux kernel to be compiled with its own EFI stub, turning the kernel itself into an EFI executable. With this option enabled, you can boot the kernel directly from UEFI without needing any additional bootloader or boot stub. It’s ideal for minimal setups or embedded systems where the boot process is streamlined.
  • linux<arch>.efi.stub (Systemd Stub): This architecture-specific stub, provided by the systemd-boot package, allows more flexibility than CONFIG_EFI_STUB. It separates the kernel from the boot configuration, enabling developers to modify kernel parameters without recompiling the kernel. The systemd stub reads kernel parameters from the EFI configuration or from sections in the UKI, such as .cmdline and .initrd, allowing dynamic updates.
Can They Be Used Together?
  • UKI (Unified Kernel Image): Both CONFIG_EFI_STUB and linux<arch>.efi.stub can be used together when creating a Unified Kernel Image. In this case, the kernel is compiled with CONFIG_EFI_STUB, while the systemd stub provides additional flexibility in managing the boot process by fetching boot parameters dynamically from the EFI configuration. The systemd stub is highly recommended for UKI because it enables updates to kernel parameters and other components without requiring a full kernel rebuild.
Are They Mandatory for UKI?
  • CONFIG_EFI_STUB is mandatory when creating a UKI because the kernel must be EFI-compliant to be included in the image.
  • linuxx64.efi.stub (systemd boot stub) is not mandatory but highly recommended for UKI creation due to its flexibility in handling kernel parameters and boot configuration.

In summary, while CONFIG_EFI_STUB enables the kernel to be directly booted as an EFI binary, using linuxx64.efi.stub offers additional flexibility and is preferred for dynamic systems or when using UKI.

WIC: Creating Disk Images in Yocto

WIC (Write Image Creator) is a tool used in Yocto to create disk images. These images can include multiple partitions, filesystems, and bootloaders. WIC is driven by WKS (kickstart) files, which define the layout of the disk, including partition sizes, filesystem types, and the bootloader to be installed.

A typical WKS file might look like this:

part /boot --source bootimg-efi --fstype=vfat --label boot --active --align 1024 --sourceparams="loader=grub-efi" --use-uuid
part / --source rootfs --fstype=ext4 --label root --part-name system --align 1024 --use-uuid

bootloader --ptable gpt --append="console=ttyS0,115200"

In this example:

  • part /boot: Specifies the boot partition with FAT32 as the filesystem and the bootimg-efi plugin for creating the ESP.
  • part /: Defines the root filesystem partition using the bootimg-partition plugin.
  • bootloader --ptable gpt: Indicates that the GPT partition table should be used, which is required for UEFI systems.

Now that we’ve introduced WIC, let’s explore the concept of plugins in Yocto and discuss the key plugins used for bootloader management.

Understanding Yocto WIC Plugins

WIC plugins are responsible for handling different aspects of disk image creation in Yocto. Plugins manage the creation of partitions, bootloader installation, and the population of files in these partitions. While yocto is shipped with several plugins, two mains plugins are of interest when it comes to deal with boot partition for UEFI systems: bootimg-efi and bootimg-partition.

Key Plugins: bootimg-efi and bootimg-partition

In Yocto, two main plugins are used for managing bootloaders and partition creation:

  • bootimg-efi: Designed for UEFI-compliant bootloader installations. This plugin automates the creation of the EFI System Partition (ESP) and places bootloader binaries in the correct location.
  • bootimg-partition: A general-purpose plugin used for creating partitions. This plugin requires more manual intervention when installing bootloaders and populating partitions with files.

Plugin-Specific File Management Variables

Each plugin uses specific Yocto variables to manage which files are copied to the corresponding partitions.

  • IMAGE_EFI_BOOT_FILES: Used with the bootimg-efi plugin to specify files that need to be placed in the ESP. This includes bootloader binaries, configuration files, and kernel images.Example:
    IMAGE_EFI_BOOT_FILES:append = " \
    grub-efi-bootx64.efi;EFI/BOOT/bootx64.efi \
    grub.cfg;grub/grub.cfg \
    "
  • IMAGE_BOOT_FILES: Used with the bootimg-partition plugin to specify files that need to be placed in non-ESP partitions, such as the root filesystem or boot partitions in non-UEFI environments.Example:
    IMAGE_BOOT_FILES:append = " \
    vmlinuz;boot/vmlinuz \
    initrd.img;boot/initrd.img \
    "

Whatever the plugin that is used, files to be shipped must first have been deployed (must exist) into DEPLOY_DIR_IMAGE.

The Role of sourceparams and loader in WKS file

The sourceparams option in WIC allows you to pass parameters to the partition creation plugin. One of the key parameters is loader, which specifies which bootloader to install. In addition to that, it helps generating and deploying the bootloader’s configuration files, which can then be shipped in the boot ESP or boot partition.

The loader parameter tells Yocto which bootloader to install in the ESP, and handles configuration file creation. Supported values include:

  • grub-efi: Installs GRUB as the bootloader.
  • systemd-boot: Installs systemd-boot as the bootloader.
  • "": empty, it skips installing a traditional bootloader, allowing direct kernel booting with the EFI stub.

By setting the loader parameter, Yocto automatically generates the necessary bootloader configuration files, such as grub.cfg for GRUB or loader.conf for systemd-boot, and places them in the appropriate directories.

Description of bootimg-efi Plugin

The bootimg-efi plugin is used when creating UEFI-compliant disk images. It automatically creates the EFI System Partition (ESP), formats it as FAT32, and installs the selected bootloader. It places the bootloader and related files in the appropriate directories within the ESP based on IMAGE_EFI_BOOT_FILES.

Its key features are:

  • ESP Creation: Automatically creates and formats the EFI System Partition.
  • Bootloader Installation: Handles the installation of UEFI-compliant bootloaders like GRUB and systemd-boot.
  • File Management: Uses IMAGE_EFI_BOOT_FILES to manage files that should be copied into the ESP.

Example of IMAGE_EFI_BOOT_FILES:

IMAGE_EFI_BOOT_FILES:append = " \
grub-efi-bootx64.efi;EFI/BOOT/bootx64.efi \
grub.cfg;grub/grub.cfg \
"

This example ensures that the GRUB binary and configuration file are copied into the correct directories in the ESP.

Description of bootimg-partition Plugin

The bootimg-partition plugin is a general-purpose tool for creating partitions in Yocto. Unlike bootimg-efi, this plugin does not automatically handle UEFI bootloaders or ESP creation. Instead, it allows for more flexibility, letting you manually define how files are copied into the partition using IMAGE_BOOT_FILES.

Its key features are:

  • Flexible Partition Creation: Allows you to create and format partitions with any supported filesystem type.
  • Manual Bootloader Handling: Requires you to manually specify the bootloader files and their locations.
  • File Management: Uses IMAGE_BOOT_FILES to define which files are copied to the partition.

Example of IMAGE_BOOT_FILES:

IMAGE_BOOT_FILES:append = " \
vmlinuz;boot/vmlinuz \
initrd.img;boot/initrd.img \
"

This example ensures that the kernel and initramfs are copied into the /boot directory of the partition.

Comparison of bootimg-efi and bootimg-partition

Now that we’ve described both plugins, let’s compare them based on key features and use cases:

Feature bootimg-efi bootimg-partition
ESP Creation Automatically creates the ESP and formats it as FAT32. Requires manual setup for ESP (if needed).
Bootloader Handling Installs UEFI-compliant bootloaders like GRUB and systemd-boot. Requires manual placement of bootloader files.
File Management Uses IMAGE_EFI_BOOT_FILES to manage files in the ESP. Uses IMAGE_BOOT_FILES to manage files in the partition.
Use Case Ideal for UEFI booting with minimal configuration effort. Best for custom setups or non-UEFI partitions.

Bootloader Integration with Yocto Using WIC Plugins

Now that we understand how the plugins work, let’s explore how to integrate specific bootloaders — GRUB, systemd-boot, and Unified Kernel Image (UKI) — into Yocto using the bootimg-efi and bootimg-partition plugins.

Integrating GRUB with bootimg-efi

GRUB is a robust, flexible bootloader that works well in both BIOS and UEFI systems. The bootimg-efi plugin makes it easy to install GRUB in the EFI System Partition.

WIC Example for GRUB:

part /boot --source bootimg-efi --fstype=vfat --label boot --active --align 1024 --size 64 --sourceparams="loader=grub-efi"

bootloader --ptable gpt

In this example, loader=grub-efi specifies that GRUB should be installed in the ESP. Yocto will automatically generate the grub.cfg file and install it in the appropriate location.

Using IMAGE_EFI_BOOT_FILES for GRUB:
IMAGE_EFI_BOOT_FILES:append = " \ 
grub-efi-bootx64.efi;EFI/BOOT/bootx64.efi \
grub.cfg;grub/grub.cfg \
"

This configuration ensures that the GRUB binary and its configuration are correctly placed in the ESP. This would result in:

/boot (FAT32)

├── EFI/
│ └── BOOT/
│ └── bootx64.efi # GRUB EFI binary (grubx64.efi)
├── grub/
│ └── grub.cfg # GRUB configuration file
└── vmlinuz-5.10 # Kernel image for version 5.10
initramfs-5.10.img # Initramfs for Kernel 5.10
vmlinuz-4.19 # Kernel image for version 4.19
initramfs-4.19.img # Initramfs for Kernel 4.19

Integrating Systemd-Boot with bootimg-efi

Systemd-boot is a lightweight boot manager suitable for UEFI systems. Like GRUB, systemd-boot can be installed using the bootimg-efi plugin.

WIC Example for Systemd-Boot:

part /boot --source bootimg-efi --fstype=vfat --label boot --active --align 1024 --size 64 --sourceparams="loader=systemd-boot"

bootloader --ptable gpt

In this example, loader=systemd-boot tells Yocto to install systemd-boot and generate the necessary configuration files, such as loader.conf and boot entries.

Using IMAGE_EFI_BOOT_FILES for Systemd-Boot:
IMAGE_EFI_BOOT_FILES:append = " \
systemd-bootx64.efi;EFI/BOOT/bootx64.efi \
loader.conf;loader/loader.conf \
boot-5.10.conf;loader/entries/boot-5.10.conf \
boot-4.19.conf;loader/entries/boot-4.19.conf \
"

This configuration ensures that systemd-boot and its configuration files are placed in the ESP. It would result in:

/boot (FAT32)

├── EFI/
│ └── BOOT/
│ └── bootx64.efi # systemd-boot binary
├── loader/
│ └── loader.conf # systemd-boot main configuration
│ └── entries/
│ ├── boot-5.10.conf # Boot entry for Kernel 5.10
│ └── boot-4.19.conf # Boot entry for Kernel 4.19
└── vmlinuz-5.10 # Kernel image for version 5.10
initramfs-5.10.img # Initramfs for Kernel 5.10
vmlinuz-4.19 # Kernel image for version 4.19
initramfs-4.19.img # Initramfs for Kernel 4.19

Integrating Unified Kernel Image (UKI) with bootimg-efi

The Unified Kernel Image (UKI) is a single EFI-compliant binary that combines the kernel, initrd, and kernel parameters into a unified file. This approach simplifies bootloader configuration, as everything is bundled into one binary.

WIC Example for UKI:

part /boot --source bootimg-efi --fstype=vfat --label boot --active --align 1024 --size 64

bootloader --ptable gpt

In this case, loader=uefi-kernel bypasses the need for a bootloader and allows the UEFI firmware to boot the kernel directly.

Using IMAGE_EFI_BOOT_FILES for UKI:
IMAGE_EFI_BOOT_FILES:append = " \
unified-linux.efi;EFI/BOOT/bootx64.efi \
"

This configuration places the UKI binary directly in the ESP for booting. This would result in:

/boot (FAT32)

├── EFI/
│ └── BOOT/
│ └── bootx64.efi # Unified Kernel Image (UKI binary)

Conclusion

In Yocto, managing bootloaders for UEFI-based x86 platforms requires an understanding of WIC plugins like bootimg-efi and bootimg-partition. The bootimg-efi plugin simplifies the process by automating the creation of the ESP and installing bootloaders like GRUB or systemd-boot, while bootimg-partition offers more flexibility for custom partitioning setups. By using the loader parameter and managing files through IMAGE_EFI_BOOT_FILES or IMAGE_BOOT_FILES, you can easily configure your UEFI boot process.

With this knowledge, you can select the appropriate plugin based on your project’s requirements and integrate the bootloader of your choice, ensuring a smooth and reliable boot process for your Yocto-based system.

Share this post on:

Leave a Reply

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