Page 1 of 1

UEFI bootloader todo

Posted: Sun Apr 11, 2021 4:58 am
by Astral
Hi. For legacy BIOS there is a nice osdev wiki entry https://wiki.osdev.org/Rolling_Your_Own_Bootloader

I had a search but could not find something equivalent for modern UEFI based bootloaders (UEFI applications). A lot of the stuff mentioned in the above article is already handled in the EDK2 framework if using OVMF.

So my question, what do you think is a bunch of todo's for a basic modern UEFI application (bootloader) if the OVMF BIOS is already setup.

- Enable GOP and display some boot splash screen?
- Store splash screen in EFI variables?
- Prepare memory map to pass to kernel?
- Create runtime services?
- Enumerate PCI bus?
- Create basic filesystem parser
- Find your harddisk device and load the kernel using your parser?
- ...?

Re: UEFI bootloader todo

Posted: Sun Apr 11, 2021 5:50 am
by klange
Astral wrote:- Enable GOP and display some boot splash screen? Yes
- Store splash screen in EFI variables? No
- Prepare memory map to pass to kernel? Yes
- Create runtime services? No? What do you think you're going to do with EFI runtime services? You're writing a bootloader, not a driver.
- Enumerate PCI bus? No.
- Create basic filesystem parser No, put your kernel somewhere EFI can find it; when you have a driver written for your kernel for a different filesystem you can port that to the loader
- Find your harddisk device and load the kernel using your parser?
- ...?

Re: UEFI bootloader todo

Posted: Sun Apr 11, 2021 10:02 am
by kzinti
Astral wrote: - Find your harddisk device and load the kernel using your parser?
- ...?
Just load your kernel and any other things (initrd, startup modules, etc) you need using UEFI protocols. That's what UEFI is for: to load your OS.

Re: UEFI bootloader todo

Posted: Sun Apr 11, 2021 12:28 pm
by bzt
Astral wrote:- Create basic filesystem parser
- Find your harddisk device and load the kernel using your parser?
- ...?
If you're fine with FAT only and loading everything from a single partition (ESP), then use EFI_SIMPLE_FILE_SYSTEM_PROTOCOL. If you want to load from multiple partitions, and/or from non-FAT filesystems (like BIOS Boot Partition or using ext2 for example), then you'll have to implement the file system parser yourself and use EFI_BLOCK_IO_PROTOCOL.

Cheers,
bzt

Re: UEFI bootloader todo

Posted: Sun Apr 11, 2021 3:25 pm
by zaval
Astral wrote: Hi. For legacy BIOS there is a nice osdev wiki entry https://wiki.osdev.org/Rolling_Your_Own_Bootloader

I had a search but could not find something equivalent for modern UEFI based bootloaders (UEFI applications). A lot of the stuff mentioned in the above article is already handled in the EDK2 framework if using OVMF.

So my question, what do you think is a bunch of todo's for a basic modern UEFI application (bootloader) if the OVMF BIOS is already setup.

- Enable GOP and display some boot splash screen?
- Store splash screen in EFI variables?
- Prepare memory map to pass to kernel?
- Create runtime services?
- Enumerate PCI bus?
- Create basic filesystem parser
- Find your harddisk device and load the kernel using your parser?
- ...?
You don't "enable" GOP, but find if it's present, for example - using LocateProtocol() Boot Service. Don't forget, that all protocols, and GOP among them, are Boot Services time entities, they won't work after ExitBootServices(), so they aren't suitable for using by the kernel (often people forget this). However, you get the framebuffer parameters (and its (system space) address) from GOP and these can be used by your kernel.
Your splash screen (I call it Boot Screen) elements need to be "saved" in memory and transferred by the loader to the kernel via specific to your OS "boot protocol" - you are free to devise your own. EFI variables for storing bitmaps is a nice way to blow up the FW. :mrgreen:
Runtime Services are already created by the FW, you need to pass info about them (memory map has entries, marked as RTS) to the kernel and it should decide what to do with them. You will need to set mapping for every RTS range, if you want them appear in the "virtual" address space at addresses, you wish (as opposite to 1:1 mapping). It's done by your Loader, using SetVirtualAddressMap() Runtime Service, after calling ExitBootServices() Boot Service. Most probably you would want, because despite the idea of leaving RTS pages at the same (numerically) addresses in virtual address space(s) sounds attractive, it's not conflict free. For example, if your kernel puts some of its entities at the (virtual) address X, there is no guarantee, that some RTS page range isn't placed there (in the system AS).
- Create basic filesystem parser
- Find your harddisk device and load the kernel using your parser?
If your FS is not supported by UEFI implementation, then yes, the parser is needed.
Yes, you need to find it.
The easiest way of "finding" your harddisk (Boot Volume), where the OS is installed is to place its device path (during the OS installation) into its Load Option descriptor into the second FilePathList array element (that is FilePathList[1]. Then your loader would have easy time of figuring out where to load from. More detailized:
During installation, your installer, figures out what volume/partition the OS is going to be installed on.
Somehow, it passes this info to the Loader, or better yet - an UEFI part of the installer, some tiny app, that would be run just during the installation process. With the GPT partitioning scheme, it's easy - just tell the GUIDs of the disk and/or partition. Installer thus creates a Load Option with filled in FilePathList array - the first element points to your Loader, the second points to your Boot Volume (or even to the directory, that plays the "system root" role for your OS).
This way, the loader is freed of all the burden of messing around finding the Boot Volume every time and it just processes routinely the Boot variable it gets. If it's a normal, post installation run, then it fetches the Boot Volume path and uses either SIMPLE_FILE_SYSTEM_PROTOCOL if the FS is FAT or BLOCK_IO_PROTOCOL and its own FS parser if it's something else. If it's a preinstallation, or "Live" mode run, then BootCurrent variable either points to something, that doesn't contain your descriptor (for example if your user ran your loader through UEFI shell, then the descriptor would point to that plus user can provide some "flags" - they will be passed in the "OptionalData" descriptor's field. Confusingly enough, this field is called "LoadOptions" in the LOADED_IMAGE_PROTOCOL, from where you can get them too. Or the BootCurrent doesn't exist at all, even in the OVMF, which is against the spec. So if BootCurrent is absent or doesn't point to your ordinary descriptor (which you can recognize by putting in its "OptionalData" field some GUID or shorter "magic" number), then it's a preinstallation run and the loader searches for the OS in the same volume, where it has been loaded from (DeviceHandle field of the LOADED_IMAGE_PROTOCOL instance of the loader image, reports that volume). It's logical, right, if it's a live session, your OS resides where your loader does (USB stick for example).