efiboot - An UEFI payload for coreboot
This is the project homepage for efiboot, a project that aims to build an UEFI payload for coreboot. The website describes the various components that make up efiboot and also how an UEFI payload can be built and packaged for use with coreboot. So far, the project has not been tested on real hardware but instead, QEMU has been used for development and testing.
Last updated: $Date: 2013/01/22 20:58:32 $.
Updates
Jan 22nd, 2013
Fixed the output from the DXE Core and updated the "screenshot".
Jan 21st, 2013
Add a new "screenshot" and information on how to interpret it.
Jan 20th, 2013
Re-work the build instructions to reflect recent check-outs of the TianoCore tree. Also, attempt to document some parts of the CorebootPkg.
Jan 5th, 2013
HTML changes related to my home-grown "CMS."
Architecture
Overview
A three layer approach is taken. The bottom layer is made up of coreboot in an unmodified version. Its task is to initialize and gather information about the hardware in the system. When coreboot has completed execution, it hands off control to a payload, the middle layer of the architecture.
The payload is efiload, a loader for the upper layer. The loader detects and parses the coreboot tables and converts the information encoded in the tables into a form understood by the UEFI components. Then, it loads and starts the UEFI components.
Fig. 1: Architecture
The third layer is composed of the UEFI components. The design and architecture of the UEFI components is dictated by the UEFI specification and the PI architecture.
Figure 1 above illustrates the architecture on a high level. It should be noted that the DXE Core presents the UEFI interfaces to any upper layers, e.g. an operating system, and completely hides the bottom layers. In fact, when the DXE Core has been started, the bottom layers do not exist anymore.
The corebooot layer
Coreboot performs platform initialization and starts a so-called "payload" after the initialization phase. The payload needs to be a standard ELF file, which is loaded at its linked address and then executed. Coreboot passes information about the system, like e.g. memory size, to the payload in the coreboot tables.
The efiload layer
The UEFI components expect that some basic information about the system is passed in a list of Hand-off Blocks (HOBs). The HOB list must be created by the efiload component from the information found in the coreboot tables. Most imporantly, information about the memory layout and the location of the UEFI Firmware Volume must be encoded in the HOBs.
The UEFI layer
The UEFI components implement the DXE phase of the PI architecture. In the DXE phase, the so-called DXE core starts several independent DXE drivers which implement the UEFI Boot Services and the UEFI Runtime Services. When all DXE drivers in the UEFI Firmware Volume have been executed, UEFI drivers and applications are started. Those drivers and applications make use of the UEFI services to fully initialize the system. As drivers execute, new hardware may be discovered. Information about hardware is passed to the DXE core via the UEFI Boot Services. The DXE core takes that information and builds a device tree representation of the system hardware. When a boot loader or an operating system is started, it may query said information through the UEFI system table.
Implementation
... of coreboot
Coreboot is a separate and independent project. No changes to coreboot are required for efiboot.
... of efiload
The efiload component is build with the help of libpayload which is a library part of the coreboot project. The library implements a limited set of the standard C library functions. It also implements start-up code so that an application can be written to start at its main() function instead of worrying about the low-level details. Efiload makes use of those facilities and uses libpayload data structures to locate and parse the coreboot tables. This latter task can be regarded as the input path of efiload.
The output path of efiload uses data structures implemented as part of the TianoCore EDK2 project. Those structures are documented in the UEFI and PI specifications and define how a Hand-off Block (HOB) should look like. Efiload transforms the coreboot tables into a HOB list.
The HOB list produced by efiload contains the following HOBs:
- The HOB List Header, described by a structure of type EFI_HOB_HANDOFF_INFO_TABLE. This HOB describes the basic memory layout of the system, i.e. lowest and highest addresses along with information on what memory is available to the DXE phase.
- A Firmware Volume HOB, encoded in a structure of type EFI_HOB_FIRMWARE_VOLUME. This HOB encodes information on where the Firmware Volume (FV) of the boot flash resides. Because the FV is encapsulated in the coreboot flash image, the efiload component shadows the firmware volume to main memory and stores information about the shadow copy only in the HOB. Currently, the location of the shadow copy is defined to be the highest memory range available, e.g. an 8 MByte flash image will be shadowed to 8 MBytes below the top of physical memory.
- One or more resource description HOBs which are structures of type EFI_HOB_RESOURCE_DESCRIPTOR. The resource description HOBs are used to pass information about the location of individual, possibly non-continuous blocks of physical system memory. Because the Firmware Volume is shadowed below the top of physical memory, the resource descriptor HOBs currently do not announce that memory region to the DXE phase.
- One memory allocation HOB of type EFI_HOB_MEMORY_ALLOCATION_MODULE. The HOB is used to record the memory allocation of the loaded DXE core ELF image.
- One CPU HOB, i.e. a EFI_HOB_CPU structure, that describes the capabilities of the CPU, namely number of address lines and the size of the CPUs I/O space. Currently, the information passed in this HOB is hard-coded to the i386 values of 32 address lines and 16 Bit wide I/O addresses.
- The HOB list is terminated by a HOB of type EFI_HOB_TYPE_END_OF_HOB_LIST which consists only of a generic HOB header, a structure of type EFI_HOB_GENERIC_HEADER.
When efiload is done with the data transformation, it locates the DXE core in a UEFI Firmware Volume (FV). For ease of implementation and testing, the FV is appended as a binary section to the efiload binary. This allows the efiload code to address the size and location of the FV using symbols, i.e. simple C pointers. The code in efiload that searches the FV also makes use of data structures implemented in the TianoCore EDK2 project.
... of the UEFI layer
The UEFI Layer is implemented using the code provided by the TianoCore EDK II project. The EDK II code implements all three Platform Initialization (PI) phases SEC, PEI and DXE mandated by the UEFI-related Platform Initialization Specification. For the efiboot project, only the DXE phase is relevant. All other code is not used.
The code contained in the EDK II itself can probably be used without modifications. However, the CorebootPkg has been added to package and enhance the EDK II code. Also, to aid debugging, the EDK II build tools have been modified to work with ELF images instead of PE32+ binaries. By default, the TianoCore tools will work with ELF images, but convert them to a PE32+ binary before placing them into the firmware flash.
Boot Flash Layout
Coreboot expects the payload to be known at compile time. This is required because the coreboot build process compresses the payload and packages it along with the coreboot code in a flash image.
The UEFI specification and the implementation of the UEFI components dictate that all of the UEFI components are stored in an UEFI Firmware Volume (FV), a special format for a kind of file system in the firmware flash.
Fig. 2: Boot Flash Layout
In order to fulfill both somewhat contradicting requirements, the UEFI Firmware Volume is encapsuled in the coreboot payload at compile time. At runtime, the efiload layer extracts the UEFI Firmware Volume and shadows it to a memory location. This means, the coreboot build process is used to assemble a bootable flash image and hence the boot flash layout follows the coreboot guidelines. Also, the UEFI components will always work on a UEFI Firmware Volume shadowed into system memory.
Screenshots
Because coreboot, efiload and the UEFI code produce console only output, the "screenshot" is presented in text-only format.
The first line printed by efiload is:
Everything printed before that line is emitted by coreboot.
Control is handed over to the DXE core after efiload printed the following line:
All output after that comes from the EDK II code. As you can see, the DXE core starts up and eventually aborts due to a failed assertion. This is because the Firmware Volume (FV) used in the test cycle does not contain any other modules than the DXE core itself. Consequently, the DXE core aborts because modules that are mandatory in order to provide all boot services demanded by UEFI are missing.
There are a lot of messages from the elf_lookup_symbol() method like the one below. Those seem to be harmless. They disappear if the output ELF image isn't stripped as part of the build. To do that, adjust Conf/build_rule.txt and remove the --strip-unneeded parameter from the objdump call.
In the previous "screenshot" published here, output from the EDK II code was not shown. This was because a few variables controlling debug output weren't set properly during the build.
A remote GDB session however revealed that the DXE core does in fact run to the point where it attempts to jump into the "Boot Device Selection" protocol:
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x003155fe in CpuDeadLoop ()
at MdePkg/Library/BaseLib/CpuDeadLoop.c:37
37 for (Index = 0; Index == 0;);
(gdb) bt
#0 0x003155fe in CpuDeadLoop ()
at MdePkg/Library/BaseLib/CpuDeadLoop.c:37
#1 0x00301077 in DxeMain (HobStart=0x3eb4010)
at MdeModulePkg/Core/Dxe/DxeMain/DxeMain.c:481
#2 0x003005c8 in ProcessModuleEntryPointList (HobStart=0x207000)
at Build/Coreboot/DEBUG_ELFGCC/IA32/MdeModulePkg/Core/Dxe/DxeMain/DEBUG/AutoGen.c:380
#3 0x00300019 in _ModuleEntryPoint ()
#4 0x00103e31 in ?? ()
#5 0x00100d73 in ?? ()
#6 0x00100051 in ?? ()
#7 0x820fd045 in ?? ()
#8 0x00000000 in ?? ()
(gdb)
To start QEMU in a way that lets you attach the debugger, use the following command (on Ubuntu 12.10) in the coreboot build directory:
The debugger can be attached like this:
Download
The source code of coreboot, libpayload and TianoCore EDK2 can be obtained from the respective project webpages.
Unfortunately, the efiload source code as well as the CorebootPkg cannot be distributed at this time due to legal obligations.
Hacking
Because the source code for this project cannot be released at this time, the information contained in this section is a little sparse and probably not too interesting for the greater public.
The efiboot code is built in a sandbox w/ the following directory structure:
efiboot/coreboot
efiboot/libpayload
efiboot/edk2
efiboot/efiload
The CorebootPkg
Check out the CorebootPkg and place it in efiboot/edk2/CorebootPkg. No further adjustments are required.
The CorebootPkg contains a platform description file (*.dsc), a package declaration file (*.dec), a flash description file (*.fdf) and an implementation of the PeCoffLib interface which operates on ELF images instead of PE32+ files.
The platform description file determines what actual implementation backs which library interface. It also determines which modules are built for this platform. Finally, it contains the definitions (as opposed to the declarations) of the PCD values known at compile time. The platform description file (*.dsc) format is specified by the TianoCore EDK II. The CorebootPkg is very similar to all other packages in the EDK II tree. The one notable difference is that it maps the PeCoffLib library interface to its own implementation.
When modifying the CorebootPkg, either by adding new modules or when updating to a newer TianoCore code base, it is frequently required to find out which implementations of a given library interface are available. A command like this one, issued from the edk2/ directory can be used for this task:
The package declaration file must declare (as opposed to define) all PCD values known at build time and referenced in the package. The package declaration file (*.dec) format is specified by the TianoCore EDK II. Previously, those values had been declared in the platform description file, too, but have apparently been moved to a separate file in more recent version of the TianoCore EDK II source tree. Failing to declare the PCDs will yield in errors from the build toolchain.
The flash description file (*.fdf) format is specified by the TianoCore EDK II as well. The file contains meta-data about the flash devices and images the build system deals with. Specifically, it defines what firmware volumes will be build and what modules they will contain. The CorebootPkg defines one firmware volume named "FvRecovery" containing all required modules. The minimum requirements are the DXE Core plus those modules that install the "architectural protocols", i.e. implement the UEFI boot and runtime services referenced from the system table.
UEFI components
Place the EDK II source code in the efiboot/edk2 directory. Go to the BaseTools subdirectory and build the toolchain for the EDK II code. On Ubuntu, make sure the uuid-dev package is installed (along with the compiler, of course). In order to work with ELF images only, replace BaseTools/Source/C/GenFw/GenFw.c. The replacement tool must accept the same parameters as the original one. It's only task is to copy the input file to a "branded" output file: The build process passes the "-e" parameter when invoking the GenFw utility, indicating what kind of module the binary is (e.g. UEFI_DRIVER, DXE_CORE, ...). This information is used at runtime to e.g. find the DXE Core module in the firmware volume. The e_flags field in the ELF header can be used to store that data.
Source in the edksetup.sh script from the edk2 directory in order to set-up the required workspace files such as e.g. Conf/tools_def.txt.
Then adjust the file Conf/tools_def.txt so the tool chain produces executable files instead of shared objects: Look for the ELFGCC tag and remove the --shared parameter from the linker flags. Instead add -Ttext 0x00 to the linker flags. The linker flags have two effects: First, the linker will fail if the output file has unresolved symbols. Second, linking the binary at address 0x00 will make it easier to do the math, should you attempt to attach a debugger and load a symbol file. Depending on your compiler, you may also need to add -fno-stack-protector to the compiler flags.
Adjust the file Conf/target.txt so the active target is the CorebootPkg and the toolchain tag is ELFGCC.
Finally, run build.
The file BuildNotes2.txt, also part of the EDK II source tree, contains further information about the build process and tweaking it.
efiload
Store the efiload source code in efiboot/efiload. Create a symbolic link named FVRECOVERY.Fv that points to ../edk2/Build/Coreboot/DEBUG_ELFGCC/FV/FVRECOVERY.Fv. Compile the code via make. The output of the build process is the file efiloader.elf which is used as the coreboot payload.
coreboot
Place the coreboot source code in the efiboot/coreboot directory. The coreboot Build HOWTO contains further information on this topic. When configuring coreboot via make menuconfig, make sure the payload points to the efiloader.elf file that contains the efiload code.
Links
Here are a few links to related projects.
- Homepage of coreboot project
- The libpayload page in the coreboot wiki
- The EDK2 Project Page, part of the TianoCore project
- Web presence of the UEFI Forum, host of the UEFI and PI specifications
- The QEMU Website
- A few EDK II Specifications.
Contact
Contact the author Philip Schulz by Email.