Writing an Allocator for UEFIAyush Singh June 23, 2022 #rust #tianocore #gsoc22 #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.
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.
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
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.
Firstly, we need to be aware of a few things:
GlobalAlloccan be 0. It is up to the implementation to check for that case.
- According to docs, we should return the
NULLpointer in case of errors.
allocfunction should not unwind.
Keeping all that in mind, here is the basic implementation of alloc:
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.
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.
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
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.
alloc function looks like this:
The magic happens in the
align_size and the
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 we store the original address as
Header in front of the aligned_ptr.
align_ptr function assumes that the allocation size has been increased beforehand by
dealloc looks like this:
unalign_ptr function basically undoes what the
align_ptr function did and gives back the original pointer.
The same example as before should work even now without any change.
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.
Back to top