Skip to content

Commit 92515cc

Browse files
Implement open_protocol, use it to fix flaky screenshot test (#318)
1 parent 1818cd7 commit 92515cc

File tree

8 files changed

+266
-34
lines changed

8 files changed

+266
-34
lines changed

src/table/boot.rs

Lines changed: 199 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,20 @@ pub struct BootServices {
123123
disconnect_controller: usize,
124124

125125
// Protocol open / close services
126-
open_protocol: usize,
127-
close_protocol: usize,
126+
open_protocol: extern "efiapi" fn(
127+
handle: Handle,
128+
protocol: &Guid,
129+
interface: &mut *mut c_void,
130+
agent_handle: Handle,
131+
controller_handle: Option<Handle>,
132+
attributes: u32,
133+
) -> Status,
134+
close_protocol: extern "efiapi" fn(
135+
handle: Handle,
136+
protocol: &Guid,
137+
agent_handle: Handle,
138+
controller_handle: Option<Handle>,
139+
) -> Status,
128140
open_protocol_information: usize,
129141

130142
// Library services
@@ -481,10 +493,16 @@ impl BootServices {
481493
/// This function attempts to get the protocol implementation of a handle,
482494
/// based on the protocol GUID.
483495
///
496+
/// It is recommended that all new drivers and applications use
497+
/// [`open_protocol`] instead of `handle_protocol`.
498+
///
484499
/// UEFI protocols are neither thread-safe nor reentrant, but the firmware
485500
/// provides no mechanism to protect against concurrent usage. Such
486501
/// protections must be implemented by user-level code, for example via a
487502
/// global `HashSet`.
503+
///
504+
/// [`open_protocol`]: BootServices::open_protocol
505+
#[deprecated(note = "it is recommended to use `open_protocol` instead")]
488506
pub fn handle_protocol<P: Protocol>(&self, handle: Handle) -> Result<&UnsafeCell<P>> {
489507
let mut ptr = ptr::null_mut();
490508
(self.handle_protocol)(handle, &P::GUID, &mut ptr).into_with_val(|| {
@@ -672,6 +690,67 @@ impl BootServices {
672690
unsafe { (self.set_watchdog_timer)(timeout, watchdog_code, data_len, data) }.into()
673691
}
674692

693+
/// Open a protocol interface for a handle.
694+
///
695+
/// This function attempts to get the protocol implementation of a
696+
/// handle, based on the protocol GUID. It is an extended version of
697+
/// [`handle_protocol`]. It is recommended that all
698+
/// new drivers and applications use `open_protocol` instead of
699+
/// `handle_protocol`.
700+
///
701+
/// See [`OpenProtocolParams`] and [`OpenProtocolAttributes`] for
702+
/// details of the input parameters.
703+
///
704+
/// If successful, a [`ScopedProtocol`] is returned that will
705+
/// automatically close the protocol interface when dropped.
706+
///
707+
/// UEFI protocols are neither thread-safe nor reentrant, but the firmware
708+
/// provides no mechanism to protect against concurrent usage. Such
709+
/// protections must be implemented by user-level code, for example via a
710+
/// global `HashSet`.
711+
///
712+
/// [`handle_protocol`]: BootServices::handle_protocol
713+
pub fn open_protocol<P: Protocol>(
714+
&self,
715+
params: OpenProtocolParams,
716+
attributes: OpenProtocolAttributes,
717+
) -> Result<ScopedProtocol<P>> {
718+
let mut interface = ptr::null_mut();
719+
(self.open_protocol)(
720+
params.handle,
721+
&P::GUID,
722+
&mut interface,
723+
params.agent,
724+
params.controller,
725+
attributes as u32,
726+
)
727+
.into_with_val(|| {
728+
let interface = interface as *mut P as *mut UnsafeCell<P>;
729+
unsafe {
730+
ScopedProtocol {
731+
interface: &*interface,
732+
open_params: params,
733+
boot_services: self,
734+
}
735+
}
736+
})
737+
}
738+
739+
/// Test whether a handle supports a protocol.
740+
pub fn test_protocol<P: Protocol>(&self, params: OpenProtocolParams) -> Result<()> {
741+
const TEST_PROTOCOL: u32 = 0x04;
742+
let mut interface = ptr::null_mut();
743+
(self.open_protocol)(
744+
params.handle,
745+
&P::GUID,
746+
&mut interface,
747+
params.agent,
748+
params.controller,
749+
TEST_PROTOCOL,
750+
)
751+
.into_with_val(|| ())
752+
}
753+
675754
/// Get the list of protocol interface [`Guids`][Guid] that are installed
676755
/// on a [`Handle`].
677756
pub fn protocols_per_handle(&self, handle: Handle) -> Result<ProtocolsPerHandle> {
@@ -770,24 +849,45 @@ impl BootServices {
770849
pub fn get_image_file_system(
771850
&self,
772851
image_handle: Handle,
773-
) -> Result<&UnsafeCell<SimpleFileSystem>> {
852+
) -> Result<ScopedProtocol<SimpleFileSystem>> {
774853
let loaded_image = self
775-
.handle_protocol::<LoadedImage>(image_handle)?
854+
.open_protocol::<LoadedImage>(
855+
OpenProtocolParams {
856+
handle: image_handle,
857+
agent: image_handle,
858+
controller: None,
859+
},
860+
OpenProtocolAttributes::Exclusive,
861+
)?
776862
.expect("Failed to retrieve `LoadedImage` protocol from handle");
777-
let loaded_image = unsafe { &*loaded_image.get() };
863+
let loaded_image = unsafe { &*loaded_image.interface.get() };
778864

779865
let device_handle = loaded_image.device();
780866

781867
let device_path = self
782-
.handle_protocol::<DevicePath>(device_handle)?
868+
.open_protocol::<DevicePath>(
869+
OpenProtocolParams {
870+
handle: device_handle,
871+
agent: image_handle,
872+
controller: None,
873+
},
874+
OpenProtocolAttributes::Exclusive,
875+
)?
783876
.expect("Failed to retrieve `DevicePath` protocol from image's device handle");
784-
let mut device_path = unsafe { &*device_path.get() };
877+
let mut device_path = unsafe { &*device_path.interface.get() };
785878

786879
let device_handle = self
787880
.locate_device_path::<SimpleFileSystem>(&mut device_path)?
788881
.expect("Failed to locate `SimpleFileSystem` protocol on device path");
789882

790-
self.handle_protocol::<SimpleFileSystem>(device_handle)
883+
self.open_protocol::<SimpleFileSystem>(
884+
OpenProtocolParams {
885+
handle: device_handle,
886+
agent: image_handle,
887+
controller: None,
888+
},
889+
OpenProtocolAttributes::Exclusive,
890+
)
791891
}
792892
}
793893

@@ -962,6 +1062,97 @@ impl Drop for TplGuard<'_> {
9621062
}
9631063
}
9641064

1065+
// OpenProtocolAttributes is safe to model as a regular enum because it
1066+
// is only used as an input. The attributes are bitflags, but all valid
1067+
// combinations are listed in the spec and only ByDriver and Exclusive
1068+
// can actually be combined.
1069+
//
1070+
// Some values intentionally excluded:
1071+
//
1072+
// ByHandleProtocol (0x01) excluded because it is only intended to be
1073+
// used in an implementation of `HandleProtocol`.
1074+
//
1075+
// TestProtocol (0x04) excluded because it doesn't actually open the
1076+
// protocol, just tests if it's present on the handle. Since that
1077+
// changes the interface significantly, that's exposed as a separate
1078+
// method: `BootServices::test_protocol`.
1079+
1080+
/// Attributes for [`BootServices::open_protocol`].
1081+
#[repr(u32)]
1082+
pub enum OpenProtocolAttributes {
1083+
/// Used by drivers to get a protocol interface for a handle. The
1084+
/// driver will not be informed if the interface is uninstalled or
1085+
/// reinstalled.
1086+
GetProtocol = 0x02,
1087+
1088+
/// Used by bus drivers to show that a protocol is being used by one
1089+
/// of the child controllers of the bus.
1090+
ByChildController = 0x08,
1091+
1092+
/// Used by a driver to gain access to a protocol interface. When
1093+
/// this mode is used, the driver's `Stop` function will be called
1094+
/// if the protocol interface is reinstalled or uninstalled. Once a
1095+
/// protocol interface is opened with this attribute, no other
1096+
/// drivers will be allowed to open the same protocol interface with
1097+
/// the `ByDriver` attribute.
1098+
ByDriver = 0x10,
1099+
1100+
/// Used by a driver to gain exclusive access to a protocol
1101+
/// interface. If any other drivers have the protocol interface
1102+
/// opened with an attribute of `ByDriver`, then an attempt will be
1103+
/// made to remove them with `DisconnectController`.
1104+
ByDriverExclusive = 0x30,
1105+
1106+
/// Used by applications to gain exclusive access to a protocol
1107+
/// interface. If any drivers have the protocol opened with an
1108+
/// attribute of `ByDriver`, then an attempt will be made to remove
1109+
/// them by calling the driver's `Stop` function.
1110+
Exclusive = 0x20,
1111+
}
1112+
1113+
/// Parameters passed to [`BootServices::open_protocol`].
1114+
pub struct OpenProtocolParams {
1115+
/// The handle for the protocol to open.
1116+
pub handle: Handle,
1117+
1118+
/// The handle of the calling agent. For drivers, this is the handle
1119+
/// containing the `EFI_DRIVER_BINDING_PROTOCOL` instance. For
1120+
/// applications, this is the image handle.
1121+
pub agent: Handle,
1122+
1123+
/// For drivers, this is the controller handle that requires the
1124+
/// protocol interface. For applications this should be set to
1125+
/// `None`.
1126+
pub controller: Option<Handle>,
1127+
}
1128+
1129+
/// An open protocol interface. Automatically closes the protocol
1130+
/// interface on drop.
1131+
pub struct ScopedProtocol<'a, P: Protocol> {
1132+
/// The protocol interface.
1133+
pub interface: &'a UnsafeCell<P>,
1134+
1135+
open_params: OpenProtocolParams,
1136+
boot_services: &'a BootServices,
1137+
}
1138+
1139+
impl<'a, P: Protocol> Drop for ScopedProtocol<'a, P> {
1140+
fn drop(&mut self) {
1141+
let status = (self.boot_services.close_protocol)(
1142+
self.open_params.handle,
1143+
&P::GUID,
1144+
self.open_params.agent,
1145+
self.open_params.controller,
1146+
);
1147+
// All of the error cases for close_protocol boil down to
1148+
// calling it with a different set of parameters than what was
1149+
// passed to open_protocol. The public API prevents such errors,
1150+
// and the error can't be propagated out of drop anyway, so just
1151+
// assert success.
1152+
assert_eq!(status, Status::SUCCESS);
1153+
}
1154+
}
1155+
9651156
/// Type of allocation to perform.
9661157
#[derive(Debug, Copy, Clone)]
9671158
pub enum AllocateType {

uefi-test-runner/src/main.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use alloc::string::String;
1212
use core::mem;
1313
use uefi::prelude::*;
1414
use uefi::proto::console::serial::Serial;
15-
use uefi::table::boot::MemoryDescriptor;
15+
use uefi::table::boot::{MemoryDescriptor, OpenProtocolAttributes, OpenProtocolParams};
1616

1717
mod boot;
1818
mod proto;
@@ -77,13 +77,25 @@ fn check_revision(rev: uefi::table::Revision) {
7777
/// This functionality is very specific to our QEMU-based test runner. Outside
7878
/// of it, we just pause the tests for a couple of seconds to allow visual
7979
/// inspection of the output.
80-
fn check_screenshot(bt: &BootServices, name: &str) {
80+
fn check_screenshot(image: Handle, bt: &BootServices, name: &str) {
8181
if cfg!(feature = "qemu") {
82-
// Access the serial port (in a QEMU environment, it should always be there)
82+
let serial_handle = *bt
83+
.find_handles::<Serial>()
84+
.expect_success("Failed to get serial handles")
85+
.first()
86+
.expect("Could not find serial port");
87+
8388
let serial = bt
84-
.locate_protocol::<Serial>()
85-
.expect_success("Could not find serial port");
86-
let serial = unsafe { &mut *serial.get() };
89+
.open_protocol::<Serial>(
90+
OpenProtocolParams {
91+
handle: serial_handle,
92+
agent: image,
93+
controller: None,
94+
},
95+
OpenProtocolAttributes::Exclusive,
96+
)
97+
.expect_success("Could not open serial protocol");
98+
let serial = unsafe { &mut *serial.interface.get() };
8799

88100
// Set a large timeout to avoid problems with Travis
89101
let mut io_mode = *serial.io_mode();

uefi-test-runner/src/proto/console/gop.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use uefi::prelude::*;
22
use uefi::proto::console::gop::{BltOp, BltPixel, FrameBuffer, GraphicsOutput, PixelFormat};
33
use uefi::table::boot::BootServices;
44

5-
pub fn test(bt: &BootServices) {
5+
pub fn test(image: Handle, bt: &BootServices) {
66
info!("Running graphics output protocol test");
77
if let Ok(gop) = bt.locate_protocol::<GraphicsOutput>() {
88
let gop = gop.expect("Warnings encountered while opening GOP");
@@ -12,7 +12,7 @@ pub fn test(bt: &BootServices) {
1212
fill_color(gop);
1313
draw_fb(gop);
1414

15-
crate::check_screenshot(bt, "gop_test");
15+
crate::check_screenshot(image, bt, "gop_test");
1616
} else {
1717
// No tests can be run.
1818
warn!("UEFI Graphics Output Protocol is not supported");

uefi-test-runner/src/proto/console/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use uefi::prelude::*;
22

3-
pub fn test(st: &mut SystemTable<Boot>) {
3+
pub fn test(image: Handle, st: &mut SystemTable<Boot>) {
44
info!("Testing console protocols");
55

66
stdout::test(st.stdout());
77

88
let bt = st.boot_services();
99
serial::test(bt);
10-
gop::test(bt);
10+
gop::test(image, bt);
1111
pointer::test(bt);
1212
}
1313

uefi-test-runner/src/proto/debug.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
use core::ffi::c_void;
22
use uefi::proto::debug::{DebugSupport, ExceptionType, ProcessorArch, SystemContext};
3-
use uefi::table::boot::BootServices;
4-
use uefi::ResultExt;
3+
use uefi::table::boot::{BootServices, OpenProtocolAttributes, OpenProtocolParams};
4+
use uefi::{Handle, ResultExt};
55

6-
pub fn test(bt: &BootServices) {
6+
pub fn test(image: Handle, bt: &BootServices) {
77
info!("Running UEFI debug connection protocol test");
88
if let Ok(handles) = bt.find_handles::<DebugSupport>() {
99
let handles = handles.expect("Problem encountered while querying handles for DebugSupport");
1010

1111
for handle in handles {
12-
if let Ok(debug_support) = bt.handle_protocol::<DebugSupport>(handle) {
12+
if let Ok(debug_support) = bt.open_protocol::<DebugSupport>(
13+
OpenProtocolParams {
14+
handle,
15+
agent: image,
16+
controller: None,
17+
},
18+
OpenProtocolAttributes::Exclusive,
19+
) {
1320
let debug_support = debug_support
1421
.expect("Warnings encountered while opening debug support protocol");
15-
let debug_support = unsafe { &mut *debug_support.get() };
22+
let debug_support = unsafe { &mut *debug_support.interface.get() };
1623

1724
// make sure that the max processor index is a sane value, i.e. it works
1825
let maximum_processor_index = debug_support.get_maximum_processor_index();

0 commit comments

Comments
 (0)