blob: ba53ed88f3765b28dbd664f3f06a3848cf395a49 [file] [log] [blame]
//! Contains most of the shared UEFI specific stuff. Some of this might be moved to `std::os::uefi`
//! if needed but no point in adding extra public API when there is not Std support for UEFI in the
//! first place
//!
//! Some Nomenclature
//! * Protocol:
//! - Protocols serve to enable communication between separately built modules, including drivers.
//! - Every protocol has a GUID associated with it. The GUID serves as the name for the protocol.
//! - Protocols are produced and consumed.
//! - More information about protocols can be found [here](https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/3_foundation/36_protocols_and_handles)
use r_efi::efi::{self, Guid};
use crate::mem::{size_of, MaybeUninit};
use crate::os::uefi;
use crate::ptr::NonNull;
use crate::{
io::{self, const_io_error},
os::uefi::env::boot_services,
};
const BOOT_SERVICES_UNAVAILABLE: io::Error =
const_io_error!(io::ErrorKind::Other, "Boot Services are no longer available");
/// Locate Handles with a particular Protocol GUID
/// Implemented using `EFI_BOOT_SERVICES.LocateHandles()`
///
/// Returns an array of [Handles](r_efi::efi::Handle) that support a specified protocol.
pub(crate) fn locate_handles(mut guid: Guid) -> io::Result<Vec<NonNull<crate::ffi::c_void>>> {
fn inner(
guid: &mut Guid,
boot_services: NonNull<r_efi::efi::BootServices>,
buf_size: &mut usize,
buf: *mut r_efi::efi::Handle,
) -> io::Result<()> {
let r = unsafe {
((*boot_services.as_ptr()).locate_handle)(
r_efi::efi::BY_PROTOCOL,
guid,
crate::ptr::null_mut(),
buf_size,
buf,
)
};
if r.is_error() { Err(crate::io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) }
}
let boot_services = boot_services().ok_or(BOOT_SERVICES_UNAVAILABLE)?.cast();
let mut buf_len = 0usize;
// This should always fail since the size of buffer is 0. This call should update the buf_len
// variable with the required buffer length
match inner(&mut guid, boot_services, &mut buf_len, crate::ptr::null_mut()) {
Ok(()) => unreachable!(),
Err(e) => match e.kind() {
io::ErrorKind::FileTooLarge => {}
_ => return Err(e),
},
}
// The returned buf_len is in bytes
assert_eq!(buf_len % size_of::<r_efi::efi::Handle>(), 0);
let num_of_handles = buf_len / size_of::<r_efi::efi::Handle>();
let mut buf: Vec<r_efi::efi::Handle> = Vec::with_capacity(num_of_handles);
match inner(&mut guid, boot_services, &mut buf_len, buf.as_mut_ptr()) {
Ok(()) => {
// This is safe because the call will succeed only if buf_len >= required length.
// Also, on success, the `buf_len` is updated with the size of bufferv (in bytes) written
unsafe { buf.set_len(num_of_handles) };
Ok(buf.into_iter().filter_map(|x| NonNull::new(x)).collect())
}
Err(e) => Err(e),
}
}
/// Open Protocol on a handle.
/// Internally just a call to `EFI_BOOT_SERVICES.OpenProtocol()`.
///
/// Queries a handle to determine if it supports a specified protocol. If the protocol is
/// supported by the handle, it opens the protocol on behalf of the calling agent.
pub(crate) fn open_protocol<T>(
handle: NonNull<crate::ffi::c_void>,
mut protocol_guid: Guid,
) -> io::Result<NonNull<T>> {
let boot_services: NonNull<efi::BootServices> =
boot_services().ok_or(BOOT_SERVICES_UNAVAILABLE)?.cast();
let system_handle = uefi::env::image_handle();
let mut protocol: MaybeUninit<*mut T> = MaybeUninit::uninit();
let r = unsafe {
((*boot_services.as_ptr()).open_protocol)(
handle.as_ptr(),
&mut protocol_guid,
protocol.as_mut_ptr().cast(),
system_handle.as_ptr(),
crate::ptr::null_mut(),
r_efi::system::OPEN_PROTOCOL_GET_PROTOCOL,
)
};
if r.is_error() {
Err(crate::io::Error::from_raw_os_error(r.as_usize()))
} else {
NonNull::new(unsafe { protocol.assume_init() })
.ok_or(const_io_error!(io::ErrorKind::Other, "null protocol"))
}
}
pub(crate) fn create_event(
signal: u32,
tpl: efi::Tpl,
handler: Option<efi::EventNotify>,
context: *mut crate::ffi::c_void,
) -> io::Result<NonNull<crate::ffi::c_void>> {
let boot_services: NonNull<efi::BootServices> =
boot_services().ok_or(BOOT_SERVICES_UNAVAILABLE)?.cast();
let mut event: r_efi::efi::Event = crate::ptr::null_mut();
let r = unsafe {
let create_event = (*boot_services.as_ptr()).create_event;
(create_event)(signal, tpl, handler, context, &mut event)
};
if r.is_error() {
Err(crate::io::Error::from_raw_os_error(r.as_usize()))
} else {
NonNull::new(event).ok_or(const_io_error!(io::ErrorKind::Other, "null protocol"))
}
}
/// # SAFETY
/// - The supplied event must be valid
pub(crate) unsafe fn close_event(evt: NonNull<crate::ffi::c_void>) -> io::Result<()> {
let boot_services: NonNull<efi::BootServices> =
boot_services().ok_or(BOOT_SERVICES_UNAVAILABLE)?.cast();
let r = unsafe {
let close_event = (*boot_services.as_ptr()).close_event;
(close_event)(evt.as_ptr())
};
if r.is_error() { Err(crate::io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) }
}
/// Get the Protocol for current system handle.
/// Note: Some protocols need to be manually freed. It is the callers responsibility to do so.
pub(crate) fn image_handle_protocol<T>(protocol_guid: Guid) -> Option<NonNull<T>> {
let system_handle = uefi::env::try_image_handle()?;
open_protocol(system_handle, protocol_guid).ok()
}
/// Get RuntimeServices
pub(crate) fn runtime_services() -> Option<NonNull<r_efi::efi::RuntimeServices>> {
let system_table: NonNull<r_efi::efi::SystemTable> =
crate::os::uefi::env::try_system_table()?.cast();
let runtime_services = unsafe { (*system_table.as_ptr()).runtime_services };
NonNull::new(runtime_services)
}