diff --git a/Cargo.lock b/Cargo.lock index 0fb6093c0..919b39e48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -853,8 +853,10 @@ name = "uefi" version = "0.27.0" dependencies = [ "bitflags 2.5.0", + "cfg-if", "log", "ptr_meta", + "qemu-exit", "ucs2", "uefi-macros", "uefi-raw", @@ -885,9 +887,6 @@ dependencies = [ name = "uefi-services" version = "0.24.0" dependencies = [ - "cfg-if", - "log", - "qemu-exit", "uefi", ] @@ -898,7 +897,6 @@ dependencies = [ "log", "qemu-exit", "uefi", - "uefi-services", ] [[package]] @@ -906,7 +904,6 @@ name = "uefi_app" version = "0.1.0" dependencies = [ "uefi", - "uefi-services", ] [[package]] diff --git a/README.md b/README.md index 5700e1a01..434172832 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,10 @@ This repository provides various crates: - `uefi-raw`: Raw Rust UEFI bindings for basic structures and functions. - `uefi`: High-level wrapper around various low-level UEFI APIs. \ - Most probably, you want to use this crate. -- `uefi-services`: Optional Rust convenience with a global allocator and a - `log`-based logger implementation. + Offers various optional features for typical Rust convenience, such as a + Logger and an Allocator. (_This is what you are usually looking for!_) +- `uefi-macros`: Helper macros. Used by `uefi`. + You can use the abstractions for example to: @@ -44,6 +45,8 @@ our Rust library. ## User Documentation + + For a quick start, please check out [the UEFI application template](template). The [uefi-rs book] contains a tutorial, how-tos, and overviews of some important @@ -53,7 +56,6 @@ UEFI concepts. Reference documentation for the various crates can be found on - [docs.rs/uefi](https://docs.rs/uefi) - [docs.rs/uefi-macros](https://docs.rs/uefi-macros) - [docs.rs/uefi-raw](https://docs.rs/uefi-raw) -- [docs.rs/uefi-services](https://docs.rs/uefi-services) For additional information, refer to the [UEFI specification][spec]. diff --git a/template/Cargo.toml b/template/Cargo.toml index fd2d904a7..527713ab8 100644 --- a/template/Cargo.toml +++ b/template/Cargo.toml @@ -6,4 +6,3 @@ publish = false [dependencies] uefi = { version = "0.27.0", features = ["alloc"] } -uefi-services = "0.24.0" diff --git a/template/src/main.rs b/template/src/main.rs index e89e4bfe0..64ae41733 100644 --- a/template/src/main.rs +++ b/template/src/main.rs @@ -5,7 +5,7 @@ use uefi::prelude::*; #[entry] fn main(_handle: Handle, mut system_table: SystemTable) -> Status { - uefi_services::init(&mut system_table).unwrap(); + uefi::helpers::init(&mut system_table).unwrap(); Status::SUCCESS } diff --git a/uefi-services/CHANGELOG.md b/uefi-services/CHANGELOG.md index 4d4201b07..6b9550b77 100644 --- a/uefi-services/CHANGELOG.md +++ b/uefi-services/CHANGELOG.md @@ -1,8 +1,8 @@ # uefi-services - [Unreleased] ## Changed -- The implicit `qemu-exit` crate feature has been removed. (Note that this is - different from the `qemu` crate feature, which is unchanged.) +- `uefi-services` is deprecated and should be removed. All functionality was + moved to `uefi::helpers::init()` # uefi-services - 0.23.0 (2023-11-12) diff --git a/uefi-services/Cargo.toml b/uefi-services/Cargo.toml index 176496ce7..9ceb954f1 100644 --- a/uefi-services/Cargo.toml +++ b/uefi-services/Cargo.toml @@ -2,7 +2,7 @@ name = "uefi-services" version = "0.24.0" readme = "README.md" -description = "Higher-level utilities for the `uefi` crate." +description = "Deprecated. Please migrate to `uefi::helpers`." authors.workspace = true categories.workspace = true @@ -14,13 +14,10 @@ rust-version.workspace = true [dependencies] uefi = { version = "0.27.0", features = ["global_allocator"] } -log.workspace = true -cfg-if = "1.0.0" -qemu-exit = { version = "3.0.1", optional = true } [features] default = ["panic_handler", "logger"] # Enable QEMU-specific functionality -qemu = ["dep:qemu-exit"] -panic_handler = [] +qemu = ["uefi/qemu"] +panic_handler = [ "uefi/panic_handler" ] logger = ["uefi/logger"] diff --git a/uefi-services/README.md b/uefi-services/README.md index 885916dd4..642763126 100644 --- a/uefi-services/README.md +++ b/uefi-services/README.md @@ -1,17 +1,3 @@ # uefi-services -[![Crates.io](https://img.shields.io/crates/v/uefi-services)](https://crates.io/crates/uefi-services) -[![Docs.rs](https://docs.rs/uefi-macros/badge.svg)](https://docs.rs/uefi-services) - -This crate enables you some convenience features on top of the -[`uefi`](https://crates.io/crates/uefi) crate. It includes a panic handler, a logger, and -a global allocator. - -`uefi-services` is part of the `uefi-rs` project. Please refer to - for comprehensive documentation. - -## Optional features - -This crate's features are described in [`src/lib.rs`]. - -[`src/lib.rs`]: src/lib.rs +WARNING: `uefi-services` is deprecated. Functionality was moved to `uefi::helpers::init`. diff --git a/uefi-services/build.rs b/uefi-services/build.rs new file mode 100644 index 000000000..96cea2f34 --- /dev/null +++ b/uefi-services/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:warning=`uefi-services` is deprecated. Functionality was moved to `uefi::helpers::init`."); +} diff --git a/uefi-services/src/lib.rs b/uefi-services/src/lib.rs index 5006b2838..3b73e4c16 100644 --- a/uefi-services/src/lib.rs +++ b/uefi-services/src/lib.rs @@ -1,253 +1,20 @@ -//! 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`. -//! -//! ## Optional crate features -//! -//! - `logger` (enabled by default): Initialize a global logger. -//! - `panic_handler` (enabled by default): Register a panic handler. A -//! panic handler must be provided for your program to compile, but -//! you can choose to provide your own if you don't want to use this -//! one. -//! - `qemu`: On x86_64, make qemu exit with code 3 if a panic -//! occurs. This feature assumes the program is running under QEMU. -//! -//! [`exit_boot_services`]: uefi::table::SystemTable::exit_boot_services - +//! WARNING: `uefi-services` is deprecated. Functionality was moved to `uefi::helpers::init`. #![no_std] -#![deny(clippy::must_use_candidate)] -#![deny(missing_debug_implementations)] - -extern crate log; -// Core types. -extern crate uefi; - -use core::ffi::c_void; -use core::fmt::Write; -use core::ptr::{self, NonNull}; -use core::sync::atomic::{AtomicPtr, Ordering}; - -#[cfg(feature = "panic_handler")] -use cfg_if::cfg_if; - -use uefi::table::boot::{EventType, Tpl}; -use uefi::table::{Boot, SystemTable}; -use uefi::{Event, Result, Status, StatusExt}; -/// 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 SYSTEM_TABLE: AtomicPtr = AtomicPtr::new(ptr::null_mut()); +use uefi::prelude::*; +use uefi::Event; +use uefi::Result; -/// Global logger object -#[cfg(feature = "logger")] -static LOGGER: uefi::logger::Logger = uefi::logger::Logger::new(); +pub use uefi::{print, println}; -#[must_use] -fn system_table_opt() -> Option> { - let ptr = SYSTEM_TABLE.load(Ordering::Acquire); - // Safety: the `SYSTEM_TABLE` pointer either be null or a valid system - // table. - // - // Null is the initial value, as well as the value set when exiting boot - // services. Otherwise, the value is set by the call to `init`, which - // requires a valid system table reference as input. - unsafe { SystemTable::from_ptr(ptr) } -} - -/// 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. -#[must_use] -pub fn system_table() -> SystemTable { - system_table_opt().expect("The system table handle is not available") -} - -/// Initialize the UEFI utility library. -/// -/// This must be called as early as possible, -/// before trying to use logging or memory allocation capabilities. +/// Deprecated. Use [`uefi::helpers::init`] instead. +#[deprecated = "WARNING: `uefi-services` is deprecated. Functionality was moved to `uefi::helpers::init`."] pub fn init(st: &mut SystemTable) -> Result> { - if system_table_opt().is_some() { - // Avoid double initialization. - return Status::SUCCESS.to_result_with_val(|| None); - } - - // Setup the system table singleton - SYSTEM_TABLE.store(st.as_ptr().cast_mut(), Ordering::Release); - - unsafe { - // Setup logging and memory allocation - - #[cfg(feature = "logger")] - init_logger(st); - - uefi::allocator::init(st); - - // Schedule these tools to be disabled on exit from UEFI boot services - let boot_services = st.boot_services(); - boot_services - .create_event( - EventType::SIGNAL_EXIT_BOOT_SERVICES, - Tpl::NOTIFY, - Some(exit_boot_services), - None, - ) - .map(Some) - } -} - -// Internal function for print macros. -#[doc(hidden)] -pub fn _print(args: core::fmt::Arguments) { - system_table() - .stdout() - .write_fmt(args) - .expect("Failed to write to stdout"); + uefi::helpers::init(st).map(|_| None) } -/// Prints to the standard output. -/// -/// # Panics -/// Will panic if `SYSTEM_TABLE` is `None` (Before [init()] and after [uefi::prelude::SystemTable::exit_boot_services()]). -/// -/// # Examples -/// ``` -/// print!(""); -/// print!("Hello World\n"); -/// print!("Hello {}", "World"); -/// ``` -#[macro_export] -macro_rules! print { - ($($arg:tt)*) => ($crate::_print(core::format_args!($($arg)*))); -} - -/// Prints to the standard output, with a newline. -/// -/// # Panics -/// Will panic if `SYSTEM_TABLE` is `None` (Before [init()] and after [uefi::prelude::SystemTable::exit_boot_services()]). -/// -/// # Examples -/// ``` -/// println!(); -/// println!("Hello World"); -/// println!("Hello {}", "World"); -/// ``` -#[macro_export] -macro_rules! println { - () => ($crate::print!("\n")); - ($($arg:tt)*) => ($crate::_print(core::format_args!("{}{}", core::format_args!($($arg)*), "\n"))); -} - -/// Set up logging -/// -/// This is unsafe because you must arrange for the logger to be reset with -/// disable() on exit from UEFI boot services. -#[cfg(feature = "logger")] -unsafe fn init_logger(st: &mut SystemTable) { - // Connect the logger to stdout. - LOGGER.set_output(st.stdout()); - - // Set the logger. - log::set_logger(&LOGGER).unwrap(); // Can only fail if already initialized. - - // Set logger max level to level specified by log features - log::set_max_level(log::STATIC_MAX_LEVEL); -} - -/// 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>) { - // 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.store(ptr::null_mut(), Ordering::Release); - - #[cfg(feature = "logger")] - LOGGER.disable(); - - uefi::allocator::exit_boot_services(); -} - -#[cfg(feature = "panic_handler")] -#[panic_handler] -fn panic_handler(info: &core::panic::PanicInfo) -> ! { - println!("[PANIC]: {}", info); - - // Give the user some time to read the message - if let Some(st) = system_table_opt() { - 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) = system_table_opt() { - 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 - log::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 - } - } - } - } - } +/// Deprecated. Use [`uefi::helpers::system_table`] instead. +#[deprecated = "WARNING: `uefi-services` is deprecated. Functionality was moved to `uefi::helpers::system_table`."] +pub fn system_table() -> SystemTable { + uefi::helpers::system_table() } diff --git a/uefi-test-runner/Cargo.toml b/uefi-test-runner/Cargo.toml index b31a9f7ed..be314c930 100644 --- a/uefi-test-runner/Cargo.toml +++ b/uefi-test-runner/Cargo.toml @@ -6,8 +6,7 @@ publish = false edition = "2021" [dependencies] -uefi = { path = "../uefi", features = ["alloc"] } -uefi-services = { path = "../uefi-services" } +uefi = { path = "../uefi", features = ["alloc", "global_allocator", "panic_handler", "logger", "qemu"] } log.workspace = true diff --git a/uefi-test-runner/examples/hello_world.rs b/uefi-test-runner/examples/hello_world.rs index 2c3c3a52a..a2b46d826 100644 --- a/uefi-test-runner/examples/hello_world.rs +++ b/uefi-test-runner/examples/hello_world.rs @@ -14,7 +14,7 @@ use uefi::prelude::*; fn main(_image_handle: Handle, mut system_table: SystemTable) -> Status { // ANCHOR_END: entry // ANCHOR: services - uefi_services::init(&mut system_table).unwrap(); + uefi::helpers::init(&mut system_table).unwrap(); // ANCHOR_END: services // ANCHOR: log info!("Hello world!"); diff --git a/uefi-test-runner/examples/loaded_image.rs b/uefi-test-runner/examples/loaded_image.rs index d565115fa..01debc0cb 100644 --- a/uefi-test-runner/examples/loaded_image.rs +++ b/uefi-test-runner/examples/loaded_image.rs @@ -14,7 +14,7 @@ use uefi::{Identify, Result}; // ANCHOR: main #[entry] fn main(image_handle: Handle, mut system_table: SystemTable) -> Status { - uefi_services::init(&mut system_table).unwrap(); + uefi::helpers::init(&mut system_table).unwrap(); let boot_services = system_table.boot_services(); print_image_path(boot_services).unwrap(); diff --git a/uefi-test-runner/examples/shell_params.rs b/uefi-test-runner/examples/shell_params.rs index eb9beb092..ad95a89f4 100644 --- a/uefi-test-runner/examples/shell_params.rs +++ b/uefi-test-runner/examples/shell_params.rs @@ -7,8 +7,8 @@ use log::error; // ANCHOR: use use uefi::prelude::*; +use uefi::println; use uefi::proto::shell_params::ShellParameters; -use uefi_services::println; extern crate alloc; use alloc::string::{String, ToString}; @@ -20,7 +20,7 @@ use alloc::vec::Vec; fn main(image_handle: Handle, mut system_table: SystemTable) -> Status { // ANCHOR_END: entry // ANCHOR: services - uefi_services::init(&mut system_table).unwrap(); + uefi::helpers::init(&mut system_table).unwrap(); let boot_services = system_table.boot_services(); // ANCHOR_END: services diff --git a/uefi-test-runner/examples/sierpinski.rs b/uefi-test-runner/examples/sierpinski.rs index ad21904b5..bbd2de48c 100644 --- a/uefi-test-runner/examples/sierpinski.rs +++ b/uefi-test-runner/examples/sierpinski.rs @@ -125,7 +125,7 @@ fn draw_sierpinski(bt: &BootServices) -> Result { #[entry] fn main(_handle: Handle, mut system_table: SystemTable) -> Status { - uefi_services::init(&mut system_table).unwrap(); + uefi::helpers::init(&mut system_table).unwrap(); let bt = system_table.boot_services(); draw_sierpinski(bt).unwrap(); Status::SUCCESS diff --git a/uefi-test-runner/src/bin/shell_launcher.rs b/uefi-test-runner/src/bin/shell_launcher.rs index e322a2d2d..4accdca6f 100644 --- a/uefi-test-runner/src/bin/shell_launcher.rs +++ b/uefi-test-runner/src/bin/shell_launcher.rs @@ -46,7 +46,7 @@ fn get_shell_app_device_path<'a>( #[entry] fn efi_main(image: Handle, mut st: SystemTable) -> Status { - uefi_services::init(&mut st).unwrap(); + uefi::helpers::init(&mut st).unwrap(); let boot_services = st.boot_services(); let mut storage = Vec::new(); diff --git a/uefi-test-runner/src/main.rs b/uefi-test-runner/src/main.rs index 7b96d570c..b3a3e7ad4 100644 --- a/uefi-test-runner/src/main.rs +++ b/uefi-test-runner/src/main.rs @@ -14,7 +14,7 @@ use uefi::proto::device_path::build::{self, DevicePathBuilder}; use uefi::proto::device_path::messaging::Vendor; use uefi::table::boot::MemoryType; use uefi::Result; -use uefi_services::{print, println}; +use uefi::{print, println}; mod boot; mod fs; @@ -24,7 +24,7 @@ mod runtime; #[entry] fn efi_main(image: Handle, mut st: SystemTable) -> Status { // Initialize utilities (logging, memory allocation...) - uefi_services::init(&mut st).expect("Failed to initialize utilities"); + uefi::helpers::init(&mut st).expect("Failed to initialize utilities"); // unit tests here diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 2e68cd714..6d221044c 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -4,6 +4,11 @@ - Added `Timestamp` protocol. - Added `UnalignedSlice::as_ptr`. - Added common derives for `Event` and `Handle`. +- `uefi::helpers::init` with the functionality that used to be in + `uefi::services`. With that, new features were added: + - `global_allocator` + - `panic_handler` + - `qemu` # uefi - 0.27.0 (2024-03-17) diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index 7dd6fbc13..9e2fe0e6f 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -15,23 +15,30 @@ rust-version.workspace = true [features] default = ["panic-on-logger-errors"] alloc = [] -global_allocator = [] + +# Generic gate to code that uses unstable features of Rust. You usually need a nightly toolchain. +unstable = [] + +# Helper features: logger = [] +global_allocator = [] +panic_handler = [] # Ignore text output errors in logger as a workaround for firmware issues that # were observed on the VirtualBox UEFI implementation (see uefi-rs#121). # In those cases, this feature can be excluded by removing the default features. panic-on-logger-errors = [] -# Generic gate to code that uses unstable features of Rust. You usually need a nightly toolchain. -unstable = [] +qemu = ["dep:qemu-exit", "panic_handler"] # panic_handler: logical, not technical dependency [dependencies] bitflags.workspace = true log.workspace = true ptr_meta.workspace = true +uguid.workspace = true +cfg-if = "1.0.0" ucs2 = "0.3.2" uefi-macros = "0.13.0" uefi-raw = "0.5.1" -uguid.workspace = true +qemu-exit = { version = "3.0.2", optional = true } [package.metadata.docs.rs] all-features = true diff --git a/uefi/README.md b/uefi/README.md index 51477f4fc..625a39363 100644 --- a/uefi/README.md +++ b/uefi/README.md @@ -2,47 +2,35 @@ [![Crates.io](https://img.shields.io/crates/v/uefi)](https://crates.io/crates/uefi) [![Docs.rs](https://docs.rs/uefi/badge.svg)](https://docs.rs/uefi) -![Stars](https://img.shields.io/github/stars/rust-osdev/uefi-rs) ![License](https://img.shields.io/github/license/rust-osdev/uefi-rs) ![Build status](https://github.com/rust-osdev/uefi-rs/workflows/Rust/badge.svg) +![Stars](https://img.shields.io/github/stars/rust-osdev/uefi-rs) -[UEFI] is the successor to the BIOS. It provides an early boot environment for -OS loaders, hypervisors and other low-level applications. - -The `uefi` crate makes it easy to: -- Write UEFI applications in Rust (for `i686`, `x86_64`, or `aarch64`) -- Call UEFI functions from an OS (usually built with a [custom target][rustc-custom]) - -The objective is to provide **safe** and **performant** wrappers for UEFI interfaces, -and allow developers to write idiomatic Rust code. -Check out the [UEFI application template] for a quick start. +For an introduction to the `uefi-rs` project and documentation, please refer to +our main [README]. -[UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface -[rustc-custom]: https://doc.rust-lang.org/rustc/targets/custom.html -[UEFI application template]: https://github.com/rust-osdev/uefi-rs/tree/HEAD/template +[README]: https://github.com/rust-osdev/uefi-rs/blob/main/README.md ## Optional features This crate's features are described in [`src/lib.rs`]. -See also the [`uefi-services`] crate, which provides a panic handler and -initializes the `global_allocator` and `logger` features. - [`src/lib.rs`]: src/lib.rs -[`uefi-services`]: https://crates.io/crates/uefi-services -## Documentation +## User Documentation + + -The [uefi-rs book] contains a tutorial, how-tos, and overviews of some -important UEFI concepts. +For a quick start, please check out [the UEFI application template](template). + +The [uefi-rs book] contains a tutorial, how-tos, and overviews of some important +UEFI concepts. Reference documentation for the various crates can be found on +[docs.rs]: -Reference documentation can be found on docs.rs: - [docs.rs/uefi](https://docs.rs/uefi) - [docs.rs/uefi-macros](https://docs.rs/uefi-macros) -- [docs.rs/uefi-services](https://docs.rs/uefi-services) - -For additional information, refer to the [UEFI specification][spec]. +- [docs.rs/uefi-raw](https://docs.rs/uefi-raw) [spec]: http://www.uefi.org/specifications [uefi-rs book]: https://rust-osdev.github.io/uefi-rs/HEAD diff --git a/uefi/src/allocator.rs b/uefi/src/allocator.rs index 364326e09..3be75c164 100644 --- a/uefi/src/allocator.rs +++ b/uefi/src/allocator.rs @@ -130,7 +130,3 @@ unsafe impl GlobalAlloc for Allocator { (*boot_services()).free_pool(ptr).unwrap(); } } - -#[cfg(feature = "global_allocator")] -#[global_allocator] -static ALLOCATOR: Allocator = Allocator; diff --git a/uefi/src/helpers/global_allocator.rs b/uefi/src/helpers/global_allocator.rs new file mode 100644 index 000000000..4ca5bdd06 --- /dev/null +++ b/uefi/src/helpers/global_allocator.rs @@ -0,0 +1,4 @@ +use crate::allocator::Allocator; + +#[global_allocator] +static ALLOCATOR: Allocator = Allocator; diff --git a/uefi/src/logger.rs b/uefi/src/helpers/logger.rs similarity index 90% rename from uefi/src/logger.rs rename to uefi/src/helpers/logger.rs index 64434cf0b..3fc386bb1 100644 --- a/uefi/src/logger.rs +++ b/uefi/src/helpers/logger.rs @@ -14,10 +14,35 @@ use crate::proto::console::text::Output; +use crate::prelude::{Boot, SystemTable}; use core::fmt::{self, Write}; use core::ptr; use core::sync::atomic::{AtomicPtr, Ordering}; +/// Global logger object +#[cfg(feature = "logger")] +static LOGGER: Logger = Logger::new(); + +/// Set up logging +/// +/// This is unsafe because you must arrange for the logger to be reset with +/// disable() on exit from UEFI boot services. +#[cfg(feature = "logger")] +pub unsafe fn init(st: &mut SystemTable) { + // Connect the logger to stdout. + LOGGER.set_output(st.stdout()); + + // Set the logger. + log::set_logger(&LOGGER).unwrap(); // Can only fail if already initialized. + + // Set logger max level to level specified by log features + log::set_max_level(log::STATIC_MAX_LEVEL); +} + +pub fn disable() { + LOGGER.disable(); +} + /// Logging implementation which writes to a UEFI output stream. /// /// If this logger is used as a global logger, you must disable it using the diff --git a/uefi/src/helpers/mod.rs b/uefi/src/helpers/mod.rs new file mode 100644 index 000000000..0f31f765a --- /dev/null +++ b/uefi/src/helpers/mod.rs @@ -0,0 +1,113 @@ +//! This module provides miscellaneous opinionated but optional helpers to +//! better integrate your application with the Rust runtime and the Rust +//! ecosystem. +//! +//! For now, this includes: +//! - using [`uefi::allocator::Allocator`] as global allocator (feature `global_allocator`) +//! - an implementation of [`log::Log`] (feature `logger`) +//! - [`print!`][print_macro] and [`println!`][println_macro] macros defaulting +//! to the uefi boot service stdout stream +//! - default panic handler (feature `panic_handler`) +//! +//! **PLEASE NOTE** that these helpers are meant for the pre exit boot service +//! epoch. +//! +//! [print_macro]: uefi::print! +//! [println_macro]: uefi::println! + +use crate::prelude::{Boot, SystemTable}; +use crate::Result; +use crate::StatusExt; +use core::ffi::c_void; +use core::ptr; +use core::sync::atomic::{AtomicPtr, Ordering}; +#[doc(hidden)] +pub use println::_print; +use uefi_raw::Status; + +#[cfg(feature = "global_allocator")] +mod global_allocator; +#[cfg(feature = "logger")] +mod logger; +#[cfg(feature = "panic_handler")] +mod panic_handler; +mod println; + +/// 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 SYSTEM_TABLE: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); + +#[must_use] +fn system_table_opt() -> Option> { + let ptr = SYSTEM_TABLE.load(Ordering::Acquire); + // Safety: the `SYSTEM_TABLE` pointer either be null or a valid system + // table. + // + // Null is the initial value, as well as the value set when exiting boot + // services. Otherwise, the value is set by the call to `init`, which + // requires a valid system table reference as input. + unsafe { SystemTable::from_ptr(ptr) } +} + +/// 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. +#[must_use] +// TODO do we want to keep this public? +pub fn system_table() -> SystemTable { + system_table_opt().expect("The system table handle is not available") +} + +/// Initialize all helpers defined in [`uefi::helpers`] whose Cargo features +/// are activated. +/// +/// This must be called as early as possible, before trying to use logging or +/// memory allocation capabilities. +/// +/// **PLEASE NOTE** that these helpers are meant for the pre exit boot service +/// epoch. +pub fn init(st: &mut SystemTable) -> Result<()> { + if system_table_opt().is_some() { + // Avoid double initialization. + return Status::SUCCESS.to_result_with_val(|| ()); + } + + // Setup the system table singleton + SYSTEM_TABLE.store(st.as_ptr().cast_mut(), Ordering::Release); + + unsafe { + // Setup logging and memory allocation + + #[cfg(feature = "logger")] + logger::init(st); + + #[cfg(feature = "global_allocator")] + uefi::allocator::init(st); + } + + Ok(()) +} + +pub(crate) fn exit() { + // 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.store(ptr::null_mut(), Ordering::Release); + + #[cfg(feature = "logger")] + logger::disable(); + + #[cfg(feature = "global_allocator")] + uefi::allocator::exit_boot_services(); +} diff --git a/uefi/src/helpers/panic_handler.rs b/uefi/src/helpers/panic_handler.rs new file mode 100644 index 000000000..b70b33273 --- /dev/null +++ b/uefi/src/helpers/panic_handler.rs @@ -0,0 +1,63 @@ +use crate::helpers::system_table_opt; +use crate::println; +use cfg_if::cfg_if; + +#[panic_handler] +fn panic_handler(info: &core::panic::PanicInfo) -> ! { + println!("[PANIC]: {}", info); + + // Give the user some time to read the message + if let Some(st) = system_table_opt() { + 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) = system_table_opt() { + 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 + log::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 + } + } + } + } + } +} diff --git a/uefi/src/helpers/println.rs b/uefi/src/helpers/println.rs new file mode 100644 index 000000000..265911da1 --- /dev/null +++ b/uefi/src/helpers/println.rs @@ -0,0 +1,46 @@ +use crate::helpers::system_table; +use core::fmt::Write; + +/// INTERNAL API! Helper for print macros. +#[doc(hidden)] +pub fn _print(args: core::fmt::Arguments) { + system_table() + .stdout() + .write_fmt(args) + .expect("Failed to write to stdout"); +} + +/// Prints to the standard output. +/// +/// # Panics +/// Will panic if `SYSTEM_TABLE` is `None` (Before [`uefi::helpers::init()`] and +/// after [`uefi::prelude::SystemTable::exit_boot_services()`]). +/// +/// # Examples +/// ``` +/// print!(""); +/// print!("Hello World\n"); +/// print!("Hello {}", "World"); +/// ``` +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::helpers::_print(core::format_args!($($arg)*))); +} + +/// Prints to the standard output, with a newline. +/// +/// # Panics +/// Will panic if `SYSTEM_TABLE` is `None` (Before [`uefi::helpers::init()`] and +/// after [`uefi::prelude::SystemTable::exit_boot_services()`]). +/// +/// # Examples +/// ``` +/// println!(); +/// println!("Hello World"); +/// println!("Hello {}", "World"); +/// ``` +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::helpers::_print(core::format_args!("{}{}", core::format_args!($($arg)*), "\n"))); +} diff --git a/uefi/src/lib.rs b/uefi/src/lib.rs index f8fa31f62..d715bfb55 100644 --- a/uefi/src/lib.rs +++ b/uefi/src/lib.rs @@ -7,6 +7,12 @@ //! Feel free to file bug reports and questions in our [issue tracker], and [PR //! contributions][contributing] are also welcome! //! +//! # Interaction with uefi services +//! +//! With this crate you can write code for the pre- and post-exit boot services +//! epochs. However, the `uefi` crate unfolds its true potential when +//! interacting with UEFI boot services. +//! //! # Crate organisation //! //! The top-level module contains some of the most used types and macros, @@ -51,24 +57,26 @@ //! - `logger`: Logging implementation for the standard [`log`] crate //! that prints output to the UEFI console. No buffering is done; this //! is not a high-performance logger. +//! - `panic_handler`: Add a default panic handler that logs to `stdout`. //! - `panic-on-logger-errors` (enabled by default): Panic if a text //! output error occurs in the logger. //! - `unstable`: Enable functionality that depends on [unstable //! features] in the nightly compiler. //! As example, in conjunction with the `alloc`-feature, this gate allows //! the `allocator_api` on certain functions. +//! - `qemu`: Enable some code paths to adapt their execution when executed +//! in QEMU, such as using the special `qemu-exit` device when the panic +//! handler is called. //! -//! The `global_allocator` and `logger` features require special -//! handling to perform initialization and tear-down. The -//! [`uefi-services`] crate provides an `init` method that takes care of -//! this. +//! Some of these features, such as the `logger` or `panic_handler` features, +//! only unfold their potential when you invoke `uefi::helpers::init` as soon +//! as possible in your application. //! //! [Rust UEFI Book]: https://rust-osdev.github.io/uefi-rs/HEAD/ //! [UEFI]: https://uefi.org/ //! [`BootServices`]: table::boot::BootServices //! [`GlobalAlloc`]: alloc::alloc::GlobalAlloc //! [`SystemTable`]: table::SystemTable -//! [`uefi-services`]: https://crates.io/crates/uefi-services //! [`unsafe_protocol`]: proto::unsafe_protocol //! [contributing]: https://github.com/rust-osdev/uefi-rs/blob/main/CONTRIBUTING.md //! [issue tracker]: https://github.com/rust-osdev/uefi-rs/issues @@ -114,9 +122,6 @@ pub mod prelude; pub mod allocator; -#[cfg(feature = "logger")] -pub mod logger; - #[cfg(feature = "alloc")] pub mod fs; @@ -126,6 +131,8 @@ pub(crate) mod mem; pub(crate) mod polyfill; +pub mod helpers; + mod util; #[cfg(test)] diff --git a/uefi/src/table/boot.rs b/uefi/src/table/boot.rs index abf4dca2b..ea6b1e63f 100644 --- a/uefi/src/table/boot.rs +++ b/uefi/src/table/boot.rs @@ -1942,7 +1942,7 @@ pub struct ProtocolSearchKey(NonNull); #[cfg(test)] mod tests { - use core::mem::size_of; + use core::mem::{size_of, size_of_val}; use crate::table::boot::{MemoryAttribute, MemoryMap, MemoryType}; @@ -1952,8 +1952,9 @@ mod tests { let desc_count = buffer.len(); let byte_buffer = { - let size = desc_count * size_of::(); - unsafe { core::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, size) } + unsafe { + core::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, size_of_val(buffer)) + } }; MemoryMap::from_raw(byte_buffer, size_of::()) diff --git a/uefi/src/table/system.rs b/uefi/src/table/system.rs index 88f9a5296..d9cdec170 100644 --- a/uefi/src/table/system.rs +++ b/uefi/src/table/system.rs @@ -206,12 +206,10 @@ impl SystemTable { /// live until the program ends. The lifetime of the memory map is therefore /// `'static`. /// - /// Once boot services are exited, the logger and allocator provided by - /// this crate can no longer be used. The logger should be disabled using - /// the [`Logger::disable`] method, and the allocator should be disabled by - /// calling [`allocator::exit_boot_services`]. Note that if the logger and - /// allocator were initialized with [`uefi_services::init`], they will be - /// disabled automatically when `exit_boot_services` is called. + /// Note that once the boot services are exited, associated loggers and + /// allocators can't use the boot services anymore. For the corresponding + /// abstractions provided by this crate, invoking this function will + /// automatically disable them. /// /// # Errors /// @@ -222,15 +220,13 @@ impl SystemTable { /// All errors are treated as unrecoverable because the system is /// now in an undefined state. Rather than returning control to the /// caller, the system will be reset. - /// - /// [`allocator::exit_boot_services`]: crate::allocator::exit_boot_services - /// [`Logger::disable`]: crate::logger::Logger::disable - /// [`uefi_services::init`]: https://docs.rs/uefi-services/latest/uefi_services/fn.init.html #[must_use] pub fn exit_boot_services( self, memory_type: MemoryType, ) -> (SystemTable, MemoryMap<'static>) { + crate::helpers::exit(); + let boot_services = self.boot_services(); // Reboot the device.