Module r_efi::base

source ·
Expand description

UEFI Base Environment

This module defines the base environment for UEFI development. It provides types and macros as declared in the UEFI specification, as well as de-facto standard additions provided by the reference implementation by Intel.

§Target Configuration

Wherever possible, native rust types are used to represent their UEFI counter-parts. However, this means the ABI depends on the implementation of said rust types. Hence, native rust types are only used where rust supports a stable ABI of said types, and their ABI matches the ABI defined by the UEFI specification.

Nevertheless, even if the ABI of a specific type is marked stable, this does not imply that it is the same across architectures. For instance, rust’s u64 type has the same binary representation as the UINT64 type in UEFI. But this does not imply that it has the same binary representation on x86_64 and on ppc64be. As a result of this, the compilation of this module is tied to the target-configuration you passed to the rust compiler. Wherever possible and reasonable, any architecture differences are abstracted, though. This means that in most cases you can use this module even though your target-configuration might not match the native UEFI target-configuration.

The recommend way to compile your code, is to use the native target-configuration for UEFI. These configurations are not necessarily included in the upstream rust compiler. Hence, you might have to craft one yourself. For all systems that we can test on, we make sure to push the target configuration into upstream rust-lang.

However, there are situations where you want to access UEFI data from a non-native host. For instance, a UEFI boot loader might store data in boot variables, formatted according to types declared in the UEFI specification. An OS booted thereafter might want to access these variables, but it might be compiled with a different target-configuration than the UEFI environment that it was booted from. A similar situation occurs when you call UEFI runtime functions from your OS. In all those cases, you should very likely be able to use this module to interact with UEFI as well. This is, because most bits of the target-configuration of UEFI and your OS very likely match. In fact, to figure out whether this is safe, you need to make sure that the rust ABI would match in both target-configurations. If it is, all other details are handled within this module just fine.

In case of doubt, contact us!

§Core Primitives

Several of the UEFI primitives are represented by native Rust. These have no type aliases or other definitions here, but you are recommended to use native rust directly. These include:

  • NULL, void *: Void pointers have a native rust implementation in c_void. NULL is represented through null and is_null() for all pointer types.

  • uint8_t..uint64_t, int8_t..int64_t: Fixed-size integers are represented by their native rust equivalents (u8..u64, i8..i64).

  • UINTN, INTN: Native-sized (or instruction-width sized) integers are represented by their native rust equivalents (usize, isize).

§UEFI Details

The UEFI Specification describes its target environments in detail. Each supported architecture has a separate section with details on calling conventions, CPU setup, and more. You are highly recommended to conduct the UEFI Specification for details on the programming environment. Following a summary of key parts relevant to rust developers:

  • Similar to rust, integers are either fixed-size, or native size. This maps nicely to the native rust types. The common long, int, short types known from ISO-C are not used. Whenever you refer to memory (either pointing to it, or remember the size of a memory block), the native size integers should be your tool of choice.

  • Even though the CPU might run in any endianness, all stored data is little-endian. That means, if you encounter integers split into byte-arrays (e.g., CEfiDevicePathProtocol.length), you must assume it is little-endian encoded. But if you encounter native integers, you must assume they are encoded in native endianness. For now the UEFI specification only defines little-endian architectures, hence this did not pop up as actual issue. Future extensions might change this, though.

  • The Microsoft calling-convention is used. That is, all external calls to UEFI functions follow a calling convention that is very similar to that used on Microsoft Windows. All such ABI functions must be marked with the right calling-convention. The UEFI Specification defines some additional common rules for all its APIs, though. You will most likely not see any of these mentioned in the individual API documentions. So here is a short reminder:

    • Pointers must reference physical-memory locations (no I/O mappings, no virtual addresses, etc.). Once ExitBootServices() was called, and the virtual address mapping was set, you must provide virtual-memory locations instead.
    • Pointers must be correctly aligned.
    • NULL is disallowed, unless explicitly mentioned otherwise.
    • Data referenced by pointers is undefined on error-return from a function.
    • You must not pass data larger than native-size (sizeof(CEfiUSize)) on the stack. You must pass them by reference.
  • Stack size is at least 128KiB and 16-byte aligned. All stack space might be marked non-executable! Once ExitBootServices() was called, you must guarantee at least 4KiB of stack space, 16-byte aligned for all runtime services you call. Details might differ depending on architectures. But the numbers here should serve as ball-park figures.

Structs§

Type Aliases§

Unions§