A unified kernel image (UKI) is a single executable which can be booted directly from UEFI firmware, or automatically sourced by boot-loaders with little or no configuration.
Although Arch supported kernels themselves can be loaded by UEFI firmware, a unified image allows to incorporate all or a subset of the following:
- a UEFI stub loader like systemd-stub(7),
- the kernel command line,
- microcode,
- an initramfs image,
- a kernel image,
- a splash screen.
The resulting executable, and therefore all these elements can then be easily signed for use with Secure Boot.
esp
denotes the mountpoint of the EFI system partition.Preparing a unified kernel image
mkinitcpio
One can test the feature by running as an example
# mkdir -p esp/EFI/Linux # mkinitcpio -p linux -- --uki esp/EFI/Linux/test-systemd.efi
This would produce a kernel image for the linux preset.
Kernel command line
First, create /etc/kernel/cmdline
with your kernel parameters, taking care to remove entries pointing at microcode and initramfs, e.g.
/etc/kernel/cmdline
quiet bgrt_disable
bgrt_disable
parameter tells Linux to not display the OEM logo after loading the ACPI tables..preset file
Next modify /etc/mkinitcpio.d/linux.preset
, or the preset that you are using, as follows, with the appropriate mount point of the EFI system partition :
- If your system requires Microcode, add
ALL_microcode=(/boot/*-ucode.img)
, - Add a
PRESET_uki=
parameter for each item inPRESETS=
, - Optionally, comment out
PRESET_image=
to avoid building redundantinitramfs-*.img
, - Optionally, append a
--splash
parameter to eachPRESET_options=
line for which you want to add a splash image.
Here is a working example linux.preset
for the linux kernel and the Arch splash screen.
/etc/mkinitcpio.d/linux.preset
# mkinitcpio preset file for the 'linux' package ALL_config="/etc/mkinitcpio.conf" ALL_kver="/boot/vmlinuz-linux" ALL_microcode=(/boot/*-ucode.img) PRESETS=('default' 'fallback') #default_image="/boot/initramfs-linux.img" default_uki="esp/EFI/Linux/archlinux-linux.efi" default_options="--splash=/usr/share/systemd/bootctl/splash-arch.bmp" #fallback_image="/boot/initramfs-linux-fallback.img" fallback_uki="esp/EFI/Linux/archlinux-linux-fallback.efi" fallback_options="-S autodetect"
- If all you want to do is boot from the unified kernel images, you can mount the ESP to
/efi
and only those need to reside on the ESP partition. - You can append
--cmdline /etc/kernel/fallback_cmdline
tofallback_options
to use different a different cmdline than above for the fallback image (e.g. withoutquiet
)
Finally: make sure that the directory for the UKI exists, regenerate the initramfs, and optionally remove any leftover initramfs-*.img
from /boot
or /efi
.
kernel-install
You can use systemd's kernel-install(8) script to automatically install kernels in the UKI format to the esp both for custom kernels and for kernel packages (installed using Pacman) by switching Pacman hooks from mkinitcpio to kernel-install.
kernel-install
is not an initramfs generator, but it is a framework where packages can hook into the installation/generation of kernels of the system, through its "plugin" system. During its execution it will call the proper initramfs generator of the system (i.e.: mkinitcpio). The plugins are involved in kernel image/initramfs generation, signing, installation, etc. Packages that care about doing something during kernel installation can be notified by installing their own "plugin" for kernel-install
. (The "plugins" are located in /usr/lib/kernel/install.d/
.)
There are configuration options like "layout" available that affect where and how the kernel is installed when kernel-install
is getting called.
mkinitcpio ships with a kernel-install
plugin that generate the appropriate image (for layout=uki an UKI image). Other programs, such as sbctl, also ship with a kernel-install
plugin.
To setup kernel-install to produce UKIs:
- Set the kernel-install layout to 'uki'. e.g.:
# echo "layout=uki" >> /etc/kernel/install.conf
- Mask the direct kernel installation Pacman hooks:
# ln -s /dev/null /etc/pacman.d/hooks/60-mkinitcpio-remove.hook # ln -s /dev/null /etc/pacman.d/hooks/90-mkinitcpio-install.hook
- Create a Pacman hook for kernel-install. You can use pacman-hook-kernel-installAUR.
- Remove and reinstall the kernel packages that you use.
kernel-install
hook to conflict with the upstream kernel-install 90-uki-copy.install
file. To fix the issue temporarily mask the 90-uki-copy.install install file with ln -s /dev/null /etc/kernel/install.d/90-uki-copy.install
.dracut
See dracut#Unified kernel image and dracut#Generate a new initramfs on kernel upgrade.
sbctl
Install the sbctl package. Store the kernel command line in /etc/kernel/cmdline
. Use the sbctl bundle
command with the --save
parameter to create a bundle and have it be regenerated by a Pacman hook at appropriate times:
# sbctl bundle --save esp/archlinux.efi
To create more EFI binaries for other kernels and initramfs images, repeat the above command with parameters --kernel-img
and --initramfs
, see sbctl(8) § EFI BINARY COMMANDS. The EFI binaries can be regenerated at any time with sbctl generate-bundles
.
Manually
Put the kernel command line you want to use in a file, and create the bundle file using objcopy(1).
For microcode, first concatenate the microcode file and your initrd, as follows:
$ cat esp/cpu_manufacturer-ucode.img esp/initramfs-linux.img > /tmp/combined_initrd.img
When building the unified kernel image, pass in /tmp/combined_initrd.img
as the initrd. This file can be removed afterwards.
$ osrel_offs=$(objdump -h "/usr/lib/systemd/boot/efi/linuxx64.efi.stub" | awk 'NF==7 {size=strtonum("0x"$3); offset=strtonum("0x"$4)} END {print size + offset}') $ cmdline_offs=$((osrel_offs + $(stat -Lc%s "/usr/lib/os-release"))) $ splash_offs=$((cmdline_offs + $(stat -Lc%s "/etc/kernel/cmdline"))) $ linux_offs=$((splash_offs + $(stat -Lc%s "/usr/share/systemd/bootctl/splash-arch.bmp"))) $ initrd_offs=$((linux_offs + $(stat -Lc%s "vmlinuz-file"))) $ objcopy \ --add-section .osrel="/usr/lib/os-release" --change-section-vma .osrel=$(printf 0x%x $osrel_offs) \ --add-section .cmdline="/etc/kernel/cmdline" \ --change-section-vma .cmdline=$(printf 0x%x $cmdline_offs) \ --add-section .splash="/usr/share/systemd/bootctl/splash-arch.bmp" \ --change-section-vma .splash=$(printf 0x%x $splash_offs) \ --add-section .linux="vmlinuz-file" \ --change-section-vma .linux=$(printf 0x%x $linux_offs) \ --add-section .initrd="initrd-file" \ --change-section-vma .initrd=$(printf 0x%x $initrd_offs) \ "/usr/lib/systemd/boot/efi/linuxx64.efi.stub" "linux.efi"
The offsets are simply calculated so no sections overlap, as recommended in [1].
After creating the image, copy it to the EFI system partition:
# cp linux.efi esp/EFI/Linux/
Booting
systemd-boot
systemd-boot searches in esp/EFI/Linux/
for unified kernel images, and there is no further configuration needed. See sd-boot(7) § FILES
rEFInd
rEFInd will autodetect unified kernel images on your EFI system partition, and is capable of loading them. They can also be manually specified in refind.conf
, by default located at:
esp/EFI/refind/refind.conf
menuentry Linux { loader esp/EFI/Linux/archlinux-linux.efi }
If the image is at the root of the ESP, rEFInd only requires its name, as follows: loader archlinux-linux.efi
. Recall that no kernel parameters from esp/EFI/refind_linux.conf
will be passed when booting this way.
Directly from UEFI
efibootmgr can be used to create a UEFI boot entry for the .efi file:
# efibootmgr --create --disk /dev/sdX --part partition_number --label "label" --loader 'EFI\Linux\file.efi' --unicode
See efibootmgr(8) for an explanation of the options.
options
is present in a boot entry and Secure Boot is disabled, the value of options
will override any .cmdline
string embedded in the EFI image that is specified by efi
or linux
(see #Preparing a unified kernel image). With Secure Boot, however, options
(and any edits made to the kernel command line in the bootloader UI) will be ignored, and only the embedded .cmdline
will be used.