1//! This crate simplifies the writing of higher-level code for UEFI.
2//!
3//! It initializes the memory allocation and logging crates,
4//! allowing code to use Rust's data structures and to log errors.
5//!
6//! Logging and allocation are only allowed while boot services are
7//! active. Once runtime services are activated by calling
8//! [`exit_boot_services`], the logger will be disabled and the
9//! allocator will always return null.
10//!
11//! It also stores a global reference to the UEFI system table,
12//! in order to reduce the redundant passing of references to it.
13//!
14//! Library code can simply use global UEFI functions
15//! through the reference provided by `system_table`.
16//!
17//! ## Optional crate features
18//!
19//! - `logger` (enabled by default): Initialize a global logger.
20//! - `panic_handler` (enabled by default): Register a panic handler. A
21//! panic handler must be provided for your program to compile, but
22//! you can choose to provide your own if you don't want to use this
23//! one.
24//! - `qemu`: On x86_64, make qemu exit with code 3 if a panic
25//! occurs. This feature assumes the program is running under QEMU.
26//!
27//! [`exit_boot_services`]: uefi::table::SystemTable::exit_boot_services
28
29#![no_std]
30#![deny(clippy::must_use_candidate)]
31
32extern crate log;
33// Core types.
34extern crate uefi;
35
36use core::ffi::c_void;
37use core::fmt::Write;
38use core::ptr::NonNull;
39
40use cfg_if::cfg_if;
41
42use uefi::prelude::*;
43use uefi::table::boot::{EventType, Tpl};
44use uefi::table::{Boot, SystemTable};
45use uefi::{Event, Result};
46
47/// Reference to the system table.
48///
49/// This table is only fully safe to use until UEFI boot services have been exited.
50/// After that, some fields and methods are unsafe to use, see the documentation of
51/// UEFI's ExitBootServices entry point for more details.
52static mut SYSTEM_TABLE: Option<SystemTable<Boot>> = None;
53
54/// Global logger object
55#[cfg(feature = "logger")]
56static mut LOGGER: Option<uefi::logger::Logger> = None;
57
58/// Obtains a pointer to the system table.
59///
60/// This is meant to be used by higher-level libraries,
61/// which want a convenient way to access the system table singleton.
62///
63/// `init` must have been called first by the UEFI app.
64///
65/// The returned pointer is only valid until boot services are exited.
66#[must_use]
67pub fn system_table() -> NonNull<SystemTable<Boot>> {
68 unsafe {
69 let table_ref: &SystemTable = SYSTEM_TABLE
70 .as_ref()
71 .expect(msg:"The system table handle is not available");
72 NonNull::new(ptr:table_ref as *const _ as *mut _).unwrap()
73 }
74}
75
76/// Initialize the UEFI utility library.
77///
78/// This must be called as early as possible,
79/// before trying to use logging or memory allocation capabilities.
80pub fn init(st: &mut SystemTable<Boot>) -> Result {
81 unsafe {
82 // Avoid double initialization.
83 if SYSTEM_TABLE.is_some() {
84 return Status::SUCCESS.into();
85 }
86
87 // Setup the system table singleton
88 SYSTEM_TABLE = Some(st.unsafe_clone());
89
90 // Setup logging and memory allocation
91
92 #[cfg(feature = "logger")]
93 init_logger(st);
94
95 let boot_services = st.boot_services();
96 uefi::global_allocator::init(boot_services);
97
98 // Schedule these tools to be disabled on exit from UEFI boot services
99 boot_services
100 .create_event(
101 EventType::SIGNAL_EXIT_BOOT_SERVICES,
102 Tpl::NOTIFY,
103 Some(exit_boot_services),
104 None,
105 )
106 .map(|_| ())
107 }
108}
109
110// Internal function for print macros.
111#[doc(hidden)]
112pub fn _print(args: core::fmt::Arguments) {
113 unsafe {
114 let st: &mut SystemTable = SYSTEM_TABLE
115 .as_mut()
116 .expect(msg:"The system table handle is not available");
117
118 st.stdout()
119 .write_fmt(args)
120 .expect(msg:"Failed to write to stdout");
121 }
122}
123
124/// Prints to the standard output.
125///
126/// # Panics
127/// Will panic if `SYSTEM_TABLE` is `None` (Before [init()] and after [uefi::prelude::SystemTable::exit_boot_services()]).
128///
129/// # Examples
130/// ```
131/// print!("");
132/// print!("Hello World\n");
133/// print!("Hello {}", "World");
134/// ```
135#[macro_export]
136macro_rules! print {
137 ($($arg:tt)*) => ($crate::_print(core::format_args!($($arg)*)));
138}
139
140/// Prints to the standard output, with a newline.
141///
142/// # Panics
143/// Will panic if `SYSTEM_TABLE` is `None` (Before [init()] and after [uefi::prelude::SystemTable::exit_boot_services()]).
144///
145/// # Examples
146/// ```
147/// println!();
148/// println!("Hello World");
149/// println!("Hello {}", "World");
150/// ```
151#[macro_export]
152macro_rules! println {
153 () => ($crate::print!("\n"));
154 ($($arg:tt)*) => ($crate::_print(core::format_args!("{}{}", core::format_args!($($arg)*), "\n")));
155}
156
157/// Set up logging
158///
159/// This is unsafe because you must arrange for the logger to be reset with
160/// disable() on exit from UEFI boot services.
161#[cfg(feature = "logger")]
162unsafe fn init_logger(st: &mut SystemTable<Boot>) {
163 let stdout: &mut Output = st.stdout();
164
165 // Construct the logger.
166 let logger: &Logger = {
167 LOGGER = Some(uefi::logger::Logger::new(output:stdout));
168 LOGGER.as_ref().unwrap()
169 };
170
171 // Set the logger.
172 log::set_logger(logger).unwrap(); // Can only fail if already initialized.
173
174 // Set logger max level to level specified by log features
175 log::set_max_level(log::STATIC_MAX_LEVEL);
176}
177
178/// Notify the utility library that boot services are not safe to call anymore
179/// As this is a callback, it must be `extern "efiapi"`.
180unsafe extern "efiapi" fn exit_boot_services(_e: Event, _ctx: Option<NonNull<c_void>>) {
181 // DEBUG: The UEFI spec does not guarantee that this printout will work, as
182 // the services used by logging might already have been shut down.
183 // But it works on current OVMF, and can be used as a handy way to
184 // check that the callback does get called.
185 //
186 // info!("Shutting down the UEFI utility library");
187 SYSTEM_TABLE = None;
188
189 #[cfg(feature = "logger")]
190 if let Some(ref mut logger: &mut Logger) = LOGGER {
191 logger.disable();
192 }
193
194 uefi::global_allocator::exit_boot_services();
195}
196
197#[cfg(feature = "panic_handler")]
198#[panic_handler]
199fn panic_handler(info: &core::panic::PanicInfo) -> ! {
200 println!("[PANIC]: {}", info);
201
202 // Give the user some time to read the message
203 if let Some(st) = unsafe { SYSTEM_TABLE.as_ref() } {
204 st.boot_services().stall(10_000_000);
205 } else {
206 let mut dummy = 0u64;
207 // FIXME: May need different counter values in debug & release builds
208 for i in 0..300_000_000 {
209 unsafe {
210 core::ptr::write_volatile(&mut dummy, i);
211 }
212 }
213 }
214
215 cfg_if! {
216 if #[cfg(all(target_arch = "x86_64", feature = "qemu"))] {
217 // If running in QEMU, use the f4 exit port to signal the error and exit
218 use qemu_exit::QEMUExit;
219 let custom_exit_success = 3;
220 let qemu_exit_handle = qemu_exit::X86::new(0xF4, custom_exit_success);
221 qemu_exit_handle.exit_failure();
222 } else {
223 // If the system table is available, use UEFI's standard shutdown mechanism
224 if let Some(st) = unsafe { SYSTEM_TABLE.as_ref() } {
225 use uefi::table::runtime::ResetType;
226 st.runtime_services()
227 .reset(ResetType::Shutdown, uefi::Status::ABORTED, None);
228 }
229
230 // If we don't have any shutdown mechanism handy, the best we can do is loop
231 log::error!("Could not shut down, please power off the system manually...");
232
233 cfg_if! {
234 if #[cfg(target_arch = "x86_64")] {
235 loop {
236 unsafe {
237 // Try to at least keep CPU from running at 100%
238 core::arch::asm!("hlt", options(nomem, nostack));
239 }
240 }
241 } else if #[cfg(target_arch = "aarch64")] {
242 loop {
243 unsafe {
244 // Try to at least keep CPU from running at 100%
245 core::arch::asm!("hlt 420", options(nomem, nostack));
246 }
247 }
248 } else {
249 loop {
250 // just run forever dammit how do you return never anyway
251 }
252 }
253 }
254 }
255 }
256}
257