1use core::ffi::c_void;
2use core::fmt::{Debug, Formatter};
3use core::marker::PhantomData;
4use core::ptr::NonNull;
5use core::{ptr, slice};
6
7use crate::proto::console::text;
8use crate::{CStr16, Char16, Handle, Result, Status};
9
10use super::boot::{BootServices, MemoryDescriptor, MemoryMap, MemoryType};
11use super::runtime::{ResetType, RuntimeServices};
12use super::{cfg, Header, Revision};
13
14/// Marker trait used to provide different views of the UEFI System Table
15pub trait SystemTableView {}
16
17/// Marker struct associated with the boot view of the UEFI System Table
18pub struct Boot;
19impl SystemTableView for Boot {}
20
21/// Marker struct associated with the run-time view of the UEFI System Table
22pub struct Runtime;
23impl SystemTableView for Runtime {}
24
25/// UEFI System Table interface
26///
27/// The UEFI System Table is the gateway to all UEFI services which an UEFI
28/// application is provided access to on startup. However, not all UEFI services
29/// will remain accessible forever.
30///
31/// Some services, called "boot services", may only be called during a bootstrap
32/// stage where the UEFI firmware still has control of the hardware, and will
33/// become unavailable once the firmware hands over control of the hardware to
34/// an operating system loader. Others, called "runtime services", may still be
35/// used after that point, but require a rather specific CPU configuration which
36/// an operating system loader is unlikely to preserve.
37///
38/// We handle this state transition by providing two different views of the UEFI
39/// system table, the "Boot" view and the "Runtime" view. An UEFI application
40/// is initially provided with access to the "Boot" view, and may transition
41/// to the "Runtime" view through the ExitBootServices mechanism that is
42/// documented in the UEFI spec. At that point, the boot view of the system
43/// table will be destroyed (which conveniently invalidates all references to
44/// UEFI boot services in the eye of the Rust borrow checker) and a runtime view
45/// will be provided to replace it.
46#[repr(transparent)]
47#[derive(Debug)]
48pub struct SystemTable<View: SystemTableView> {
49 table: &'static SystemTableImpl,
50 _marker: PhantomData<View>,
51}
52
53// These parts of the UEFI System Table interface will always be available
54impl<View: SystemTableView> SystemTable<View> {
55 /// Return the firmware vendor string
56 #[must_use]
57 pub fn firmware_vendor(&self) -> &CStr16 {
58 unsafe { CStr16::from_ptr(self.table.fw_vendor) }
59 }
60
61 /// Return the firmware revision
62 #[must_use]
63 pub const fn firmware_revision(&self) -> u32 {
64 self.table.fw_revision
65 }
66
67 /// Returns the revision of this table, which is defined to be
68 /// the revision of the UEFI specification implemented by the firmware.
69 #[must_use]
70 pub const fn uefi_revision(&self) -> Revision {
71 self.table.header.revision
72 }
73
74 /// Returns the config table entries, a linear array of structures
75 /// pointing to other system-specific tables.
76 #[allow(clippy::missing_const_for_fn)] // Required until we bump the MSRV.
77 #[must_use]
78 pub fn config_table(&self) -> &[cfg::ConfigTableEntry] {
79 unsafe { slice::from_raw_parts(self.table.cfg_table, self.table.nr_cfg) }
80 }
81
82 /// Creates a new `SystemTable<View>` from a raw address. The address might
83 /// come from the Multiboot2 information structure or something similar.
84 ///
85 /// # Example
86 /// ```no_run
87 /// use core::ffi::c_void;
88 /// use uefi::prelude::{Boot, SystemTable};
89 ///
90 /// let system_table_addr = 0xdeadbeef as *mut c_void;
91 ///
92 /// let mut uefi_system_table = unsafe {
93 /// SystemTable::<Boot>::from_ptr(system_table_addr).expect("Pointer must not be null!")
94 /// };
95 /// ```
96 ///
97 /// # Safety
98 /// This function is unsafe because the caller must be sure that the pointer
99 /// is valid. Otherwise, further operations on the object might result in
100 /// undefined behaviour, even if the methods aren't marked as unsafe.
101 pub unsafe fn from_ptr(ptr: *mut c_void) -> Option<Self> {
102 NonNull::new(ptr.cast()).map(|ptr| Self {
103 table: ptr.as_ref(),
104 _marker: PhantomData,
105 })
106 }
107}
108
109// These parts of the UEFI System Table interface may only be used until boot
110// services are exited and hardware control is handed over to the OS loader
111impl SystemTable<Boot> {
112 /// Returns the standard input protocol.
113 pub fn stdin(&mut self) -> &mut text::Input {
114 unsafe { &mut *self.table.stdin }
115 }
116
117 /// Returns the standard output protocol.
118 pub fn stdout(&mut self) -> &mut text::Output {
119 unsafe { &mut *self.table.stdout.cast() }
120 }
121
122 /// Returns the standard error protocol.
123 pub fn stderr(&mut self) -> &mut text::Output {
124 unsafe { &mut *self.table.stderr.cast() }
125 }
126
127 /// Access runtime services
128 #[must_use]
129 pub const fn runtime_services(&self) -> &RuntimeServices {
130 self.table.runtime
131 }
132
133 /// Access boot services
134 #[must_use]
135 pub const fn boot_services(&self) -> &BootServices {
136 unsafe { &*self.table.boot }
137 }
138
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<MemoryMap<'static>> {
162 let boot_services = self.boot_services();
163
164 // Get the memory map.
165 let memory_map = 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)
175 }
176
177 /// Exit the UEFI boot services.
178 ///
179 /// After this function completes, UEFI hands over control of the hardware
180 /// to the executing OS loader, which implies that the UEFI boot services
181 /// are shut down and cannot be used anymore. Only UEFI configuration tables
182 /// and run-time services can be used, and the latter requires special care
183 /// from the OS loader. We model this situation by consuming the
184 /// `SystemTable<Boot>` view of the System Table and returning a more
185 /// restricted `SystemTable<Runtime>` view as an output.
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 ///
194 /// Once boot services are exited, the logger and allocator provided by
195 /// this crate can no longer be used. The logger should be disabled using
196 /// the [`Logger::disable`] method, and the allocator should be disabled by
197 /// calling [`global_allocator::exit_boot_services`]. Note that if the logger and
198 /// allocator were initialized with [`uefi_services::init`], they will be
199 /// disabled automatically when `exit_boot_services` is called.
200 ///
201 /// # Errors
202 ///
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).
206 ///
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.
210 ///
211 /// [`global_allocator::exit_boot_services`]: crate::global_allocator::exit_boot_services
212 /// [`Logger::disable`]: crate::logger::Logger::disable
213 /// [`uefi_services::init`]: https://docs.rs/uefi-services/latest/uefi_services/fn.init.html
214 #[must_use]
215 pub fn exit_boot_services(self) -> (SystemTable<Runtime>, MemoryMap<'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);
249 }
250 Err(err) => status = err.status(),
251 }
252 }
253
254 // Failed to exit boot services.
255 reset(status)
256 }
257
258 /// Clone this boot-time UEFI system table interface
259 ///
260 /// # Safety
261 ///
262 /// This is unsafe because you must guarantee that the clone will not be
263 /// used after boot services are exited. However, the singleton-based
264 /// designs that Rust uses for memory allocation, logging, and panic
265 /// handling require taking this risk.
266 #[must_use]
267 pub const unsafe fn unsafe_clone(&self) -> Self {
268 SystemTable {
269 table: self.table,
270 _marker: PhantomData,
271 }
272 }
273}
274
275impl Debug for SystemTable<Boot> {
276 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
277 self.table.fmt(f)
278 }
279}
280
281// These parts of the SystemTable struct are only visible after exit from UEFI
282// boot services. They provide unsafe access to the UEFI runtime services, which
283// which were already available before but in safe form.
284impl SystemTable<Runtime> {
285 /// Access runtime services
286 ///
287 /// # Safety
288 ///
289 /// This is unsafe because UEFI runtime services require an elaborate
290 /// CPU configuration which may not be preserved by OS loaders. See the
291 /// "Calling Conventions" chapter of the UEFI specification for details.
292 #[must_use]
293 pub const unsafe fn runtime_services(&self) -> &RuntimeServices {
294 self.table.runtime
295 }
296
297 /// Changes the runtime addressing mode of EFI firmware from physical to virtual.
298 /// It is up to the caller to translate the old SystemTable address to a new virtual
299 /// address and provide it for this function.
300 /// See [`get_current_system_table_addr`]
301 ///
302 /// # Safety
303 ///
304 /// Setting new virtual memory map is unsafe and may cause undefined behaviors.
305 ///
306 /// [`get_current_system_table_addr`]: SystemTable::get_current_system_table_addr
307 pub unsafe fn set_virtual_address_map(
308 self,
309 map: &mut [MemoryDescriptor],
310 new_system_table_virtual_addr: u64,
311 ) -> Result<Self> {
312 // Unsafe Code Guidelines guarantees that there is no padding in an array or a slice
313 // between its elements if the element type is `repr(C)`, which is our case.
314 //
315 // See https://rust-lang.github.io/unsafe-code-guidelines/layout/arrays-and-slices.html
316 let map_size = core::mem::size_of_val(map);
317 let entry_size = core::mem::size_of::<MemoryDescriptor>();
318 let entry_version = crate::table::boot::MEMORY_DESCRIPTOR_VERSION;
319 let map_ptr = map.as_mut_ptr();
320 (self.table.runtime.set_virtual_address_map)(map_size, entry_size, entry_version, map_ptr)
321 .into_with_val(|| {
322 let new_table_ref =
323 &mut *(new_system_table_virtual_addr as usize as *mut SystemTableImpl);
324 Self {
325 table: new_table_ref,
326 _marker: PhantomData,
327 }
328 })
329 }
330
331 /// Return the address of the SystemTable that resides in a UEFI runtime services
332 /// memory region.
333 #[must_use]
334 pub fn get_current_system_table_addr(&self) -> u64 {
335 self.table as *const _ as usize as u64
336 }
337}
338
339/// The actual UEFI system table
340#[repr(C)]
341struct SystemTableImpl {
342 header: Header,
343 /// Null-terminated string representing the firmware's vendor.
344 fw_vendor: *const Char16,
345 fw_revision: u32,
346 stdin_handle: Handle,
347 stdin: *mut text::Input,
348 stdout_handle: Handle,
349 stdout: *mut text::Output,
350 stderr_handle: Handle,
351 stderr: *mut text::Output,
352 /// Runtime services table.
353 runtime: &'static RuntimeServices,
354 /// Boot services table.
355 boot: *const BootServices,
356 /// Number of entries in the configuration table.
357 nr_cfg: usize,
358 /// Pointer to beginning of the array.
359 cfg_table: *const cfg::ConfigTableEntry,
360}
361
362impl<View: SystemTableView> super::Table for SystemTable<View> {
363 const SIGNATURE: u64 = 0x5453_5953_2049_4249;
364}
365
366impl Debug for SystemTableImpl {
367 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
368 f&mut DebugStruct<'_, '_>.debug_struct("UefiSystemTable")
369 .field("header", &self.header)
370 .field("fw_vendor", &(unsafe { CStr16::from_ptr(self.fw_vendor) }))
371 .field("fw_revision", &self.fw_revision)
372 .field("stdin_handle", &self.stdin_handle)
373 .field("stdin", &self.stdin)
374 .field("stdout_handle", &self.stdout_handle)
375 .field("stdout", &self.stdout)
376 .field("stderr_handle", &self.stderr_handle)
377 .field("stderr", &self.stderr)
378 .field("runtime", &self.runtime)
379 // a little bit of extra work needed to call debug-fmt on the BootServices
380 // instead of printing the raw pointer
381 .field("boot", &(unsafe { ptr::read(self.boot) }))
382 .field("nf_cfg", &self.nr_cfg)
383 .field(name:"cfg_table", &self.cfg_table)
384 .finish()
385 }
386}
387