@@ -5,10 +5,10 @@ use core::ptr::NonNull;
5
5
use core:: { ptr, slice} ;
6
6
7
7
use crate :: proto:: console:: text;
8
- use crate :: { CStr16 , Char16 , Handle , Result , ResultExt , Status } ;
8
+ use crate :: { CStr16 , Char16 , Handle , Result , Status } ;
9
9
10
- use super :: boot:: { BootServices , MemoryDescriptor , MemoryMapIter } ;
11
- use super :: runtime:: RuntimeServices ;
10
+ use super :: boot:: { BootServices , MemoryDescriptor , MemoryMapIter , MemoryType } ;
11
+ use super :: runtime:: { ResetType , RuntimeServices } ;
12
12
use super :: { cfg, Header , Revision } ;
13
13
14
14
/// Marker trait used to provide different views of the UEFI System Table
@@ -136,7 +136,45 @@ impl SystemTable<Boot> {
136
136
unsafe { & * self . table . boot }
137
137
}
138
138
139
- /// Exit the UEFI boot services
139
+ /// Get the size in bytes of the buffer to allocate for storing the memory
140
+ /// map in `exit_boot_services`.
141
+ ///
142
+ /// This map contains some extra room to avoid needing to allocate more than
143
+ /// once.
144
+ ///
145
+ /// Returns `None` on overflow.
146
+ fn memory_map_size_for_exit_boot_services ( & self ) -> Option < usize > {
147
+ // Allocate space for extra entries beyond the current size of the
148
+ // memory map. The value of 8 matches the value in the Linux kernel:
149
+ // https://github.com/torvalds/linux/blob/e544a07438/drivers/firmware/efi/libstub/efistub.h#L173
150
+ let extra_entries = 8 ;
151
+
152
+ let memory_map_size = self . boot_services ( ) . memory_map_size ( ) ;
153
+ let extra_size = memory_map_size. entry_size . checked_mul ( extra_entries) ?;
154
+ memory_map_size. map_size . checked_add ( extra_size)
155
+ }
156
+
157
+ /// Get the current memory map and exit boot services.
158
+ unsafe fn get_memory_map_and_exit_boot_services (
159
+ & self ,
160
+ buf : & ' static mut [ u8 ] ,
161
+ ) -> Result < MemoryMapIter < ' static > > {
162
+ let boot_services = self . boot_services ( ) ;
163
+
164
+ // Get the memory map.
165
+ let ( memory_map_key, memory_map_iter) = boot_services. memory_map ( buf) ?;
166
+
167
+ // Try to exit boot services using the memory map key. Note that after
168
+ // the first call to `exit_boot_services`, there are restrictions on
169
+ // what boot services functions can be called. In UEFI 2.8 and earlier,
170
+ // only `get_memory_map` and `exit_boot_services` are allowed. Starting
171
+ // in UEFI 2.9 other memory allocation functions may also be called.
172
+ boot_services
173
+ . exit_boot_services ( boot_services. image_handle ( ) , memory_map_key)
174
+ . map ( move |( ) | memory_map_iter)
175
+ }
176
+
177
+ /// Exit the UEFI boot services.
140
178
///
141
179
/// After this function completes, UEFI hands over control of the hardware
142
180
/// to the executing OS loader, which implies that the UEFI boot services
@@ -146,73 +184,75 @@ impl SystemTable<Boot> {
146
184
/// `SystemTable<Boot>` view of the System Table and returning a more
147
185
/// restricted `SystemTable<Runtime>` view as an output.
148
186
///
187
+ /// The memory map at the time of exiting boot services is also
188
+ /// returned. The map is backed by a [`MemoryType::LOADER_DATA`]
189
+ /// allocation. Since the boot services function to free that memory is no
190
+ /// longer available after calling `exit_boot_services`, the allocation is
191
+ /// live until the program ends. The lifetime of the memory map is therefore
192
+ /// `'static`.
193
+ ///
149
194
/// Once boot services are exited, the logger and allocator provided by
150
195
/// this crate can no longer be used. The logger should be disabled using
151
196
/// the [`Logger::disable`] method, and the allocator should be disabled by
152
197
/// calling [`global_allocator::exit_boot_services`]. Note that if the logger and
153
198
/// allocator were initialized with [`uefi_services::init`], they will be
154
199
/// disabled automatically when `exit_boot_services` is called.
155
200
///
156
- /// The handle passed must be the one of the currently executing image,
157
- /// which is received by the entry point of the UEFI application. In
158
- /// addition, the application must provide storage for a memory map, which
159
- /// will be retrieved automatically (as having an up-to-date memory map is a
160
- /// prerequisite for exiting UEFI boot services).
161
- ///
162
- /// The storage must be aligned like a `MemoryDescriptor`.
201
+ /// # Errors
163
202
///
164
- /// The size of the memory map can be estimated by calling
165
- /// `BootServices::memory_map_size()`. But the memory map can grow under the
166
- /// hood between the moment where this size estimate is returned and the
167
- /// moment where boot services are exited, and calling the UEFI memory
168
- /// allocator will not be possible after the first attempt to exit the boot
169
- /// services. Therefore, UEFI applications are advised to allocate storage
170
- /// for the memory map right before exiting boot services, and to allocate a
171
- /// bit more storage than requested by memory_map_size.
203
+ /// This function will fail if it is unable to allocate memory for
204
+ /// the memory map, if it fails to retrieve the memory map, or if
205
+ /// exiting boot services fails (with up to one retry).
172
206
///
173
- /// If `exit_boot_services` succeeds, it will return a runtime view of the
174
- /// system table which more accurately reflects the state of the UEFI
175
- /// firmware following exit from boot services, along with a high-level
176
- /// iterator to the UEFI memory map.
207
+ /// All errors are treated as unrecoverable because the system is
208
+ /// now in an undefined state. Rather than returning control to the
209
+ /// caller, the system will be reset.
177
210
///
178
211
/// [`global_allocator::exit_boot_services`]: crate::global_allocator::exit_boot_services
179
212
/// [`Logger::disable`]: crate::logger::Logger::disable
180
213
/// [`uefi_services::init`]: https://docs.rs/uefi-services/latest/uefi_services/fn.init.html
181
- pub fn exit_boot_services (
182
- self ,
183
- image : Handle ,
184
- mmap_buf : & mut [ u8 ] ,
185
- ) -> Result < ( SystemTable < Runtime > , MemoryMapIter < ' _ > ) > {
186
- unsafe {
187
- let boot_services = self . boot_services ( ) ;
188
-
189
- loop {
190
- // Fetch a memory map, propagate errors and split the completion
191
- // FIXME: This sad pointer hack works around a current
192
- // limitation of the NLL analysis (see Rust bug 51526).
193
- let mmap_buf = & mut * ( mmap_buf as * mut [ u8 ] ) ;
194
- let mmap_comp = boot_services. memory_map ( mmap_buf) ?;
195
- let ( mmap_key, mmap_iter) = mmap_comp;
196
-
197
- // Try to exit boot services using this memory map key
198
- let result = boot_services. exit_boot_services ( image, mmap_key) ;
199
-
200
- // Did we fail because the memory map was updated concurrently?
201
- if result. status ( ) == Status :: INVALID_PARAMETER {
202
- // If so, fetch another memory map and try again
203
- continue ;
204
- } else {
205
- // If not, report the outcome of the operation
206
- return result. map ( |_| {
207
- let st = SystemTable {
208
- table : self . table ,
209
- _marker : PhantomData ,
210
- } ;
211
- ( st, mmap_iter)
212
- } ) ;
214
+ #[ must_use]
215
+ pub fn exit_boot_services ( self ) -> ( SystemTable < Runtime > , MemoryMapIter < ' static > ) {
216
+ let boot_services = self . boot_services ( ) ;
217
+
218
+ // Reboot the device.
219
+ let reset = |status| -> ! { self . runtime_services ( ) . reset ( ResetType :: Cold , status, None ) } ;
220
+
221
+ // Get the size of the buffer to allocate. If that calculation
222
+ // overflows treat it as an unrecoverable error.
223
+ let buf_size = match self . memory_map_size_for_exit_boot_services ( ) {
224
+ Some ( buf_size) => buf_size,
225
+ None => reset ( Status :: ABORTED ) ,
226
+ } ;
227
+
228
+ // Allocate a byte slice to hold the memory map. If the
229
+ // allocation fails treat it as an unrecoverable error.
230
+ let buf: * mut u8 = match boot_services. allocate_pool ( MemoryType :: LOADER_DATA , buf_size) {
231
+ Ok ( buf) => buf,
232
+ Err ( err) => reset ( err. status ( ) ) ,
233
+ } ;
234
+
235
+ // Calling `exit_boot_services` can fail if the memory map key is not
236
+ // current. Retry a second time if that occurs. This matches the
237
+ // behavior of the Linux kernel:
238
+ // https://github.com/torvalds/linux/blob/e544a0743/drivers/firmware/efi/libstub/efi-stub-helper.c#L375
239
+ let mut status = Status :: ABORTED ;
240
+ for _ in 0 ..2 {
241
+ let buf: & mut [ u8 ] = unsafe { slice:: from_raw_parts_mut ( buf, buf_size) } ;
242
+ match unsafe { self . get_memory_map_and_exit_boot_services ( buf) } {
243
+ Ok ( memory_map) => {
244
+ let st = SystemTable {
245
+ table : self . table ,
246
+ _marker : PhantomData ,
247
+ } ;
248
+ return ( st, memory_map) ;
213
249
}
250
+ Err ( err) => status = err. status ( ) ,
214
251
}
215
252
}
253
+
254
+ // Failed to exit boot services.
255
+ reset ( status)
216
256
}
217
257
218
258
/// Clone this boot-time UEFI system table interface
0 commit comments