POSIX-UEFI

From OSDev Wiki
Jump to navigation Jump to search

POSIX-UEFI is a very lightweight developing environment to create UEFI applications, similar to GNU-EFI. The EDK2 is a large environment with its own build system. POSIX-UEFI on the other hand is just a single, statically linked library (about 32k) and headers for compiling UEFI applications under Linux (and other POSIX compatible systems).

This tutorial will guide you through to create a simple Hello World UEFI application using this library.

Basic Concept

The concept is, EDK2's API (which gnu-efi tries to comply with) has a reasonably unfamiliar API. POSIX-UEFI aims to wrap that over with a more conventional POSIX API.

Requirements

Download POSIX-UEFI and copy the uefi directory into your source tree (about a dozen files, ca. 132K).

$ git clone https://gitlab.com/bztsrc/posix-uefi.git
$ cd (your project)
$ ln -s ../posix-uefi/uefi

Libraries

Normally this is completely hidden from the developer, not needed to know. When you first time run USE_GCC=1 make, it will create the following files in the "uefi/" directory:

  • crt0_x86_64.o: A CRT0 (C runtime initialization code) that will call your "main" function (note: no efi_main!!!).
  • libuefi.a: A small static library which provides the POSIX compatibility layer.

This is only when you use GCC. CLang links directly with the object files, because it is configured to generate PE files in the first place, so it can't link with an ELF static library.

Headers

POSIX-UEFI provides everything in a single header file. It has defines and typedefs for almost everything you'll ever need (Simple File System Protocol, Console In/Out Protocols, Graphics Output Protocol, Serio IO Protocol, Block IO Protocol etc. etc. etc.), but it not complete, and never intended to be. The goal is to have all the defines needed for it's libc. Since it provides a POSIX layer, all the usual UEFI defines are renamed according to ANSI C standards, like EFI_MEMORY_DESCRIPTOR -> efi_memory_descriptor_t, UINTN -> uintn_t etc.

If you need some more header files, you can use the EDK2 or gnu-efi ones under /usr/include/efi. POSIX-UEFI will add those include paths as well, and its header file was carefully written so that it can work with and without those headers, no naming conflicts.

Linker Script

Don't care. POSIX-UEFI has your back covered and chooses the correct script for you.

Code Examples

The repository contains examples for all the functions that a boot loader might need:

  • The source for the Hello World example we are talking about
  • Querying and displaying command line arguments
  • Listing directories and loading files (lot simpler to use "fopen" than using UEFI to load files)
  • Reading and writing sectors (again, lot simpler than using UEFI to read sectors)
  • Using the serial port with fprintf to print debug messages
  • Detecting and listing memory map
  • Querying and setting video modes with GOP
  • Printing UTF-8 strings with bitmap fonts, similar to VGA Fonts but proportional
  • Printing UTF-8 strings with vector based fonts using Scalable Screen Font library
  • Loading and blitting PNG images to the screen (without libpng using GOP->Blt)
  • Loading and parsing ELF files
  • Exit Boot Services and pass parameters to an ELF "kernel"

You can use these examples as a base for your bootloader. If this isn't enough, then I'd suggest to take a look at the BOOTBOOT loader.

Creating an EFI executable

The traditional "Hello, world" UEFI program is shown below, which we will compile using POSIX-UEFI in this tutorial.

main.c:

#include <uefi.h>

int main (int argc, char **argv)
{
  printf("Hello, world!\n");
  return 0;
}

As you can see, almost exactly the same as the standard POSIX version, except for the include. There's one notable difference, the UEFI environment uses wide characters, so you get the command line wchar_t, and all the string literals must be prefixed like this: L"", but POSIX-UEFI does this conversion transparently for you. If you comment out USE_UTF8 in uefi.h, then you'll get wchar_t everywhere, and your string will be passed as-is to the UEFI interfaces.

Compiling and Linking

Create a minimalistic Makefile for your project:

Makefile:

TARGET = helloworld.efi

include uefi/Makefile

That's all. Now you can compile your project for UEFI by running make. By default compiles using LLVM toolchain, for GNU gcc, add "USE_GCC=1" before the include. For a full list of available Makefile variables, see the documentation.

The point being, just don't care about the details. POSIX-UEFI takes care of everything, from figuring out whether you can use the host compiler or you need a cross one, or which flags has to be used for gcc and for CLang, etc. That's the point of POSIX-UEFI, to make your life easier.

As a result, you'll get "helloworld.efi" in your project's directory.

Calling Any Arbitrary UEFI Function

Unlike GNU-EFI, which has many many library functions to cover up the EFI API, POSIX-UEFI is really lightweight and only provides two additional functions, no more. It provides the same globals like GNU-EFI (BS, RT, ST). Furthermore it gives you 'exit_bs' to leave the UEFI realm, and 'getchar_ifany' for non-blocking key press reads. Everything else can be accessed just like in EDK2, no need for "uefi_call_wrapper":

ST->ConOut->OutputString(ST->ConOut, buffer);

On the other hand, POSIX-UEFI provides standard libc API (hence its name). For example, opening a file goes just like under Linux, you can use fopen. Printing with printf, fprintf; using streams like stdin, stdout, stderr; allocating using malloc / free works the same way as expected on a POSIX compliant system.

See also

Articles

External Links