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