Skip to content

Commit e34f624

Browse files
committed
uefi: add boot::[un_]install_multiple_protocol_interface()
1 parent a65ba3e commit e34f624

File tree

3 files changed

+306
-9
lines changed

3 files changed

+306
-9
lines changed

uefi-test-runner/src/boot/misc.rs

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
// SPDX-License-Identifier: MIT OR Apache-2.0
22

3+
use alloc::boxed::Box;
4+
use alloc::vec::Vec;
35
use core::ffi::c_void;
46
use core::ptr::{self, NonNull};
57

68
use uefi::boot::{
79
EventType, OpenProtocolAttributes, OpenProtocolParams, SearchType, TimerTrigger, Tpl,
810
};
911
use uefi::mem::memory_map::MemoryType;
10-
use uefi::proto::unsafe_protocol;
11-
use uefi::{Event, Guid, Identify, boot, guid, system};
12+
use uefi::proto::device_path::build::DevicePathBuilder;
13+
use uefi::proto::device_path::{DevicePath, build};
14+
use uefi::proto::{ProtocolPointer, unsafe_protocol};
15+
use uefi::{Event, Guid, Identify, ResultExt, boot, cstr16, guid, system};
16+
use uefi_raw::Status;
1217

1318
pub fn test() {
1419
test_tpl();
@@ -25,6 +30,8 @@ pub fn test() {
2530
test_install_protocol_interface();
2631
test_reinstall_protocol_interface();
2732
test_uninstall_protocol_interface();
33+
test_install_multiple_protocol_interface();
34+
test_uninstall_multiple_protocol_interface();
2835
test_install_configuration_table();
2936
info!("Testing crc32...");
3037
test_calculate_crc32();
@@ -210,13 +217,130 @@ fn test_uninstall_protocol_interface() {
210217
&mut *sp
211218
};
212219

213-
boot::uninstall_protocol_interface(handle, &TestProtocol::GUID, interface_ptr.cast())
220+
boot::uninstall_protocol_interface::<TestProtocol>(handle, interface_ptr.cast())
214221
.expect("Failed to uninstall protocol interface");
215222

216223
boot::free_pool(NonNull::new(interface_ptr.cast()).unwrap()).unwrap();
217224
}
218225
}
219226

227+
fn test_install_multiple_protocol_interface() {
228+
info!("Installing multiple test protocols");
229+
230+
let alloc: *mut TestProtocol =
231+
boot::allocate_pool(MemoryType::BOOT_SERVICES_DATA, size_of::<TestProtocol>())
232+
.unwrap()
233+
.cast()
234+
.as_ptr();
235+
unsafe { alloc.write(TestProtocol { data: 123 }) };
236+
237+
let dvp = {
238+
let mut vec = Vec::new();
239+
DevicePathBuilder::with_vec(&mut vec)
240+
.push(&build::media::FilePath {
241+
path_name: cstr16!("foobar"),
242+
})
243+
.unwrap()
244+
.finalize()
245+
.unwrap()
246+
.to_boxed()
247+
};
248+
// Memory must stay valid as long as handle with interfaces lives:
249+
// => so we leak the memory but will free it in the uninstall hook again.
250+
let dvp = Box::leak(dvp);
251+
252+
let handle = unsafe {
253+
boot::install_multiple_protocol_interface(
254+
None,
255+
&[
256+
(&TestProtocol::GUID, alloc.cast()),
257+
(&DevicePath::GUID, dvp.as_ffi_ptr().cast()),
258+
],
259+
)
260+
.expect("Failed to install protocol interface")
261+
};
262+
263+
// Test we indeed installed the protocols.
264+
{
265+
assert_eq!(
266+
boot::test_protocol::<DevicePath>(OpenProtocolParams {
267+
handle,
268+
agent: boot::image_handle(),
269+
controller: None,
270+
}),
271+
Ok(true)
272+
);
273+
}
274+
275+
// Test that installing the device path protocol multiple times results in
276+
// EFI_ALREADY_STARTED
277+
{
278+
let res = unsafe {
279+
boot::install_multiple_protocol_interface(
280+
Some(handle),
281+
&[(&DevicePath::GUID, dvp.as_ffi_ptr().cast())],
282+
)
283+
};
284+
assert_eq!(res.status(), Status::ALREADY_STARTED);
285+
}
286+
}
287+
288+
fn test_uninstall_multiple_protocol_interface() {
289+
info!("Uninstalling multiple test protocols");
290+
291+
let handles = boot::locate_handle_buffer(SearchType::from_proto::<TestProtocol>())
292+
.expect("Failed to find protocol after it was installed");
293+
let handle = *handles.first().unwrap();
294+
295+
let interface_test_protocol: *mut TestProtocol = unsafe {
296+
let mut sp = boot::open_protocol::<TestProtocol>(
297+
OpenProtocolParams {
298+
handle,
299+
agent: boot::image_handle(),
300+
controller: None,
301+
},
302+
OpenProtocolAttributes::GetProtocol,
303+
)
304+
.unwrap();
305+
assert_eq!(sp.data, 123);
306+
&mut *sp
307+
};
308+
309+
let interface_dvp: *mut DevicePath = unsafe {
310+
let mut sp = boot::open_protocol::<DevicePath>(
311+
OpenProtocolParams {
312+
handle,
313+
agent: boot::image_handle(),
314+
controller: None,
315+
},
316+
OpenProtocolAttributes::GetProtocol,
317+
)
318+
.unwrap();
319+
&mut *sp
320+
};
321+
322+
unsafe {
323+
boot::uninstall_multiple_protocol_interface(
324+
handle,
325+
&[
326+
(&TestProtocol::GUID, interface_test_protocol.cast()),
327+
(&DevicePath::GUID, interface_dvp.cast()),
328+
],
329+
)
330+
}
331+
.expect("should uninstall multiple protocols");
332+
333+
let dvp = unsafe {
334+
DevicePath::mut_ptr_from_ffi(interface_dvp.cast())
335+
.as_mut()
336+
.expect("should be valid device path")
337+
};
338+
339+
// Reconstruct the Rust box to ensure that the object is properly freed in
340+
// the Rust global allocator
341+
let _ = unsafe { Box::from_raw(dvp) };
342+
}
343+
220344
fn test_install_configuration_table() {
221345
// Get the current number of entries.
222346
let initial_table_count = system::with_config_table(|t| t.len());

uefi/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
- Added `boot::test_protocol_by_guid()`
1111
- Added `boot::register_protocol_notify_by_guid()`
1212
- Added `boot::[re_,un_]install_protocol_interface_by_guid()` functions.
13+
- Added `boot::[un_]install_multiple_protocol_interface`. Currently, this
14+
replicates the functionality of the EDK2 implementation rather than using it
15+
due to Rusts limited support for variadic arguments.
1316

1417
## Changed
1518
- Changed ordering of `proto::pci::PciIoAddress` to (bus -> dev -> fun -> reg -> ext_reg).

uefi/src/boot.rs

Lines changed: 176 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,20 @@ use crate::proto::{BootPolicy, Protocol, ProtocolPointer};
3737
use crate::runtime::{self, ResetType};
3838
use crate::table::Revision;
3939
use crate::util::opt_nonnull_to_ptr;
40-
use crate::{Char16, Error, Event, Guid, Handle, Result, Status, StatusExt, table};
40+
use crate::{
41+
Char16, Error, Event, Guid, Handle, Identify, Result, ResultExt, Status, StatusExt, table,
42+
};
43+
#[cfg(feature = "alloc")]
44+
use alloc::vec::Vec;
4145
use core::ffi::c_void;
4246
use core::mem::MaybeUninit;
4347
use core::ops::{Deref, DerefMut};
4448
use core::ptr::{self, NonNull};
4549
use core::sync::atomic::{AtomicPtr, Ordering};
4650
use core::time::Duration;
4751
use core::{mem, slice};
52+
use log::error;
4853
use uefi_raw::table::boot::{AllocateType as RawAllocateType, InterfaceType, TimerDelay};
49-
#[cfg(feature = "alloc")]
50-
use {alloc::vec::Vec, uefi::ResultExt};
5154

5255
/// Global image handle. This is only set by [`set_image_handle`], and it is
5356
/// only read by [`image_handle`].
@@ -713,7 +716,12 @@ pub fn disconnect_controller(
713716
.to_result_with_err(|_| ())
714717
}
715718

716-
/// Installs a protocol interface on a device handle.
719+
/// Installs a protocol interface on a device handle. If no handle is
720+
/// specified, a new handle will be allocated and returned.
721+
///
722+
/// It is recommended to use [`install_multiple_protocol_interface`] when you
723+
/// plan to install multiple protocols, as it performs more error checking
724+
/// and cleanup under the hood.
717725
///
718726
/// When a protocol interface is installed, firmware will call all functions
719727
/// that have registered to wait for that interface to be installed.
@@ -736,7 +744,7 @@ pub fn disconnect_controller(
736744
pub unsafe fn install_protocol_interface<P: ProtocolPointer + ?Sized>(
737745
handle: Option<Handle>,
738746
interface: *const c_void,
739-
) -> Result<Handle> {
747+
) -> Result<Handle /* new if input was None */> {
740748
unsafe { install_protocol_interface_by_guid(handle, &P::GUID, interface) }
741749
}
742750

@@ -750,7 +758,7 @@ pub unsafe fn install_protocol_interface_by_guid(
750758
handle: Option<Handle>,
751759
protocol: &Guid,
752760
interface: *const c_void,
753-
) -> Result<Handle> {
761+
) -> Result<Handle /* new if Input was None */> {
754762
let bt = boot_services_raw_panicking();
755763
let bt = unsafe { bt.as_ref() };
756764

@@ -852,6 +860,168 @@ pub unsafe fn uninstall_protocol_interface_by_guid(
852860
unsafe { (bt.uninstall_protocol_interface)(handle.as_ptr(), protocol, interface).to_result() }
853861
}
854862

863+
/// Installs multiple protocol interfaces for a given handle at once, and
864+
/// reverts all operations if a single operation fails. If no handle is
865+
/// specified, a new handle will be allocated and returned.
866+
///
867+
/// When a protocol interface is installed, firmware will call all functions
868+
/// that have registered to wait for that interface to be installed.
869+
///
870+
/// As Rust is not having proper C variadic support, this function emulates the
871+
/// behavior of the `CoreInstallMultipleProtocolInterfaces` function from
872+
/// `edk2`. Effectively, the behavior is the same, but it doesn't use the
873+
/// corresponding boot service under the hood.
874+
///
875+
/// # Arguments
876+
///
877+
/// - `handle`: Either `None` to allocate a new handle or an existing handle.
878+
/// - `pairs`: Pairs of the [`Guid`] of the [`Protocol`] to install and the
879+
/// protocol implementation. The memory backing the implementation
880+
/// **must live as long as the handle!**. Callers need to ensure a matching
881+
/// lifetime!
882+
///
883+
/// # Safety
884+
///
885+
/// The caller is responsible for ensuring that they pass a valid `Guid` for `protocol`.
886+
///
887+
/// # Errors
888+
///
889+
/// * [`Status::OUT_OF_RESOURCES`]: failed to allocate a new handle.
890+
/// * [`Status::INVALID_PARAMETER`]: this protocol is already installed on the handle.
891+
/// * [`Status::ALREADY_STARTED`]: A Device Path Protocol instance was passed in that is already present in the handle database.
892+
pub unsafe fn install_multiple_protocol_interface(
893+
mut handle: Option<Handle>,
894+
pairs: &[(&Guid, *const c_void)],
895+
) -> Result<Handle /* new if input was None */> {
896+
// TODO once Rust has sensible variadic argument support, we should
897+
// fallback to the correct boot service.
898+
899+
// Taken from edk2 source.
900+
const TPL_NOTIFY: Tpl = Tpl(16);
901+
let tpl = TPL_NOTIFY;
902+
// SAFETY: We do not want our loop to be interrupted.
903+
let _old_tpl = unsafe { raise_tpl(tpl) };
904+
905+
// Variables that are updated in the loop.
906+
let mut installed_count = 0;
907+
let mut status = Status::SUCCESS;
908+
909+
// try to install all interfaces and update `handle` if it is `None`
910+
for (guid, interface) in pairs {
911+
// prevent multiple installations of the device path protocol on the
912+
// same handle:
913+
if let Some(handle) = handle {
914+
if *guid == &DevicePath::GUID
915+
&& test_protocol_by_guid(
916+
guid,
917+
OpenProtocolParams {
918+
handle,
919+
agent: image_handle(),
920+
controller: None,
921+
},
922+
)?
923+
{
924+
status = Status::ALREADY_STARTED;
925+
break;
926+
}
927+
}
928+
929+
let result = unsafe { install_protocol_interface_by_guid(handle, guid, *interface) };
930+
931+
match (result, handle, installed_count) {
932+
(Ok(new_handle), None, 0) => {
933+
handle = Some(new_handle);
934+
}
935+
(Ok(_handle), _, _) => {}
936+
(Err(err), _, _) => {
937+
error!("Failed to install protocol interface: {err}");
938+
// next, we need to uninstall for all succeeded iterations
939+
status = err.status();
940+
break;
941+
}
942+
}
943+
944+
installed_count += 1;
945+
}
946+
947+
if !status.is_success() {
948+
// try to uninstall all that were just successfully installed
949+
for (guid, interface) in pairs.iter().take(installed_count) {
950+
let res =
951+
unsafe { uninstall_protocol_interface_by_guid(handle.unwrap(), guid, *interface) };
952+
if let Err(e) = res {
953+
let handle_addr = &raw const *handle.as_ref().unwrap();
954+
// We don't fail here, as this would break the contract of the
955+
// function.
956+
error!(
957+
"Failed to uninstall interface after failed multiple install attempt: handle={handle_addr:?}, guid={}, interface={:?}, error={e}",
958+
guid, interface
959+
);
960+
}
961+
}
962+
963+
Err(status.into())
964+
} else {
965+
Ok(handle.unwrap())
966+
}
967+
}
968+
969+
/// Removes one or more protocol interfaces into the boot services environment.
970+
///
971+
/// If any errors are generated while the protocol interfaces are being
972+
/// uninstalled, then the protocols uninstalled prior to the error will be
973+
/// reinstalled.
974+
///
975+
/// # Safety
976+
///
977+
/// The caller is responsible for ensuring that there are no references to a protocol interface
978+
/// that has been removed. Some protocols may not be able to be removed as there is no information
979+
/// available regarding the references. This includes Console I/O, Block I/O, Disk I/o, and handles
980+
/// to device protocols.
981+
///
982+
/// # Errors
983+
///
984+
/// * [`Status::NOT_FOUND`]: the interface was not found on the handle.
985+
/// * [`Status::ACCESS_DENIED`]: the interface is still in use and cannot be uninstalled.
986+
pub unsafe fn uninstall_multiple_protocol_interface(
987+
handle: Handle,
988+
pairs: &[(&Guid, *const c_void)],
989+
) -> Result<()> {
990+
let mut uninstalled_count = 0;
991+
let mut status = Status::SUCCESS;
992+
993+
// try to install all interfaces and update `handle` if it is `None`
994+
for (guid, interface) in pairs {
995+
let result = unsafe { uninstall_protocol_interface_by_guid(handle, guid, *interface) };
996+
997+
if result.is_err() {
998+
// next, we need to install for all succeeded iterations
999+
status = result.status();
1000+
break;
1001+
}
1002+
1003+
uninstalled_count += 1;
1004+
}
1005+
1006+
if !status.is_success() {
1007+
// try to uninstall all failed ones
1008+
for (guid, interface) in pairs.iter().take(uninstalled_count) {
1009+
let res = unsafe { install_protocol_interface_by_guid(Some(handle), guid, *interface) };
1010+
if let Err(e) = res {
1011+
let handle_addr = &raw const handle;
1012+
// We don't fail here, as this would break the contract of the
1013+
// function.
1014+
error!(
1015+
"Failed to install interface after failed multiple uninstall attempt: handle={handle_addr:?}, guid={}, interface={:?}, error={e}",
1016+
guid, interface
1017+
);
1018+
}
1019+
}
1020+
}
1021+
1022+
Ok(())
1023+
}
1024+
8551025
/// Registers `event` to be signaled whenever a protocol interface is registered for
8561026
/// `protocol` by [`install_protocol_interface`] or [`reinstall_protocol_interface`].
8571027
///

0 commit comments

Comments
 (0)