| //! 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>); |