Using Protocols
The open a protocol, you must first get a handle, then open a protocol on that handle. See Handles and Protocols for an overview of what these terms mean.
To get a handle you can use:
boot::locate_handle_buffer
: this can be used to get all available handles, or just the handles that support a particular protocol.boot::locate_handle
: the same aslocate_handle_buffer
, but you provide the slice that stores the handles.boot::locate_device_path
: find a handle by Device Path.
Once you have obtained a handle, use
boot::open_protocol_exclusive
to open a protocol on that
handle. This returns a ScopedProtocol
, which automatically closes
the protocol when dropped.
Using boot::open_protocol_exclusive
is the safest way to
open a protocol, but in some cases a protocol cannot be opened in
exclusive mode. The unsafe
boot::open_protocol
can be used
in that case.
Example
For this example we'll look at a program that opens a couple different
protocols. This program opens the LoadedImage
protocol to get
information about an executable (the currently-running program in this
case). It also opens the DevicePathToText
protocol to get the file
system path that the program was launched from.
We'll walk through the details of this program shortly, but first here's the whole thing:
#![no_main] #![no_std] use log::info; use uefi::boot::{self, SearchType}; use uefi::prelude::*; use uefi::proto::device_path::text::{ AllowShortcuts, DevicePathToText, DisplayOnly, }; use uefi::proto::loaded_image::LoadedImage; use uefi::{Identify, Result}; #[entry] fn main() -> Status { uefi::helpers::init().unwrap(); print_image_path().unwrap(); boot::stall(10_000_000); Status::SUCCESS } fn print_image_path() -> Result { let loaded_image = boot::open_protocol_exclusive::<LoadedImage>(boot::image_handle())?; let device_path_to_text_handle = *boot::locate_handle_buffer( SearchType::ByProtocol(&DevicePathToText::GUID), )? .first() .expect("DevicePathToText is missing"); let device_path_to_text = boot::open_protocol_exclusive::<DevicePathToText>( device_path_to_text_handle, )?; let image_device_path = loaded_image.file_path().expect("File path is not set"); let image_device_path_text = device_path_to_text .convert_device_path_to_text( image_device_path, DisplayOnly(true), AllowShortcuts(false), ) .expect("convert_device_path_to_text failed"); info!("Image path: {}", &*image_device_path_text); Ok(()) }
When the program is run it will print something like this:
[ INFO]: example.rs@058: Image path: \EFI\BOOT\BOOTX64.EFI
Walkthrough
The main
function looks much like the "Hello world!" example. It
sets up logging, calls print_image_path
, and pauses for ten seconds to
give you time to read the output. Let's look at print_image_path
:
#![allow(unused)] fn main() { fn print_image_path() -> Result { }
The return type is a uefi::Result
, which is a Result
alias that
combines uefi::Status
with the error data. Both the success and
error data types are ()
by default.
The function starts by opening the LoadedImage
protocol:
#![allow(unused)] fn main() { let loaded_image = boot::open_protocol_exclusive::<LoadedImage>(boot::image_handle())?; }
The boot::open_protocol_exclusive
method takes a type parameter, which is
the type of Protocol
you want to open (LoadedImage
in this
case). It also takes one regular argument of type Handle
. For this
example we want the handle of the currently-running image, conveniently
accessible through boot::image_handle
.
Next the program opens the DevicePathToText
protocol:
#![allow(unused)] fn main() { let device_path_to_text_handle = *boot::locate_handle_buffer( SearchType::ByProtocol(&DevicePathToText::GUID), )? .first() .expect("DevicePathToText is missing"); let device_path_to_text = boot::open_protocol_exclusive::<DevicePathToText>( device_path_to_text_handle, )?; }
This protocol isn't available for the image_handle
, so we start by
using boot::locate_handle_buffer
to find all handles that support
DevicePathToText
. We only need one handle though, so we call first()
and discard the rest. Then we call boot::open_protocol_exclusive
again. It
looks more or less like the previous time, but with DevicePathToText
as the type parameter and device_path_to_text_handle
as the handle.
Now that we have both protocols open, we can use them together to get the program's path and convert it to text:
#![allow(unused)] fn main() { let image_device_path = loaded_image.file_path().expect("File path is not set"); let image_device_path_text = device_path_to_text .convert_device_path_to_text( image_device_path, DisplayOnly(true), AllowShortcuts(false), ) .expect("convert_device_path_to_text failed"); info!("Image path: {}", &*image_device_path_text); Ok(()) } }
Since protocols do a wide range of different things, the methods available to call are very specific to each individual protocol. The best places to find out what each protocol can do are the uefi-rs reference documentation and the UEFI Specification.