[−][src]Module r_efi::base
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 inc_void
.NULL
is represented throughnull
andis_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
Boolean | Boolean Type |
Guid | Globally Unique Identifiers |
Status | Status Codes |
Type Definitions
Char8 | Single-byte Character Type |
Char16 | Dual-byte Character Type |
Event | Event Objects |
Handle | Object Handles |
ImageEntryPoint | Application Entry Point |
Lba | Logical Block Addresses |
PhysicalAddress | Physical Memory Address |
Tpl | Thread Priority Levels |
VirtualAddress | Virtual Memory Address |