blob: 6ffcdfd16850d4c91875f95882c289a65c88b2c5 [file]
// Copyright 2023, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The library implements Rust wrappers for a set of UEFI interfaces needed by GBL. It also
//! provides a global allocator and supports auto release of dynamic UEFI resource such as
//! protocols and UEFI allocated buffers.
//!
//! # Examples
//!
//! The following example covers the basic use pattern of the library. It scans all block devices
//! and prints out the device path, block size and io alignment info for each of them.
//!
//! ```
//! fn main(image: EfiHandle, systab_ptr: *mut EfiSystemTable) -> liberror::Result<()> {
//! let efi_entry = initialize(image, systab_ptr)?;
//! let mut con_out = efi_entry.system_table().con_out()?;
//! let boot_services = efi_entry.system_table().boot_services();
//! let path_to_text = boot_services.find_first_and_open::<DevicePathToTextProtocol>()?;
//!
//! write!(con_out, "Scanning block devices...\n")?;
//!
//! let block_handles = boot_services.locate_handle_buffer_by_protocol::<BlockIoProtocol>()?;
//!
//! for (i, handle) in block_handles.handles().iter().enumerate() {
//! let path = boot_services.open_protocol::<DevicePathProtocol>(*handle)?;
//! write!(con_out, "Block Device #{}: ", i)?;
//! path_to_text.convert_device_path_to_text(&path, false, false)?.print()?;
//! write!(con_out, "\n")?;
//!
//! let block_io_protocol = boot_services.open_protocol::<BlockIoProtocol>(*handle)?;
//! let media = block_io_protocol.media()?;
//! write!(con_out, " block size = {}\n", media.block_size)?;
//! write!(con_out, " io alignment = {}\n", media.io_align)?;
//! }
//!
//! Ok(())
//! }
//! ```
#![cfg_attr(not(test), no_std)]
extern crate alloc;
use alloc::vec::Vec;
#[cfg(not(test))]
mod allocation;
#[cfg(not(test))]
pub use allocation::{efi_free, efi_malloc, EfiAllocator};
/// The Android EFI protocol implementation of an A/B slot manager.
pub mod ab_slots;
pub mod protocol;
pub mod utils;
#[cfg(not(test))]
use core::{fmt::Write, panic::PanicInfo};
use core::{marker::PhantomData, ptr::null_mut, slice::from_raw_parts, time::Duration};
use efi_types::{
EfiBootService, EfiConfigurationTable, EfiEvent, EfiGuid, EfiHandle,
EfiMemoryAttributesTableHeader, EfiMemoryDescriptor, EfiMemoryType, EfiRuntimeService,
EfiSystemTable, EfiTimerDelay, EFI_EVENT_TYPE_NOTIFY_SIGNAL, EFI_EVENT_TYPE_NOTIFY_WAIT,
EFI_EVENT_TYPE_RUNTIME, EFI_EVENT_TYPE_SIGNAL_EXIT_BOOT_SERVICES,
EFI_EVENT_TYPE_SIGNAL_VIRTUAL_ADDRESS_CHANGE, EFI_EVENT_TYPE_TIMER,
EFI_LOCATE_HANDLE_SEARCH_TYPE_BY_PROTOCOL, EFI_OPEN_PROTOCOL_ATTRIBUTE_BY_HANDLE_PROTOCOL,
EFI_RESET_TYPE, EFI_RESET_TYPE_EFI_RESET_COLD, EFI_STATUS, EFI_STATUS_SUCCESS,
};
use liberror::{Error, Result};
use libutils::aligned_subslice;
use protocol::{
simple_text_output::SimpleTextOutputProtocol,
{Protocol, ProtocolInfo},
};
use zerocopy::{FromBytes, Ref};
/// `EfiEntry` stores the EFI system table pointer and image handle passed from the entry point.
/// It's the root data structure that derives all other wrapper APIs and structures.
pub struct EfiEntry {
image_handle: EfiHandle,
systab_ptr: *const EfiSystemTable,
}
impl EfiEntry {
/// Gets an instance of `SystemTable`.
///
/// Panics if the pointer is NULL.
pub fn system_table(&self) -> SystemTable {
self.system_table_checked().unwrap()
}
/// Gets an instance of `SystemTable` if pointer is valid.
pub fn system_table_checked(&self) -> Result<SystemTable> {
// SAFETY: Pointers to UEFI data strucutres.
Ok(SystemTable {
efi_entry: self,
table: unsafe { self.systab_ptr.as_ref() }.ok_or(Error::Unsupported)?,
})
}
/// Gets the image handle.
pub fn image_handle(&self) -> DeviceHandle {
DeviceHandle(self.image_handle)
}
}
/// The vendor GUID for UEFI variables defined by GBL.
pub const GBL_EFI_VENDOR_GUID: EfiGuid =
EfiGuid::new(0x5a6d92f3, 0xa2d0, 0x4083, [0x91, 0xa1, 0xa5, 0x0f, 0x6c, 0x3d, 0x98, 0x30]);
/// GUID for UEFI Memory Attributes Table
pub const EFI_MEMORY_ATTRIBUTES_GUID: EfiGuid =
EfiGuid::new(0xdcfa911d, 0x26eb, 0x469f, [0xa2, 0x20, 0x38, 0xb7, 0xdc, 0x46, 0x12, 0x20]);
/// The name of the UEFI variable that GBL defines to determine whether to boot Fuchsia.
/// The value of the variable is ignored: if the variable is present,
/// it indicates that the bootloader should attempt to boot a Fuchsia target.
/// This may include reinitializing GPT partitions and partition contents.
pub const GBL_EFI_OS_BOOT_TARGET_VARNAME: &str = "gbl_os_boot_fuchsia";
/// Creates an `EfiEntry` and initialize EFI global allocator.
///
/// # Safety
///
/// The API modifies internal global state. It should only be called once upon EFI entry to obtain
/// an instance of `EfiEntry` for accessing other APIs. Calling it again when EFI APIs are already
/// being used can introduce a risk of race.
#[cfg(not(test))]
pub unsafe fn initialize(
image_handle: EfiHandle,
systab_ptr: *const EfiSystemTable,
) -> Result<EfiEntry> {
// SAFETY: By safety requirement of this function, `initialize` is only called once upon
// entering EFI application, where there should be no event notify function that can be
// triggered.
unsafe {
// Create one for internal global allocator.
allocation::init_efi_global_alloc(EfiEntry { image_handle, systab_ptr })?;
}
Ok(EfiEntry { image_handle, systab_ptr })
}
/// Exits boot service and returns the memory map in the given buffer.
///
/// The API takes ownership of the given `entry` and causes it to go out of scope.
/// This enforces strict compile time check that any reference/borrow in effect will cause compile
/// errors.
///
/// Existing heap allocated memories will maintain their states. All system memory including them
/// will be under onwership of the subsequent OS or OS loader code.
pub fn exit_boot_services(entry: EfiEntry, mmap_buffer: &mut [u8]) -> Result<EfiMemoryMap> {
let aligned = aligned_subslice(mmap_buffer, core::mem::align_of::<EfiMemoryDescriptor>())?;
let res = entry.system_table().boot_services().get_memory_map(aligned)?;
entry.system_table().boot_services().exit_boot_services(&res)?;
// SAFETY:
// At this point, UEFI has successfully exited boot services and no event/notification can be
// triggered.
#[cfg(not(test))]
unsafe {
allocation::exit_efi_global_alloc();
}
Ok(res)
}
/// `SystemTable` provides methods for accessing fields in `EFI_SYSTEM_TABLE`.
#[derive(Clone, Copy)]
pub struct SystemTable<'a> {
efi_entry: &'a EfiEntry,
table: &'a EfiSystemTable,
}
impl<'a> SystemTable<'a> {
/// Creates an instance of `BootServices`
///
/// Panics if not implemented by UEFI.
pub fn boot_services(&self) -> BootServices<'a> {
self.boot_services_checked().unwrap()
}
/// Creates an instance of `BootServices`
///
/// Returns Err(()) if not implemented by UEFI.
pub fn boot_services_checked(&self) -> Result<BootServices<'a>> {
Ok(BootServices {
efi_entry: self.efi_entry,
// SAFETY: Pointers to UEFI data strucutres.
boot_services: unsafe { self.table.boot_services.as_ref() }
.ok_or(Error::Unsupported)?,
})
}
/// Creates an instance of `RuntimeServices`
///
/// Panics if run time services is not implemented.
pub fn runtime_services(&self) -> RuntimeServices {
self.runtime_services_checked().unwrap()
}
/// Creates an instance of `RuntimeServices` if available from system table.
pub fn runtime_services_checked(&self) -> Result<RuntimeServices> {
Ok(RuntimeServices {
// SAFETY: Pointers to UEFI data strucutres.
runtime_services: *unsafe { self.table.runtime_services.as_ref() }
.ok_or(Error::Unsupported)?,
})
}
/// Gets the `EFI_SYSTEM_TABLE.ConOut` field.
pub fn con_out(&self) -> Result<Protocol<'a, SimpleTextOutputProtocol>> {
// SAFETY: `EFI_SYSTEM_TABLE.ConOut` is a pointer to EfiSimpleTextOutputProtocol structure
// by definition. It lives until ExitBootService and thus as long as `self.efi_entry` or,
// 'a
Ok(unsafe {
Protocol::<SimpleTextOutputProtocol>::new(
// No device handle. This protocol is a permanent reference.
DeviceHandle(null_mut()),
self.table.con_out,
self.efi_entry,
)
})
}
/// Gets the `EFI_SYSTEM_TABLE.ConfigurationTable` array.
pub fn configuration_table(&self) -> Option<&[EfiConfigurationTable]> {
match self.table.configuration_table.is_null() {
true => None,
// SAFETY: Non-null pointer to EFI configuration table.
false => unsafe {
Some(from_raw_parts(
self.table.configuration_table,
self.table.number_of_table_entries,
))
},
}
}
}
/// `BootServices` provides methods for accessing various EFI_BOOT_SERVICES interfaces.
#[derive(Clone, Copy)]
pub struct BootServices<'a> {
efi_entry: &'a EfiEntry,
boot_services: &'a EfiBootService,
}
impl<'a> BootServices<'a> {
/// Wrapper of `EFI_BOOT_SERVICES.AllocatePool()`.
#[allow(dead_code)]
fn allocate_pool(
&self,
pool_type: EfiMemoryType,
size: usize,
) -> Result<*mut core::ffi::c_void> {
let mut out: *mut core::ffi::c_void = null_mut();
// SAFETY: `EFI_BOOT_SERVICES` method call.
unsafe {
efi_call!(self.boot_services.allocate_pool, pool_type, size, &mut out)?;
}
Ok(out)
}
/// Wrapper of `EFI_BOOT_SERVICES.FreePool()`.
fn free_pool(&self, buf: *mut core::ffi::c_void) -> Result<()> {
// SAFETY: `EFI_BOOT_SERVICES` method call.
unsafe { efi_call!(self.boot_services.free_pool, buf) }
}
/// Wrapper of `EFI_BOOT_SERVICES.OpenProtocol()`.
pub fn open_protocol<T: ProtocolInfo>(&self, handle: DeviceHandle) -> Result<Protocol<'a, T>> {
let mut out_handle: EfiHandle = null_mut();
// SAFETY: EFI_BOOT_SERVICES method call.
unsafe {
efi_call!(
self.boot_services.open_protocol,
handle.0,
&T::GUID,
&mut out_handle as *mut _,
self.efi_entry.image_handle().0,
null_mut(),
EFI_OPEN_PROTOCOL_ATTRIBUTE_BY_HANDLE_PROTOCOL
)?;
}
// SAFETY: `EFI_SYSTEM_TABLE.OpenProtocol` returns a valid pointer to `T::InterfaceType`
// on success. The pointer remains valid until closed by
// `EFI_BOOT_SERVICES.CloseProtocol()` when Protocol goes out of scope.
Ok(unsafe { Protocol::<T>::new(handle, out_handle as *mut _, self.efi_entry) })
}
/// Wrapper of `EFI_BOOT_SERVICES.CloseProtocol()`.
#[allow(dead_code)]
fn close_protocol<T: ProtocolInfo>(&self, handle: DeviceHandle) -> Result<()> {
// SAFETY: EFI_BOOT_SERVICES method call.
unsafe {
efi_call!(
self.boot_services.close_protocol,
handle.0,
&T::GUID,
self.efi_entry.image_handle().0,
null_mut()
)
}
}
/// Call `EFI_BOOT_SERVICES.LocateHandleBuffer()` with fixed
/// `EFI_LOCATE_HANDLE_SEARCH_TYPE_BY_PROTOCOL` and without search key.
pub fn locate_handle_buffer_by_protocol<T: ProtocolInfo>(&self) -> Result<LocatedHandles<'a>> {
let mut num_handles: usize = 0;
let mut handles: *mut EfiHandle = null_mut();
// SAFETY: EFI_BOOT_SERVICES method call.
unsafe {
efi_call!(
self.boot_services.locate_handle_buffer,
EFI_LOCATE_HANDLE_SEARCH_TYPE_BY_PROTOCOL,
&T::GUID,
null_mut(),
&mut num_handles as *mut usize as *mut _,
&mut handles as *mut *mut EfiHandle
)?
};
// `handles` should be a valid pointer if the above succeeds. But just double check
// to be safe. If assert fails, then there's a bug in the UEFI firmware.
assert!(!handles.is_null());
Ok(LocatedHandles::new(handles, num_handles, self.efi_entry))
}
/// Search and open the first found target EFI protocol.
pub fn find_first_and_open<T: ProtocolInfo>(&self) -> Result<Protocol<'a, T>> {
// We don't use EFI_BOOT_SERVICES.LocateProtocol() because it doesn't give device handle
// which is required to close the protocol.
let handle = *self
.locate_handle_buffer_by_protocol::<T>()?
.handles()
.first()
.ok_or(Error::NotFound)?;
self.open_protocol::<T>(handle)
}
/// Wrapper of `EFI_BOOT_SERVICE.GetMemoryMap()`.
pub fn get_memory_map<'b>(&self, mmap_buffer: &'b mut [u8]) -> Result<EfiMemoryMap<'b>> {
let mut mmap_size = mmap_buffer.len();
let mut map_key: usize = 0;
let mut descriptor_size: usize = 0;
let mut descriptor_version: u32 = 0;
// SAFETY: EFI_BOOT_SERVICES method call.
unsafe {
efi_call!(
self.boot_services.get_memory_map,
&mut mmap_size,
mmap_buffer.as_mut_ptr() as *mut _,
&mut map_key,
&mut descriptor_size,
&mut descriptor_version
)
}?;
Ok(EfiMemoryMap::new(
&mut mmap_buffer[..mmap_size],
map_key,
descriptor_size,
descriptor_version,
))
}
/// Wrapper of `EFI_BOOT_SERVICE.ExitBootServices()`.
fn exit_boot_services<'b>(&self, mmap: &'b EfiMemoryMap<'b>) -> Result<()> {
// SAFETY: EFI_BOOT_SERVICES method call.
unsafe {
efi_call!(
self.boot_services.exit_boot_services,
self.efi_entry.image_handle().0,
mmap.map_key()
)
}
}
/// Wrapper of `EFI_BOOT_SERVICE.Stall()`.
pub fn stall(&self, micro: usize) -> Result<()> {
// SAFETY: EFI_BOOT_SERVICES method call.
unsafe { efi_call!(self.boot_services.stall, micro) }
}
/// Wraps `EFI_BOOT_SERVICE.CreateEvent()`.
///
/// This function creates an event without a notification callback function; to create an event
/// with a notification, see [create_event_with_notification].
///
/// # Arguments
/// * `event_type`: The EFI event type.
pub fn create_event(&self, event_type: EventType) -> Result<Event<'a, 'static>> {
let mut efi_event: EfiEvent = null_mut();
// SAFETY:
// * all parameters obey the `CreateEvent()` spec
// * on success we take ownership of the provided `efi_event`
unsafe {
efi_call!(
self.boot_services.create_event,
event_type as u32,
0,
None,
null_mut(),
&mut efi_event
)?;
}
Ok(Event::new(self.efi_entry, efi_event, None))
}
/// Wraps `EFI_BOOT_SERVICE.CreateEvent()`.
///
/// This function creates an event with a notification callback function.
///
/// Unlike [create_event], this function is unsafe because the callback will be executed
/// concurrently with the main application code at a higher interrupt level, and there are
/// a few cases where this can lead to races.
///
/// # Arguments
/// * `event_type`: The EFI event type.
/// * `cb`: An [EventNotify] which implements the event notification function and provides the
/// task level priority setting.
///
/// # Safety
/// Most of the safety conditions are enforced at compile-time by the [Sync] requirement on
/// [EventNotifyCallback] - this ensures that e.g. callers cannot capture their raw [EfiEntry]
/// in a callback, but will need to wrap it in a [Sync] type which will ensure safe sharing
/// between the main application and the callback.
///
/// The exception is the global allocation and panic hooks, which use a separate global
/// [EfiEntry] that is not synchronized outside the main application. The caller must ensure
/// that the main application code is not using its [EfiEntry] while a notification callback
/// is trying to concurrently use the global [EfiEntry].
///
/// The easiest way to accomplish this is to write notifications callbacks that:
/// * do not allocate or deallocate heap memory
/// * do not panic
/// Callbacks following these guidelines are safe as they do not use the global [EfiEntry].
///
/// If that is not possible, then the caller must ensure that nothing else makes any calls into
/// UEFI while the returned [Event] is alive; the callback function must have exclusive access
/// to the UEFI APIs so it can use the globals without triggering UEFI reentry.
///
/// In unittests there is no global [EfiEntry] so this is always safe.
pub unsafe fn create_event_with_notification<'e>(
&self,
event_type: EventType,
notify: &'e mut EventNotify,
) -> Result<Event<'a, 'e>> {
let mut efi_event: EfiEvent = null_mut();
// SAFETY:
// Pointers passed are output/callback context pointers which will not be retained by the
// callback (`fn efi_event_cb()`).
// The returned `Event` enforces a borrow to `cb` for 'e. It closes the event when it
// goes out of scope. This ensures that `cb` lives at least as long as the event is in
// effect and there can be no other borrows to `cb`.
unsafe {
efi_call!(
self.boot_services.create_event,
event_type as u32,
notify.tpl as usize,
Some(efi_event_cb),
notify as *mut _ as *mut _,
&mut efi_event
)?;
}
Ok(Event::new(self.efi_entry, efi_event, Some(notify.cb)))
}
/// Wrapper of `EFI_BOOT_SERVICE.CloseEvent()`.
fn close_event(&self, event: &Event) -> Result<()> {
// SAFETY: EFI_BOOT_SERVICES method call.
unsafe { efi_call!(self.boot_services.close_event, event.efi_event) }
}
/// Wrapper of `EFI_BOOT_SERVICE.CheckEvent()`.
///
/// On success, returns true if the event is signaled, false if not.
pub fn check_event(&self, event: &Event) -> Result<bool> {
// SAFETY: EFI_BOOT_SERVICES method call.
match unsafe { efi_call!(self.boot_services.check_event, event.efi_event) } {
Err(e) if e != Error::NotReady => Err(e),
Ok(()) => Ok(true),
_ => Ok(false),
}
}
/// Wrapper of `EFI_BOOT_SERVICE.SetTimer()`.
pub fn set_timer(
&self,
event: &Event,
delay_type: EfiTimerDelay,
trigger_time: Duration,
) -> Result<()> {
// SAFETY: EFI_BOOT_SERVICES method call.
unsafe {
efi_call!(
self.boot_services.set_timer,
event.efi_event,
delay_type,
(trigger_time.as_nanos() / 100).try_into()?
)
}
}
}
/// `RuntimeServices` provides methods for accessing various EFI_RUNTIME_SERVICES interfaces.
#[derive(Clone, Copy)]
pub struct RuntimeServices {
runtime_services: EfiRuntimeService,
}
impl RuntimeServices {
/// Wrapper of `EFI_RUNTIME_SERVICES.GetVariable()`.
pub fn get_variable(&self, guid: &EfiGuid, name: &str, out: &mut [u8]) -> Result<usize> {
let mut size = out.len();
let mut name_utf16: Vec<u16> = name.encode_utf16().collect();
name_utf16.push(0); // null-terminator
// SAFETY:
// * `&mut size` and `&mut out` are input/output params only and will not be retained
// * `&mut size` and `&mut out` are valid pointers and outlive the call
unsafe {
efi_call!(
@bufsize size,
self.runtime_services.get_variable,
name_utf16.as_ptr(),
guid,
null_mut(),
&mut size,
out.as_mut_ptr() as *mut core::ffi::c_void
)?;
}
Ok(size)
}
/// Wrapper of `EFI_RUNTIME_SERVICES.SetVariable()`.
pub fn set_variable(&self, guid: &EfiGuid, name: &str, data: &[u8]) -> Result<()> {
let mut name_utf16: Vec<u16> = name.encode_utf16().collect();
name_utf16.push(0); // null-terminator
// SAFETY:
// * `data.as_mut_ptr()` and `name_utf16.as_ptr()` are valid pointers,
// * outlive the call, and are not retained.
unsafe {
efi_call!(
self.runtime_services.set_variable,
name_utf16.as_ptr(),
guid,
0,
data.len(),
data.as_ptr() as *const core::ffi::c_void
)
}
}
/// Wrapper of `EFI_RUNTIME_SERVICES.reset_system`.
pub fn reset_system(
&self,
reset_type: EFI_RESET_TYPE,
reset_status: EFI_STATUS,
reset_data: Option<&mut [u8]>,
) -> ! {
let (reset_data_len, reset_data_ptr) = match reset_data {
Some(v) => (v.len(), v.as_mut_ptr() as _),
_ => (0, null_mut()),
};
// SAFETY:
// * `reset_data_ptr` is either a valid pointer or NULL which by UEFI spec is allowed.
// * The call reboots the device and thus is not expected to return.
unsafe {
self.runtime_services.reset_system.unwrap()(
reset_type,
reset_status,
reset_data_len,
reset_data_ptr,
);
}
unreachable!();
}
/// Performs a cold reset without status code or data.
pub fn cold_reset(&self) -> ! {
self.reset_system(EFI_RESET_TYPE_EFI_RESET_COLD, EFI_STATUS_SUCCESS, None)
}
}
/// EFI Event type to pass to BootServicess::create_event.
/// See UEFI documentation for details.
#[allow(missing_docs)]
#[repr(u32)]
pub enum EventType {
Timer = EFI_EVENT_TYPE_TIMER,
RunTime = EFI_EVENT_TYPE_RUNTIME,
NotifyWait = EFI_EVENT_TYPE_NOTIFY_WAIT,
NotifySignal = EFI_EVENT_TYPE_NOTIFY_SIGNAL,
SignalExitBootServices = EFI_EVENT_TYPE_SIGNAL_EXIT_BOOT_SERVICES,
SignalVirtualAddressChange = EFI_EVENT_TYPE_SIGNAL_VIRTUAL_ADDRESS_CHANGE,
// Valid combinations:
TimerNotifySignal = EFI_EVENT_TYPE_TIMER | EFI_EVENT_TYPE_NOTIFY_SIGNAL,
}
/// EFI task level priority setting for event notify function.
/// See UEFI documentation for details.
#[allow(missing_docs)]
#[repr(usize)]
#[derive(Copy, Clone)]
pub enum Tpl {
Application = 4,
Callback = 8,
Notify = 16,
HighLevel = 31,
}
/// Event notification callback function.
///
/// The callback function itself takes the [EfiEvent] as an argument and has no return value.
/// This type is a mutable borrow of a closure to ensure that it will outlive the [EfiEvent] and
/// that the callback has exclusive access to it.
///
/// Additionally, the function must be [Sync] because it will be run concurrently to the main app
/// code at a higher interrupt level. One consequence of this is that we cannot capture an
/// [EfiEntry] or any related object in the closure, as they are not [Sync]. This is intentional;
/// in general UEFI APIs are not reentrant except in very limited ways, and we could trigger
/// undefined behavior if we try to call into UEFI while the main application code is also in the
/// middle of a UEFI call. Instead, the notification should signal the main app code to make any
/// necessary UEFI calls once it regains control.
pub type EventNotifyCallback<'a> = &'a mut (dyn FnMut(EfiEvent) + Sync);
/// `EventNotify` contains the task level priority setting and a mutable reference to a
/// closure for the callback. It is passed as the context pointer to low level EFI event
/// notification function entry (`unsafe extern "C" fn efi_event_cb(...)`).
pub struct EventNotify<'e> {
tpl: Tpl,
cb: EventNotifyCallback<'e>,
}
impl<'e> EventNotify<'e> {
/// Creates a new [EventNotify].
pub fn new(tpl: Tpl, cb: EventNotifyCallback<'e>) -> Self {
Self { tpl, cb }
}
}
/// `Event` wraps the raw `EfiEvent` handle and internally enforces a borrow of the registered
/// callback for the given life time `'n`. The event is automatically closed when going out of
/// scope.
pub struct Event<'a, 'n> {
// If `efi_entry` is None, it represents an unowned Event and won't get closed on drop.
efi_entry: Option<&'a EfiEntry>,
efi_event: EfiEvent,
// The actual callback has been passed into UEFI via raw pointer in [create_event], so we
// use [PhantomData] to ensure the callback will outlive the event.
cb: PhantomData<Option<EventNotifyCallback<'n>>>,
}
impl<'a, 'n> Event<'a, 'n> {
/// Creates an instance of owned `Event`. The `Event` is closed when going out of scope.
fn new(
efi_entry: &'a EfiEntry,
efi_event: EfiEvent,
_cb: Option<EventNotifyCallback<'n>>,
) -> Self {
Self { efi_entry: Some(efi_entry), efi_event, cb: PhantomData }
}
/// Creates an unowned `Event`. The `Event` is not closed when going out of scope.
// TODO allow unused?
#[allow(dead_code)]
fn new_unowned(efi_event: EfiEvent) -> Self {
Self { efi_entry: None, efi_event: efi_event, cb: PhantomData }
}
}
impl Drop for Event<'_, '_> {
fn drop(&mut self) {
if let Some(efi_entry) = self.efi_entry {
efi_entry.system_table().boot_services().close_event(self).unwrap();
}
}
}
/// Event notify function entry for EFI events.
///
/// Safety:
///
/// `ctx` must point to a `EventNotify` type object.
/// `ctx` must live longer than the event.
/// There should be no other references to `ctx`.
unsafe extern "C" fn efi_event_cb(event: EfiEvent, ctx: *mut core::ffi::c_void) {
// SAFETY: By safety requirement of this function, ctx points to a valid `EventNotify` object,
// outlives the event/the function call, and there is no other borrows.
let event_cb = unsafe { (ctx as *mut EventNotify).as_mut() }.unwrap();
(event_cb.cb)(event);
}
/// A type for accessing memory map.
#[derive(Debug)]
pub struct EfiMemoryMap<'a> {
buffer: &'a mut [u8],
map_key: usize,
descriptor_size: usize,
descriptor_version: u32,
}
/// Iterator for traversing `EfiMemoryDescriptor` items in `EfiMemoryMap::buffer`.
pub struct EfiMemoryMapIter<'a: 'b, 'b> {
memory_map: &'b EfiMemoryMap<'a>,
offset: usize,
}
impl<'a, 'b> Iterator for EfiMemoryMapIter<'a, 'b> {
type Item = &'b EfiMemoryDescriptor;
fn next(&mut self) -> Option<Self::Item> {
if self.offset >= self.memory_map.buffer.len() {
return None;
}
let bytes = &self.memory_map.buffer[self.offset..][..self.memory_map.descriptor_size];
self.offset += self.memory_map.descriptor_size;
Some(Ref::into_ref(Ref::<_, EfiMemoryDescriptor>::new_from_prefix(bytes).unwrap().0))
}
}
impl<'a> EfiMemoryMap<'a> {
/// Creates a new instance with the given parameters obtained from `get_memory_map()`.
fn new(
buffer: &'a mut [u8],
map_key: usize,
descriptor_size: usize,
descriptor_version: u32,
) -> Self {
Self { buffer, map_key, descriptor_size, descriptor_version }
}
/// Returns the buffer.
pub fn buffer(&self) -> &[u8] {
self.buffer
}
/// Returns the value of `map_key`.
pub fn map_key(&self) -> usize {
self.map_key
}
/// Returns the value of `descriptor_version`.
pub fn descriptor_version(&self) -> u32 {
self.descriptor_version
}
/// Returns the number of descriptors.
pub fn len(&self) -> usize {
self.buffer.len() / self.descriptor_size
}
}
impl<'a: 'b, 'b> IntoIterator for &'b EfiMemoryMap<'a> {
type Item = &'b EfiMemoryDescriptor;
type IntoIter = EfiMemoryMapIter<'a, 'b>;
fn into_iter(self) -> Self::IntoIter {
EfiMemoryMapIter { memory_map: self, offset: 0 }
}
}
/// A type for accessing Memory attributes table
pub struct EfiMemoryAttributesTable<'a> {
/// EfiMemoryAttributesTable header
pub header: &'a EfiMemoryAttributesTableHeader,
tail: &'a [u8],
}
/// Iterator for traversing `EfiMemoryAttributesTable` descriptors.
pub struct EfiMemoryAttributesTableIter<'a> {
descriptor_size: usize,
tail: &'a [u8],
}
impl<'a> Iterator for EfiMemoryAttributesTableIter<'a> {
type Item = &'a EfiMemoryDescriptor;
fn next(&mut self) -> Option<Self::Item> {
// Descriptor size can be greater than `EfiMemoryDescriptor`, so we potentially slice off
// pieces greater than struct size. Thus can't just convert buffer to slice of
// corresponding type.
if let Some((desc_bytes, tail_new)) = self.tail.split_at_checked(self.descriptor_size) {
let desc = Ref::into_ref(
Ref::<_, EfiMemoryDescriptor>::new_from_prefix(desc_bytes).unwrap().0,
);
self.tail = tail_new;
Some(desc)
} else {
None
}
}
}
impl<'a> EfiMemoryAttributesTable<'a> {
/// Creates a new instance with the given parameters obtained from `get_memory_map()`.
///
/// # Returns
/// Ok(EfiMemoryAttributesTable) - on success
/// Err(Error::NotFound) - if table type is incorrect
/// Err(e) - if error `e` occurred parsing table buffer
//
// SAFETY:
// `configuration_table` must be valid EFI Configuration Table object.
pub unsafe fn new(
configuration_table: EfiConfigurationTable,
) -> Result<EfiMemoryAttributesTable<'a>> {
if configuration_table.vendor_guid != EFI_MEMORY_ATTRIBUTES_GUID {
return Err(Error::NotFound);
}
let buf = configuration_table.vendor_table;
// SAFETY: Buffer provided by EFI configuration table.
let header = unsafe {
let header_bytes =
from_raw_parts(buf as *const u8, size_of::<EfiMemoryAttributesTableHeader>());
EfiMemoryAttributesTableHeader::ref_from(header_bytes).ok_or(Error::InvalidInput)?
};
// Note: `descriptor_size` may be bigger than `EfiMemoryDescriptor`.
let descriptor_size: usize = header.descriptor_size.try_into().unwrap();
let descriptors_count: usize = header.number_of_entries.try_into().unwrap();
// SAFETY: Buffer provided by EFI configuration table.
let tail = unsafe {
from_raw_parts(
(buf as *const u8).add(core::mem::size_of_val(header)),
descriptors_count * descriptor_size,
)
};
Ok(Self { header, tail })
}
}
impl<'a> IntoIterator for &EfiMemoryAttributesTable<'a> {
type Item = &'a EfiMemoryDescriptor;
type IntoIter = EfiMemoryAttributesTableIter<'a>;
fn into_iter(self) -> Self::IntoIter {
let descriptor_size = usize::try_from(self.header.descriptor_size).unwrap();
let tail = &self.tail[..];
EfiMemoryAttributesTableIter { descriptor_size, tail }
}
}
/// A type representing a UEFI handle to a UEFI device.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct DeviceHandle(EfiHandle);
impl DeviceHandle {
/// Public constructor
pub fn new(handle: EfiHandle) -> Self {
Self(handle)
}
}
/// `LocatedHandles` holds the array of handles return by
/// `BootServices::locate_handle_buffer_by_protocol()`.
pub struct LocatedHandles<'a> {
handles: &'a [DeviceHandle],
efi_entry: &'a EfiEntry,
}
impl<'a> LocatedHandles<'a> {
pub(crate) fn new(handles: *mut EfiHandle, len: usize, efi_entry: &'a EfiEntry) -> Self {
// Implementation is not suppose to call this with a NULL pointer.
debug_assert!(!handles.is_null());
Self {
// SAFETY: Given correct UEFI firmware, non-null pointer points to valid memory.
// The memory is owned by the objects.
handles: unsafe { from_raw_parts(handles as *mut DeviceHandle, len) },
efi_entry: efi_entry,
}
}
/// Get the list of handles as a slice.
pub fn handles(&self) -> &[DeviceHandle] {
self.handles
}
}
impl Drop for LocatedHandles<'_> {
fn drop(&mut self) {
self.efi_entry
.system_table()
.boot_services()
.free_pool(self.handles.as_ptr() as *mut _)
.unwrap();
}
}
/// Helper macro for printing message via `EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL` in
/// `EFI_SYSTEM_TABLE.ConOut`.
#[macro_export]
macro_rules! efi_print {
( $efi_entry:expr, $( $x:expr ),* $(,)? ) => {
write!($efi_entry.system_table().con_out().unwrap(), $($x,)*).unwrap()
};
}
/// Similar to [efi_print!], but automatically adds the UEFI newline sequence (`\r\n`).
#[macro_export]
macro_rules! efi_println {
( $efi_entry:expr, $( $x:expr ),* $(,)? ) => {
{
efi_print!($efi_entry, $($x,)*);
efi_print!($efi_entry, "\r\n");
}
};
}
/// Resets system. Hangs if not supported.
#[cfg(not(test))]
pub fn reset() -> ! {
efi_try_print!("Resetting...\r\n");
match allocation::internal_efi_entry_and_rt().1 {
Some(rt) => rt.cold_reset(),
_ => efi_try_print!("Runtime services not supported. Hangs...\r\n"),
}
loop {}
}
/// Provides a builtin panic handler.
/// In the long term, to improve flexibility, consider allowing application to install a custom
/// handler into `EfiEntry` to be called here.
/// Don't set this as the panic handler so that other crates' tests can depend on libefi.
#[cfg(not(test))]
pub fn panic(panic: &PanicInfo) -> ! {
efi_try_print!("Panics! {}\r\n", panic);
reset();
}
#[cfg(test)]
mod test {
use super::*;
use crate::protocol::block_io::BlockIoProtocol;
use efi_types::{
EfiBlockIoProtocol, EfiEventNotify, EfiLocateHandleSearchType, EfiStatus, EfiTpl,
EFI_MEMORY_TYPE_LOADER_CODE, EFI_MEMORY_TYPE_LOADER_DATA, EFI_STATUS_NOT_FOUND,
EFI_STATUS_NOT_READY, EFI_STATUS_SUCCESS, EFI_STATUS_UNSUPPORTED,
};
use std::{cell::RefCell, collections::VecDeque, mem::size_of, slice::from_raw_parts_mut};
use zerocopy::IntoBytes;
/// Helper function to generate a Protocol from an interface type.
pub fn generate_protocol<'a, P: ProtocolInfo>(
efi_entry: &'a EfiEntry,
proto: &'a mut P::InterfaceType,
) -> Protocol<'a, P> {
// SAFETY: proto is a valid pointer and lasts at least as long as efi_entry.
unsafe { Protocol::<'a, P>::new(DeviceHandle::new(null_mut()), proto, efi_entry) }
}
/// A structure to store the traces of arguments/outputs for EFI methods.
#[derive(Default)]
pub struct EfiCallTraces {
pub free_pool_trace: FreePoolTrace,
pub open_protocol_trace: OpenProtocolTrace,
pub close_protocol_trace: CloseProtocolTrace,
pub locate_handle_buffer_trace: LocateHandleBufferTrace,
pub get_memory_map_trace: GetMemoryMapTrace,
pub exit_boot_services_trace: ExitBootServicespTrace,
pub create_event_trace: CreateEventTrace,
pub close_event_trace: CloseEventTrace,
pub check_event_trace: CheckEventTrace,
}
// Declares a global instance of EfiCallTraces.
// Need to use thread local storage because rust unit test is multi-threaded.
thread_local! {
static EFI_CALL_TRACES: RefCell<EfiCallTraces> = RefCell::new(Default::default());
}
/// Exports for unit-test in submodules.
pub fn efi_call_traces() -> &'static std::thread::LocalKey<RefCell<EfiCallTraces>> {
&EFI_CALL_TRACES
}
/// EFI_BOOT_SERVICE.FreePool() test implementation.
#[derive(Default)]
pub struct FreePoolTrace {
// Capture `buf`
pub inputs: VecDeque<*mut core::ffi::c_void>,
}
/// Mock of the `EFI_BOOT_SERVICE.FreePool` C API in test environment.
extern "C" fn free_pool(buf: *mut core::ffi::c_void) -> EfiStatus {
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().free_pool_trace.inputs.push_back(buf);
EFI_STATUS_SUCCESS
})
}
/// EFI_BOOT_SERVICE.OpenProtocol() test implementation.
#[derive(Default)]
pub struct OpenProtocolTrace {
// Capture `handle`, `protocol_guid`, `agent_handle`.
pub inputs: VecDeque<(DeviceHandle, EfiGuid, EfiHandle)>,
// Return `intf`, EfiStatus.
pub outputs: VecDeque<(EfiHandle, EfiStatus)>,
}
/// Mock of the `EFI_BOOT_SERVICE.OpenProtocol` C API in test environment.
///
/// # Safety
///
/// Caller should guarantee that `intf` and `protocol_guid` point to valid memory locations.
unsafe extern "C" fn open_protocol(
handle: EfiHandle,
protocol_guid: *const EfiGuid,
intf: *mut *mut core::ffi::c_void,
agent_handle: EfiHandle,
_: EfiHandle,
attr: u32,
) -> EfiStatus {
assert_eq!(attr, EFI_OPEN_PROTOCOL_ATTRIBUTE_BY_HANDLE_PROTOCOL);
EFI_CALL_TRACES.with(|traces| {
let trace = &mut traces.borrow_mut().open_protocol_trace;
trace.inputs.push_back((
DeviceHandle(handle),
// SAFETY: function safety docs require valid `protocol_guid`.
unsafe { *protocol_guid },
agent_handle,
));
let (intf_handle, status) = trace.outputs.pop_front().unwrap();
// SAFETY: function safety docs require valid `intf`.
unsafe { *intf = intf_handle };
status
})
}
/// EFI_BOOT_SERVICE.CloseProtocol() test implementation.
#[derive(Default)]
pub struct CloseProtocolTrace {
// Capture `handle`, `protocol_guid`, `agent_handle`
pub inputs: VecDeque<(DeviceHandle, EfiGuid, EfiHandle)>,
}
/// Mock of the `EFI_BOOT_SERVICE.CloseProtocol` C API in test environment.
///
/// # Safety
///
/// Caller should guarantee that `protocol_guid` points to valid memory location.
unsafe extern "C" fn close_protocol(
handle: EfiHandle,
protocol_guid: *const EfiGuid,
agent_handle: EfiHandle,
_: EfiHandle,
) -> EfiStatus {
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().close_protocol_trace.inputs.push_back((
DeviceHandle(handle),
// SAFETY: function safety docs require valid `protocol_guid`.
unsafe { *protocol_guid },
agent_handle,
));
EFI_STATUS_SUCCESS
})
}
/// EFI_BOOT_SERVICE.LocateHandleBuffer.
#[derive(Default)]
pub struct LocateHandleBufferTrace {
// Capture `protocol`.
pub inputs: VecDeque<EfiGuid>,
// For returning in `num_handles` and `buf`.
pub outputs: VecDeque<(usize, *mut DeviceHandle)>,
}
/// Mock of the `EFI_BOOT_SERVICE.LocateHandleBuffer` C API in test environment.
///
/// # Safety
/// Caller should guarantee that `protocol`, `num_handles`, and `buf` point to valid memory
/// locations.
unsafe extern "C" fn locate_handle_buffer(
search_type: EfiLocateHandleSearchType,
protocol: *const EfiGuid,
search_key: *mut core::ffi::c_void,
num_handles: *mut usize,
buf: *mut *mut EfiHandle,
) -> EfiStatus {
assert_eq!(search_type, EFI_LOCATE_HANDLE_SEARCH_TYPE_BY_PROTOCOL);
assert_eq!(search_key, null_mut());
EFI_CALL_TRACES.with(|traces| {
let trace = &mut traces.borrow_mut().locate_handle_buffer_trace;
// SAFETY: function safety docs require valid `protocol`.
unsafe { trace.inputs.push_back(*protocol) };
let (num, handles) = trace.outputs.pop_front().unwrap();
// SAFETY: function safety docs require valid `num_handles`.
unsafe { *num_handles = num as usize };
// SAFETY: function safety docs require valid `buf`.
unsafe { *buf = handles as *mut EfiHandle };
EFI_STATUS_SUCCESS
})
}
/// EFI_BOOT_SERVICE.GetMemoryMap.
#[derive(Default)]
pub struct GetMemoryMapTrace {
// Capture `memory_map_size` and `memory_map` argument.
pub inputs: VecDeque<(usize, *mut EfiMemoryDescriptor)>,
// Output value `map_key`, `memory_map_size`.
pub outputs: VecDeque<(usize, usize)>,
}
/// Mock of the `EFI_BOOT_SERVICE.GetMemoryMap` C API in test environment.
///
/// # Safety
///
/// Caller should guarantee that `memory_map_size`, `map_key` and `desc_size` point to valid
/// memory locations.
unsafe extern "C" fn get_memory_map(
memory_map_size: *mut usize,
memory_map: *mut EfiMemoryDescriptor,
map_key: *mut usize,
desc_size: *mut usize,
_: *mut u32,
) -> EfiStatus {
EFI_CALL_TRACES.with(|traces| {
let trace = &mut traces.borrow_mut().get_memory_map_trace;
trace.inputs.push_back((unsafe { *memory_map_size }, memory_map));
// SAFETY: function safety docs require valid `memory_map_size`and `map_key`.
unsafe { (*map_key, *memory_map_size) = trace.outputs.pop_front().unwrap() };
// SAFETY: function safety docs require valid `desc_size`.
unsafe { *desc_size = size_of::<EfiMemoryDescriptor>() };
EFI_STATUS_SUCCESS
})
}
/// EFI_BOOT_SERVICE.ExitBootServices.
#[derive(Default)]
pub struct ExitBootServicespTrace {
// Capture `image_handle`, `map_key`
pub inputs: VecDeque<(EfiHandle, usize)>,
}
/// Mock of the `EFI_BOOT_SERVICE.ExitBootServices` C API in test environment.
extern "C" fn exit_boot_services(image_handle: EfiHandle, map_key: usize) -> EfiStatus {
EFI_CALL_TRACES.with(|traces| {
let trace = &mut traces.borrow_mut().exit_boot_services_trace;
trace.inputs.push_back((image_handle, map_key));
EFI_STATUS_SUCCESS
})
}
/// EFI_BOOT_SERVICE.CreateEvent.
#[derive(Default)]
pub struct CreateEventTrace {
// Capture `type_`, `notify_tpl`, `notify_fn`, `notify_ctx`
pub inputs: VecDeque<(u32, EfiTpl, EfiEventNotify, *mut core::ffi::c_void)>,
// Output a EfiEvent.
pub outputs: VecDeque<EfiEvent>,
}
/// Mock of the `EFI_BOOT_SERVICE.CreateEvent` C API in test environment.
///
/// # Safety
///
/// Caller should guarantee that `event` points to valid memory location.
unsafe extern "C" fn create_event(
type_: u32,
notify_tpl: EfiTpl,
notify_fn: EfiEventNotify,
notify_ctx: *mut core::ffi::c_void,
event: *mut EfiEvent,
) -> EfiStatus {
EFI_CALL_TRACES.with(|traces| {
let trace = &mut traces.borrow_mut().create_event_trace;
trace.inputs.push_back((type_, notify_tpl, notify_fn, notify_ctx));
// SAFETY: function safety docs require valid `event`.
unsafe { *event = trace.outputs.pop_front().unwrap() };
EFI_STATUS_SUCCESS
})
}
/// EFI_BOOT_SERVICE.CloseEvent.
#[derive(Default)]
pub struct CloseEventTrace {
// Capture `event`
pub inputs: VecDeque<EfiEvent>,
}
/// Mock of the `EFI_BOOT_SERVICE.CloseEvent` C API in test environment.
extern "C" fn close_event(event: EfiEvent) -> EfiStatus {
EFI_CALL_TRACES.with(|traces| {
let trace = &mut traces.borrow_mut().close_event_trace;
trace.inputs.push_back(event);
EFI_STATUS_SUCCESS
})
}
/// EFI_BOOT_SERVICE.CheckEvent.
#[derive(Default)]
pub struct CheckEventTrace {
// EfiStatus for return.
pub outputs: VecDeque<EfiStatus>,
}
/// Mock of the `EFI_BOOT_SERVICE.CheckEvent` C API in test environment.
extern "C" fn check_event(_: EfiEvent) -> EfiStatus {
EFI_CALL_TRACES.with(|traces| {
let trace = &mut traces.borrow_mut().check_event_trace;
trace.outputs.pop_front().unwrap()
})
}
/// A test wrapper that sets up a system table, image handle and runs a test function like it
/// is an EFI application.
/// TODO(300168989): Investigate using procedural macro to generate test that auto calls this.
pub fn run_test(func: impl FnOnce(EfiHandle, *mut EfiSystemTable) -> ()) {
// Reset all traces
EFI_CALL_TRACES.with(|trace| {
*trace.borrow_mut() = Default::default();
});
let mut systab: EfiSystemTable = Default::default();
let mut boot_services: EfiBootService = Default::default();
boot_services.free_pool = Some(free_pool);
boot_services.open_protocol = Some(open_protocol);
boot_services.close_protocol = Some(close_protocol);
boot_services.locate_handle_buffer = Some(locate_handle_buffer);
boot_services.get_memory_map = Some(get_memory_map);
boot_services.exit_boot_services = Some(exit_boot_services);
boot_services.create_event = Some(create_event);
boot_services.close_event = Some(close_event);
boot_services.check_event = Some(check_event);
systab.boot_services = &mut boot_services as *mut _;
let image_handle: usize = 1234; // Don't care.
func(image_handle as EfiHandle, &mut systab as *mut _);
// Reset all traces
EFI_CALL_TRACES.with(|trace| {
*trace.borrow_mut() = Default::default();
});
}
/// Constructs a mock protocol `P` and run the given callback on it.
///
/// This is similar to `run_test()`, but also provides the construction of a single mock
/// protocol to reduce boilerplate for tests to check the interface between a C EFI protocol
/// struct and our Rust wrappers.
///
/// # Arguments
/// * `c_interface`: the raw C struct interface implementing the desired protocol.
/// * `f`: the callback function to run, given the resulting protocol as an argument.
pub fn run_test_with_mock_protocol<P: ProtocolInfo>(
mut c_interface: P::InterfaceType,
f: impl FnOnce(&Protocol<P>),
) {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
// SAFETY:
// * `c_interface` is a valid C interface for proto `P`
// * `c_interface` outlives the created `protocol`
let protocol = unsafe {
Protocol::new(DeviceHandle::new(null_mut()), &mut c_interface, &efi_entry)
};
f(&protocol);
});
}
/// Get the pointer to an object as an EfiHandle type.
pub fn as_efi_handle<T>(val: &mut T) -> EfiHandle {
val as *mut T as *mut _
}
#[test]
fn test_open_close_protocol() {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
// Set up open_protocol trace
let mut block_io: EfiBlockIoProtocol = Default::default();
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().open_protocol_trace.outputs =
VecDeque::from([(as_efi_handle(&mut block_io), EFI_STATUS_SUCCESS)]);
});
let mut device_handle: usize = 0; // Don't care
{
// Open a protocol
let protocol = efi_entry
.system_table()
.boot_services()
.open_protocol::<BlockIoProtocol>(DeviceHandle(as_efi_handle(
&mut device_handle,
)))
.unwrap();
// Validate call args
EFI_CALL_TRACES.with(|trace| {
assert_eq!(
trace.borrow_mut().open_protocol_trace.inputs,
[(
DeviceHandle(as_efi_handle(&mut device_handle)),
BlockIoProtocol::GUID,
image_handle
),]
);
// close_protocol not called yet.
assert_eq!(trace.borrow_mut().close_protocol_trace.inputs, []);
});
// The protocol gets the correct EfiBlockIoProtocol structure we pass in.
assert_eq!(protocol.interface_ptr(), &mut block_io as *mut _);
}
// Close protocol is called as `protocol` goes out of scope.
EFI_CALL_TRACES
.with(|trace| assert_eq!(trace.borrow_mut().close_protocol_trace.inputs, []));
})
}
#[test]
fn test_null_efi_method() {
// Test that wrapper call fails if efi method is None.
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
// Set up open_protocol trace
let mut block_io: EfiBlockIoProtocol = Default::default();
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().open_protocol_trace.outputs =
VecDeque::from([(as_efi_handle(&mut block_io), EFI_STATUS_SUCCESS)]);
});
// Set the method to None.
// SAFETY:
// run_test() guarantees `boot_services` pointer points to valid object.
unsafe { (*(*systab_ptr).boot_services).open_protocol = None };
let mut device_handle: usize = 0; // Don't care
assert!(efi_entry
.system_table()
.boot_services()
.open_protocol::<BlockIoProtocol>(DeviceHandle(as_efi_handle(&mut device_handle)))
.is_err());
})
}
#[test]
fn test_error_efi_method() {
// Test that wrapper call fails if efi method returns error.
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
// Set up open_protocol trace.
let mut block_io: EfiBlockIoProtocol = Default::default();
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().open_protocol_trace.outputs =
VecDeque::from([(as_efi_handle(&mut block_io), EFI_STATUS_NOT_FOUND)]);
});
let mut device_handle: usize = 0; // Don't care
assert!(efi_entry
.system_table()
.boot_services()
.open_protocol::<BlockIoProtocol>(DeviceHandle(as_efi_handle(&mut device_handle)))
.is_err());
})
}
#[test]
fn test_locate_handle_buffer_by_protocol() {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
// Set up locate_handle_buffer_trace trace.
let mut located_handles: [DeviceHandle; 3] =
[DeviceHandle(1 as *mut _), DeviceHandle(2 as *mut _), DeviceHandle(3 as *mut _)];
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().locate_handle_buffer_trace.outputs =
VecDeque::from([(located_handles.len(), located_handles.as_mut_ptr())]);
});
{
let handles = efi_entry
.system_table()
.boot_services()
.locate_handle_buffer_by_protocol::<BlockIoProtocol>()
.unwrap();
// Returned handles are expected.
assert_eq!(handles.handles().to_vec(), located_handles);
}
EFI_CALL_TRACES.with(|traces| {
let traces = traces.borrow_mut();
// Arguments are passed correctly.
assert_eq!(traces.locate_handle_buffer_trace.inputs, [BlockIoProtocol::GUID]);
// Free pool is called with the correct address.
assert_eq!(traces.free_pool_trace.inputs, [located_handles.as_mut_ptr() as *mut _]);
});
})
}
#[test]
fn test_find_first_and_open() {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
// Set up locate_handle_buffer_trace trace.
let mut located_handles: [DeviceHandle; 3] =
[DeviceHandle(1 as *mut _), DeviceHandle(2 as *mut _), DeviceHandle(3 as *mut _)];
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().locate_handle_buffer_trace.outputs =
VecDeque::from([(located_handles.len(), located_handles.as_mut_ptr())]);
});
// Set up open_protocol trace.
let mut block_io: EfiBlockIoProtocol = Default::default();
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().open_protocol_trace.outputs =
VecDeque::from([(as_efi_handle(&mut block_io), EFI_STATUS_SUCCESS)]);
});
efi_entry
.system_table()
.boot_services()
.find_first_and_open::<BlockIoProtocol>()
.unwrap();
// Check open_protocol is called on the first handle.
EFI_CALL_TRACES.with(|traces| {
assert_eq!(
traces.borrow_mut().open_protocol_trace.inputs,
[(DeviceHandle(1 as *mut _), BlockIoProtocol::GUID, image_handle),]
);
});
})
}
#[test]
fn test_exit_boot_services() {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
// Create a buffer large enough to hold two EfiMemoryDescriptor.
let mut descriptors: [EfiMemoryDescriptor; 2] = [
EfiMemoryDescriptor {
memory_type: EFI_MEMORY_TYPE_LOADER_DATA,
padding: 0,
physical_start: 0,
virtual_start: 0,
number_of_pages: 0,
attributes: 0,
},
EfiMemoryDescriptor {
memory_type: EFI_MEMORY_TYPE_LOADER_CODE,
padding: 0,
physical_start: 0,
virtual_start: 0,
number_of_pages: 0,
attributes: 0,
},
];
let map_key: usize = 12345;
// Set up get_memory_map trace.
EFI_CALL_TRACES.with(|traces| {
// Output only the first EfiMemoryDescriptor.
traces.borrow_mut().get_memory_map_trace.outputs =
VecDeque::from([(map_key, 1 * size_of::<EfiMemoryDescriptor>())]);
});
// SAFETY: Buffer is guaranteed valid.
let buffer = unsafe {
from_raw_parts_mut(
descriptors.as_mut_ptr() as *mut u8,
descriptors.len() * size_of::<EfiMemoryDescriptor>(),
)
};
// Test `exit_boot_services`
let desc = super::exit_boot_services(efi_entry, buffer).unwrap();
// Validate that UEFI APIs are correctly called.
EFI_CALL_TRACES.with(|traces| {
assert_eq!(
traces.borrow_mut().get_memory_map_trace.inputs,
[(
descriptors.len() * size_of::<EfiMemoryDescriptor>(),
descriptors.as_mut_ptr()
)]
);
assert_eq!(
traces.borrow_mut().exit_boot_services_trace.inputs,
[(image_handle, map_key)],
);
});
// Validate that the returned `EfiMemoryMap` contains only 1 EfiMemoryDescriptor.
assert_eq!(desc.into_iter().map(|v| *v).collect::<Vec<_>>(), descriptors[..1].to_vec());
// Validate that the returned `EfiMemoryMap` has the correct map_key.
assert_eq!(desc.map_key(), map_key);
})
}
#[test]
fn test_exit_boot_services_unaligned_buffer() {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
// Create a buffer for 2 EfiMemoryDescriptor.
let descriptors: [EfiMemoryDescriptor; 2] = [
EfiMemoryDescriptor {
memory_type: EFI_MEMORY_TYPE_LOADER_DATA,
padding: 0,
physical_start: 0,
virtual_start: 0,
number_of_pages: 0,
attributes: 0,
},
EfiMemoryDescriptor {
memory_type: EFI_MEMORY_TYPE_LOADER_CODE,
padding: 0,
physical_start: 0,
virtual_start: 0,
number_of_pages: 0,
attributes: 0,
},
];
let map_key: usize = 12345;
// Set up get_memory_map trace.
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().get_memory_map_trace.outputs =
VecDeque::from([(map_key, 2 * size_of::<EfiMemoryDescriptor>())]);
});
// Construct the destination buffer.
let mut buffer = [0u8; 256];
let alignment = core::mem::align_of::<EfiMemoryDescriptor>();
let size = core::mem::size_of::<EfiMemoryDescriptor>();
let aligned = aligned_subslice(&mut buffer[..], alignment).unwrap();
// Offset by 1 element so that we can make an unaligned buffer starting somewhere in
// between.
let start = aligned.get_mut(size..).unwrap();
start[..size].clone_from_slice(descriptors[0].as_bytes());
start[size..][..size].clone_from_slice(descriptors[1].as_bytes());
// Pass an unaligned address.
let desc = super::exit_boot_services(efi_entry, &mut aligned[size - 1..]).unwrap();
// Validate that the returned `EfiMemoryMap` contains the correct EfiMemoryDescriptor.
assert_eq!(desc.into_iter().map(|v| *v).collect::<Vec<_>>(), descriptors[..2].to_vec());
// Validate that the returned `EfiMemoryMap` has the correct map_key.
assert_eq!(desc.map_key(), map_key);
});
}
#[test]
fn test_create_event_with_notify_fn() {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
let mut cb_impl = |_: EfiEvent| {};
let mut cb = EventNotify::new(Tpl::Callback, &mut cb_impl);
let event: EfiEvent = 1234usize as _;
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().create_event_trace.outputs.push_back(event);
});
{
// SAFETY: event notifications are always safe in unittests.
let _ = unsafe {
efi_entry
.system_table()
.boot_services()
.create_event_with_notification(EventType::Timer, &mut cb)
}
.unwrap();
}
let efi_cb: EfiEventNotify = Some(efi_event_cb);
EFI_CALL_TRACES.with(|traces| {
assert_eq!(
traces.borrow_mut().create_event_trace.inputs,
[(
EventType::Timer as _,
Tpl::Callback as _,
efi_cb,
&mut cb as *mut _ as *mut _
)]
)
});
// Verify close_event is called.
EFI_CALL_TRACES
.with(|traces| assert_eq!(traces.borrow_mut().close_event_trace.inputs, [event]));
});
}
#[test]
fn test_create_event_wo_notify_fn() {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
let event: EfiEvent = 1234usize as _;
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().create_event_trace.outputs.push_back(event);
});
{
let _ = efi_entry
.system_table()
.boot_services()
.create_event(EventType::Timer)
.unwrap();
}
EFI_CALL_TRACES.with(|traces| {
assert_eq!(
traces.borrow_mut().create_event_trace.inputs,
[(EventType::Timer as _, 0, None, null_mut())]
)
});
});
}
#[test]
fn test_check_event() {
run_test(|image_handle, systab_ptr| {
let efi_entry = EfiEntry { image_handle, systab_ptr };
let event: EfiEvent = 1234usize as _;
EFI_CALL_TRACES.with(|traces| {
traces.borrow_mut().create_event_trace.outputs.push_back(event);
traces.borrow_mut().check_event_trace.outputs.push_back(EFI_STATUS_SUCCESS);
traces.borrow_mut().check_event_trace.outputs.push_back(EFI_STATUS_NOT_READY);
traces.borrow_mut().check_event_trace.outputs.push_back(EFI_STATUS_UNSUPPORTED);
});
let res =
efi_entry.system_table().boot_services().create_event(EventType::Timer).unwrap();
assert_eq!(efi_entry.system_table().boot_services().check_event(&res), Ok(true));
assert_eq!(efi_entry.system_table().boot_services().check_event(&res), Ok(false));
assert!(efi_entry.system_table().boot_services().check_event(&res).is_err());
});
}
}