11// SPDX-License-Identifier: MIT OR Apache-2.0
22
3- //! This module implements Rust's global allocator interface using UEFI's memory allocation functions .
3+ //! This module exports [`Allocator`] .
44//!
5- //! If the `global_allocator` feature is enabled, the [`Allocator`] will be used
6- //! as the global Rust allocator .
5+ //! The allocator can be used as global Rust allocator using the
6+ //! `global_allocator` crate feature. See [`helpers`] for more info .
77//!
8- //! This allocator can only be used while boot services are active. If boot
9- //! services are not active, `alloc` will return a null pointer, and `dealloc`
10- //! will panic.
8+ //! [`helpers`]: uefi::helpers
119
12- use crate :: boot;
10+ use crate :: boot:: { self , AllocateType } ;
1311use crate :: mem:: memory_map:: MemoryType ;
1412use crate :: proto:: loaded_image:: LoadedImage ;
1513use core:: alloc:: { GlobalAlloc , Layout } ;
1614use core:: ptr:: { self , NonNull } ;
1715use core:: sync:: atomic:: { AtomicU32 , Ordering } ;
16+ use uefi_raw:: table:: boot:: PAGE_SIZE ;
1817
1918/// Get the memory type to use for allocation.
2019///
@@ -42,15 +41,69 @@ fn get_memory_type() -> MemoryType {
4241 }
4342}
4443
45- /// Allocator which uses the UEFI pool allocation functions.
44+ /// Helper to get a custom alignment out of an allocation with an alignment of
45+ /// eight (UEFI default alignment). This works by allocating extra space and
46+ /// storing a pointer to the actual allocation right above the allocation
47+ /// handed out via the public API.
48+ fn alloc_pool_aligned ( memory_type : MemoryType , size : usize , align : usize ) -> * mut u8 {
49+ let full_alloc_ptr = boot:: allocate_pool ( memory_type, size + align) ;
50+ let full_alloc_ptr = if let Ok ( ptr) = full_alloc_ptr {
51+ ptr. as_ptr ( )
52+ } else {
53+ return ptr:: null_mut ( ) ;
54+ } ;
55+
56+ // Calculate the offset needed to get an aligned pointer within the
57+ // full allocation. If that offset is zero, increase it to `align`
58+ // so that we still have space to store the extra pointer described
59+ // below.
60+ let mut offset = full_alloc_ptr. align_offset ( align) ;
61+ if offset == 0 {
62+ offset = align;
63+ }
64+
65+ // Before returning the aligned allocation, store a pointer to the
66+ // full unaligned allocation in the bytes just before the aligned
67+ // allocation. We know we have at least eight bytes there due to
68+ // adding `align` to the memory allocation size. We also know the
69+ // write is appropriately aligned for a `*mut u8` pointer because
70+ // `align_ptr` is aligned, and alignments are always powers of two
71+ // (as enforced by the `Layout` type).
72+ unsafe {
73+ let aligned_ptr = full_alloc_ptr. add ( offset) ;
74+ ( aligned_ptr. cast :: < * mut u8 > ( ) ) . sub ( 1 ) . write ( full_alloc_ptr) ;
75+ aligned_ptr
76+ }
77+ }
78+
79+ /// Returns whether the allocation is a multiple of a [`PAGE_SIZE`] and is
80+ /// aligned to [`PAGE_SIZE`].
81+ ///
82+ /// This does not only check the alignment but also the size. For types
83+ /// allocated by Rust itself (e.g., `Box<T>`), the size is always at least the
84+ /// alignment, as specified in the [Rust type layout]. However, to be also safe
85+ /// when it comes to manual invocations, we additionally check if the size is
86+ /// a multiple of [`PAGE_SIZE`].
87+ ///
88+ /// [Rust type layout]: https://doc.rust-lang.org/reference/type-layout.html
89+ const fn layout_allows_page_alloc_shortcut ( layout : & Layout ) -> bool {
90+ layout. size ( ) % PAGE_SIZE == 0 && layout. align ( ) == PAGE_SIZE
91+ }
92+
93+ /// Allocator using UEFI boot services.
94+ ///
95+ /// This type implements [`GlobalAlloc`] and can be marked with the
96+ /// `#[global_allocator]` attribute to be used as global Rust allocator.
4697///
47- /// Only valid for as long as the UEFI boot services are available.
98+ /// Note that if boot services are not active (anymore), [`Allocator::alloc`]
99+ /// will return a null pointer and [`Allocator::dealloc`] will panic.
48100#[ derive( Debug ) ]
49101pub struct Allocator ;
50102
51103unsafe impl GlobalAlloc for Allocator {
52- /// Allocate memory using [`boot::allocate_pool`]. The allocation's [memory
53- /// type] matches the current image's [data type].
104+ /// Allocate memory using the UEFI boot services.
105+ ///
106+ /// The allocation's [memory type] matches the current image's [data type].
54107 ///
55108 /// [memory type]: MemoryType
56109 /// [data type]: LoadedImage::data_type
@@ -59,64 +112,60 @@ unsafe impl GlobalAlloc for Allocator {
59112 return ptr:: null_mut ( ) ;
60113 }
61114
62- let size = layout. size ( ) ;
63- let align = layout. align ( ) ;
64115 let memory_type = get_memory_type ( ) ;
116+ let use_page_shortcut = layout_allows_page_alloc_shortcut ( & layout) ;
65117
66- if align > 8 {
67- // The requested alignment is greater than 8, but `allocate_pool` is
68- // only guaranteed to provide eight-byte alignment. Allocate extra
69- // space so that we can return an appropriately-aligned pointer
70- // within the allocation.
71- let full_alloc_ptr = if let Ok ( ptr) = boot:: allocate_pool ( memory_type, size + align) {
72- ptr. as_ptr ( )
73- } else {
74- return ptr:: null_mut ( ) ;
75- } ;
76-
77- // Calculate the offset needed to get an aligned pointer within the
78- // full allocation. If that offset is zero, increase it to `align`
79- // so that we still have space to store the extra pointer described
80- // below.
81- let mut offset = full_alloc_ptr. align_offset ( align) ;
82- if offset == 0 {
83- offset = align;
118+ match ( use_page_shortcut, layout. align ( ) ) {
119+ // Allocating pages is actually very expected in UEFI OS loaders, so
120+ // it makes sense to provide this optimization.
121+ ( true , _) => {
122+ // To spammy, but useful for manual testing.
123+ // log::trace!("Taking PAGE_SIZE shortcut for layout={layout:?}");
124+ let count = layout. size ( ) . div_ceil ( PAGE_SIZE ) ;
125+ boot:: allocate_pages ( AllocateType :: AnyPages , memory_type, count)
126+ . map ( |ptr| ptr. as_ptr ( ) )
127+ . unwrap_or ( ptr:: null_mut ( ) )
84128 }
85-
86- // Before returning the aligned allocation, store a pointer to the
87- // full unaligned allocation in the bytes just before the aligned
88- // allocation. We know we have at least eight bytes there due to
89- // adding `align` to the memory allocation size. We also know the
90- // write is appropriately aligned for a `*mut u8` pointer because
91- // `align_ptr` is aligned, and alignments are always powers of two
92- // (as enforced by the `Layout` type).
93- unsafe {
94- let aligned_ptr = full_alloc_ptr. add ( offset) ;
95- ( aligned_ptr. cast :: < * mut u8 > ( ) ) . sub ( 1 ) . write ( full_alloc_ptr) ;
96- aligned_ptr
129+ ( false , 0 ..=8 /* UEFI default alignment */ ) => {
130+ // The requested alignment is less than or equal to eight, and
131+ // `allocate_pool` always provides eight-byte alignment, so we can
132+ // use `allocate_pool` directly.
133+ boot:: allocate_pool ( memory_type, layout. size ( ) )
134+ . map ( |ptr| ptr. as_ptr ( ) )
135+ . unwrap_or ( ptr:: null_mut ( ) )
97136 }
98- } else {
99- // The requested alignment is less than or equal to eight, and
100- // `allocate_pool` always provides eight-byte alignment, so we can
101- // use `allocate_pool` directly.
102- boot:: allocate_pool ( memory_type, size)
103- . map ( |ptr| ptr. as_ptr ( ) )
104- . unwrap_or ( ptr:: null_mut ( ) )
137+ ( false , 9 ..) => alloc_pool_aligned ( memory_type, layout. size ( ) , layout. align ( ) ) ,
105138 }
106139 }
107140
108- /// Deallocate memory using [`boot::free_pool`].
109- unsafe fn dealloc ( & self , mut ptr : * mut u8 , layout : Layout ) {
110- if layout. align ( ) > 8 {
111- // Retrieve the pointer to the full allocation that was packed right
112- // before the aligned allocation in `alloc`.
113- ptr = unsafe { ( ptr as * const * mut u8 ) . sub ( 1 ) . read ( ) } ;
114- }
115-
116- // OK to unwrap: `ptr` is required to be a valid allocation by the trait API.
141+ /// Deallocate memory using the UEFI boot services.
142+ ///
143+ /// This will panic after exiting boot services.
144+ unsafe fn dealloc ( & self , ptr : * mut u8 , layout : Layout ) {
117145 let ptr = NonNull :: new ( ptr) . unwrap ( ) ;
118146
119- // Warning: this will panic after exiting boot services.
120- unsafe { boot:: free_pool ( ptr) } . unwrap ( ) ;
147+ let use_page_shortcut = layout_allows_page_alloc_shortcut ( & layout) ;
148+
149+ match ( use_page_shortcut, layout. align ( ) ) {
150+ ( true , _) => {
151+ // To spammy, but useful for manual testing.
152+ // log::trace!("Taking PAGE_SIZE shortcut for layout={layout:?}");
153+ let count = layout. size ( ) . div_ceil ( PAGE_SIZE ) ;
154+ unsafe { boot:: free_pages ( ptr, count) . unwrap ( ) }
155+ }
156+ ( false , 0 ..=8 /* UEFI default alignment */ ) => {
157+ // Warning: this will panic after exiting boot services.
158+ unsafe { boot:: free_pool ( ptr) } . unwrap ( ) ;
159+ }
160+ ( false , 9 ..) => {
161+ let ptr = ptr. as_ptr ( ) . cast :: < * mut u8 > ( ) ;
162+ // Retrieve the pointer to the full allocation that was packed right
163+ // before the aligned allocation in `alloc`.
164+ let actual_alloc_ptr = unsafe { ptr. sub ( 1 ) . read ( ) } ;
165+ let ptr = NonNull :: new ( actual_alloc_ptr) . unwrap ( ) ;
166+ // Warning: this will panic after exiting boot services.
167+ unsafe { boot:: free_pool ( ptr) } . unwrap ( ) ;
168+ }
169+ }
121170 }
122171}
0 commit comments