Writing an Allocator for UEFI
Hello everyone, we will discuss how to write the Allocator for UEFI in this post. More specifically, the Allocator is for the DEX phase in UEFI. We will be using uefi-spec-rs, which is my wrapper around r-efi for use in the std.
UEFI Memory Memory Management Services
First, let us look at the Memory Management Services we will be using in UEFI.
AllocatePool
Prototype
typedef
;
Description
This function allocates a memory region of Size
bytes from the memory of type PoolType
and returns the address of the allocated memory in the location referenced by Buffer. This function allocates pages from EfiConventionalMemory as needed to grow the requested pool type.
Note: All allocations are eight-byte aligned.
Status Codes Returned
- EFI_SUCCESS : The requested number of bytes was allocated.
- EFI_OUT_OF_RESOURCES : The pool requested could not be allocated.
- EFI_INVALID_PARAMETER : PoolType is in the range EfiMaxMemoryType..0x6FFFFFFF.
- EFI_INVALID_PARAMETER : PoolType is EfiPersistentMemory.
- EFI_INVALID_PARAMETER : Buffer is NULL.
FreePool
Prototype
typedef
;
Description
This function returns the memory specified by Buffer to the system. In return, the memory’s type is EfiConventionalMemory. The freed Buffer must have been allocated by AllocatePool()
.
Status Code Returned
- EFI_SUCCESS : The memory was returned to the system.
- EFI_INVALID_PARAMETER : Buffer was invalid.
Writing a basic allocator
First, we will write an allocator which works for alignment <= 8 bytes. To make a global allocator, we need to implement the’ GlobalAlloc’ trait. In the case of std, we would implement this trait on the System
allocator. However, since I would like to test things out, I will be implementing the allocator as an example in the uefi-spec-rs crate.
static mut GLOBAL_SYSTEM_TABLE: = new;
static GLOBAL_ALLOCATOR: Allocator = Allocator;
;
unsafe
Here we create an empty struct (Allocator
) as a placeholder for System
and implement GlobalAlloc on it. The GLOBAL_SYSTEM_TABLE
stores an AtomicPtr
to the SystemTable
. I will also be adding a way to access the SystemTable in std::os::uefi
. However, I will not be exposing the static mutable variable there.
Implement alloc
Firstly, we need to be aware of a few things:
- The
layout.size()
supplied inGlobalAlloc
can be 0. It is up to the implementation to check for that case. - According to docs, we should return the
NULL
pointer in case of errors. - The
alloc
function should not unwind.
Keeping all that in mind, here is the basic implementation of alloc:
unsafe
This is pretty simple. We fail for alignment > 0. we also fail if the ptr
is null after allocation or if the error status is returned.
Implement dealloc
We need to check that size is non-zero. The only error that FreePool()
returns is in the case of invalid ptr
, so ideally, this should not happen in System
allocator. However, I am adding the assert for now to improve debugging. That assert will probably be removed in production.
unsafe
Testing the new allocator
We just add a efi_main
function that initializes the GLOBAL_SYSTEM_TABLE
and we should be good to use the alloc
types:
pub extern "C"
This example works as expected.
Getting allocations > 8-byte align to work
While the previous allocator works for many cases, there is a clever way to work around the 8-byte alignment limit. This is used in the windows allocator as well as r-efi-alloc.
Implement alloc
The new alloc
function looks like this:
unsafe
The magic happens in the align_size
and the align_ptr
functions.
In align_size
function, we just allocate extra padding in case align
is greater than 8. This padding will later be used in the align_ptr
function.
In the align_ptr
function we store the original address as Header
in front of the aligned_ptr.
;
unsafe
Note: the align_ptr
function assumes that the allocation size has been increased beforehand by align_size
.
Implement dealloc
The new dealloc
looks like this:
unsafe
The unalign_ptr
function basically undoes what the align_ptr
function did and gives back the original pointer.
unsafe
The same example as before should work even now without any change.
Conclusion
With this, we now have a global allocator. I will soon be integrating it into the Rust std. In the next post, we will discuss implementing stdin
for UEFI.
Consider supporting me if you like my work.