Using Protocols
About UEFI Protocols
UEFI protocols are a structured collection of functions and/or data. Please head to the module documentation in uefi for more technical information.
Usage in uefi-rs
To 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 core::time::Duration; 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(Duration::from_secs(10)); 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.