Skip to content

Commit 74dd331

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

File tree

3 files changed

+302
-7
lines changed

3 files changed

+302
-7
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: 172 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,15 @@ 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::{Char16, Error, Event, Guid, Handle, Identify, Result, Status, StatusExt, table};
4141
use core::ffi::c_void;
4242
use core::mem::MaybeUninit;
4343
use core::ops::{Deref, DerefMut};
4444
use core::ptr::{self, NonNull};
4545
use core::sync::atomic::{AtomicPtr, Ordering};
4646
use core::time::Duration;
4747
use core::{mem, slice};
48+
use log::error;
4849
use uefi_raw::table::boot::{AllocateType as RawAllocateType, InterfaceType, TimerDelay};
4950
#[cfg(feature = "alloc")]
5051
use {alloc::vec::Vec, uefi::ResultExt};
@@ -713,7 +714,12 @@ pub fn disconnect_controller(
713714
.to_result_with_err(|_| ())
714715
}
715716

716-
/// Installs a protocol interface on a device handle.
717+
/// Installs a protocol interface on a device handle. If no handle is
718+
/// specified, a new handle will be allocated and returned.
719+
///
720+
/// It is recommended to use [`install_multiple_protocol_interface`] when you
721+
/// plan to install multiple protocols, as it performs more error checking
722+
/// and cleanup under the hood.
717723
///
718724
/// When a protocol interface is installed, firmware will call all functions
719725
/// that have registered to wait for that interface to be installed.
@@ -736,7 +742,7 @@ pub fn disconnect_controller(
736742
pub unsafe fn install_protocol_interface<P: ProtocolPointer + ?Sized>(
737743
handle: Option<Handle>,
738744
interface: *const c_void,
739-
) -> Result<Handle> {
745+
) -> Result<Handle /* new if input was None */> {
740746
unsafe { install_protocol_interface_by_guid(handle, &P::GUID, interface) }
741747
}
742748

@@ -750,7 +756,7 @@ pub unsafe fn install_protocol_interface_by_guid(
750756
handle: Option<Handle>,
751757
protocol: &Guid,
752758
interface: *const c_void,
753-
) -> Result<Handle> {
759+
) -> Result<Handle /* new if Input was None */> {
754760
let bt = boot_services_raw_panicking();
755761
let bt = unsafe { bt.as_ref() };
756762

@@ -852,6 +858,168 @@ pub unsafe fn uninstall_protocol_interface_by_guid(
852858
unsafe { (bt.uninstall_protocol_interface)(handle.as_ptr(), protocol, interface).to_result() }
853859
}
854860

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

0 commit comments

Comments
 (0)