Page MenuHomeFreeBSD

loader.efi: Build a boot menu based on UEFI boot manager
Needs ReviewPublic

Authored by stephane.rochoy_stormshield.eu on Thu, Mar 19, 1:49 PM.
Tags
None
Referenced Files
Unknown Object (File)
Mon, Mar 30, 1:06 PM
Unknown Object (File)
Mon, Mar 30, 8:47 AM
Unknown Object (File)
Sat, Mar 28, 3:26 AM
Unknown Object (File)
Fri, Mar 27, 9:49 AM
Unknown Object (File)
Fri, Mar 27, 9:37 AM
Unknown Object (File)
Fri, Mar 27, 1:17 AM
Unknown Object (File)
Thu, Mar 26, 2:13 AM
Unknown Object (File)
Thu, Mar 26, 1:49 AM

Details

Summary

Following the disussion in D55559, here's a (very shy) first step starting to
demonstrate the use of the UEFI Boot Manager to build a menu with Lua to let the
user pick a partition.

The patch is enabled with the WITH_LOADER_EFIBOOTMGRMENU option and require to
manually populate ESP's /EFI/FreeBSD/lua directory with the content of
stand/lua/ (creating any missing directory).

This is a PoC, so it is advised to use the BootNext mechanism to boot the
resulting loader.efi ;)

It expose BootCurrent, Timeout and BootOrder under the efibootmgr Lua
module as follow:

  • boot_current: an integer, e.g., 5 for Boot0005.
  • timeout: an integer for the timeout duration in seconds.
  • boot_order: a table (with first index being 1) were each element is an associative array with the following keys:
    • bootnum: boot number as an integer, e.g., 5 for Boot0005
    • description: the description (a.k.a., label) as a string
    • attributes: attributes as an integer
    • is_active: boolean telling if the active attribute is set
    • is_hidden: boolean telling if the hidden attribute is set
    • devpath: device path as a string

It then start efibootmgr.lua instead of loader.lua.

It don't boot anything. I first wanted to validate I correctly understood the
target approach. I also need some guidance on how to convert a load option into
the required things necessary to boot a given kernel. E.g., how do I map it to
things like currdev, kernelname vfs.root.mountfrom, etc…

Some implementation notes:

  • it change liblua's LUAPATH from /boot/lua (in the booted partition) to

/EFI/FreeBSD/lua in the ESP

  • it conflict with LOADER_KBOOT (don't know why):
--- all_subdir_stand/kboot ---
ld: error: undefined symbol: efi_global_getenv
>>> referenced by lefibootmgr.c:29 (…/stand/liblua/lefibootmgr.c:29)
>>>               lefibootmgr.o:(luaopen_efibootmgr) in archive
…/amd64.amd64/stand
/liblua/liblua.a
Test Plan

Here is a sample output:

Setting currdev to disk0p1:
FreeBSD/amd64 EFI loader, Revision 3.0
(Thu Mar 19 14:09:07 CET 2026 stephaner@sns-ci-main.delta-amd64.labo.int)

   Command line arguments: loader.efi
   Image base: 0x6d36c000
   EFI version: 2.80
   EFI Firmware: American Megatrends (rev 5.27)
   Console: efi (0x1000)
   Load Path: \EFI\BOOT\efibootmgrmenu.efi
   Load Device: PciRoot(0x0)/Pci(0xD,0x0)/USB(0x1,0x0)/HD(1,MBR,0x90909090,0x1,0x10418)
   BootCurrent: 0006
   BootOrder: 0005 0006[*] 0001 0004 0002 0000 0003
package.path:    /EFI/FreeBSD/lua/?.lua
loader.lua_path: /EFI/FreeBSD/lua
>>> EFI Boot Manager Menu
[1]  Boot0005 UEFI OS*
        attributes: 0x1<ACTIVE,CATEGORY_BOOT,>
        devpath:    PciRoot(0x0)/Pci(0xD,0x0)/USB(0x1,0x0)/HD(1,MBR,0x90909090,0x1,0x10418)/\EFI\BOOT\BOOTX64.EFI
[2] +Boot0006 efibootmgr*
        attributes: 0x1<ACTIVE,CATEGORY_BOOT,>
        devpath:    HD(1,MBR,0x90909090,0x1,0x10418)/\EFI\BOOT\efibootmgrmenu.efi
[3]  Boot0001 UEFI OS*
        attributes: 0x1<ACTIVE,CATEGORY_BOOT,>
        devpath:    HD(4,GPT,7E15E2B7-CECA-11F0-8F93-000DB42C8588,0x401000,0x32000)/\EFI\BOOT\BOOTX64.EFI
[4]  Boot0004 BACKUP*
        attributes: 0x1<ACTIVE,CATEGORY_BOOT,>
        devpath:    HD(4,GPT,7E15E2B7-CECA-11F0-8F93-000DB42C8588,0x401000,0x32000)/\EFI\BOOT\backup.efi
[5]  Boot0002 MAIN*
        attributes: 0x1<ACTIVE,CATEGORY_BOOT,>
        devpath:    HD(4,GPT,7E15E2B7-CECA-11F0-8F93-000DB42C8588,0x401000,0x32000)/\EFI\BOOT\main.efi
[6]  Boot0000 TEST*
        attributes: 0x9<ACTIVE,HIDDEN,CATEGORY_BOOT,>
        devpath:    VenHw(99E275E7-75A0-4B37-A2E6-C5385E6C00CB)
[7]  Boot0003 debian*
        attributes: 0x9<ACTIVE,HIDDEN,CATEGORY_BOOT,>
        devpath:    VenHw(99E275E7-75A0-4B37-A2E6-C5385E6C00CB)
Default: Boot0005 UEFI OS
Choose:
Booting Boot0005 UEFI OS...
Loading kernel...
No kernel set, failed to load from module_path
can't load 'kernel'
<<< EFI Boot Manager Menu

can't load 'kernel'

Type '?' for a list of commands, 'help' for more detailed help.
OK

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Passed
Unit
No Test Coverage
Build Status
Buildable 71510
Build 68393: arc lint + arc unit

Event Timeline

Don't set LUAPATH from stand/efi/loader/Makefile as it seems to be ignored.

@kevans I set you as a reviewer since you seems to have pointed @imp at something [you'd] started that will help that a lot... ;)

@imp @kevans I totally understand you may not have time to review this patch right now but could you just provide me with some hints (or code/commits to look at) on how I should convert EFI load options into something I can feed core.boot() and the likes with? It would help me prune a bit more the problem I guess ;)

@imp @kevans I totally understand you may not have time to review this patch right now but could you just provide me with some hints (or code/commits to look at) on how I should convert EFI load options into something I can feed core.boot() and the likes with? It would help me prune a bit more the problem I guess ;)

Sorry, I missed that there was a direct inquiry here previously- I think I need a little more of a concrete example of the problem. Our efi loader today takes load options and processes them into args for main(), which then either get processed into howto flags or set in the environment. Are we wanting different behavior here?

@imp @kevans I totally understand you may not have time to review this patch right now but could you just provide me with some hints (or code/commits to look at) on how I should convert EFI load options into something I can feed core.boot() and the likes with? It would help me prune a bit more the problem I guess ;)

Sorry, I missed that there was a direct inquiry here previously- I think I need a little more of a concrete example of the problem. Our efi loader today takes load options and processes them into args for main(), which then either get processed into howto flags or set in the environment. Are we wanting different behavior here?

We want to expose the load options to Lua, let the user pick one from a menu, and boot it. One question I have, for example, is how do you convert a load option (an EFI device path) into a value suitable for currdev?

In D55936#1282707, @kevans wrote:>>

Sorry, I missed that there was a direct inquiry here previously- I think I need a little more of a concrete example of the problem. Our efi loader today takes load options and processes them into args for main(), which then either get processed into howto flags or set in the environment. Are we wanting different behavior here?

We want to expose the load options to Lua, let the user pick one from a menu, and boot it. One question I have, for example, is how do you convert a load option (an EFI device path) into a value suitable for currdev?

Ahh, I think I see where I misunderstood. This is actually kind of challenging, because the Boot entry device path isn't necessarily well-specified enough in all cases. match_boot_info in loader/main.c is probably the starting point you want, but the tail end is where I think it gets hairy: for UFS you'll end up with a UFS partition and things are easy, but today we have to probe around for ZFS pools (because currdev for ZFS uses zfs:pool/dataset:). I think in an ideal world, you'd need to hammer out a device path spec for ZFS (presumably we'd be storing the zpool GUID, a dataset GUID, and a path name).

In D55936#1282707, @kevans wrote:>>

Sorry, I missed that there was a direct inquiry here previously- I think I need a little more of a concrete example of the problem. Our efi loader today takes load options and processes them into args for main(), which then either get processed into howto flags or set in the environment. Are we wanting different behavior here?

We want to expose the load options to Lua, let the user pick one from a menu, and boot it. One question I have, for example, is how do you convert a load option (an EFI device path) into a value suitable for currdev?

Ahh, I think I see where I misunderstood. This is actually kind of challenging, because the Boot entry device path isn't necessarily well-specified enough in all cases. match_boot_info in loader/main.c is probably the starting point you want, but the tail end is where I think it gets hairy: for UFS you'll end up with a UFS partition and things are easy, but today we have to probe around for ZFS pools (because currdev for ZFS uses zfs:pool/dataset:). I think in an ideal world, you'd need to hammer out a device path spec for ZFS (presumably we'd be storing the zpool GUID, a dataset GUID, and a path name).

Wow, I'll first start with the easy part then… Thanks for the hints :)

In D55936#1282707, @kevans wrote:>>

Sorry, I missed that there was a direct inquiry here previously- I think I need a little more of a concrete example of the problem. Our efi loader today takes load options and processes them into args for main(), which then either get processed into howto flags or set in the environment. Are we wanting different behavior here?

We want to expose the load options to Lua, let the user pick one from a menu, and boot it. One question I have, for example, is how do you convert a load option (an EFI device path) into a value suitable for currdev?

Ahh, I think I see where I misunderstood. This is actually kind of challenging, because the Boot entry device path isn't necessarily well-specified enough in all cases. match_boot_info in loader/main.c is probably the starting point you want, but the tail end is where I think it gets hairy: for UFS you'll end up with a UFS partition and things are easy, but today we have to probe around for ZFS pools (because currdev for ZFS uses zfs:pool/dataset:). I think in an ideal world, you'd need to hammer out a device path spec for ZFS (presumably we'd be storing the zpool GUID, a dataset GUID, and a path name).

Wow, I'll first start with the easy part then… Thanks for the hints :)

I wrote a ZFS device spec years ago. Nobody implemented it, alas. It was just a VendorSpec(UUID,be-info) sort of thing, though. I think it would be better to start from scratch.

For the UEFI Boot items, there's another pull request that starts to export EFI data to Lua that might be useful... But I've not had time to look closely at both of these.

In D55936#1282780, @imp wrote:

For the UEFI Boot items, there's another pull request that starts to export EFI data to Lua that might be useful... But I've not had time to look closely at both of these.

You're probably talking about #1982. I'll have a close look at it.