Skip to content

boot: Add freestanding version of open_protocol #1270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions uefi-test-runner/src/boot/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use uefi::table::boot::{
Tpl,
};
use uefi::table::{Boot, SystemTable};
use uefi::{guid, Event, Guid, Identify};
use uefi::{boot, guid, Event, Guid, Identify};

pub fn test(st: &SystemTable<Boot>) {
let bt = st.boot_services();
Expand Down Expand Up @@ -164,16 +164,15 @@ fn test_uninstall_protocol_interface(bt: &BootServices) {
// pointer. Open the protocol to get that pointer, making sure to drop
// the `ScopedProtocol` _before_ uninstalling the protocol interface.
let interface_ptr: *mut TestProtocol = {
let mut sp = bt
.open_protocol::<TestProtocol>(
OpenProtocolParams {
handle,
agent: bt.image_handle(),
controller: None,
},
OpenProtocolAttributes::GetProtocol,
)
.unwrap();
let mut sp = boot::open_protocol::<TestProtocol>(
OpenProtocolParams {
handle,
agent: bt.image_handle(),
controller: None,
},
OpenProtocolAttributes::GetProtocol,
)
.unwrap();
assert_eq!(sp.data, 123);
&mut *sp
};
Expand Down
139 changes: 133 additions & 6 deletions uefi/src/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@
//! These functions will panic if called after exiting boot services.

use crate::data_types::PhysicalAddress;
use crate::proto::{Protocol, ProtocolPointer};
use core::ffi::c_void;
use core::ops::Deref;
use core::ops::{Deref, DerefMut};
use core::ptr::{self, NonNull};
use core::slice;
use core::sync::atomic::{AtomicPtr, Ordering};
use uefi::{table, Handle, Result, StatusExt};
use uefi::{table, Handle, Result, Status, StatusExt};

#[cfg(doc)]
use uefi::Status;

pub use uefi::table::boot::{AllocateType, SearchType};
pub use uefi::table::boot::{AllocateType, OpenProtocolAttributes, OpenProtocolParams, SearchType};
pub use uefi_raw::table::boot::MemoryType;

/// Global image handle. This is only set by [`set_image_handle`], and it is
Expand Down Expand Up @@ -162,6 +160,60 @@ pub fn locate_handle_buffer(search_ty: SearchType) -> Result<HandleBuffer> {
})
}

/// 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(|| ScopedProtocol {
interface: NonNull::new(P::mut_ptr_from_ffi(interface)),
open_params: params,
})
}

/// A buffer returned by [`locate_handle_buffer`] that contains an array of
/// [`Handle`]s that support the requested protocol.
#[derive(Debug, Eq, PartialEq)]
Expand All @@ -183,3 +235,78 @@ impl Deref for HandleBuffer {
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() })
}
}
14 changes: 7 additions & 7 deletions uefi/src/table/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1136,13 +1136,13 @@ impl BootServices {
///
/// # 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.
/// 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, and the caller must ensure
/// that there is never more than one mutable reference to the protocol.
///
/// [`open_protocol`]: BootServices::open_protocol
/// [`open_protocol_exclusive`]: BootServices::open_protocol_exclusive
Expand Down