2021-02-01 01:09:23 +01:00
|
|
|
POSIX-UEFI
|
|
|
|
==========
|
|
|
|
|
|
|
|
```
|
|
|
|
We hate that horrible and ugly UEFI API, we want POSIX!
|
|
|
|
```
|
|
|
|
|
2021-02-02 01:26:30 +01:00
|
|
|
This is a very small build environment that helps you to develop for UEFI under Linux (and other POSIX systems). It was
|
|
|
|
greatly inspired by [gnu-efi](https://sourceforge.net/projects/gnu-efi) (big big kudos to those guys), but it is a lot
|
|
|
|
smaller, easier to integrate (works with Clang and GNU gcc both) and easier to use because it provides a POSIX like API.
|
2021-02-01 01:09:23 +01:00
|
|
|
|
2021-02-02 01:26:30 +01:00
|
|
|
You have two options on how to integrate it into your project:
|
2021-02-01 01:09:23 +01:00
|
|
|
|
|
|
|
Distributing as Static Library
|
|
|
|
------------------------------
|
|
|
|
|
2021-02-01 01:11:09 +01:00
|
|
|
In the `uefi` directory, run
|
2021-02-01 01:09:23 +01:00
|
|
|
```sh
|
|
|
|
$ make
|
|
|
|
```
|
|
|
|
This will create `build/uefi` with all the necessary files in it. These are:
|
|
|
|
|
|
|
|
- **crt0.o**, the run-time that bootstraps POSIX-UEFI
|
|
|
|
- **link.ld**, the linker script you must use with POSIX-UEFI (same as with gnu-efi)
|
|
|
|
- **libuefi.a**, the library itself
|
|
|
|
- **uefi.h**, the all-in-one C / C++ header
|
|
|
|
|
|
|
|
You can use this and link your application with it, but you won't be able to recompile it, plus you're on your own with
|
|
|
|
the linking and converting.
|
|
|
|
|
|
|
|
Strictly speaking you'll only need **crt0.o** and **link.ld**, that will get you started and will call your application's
|
|
|
|
"main()", but to get libc functions like memcmp, strcpy, malloc or fopen, you'll have to link with **libuefi.a**.
|
|
|
|
|
|
|
|
Distributing as Source
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
This is the preferred way, as it also provides a Makefile to set up your toolchain properly.
|
|
|
|
|
2021-02-02 11:49:07 +01:00
|
|
|
1. simply copy the `uefi` directory into your source tree (or set up a git submodule). A dozen files, about 128K in total.
|
2021-02-02 01:26:30 +01:00
|
|
|
2. create an extremely simple **Makefile** like the one below
|
2021-02-01 01:09:23 +01:00
|
|
|
3. compile your code for UEFI by running `make`
|
|
|
|
|
|
|
|
```
|
|
|
|
TARGET = helloworld.efi
|
|
|
|
|
|
|
|
include uefi/Makefile
|
|
|
|
```
|
|
|
|
An example **helloworld.c** goes like this:
|
|
|
|
```c
|
|
|
|
#include <uefi.h>
|
|
|
|
|
|
|
|
int main(int argc, wchar_t **argv)
|
|
|
|
{
|
|
|
|
printf(L"Hello World!\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
```
|
2021-02-02 01:26:30 +01:00
|
|
|
By default it uses the host native's GNU gcc + ld, creates a shared object and converts that into .efi file. If `USE_LLVM`
|
|
|
|
is given, then LLVM CLang + lld used, and native PE is generated, no conversion involved.
|
2021-02-01 01:09:23 +01:00
|
|
|
|
|
|
|
### Available Makefile Options
|
|
|
|
|
|
|
|
| Variable | Description |
|
|
|
|
|------------|------------------------------------------------------------------------------------------------------|
|
|
|
|
| `TARGET` | the target application (required) |
|
|
|
|
| `SRCS` | list of source files you want to compile (defaults to \*.c \*.S) |
|
2021-02-02 01:26:30 +01:00
|
|
|
| `CFLAGS` | compiler flags you want to use (empty by default, like "-Wall -pedantic -std=c99") |
|
2021-02-01 01:09:23 +01:00
|
|
|
| `LDFLAGS` | linker flags you want to use (I don't think you'll ever need this, just in case) |
|
|
|
|
| `LIBS` | additional libraries you want to link with (like "-lm", only static .a libraries allowed) |
|
2021-02-02 01:26:30 +01:00
|
|
|
| `USE_LLVM` | set this if you want LLVM Clang + Lld instead of GNU gcc + ld |
|
2021-02-02 23:00:56 +01:00
|
|
|
| `ARCH` | the target architecture |
|
2021-02-01 01:09:23 +01:00
|
|
|
|
|
|
|
Here's a more advanced **Makefile** example:
|
|
|
|
```
|
|
|
|
ARCH = x86_64
|
|
|
|
TARGET = helloworld.efi
|
|
|
|
SRCS = $(wildcard *.c)
|
|
|
|
CFLAGS = -pedantic -Wall -Wextra -Werror --std=c11 -O2
|
|
|
|
LDFLAGS =
|
|
|
|
LIBS = -lm
|
|
|
|
|
|
|
|
USE_LLVM = 1
|
|
|
|
include uefi/Makefile
|
|
|
|
```
|
2021-02-02 23:00:56 +01:00
|
|
|
The build environment configurator was created in a way that it can handle any number of architectures, however
|
|
|
|
there's only `x86_64` crt0 implemented for now. There's an experimental `aarch64` crt0, which only compiles with
|
|
|
|
the LLVM toolchain, GNU ld has some issues with it, saying "unsupported relocation" for ImageBase.
|
2021-02-01 01:09:23 +01:00
|
|
|
|
|
|
|
Notable Differences to POSIX libc
|
|
|
|
---------------------------------
|
|
|
|
|
|
|
|
This library is nowhere near as complete as glibc or musl for example. It only provides the very basic libc functions
|
|
|
|
for you, because simplicity was one of its main goals. It is the best to say this is just wrapper around the UEFI API,
|
|
|
|
rather than a POSIX compatible libc.
|
|
|
|
|
|
|
|
All strings in the UEFI environment are stored with 16 bits wide characters. The library provides `wchar_t` type for that,
|
|
|
|
so for example your main() is NOT like `main(int argc, char **argv)`, but `main(int argc, wchar_t **argv)` instead. All
|
2021-02-02 11:49:07 +01:00
|
|
|
the other string related libc functions (like strlen() for example) use this wide character type too. For this reason, you
|
|
|
|
must specify your string literals with `L""` and characters with `L''`. Functions that supposed to handle characters in int
|
2021-02-02 23:00:56 +01:00
|
|
|
type (like `getchar`, `putchar`), do not truncate to unsigned char, rather to wchar_t.
|
2021-02-01 01:09:23 +01:00
|
|
|
|
2021-02-02 11:49:07 +01:00
|
|
|
File types in dirent are limited to directories and files only (DT_DIR, DT_REG), but for stat in addition to S_IFDIR and
|
|
|
|
S_IFREG, S_IFIFO also returned (for console streams: stdin, stdout, stderr).
|
|
|
|
|
2021-02-02 23:00:56 +01:00
|
|
|
Note that `getenv` and `setenv` aren't POSIX standard, because UEFI environment variables are binary blobs.
|
|
|
|
|
2021-02-01 01:09:23 +01:00
|
|
|
That's about it, everything else is the same.
|
|
|
|
|
|
|
|
List of Provided POSIX Functions
|
|
|
|
--------------------------------
|
|
|
|
|
2021-02-02 11:49:07 +01:00
|
|
|
### dirent.h
|
|
|
|
|
|
|
|
| Function | Description |
|
|
|
|
|---------------|----------------------------------------------------------------------------|
|
|
|
|
| opendir | as usual, but accepts wide char strings |
|
|
|
|
| readdir | as usual |
|
|
|
|
| rewinddir | as usual |
|
|
|
|
| closedir | as usual |
|
|
|
|
|
2021-02-02 23:00:56 +01:00
|
|
|
Because UEFI has no concept of device files nor of symlinks, dirent fields are limited and only DT_DIR and DT_REG supported.
|
2021-02-02 11:49:07 +01:00
|
|
|
|
2021-02-01 01:09:23 +01:00
|
|
|
### stdlib.h
|
|
|
|
|
|
|
|
| Function | Description |
|
|
|
|
|---------------|----------------------------------------------------------------------------|
|
|
|
|
| atoi | as usual, but accepts wide char strings and understands "0x" prefix |
|
|
|
|
| atol | as usual, but accepts wide char strings and understands "0x" prefix |
|
|
|
|
| strtol | as usual, but accepts wide char strings |
|
|
|
|
| malloc | as usual |
|
|
|
|
| calloc | as usual |
|
|
|
|
| realloc | as usual (needs testing) |
|
|
|
|
| free | as usual |
|
|
|
|
| abort | as usual |
|
|
|
|
| exit | as usual |
|
|
|
|
| mbtowc | as usual (UTF-8 char to wchar_t) |
|
|
|
|
| wctomb | as usual (wchar_t to UTF-8 char) |
|
|
|
|
| mbstowcs | as usual (UTF-8 string to wchar_t string) |
|
|
|
|
| wcstombs | as usual (wchar_t string to UTF-8 string) |
|
2021-02-02 11:49:07 +01:00
|
|
|
| srand | as usual |
|
2021-02-02 23:00:56 +01:00
|
|
|
| rand | as usual, but uses EFI_RNG_PROTOCOL if possible |
|
|
|
|
| getenv | pretty UEFI specific |
|
|
|
|
| setenv | pretty UEFI specific |
|
|
|
|
|
|
|
|
```c
|
|
|
|
uint8_t *getenv(wchar_t *name, uintn_t *len);
|
|
|
|
```
|
|
|
|
Query the value of environment variable `name`. On success, `len` is set, and a malloc'd buffer returned. It is
|
|
|
|
the caller's responsibility to free the buffer later. On error returns NULL.
|
|
|
|
```c
|
|
|
|
int setenv(wchar_t *name, uintn_t len, uint8_t *data);
|
|
|
|
```
|
|
|
|
Sets an environment variable by `name` with `data` of length `len`. On success returns 1, otherwise 0 on error.
|
2021-02-01 01:09:23 +01:00
|
|
|
|
|
|
|
### stdio.h
|
|
|
|
|
|
|
|
| Function | Description |
|
|
|
|
|---------------|----------------------------------------------------------------------------|
|
2021-02-02 23:00:56 +01:00
|
|
|
| fopen | as usual, but accepts wide char strings, also for mode |
|
2021-02-01 01:09:23 +01:00
|
|
|
| fclose | as usual |
|
|
|
|
| fflush | as usual |
|
|
|
|
| fread | as usual, only real files accepted (no stdin) |
|
|
|
|
| fwrite | as usual, only real files accepted (no stdout nor stderr) |
|
|
|
|
| fseek | as usual, only real files accepted (no stdin, stdout, stderr) |
|
|
|
|
| ftell | as usual, only real files accepted (no stdin, stdout, stderr) |
|
2021-02-02 11:49:07 +01:00
|
|
|
| feof | as usual, only real files accepted (no stdin, stdout, stderr) |
|
2021-02-01 01:09:23 +01:00
|
|
|
| fprintf | as usual, but accepts wide char strings, max BUFSIZ, files, stdout, stderr |
|
|
|
|
| printf | as usual, but accepts wide char strings, max BUFSIZ, stdout only |
|
|
|
|
| sprintf | as usual, but accepts wide char strings, max BUFSIZ |
|
|
|
|
| vfprintf | as usual, but accepts wide char strings, max BUFSIZ, files, stdout, stderr |
|
|
|
|
| vprintf | as usual, but accepts wide char strings, max BUFSIZ |
|
|
|
|
| vsprintf | as usual, but accepts wide char strings, max BUFSIZ |
|
|
|
|
| snprintf | as usual, but accepts wide char strings |
|
|
|
|
| vsnprintf | as usual, but accepts wide char strings |
|
|
|
|
| getchar | as usual, waits for a key, blocking, stdin only (no redirects) |
|
|
|
|
| getchar_ifany | non-blocking, returns 0 if there was no key press, UNICODE otherwise |
|
|
|
|
| putchar | as usual, stdout only (no redirects) |
|
2021-02-04 04:53:06 +01:00
|
|
|
| exit_bs | leave this entire UEFI bullshit behind (exit Boot Services) |
|
|
|
|
|
|
|
|
```c
|
|
|
|
int exit_bs()
|
|
|
|
```
|
|
|
|
Exit Boot Services. Returns 0 on success.
|
2021-02-01 01:09:23 +01:00
|
|
|
|
2021-02-02 23:00:56 +01:00
|
|
|
File open modes: `L"r"` read, `L"w"` write, `L"a"` append. Because of UEFI peculiarities, `L"wd"` creates directory.
|
2021-02-02 11:49:07 +01:00
|
|
|
|
2021-02-01 01:09:23 +01:00
|
|
|
String formating is limited; only supports padding via number prefixes, `%d`, `%x`, `%X`, `%c`, `%s`, `%q` and
|
|
|
|
`%p`. Because it operates on wchar_t, it also supports the non-standard `%C` (printing an UTF-8 character, needs
|
|
|
|
char\*), `%S` (printing an UTF-8 string), `%Q` (printing an escaped UTF-8 string). These functions don't allocate
|
2021-02-02 01:26:30 +01:00
|
|
|
memory, but in return the total length of the output string cannot be longer than BUFSIZ (8k if you haven't
|
|
|
|
defined otherwise), except for the variants which have a maxlen argument. For convenience, `%D` requires
|
|
|
|
`efi_physical_address_t` as argument, and it dumps memory, 16 bytes or one line at once. With the padding
|
2021-02-02 11:49:07 +01:00
|
|
|
modifier you can dump more lines, for example `%5D` gives you 5 lines (80 dumped bytes).
|
2021-02-01 01:09:23 +01:00
|
|
|
|
2021-02-04 04:53:06 +01:00
|
|
|
Special "device files" you can open:
|
|
|
|
|
|
|
|
| Name | Description |
|
|
|
|
|---------------------|----------------------------------------------------|
|
|
|
|
| `/dev/stdin` | returns ST->ConIn |
|
|
|
|
| `/dev/stdout` | returns ST->ConOut, fprintf |
|
|
|
|
| `/dev/stderr` | returns ST->StdErr, fprintf |
|
|
|
|
| `/dev/serial(baud)` | returns Serial IO protocol, fread, fwrite, fprintf |
|
|
|
|
| `/dev/disk(n)` | returns Block IO protocol, fread, fwrite |
|
|
|
|
|
|
|
|
With disk devices, `fread` and `fwrite` arguments look like this: fread(ptr, buffer size, lba number, stream).
|
|
|
|
|
2021-02-01 01:09:23 +01:00
|
|
|
### string.h
|
|
|
|
|
|
|
|
| Function | Description |
|
|
|
|
|---------------|----------------------------------------------------------------------------|
|
|
|
|
| memcpy | as usual, works on bytes |
|
|
|
|
| memmove | as usual, works on bytes |
|
|
|
|
| memset | as usual, works on bytes |
|
|
|
|
| memcmp | as usual, works on bytes |
|
|
|
|
| memchr | as usual, works on bytes |
|
|
|
|
| memrchr | as usual, works on bytes |
|
|
|
|
| memmem | as usual, works on bytes |
|
|
|
|
| memrmem | as usual, works on bytes |
|
|
|
|
| strcpy | works on wide char strings |
|
|
|
|
| strncpy | works on wide char strings |
|
|
|
|
| strcat | works on wide char strings |
|
|
|
|
| strncat | works on wide char strings |
|
|
|
|
| strcmp | works on wide char strings |
|
|
|
|
| strncmp | works on wide char strings |
|
|
|
|
| strdup | works on wide char strings |
|
|
|
|
| strchr | works on wide char strings |
|
|
|
|
| strrchr | works on wide char strings |
|
|
|
|
| strstr | works on wide char strings |
|
|
|
|
| strtok | works on wide char strings |
|
|
|
|
| strtok_r | works on wide char strings |
|
|
|
|
| strlen | works on wide char strings |
|
|
|
|
|
2021-02-02 11:49:07 +01:00
|
|
|
### sys/stat.h
|
|
|
|
|
|
|
|
| Function | Description |
|
|
|
|
|---------------|----------------------------------------------------------------------------|
|
|
|
|
| stat | as usual, but accepts wide char strings |
|
|
|
|
| fstat | UEFI doesn't have fd, so it uses FILE\* |
|
|
|
|
| mkdir | as usual, but accepts wide char strings, and mode unused |
|
|
|
|
|
|
|
|
Because UEFI has no concept of device major and minor number nor of inodes, struct stat's fields are limited.
|
|
|
|
|
2021-02-01 01:09:23 +01:00
|
|
|
### time.h
|
|
|
|
|
|
|
|
| Function | Description |
|
|
|
|
|---------------|----------------------------------------------------------------------------|
|
2021-02-02 01:26:30 +01:00
|
|
|
| localtime | argument unused, always returns current time in struct tm |
|
2021-02-02 11:49:07 +01:00
|
|
|
| mktime | as usual |
|
|
|
|
| time | as usual |
|
2021-02-01 01:09:23 +01:00
|
|
|
|
|
|
|
### unistd.h
|
|
|
|
|
|
|
|
| Function | Description |
|
|
|
|
|---------------|----------------------------------------------------------------------------|
|
|
|
|
| usleep | the usual |
|
|
|
|
| sleep | the usual |
|
2021-02-02 11:49:07 +01:00
|
|
|
| unlink | as usual, but accepts wide char strings |
|
|
|
|
| rmdir | as usual, but accepts wide char strings |
|
|
|
|
|
|
|
|
Accessing UEFI Services
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
It is very likely that you want to call UEFI specific functions directly. For that, POSIX-UEFI specifies some globals
|
|
|
|
in `uefi.h`:
|
|
|
|
|
|
|
|
| Global Variable | Description |
|
|
|
|
|-----------------|----------------------------------------------------------|
|
|
|
|
| `*BS` | *efi_boot_services_t*, pointer to the Boot Time Services |
|
|
|
|
| `*RT` | *efi_runtime_t*, pointer to the Runtime Services |
|
|
|
|
| `*ST` | *efi_system_table_t*, pointer to the UEFI System Table |
|
|
|
|
| `IM` | *efi_handle_t* of your Loaded Image |
|
|
|
|
|
|
|
|
The EFI structures, enums, typedefs and defines are all converted to ANSI C standard POSIX style, for example
|
|
|
|
BOOLEAN -> boolean_t, UINTN -> uintn_t, EFI_MEMORY_DESCRIPTOR -> efi_memory_descriptor_t, and of course
|
|
|
|
EFI_BOOT_SERVICES -> efi_boot_services_t.
|
|
|
|
|
|
|
|
Calling UEFI functions is as simple as with EDK II, just do the call, no need for "uefi_call_wrapper":
|
|
|
|
```c
|
|
|
|
ST->ConOut->OutputString(ST->ConOut, L"Hello World!\r\n");
|
|
|
|
```
|
|
|
|
|
|
|
|
There are two additional, non-POSIX calls in the library. One is `exit_bs()` to exit Boot Services, and the other is
|
2021-02-02 23:00:56 +01:00
|
|
|
a non-blocking version `getchar_ifany()`.
|
2021-02-02 11:49:07 +01:00
|
|
|
|
|
|
|
Unlike gnu-efi, POSIX-UEFI does not pollute your application's namespace with unused GUID variables. It only provides
|
|
|
|
header definitions, so you must create each GUID instance if and when you need them.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
```c
|
|
|
|
efi_guid_t gopGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
|
|
|
|
efi_gop_t *gop = NULL;
|
|
|
|
|
|
|
|
status = BS->LocateProtocol(&gopGuid, NULL, (void**)&gop);
|
|
|
|
```
|
|
|
|
|
|
|
|
Also unlike gnu-efi, POSIX-UEFI does not provide standard EFI headers. It expects that you have installed those under
|
|
|
|
/usr/include/efi from EDK II or gnu-efi, and POSIX-UEFI makes it possible to use those system wide headers without
|
|
|
|
naming conflicts. POSIX-UEFI itself ships the very minimum set of typedefs and structs (with POSIX-ized names).
|
|
|
|
```c
|
|
|
|
#include <efi.h>
|
|
|
|
#include <uefi.h> /* this will work as expected! Both POSIX-UEFI and EDK II / gnu-efi typedefs available */
|
|
|
|
```
|
2021-02-02 23:00:56 +01:00
|
|
|
The advantage of this is that you can use the simplicity of the POSIX-UEFI library and build environment, while getting
|
|
|
|
access to the most up-to-date protocol and interface definitions at the same time.
|
|
|
|
|
|
|
|
License
|
|
|
|
-------
|
|
|
|
|
|
|
|
POSIX_UEFI is licensed under the terms of the MIT license.
|
|
|
|
|
|
|
|
Cheers,
|
|
|
|
|
|
|
|
bzt
|