1
1
// SPDX-License-Identifier: MIT OR Apache-2.0
2
2
3
- //! This module implements Rust's global allocator interface using UEFI's memory allocation functions .
3
+ //! This module exports [`Allocator`] .
4
4
//!
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 .
7
7
//!
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
11
9
12
- use crate :: boot;
10
+ use crate :: boot:: { self , AllocateType } ;
13
11
use crate :: mem:: memory_map:: MemoryType ;
14
12
use crate :: proto:: loaded_image:: LoadedImage ;
15
13
use core:: alloc:: { GlobalAlloc , Layout } ;
16
14
use core:: ptr:: { self , NonNull } ;
17
15
use core:: sync:: atomic:: { AtomicU32 , Ordering } ;
16
+ use uefi_raw:: table:: boot:: PAGE_SIZE ;
18
17
19
18
/// Get the memory type to use for allocation.
20
19
///
@@ -42,15 +41,69 @@ fn get_memory_type() -> MemoryType {
42
41
}
43
42
}
44
43
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.
46
97
///
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.
48
100
#[ derive( Debug ) ]
49
101
pub struct Allocator ;
50
102
51
103
unsafe 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].
54
107
///
55
108
/// [memory type]: MemoryType
56
109
/// [data type]: LoadedImage::data_type
@@ -59,64 +112,60 @@ unsafe impl GlobalAlloc for Allocator {
59
112
return ptr:: null_mut ( ) ;
60
113
}
61
114
62
- let size = layout. size ( ) ;
63
- let align = layout. align ( ) ;
64
115
let memory_type = get_memory_type ( ) ;
116
+ let use_page_shortcut = layout_allows_page_alloc_shortcut ( & layout) ;
65
117
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 ( ) )
84
128
}
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 ( ) )
97
136
}
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 ( ) ) ,
105
138
}
106
139
}
107
140
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 ) {
117
145
let ptr = NonNull :: new ( ptr) . unwrap ( ) ;
118
146
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
+ }
121
170
}
122
171
}
0 commit comments