From 60c6b9db7ed35b86fe6c11d4557360a2168ef0b2 Mon Sep 17 00:00:00 2001 From: Nicholas Bishop Date: Mon, 29 Jul 2024 14:46:15 -0400 Subject: [PATCH] boot: Add freestanding version of open_protocol This comes with its own version of the `ScopedProtocol` struct. This one has no `BootServices` reference and no lifetime param. For test coverage, updated `test_uninstall_protocol_interface` to use `boot::open_protocol` instead of the `BootServices` method. The `BootServices` method still has plenty of coverage in the test runner since `BootServices::open_protocol_exclusive` calls `BootServices::open_protocol`, and the former is called in lots of places. --- uefi-test-runner/src/boot/misc.rs | 21 +++-- uefi/src/boot.rs | 139 ++++++++++++++++++++++++++++-- uefi/src/table/boot.rs | 14 +-- 3 files changed, 150 insertions(+), 24 deletions(-) diff --git a/uefi-test-runner/src/boot/misc.rs b/uefi-test-runner/src/boot/misc.rs index 148603c8c..2513b3f61 100644 --- a/uefi-test-runner/src/boot/misc.rs +++ b/uefi-test-runner/src/boot/misc.rs @@ -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) { let bt = st.boot_services(); @@ -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::( - OpenProtocolParams { - handle, - agent: bt.image_handle(), - controller: None, - }, - OpenProtocolAttributes::GetProtocol, - ) - .unwrap(); + let mut sp = boot::open_protocol::( + OpenProtocolParams { + handle, + agent: bt.image_handle(), + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + .unwrap(); assert_eq!(sp.data, 123); &mut *sp }; diff --git a/uefi/src/boot.rs b/uefi/src/boot.rs index 1fdb0e3fd..5f05c7e46 100644 --- a/uefi/src/boot.rs +++ b/uefi/src/boot.rs @@ -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 @@ -162,6 +160,60 @@ pub fn locate_handle_buffer(search_ty: SearchType) -> Result { }) } +/// 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( + params: OpenProtocolParams, + attributes: OpenProtocolAttributes, +) -> Result> { + 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)] @@ -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 { + /// The protocol interface. + interface: Option>, + open_params: OpenProtocolParams, +} + +impl Drop for ScopedProtocol

{ + 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 Deref for ScopedProtocol

{ + type Target = P; + + #[track_caller] + fn deref(&self) -> &Self::Target { + unsafe { self.interface.unwrap().as_ref() } + } +} + +impl DerefMut for ScopedProtocol

{ + #[track_caller] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.interface.unwrap().as_mut() } + } +} + +impl ScopedProtocol

{ + /// 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() }) + } +} diff --git a/uefi/src/table/boot.rs b/uefi/src/table/boot.rs index 9baef842b..576af1efd 100644 --- a/uefi/src/table/boot.rs +++ b/uefi/src/table/boot.rs @@ -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