Skip to content

Commit 1f8cdfc

Browse files
authored
Merge pull request #1270 from rust-osdev/bishop-free-open-proto
boot: Add freestanding version of open_protocol
2 parents 0358496 + 60c6b9d commit 1f8cdfc

File tree

3 files changed

+150
-24
lines changed

3 files changed

+150
-24
lines changed

Diff for: uefi-test-runner/src/boot/misc.rs

+10-11
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use uefi::table::boot::{
99
Tpl,
1010
};
1111
use uefi::table::{Boot, SystemTable};
12-
use uefi::{guid, Event, Guid, Identify};
12+
use uefi::{boot, guid, Event, Guid, Identify};
1313

1414
pub fn test(st: &SystemTable<Boot>) {
1515
let bt = st.boot_services();
@@ -164,16 +164,15 @@ fn test_uninstall_protocol_interface(bt: &BootServices) {
164164
// pointer. Open the protocol to get that pointer, making sure to drop
165165
// the `ScopedProtocol` _before_ uninstalling the protocol interface.
166166
let interface_ptr: *mut TestProtocol = {
167-
let mut sp = bt
168-
.open_protocol::<TestProtocol>(
169-
OpenProtocolParams {
170-
handle,
171-
agent: bt.image_handle(),
172-
controller: None,
173-
},
174-
OpenProtocolAttributes::GetProtocol,
175-
)
176-
.unwrap();
167+
let mut sp = boot::open_protocol::<TestProtocol>(
168+
OpenProtocolParams {
169+
handle,
170+
agent: bt.image_handle(),
171+
controller: None,
172+
},
173+
OpenProtocolAttributes::GetProtocol,
174+
)
175+
.unwrap();
177176
assert_eq!(sp.data, 123);
178177
&mut *sp
179178
};

Diff for: uefi/src/boot.rs

+133-6
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@
33
//! These functions will panic if called after exiting boot services.
44
55
use crate::data_types::PhysicalAddress;
6+
use crate::proto::{Protocol, ProtocolPointer};
67
use core::ffi::c_void;
7-
use core::ops::Deref;
8+
use core::ops::{Deref, DerefMut};
89
use core::ptr::{self, NonNull};
910
use core::slice;
1011
use core::sync::atomic::{AtomicPtr, Ordering};
11-
use uefi::{table, Handle, Result, StatusExt};
12+
use uefi::{table, Handle, Result, Status, StatusExt};
1213

13-
#[cfg(doc)]
14-
use uefi::Status;
15-
16-
pub use uefi::table::boot::{AllocateType, SearchType};
14+
pub use uefi::table::boot::{AllocateType, OpenProtocolAttributes, OpenProtocolParams, SearchType};
1715
pub use uefi_raw::table::boot::MemoryType;
1816

1917
/// 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<HandleBuffer> {
162160
})
163161
}
164162

163+
/// Opens a protocol interface for a handle.
164+
///
165+
/// See also `open_protocol_exclusive`, which provides a safe subset of this
166+
/// functionality.
167+
///
168+
/// This function attempts to get the protocol implementation of a handle, based
169+
/// on the [protocol GUID].
170+
///
171+
/// See [`OpenProtocolParams`] and [`OpenProtocolAttributes`] for details of the
172+
/// input parameters.
173+
///
174+
/// If successful, a [`ScopedProtocol`] is returned that will automatically
175+
/// close the protocol interface when dropped.
176+
///
177+
/// [protocol GUID]: uefi::data_types::Identify::GUID
178+
///
179+
/// # Safety
180+
///
181+
/// This function is unsafe because it can be used to open a protocol in ways
182+
/// that don't get tracked by the UEFI implementation. This could allow the
183+
/// protocol to be removed from a handle, or for the handle to be deleted
184+
/// entirely, while a reference to the protocol is still active. The caller is
185+
/// responsible for ensuring that the handle and protocol remain valid until the
186+
/// `ScopedProtocol` is dropped.
187+
///
188+
/// # Errors
189+
///
190+
/// * [`Status::INVALID_PARAMETER`]: an invalid combination of `params` and
191+
/// `attributes` was provided.
192+
/// * [`Status::UNSUPPORTED`]: the handle does not support the protocol.
193+
/// * [`Status::ACCESS_DENIED`] or [`Status::ALREADY_STARTED`]: the protocol is
194+
/// already open in a way that is incompatible with the new request.
195+
pub unsafe fn open_protocol<P: ProtocolPointer + ?Sized>(
196+
params: OpenProtocolParams,
197+
attributes: OpenProtocolAttributes,
198+
) -> Result<ScopedProtocol<P>> {
199+
let bt = boot_services_raw_panicking();
200+
let bt = unsafe { bt.as_ref() };
201+
202+
let mut interface = ptr::null_mut();
203+
(bt.open_protocol)(
204+
params.handle.as_ptr(),
205+
&P::GUID,
206+
&mut interface,
207+
params.agent.as_ptr(),
208+
Handle::opt_to_ptr(params.controller),
209+
attributes as u32,
210+
)
211+
.to_result_with_val(|| ScopedProtocol {
212+
interface: NonNull::new(P::mut_ptr_from_ffi(interface)),
213+
open_params: params,
214+
})
215+
}
216+
165217
/// A buffer returned by [`locate_handle_buffer`] that contains an array of
166218
/// [`Handle`]s that support the requested protocol.
167219
#[derive(Debug, Eq, PartialEq)]
@@ -183,3 +235,78 @@ impl Deref for HandleBuffer {
183235
unsafe { slice::from_raw_parts(self.buffer.as_ptr(), self.count) }
184236
}
185237
}
238+
239+
/// An open protocol interface. Automatically closes the protocol
240+
/// interface on drop.
241+
///
242+
/// Most protocols have interface data associated with them. `ScopedProtocol`
243+
/// implements [`Deref`] and [`DerefMut`] to access this data. A few protocols
244+
/// (such as [`DevicePath`] and [`LoadedImageDevicePath`]) may be installed with
245+
/// null interface data, in which case [`Deref`] and [`DerefMut`] will
246+
/// panic. The [`get`] and [`get_mut`] methods may be used to access the
247+
/// optional interface data without panicking.
248+
///
249+
/// [`DevicePath`]: crate::proto::device_path::DevicePath
250+
/// [`LoadedImageDevicePath`]: crate::proto::device_path::LoadedImageDevicePath
251+
/// [`get`]: ScopedProtocol::get
252+
/// [`get_mut`]: ScopedProtocol::get_mut
253+
#[derive(Debug)]
254+
pub struct ScopedProtocol<P: Protocol + ?Sized> {
255+
/// The protocol interface.
256+
interface: Option<NonNull<P>>,
257+
open_params: OpenProtocolParams,
258+
}
259+
260+
impl<P: Protocol + ?Sized> Drop for ScopedProtocol<P> {
261+
fn drop(&mut self) {
262+
let bt = boot_services_raw_panicking();
263+
let bt = unsafe { bt.as_ref() };
264+
265+
let status = unsafe {
266+
(bt.close_protocol)(
267+
self.open_params.handle.as_ptr(),
268+
&P::GUID,
269+
self.open_params.agent.as_ptr(),
270+
Handle::opt_to_ptr(self.open_params.controller),
271+
)
272+
};
273+
// All of the error cases for close_protocol boil down to
274+
// calling it with a different set of parameters than what was
275+
// passed to open_protocol. The public API prevents such errors,
276+
// and the error can't be propagated out of drop anyway, so just
277+
// assert success.
278+
assert_eq!(status, Status::SUCCESS);
279+
}
280+
}
281+
282+
impl<P: Protocol + ?Sized> Deref for ScopedProtocol<P> {
283+
type Target = P;
284+
285+
#[track_caller]
286+
fn deref(&self) -> &Self::Target {
287+
unsafe { self.interface.unwrap().as_ref() }
288+
}
289+
}
290+
291+
impl<P: Protocol + ?Sized> DerefMut for ScopedProtocol<P> {
292+
#[track_caller]
293+
fn deref_mut(&mut self) -> &mut Self::Target {
294+
unsafe { self.interface.unwrap().as_mut() }
295+
}
296+
}
297+
298+
impl<P: Protocol + ?Sized> ScopedProtocol<P> {
299+
/// Get the protocol interface data, or `None` if the open protocol's
300+
/// interface is null.
301+
#[must_use]
302+
pub fn get(&self) -> Option<&P> {
303+
self.interface.map(|p| unsafe { p.as_ref() })
304+
}
305+
306+
/// Get the protocol interface data, or `None` if the open protocol's
307+
/// interface is null.
308+
#[must_use]
309+
pub fn get_mut(&mut self) -> Option<&mut P> {
310+
self.interface.map(|mut p| unsafe { p.as_mut() })
311+
}
312+
}

Diff for: uefi/src/table/boot.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -1136,13 +1136,13 @@ impl BootServices {
11361136
///
11371137
/// # Safety
11381138
///
1139-
/// This function is unsafe because it can be used to open a
1140-
/// protocol in ways that don't get tracked by the UEFI
1141-
/// implementation. This could allow the protocol to be removed from
1142-
/// a handle, or for the handle to be deleted entirely, while a
1143-
/// reference to the protocol is still active. The caller is
1144-
/// responsible for ensuring that the handle and protocol remain
1145-
/// valid until the `ScopedProtocol` is dropped.
1139+
/// This function is unsafe because it can be used to open a protocol in
1140+
/// ways that don't get tracked by the UEFI implementation. This could allow
1141+
/// the protocol to be removed from a handle, or for the handle to be
1142+
/// deleted entirely, while a reference to the protocol is still active. The
1143+
/// caller is responsible for ensuring that the handle and protocol remain
1144+
/// valid until the `ScopedProtocol` is dropped, and the caller must ensure
1145+
/// that there is never more than one mutable reference to the protocol.
11461146
///
11471147
/// [`open_protocol`]: BootServices::open_protocol
11481148
/// [`open_protocol_exclusive`]: BootServices::open_protocol_exclusive

0 commit comments

Comments
 (0)