zephyr/arch/x86/zefi
Jakub Michalski f568e2d3ca zefi: add bootargs support
Add bootargs support to zefi. This implements
get_bootargs() when both efi and bootargs are
selected in config.

Signed-off-by: Jakub Michalski <jmichalski@internships.antmicro.com>
Signed-off-by: Filip Kokosinski <fkokosinski@antmicro.com>
2024-09-05 12:30:39 -05:00
..
README.txt
efi.h
efi.ld
printf.h
zefi.c
zefi.py

README.txt

Operation
=========

Run the "zefi.py" script, passing a path to a built zephyr.elf file
(NOT a "zephyr.strip" intended for grub/multiboot loading -- we need
the symbol table) for the target as its sole argument.  It will emit a
"zephyr.efi" file in the current directory.  Copy this to your target
device's EFI boot partition via whatever means you like and run it
from the EFI shell.

Theory
======

This works by creating a "zephyr.efi" EFI binary containing a zephyr
image extracted from a built zephyr.elf file.  EFI applications are
relocatable, and cannot be placed at specific locations in memory.
Instead, the stub code will copy the embedded zephyr sections to the
appropriate locations at startup, clear any zero-filled (BSS, etc...)
areas, then jump into the 64 bit entry point.

Arguably this is needlessly inefficient, having to load the whole
binary into memory and copy it vs. a bootloader like grub that will
load it from the EFI boot filesystem into its correct location with no
copies.  But then, the biggest Zephyr application binaries we have on
any platform top out at 200kb or so, and grub is at minimum about 5x
that size, and potentially MUCH more if you start enabling the default
modules.

Source Code & Linkage
=====================

The code and link environment here is non-obvious.  The simple rules
are:

1. All code must live in a single C file
2. All symbols must be declared static

And if you forget this and use a global by mistake, the build will
work fine and then fail inexplicably at runtime with a garbage
address.  The sole exception is the "efi_entry()" function, whose
address is not generated at runtime by the C code here (it's address
goes into the PE file header instead).

The reason is that we're building an EFI Application Binary with a
Linux toolchain.  EFI binaries are relocatable PE-COFF files --
basically Windows DLLs.  But our compiler only generates code for ELF
targets.

These environments differ in the way they implement position
independent code.  Non-static global variables and function addresses
in ELF get found via GOT and PLT tables that are populated at load
time by a system binary (ld-linux.so).  But there is no ld-linux.so in
EFI firmware, and the EFI loader only knows how to fill in the PE
relocation fields, which are a different data structure.

So we can only rely on the C file's internal linkage, which is done
via the x86_64 RIP-relative addressing mode and doesn't rely on any
support from the environment.  But that only works for symbols that
the compiler (not the linker) knows a-priori will never be in
externally linked shared libraries.  Which is to say: only static
symbols work.

You might ask: why build a position-independent executable in the
first place?  Why not just link to a specific address?  The PE-COFF
format certainly allows that, and the UEFI specification doesn't
clearly rule it out.  But my experience with real EFI loaders is that
they ignore the preferred load address and will put the image at
arbitrary locations in memory.  I couldn't make it work, basically.

Bugs
====

No support for 32 bit EFI environments.  This would be a very tall
order given the linker constraints described above (i386 doesn't have
a IP-relative addressing mode, so we'd have to do some kind of
relocation of our own).  Will probably never happen unless we move to
a proper PE-COFF toolchain.

There is no protection against colliding mappings.  We just assume
that the EFI environment will load our image at an address that
doesn't overlap with the target Zephyr memory.  In practice on the
systems I've tried, EFI uses memory near the top of 32-bit-accessible
physical memory, while Zephyr prefers to load into the bottom of RAM
at 0x10000.  Even given collisions, this is probably tolerable, as we
could control the Zephyr mapping addresses per-platform if needed to
sneak around EFI areas.  But the Zephyr loader should at least detect
the overlap and warn about it.

It runs completely standalone, writing output to the current
directory, and uses the (x86_64 linux) host toolchain right now, when
it should be integrated with the Zephyr toolchain and build system.