Skip to content

DevicePathToText and DevicePathFromText Protocol #402

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/proto/device_path.rs → src/proto/device_path/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
//! the rest of the structure, and the `length` field indicates the
//! total size of the Node including the header.

pub mod text;

use crate::{proto::Protocol, unsafe_guid};
use core::slice;

Expand All @@ -36,7 +38,7 @@ pub struct DevicePathHeader {
/// This can be opened on a `LoadedImage.device()` handle using the `HandleProtocol` boot service.
#[repr(C, packed)]
#[unsafe_guid("09576e91-6d3f-11d2-8e39-00a0c969723b")]
#[derive(Eq, Protocol)]
#[derive(Debug, Eq, Protocol)]
pub struct DevicePath {
header: DevicePathHeader,
}
Expand Down
165 changes: 165 additions & 0 deletions src/proto/device_path/text.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
//! `DevicePathToText` and `DevicePathFromText` Protocol

use crate::{
proto::{device_path::DevicePath, Protocol},
table::boot::BootServices,
unsafe_guid, CStr16, Char16,
};
use core::ops::Deref;

/// This struct is a wrapper of `display_only` parameter
/// used by Device Path to Text protocol.
///
/// The `display_only` parameter controls whether the longer
/// (parseable) or shorter (display-only) form of the conversion
/// is used. If `display_only` is TRUE, then the shorter text
/// representation of the display node is used, where applicable.
/// If `display_only` is FALSE, then the longer text representation
/// of the display node is used.
#[derive(Clone, Copy)]
pub struct DisplayOnly(pub bool);

/// This struct is a wrapper of `allow_shortcuts` parameter
/// used by Device Path to Text protocol.
///
/// The `allow_shortcuts` is FALSE, then the shortcut forms of
/// text representation for a device node cannot be used. A
/// shortcut form is one which uses information other than the
/// type or subtype. If `allow_shortcuts is TRUE, then the
/// shortcut forms of text representation for a device node
/// can be used, where applicable.
#[derive(Clone, Copy)]
pub struct AllowShortcuts(pub bool);

/// Wrapper for a string internally allocated from
/// UEFI boot services memory.
pub struct PoolString<'a> {
boot_services: &'a BootServices,
text: *const Char16,
}

impl<'a> PoolString<'a> {
fn new(boot_services: &'a BootServices, text: *const Char16) -> Option<Self> {
if text.is_null() {
None
} else {
Some(Self {
boot_services,
text,
})
}
}
}

impl<'a> Deref for PoolString<'a> {
type Target = CStr16;

fn deref(&self) -> &Self::Target {
unsafe { CStr16::from_ptr(self.text) }
}
}

impl Drop for PoolString<'_> {
fn drop(&mut self) {
let addr = self.text as *mut u8;
self.boot_services
.free_pool(addr)
.expect("Failed to free pool [{addr:#?}]");
}
}

/// Device Path to Text protocol.
///
/// This protocol provides common utility functions for converting device
/// nodes and device paths to a text representation.
#[repr(C)]
#[unsafe_guid("8b843e20-8132-4852-90cc-551a4e4a7f1c")]
#[derive(Protocol)]
pub struct DevicePathToText {
convert_device_node_to_text: unsafe extern "efiapi" fn(
device_node: *const DevicePath,
display_only: bool,
allow_shortcuts: bool,
) -> *const Char16,
convert_device_path_to_text: unsafe extern "efiapi" fn(
device_path: *const DevicePath,
display_only: bool,
allow_shortcuts: bool,
) -> *const Char16,
}

impl DevicePathToText {
/// Convert a device node to its text representation.
///
/// Returns `None` if `device_node` was NULL or there was
/// insufficient memory.
pub fn convert_device_node_to_text<'boot>(
&self,
boot_services: &'boot BootServices,
device_node: &DevicePath,
display_only: DisplayOnly,
allow_shortcuts: AllowShortcuts,
) -> Option<PoolString<'boot>> {
let text_device_node = unsafe {
(self.convert_device_node_to_text)(device_node, display_only.0, allow_shortcuts.0)
};
PoolString::new(boot_services, text_device_node)
}

/// Convert a device path to its text representation.
///
/// Returns `None` if `device_path` was NULL or there was
/// insufficient memory.
pub fn convert_device_path_to_text<'boot>(
&self,
boot_services: &'boot BootServices,
device_path: &DevicePath,
display_only: DisplayOnly,
allow_shortcuts: AllowShortcuts,
) -> Option<PoolString<'boot>> {
let text_device_path = unsafe {
(self.convert_device_path_to_text)(device_path, display_only.0, allow_shortcuts.0)
};
PoolString::new(boot_services, text_device_path)
}
}

/// Device Path from Text protocol.
///
/// This protocol provides common utilities for converting text to
/// device paths and device nodes.
#[repr(C)]
#[unsafe_guid("05c99a21-c70f-4ad2-8a5f-35df3343f51e")]
#[derive(Protocol)]
pub struct DevicePathFromText {
convert_text_to_device_node:
unsafe extern "efiapi" fn(text_device_node: *const Char16) -> *const DevicePath,
convert_text_to_device_path:
unsafe extern "efiapi" fn(text_device_path: *const Char16) -> *const DevicePath,
}

impl DevicePathFromText {
/// Convert text to the binary representation of a device node.
///
/// `text_device_node` is the text representation of a device node.
/// Conversion starts with the first character and continues until
/// the first non-device node character.
///
/// Returns `None` if `text_device_node` was NULL or there was
/// insufficient memory.
pub fn convert_text_to_device_node(&self, text_device_node: &CStr16) -> Option<&DevicePath> {
unsafe { (self.convert_text_to_device_node)(text_device_node.as_ptr()).as_ref() }
}

/// Convert a text to its binary device path representation.
///
/// `text_device_path` is the text representation of a device path.
/// Conversion starts with the first character and continues until
/// the first non-device path character.
///
/// Returns `None` if `text_device_path` was NULL or there was
/// insufficient memory.
pub fn convert_text_to_device_path(&self, text_device_path: &CStr16) -> Option<&DevicePath> {
unsafe { (self.convert_text_to_device_path)(text_device_path.as_ptr()).as_ref() }
}
}
23 changes: 22 additions & 1 deletion uefi-test-runner/src/proto/device_path.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use uefi::prelude::*;
use uefi::proto::device_path::DevicePath;
use uefi::proto::device_path::{text::*, DevicePath};
use uefi::proto::loaded_image::LoadedImage;
use uefi::table::boot::{BootServices, OpenProtocolAttributes, OpenProtocolParams};

Expand Down Expand Up @@ -30,12 +30,33 @@ pub fn test(image: Handle, bt: &BootServices) {
.expect("Failed to open DevicePath protocol");
let device_path = unsafe { &*device_path.interface.get() };

let device_path_to_text = bt
.locate_protocol::<DevicePathToText>()
.expect("Failed to open DevicePathToText protocol");
let device_path_to_text = unsafe { &*device_path_to_text.get() };

let device_path_from_text = bt
.locate_protocol::<DevicePathFromText>()
.expect("Failed to open DevicePathFromText protocol");
let device_path_from_text = unsafe { &*device_path_from_text.get() };

for path in device_path.iter() {
info!(
"path: type={:?}, subtype={:?}, length={}",
path.device_type(),
path.sub_type(),
path.length(),
);

let text = device_path_to_text
.convert_device_path_to_text(bt, path, DisplayOnly(true), AllowShortcuts(false))
.expect("Failed to convert device path to text");
let text = &*text;
info!("path name: {text}");

let convert = device_path_from_text
.convert_text_to_device_path(text)
.expect("Failed to convert text to device path");
assert_eq!(path, convert);
}
}