Background
Unified Extensible Firmware Interface (UEFI) is a specification that describes an interface between a motherboard firmware and an operating system (OS). It follows the Extensible Firmware Interface (EFI), formerly implemented by Intel. It slowly replaces the old Basic Input Output System (BIOS) originally provided on IBM PC compatible computers.
Since few years motherboard manufacturers are providing both UEFI and BIOS compatible firmwares. The Linux kernel provides the appropriate stubs to be booted directly by a compatible UEFI firmware. It removes the need of an intermediate boot loader like GRUB.
If you don’t need an intermediate boot loader, because you have only one boot option for instance, you can setup EFI stubs on your Debian 10 system. It will be booted directly by your UEFI. In the following lines I will describe how to setup this configuration.
Configuration
EFI Boot partition
The first step is to ensure your system is installed with a readable EFI boot partition. Usually it is a small partition of your disk, located at the beginning and formatted using a very simple file system like FAT or EXT2. This partition will be used to store the EFI compatible binaries : either your GRUB or Linux kernel binaries. During the boot phase, the UEFI will traverse the partition and try to find compatible binaries. Here is an example on my workstation:
# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 119.2G 0 disk ├─sda1 8:1 0 953M 0 part /boot/efi └─sda2 8:2 0 118.3G 0 part /
If you don’t have this kind of partition, you will have to create one, maybe while re-installing your system. But this is outside the scope of this article, you can get more information here.
Synchronizing images
To be able to boot directly, the UEFI will need to access your kernel image and its ramdisk. It is possible only if both these images are in the partition mentioned above. On Debian systems, these images are available at the root at the file system, we’ll need to setup something to copy them in the EFI partition, each time it is updated. So that after any regular system update, the kernel and ramdisk images will be updated too in the special partition.
In Debian wiki EFI Stubs, the method used relies on post install hooks available for kernel and ramdisk images. Unfortunately it doesn’t work anymore on Buster release because the post install hooks for initramfs
(ramdisk) is not available anymore. Thus we’ll do this update using systemd
service units and unit triggers based on file monitoring. The idea is to create a one shot service unit that will copy the file. But instead of launching it at boot (or so), we’ll trigger it using a file monitor. We just have to declare it and systemd
will do the job.
Kernel
Here we go. We start by creating the service unit in /etc/systemd/system/uefi-kernel-update.service
:
# cat /etc/systemd/system/uefi-kernel-update.service [Unit] Description=UEFI Kernel update After=network.target [Service] Type=oneshot ExecStart=/bin/cp /vmlinuz /boot/efi/EFI/debian/
Then create the file trigger /etc/systemd/system/uefi-kernel-update.path
:
# cat /etc/systemd/system/uefi-kernel-update.path [Path] PathChanged=/vmlinuz [Install] WantedBy=multi-user.target
Finally enable the trigger:
# systemctl enable uefi-kernel-update.path Created symlink /etc/systemd/system/multi-user.target.wants/uefi-kernel-update.path → /etc/systemd/system/uefi-kernel-update.path. # systemctl start uefi-kernel-update.path
Finally, you can test it works correctly by running touch /vmlinuz
and checking in systemd
logs that the unit was triggered:
# touch /vmlinuz # journalctl -xn Dec 14 18:14:18 fixe-damien systemd[1]: Starting UEFI Kernel update… -- Subject: A start job for unit uefi-kernel-update.service has begun execution -- Defined-By: systemd -- Support: https://www.debian.org/support -- A start job for unit uefi-kernel-update.service has begun execution. -- The job identifier is 2611. Dec 14 18:14:18 fixe-damien systemd[1]: uefi-kernel-update.service: Succeeded. -- Subject: Unit succeeded -- Defined-By: systemd -- Support: https://www.debian.org/support -- The unit uefi-kernel-update.service has successfully entered the 'dead' state. Dec 14 18:14:18 fixe-damien systemd[1]: Started UEFI Kernel update. -- Subject: A start job for unit uefi-kernel-update.service has finished successfully -- Defined-By: systemd -- Support: https://www.debian.org/support
Ramdisk (initrd)
Repeat the operation by creating a service unit and a path trigger to update /initrd.img
file. Unit files are provided below:
# cat /etc/systemd/system/uefi-initrd-update.service [Unit] Description=UEFI ignited update After=network.target [Service] Type=oneshot ExecStart=/bin/cp /initrd.img /boot/efi/EFI/debian/
# cat /etc/systemd/system/uefi-initrd-update.path [Path] PathChanged=/initrd.img [Install] WantedBy=multi-user.target
Finally enable the unit and the trigger.
# systemctl enable uefi-initrd-update.path Created symlink /etc/systemd/system/multi-user.target.wants/uefi-initrd-update.path → /etc/systemd/system/uefi-initrd-update.path. # systemctl start uefi-initrd-update.path
Don’t forget to test it works with touch
and journalctl
.
Adding an UEFI boot entry
Now that we’re sure the kernel and the ramdisk will be correctly deployed and updated, we have to add a boot entry to the UEFI to let him know we can boot the kernel directly.
First we have to gather the kernel command line from GRUB configuration. In the file /boot/grub/grub.cfg
, find the menuentry ... { ... }
block that matches the entry to use in GRUB to boot. Then read the block and find the linux /boot/vmlinuz...
entry, it provides the kernel command line parameters. For me it looks like:
menuentry 'Debian GNU/Linux' ... { ... echo 'Loading Linux 4.19.0-6-amd64 ...' linux /boot/vmlinuz-4.19.0-6-amd64 root=UUID=3c51b884-79b9-46f5-aff9-a2d8f68cd308 ro quiet echo 'Loading initial ramdisk ...' initrd /boot/initrd.img-4.19.0-6-amd64 }
The kernel command line parameters are root=UUID=3c51b884-79b9-46f5-aff9-a2d8f68cd308 ro quiet
. Then we can use efibootmgr
to add an entry to the UEFI, don’t forget to update the command with your kernel parameters:
# efibootmgr -c -g -L "Debian (EFI stubs)" -l '\EFI\debian\vmlinuz' -u "root=UUID=3c51b884-79b9-46f5-aff9-a2d8f68cd308 ro quiet rootfstype=ext4 add_efi_memmap initrd=\\EFI\\debian\\initrd.img"
If the command fails, you may need to add an option to help to find the EFI partition like: --disk /dev/nvme0n1
.
Finally you can check it is added correctly:
# efibootmgr BootCurrent: 0008 Timeout: 1 seconds BootOrder: 0008,0000,0009,0003,0001,0002,0004,0005 Boot0000* debian Boot0001* Hard Drive Boot0002* UEFI:CD/DVD Drive Boot0003* CD/DVD Drive Boot0004* UEFI:Removable Device Boot0005* UEFI:Network Device Boot0008* Debian (EFI stubs) Boot0009* debian
The next step is: reboot! If everything is fine, your computer should restart and skip GRUB. In case the configuration is not correct you may not able to reboot correctly. If it happens, you can still chose the boot entry manually in you UEFI menu (early computer startup) and boot on the standard debian
entry. Then you can troubleshoot the problem.
Conclusion
Obviously, this is mainly a technical achievement because going through GRUB at boot time is not so long or expensive. But It was important for me to write a complete procedure to implement this as I’m using this configuration every day. I hope it will help some of you.
By the way, there’s some possible improvements. For instance it would be good to regenerate the UEFI loader boot entry automatically when the kernel command line parameters are updated. Even if it is not updated so often it would ensure not to miss any configuration change.