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 | |
32 | extern crate log; |
33 | // Core types. |
34 | extern crate uefi; |
35 | |
36 | use core::ffi::c_void; |
37 | use core::fmt::Write; |
38 | use core::ptr::NonNull; |
39 | |
40 | use cfg_if::cfg_if; |
41 | |
42 | use uefi::prelude::*; |
43 | use uefi::table::boot::{EventType, Tpl}; |
44 | use uefi::table::{Boot, SystemTable}; |
45 | use 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. |
52 | static mut SYSTEM_TABLE: Option<SystemTable<Boot>> = None; |
53 | |
54 | /// Global logger object |
55 | #[cfg (feature = "logger" )] |
56 | static 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 ] |
67 | pub 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. |
80 | pub 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)] |
112 | pub 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 ] |
136 | macro_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 ] |
152 | macro_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" )] |
162 | unsafe 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"`. |
180 | unsafe 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 ] |
199 | fn 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 | |