Ivan Kovmir

UEFI Demystified - Ivan Kovmir

UEFI Demystified

Last edit: 2024-09-17

UEFI disambiguation, and practical configuration guide.

Introduction

UEFI Structure & Toolchain

UEFI requires its own GPT disk partition of type EFI, formatted with FAT32 file system. People usually follow the old habit from the days of Legacy BIOS and create this partition at the beginning of a disk, not sure whether it is still mandatory for UEFI (but it would not hurt). If you are unsure about the size, make it 512 MiB. EFI partition is where the bootloader resides among other things, a bootloader is a file with .efi extension.

If you have a running Linux, try to locate and observe your existing EFI partition. Open /etc/fstab, or execute lsblk -f and find a partition with FAT filesystem, then look for its mount point. Within /etc/fstab the entry would look similar to the following:

/dev/sda1         /boot/efi        vfat        defaults         0   0

The partition is mounted under /boot/efi, let us take a look at it:

$ tree /boot/efi
/boot/efi
├── EFI
│   └── Slackware
│       ├── elilo.conf
│       ├── elilo.efi
│       ├── initrd.gz
│       ├── vmlinuz-generic
│       └── vmlinuz-huge
└── limine
    ├── limine.cfg
    └── liminex64.efi

4 directories, 8 files

Indeed, it has: two bootloaders elilo.efi, and liminex64.efi; their config files elilo.conf, and limine.cfg; Linux kernel vmlinuz-* and ramdisk initrd.gz used by the bootloader to boot Linux.

So, you have created a UEFI partition, installed a UEFI bootloader, written a config file and provided the kernel and ramdisk. There is one last thing left to do: you have to tell your UEFI system within the motherboard where to find a UEFI bootloader within a UEFI partition. One of the ways is to put a bootloader under /EFI/BOOT/BOOTX64.EFI, which is a path that should be checked by default. If you put it somewhere else (but within a UEFI partition), you have to tell your UEFI system within the motherboard where to find it. Linux has efibootmgr program to help us. Execute efibootmgr -v to see the current state of things:

BootCurrent: 0000
Timeout: 2 seconds
BootOrder: 0000,0001
Boot0000* Limine    HD(1,GPT,578fd4d6-329c-3e42-b9fc-c6f5a32c7bd0,0x800,0xfa000)/File(\limine\liminex64.efi)
      dp: 04 01 2a 00 01 00 00 00 00 08 00 00 00 00 00 00 00 a0 0f 00 00 00 00 00 d6 d4 8f 57 9c 32 42 3e b9 fc c6 f5 a3 2c 7b d0 02 02 / 04 04 30 00 5c 00 6c 00 69 00 6d 00 69 00 6e 00 65 00 5c 00 6c 00 69 00 6d 00 69 00 6e 00 65 00 78 00 36 00 34 00 2e 00 65 00 66 00 69 00 00 00 / 7f ff 04 00
Boot0001* Slackware HD(1,GPT,578fd4d6-329c-3e42-b9fc-c6f5a32c7bd0,0x800,0xfa000)/File(\EFI\Slackware\elilo.efi)
      dp: 04 01 2a 00 01 00 00 00 00 08 00 00 00 00 00 00 00 a0 0f 00 00 00 00 00 d6 d4 8f 57 9c 32 42 3e b9 fc c6 f5 a3 2c 7b d0 02 02 / 04 04 36 00 5c 00 45 00 46 00 49 00 5c 00 53 00 6c 00 61 00 63 00 6b 00 77 00 61 00 72 00 65 00 5c 00 65 00 6c 00 69 00 6c 00 6f 00 2e 00 65 00 66 00 69 00 00 00 / 7f ff 04 00

The output shows the currently existing entries, and the bootloaders they point to. As you can see, I have two of them: ‘Limine’ which points to \limine\liminex64.efi, and ‘Slackware’ which points to EFI\Slackware\elilo.efi. Backslashes \ are used because FAT is a Microsoft filesystem. It is also worth noting that Microsoft filesystem are, unlike UNIX, case-insensitive, so ‘myFile’ and ‘MYFILE’ are the same name. There is no need to create an entry to point to /EFI/BOOT/BOOTX64.EFI, as it is being checked by default, but it should not hurt if you do. Gentoo wiki does a very good job explaining how to use efibootmgr, read it. To add a boot entry you would have to issue the following command:

efibootmgr -c -d /dev/sda -p 1 -L "EntryName" -l '\path\to\bootloader.efi' # -p to specify partition number.

That is pretty much all you need to know.

Practical UEFI

Let us configure a UEFI bootloader. Limine is my favorite one: dead simple, yet highly configurable. It supports FAT file system only, as it should, the rest is the kernel’s job. It supports Linux boot protocol natively, and it can simply run other UEFI bootloaders—that is, chainloading. Figure 1 shows example configuration. It is possible to try out Limine on your existing system without breaking anything, which is what I will do in this guide further down.

Limine's boot menu
Fig. 1: Limine Bootloader Screenshot

You can build it from the sources, or install the binaries provided on their GitHub repository.

To install Limine, copy .efi file to anywhere within the EFI partition; anywhere under /boot/efi in my case. Then add a boot entry with efibootmgr, follow the example from the previous paragraph. You can now boot into Limine, just write a config file to specify what you want to load. To boot Linux, you need two things: the kernel, and initial ramdisk. A typical Linux Limine entry would look like that:

# Save me as 'limine.cfg' at the root of the EFI partiton.
/WhateverName
    PROTOCOL: linux
    KERNEL_PATH: boot():/path/to/kernel
    MODULE_PATH: boot():/path/to/ramdisk
    CMDLINE: root=/path/to/root/system/partition

CMDLINE specifies arguments to pass to the kernel. Copy these from the existing bootloader config, at the very least you should specify the system’s root partition. boot(): states Limine should look for the file on the same partition where Limine bootloader resides, read Limine documentation to get more details.

Suppose you have M$ Windows installed as your second OS, and you want to be able to choose which OS Limine should run. Unlike Linux, Limine does not support Windows boot protocol natively, but Limine can do bootloader chainloading. It means you can ask Limine to launch Windows' UEFI bootloader, which will handle Windows boot process. So add another entry to make your limine.cfg file look like that:

# Save me as 'limine.cfg' at the root of the EFI partiton.
# Forward slashes ('/') should be used to build paths.
/Some Linux
    PROTOCOL: linux
    KERNEL_PATH: boot():/path/to/kernel
    MODULE_PATH: boot():/path/to/ramdisk
    CMDLINE: root=/path/to/root/system/partition

/M$ Windows 11
    PROTOCOL: efi_chainload
    IMAGE_PATH: boot():/EFI/Microsoft/Boot/bootmgfw.efi

My Limine config looks like that:

TIMEOUT: 5
TERM_FONT_SCALE: 2x2

/Slackware GENERIC
    PROTOCOL: linux
    KERNEL_PATH: boot():/EFI/Slackware/vmlinuz-generic
    MODULE_PATH: boot():/EFI/Slackware/initrd.gz
    CMDLINE: root=/dev/md2 vga=normal ro

/Slackware HUGE
    PROTOCOL: linux
    KERNEL_PATH: boot():/EFI/Slackware/vmlinuz-huge
    MODULE_PATH: boot():/EFI/Slackware/initrd.gz
    CMDLINE: root=/dev/md2 vga=normal ro

/M$ Windows 11
    PROTOCOL: efi_chainload
    IMAGE_PATH: guid(0eaaaa4b-98bf-4e16-9f3a-500000000003):/EFI/Microsoft/Boot/bootmgfw.efi

Refer to Limine documentation for more options.

Conclusion

Bye.

frogbar

© 2024 Ivan Kovmir — CC BY-NC-SA 4.0 License

Created with swege Best viewed with a computer Indexed by Wiby