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}