blob: 3cc3d60f9fe7a660e946c2664e2cd3b4f2450f7e [file] [log] [blame]
//! UEFI boot services.
//!
//! These functions will panic if called after exiting boot services.
//!
//! # Accessing protocols
//!
//! Protocols can be opened using several functions in this module. Most
//! commonly, [`open_protocol_exclusive`] should be used. This ensures that
//! nothing else can use the protocol until it is closed, and returns a
//! [`ScopedProtocol`] that takes care of closing the protocol when it is
//! dropped.
//!
//! Other methods for opening protocols:
//!
//! * [`open_protocol`]
//! * [`get_image_file_system`]
//!
//! For protocol definitions, see the [`proto`] module.
//!
//! [`proto`]: crate::proto
pub use uefi_raw::table::boot::{
EventType, MemoryAttribute, MemoryDescriptor, MemoryType, Tpl, PAGE_SIZE,
};
use crate::data_types::PhysicalAddress;
use crate::mem::memory_map::{MemoryMapBackingMemory, MemoryMapKey, MemoryMapMeta, MemoryMapOwned};
use crate::polyfill::maybe_uninit_slice_assume_init_ref;
#[cfg(doc)]
use crate::proto::device_path::LoadedImageDevicePath;
use crate::proto::device_path::{DevicePath, FfiDevicePath};
use crate::proto::loaded_image::LoadedImage;
use crate::proto::media::fs::SimpleFileSystem;
use crate::proto::{BootPolicy, Protocol, ProtocolPointer};
use crate::runtime::{self, ResetType};
use crate::table::Revision;
use crate::util::opt_nonnull_to_ptr;
use crate::{table, Char16, Error, Event, Guid, Handle, Result, Status, StatusExt};
use core::ffi::c_void;
use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut};
use core::ptr::{self, NonNull};
use core::sync::atomic::{AtomicPtr, Ordering};
use core::{mem, slice};
use uefi_raw::table::boot::InterfaceType;
#[cfg(feature = "alloc")]
use {alloc::vec::Vec, uefi::ResultExt};
/// Global image handle. This is only set by [`set_image_handle`], and it is
/// only read by [`image_handle`].
static IMAGE_HANDLE: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
/// Get the [`Handle`] of the currently-executing image.
#[must_use]
pub fn image_handle() -> Handle {
let ptr = IMAGE_HANDLE.load(Ordering::Acquire);
// Safety: the image handle must be valid. We know it is, because it was set
// by `set_image_handle`, which has that same safety requirement.
unsafe { Handle::from_ptr(ptr) }.expect("set_image_handle has not been called")
}
/// Update the global image [`Handle`].
///
/// This is called automatically in the `main` entry point as part of
/// [`uefi::entry`]. It should not be called at any other point in time, unless
/// the executable does not use [`uefi::entry`], in which case it should be
/// called once before calling other boot services functions.
///
/// # Safety
///
/// This function should only be called as described above, and the
/// `image_handle` must be a valid image [`Handle`]. The safety guarantees of
/// [`open_protocol_exclusive`] rely on the global image handle being correct.
pub unsafe fn set_image_handle(image_handle: Handle) {
IMAGE_HANDLE.store(image_handle.as_ptr(), Ordering::Release);
}
/// Return true if boot services are active, false otherwise.
pub(crate) fn are_boot_services_active() -> bool {
let Some(st) = table::system_table_raw() else {
return false;
};
// SAFETY: valid per requirements of `set_system_table`.
let st = unsafe { st.as_ref() };
!st.boot_services.is_null()
}
fn boot_services_raw_panicking() -> NonNull<uefi_raw::table::boot::BootServices> {
let st = table::system_table_raw_panicking();
// SAFETY: valid per requirements of `set_system_table`.
let st = unsafe { st.as_ref() };
NonNull::new(st.boot_services).expect("boot services are not active")
}
/// Raises a task's priority level and returns a [`TplGuard`].
///
/// The effect of calling `raise_tpl` with a `Tpl` that is below the current
/// one (which, sadly, cannot be queried) is undefined by the UEFI spec,
/// which also warns against remaining at high `Tpl`s for a long time.
///
/// This function returns an RAII guard that will automatically restore the
/// original `Tpl` when dropped.
///
/// # Safety
///
/// Raising a task's priority level can affect other running tasks and
/// critical processes run by UEFI. The highest priority level is the
/// most dangerous, since it disables interrupts.
#[must_use]
pub unsafe fn raise_tpl(tpl: Tpl) -> TplGuard {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
TplGuard {
old_tpl: (bt.raise_tpl)(tpl),
}
}
/// Allocates memory pages from the system.
///
/// UEFI OS loaders should allocate memory of the type `LoaderData`.
///
/// # Errors
///
/// * [`Status::OUT_OF_RESOURCES`]: allocation failed.
/// * [`Status::INVALID_PARAMETER`]: `mem_ty` is [`MemoryType::PERSISTENT_MEMORY`],
/// [`MemoryType::UNACCEPTED`], or in the range [`MemoryType::MAX`]`..=0x6fff_ffff`.
/// * [`Status::NOT_FOUND`]: the requested pages could not be found.
pub fn allocate_pages(ty: AllocateType, mem_ty: MemoryType, count: usize) -> Result<NonNull<u8>> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let (ty, mut addr) = match ty {
AllocateType::AnyPages => (0, 0),
AllocateType::MaxAddress(addr) => (1, addr),
AllocateType::Address(addr) => (2, addr),
};
let addr =
unsafe { (bt.allocate_pages)(ty, mem_ty, count, &mut addr) }.to_result_with_val(|| addr)?;
let ptr = addr as *mut u8;
Ok(NonNull::new(ptr).expect("allocate_pages must not return a null pointer if successful"))
}
/// Frees memory pages allocated by [`allocate_pages`].
///
/// # Safety
///
/// The caller must ensure that no references into the allocation remain,
/// and that the memory at the allocation is not used after it is freed.
///
/// # Errors
///
/// * [`Status::NOT_FOUND`]: `ptr` was not allocated by [`allocate_pages`].
/// * [`Status::INVALID_PARAMETER`]: `ptr` is not page aligned or is otherwise invalid.
pub unsafe fn free_pages(ptr: NonNull<u8>, count: usize) -> Result {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let addr = ptr.as_ptr() as PhysicalAddress;
unsafe { (bt.free_pages)(addr, count) }.to_result()
}
/// Allocates from a memory pool. The pointer will be 8-byte aligned.
///
/// # Errors
///
/// * [`Status::OUT_OF_RESOURCES`]: allocation failed.
/// * [`Status::INVALID_PARAMETER`]: `mem_ty` is [`MemoryType::PERSISTENT_MEMORY`],
/// [`MemoryType::UNACCEPTED`], or in the range [`MemoryType::MAX`]`..=0x6fff_ffff`.
pub fn allocate_pool(mem_ty: MemoryType, size: usize) -> Result<NonNull<u8>> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let mut buffer = ptr::null_mut();
let ptr =
unsafe { (bt.allocate_pool)(mem_ty, size, &mut buffer) }.to_result_with_val(|| buffer)?;
Ok(NonNull::new(ptr).expect("allocate_pool must not return a null pointer if successful"))
}
/// Frees memory allocated by [`allocate_pool`].
///
/// # Safety
///
/// The caller must ensure that no references into the allocation remain,
/// and that the memory at the allocation is not used after it is freed.
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: `ptr` is invalid.
pub unsafe fn free_pool(ptr: NonNull<u8>) -> Result {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
unsafe { (bt.free_pool)(ptr.as_ptr()) }.to_result()
}
/// Queries the `get_memory_map` function of UEFI to retrieve the current
/// size of the map. Returns a [`MemoryMapMeta`].
///
/// It is recommended to add a few more bytes for a subsequent allocation
/// for the memory map, as the memory map itself also needs heap memory,
/// and other allocations might occur before that call.
#[must_use]
pub(crate) fn memory_map_size() -> MemoryMapMeta {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let mut map_size = 0;
let mut map_key = MemoryMapKey(0);
let mut desc_size = 0;
let mut desc_version = 0;
let status = unsafe {
(bt.get_memory_map)(
&mut map_size,
ptr::null_mut(),
&mut map_key.0,
&mut desc_size,
&mut desc_version,
)
};
assert_eq!(status, Status::BUFFER_TOO_SMALL);
assert_eq!(
map_size % desc_size,
0,
"Memory map must be a multiple of the reported descriptor size."
);
let mmm = MemoryMapMeta {
desc_size,
map_size,
map_key,
desc_version,
};
mmm.assert_sanity_checks();
mmm
}
/// Stores the current UEFI memory map in an UEFI-heap allocated buffer
/// and returns a [`MemoryMapOwned`].
///
/// # Parameters
///
/// - `mt`: The memory type for the backing memory on the UEFI heap.
/// Usually, this is [`MemoryType::LOADER_DATA`]. You can also use a
/// custom type.
///
/// # Errors
///
/// * [`Status::BUFFER_TOO_SMALL`]
/// * [`Status::INVALID_PARAMETER`]
pub fn memory_map(mt: MemoryType) -> Result<MemoryMapOwned> {
let mut buffer = MemoryMapBackingMemory::new(mt)?;
let meta = get_memory_map(buffer.as_mut_slice())?;
Ok(MemoryMapOwned::from_initialized_mem(buffer, meta))
}
/// Calls the underlying `GetMemoryMap` function of UEFI. On success,
/// the buffer is mutated and contains the map. The map might be shorter
/// than the buffer, which is reflected by the return value.
pub(crate) fn get_memory_map(buf: &mut [u8]) -> Result<MemoryMapMeta> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let mut map_size = buf.len();
let map_buffer = buf.as_mut_ptr().cast::<MemoryDescriptor>();
let mut map_key = MemoryMapKey(0);
let mut desc_size = 0;
let mut desc_version = 0;
assert_eq!(
(map_buffer as usize) % mem::align_of::<MemoryDescriptor>(),
0,
"Memory map buffers must be aligned like a MemoryDescriptor"
);
unsafe {
(bt.get_memory_map)(
&mut map_size,
map_buffer,
&mut map_key.0,
&mut desc_size,
&mut desc_version,
)
}
.to_result_with_val(|| MemoryMapMeta {
map_size,
desc_size,
map_key,
desc_version,
})
}
/// Creates an event.
///
/// This function creates a new event of the specified type and returns it.
///
/// Events are created in a "waiting" state, and may switch to a "signaled"
/// state. If the event type has flag `NotifySignal` set, this will result in
/// a callback for the event being immediately enqueued at the `notify_tpl`
/// priority level. If the event type has flag `NotifyWait`, the notification
/// will be delivered next time `wait_for_event` or `check_event` is called.
/// In both cases, a `notify_fn` callback must be specified.
///
/// # Safety
///
/// This function is unsafe because callbacks must handle exit from boot
/// services correctly.
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: an invalid combination of parameters was provided.
/// * [`Status::OUT_OF_RESOURCES`]: the event could not be allocated.
pub unsafe fn create_event(
event_ty: EventType,
notify_tpl: Tpl,
notify_fn: Option<EventNotifyFn>,
notify_ctx: Option<NonNull<c_void>>,
) -> Result<Event> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let mut event = ptr::null_mut();
// Safety: the argument types of the function pointers are defined
// differently, but are compatible and can be safely transmuted.
let notify_fn: Option<uefi_raw::table::boot::EventNotifyFn> = mem::transmute(notify_fn);
let notify_ctx = opt_nonnull_to_ptr(notify_ctx);
// Now we're ready to call UEFI
(bt.create_event)(event_ty, notify_tpl, notify_fn, notify_ctx, &mut event).to_result_with_val(
// OK to unwrap: event is non-null for Status::SUCCESS.
|| Event::from_ptr(event).unwrap(),
)
}
/// Creates an event in an event group.
///
/// The event's notification function, context, and task priority are specified
/// by `notify_fn`, `notify_ctx`, and `notify_tpl`, respectively. The event will
/// be added to the group of events identified by `event_group`.
///
/// If no group is specified by `event_group`, this function behaves as if the
/// same parameters had been passed to `create_event()`.
///
/// Event groups are collections of events identified by a shared GUID where,
/// when one member event is signaled, all other events are signaled and their
/// individual notification actions are taken. All events are guaranteed to be
/// signaled before the first notification action is taken. All notification
/// functions will be executed in the order specified by their `Tpl`.
///
/// An event can only be part of a single event group. An event may be removed
/// from an event group by calling [`close_event`].
///
/// The [`EventType`] of an event uses the same values as `create_event()`, except that
/// `EventType::SIGNAL_EXIT_BOOT_SERVICES` and `EventType::SIGNAL_VIRTUAL_ADDRESS_CHANGE`
/// are not valid.
///
/// For events of type `NOTIFY_SIGNAL` or `NOTIFY_WAIT`, `notify_fn` must be
/// `Some` and `notify_tpl` must be a valid task priority level. For other event
/// types these parameters are ignored.
///
/// More than one event of type `EventType::TIMER` may be part of a single event
/// group. However, there is no mechanism for determining which of the timers
/// was signaled.
///
/// This operation is only supported starting with UEFI 2.0; earlier versions
/// will fail with [`Status::UNSUPPORTED`].
///
/// # Safety
///
/// The caller must ensure they are passing a valid `Guid` as `event_group`, if applicable.
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: an invalid combination of parameters was provided.
/// * [`Status::OUT_OF_RESOURCES`]: the event could not be allocated.
pub unsafe fn create_event_ex(
event_type: EventType,
notify_tpl: Tpl,
notify_fn: Option<EventNotifyFn>,
notify_ctx: Option<NonNull<c_void>>,
event_group: Option<NonNull<Guid>>,
) -> Result<Event> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
if bt.header.revision < Revision::EFI_2_00 {
return Err(Status::UNSUPPORTED.into());
}
let mut event = ptr::null_mut();
// Safety: the argument types of the function pointers are defined
// differently, but are compatible and can be safely transmuted.
let notify_fn: Option<uefi_raw::table::boot::EventNotifyFn> = mem::transmute(notify_fn);
(bt.create_event_ex)(
event_type,
notify_tpl,
notify_fn,
opt_nonnull_to_ptr(notify_ctx),
opt_nonnull_to_ptr(event_group),
&mut event,
)
.to_result_with_val(
// OK to unwrap: event is non-null for Status::SUCCESS.
|| Event::from_ptr(event).unwrap(),
)
}
/// Checks to see if an event is signaled, without blocking execution to wait for it.
///
/// Returns `Ok(true)` if the event is in the signaled state or `Ok(false)`
/// if the event is not in the signaled state.
///
/// # Errors
///
/// Note: Instead of returning [`Status::NOT_READY`] as listed in the UEFI
/// Specification, this function will return `Ok(false)`.
///
/// * [`Status::INVALID_PARAMETER`]: `event` is of type [`NOTIFY_SIGNAL`].
///
/// [`NOTIFY_SIGNAL`]: EventType::NOTIFY_SIGNAL
pub fn check_event(event: Event) -> Result<bool> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let status = unsafe { (bt.check_event)(event.as_ptr()) };
match status {
Status::SUCCESS => Ok(true),
Status::NOT_READY => Ok(false),
_ => Err(status.into()),
}
}
/// Removes `event` from any event group to which it belongs and closes it.
///
/// If `event` was registered with [`register_protocol_notify`], then the
/// corresponding registration will be removed. Calling this function within the
/// corresponding notify function is allowed.
///
/// # Errors
///
/// The specification does not list any errors, however implementations are
/// allowed to return an error if needed.
pub fn close_event(event: Event) -> Result {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
unsafe { (bt.close_event)(event.as_ptr()) }.to_result()
}
/// Sets the trigger for an event of type [`TIMER`].
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: `event` is not valid.
///
/// [`TIMER`]: EventType::TIMER
pub fn set_timer(event: &Event, trigger_time: TimerTrigger) -> Result {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let (ty, time) = match trigger_time {
TimerTrigger::Cancel => (0, 0),
TimerTrigger::Periodic(hundreds_ns) => (1, hundreds_ns),
TimerTrigger::Relative(hundreds_ns) => (2, hundreds_ns),
};
unsafe { (bt.set_timer)(event.as_ptr(), ty, time) }.to_result()
}
/// Stops execution until an event is signaled.
///
/// This function must be called at priority level [`Tpl::APPLICATION`].
///
/// The input [`Event`] slice is repeatedly iterated from first to last until
/// an event is signaled or an error is detected. The following checks are
/// performed on each event:
///
/// * If an event is of type [`NOTIFY_SIGNAL`], then a
/// [`Status::INVALID_PARAMETER`] error is returned with the index of the
/// event that caused the failure.
/// * If an event is in the signaled state, the signaled state is cleared
/// and the index of the event that was signaled is returned.
/// * If an event is not in the signaled state but does have a notification
/// function, the notification function is queued at the event's
/// notification task priority level. If the execution of the event's
/// notification function causes the event to be signaled, then the
/// signaled state is cleared and the index of the event that was signaled
/// is returned.
///
/// To wait for a specified time, a timer event must be included in `events`.
///
/// To check if an event is signaled without waiting, an already signaled
/// event can be used as the last event in the slice being checked, or the
/// [`check_event`] interface may be used.
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: `events` is empty, or one of the events of
/// of type [`NOTIFY_SIGNAL`].
/// * [`Status::UNSUPPORTED`]: the current TPL is not [`Tpl::APPLICATION`].
///
/// [`NOTIFY_SIGNAL`]: EventType::NOTIFY_SIGNAL
pub fn wait_for_event(events: &mut [Event]) -> Result<usize, Option<usize>> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let number_of_events = events.len();
let events: *mut uefi_raw::Event = events.as_mut_ptr().cast();
let mut index = 0;
unsafe { (bt.wait_for_event)(number_of_events, events, &mut index) }.to_result_with(
|| index,
|s| {
if s == Status::INVALID_PARAMETER {
Some(index)
} else {
None
}
},
)
}
/// Connect one or more drivers to a controller.
///
/// Usually one disconnects and then reconnects certain drivers
/// to make them rescan some state that changed, e.g. reconnecting
/// a block handle after your app modified disk partitions.
///
/// # Errors
///
/// * [`Status::NOT_FOUND`]: there are no driver-binding protocol instances
/// present in the system, or no drivers are connected to `controller`.
/// * [`Status::SECURITY_VIOLATION`]: the caller does not have permission to
/// start drivers associated with `controller`.
pub fn connect_controller(
controller: Handle,
driver_image: Option<Handle>,
remaining_device_path: Option<&DevicePath>,
recursive: bool,
) -> Result {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
unsafe {
(bt.connect_controller)(
controller.as_ptr(),
Handle::opt_to_ptr(driver_image),
remaining_device_path
.map(|dp| dp.as_ffi_ptr())
.unwrap_or(ptr::null())
.cast(),
recursive,
)
}
.to_result_with_err(|_| ())
}
/// Disconnect one or more drivers from a controller.
///
/// See also [`connect_controller`].
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: `driver_image` is set but does not manage
/// `controller`, or does not support the driver binding protocol, or one of
/// the handles is invalid.
/// * [`Status::OUT_OF_RESOURCES`]: not enough resources available to disconnect
/// drivers.
/// * [`Status::DEVICE_ERROR`]: the controller could not be disconnected due to
/// a device error.
pub fn disconnect_controller(
controller: Handle,
driver_image: Option<Handle>,
child: Option<Handle>,
) -> Result {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
unsafe {
(bt.disconnect_controller)(
controller.as_ptr(),
Handle::opt_to_ptr(driver_image),
Handle::opt_to_ptr(child),
)
}
.to_result_with_err(|_| ())
}
/// Installs a protocol interface on a device handle.
///
/// When a protocol interface is installed, firmware will call all functions
/// that have registered to wait for that interface to be installed.
///
/// If `handle` is `None`, a new handle will be created and returned.
///
/// # Safety
///
/// The caller is responsible for ensuring that they pass a valid `Guid` for `protocol`.
///
/// # Errors
///
/// * [`Status::OUT_OF_RESOURCES`]: failed to allocate a new handle.
/// * [`Status::INVALID_PARAMETER`]: this protocol is already installed on the handle.
pub unsafe fn install_protocol_interface(
handle: Option<Handle>,
protocol: &Guid,
interface: *const c_void,
) -> Result<Handle> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let mut handle = Handle::opt_to_ptr(handle);
((bt.install_protocol_interface)(
&mut handle,
protocol,
InterfaceType::NATIVE_INTERFACE,
interface,
))
.to_result_with_val(|| Handle::from_ptr(handle).unwrap())
}
/// Reinstalls a protocol interface on a device handle. `old_interface` is replaced with `new_interface`.
/// These interfaces may be the same, in which case the registered protocol notifications occur for the handle
/// without replacing the interface.
///
/// As with `install_protocol_interface`, any process that has registered to wait for the installation of
/// the interface is notified.
///
/// # Safety
///
/// The caller is responsible for ensuring that there are no references to the `old_interface` that is being
/// removed.
///
/// # Errors
///
/// * [`Status::NOT_FOUND`]: the old interface was not found on the handle.
/// * [`Status::ACCESS_DENIED`]: the old interface is still in use and cannot be uninstalled.
pub unsafe fn reinstall_protocol_interface(
handle: Handle,
protocol: &Guid,
old_interface: *const c_void,
new_interface: *const c_void,
) -> Result<()> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
(bt.reinstall_protocol_interface)(handle.as_ptr(), protocol, old_interface, new_interface)
.to_result()
}
/// Removes a protocol interface from a device handle.
///
/// # Safety
///
/// The caller is responsible for ensuring that there are no references to a protocol interface
/// that has been removed. Some protocols may not be able to be removed as there is no information
/// available regarding the references. This includes Console I/O, Block I/O, Disk I/o, and handles
/// to device protocols.
///
/// The caller is responsible for ensuring that they pass a valid `Guid` for `protocol`.
///
/// # Errors
///
/// * [`Status::NOT_FOUND`]: the interface was not found on the handle.
/// * [`Status::ACCESS_DENIED`]: the interface is still in use and cannot be uninstalled.
pub unsafe fn uninstall_protocol_interface(
handle: Handle,
protocol: &Guid,
interface: *const c_void,
) -> Result<()> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
(bt.uninstall_protocol_interface)(handle.as_ptr(), protocol, interface).to_result()
}
/// Registers `event` to be signaled whenever a protocol interface is registered for
/// `protocol` by [`install_protocol_interface`] or [`reinstall_protocol_interface`].
///
/// If successful, a [`SearchType::ByRegisterNotify`] is returned. This can be
/// used with [`locate_handle`] or [`locate_handle_buffer`] to identify the
/// newly (re)installed handles that support `protocol`.
///
/// Events can be unregistered from protocol interface notification by calling [`close_event`].
///
/// # Errors
///
/// * [`Status::OUT_OF_RESOURCES`]: the event could not be allocated.
pub fn register_protocol_notify(
protocol: &'static Guid,
event: &Event,
) -> Result<SearchType<'static>> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let mut key = ptr::null();
unsafe { (bt.register_protocol_notify)(protocol, event.as_ptr(), &mut key) }.to_result_with_val(
|| {
// OK to unwrap: key is non-null for Status::SUCCESS.
SearchType::ByRegisterNotify(ProtocolSearchKey(NonNull::new(key.cast_mut()).unwrap()))
},
)
}
/// Get the list of protocol interface [`Guids`][Guid] that are installed
/// on a [`Handle`].
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: `handle` is invalid.
/// * [`Status::OUT_OF_RESOURCES`]: out of memory.
pub fn protocols_per_handle(handle: Handle) -> Result<ProtocolsPerHandle> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let mut protocols = ptr::null_mut();
let mut count = 0;
unsafe { (bt.protocols_per_handle)(handle.as_ptr(), &mut protocols, &mut count) }
.to_result_with_val(|| ProtocolsPerHandle {
count,
protocols: NonNull::new(protocols)
.expect("protocols_per_handle must not return a null pointer"),
})
}
/// Locates the handle of a device on the device path that supports the specified protocol.
///
/// The `device_path` is updated to point at the remaining part of the [`DevicePath`] after
/// the part that matched the protocol. For example, it can be used with a device path
/// that contains a file path to strip off the file system portion of the device path,
/// leaving the file path and handle to the file system driver needed to access the file.
///
/// If the first node of `device_path` matches the protocol, the `device_path`
/// is advanced to the device path terminator node. If `device_path` is a
/// multi-instance device path, the function will operate on the first instance.
///
/// # Errors
///
/// * [`Status::NOT_FOUND`]: no matching handles.
pub fn locate_device_path<P: ProtocolPointer + ?Sized>(
device_path: &mut &DevicePath,
) -> Result<Handle> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let mut handle = ptr::null_mut();
let mut device_path_ptr: *const uefi_raw::protocol::device_path::DevicePathProtocol =
device_path.as_ffi_ptr().cast();
unsafe {
(bt.locate_device_path)(&P::GUID, &mut device_path_ptr, &mut handle).to_result_with_val(
|| {
*device_path = DevicePath::from_ffi_ptr(device_path_ptr.cast());
// OK to unwrap: handle is non-null for Status::SUCCESS.
Handle::from_ptr(handle).unwrap()
},
)
}
}
/// Enumerates all handles installed on the system which match a certain query.
///
/// # Errors
///
/// * [`Status::NOT_FOUND`]: no matching handles found.
/// * [`Status::BUFFER_TOO_SMALL`]: the buffer is not large enough. The required
/// size (in number of handles, not bytes) will be returned in the error data.
pub fn locate_handle<'buf>(
search_ty: SearchType,
buffer: &'buf mut [MaybeUninit<Handle>],
) -> Result<&'buf [Handle], Option<usize>> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
// Obtain the needed data from the parameters.
let (ty, guid, key) = match search_ty {
SearchType::AllHandles => (0, ptr::null(), ptr::null()),
SearchType::ByRegisterNotify(registration) => {
(1, ptr::null(), registration.0.as_ptr().cast_const())
}
SearchType::ByProtocol(guid) => (2, guid as *const Guid, ptr::null()),
};
let mut buffer_size = buffer.len() * mem::size_of::<Handle>();
let status =
unsafe { (bt.locate_handle)(ty, guid, key, &mut buffer_size, buffer.as_mut_ptr().cast()) };
let num_handles = buffer_size / mem::size_of::<Handle>();
match status {
Status::SUCCESS => {
let buffer = &buffer[..num_handles];
// SAFETY: the entries up to `num_handles` have been initialized.
let handles = unsafe { maybe_uninit_slice_assume_init_ref(buffer) };
Ok(handles)
}
Status::BUFFER_TOO_SMALL => Err(Error::new(status, Some(num_handles))),
_ => Err(Error::new(status, None)),
}
}
/// Returns an array of handles that support the requested protocol in a
/// pool-allocated buffer.
///
/// See [`SearchType`] for details of the available search operations.
///
/// # Errors
///
/// * [`Status::NOT_FOUND`]: no matching handles.
/// * [`Status::OUT_OF_RESOURCES`]: out of memory.
pub fn locate_handle_buffer(search_ty: SearchType) -> Result<HandleBuffer> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let (ty, guid, key) = match search_ty {
SearchType::AllHandles => (0, ptr::null(), ptr::null()),
SearchType::ByRegisterNotify(registration) => {
(1, ptr::null(), registration.0.as_ptr().cast_const())
}
SearchType::ByProtocol(guid) => (2, guid as *const _, ptr::null()),
};
let mut num_handles: usize = 0;
let mut buffer: *mut uefi_raw::Handle = ptr::null_mut();
unsafe { (bt.locate_handle_buffer)(ty, guid, key, &mut num_handles, &mut buffer) }
.to_result_with_val(|| HandleBuffer {
count: num_handles,
buffer: NonNull::new(buffer.cast())
.expect("locate_handle_buffer must not return a null pointer"),
})
}
/// Returns all the handles implementing a certain protocol.
///
/// # Errors
///
/// * [`Status::NOT_FOUND`]: no matching handles.
#[cfg(feature = "alloc")]
pub fn find_handles<P: ProtocolPointer + ?Sized>() -> Result<Vec<Handle>> {
// Search by protocol.
let search_type = SearchType::from_proto::<P>();
// Determine how much we need to allocate.
let num_handles = match locate_handle(search_type, &mut []) {
Err(err) => {
if err.status() == Status::BUFFER_TOO_SMALL {
err.data().expect("error data is missing")
} else {
return Err(err.to_err_without_payload());
}
}
// This should never happen: if no handles match the search then a
// `NOT_FOUND` error should be returned.
Ok(_) => panic!("locate_handle should not return success with empty buffer"),
};
// Allocate a large enough buffer without pointless initialization.
let mut handles = Vec::with_capacity(num_handles);
// Perform the search.
let num_handles = locate_handle(search_type, handles.spare_capacity_mut())
.discard_errdata()?
.len();
// Mark the returned number of elements as initialized.
unsafe {
handles.set_len(num_handles);
}
// Emit output, with warnings
Ok(handles)
}
/// Find an arbitrary handle that supports a particular [`Protocol`]. Returns
/// [`NOT_FOUND`] if no handles support the protocol.
///
/// This method is a convenient wrapper around [`locate_handle_buffer`] for
/// getting just one handle. This is useful when you don't care which handle the
/// protocol is opened on. For example, [`DevicePathToText`] isn't tied to a
/// particular device, so only a single handle is expected to exist.
///
/// [`NOT_FOUND`]: Status::NOT_FOUND
/// [`DevicePathToText`]: uefi::proto::device_path::text::DevicePathToText
///
/// # Example
///
/// ```
/// use uefi::proto::device_path::text::DevicePathToText;
/// use uefi::{boot, Handle};
/// # use uefi::Result;
///
/// # fn get_fake_val<T>() -> T { todo!() }
/// # fn test() -> Result {
/// # let image_handle: Handle = get_fake_val();
/// let handle = boot::get_handle_for_protocol::<DevicePathToText>()?;
/// let device_path_to_text = boot::open_protocol_exclusive::<DevicePathToText>(handle)?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// * [`Status::NOT_FOUND`]: no matching handle.
/// * [`Status::OUT_OF_RESOURCES`]: out of memory.
pub fn get_handle_for_protocol<P: ProtocolPointer + ?Sized>() -> Result<Handle> {
locate_handle_buffer(SearchType::ByProtocol(&P::GUID))?
.first()
.cloned()
.ok_or_else(|| Status::NOT_FOUND.into())
}
/// Opens a protocol interface for a handle.
///
/// See also [`open_protocol_exclusive`], which provides a safe subset of this
/// functionality.
///
/// This function attempts to get the protocol implementation of a handle, based
/// on the [protocol GUID].
///
/// See [`OpenProtocolParams`] and [`OpenProtocolAttributes`] for details of the
/// input parameters.
///
/// If successful, a [`ScopedProtocol`] is returned that will automatically
/// close the protocol interface when dropped.
///
/// [protocol GUID]: uefi::data_types::Identify::GUID
///
/// # Safety
///
/// This function is unsafe because it can be used to open a protocol in ways
/// that don't get tracked by the UEFI implementation. This could allow the
/// protocol to be removed from a handle, or for the handle to be deleted
/// entirely, while a reference to the protocol is still active. The caller is
/// responsible for ensuring that the handle and protocol remain valid until the
/// `ScopedProtocol` is dropped.
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: an invalid combination of `params` and
/// `attributes` was provided.
/// * [`Status::UNSUPPORTED`]: the handle does not support the protocol.
/// * [`Status::ACCESS_DENIED`] or [`Status::ALREADY_STARTED`]: the protocol is
/// already open in a way that is incompatible with the new request.
pub unsafe fn open_protocol<P: ProtocolPointer + ?Sized>(
params: OpenProtocolParams,
attributes: OpenProtocolAttributes,
) -> Result<ScopedProtocol<P>> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let mut interface = ptr::null_mut();
(bt.open_protocol)(
params.handle.as_ptr(),
&P::GUID,
&mut interface,
params.agent.as_ptr(),
Handle::opt_to_ptr(params.controller),
attributes as u32,
)
.to_result_with_val(|| {
let interface = if interface.is_null() {
None
} else {
NonNull::new(P::mut_ptr_from_ffi(interface))
};
ScopedProtocol {
interface,
open_params: params,
}
})
}
/// Opens a protocol interface for a handle in exclusive mode.
///
/// If successful, a [`ScopedProtocol`] is returned that will automatically
/// close the protocol interface when dropped.
///
/// # Errors
///
/// * [`Status::UNSUPPORTED`]: the handle does not support the protocol.
/// * [`Status::ACCESS_DENIED`]: the protocol is already open in a way that is
/// incompatible with the new request.
pub fn open_protocol_exclusive<P: ProtocolPointer + ?Sized>(
handle: Handle,
) -> Result<ScopedProtocol<P>> {
// Safety: opening in exclusive mode with the correct agent
// handle set ensures that the protocol cannot be modified or
// removed while it is open, so this usage is safe.
unsafe {
open_protocol::<P>(
OpenProtocolParams {
handle,
agent: image_handle(),
controller: None,
},
OpenProtocolAttributes::Exclusive,
)
}
}
/// Tests whether a handle supports a protocol.
///
/// Returns `Ok(true)` if the handle supports the protocol, `Ok(false)` if not.
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: one of the handles in `params` is invalid.
pub fn test_protocol<P: ProtocolPointer + ?Sized>(params: OpenProtocolParams) -> Result<bool> {
const TEST_PROTOCOL: u32 = 0x04;
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let mut interface = ptr::null_mut();
let status = unsafe {
(bt.open_protocol)(
params.handle.as_ptr(),
&P::GUID,
&mut interface,
params.agent.as_ptr(),
Handle::opt_to_ptr(params.controller),
TEST_PROTOCOL,
)
};
match status {
Status::SUCCESS => Ok(true),
Status::UNSUPPORTED => Ok(false),
_ => Err(Error::from(status)),
}
}
/// Loads a UEFI image into memory and return a [`Handle`] to the image.
///
/// There are two ways to load the image: by copying raw image data
/// from a source buffer, or by loading the image via the
/// [`SimpleFileSystem`] protocol. See [`LoadImageSource`] for more
/// details of the `source` parameter.
///
/// The `parent_image_handle` is used to initialize the
/// `parent_handle` field of the [`LoadedImage`] protocol for the
/// image.
///
/// If the image is successfully loaded, a [`Handle`] supporting the
/// [`LoadedImage`] and [`LoadedImageDevicePath`] protocols is returned. The
/// image can be started with [`start_image`] and unloaded with
/// [`unload_image`].
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: `source` contains an invalid value.
/// * [`Status::UNSUPPORTED`]: the image type is not supported.
/// * [`Status::OUT_OF_RESOURCES`]: insufficient resources to load the image.
/// * [`Status::LOAD_ERROR`]: the image is invalid.
/// * [`Status::DEVICE_ERROR`]: failed to load image due to a read error.
/// * [`Status::ACCESS_DENIED`]: failed to load image due to a security policy.
/// * [`Status::SECURITY_VIOLATION`]: a security policy specifies that the image
/// should not be started.
pub fn load_image(parent_image_handle: Handle, source: LoadImageSource) -> Result<Handle> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let (boot_policy, device_path, source_buffer, source_size) = source.to_ffi_params();
let mut image_handle = ptr::null_mut();
unsafe {
(bt.load_image)(
boot_policy.into(),
parent_image_handle.as_ptr(),
device_path.cast(),
source_buffer,
source_size,
&mut image_handle,
)
.to_result_with_val(
// OK to unwrap: image handle is non-null for Status::SUCCESS.
|| Handle::from_ptr(image_handle).unwrap(),
)
}
}
/// Unloads a UEFI image.
///
/// # Errors
///
/// * [`Status::UNSUPPORTED`]: the image has been started, and does not support unload.
/// * [`Status::INVALID_PARAMETER`]: `image_handle` is not valid.
pub fn unload_image(image_handle: Handle) -> Result {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
unsafe { (bt.unload_image)(image_handle.as_ptr()) }.to_result()
}
/// Transfers control to a loaded image's entry point.
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: `image_handle` is not valid, or the image
/// has already been initialized with `start_image`.
/// * [`Status::SECURITY_VIOLATION`]: a security policy specifies that the image
/// should not be started.
pub fn start_image(image_handle: Handle) -> Result {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
// TODO: implement returning exit data to the caller.
let mut exit_data_size: usize = 0;
let mut exit_data: *mut u16 = ptr::null_mut();
unsafe {
(bt.start_image)(image_handle.as_ptr(), &mut exit_data_size, &mut exit_data).to_result()
}
}
/// Exits the UEFI application and returns control to the UEFI component
/// that started the UEFI application.
///
/// # Safety
///
/// The caller must ensure that resources owned by the application are properly
/// cleaned up.
///
/// Note that event callbacks installed by the application are not automatically
/// uninstalled. If such a callback is invoked after exiting the application,
/// the function's code may no longer be loaded in memory, leading to a crash or
/// other unexpected behavior.
pub unsafe fn exit(
image_handle: Handle,
exit_status: Status,
exit_data_size: usize,
exit_data: *mut Char16,
) -> ! {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
(bt.exit)(
image_handle.as_ptr(),
exit_status,
exit_data_size,
exit_data.cast(),
)
}
/// Get the current memory map and exit boot services.
unsafe fn get_memory_map_and_exit_boot_services(buf: &mut [u8]) -> Result<MemoryMapMeta> {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
// Get the memory map.
let memory_map = get_memory_map(buf)?;
// Try to exit boot services using the memory map key. Note that after
// the first call to `exit_boot_services`, there are restrictions on
// what boot services functions can be called. In UEFI 2.8 and earlier,
// only `get_memory_map` and `exit_boot_services` are allowed. Starting
// in UEFI 2.9 other memory allocation functions may also be called.
(bt.exit_boot_services)(image_handle().as_ptr(), memory_map.map_key.0)
.to_result_with_val(|| memory_map)
}
/// Exit UEFI boot services.
///
/// After this function completes, UEFI hands over control of the hardware
/// to the executing OS loader, which implies that the UEFI boot services
/// are shut down and cannot be used anymore. Only UEFI configuration tables
/// and run-time services can be used.
///
/// The memory map at the time of exiting boot services returned. The map is
/// backed by a pool allocation of the given `memory_type`. Since the boot
/// services function to free that memory is no longer available after calling
/// `exit_boot_services`, the allocation will not be freed on drop.
///
/// 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 (see the [`helpers`] module),
/// invoking this function will automatically disable them. If the
/// `global_allocator` feature is enabled, attempting to use the allocator
/// after exiting boot services will panic.
///
/// # Safety
///
/// The caller is responsible for ensuring that no references to
/// boot-services data remain. A non-exhaustive list of resources to check:
///
/// * All protocols will be invalid after exiting boot services. This
/// includes the [`Output`] protocols attached to stdout/stderr. The
/// caller must ensure that no protocol references remain.
/// * The pool allocator is not usable after exiting boot services. Types
/// such as [`PoolString`] which call [`free_pool`] on drop
/// must be cleaned up before calling `exit_boot_services`, or leaked to
/// avoid drop ever being called.
/// * All data in the memory map marked as
/// [`MemoryType::BOOT_SERVICES_CODE`] and
/// [`MemoryType::BOOT_SERVICES_DATA`] will become free memory.
///
/// # Errors
///
/// This function will fail if it is unable to allocate memory for
/// the memory map, if it fails to retrieve the memory map, or if
/// exiting boot services fails (with up to one retry).
///
/// 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.
///
/// [`helpers`]: crate::helpers
/// [`Output`]: crate::proto::console::text::Output
/// [`PoolString`]: crate::proto::device_path::text::PoolString
#[must_use]
pub unsafe fn exit_boot_services(memory_type: MemoryType) -> MemoryMapOwned {
crate::helpers::exit();
let mut buf = MemoryMapBackingMemory::new(memory_type).expect("Failed to allocate memory");
// Calling `exit_boot_services` can fail if the memory map key is not
// current. Retry a second time if that occurs. This matches the
// behavior of the Linux kernel:
// https://github.com/torvalds/linux/blob/e544a0743/drivers/firmware/efi/libstub/efi-stub-helper.c#L375
let mut status = Status::ABORTED;
for _ in 0..2 {
match unsafe { get_memory_map_and_exit_boot_services(buf.as_mut_slice()) } {
Ok(memory_map) => {
return MemoryMapOwned::from_initialized_mem(buf, memory_map);
}
Err(err) => {
log::error!("Error retrieving the memory map for exiting the boot services");
status = err.status()
}
}
}
// Failed to exit boot services.
log::warn!("Resetting the machine");
runtime::reset(ResetType::COLD, status, None);
}
/// Adds, updates, or removes a configuration table entry
/// from the EFI System Table.
///
/// # Safety
///
/// When installing or updating a configuration table, the data pointed to by
/// `table_ptr` must be a pool allocation of type
/// [`RUNTIME_SERVICES_DATA`]. Once this table has been installed, the caller
/// should not modify or free the data.
///
/// [`RUNTIME_SERVICES_DATA`]: MemoryType::RUNTIME_SERVICES_DATA
///
/// # Errors
///
/// * [`Status::NOT_FOUND`]: tried to delete a nonexistent entry.
/// * [`Status::OUT_OF_RESOURCES`]: out of memory.
pub unsafe fn install_configuration_table(
guid_entry: &'static Guid,
table_ptr: *const c_void,
) -> Result {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
(bt.install_configuration_table)(guid_entry, table_ptr).to_result()
}
/// Sets the watchdog timer.
///
/// UEFI will start a 5-minute countdown after an UEFI image is loaded. The
/// image must either successfully load an OS and exit boot services in that
/// time, or disable the watchdog.
///
/// Otherwise, the firmware will log the event using the provided numeric
/// code and data, then reset the system.
///
/// This function allows you to change the watchdog timer's timeout to a
/// certain amount of seconds or to disable the watchdog entirely. It also
/// allows you to change what will be logged when the timer expires.
///
/// The watchdog codes from 0 to 0xffff (65535) are reserved for internal
/// firmware use. Higher values can be used freely by applications.
///
/// If provided, the watchdog data must be a null-terminated string optionally
/// followed by other binary data.
///
/// # Errors
///
/// * [`Status::INVALID_PARAMETER`]: `watchdog_code` is invalid.
/// * [`Status::UNSUPPORTED`]: the system does not have a watchdog timer.
/// * [`Status::DEVICE_ERROR`]: the watchdog timer could not be set due to a
/// hardware error.
pub fn set_watchdog_timer(
timeout_in_seconds: usize,
watchdog_code: u64,
data: Option<&mut [u16]>,
) -> Result {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let (data_len, data) = data
.map(|d| {
assert!(
d.contains(&0),
"Watchdog data must start with a null-terminated string"
);
(d.len(), d.as_mut_ptr())
})
.unwrap_or((0, ptr::null_mut()));
unsafe { (bt.set_watchdog_timer)(timeout_in_seconds, watchdog_code, data_len, data) }
.to_result()
}
/// Stalls execution for the given number of microseconds.
pub fn stall(microseconds: usize) {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
unsafe {
// No error conditions are defined in the spec for this function, so
// ignore the status.
let _ = (bt.stall)(microseconds);
}
}
/// Retrieves a [`SimpleFileSystem`] protocol associated with the device the given
/// image was loaded from.
///
/// # Errors
///
/// This function can return errors from [`open_protocol_exclusive`] and
/// [`locate_device_path`]. See those functions for more details.
///
/// * [`Status::INVALID_PARAMETER`]
/// * [`Status::UNSUPPORTED`]
/// * [`Status::ACCESS_DENIED`]
/// * [`Status::ALREADY_STARTED`]
/// * [`Status::NOT_FOUND`]
pub fn get_image_file_system(image_handle: Handle) -> Result<ScopedProtocol<SimpleFileSystem>> {
let loaded_image = open_protocol_exclusive::<LoadedImage>(image_handle)?;
let device_handle = loaded_image
.device()
.ok_or(Error::new(Status::UNSUPPORTED, ()))?;
let device_path = open_protocol_exclusive::<DevicePath>(device_handle)?;
let device_handle = locate_device_path::<SimpleFileSystem>(&mut &*device_path)?;
open_protocol_exclusive(device_handle)
}
/// Protocol interface [`Guids`][Guid] that are installed on a [`Handle`] as
/// returned by [`protocols_per_handle`].
#[derive(Debug)]
pub struct ProtocolsPerHandle {
protocols: NonNull<*const Guid>,
count: usize,
}
impl Drop for ProtocolsPerHandle {
fn drop(&mut self) {
let _ = unsafe { free_pool(self.protocols.cast::<u8>()) };
}
}
impl Deref for ProtocolsPerHandle {
type Target = [&'static Guid];
fn deref(&self) -> &Self::Target {
let ptr: *const &'static Guid = self.protocols.as_ptr().cast();
// SAFETY:
//
// * The firmware is assumed to provide a correctly-aligned pointer and
// array length.
// * The firmware is assumed to provide valid GUID pointers.
// * Protocol GUIDs should be constants or statics, so a 'static
// lifetime (of the individual pointers, not the overall slice) can be
// assumed.
unsafe { slice::from_raw_parts(ptr, self.count) }
}
}
/// A buffer returned by [`locate_handle_buffer`] that contains an array of
/// [`Handle`]s that support the requested protocol.
#[derive(Debug, Eq, PartialEq)]
pub struct HandleBuffer {
count: usize,
buffer: NonNull<Handle>,
}
impl Drop for HandleBuffer {
fn drop(&mut self) {
let _ = unsafe { free_pool(self.buffer.cast::<u8>()) };
}
}
impl Deref for HandleBuffer {
type Target = [Handle];
fn deref(&self) -> &Self::Target {
unsafe { slice::from_raw_parts(self.buffer.as_ptr(), self.count) }
}
}
/// An open protocol interface. Automatically closes the protocol
/// interface on drop.
///
/// Most protocols have interface data associated with them. `ScopedProtocol`
/// implements [`Deref`] and [`DerefMut`] to access this data. A few protocols
/// (such as [`DevicePath`] and [`LoadedImageDevicePath`]) may be installed with
/// null interface data, in which case [`Deref`] and [`DerefMut`] will
/// panic. The [`get`] and [`get_mut`] methods may be used to access the
/// optional interface data without panicking.
///
/// [`DevicePath`]: crate::proto::device_path::DevicePath
/// [`LoadedImageDevicePath`]: crate::proto::device_path::LoadedImageDevicePath
/// [`get`]: ScopedProtocol::get
/// [`get_mut`]: ScopedProtocol::get_mut
#[derive(Debug)]
pub struct ScopedProtocol<P: Protocol + ?Sized> {
/// The protocol interface.
interface: Option<NonNull<P>>,
open_params: OpenProtocolParams,
}
impl<P: Protocol + ?Sized> Drop for ScopedProtocol<P> {
fn drop(&mut self) {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
let status = unsafe {
(bt.close_protocol)(
self.open_params.handle.as_ptr(),
&P::GUID,
self.open_params.agent.as_ptr(),
Handle::opt_to_ptr(self.open_params.controller),
)
};
// All of the error cases for close_protocol boil down to
// calling it with a different set of parameters than what was
// passed to open_protocol. The public API prevents such errors,
// and the error can't be propagated out of drop anyway, so just
// assert success.
assert_eq!(status, Status::SUCCESS);
}
}
impl<P: Protocol + ?Sized> Deref for ScopedProtocol<P> {
type Target = P;
#[track_caller]
fn deref(&self) -> &Self::Target {
unsafe { self.interface.unwrap().as_ref() }
}
}
impl<P: Protocol + ?Sized> DerefMut for ScopedProtocol<P> {
#[track_caller]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.interface.unwrap().as_mut() }
}
}
impl<P: Protocol + ?Sized> ScopedProtocol<P> {
/// Get the protocol interface data, or `None` if the open protocol's
/// interface is null.
#[must_use]
pub fn get(&self) -> Option<&P> {
self.interface.map(|p| unsafe { p.as_ref() })
}
/// Get the protocol interface data, or `None` if the open protocol's
/// interface is null.
#[must_use]
pub fn get_mut(&mut self) -> Option<&mut P> {
self.interface.map(|mut p| unsafe { p.as_mut() })
}
}
/// RAII guard for task priority level changes.
///
/// Will automatically restore the former task priority level when dropped.
#[derive(Debug)]
pub struct TplGuard {
old_tpl: Tpl,
}
impl Drop for TplGuard {
fn drop(&mut self) {
let bt = boot_services_raw_panicking();
let bt = unsafe { bt.as_ref() };
unsafe {
(bt.restore_tpl)(self.old_tpl);
}
}
}
// OpenProtocolAttributes is safe to model as a regular enum because it
// is only used as an input. The attributes are bitflags, but all valid
// combinations are listed in the spec and only ByDriver and Exclusive
// can actually be combined.
//
// Some values intentionally excluded:
//
// ByHandleProtocol (0x01) excluded because it is only intended to be
// used in an implementation of `HandleProtocol`.
//
// TestProtocol (0x04) excluded because it doesn't actually open the
// protocol, just tests if it's present on the handle. Since that
// changes the interface significantly, that's exposed as a separate
// method: `test_protocol`.
/// Attributes for [`open_protocol`].
#[repr(u32)]
#[derive(Debug)]
pub enum OpenProtocolAttributes {
/// Used by drivers to get a protocol interface for a handle. The
/// driver will not be informed if the interface is uninstalled or
/// reinstalled.
GetProtocol = 0x02,
/// Used by bus drivers to show that a protocol is being used by one
/// of the child controllers of the bus.
ByChildController = 0x08,
/// Used by a driver to gain access to a protocol interface. When
/// this mode is used, the driver's `Stop` function will be called
/// if the protocol interface is reinstalled or uninstalled. Once a
/// protocol interface is opened with this attribute, no other
/// drivers will be allowed to open the same protocol interface with
/// the `ByDriver` attribute.
ByDriver = 0x10,
/// Used by a driver to gain exclusive access to a protocol
/// interface. If any other drivers have the protocol interface
/// opened with an attribute of `ByDriver`, then an attempt will be
/// made to remove them with `DisconnectController`.
ByDriverExclusive = 0x30,
/// Used by applications to gain exclusive access to a protocol
/// interface. If any drivers have the protocol opened with an
/// attribute of `ByDriver`, then an attempt will be made to remove
/// them by calling the driver's `Stop` function.
Exclusive = 0x20,
}
/// Parameters passed to [`open_protocol`].
#[derive(Debug)]
pub struct OpenProtocolParams {
/// The handle for the protocol to open.
pub handle: Handle,
/// The handle of the calling agent. For drivers, this is the handle
/// containing the `EFI_DRIVER_BINDING_PROTOCOL` instance. For
/// applications, this is the image handle.
pub agent: Handle,
/// For drivers, this is the controller handle that requires the
/// protocol interface. For applications this should be set to
/// `None`.
pub controller: Option<Handle>,
}
/// Used as a parameter of [`load_image`] to provide the image source.
#[derive(Debug)]
pub enum LoadImageSource<'a> {
/// Load an image from a buffer. The data will be copied from the
/// buffer, so the input reference doesn't need to remain valid
/// after the image is loaded.
FromBuffer {
/// Raw image data.
buffer: &'a [u8],
/// If set, this path will be added as the file path of the
/// loaded image. This is not required to load the image, but
/// may be used by the image itself to load other resources
/// relative to the image's path.
file_path: Option<&'a DevicePath>,
},
/// Load an image via the [`SimpleFileSystem`] protocol. If there is
/// no instance of that protocol associated with the path then the
/// behavior depends on [`BootPolicy`]. If [`BootPolicy::BootSelection`],
/// attempt to load via the [`LoadFile`] protocol. If
/// [`BootPolicy::ExactMatch`], attempt to load via the [`LoadFile2`]
/// protocol, then fall back to [`LoadFile`].
///
/// [`LoadFile`]: crate::proto::media::load_file::LoadFile
/// [`LoadFile2`]: crate::proto::media::load_file::LoadFile2
FromDevicePath {
/// The full device path from which to load the image.
///
/// The provided path should be a full device path and not just the
/// file path portion of it. So for example, it must be (the binary
/// representation)
/// `PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0xFFFF,0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\\EFI\\BOOT\\BOOTX64.EFI`
/// and not just `\\EFI\\BOOT\\BOOTX64.EFI`.
device_path: &'a DevicePath,
/// The [`BootPolicy`] to use.
boot_policy: BootPolicy,
},
}
impl<'a> LoadImageSource<'a> {
/// Returns the raw FFI parameters for `load_image`.
#[must_use]
pub(crate) fn to_ffi_params(
&self,
) -> (
BootPolicy,
*const FfiDevicePath,
*const u8, /* buffer */
usize, /* buffer length */
) {
let boot_policy;
let device_path;
let source_buffer;
let source_size;
match self {
LoadImageSource::FromBuffer { buffer, file_path } => {
// Boot policy is ignored when loading from source buffer.
boot_policy = BootPolicy::default();
device_path = file_path.map(|p| p.as_ffi_ptr()).unwrap_or(ptr::null());
source_buffer = buffer.as_ptr();
source_size = buffer.len();
}
LoadImageSource::FromDevicePath {
device_path: d_path,
boot_policy: b_policy,
} => {
boot_policy = *b_policy;
device_path = d_path.as_ffi_ptr();
source_buffer = ptr::null();
source_size = 0;
}
};
(boot_policy, device_path, source_buffer, source_size)
}
}
/// Type of allocation to perform.
#[derive(Debug, Copy, Clone)]
pub enum AllocateType {
/// Allocate any possible pages.
AnyPages,
/// Allocate pages at any address below the given address.
MaxAddress(PhysicalAddress),
/// Allocate pages at the specified address.
Address(PhysicalAddress),
}
/// The type of handle search to perform.
#[derive(Debug, Copy, Clone)]
pub enum SearchType<'guid> {
/// Return all handles present on the system.
AllHandles,
/// Returns all handles supporting a certain protocol, specified by its GUID.
///
/// If the protocol implements the `Protocol` interface,
/// you can use the `from_proto` function to construct a new `SearchType`.
ByProtocol(&'guid Guid),
/// Return all handles that implement a protocol when an interface for that protocol
/// is (re)installed.
ByRegisterNotify(ProtocolSearchKey),
}
impl<'guid> SearchType<'guid> {
/// Constructs a new search type for a specified protocol.
#[must_use]
pub const fn from_proto<P: ProtocolPointer + ?Sized>() -> Self {
SearchType::ByProtocol(&P::GUID)
}
}
/// Event notification callback type.
pub type EventNotifyFn = unsafe extern "efiapi" fn(event: Event, context: Option<NonNull<c_void>>);
/// Timer events manipulation.
#[derive(Debug)]
pub enum TimerTrigger {
/// Cancel event's timer
Cancel,
/// The event is to be signaled periodically.
/// Parameter is the period in 100ns units.
/// Delay of 0 will be signalled on every timer tick.
Periodic(u64),
/// The event is to be signaled once in 100ns units.
/// Parameter is the delay in 100ns units.
/// Delay of 0 will be signalled on next timer tick.
Relative(u64),
}
/// Opaque pointer returned by [`register_protocol_notify`] to be used
/// with [`locate_handle`] via [`SearchType::ByRegisterNotify`].
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct ProtocolSearchKey(pub(crate) NonNull<c_void>);