//! Raw Allocator //! //! This module exposes the raw handlers behind the UEFI allocator. The //! allocator traits of the standard library are marked as unstable, hence this //! module provides stable access to the same functionality, if required. //! //! Use of the raw allocator is only recommended if the other exposes APIs are //! not an option. use r_efi::efi; // UEFI guarantees 8-byte alignments through `AllocatePool()`. Any request // higher than this alignment needs to take special precautions to align the // returned pointer, and revert that step when freeing the memory block again. const POOL_ALIGNMENT: usize = 8usize; // Alignment Marker // // Since UEFI has no functions to allocate blocks of arbitrary alignment, we // have to work around this. We extend the allocation size by the required // alignment and then offset the pointer before returning it. This will // properly align the pointer to the given request. // // However, when freeing memory again, we have to somehow get back the original // pointer. Therefore, we store the original address directly in front of the // memory block that we just aligned. When freeing memory, we simply retrieve // this marker and free the original address. #[repr(C)] struct Marker(*mut u8); fn align_request(size: usize, align: usize) -> usize { // If the alignment request is within UEFI guarantees, there is no need to // adjust the size request. In all other cases, we might have to align the // allocated memory block. Hence, we increment the request size by the // alignment size. Strictly speaking, we only need `align - POOL_ALIGNMENT` // as additional space, since the pool alignment is always guaranteed by // UEFI. However, by adding the full alignment we are guaranteed // `POOL_ALIGNMENT` extra space. This extra space is used to store a marker // so we can retrieve the original pointer when freeing the memory space. if align > POOL_ALIGNMENT { size + align } else { size } } unsafe fn align_block(ptr: *mut u8, align: usize) -> *mut u8 { // This function takes a pointer returned by the pool-allocator, and aligns // it to the requested alignment. If this alignment is smaller than the // guaranteed pool alignment, there is nothing to be done. If it is bigger, // we will have to offset the pointer. We rely on the caller using // `align_request()` to increase the allocation size beforehand. We then // store the original address as `Marker` in front of the aligned pointer, // so `unalign_block()` can retrieve it again. if align > POOL_ALIGNMENT { // In `align_request()` we guarantee the allocation size includes an // additional `align` bytes. Since the pool allocation already // guaranteed an alignment of `POOL_ALIGNMENT`, we know that // `offset >= POOL_ALIGNMENT` here. We then verify that // `POOL_ALIGNMENT` serves the needs of our `Marker` object. Note that // all but the first assertion are constant expressions, so the // compiler will optimize them away. let offset = align - (ptr as usize & (align - 1)); assert!(offset >= POOL_ALIGNMENT); assert!(POOL_ALIGNMENT >= core::mem::size_of::()); assert!(POOL_ALIGNMENT >= core::mem::align_of::()); // We calculated the alignment-offset, so adjust the pointer and store // the original address directly in front. This will allow // `unalign_block()` to retrieve the original address, so it can free // the entire memory block. let aligned = ptr.add(offset); core::ptr::write((aligned as *mut Marker).offset(-1), Marker(ptr)); aligned } else { ptr } } unsafe fn unalign_block(ptr: *mut u8, align: usize) -> *mut u8 { // This undoes what `align_block()` did. That is, we retrieve the original // address that was stored directly in front of the aligned block, and // return it to the caller. Note that this is only the case if the // alignment exceeded the guaranteed alignment of the allocator. if align > POOL_ALIGNMENT { core::ptr::read((ptr as *mut Marker).offset(-1)).0 } else { ptr } } /// Allocate Memory from UEFI Boot-Services /// /// Use the UEFI `allocate_pool` boot-services to request a block of memory /// satisfying the given memory layout. The `memory_type` parameter specifies /// which UEFI allocator to use. /// /// This returns a null-pointer if the allocator could not serve the request /// (which on UEFI implies out-of-memory). Otherwise, a non-null pointer to /// the aligned block is returned. /// /// Safety /// ------ /// /// To ensure safety of this interface, the caller must guarantee: /// /// * The allocation size must not be 0. The function will panic otherwise. /// /// * It must be safe for this function to call `allocate_pool` of the /// boot-services provided via the system-table. It is the responsibility of /// the caller to retain boot-services until the returned allocation is /// released via `dealloc()`, or to account for it otherwise. /// /// * The returned pointer is not necessarily the same pointer as returned /// by `allocate_pool` of the boot-services. A caller must not assume this /// when forwarding the pointer to other allocation services. pub unsafe fn alloc( system_table: *mut efi::SystemTable, layout: core::alloc::Layout, memory_type: efi::MemoryType, ) -> *mut u8 { // `Layout` guarantees the size+align combination does not overflow. let align = layout.align(); let size = layout.size(); // Verify our increased requirements are met. assert!(size > 0); // We need extra allocation space to guarantee large alignment requests. If // `size+align` overflows, there will be insufficient address-space for the // request, so make it fail early. if size.checked_add(align).is_none() { return core::ptr::null_mut(); } // We forward the allocation request to `AllocatePool()`. This takes the // memory-type and size as argument, and places a pointer to the allocation // in an output argument. Note that UEFI guarantees 8-byte alignment (i.e., // `POOL_ALIGNMENT`). To support higher alignments, see the // `align_request() / align_block() / unalign_block()` helpers. let mut ptr: *mut core::ffi::c_void = core::ptr::null_mut(); let size_allocated = align_request(size, align); let r = unsafe { ((*(*system_table).boot_services).allocate_pool)( memory_type, size_allocated, &mut ptr, ) }; // The only real error-scenario is OOM ("out-of-memory"). UEFI does not // clearly specify what a return value of NULL+success means (but indicates // in a lot of cases that NULL is never a valid pointer). Furthermore, // since the 0-page is usually unmapped and not available for // EFI_CONVENTIONAL_MEMORY, a NULL pointer cannot be a valid return // pointer. Therefore, we treat both a function failure as well as a NULL // pointer the same. // No known UEFI implementation returns `NULL`, hence this is mostly a // safety net in case any unknown implementation fails to adhere. if r.is_error() || ptr.is_null() { core::ptr::null_mut() } else { unsafe { align_block(ptr as *mut u8, align) } } } /// Deallocate Memory from UEFI Boot-Services /// /// Use the UEFI `free_pool` boot-services to release a block of memory /// previously allocated through `alloc()`. /// /// Safety /// ------ /// /// The memory block must be the same as previously returned by `alloc()`. /// Furthermore, this function must be able to call the UEFI boot-servies /// through the specified system table, and this must match the same /// boot-services the memory block was allocated through. /// /// The passed layout must match the layout used to allocate the memory block. pub unsafe fn dealloc( system_table: *mut efi::SystemTable, ptr: *mut u8, layout: core::alloc::Layout, ) { // UEFI never allows null-pointers for allocations, hence such a pointer // cannot have been retrieved through `alloc()` previously. assert!(!ptr.is_null()); // Un-align the pointer to get access to the actual start of the block. let original = unalign_block( ptr, layout.align(), ) as *mut core::ffi::c_void; // Release the memory block via the boot-services. let r = ((*(*system_table).boot_services).free_pool)(original); // The spec allows returning errors from `FreePool()`. However, it // must serve any valid requests. Only `INVALID_PARAMETER` is // listed as possible error. Hence, there is no point in forwarding // the return value. We still assert on it to improve diagnostics // in early-boot situations. This should be a negligible // performance penalty. assert!(!r.is_error()); } #[cfg(test)] mod tests { use super::*; // Test the `align_request()` helper and verify that it correctly // calculates the supported alignment requests. #[test] fn align() { let ptrsize = std::mem::size_of::<*mut ()>(); // UEFI ABI specifies that allocation alignment minimum is always 8. So // this can be statically verified. assert_eq!(POOL_ALIGNMENT, 8); // Loop over allocation-request sizes from 0-256 and alignments from // 1-128, and verify that in case of overalignment there is at least // space for one additional pointer to store in the allocation. for i in 0..256 { for j in &[1, 2, 4, 8, 16, 32, 64, 128] { if *j <= 8 { assert_eq!(align_request(i, *j), i); } else { assert!(align_request(i, *j) > i + ptrsize); } } } } }