diff --git a/uefi-test-runner/src/main.rs b/uefi-test-runner/src/main.rs index 129db54c7..09853acdd 100644 --- a/uefi-test-runner/src/main.rs +++ b/uefi-test-runner/src/main.rs @@ -13,7 +13,7 @@ use uefi::proto::console::serial::Serial; use uefi::proto::device_path::build::{self, DevicePathBuilder}; use uefi::proto::device_path::messaging::Vendor; use uefi::table::boot::{MemoryMap, MemoryType}; -use uefi::{print, println, Result}; +use uefi::{print, println, system, Result}; mod boot; mod fs; @@ -45,6 +45,9 @@ fn efi_main(image: Handle, mut st: SystemTable) -> Status { // Ensure the tests are run on a version of UEFI we support. check_revision(st.uefi_revision()); + // Check the `uefi::system` module. + check_system(&st); + // Test all the boot services. let bt = st.boot_services(); @@ -67,6 +70,8 @@ fn efi_main(image: Handle, mut st: SystemTable) -> Status { } fn check_revision(rev: uefi::table::Revision) { + assert_eq!(system::uefi_revision(), rev); + let (major, minor) = (rev.major(), rev.minor()); info!("UEFI {}.{}", major, minor / 10); @@ -78,6 +83,25 @@ fn check_revision(rev: uefi::table::Revision) { ); } +fn check_system(st: &SystemTable) { + assert_eq!(system::firmware_vendor(), cstr16!("EDK II")); + check_revision(system::uefi_revision()); + + assert_eq!(system::firmware_revision(), st.firmware_revision()); + system::with_config_table(|t| assert_eq!(t, st.config_table())); + + system::with_stdout(|stdout| { + stdout + .output_string(cstr16!("test system::with_stdout\n")) + .unwrap() + }); + system::with_stderr(|stdout| { + stdout + .output_string(cstr16!("test system::with_stderr\n")) + .unwrap() + }); +} + #[derive(Clone, Copy, Debug)] enum HostRequest { /// Tell the host to take a screenshot and compare against the diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 1c8c17482..5119d3547 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -1,5 +1,10 @@ # uefi - [Unreleased] +## Added +- `uefi::system` is a new module that provides freestanding functions for + accessing fields of the global system table. +- Add standard derives for `ConfigTableEntry`. + ## Changed - **Breaking:** `uefi::helpers::init` no longer takes an argument. - The lifetime of the `SearchType` returned from diff --git a/uefi/src/lib.rs b/uefi/src/lib.rs index fb658d7c3..a40d4edac 100644 --- a/uefi/src/lib.rs +++ b/uefi/src/lib.rs @@ -119,6 +119,7 @@ pub use uguid::guid; mod result; pub use result::{Error, Result, ResultExt, Status, StatusExt}; +pub mod system; pub mod table; pub mod proto; diff --git a/uefi/src/system.rs b/uefi/src/system.rs new file mode 100644 index 000000000..1d19f4f72 --- /dev/null +++ b/uefi/src/system.rs @@ -0,0 +1,146 @@ +//! Functions for accessing fields of the system table. +//! +//! Some of these functions use a callback argument rather than returning a +//! reference to the field directly. This pattern is used because some fields +//! are allowed to change, and so a static lifetime cannot be used. +//! +//! Some functions can only be called while boot services are active, and will +//! panic otherwise. See each function's documentation for details. + +use crate::proto::console::text::{Input, Output}; +use crate::table::cfg::ConfigTableEntry; +use crate::table::{self, Revision}; +use crate::{CStr16, Char16}; +use core::slice; + +/// Get the firmware vendor string. +#[must_use] +pub fn firmware_vendor() -> &'static CStr16 { + let st = table::system_table_raw_panicking(); + // SAFETY: valid per requirements of `set_system_table`. + let st = unsafe { st.as_ref() }; + + let vendor: *const Char16 = st.firmware_vendor.cast(); + + // SAFETY: this assumes that the firmware vendor string is never mutated or freed. + unsafe { CStr16::from_ptr(vendor) } +} + +/// Get the firmware revision. +#[must_use] +pub fn firmware_revision() -> u32 { + let st = table::system_table_raw_panicking(); + // SAFETY: valid per requirements of `set_system_table`. + let st = unsafe { st.as_ref() }; + + st.firmware_revision +} + +/// Get the revision of the system table, which is defined to be the revision of +/// the UEFI specification implemented by the firmware. +#[must_use] +pub fn uefi_revision() -> Revision { + let st = table::system_table_raw_panicking(); + // SAFETY: valid per requirements of `set_system_table`. + let st = unsafe { st.as_ref() }; + + st.header.revision +} + +/// Call `f` with a slice of [`ConfigTableEntry`]. Each entry provides access to +/// a vendor-specific table. +pub fn with_config_table(f: F) -> R +where + F: Fn(&[ConfigTableEntry]) -> R, +{ + let st = table::system_table_raw_panicking(); + // SAFETY: valid per requirements of `set_system_table`. + let st = unsafe { st.as_ref() }; + + let ptr: *const ConfigTableEntry = st.configuration_table.cast(); + let len = st.number_of_configuration_table_entries; + let slice = if ptr.is_null() { + &[] + } else { + unsafe { slice::from_raw_parts(ptr, len) } + }; + f(slice) +} + +/// Call `f` with the [`Input`] protocol attached to stdin. +/// +/// # Panics +/// +/// This function will panic if called after exiting boot services, or if stdin +/// is not available. +pub fn with_stdin(f: F) -> R +where + F: Fn(&mut Input) -> R, +{ + let st = table::system_table_raw_panicking(); + // SAFETY: valid per requirements of `set_system_table`. + let st = unsafe { st.as_ref() }; + // The I/O protocols cannot be used after exiting boot services. + assert!(!st.boot_services.is_null(), "boot services are not active"); + assert!(!st.stdin.is_null(), "stdin is not available"); + + let stdin: *mut Input = st.stdin.cast(); + + // SAFETY: `Input` is a `repr(transparent)` wrapper around the raw input + // type. The underlying pointer in the system table is assumed to be valid. + let stdin = unsafe { &mut *stdin }; + + f(stdin) +} + +/// Call `f` with the [`Output`] protocol attached to stdout. +/// +/// # Panics +/// +/// This function will panic if called after exiting boot services, or if stdout +/// is not available. +pub fn with_stdout(f: F) -> R +where + F: Fn(&mut Output) -> R, +{ + let st = table::system_table_raw_panicking(); + // SAFETY: valid per requirements of `set_system_table`. + let st = unsafe { st.as_ref() }; + // The I/O protocols cannot be used after exiting boot services. + assert!(!st.boot_services.is_null(), "boot services are not active"); + assert!(!st.stdout.is_null(), "stdout is not available"); + + let stdout: *mut Output = st.stdout.cast(); + + // SAFETY: `Output` is a `repr(transparent)` wrapper around the raw output + // type. The underlying pointer in the system table is assumed to be valid. + let stdout = unsafe { &mut *stdout }; + + f(stdout) +} + +/// Call `f` with the [`Output`] protocol attached to stderr. +/// +/// # Panics +/// +/// This function will panic if called after exiting boot services, or if stderr +/// is not available. +pub fn with_stderr(f: F) -> R +where + F: Fn(&mut Output) -> R, +{ + let st = table::system_table_raw_panicking(); + // SAFETY: valid per requirements of `set_system_table`. + let st = unsafe { st.as_ref() }; + // The I/O protocols cannot be used after exiting boot services. + assert!(!st.boot_services.is_null(), "boot services are not active"); + assert!(!st.stderr.is_null(), "stderr is not available"); + + let stderr: *mut Output = st.stderr.cast(); + + // SAFETY: `Output` is a `repr(transparent)` wrapper around the raw output + // type. The underlying pointer in the system table is assumed to be valid. + let stderr = unsafe { &mut *stderr }; + + f(stderr) +} diff --git a/uefi/src/table/cfg.rs b/uefi/src/table/cfg.rs index d5f5fb569..c6f19df94 100644 --- a/uefi/src/table/cfg.rs +++ b/uefi/src/table/cfg.rs @@ -14,7 +14,7 @@ use core::ffi::c_void; /// Contains a set of GUID / pointer for a vendor-specific table. /// /// The UEFI standard guarantees each entry is unique. -#[derive(Debug)] +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(C)] pub struct ConfigTableEntry { /// The GUID identifying this table. diff --git a/uefi/src/table/mod.rs b/uefi/src/table/mod.rs index 078c0f79f..cd837bba8 100644 --- a/uefi/src/table/mod.rs +++ b/uefi/src/table/mod.rs @@ -11,13 +11,25 @@ pub use header::Header; pub use system::{Boot, Runtime, SystemTable}; pub use uefi_raw::table::Revision; -use core::ptr; +use core::ptr::{self, NonNull}; use core::sync::atomic::{AtomicPtr, Ordering}; /// Global system table pointer. This is only modified by [`set_system_table`]. static SYSTEM_TABLE: AtomicPtr = AtomicPtr::new(ptr::null_mut()); +/// Get the raw system table pointer. This may only be called after +/// `set_system_table` has been used to set the global pointer. +/// +/// # Panics +/// +/// Panics if the global system table pointer is null. +#[track_caller] +pub(crate) fn system_table_raw_panicking() -> NonNull { + let ptr = SYSTEM_TABLE.load(Ordering::Acquire); + NonNull::new(ptr).expect("global system table pointer is not set") +} + /// Update the global system table pointer. /// /// This is called automatically in the `main` entry point as part of