This is an enormous blob of text, so I recommend using the Table of Contents over there.
Maybe not fair for Devember since actual work started in the beginning of November in the other thread, but someone might find it interesting anyway so here we go. Although most of that was me just deciding how to even approaching this.
1 - Introduction
1.1 - Overview
This project exists because of two changes I wanted to make to my home system that I’m rolling into one:
- Move all local storage except for the boot drive onto more reliable network storage.
- Instead of building another workstation PC, pick a way to run one of several operating systems on this one. Windows for gaming, Linux for development, etc. Probably through a hypervisor.
And so, this is the solution that came from trying different things in the old dev blog thread:
- Create and store several OS images on remote storage over InfiniBand.
- At power-up, use the UEFI to install an empty RAM disk in memory.
- Use GRUB or a similar bootloader menu to select an operating system to load.
- Each GRUB menu entry will chainload an EFI binary that will mirror a corresponding OS image onto the RAM disk, find the OS’s EFI partition, and execute it.
There are other, easier ways of doing this. Windows PE loads from a RAM disk in a way that could theoretically be ported over to desktop Windows, and the Linux kernel has about a dozen ways of creating a RAM disk. But by creating a block storage device in memory, in the EFI environment before any OS is even located, this loader can be made completely independant of what’s actually being loaded.
All it needs are the correct architecture, any device with an OS that can do block reads, and enough RAM.
Also I just think it’s neat.
1.2 - Goals
-
Practical goal, create a flexible + functional RAM disk bootloader and utilities so I can get started spec’ing hardware for my new monster desktop PC.
-
Familiarize myself with the UEFI specification and how the UEFI and its software actually functions. Things like the EFI System Table, how ‘protocols’ and ‘images’ are handled, etc.
-
Familiarize myself with the UEFI codebase/API and the fundamentals that will be needed in pretty much any UEFI application.
-
Stretch goal, but it would be a good time to clean and organize my projects folder, which at this point is scattered across two PCs and several flash drives. Time to give everything an SVN repo so I can go back to something I’ve worked on a year ago and actually know where I was at.
1.3 - Deliverables
-
Image of the EFI partition containing the parts of this project integrated together (GRUB, my own utilities, etc). Or, the image that anyone who just wants to use it can download, unzip to a flash drive, do some minor edits to a configuration file for their system, and be good to go.
-
Miscellaneous UEFI utilities. (blkmirror.efi, ramdisk.efi, efiloader.efi)
-
Source code for anything I’ve made as noted above.
-
Documentation on using said source code.
I’ll be hosting the deliverables for this one on the same little cash register web server that hosted my stuff for the last Devember for a few months. Will update the top of this post with a link once I have enough content to put it up.
2 - Project Components
2.1 - blkmirror.efi
The majority of the actual work in this project.
2.1.1 -
blkmirror set
The EFI device shell mappings (BLKx, FSx, etc) can/will change depending on the current hardware/partitions present. The
SET
,GET
, andCLEAR
commands can be used to add/remove a nonvolatile EFI environment variable which will store the absolute device path of a device mapped in the EFI shell to save a unique device across reconfigurations.Creating a new device alias:
Updating an existing device alias:
2.1.2 -
blkmirror get
Displays an existing device alias for debugging purposes.
Querying an existing device alias:
2.1.3 -
blkmirror clear
Removes an existing device alias from nonvolatile memory.
Removing an existing alias:
2.1.5 -
blkmirror transfer
Starting at LBA 0, mirror all blocks of one EFI shell block device to another.
If the source device is smaller than the destination, leftover blocks will be erased.
If the source device is larger than the destination, data which cannot fit will be truncated.
Transferring between two devices of equal size:
Transferring to a larger device:
Transferring to a smaller device:
2.1.6 -
blkmirror ramdisk
Load the RAM disk driver, initialize, and map a raw RAM disk block device in memory.
Creating a new RAM disk:
2.1.8 -
blkmirror zero
Write
0
to all bytes on a specified device or device alias.
2.1.9 -
blkmirror boot
Scan a block device for a GPT partition with a valid EFI partition signature and attempt to load the EFI executable on that partition at the specified filepath.
2.2 - GRUB
2.3 - 56G InfiniBand
3 - Walkthroughs
3.1 - Setting up the TianoCore EFI-Toolkit.
I’ll be using the original TianoCore EFI Toolkit for this, for no reason other than it’s simpler. I haven’t tried GNU-EFI, so can’t comment on it, but the newer TianoCore EDK2 appeared to be bloated with libraries and framework I have no intention of using, so I’m skipping it. The EFI Toolkit implements all of the EFI function calls and definitions provided by the Intel UEFI specification - TianoCore is a reference implementation of the EFI standard - and so it’s more than adequate for our purposes.
The EFI Toolkit can be found here:
To start with the EFI Toolkit you need a C compiler + linker, and a Make utility. Your favorite build environment for C will probably be fine here if you have one.
If you want zero additional configuration of your setup environment, the EFI Toolkit is configured out-of-the-box for Windows using the Windows Driver Development Toolkit, version 5.2.3790.1830, installed at C:\WINDDK\3790.1830
. I’ll be using this syntax in examples from here onward.
This is a compiler that’s two decades old which you’d expect to be hard to find, but is up on WinWorldPC at:
Add the bin
folder to your environment PATH
variable so you can run nmake from a command line and you’re good to go.
The EFI Toolkit includes several reference projects like osloader and mkramdisk that we won’t be using, but to make a new project the easiest way will be to copy one of these folders and update the projectname.mak
file with your new project’s name.
From there, open a command line in the toolkit’s top directory, run build
to set the environment variables (this file controls the architecture being targeted, file locations, and a few other things you might want to check), and then you can either nmake all
to build all projects, nmake clean
to clear objects that are already built if you have a problem, or cd <project>
nmake -f project.mak
to build only one specific project. Your EFI binary will be placed at build/<architecture>/bin/
.
Other useful documents you’ll want to read are the EFI specification linked below, and the library + design guides included with the toolkit at /doc/EFI_DG.pdf
and /doc/EFI_LIB.pdf
.
https://uefi.org/specifications
3.5 - Configuring GRUB.
3.6 - Configuring the InfiniBand network.
3.7 - Installing an operating system.
4 - Errata
4.1 - Known Issues
Theblkmirror
tool searches for the RAM disk driver “RAMDISK.EFI” at the absolute filepath of/EFI/SHELL/RAMDISK.EFI
. I can improve this behavior in the future but I don’t feel like taking the time to figure out how to programmatically get the directory thatblkmirror
was called from right now.
4.2 - Warnings
-
Be very careful with
blkmirror transfer
. There is no confirmation before proceeding, and there is little to no recovery from sequentially writing every byte of a drive starting at LBA 0. -
On a UEFI which presents itself as a block storage device (my test system appears to do this for ‘BIOS’ flashing), it may be possible to brick a motherboard entirely by calling
blkmirror
to overwrite the UEFI firmware without a USB BIOS flashback controller present. Fortunately these devices don’t appear to be mapped by default in the UEFI shell, so this would at least take some amount of effort to do.