r_efi_alloc/
raw.rs

1//! Raw Allocator
2//!
3//! This module exposes the raw handlers behind the UEFI allocator. The
4//! allocator traits of the standard library are marked as unstable, hence this
5//! module provides stable access to the same functionality, if required.
6//!
7//! Use of the raw allocator is only recommended if the other exposes APIs are
8//! not an option.
9
10use r_efi::efi;
11
12// UEFI guarantees 8-byte alignments through `AllocatePool()`. Any request
13// higher than this alignment needs to take special precautions to align the
14// returned pointer, and revert that step when freeing the memory block again.
15const POOL_ALIGNMENT: usize = 8usize;
16
17// Alignment Marker
18//
19// Since UEFI has no functions to allocate blocks of arbitrary alignment, we
20// have to work around this. We extend the allocation size by the required
21// alignment and then offset the pointer before returning it. This will
22// properly align the pointer to the given request.
23//
24// However, when freeing memory again, we have to somehow get back the original
25// pointer. Therefore, we store the original address directly in front of the
26// memory block that we just aligned. When freeing memory, we simply retrieve
27// this marker and free the original address.
28#[repr(C)]
29struct Marker(*mut u8);
30
31fn align_request(size: usize, align: usize) -> usize {
32    // If the alignment request is within UEFI guarantees, there is no need to
33    // adjust the size request. In all other cases, we might have to align the
34    // allocated memory block. Hence, we increment the request size by the
35    // alignment size. Strictly speaking, we only need `align - POOL_ALIGNMENT`
36    // as additional space, since the pool alignment is always guaranteed by
37    // UEFI. However, by adding the full alignment we are guaranteed
38    // `POOL_ALIGNMENT` extra space. This extra space is used to store a marker
39    // so we can retrieve the original pointer when freeing the memory space.
40    if align > POOL_ALIGNMENT {
41        size + align
42    } else {
43        size
44    }
45}
46
47unsafe fn align_block(ptr: *mut u8, align: usize) -> *mut u8 {
48    // This function takes a pointer returned by the pool-allocator, and aligns
49    // it to the requested alignment. If this alignment is smaller than the
50    // guaranteed pool alignment, there is nothing to be done. If it is bigger,
51    // we will have to offset the pointer. We rely on the caller using
52    // `align_request()` to increase the allocation size beforehand. We then
53    // store the original address as `Marker` in front of the aligned pointer,
54    // so `unalign_block()` can retrieve it again.
55    if align > POOL_ALIGNMENT {
56        // In `align_request()` we guarantee the allocation size includes an
57        // additional `align` bytes. Since the pool allocation already
58        // guaranteed an alignment of `POOL_ALIGNMENT`, we know that
59        // `offset >= POOL_ALIGNMENT` here. We then verify that
60        // `POOL_ALIGNMENT` serves the needs of our `Marker` object. Note that
61        // all but the first assertion are constant expressions, so the
62        // compiler will optimize them away.
63        let offset = align - (ptr as usize & (align - 1));
64        assert!(offset >= POOL_ALIGNMENT);
65        assert!(POOL_ALIGNMENT >= core::mem::size_of::<Marker>());
66        assert!(POOL_ALIGNMENT >= core::mem::align_of::<Marker>());
67
68        // We calculated the alignment-offset, so adjust the pointer and store
69        // the original address directly in front. This will allow
70        // `unalign_block()` to retrieve the original address, so it can free
71        // the entire memory block.
72        let aligned = ptr.add(offset);
73        core::ptr::write((aligned as *mut Marker).offset(-1), Marker(ptr));
74        aligned
75    } else {
76        ptr
77    }
78}
79
80unsafe fn unalign_block(ptr: *mut u8, align: usize) -> *mut u8 {
81    // This undoes what `align_block()` did. That is, we retrieve the original
82    // address that was stored directly in front of the aligned block, and
83    // return it to the caller. Note that this is only the case if the
84    // alignment exceeded the guaranteed alignment of the allocator.
85    if align > POOL_ALIGNMENT {
86        core::ptr::read((ptr as *mut Marker).offset(-1)).0
87    } else {
88        ptr
89    }
90}
91
92/// Allocate Memory from UEFI Boot-Services
93///
94/// Use the UEFI `allocate_pool` boot-services to request a block of memory
95/// satisfying the given memory layout. The `memory_type` parameter specifies
96/// which UEFI allocator to use.
97///
98/// This returns a null-pointer if the allocator could not serve the request
99/// (which on UEFI implies out-of-memory). Otherwise, a non-null pointer to
100/// the aligned block is returned.
101///
102/// Safety
103/// ------
104///
105/// To ensure safety of this interface, the caller must guarantee:
106///
107///  * The allocation size must not be 0. The function will panic otherwise.
108///
109///  * It must be safe for this function to call `allocate_pool` of the
110///    boot-services provided via the system-table. It is the responsibility of
111///    the caller to retain boot-services until the returned allocation is
112///    released via `dealloc()`, or to account for it otherwise.
113///
114///  * The returned pointer is not necessarily the same pointer as returned
115///    by `allocate_pool` of the boot-services. A caller must not assume this
116///    when forwarding the pointer to other allocation services.
117pub unsafe fn alloc(
118    system_table: *mut efi::SystemTable,
119    layout: core::alloc::Layout,
120    memory_type: efi::MemoryType,
121) -> *mut u8 {
122    // `Layout` guarantees the size+align combination does not overflow.
123    let align = layout.align();
124    let size = layout.size();
125
126    // Verify our increased requirements are met.
127    assert!(size > 0);
128
129    // We need extra allocation space to guarantee large alignment requests. If
130    // `size+align` overflows, there will be insufficient address-space for the
131    // request, so make it fail early.
132    if size.checked_add(align).is_none() {
133        return core::ptr::null_mut();
134    }
135
136    // We forward the allocation request to `AllocatePool()`. This takes the
137    // memory-type and size as argument, and places a pointer to the allocation
138    // in an output argument. Note that UEFI guarantees 8-byte alignment (i.e.,
139    // `POOL_ALIGNMENT`). To support higher alignments, see the
140    // `align_request() / align_block() / unalign_block()` helpers.
141    let mut ptr: *mut core::ffi::c_void = core::ptr::null_mut();
142    let size_allocated = align_request(size, align);
143    let r = unsafe {
144        ((*(*system_table).boot_services).allocate_pool)(
145            memory_type,
146            size_allocated,
147            &mut ptr,
148        )
149    };
150
151    // The only real error-scenario is OOM ("out-of-memory"). UEFI does not
152    // clearly specify what a return value of NULL+success means (but indicates
153    // in a lot of cases that NULL is never a valid pointer). Furthermore,
154    // since the 0-page is usually unmapped and not available for
155    // EFI_CONVENTIONAL_MEMORY, a NULL pointer cannot be a valid return
156    // pointer. Therefore, we treat both a function failure as well as a NULL
157    // pointer the same.
158    // No known UEFI implementation returns `NULL`, hence this is mostly a
159    // safety net in case any unknown implementation fails to adhere.
160    if r.is_error() || ptr.is_null() {
161        core::ptr::null_mut()
162    } else {
163        unsafe { align_block(ptr as *mut u8, align) }
164    }
165}
166
167/// Deallocate Memory from UEFI Boot-Services
168///
169/// Use the UEFI `free_pool` boot-services to release a block of memory
170/// previously allocated through `alloc()`.
171///
172/// Safety
173/// ------
174///
175/// The memory block must be the same as previously returned by `alloc()`.
176/// Furthermore, this function must be able to call the UEFI boot-servies
177/// through the specified system table, and this must match the same
178/// boot-services the memory block was allocated through.
179///
180/// The passed layout must match the layout used to allocate the memory block.
181pub unsafe fn dealloc(
182    system_table: *mut efi::SystemTable,
183    ptr: *mut u8,
184    layout: core::alloc::Layout,
185) {
186    // UEFI never allows null-pointers for allocations, hence such a pointer
187    // cannot have been retrieved through `alloc()` previously.
188    assert!(!ptr.is_null());
189
190    // Un-align the pointer to get access to the actual start of the block.
191    let original = unalign_block(
192        ptr,
193        layout.align(),
194    ) as *mut core::ffi::c_void;
195
196    // Release the memory block via the boot-services.
197    let r = ((*(*system_table).boot_services).free_pool)(original);
198
199    // The spec allows returning errors from `FreePool()`. However, it
200    // must serve any valid requests. Only `INVALID_PARAMETER` is
201    // listed as possible error. Hence, there is no point in forwarding
202    // the return value. We still assert on it to improve diagnostics
203    // in early-boot situations. This should be a negligible
204    // performance penalty.
205    assert!(!r.is_error());
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    // Test the `align_request()` helper and verify that it correctly
213    // calculates the supported alignment requests.
214    #[test]
215    fn align() {
216        let ptrsize = std::mem::size_of::<*mut ()>();
217
218        // UEFI ABI specifies that allocation alignment minimum is always 8. So
219        // this can be statically verified.
220        assert_eq!(POOL_ALIGNMENT, 8);
221
222        // Loop over allocation-request sizes from 0-256 and alignments from
223        // 1-128, and verify that in case of overalignment there is at least
224        // space for one additional pointer to store in the allocation.
225        for i in 0..256 {
226            for j in &[1, 2, 4, 8, 16, 32, 64, 128] {
227                if *j <= 8 {
228                    assert_eq!(align_request(i, *j), i);
229                } else {
230                    assert!(align_request(i, *j) > i + ptrsize);
231                }
232            }
233        }
234    }
235}