| 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. |