diff --git a/src/proto/device_path.rs b/src/proto/device_path/mod.rs similarity index 99% rename from src/proto/device_path.rs rename to src/proto/device_path/mod.rs index 33daf5f92..cc950724d 100644 --- a/src/proto/device_path.rs +++ b/src/proto/device_path/mod.rs @@ -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; @@ -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, } diff --git a/src/proto/device_path/text.rs b/src/proto/device_path/text.rs new file mode 100644 index 000000000..38c32a543 --- /dev/null +++ b/src/proto/device_path/text.rs @@ -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 { + 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> { + 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> { + 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() } + } +} diff --git a/uefi-test-runner/src/proto/device_path.rs b/uefi-test-runner/src/proto/device_path.rs index 46cdabbe1..09d0966a5 100644 --- a/uefi-test-runner/src/proto/device_path.rs +++ b/uefi-test-runner/src/proto/device_path.rs @@ -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}; @@ -30,6 +30,16 @@ 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::() + .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::() + .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={}", @@ -37,5 +47,16 @@ pub fn test(image: Handle, bt: &BootServices) { 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); } }