diff --git a/CHANGELOG.md b/CHANGELOG.md index 6126b0a5f..4c90b5c5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Added `FilePathMediaDevicePath`. - Added `DevicePath::as_acpi_device_path` and `DevicePath::as_file_path_media_device_path`. +- Added `DevicePathInstance`, `DevicePathNode`, and `FfiDevicePath`. ### Changed @@ -21,6 +22,9 @@ time fields can be explicitly named at the call site. - The file info types now derive `PartialEq` and `Eq`. - The `FileAttributes` type is now `repr(transparent)`. +- `DevicePath` is now a DST that represents an entire device path. The + `DevicePathInstance` and `DevicePathNode` provide views of path + instances and nodes, respectively. ### Fixed diff --git a/src/proto/device_path/mod.rs b/src/proto/device_path/mod.rs index 5992e7236..bafaa05cb 100644 --- a/src/proto/device_path/mod.rs +++ b/src/proto/device_path/mod.rs @@ -1,28 +1,92 @@ -//! `DevicePath` protocol +//! Device Path protocol //! -//! Device Paths are a packed array of Device Path Nodes. Each Node -//! immediately follows the previous one, and each node may appear on -//! any byte boundary. The array must be terminated with an End of -//! Hardware Device Path Node. +//! A UEFI device path is a very flexible structure for encoding a +//! programatic path such as a hard drive or console. //! -//! Device Path Nodes are variable-length structures that can represent -//! different types of paths. For example, a File Path Media Device -//! Path contains a typical Windows-style path such as -//! "\efi\boot\bootx64.efi", whereas an ACPI Device Path contains -//! numeric ACPI IDs. +//! A device path is made up of a packed list of variable-length nodes of +//! various types. The entire device path is terminated with an +//! [`END_ENTIRE`] node. A device path _may_ contain multiple device-path +//! instances separated by [`END_INSTANCE`] nodes, but typical paths contain +//! only a single instance (in which case no `END_INSTANCE` node is needed). //! -//! A Device Path Node always starts with the `DevicePath` header. The -//! `device_type` and `sub_type` fields determine the type of data in -//! the rest of the structure, and the `length` field indicates the -//! total size of the Node including the header. +//! Example of what a device path containing two instances (each comprised of +//! three nodes) might look like: +//! +//! ```text +//! ┌──────┬─────┬──────────────╥───────┬──────────┬────────────┐ +//! │ ACPI │ PCI │ END_INSTANCE ║ CDROM │ FILEPATH │ END_ENTIRE │ +//! └──────┴─────┴──────────────╨───────┴──────────┴────────────┘ +//! ↑ ↑ ↑ +//! ├─── DevicePathInstance ────╨────── DevicePathInstance ─────┤ +//! │ │ +//! └─────────────────── Entire DevicePath ─────────────────────┘ +//! ``` +//! +//! # Types +//! +//! To represent device paths, this module provides several types: +//! +//! * [`DevicePath`] is the root type that represents a full device +//! path, containing one or more device path instance. It ends with an +//! [`END_ENTIRE`] node. It implements [`Protocol`] (corresponding to +//! `EFI_DEVICE_PATH_PROTOCOL`). +//! +//! * [`DevicePathInstance`] represents a single path instance within a +//! device path. It ends with either an [`END_INSTANCE`] or [`END_ENTIRE`] +//! node. +//! +//! * [`DevicePathNode`] represents a single node within a path. The +//! node's [`device_type`] and [`sub_type`] must be examined to +//! determine what type of data it contains. +//! +//! Specific node types have their own structures, but only a few are +//! currently implemented: +//! * [`AcpiDevicePath`] +//! * [`FilePathMediaDevicePath`] +//! +//! * [`DevicePathHeader`] is a header present at the start of every +//! node. It describes the type of node as well as the node's size. +//! +//! * [`FfiDevicePath`] is an opaque type used whenever a device path +//! pointer is passed to or from external UEFI interfaces (i.e. where +//! the UEFI spec uses `const* EFI_DEVICE_PATH_PROTOCOL`, `*const +//! FfiDevicePath` should be used in the Rust definition). Many of the +//! other types in this module are DSTs, so pointers to the type are +//! "fat" and not suitable for FFI. +//! +//! All of these types use a packed layout and may appear on any byte +//! boundary. +//! +//! Note: the API provided by this module is currently mostly limited to +//! reading existing device paths rather than constructing new ones. +//! +//! [`END_ENTIRE`]: DeviceSubType::END_ENTIRE +//! [`END_INSTANCE`]: DeviceSubType::END_INSTANCE +//! [`device_type`]: DevicePathNode::device_type +//! [`sub_type`]: DevicePathNode::sub_type pub mod text; use crate::data_types::UnalignedCStr16; -use crate::{proto::Protocol, unsafe_guid}; -use core::{mem, ptr, slice}; +use crate::proto::{Protocol, ProtocolPointer}; +use crate::unsafe_guid; +use core::ffi::c_void; +use core::marker::{PhantomData, PhantomPinned}; +use core::{mem, ptr}; + +/// Opaque type that should be used to represent a pointer to a +/// [`DevicePath`] or [`DevicePathNode`] in foreign function interfaces. This +/// type produces a thin pointer, unlike [`DevicePath`] and +/// [`DevicePathNode`]. +#[repr(C, packed)] +pub struct FfiDevicePath { + // This representation is recommended by the nomicon: + // https://doc.rust-lang.org/stable/nomicon/ffi.html#representing-opaque-structs + _data: [u8; 0], + _marker: PhantomData<(*mut u8, PhantomPinned)>, +} -/// Header that appears at the start of every [`DevicePath`] node. +/// Header that appears at the start of every [`DevicePathNode`]. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(C, packed)] pub struct DevicePathHeader { @@ -30,21 +94,46 @@ pub struct DevicePathHeader { pub device_type: DeviceType, /// Sub type of device pub sub_type: DeviceSubType, - /// Size (in bytes) of the full [`DevicePath`] instance, including this header. + /// Size (in bytes) of the [`DevicePathNode`], including this header. pub length: u16, } -/// Device path protocol. +/// A single node within a [`DevicePath`]. /// -/// This can be opened on a `LoadedImage.device()` handle using the `HandleProtocol` boot service. +/// Each node starts with a [`DevicePathHeader`]. The rest of the data +/// in the node depends on the type of node. +/// +/// See the [module-level documentation] for more details. +/// +/// [module-level documentation]: crate::proto::device_path +#[derive(Debug, Eq, PartialEq)] #[repr(C, packed)] -#[unsafe_guid("09576e91-6d3f-11d2-8e39-00a0c969723b")] -#[derive(Debug, Eq, Protocol)] -pub struct DevicePath { +pub struct DevicePathNode { header: DevicePathHeader, + data: [u8], } -impl DevicePath { +impl DevicePathNode { + /// Create a [`DevicePathNode`] reference from an opaque pointer. + /// + /// # Safety + /// + /// The input pointer must point to valid data. That data must + /// remain valid for the lifetime `'a`, and cannot be mutated during + /// that lifetime. + pub unsafe fn from_ffi_ptr<'a>(ptr: *const FfiDevicePath) -> &'a DevicePathNode { + let header = *ptr.cast::(); + + let data_len = usize::from(header.length) - mem::size_of::(); + &*ptr::from_raw_parts(ptr.cast(), data_len) + } + + /// Cast to a [`FfiDevicePath`] pointer. + pub fn as_ffi_ptr(&self) -> *const FfiDevicePath { + let ptr: *const Self = self; + ptr.cast::() + } + /// Type of device pub fn device_type(&self) -> DeviceType { self.header.device_type @@ -55,43 +144,27 @@ impl DevicePath { self.header.sub_type } - /// Size (in bytes) of the full [`DevicePath`] instance, including the header. + /// Tuple of the node's type and subtype. + pub fn full_type(&self) -> (DeviceType, DeviceSubType) { + (self.header.device_type, self.header.sub_type) + } + + /// Size (in bytes) of the full [`DevicePathNode`], including the header. pub fn length(&self) -> u16 { self.header.length } - /// True if this node ends the entire path. + /// True if this node ends an entire [`DevicePath`]. pub fn is_end_entire(&self) -> bool { - self.device_type() == DeviceType::END && self.sub_type() == DeviceSubType::END_ENTIRE - } - - /// Get an iterator over the [`DevicePath`] nodes starting at - /// `self`. Iteration ends when a path is reached where - /// [`is_end_entire`][DevicePath::is_end_entire] is true. That ending path - /// is not returned by the iterator. - pub fn iter(&self) -> DevicePathIterator { - DevicePathIterator { path: self } - } - - /// Convert to an [`AcpiDevicePath`]. Returns `None` if the node is - /// not of the appropriate type. - pub fn as_acpi_device_path(&self) -> Option<&AcpiDevicePath> { - if self.device_type() == DeviceType::ACPI && self.sub_type() == DeviceSubType::ACPI { - let p: *const DevicePath = self; - Some(unsafe { &*p.cast() }) - } else { - None - } + self.full_type() == (DeviceType::END, DeviceSubType::END_ENTIRE) } /// Convert to a [`FilePathMediaDevicePath`]. Returns `None` if the /// node is not of the appropriate type. pub fn as_file_path_media_device_path(&self) -> Option<&FilePathMediaDevicePath> { - if self.device_type() == DeviceType::MEDIA - && self.sub_type() == DeviceSubType::MEDIA_FILE_PATH - { + if self.full_type() == (DeviceType::MEDIA, DeviceSubType::MEDIA_FILE_PATH) { // Get the length of the `path_name` field. - let path_name_size_in_bytes = usize::from(self.length()) - 4; + let path_name_size_in_bytes = usize::from(self.header.length) - 4; assert!(path_name_size_in_bytes % mem::size_of::() == 0); let path_name_len = path_name_size_in_bytes / mem::size_of::(); @@ -106,56 +179,212 @@ impl DevicePath { } } -impl PartialEq for DevicePath { - fn eq(&self, other: &DevicePath) -> bool { - // Check for equality with a byte-by-byte comparison of the device - // paths. Note that this covers the entire payload of the device path - // using the `length` field in the header, so it's not the same as just - // comparing the fields of the `DevicePath` struct. - unsafe { - let self_bytes = slice::from_raw_parts( - (self as *const DevicePath).cast::(), - self.length() as usize, - ); - let other_bytes = slice::from_raw_parts( - (other as *const DevicePath).cast::(), - other.length() as usize, - ); - - self_bytes == other_bytes +/// A single device path instance that ends with either an [`END_INSTANCE`] +/// or [`END_ENTIRE`] node. Use [`DevicePath::instance_iter`] to get the +/// path instances in a [`DevicePath`]. +/// +/// See the [module-level documentation] for more details. +/// +/// [`END_ENTIRE`]: DeviceSubType::END_ENTIRE +/// [`END_INSTANCE`]: DeviceSubType::END_INSTANCE +/// [module-level documentation]: crate::proto::device_path +#[repr(C, packed)] +#[derive(Debug, Eq, PartialEq)] +pub struct DevicePathInstance { + data: [u8], +} + +impl DevicePathInstance { + /// Get an iterator over the [`DevicePathNodes`] in this + /// instance. Iteration ends when any [`DeviceType::END`] node is + /// reached. + /// + /// [`DevicePathNodes`]: DevicePathNode + pub fn node_iter(&self) -> DevicePathNodeIterator { + DevicePathNodeIterator { + nodes: &self.data, + stop_condition: StopCondition::AnyEndNode, } } } -/// Iterator over [`DevicePath`] nodes. +/// Device path protocol. +/// +/// A device path contains one or more device path instances made of up +/// variable-length nodes. It ends with an [`END_ENTIRE`] node. /// -/// Iteration ends when a path is reached where [`DevicePath::is_end_entire`] -/// is true. That ending path is not returned by the iterator. +/// See the [module-level documentation] for more details. +/// +/// [module-level documentation]: crate::proto::device_path +/// [`END_ENTIRE`]: DeviceSubType::END_ENTIRE +#[repr(C, packed)] +#[unsafe_guid("09576e91-6d3f-11d2-8e39-00a0c969723b")] +#[derive(Debug, Eq, PartialEq, Protocol)] +pub struct DevicePath { + data: [u8], +} + +impl ProtocolPointer for DevicePath { + unsafe fn ptr_from_ffi(ptr: *const c_void) -> *const Self { + ptr::from_raw_parts(ptr.cast(), Self::size_in_bytes_from_ptr(ptr)) + } + + unsafe fn mut_ptr_from_ffi(ptr: *mut c_void) -> *mut Self { + ptr::from_raw_parts_mut(ptr.cast(), Self::size_in_bytes_from_ptr(ptr)) + } +} + +impl DevicePath { + /// Calculate the size in bytes of the entire `DevicePath` starting + /// at `ptr`. This adds up each node's length, including the + /// end-entire node. + unsafe fn size_in_bytes_from_ptr(ptr: *const c_void) -> usize { + let mut ptr = ptr.cast::(); + let mut total_size_in_bytes: usize = 0; + loop { + let node = DevicePathNode::from_ffi_ptr(ptr.cast::()); + let node_size_in_bytes = usize::from(node.length()); + total_size_in_bytes += node_size_in_bytes; + if node.is_end_entire() { + break; + } + ptr = ptr.add(node_size_in_bytes); + } + + total_size_in_bytes + } + + /// Create a [`DevicePath`] reference from an opaque pointer. + /// + /// # Safety + /// + /// The input pointer must point to valid data. That data must + /// remain valid for the lifetime `'a`, and cannot be mutated during + /// that lifetime. + pub unsafe fn from_ffi_ptr<'a>(ptr: *const FfiDevicePath) -> &'a DevicePath { + &*Self::ptr_from_ffi(ptr.cast::()) + } + + /// Cast to a [`FfiDevicePath`] pointer. + pub fn as_ffi_ptr(&self) -> *const FfiDevicePath { + let p = self as *const Self; + p.cast() + } + + /// Get an iterator over the [`DevicePathInstance`]s in this path. + pub fn instance_iter(&self) -> DevicePathInstanceIterator { + DevicePathInstanceIterator { + remaining_path: Some(self), + } + } + + /// Get an iterator over the [`DevicePathNode`]s starting at + /// `self`. Iteration ends when a path is reached where + /// [`is_end_entire`][DevicePathNode::is_end_entire] is true. That ending + /// path is not returned by the iterator. + pub fn node_iter(&self) -> DevicePathNodeIterator { + DevicePathNodeIterator { + nodes: &self.data, + stop_condition: StopCondition::EndEntireNode, + } + } +} + +/// Iterator over the [`DevicePathInstance`]s in a [`DevicePath`]. /// -/// This struct is returned by [`DevicePath::iter`]. -pub struct DevicePathIterator<'a> { - path: &'a DevicePath, +/// This struct is returned by [`DevicePath::instance_iter`]. +pub struct DevicePathInstanceIterator<'a> { + remaining_path: Option<&'a DevicePath>, } -impl<'a> Iterator for DevicePathIterator<'a> { - type Item = &'a DevicePath; +impl<'a> Iterator for DevicePathInstanceIterator<'a> { + type Item = &'a DevicePathInstance; fn next(&mut self) -> Option { - let cur = self.path; + let remaining_path = self.remaining_path?; + + let mut instance_size: usize = 0; + + // Find the end of the instance, which can be either kind of end + // node (end-instance or end-entire). Count the number of bytes + // up to and including that end node. + let node_iter = DevicePathNodeIterator { + nodes: &remaining_path.data, + stop_condition: StopCondition::NoMoreNodes, + }; + for node in node_iter { + instance_size += usize::from(node.length()); + if node.device_type() == DeviceType::END { + break; + } + } - if cur.is_end_entire() { - return None; + let (head, rest) = remaining_path.data.split_at(instance_size); + + if rest.is_empty() { + self.remaining_path = None; + } else { + self.remaining_path = unsafe { + Some(&*ptr::from_raw_parts( + rest.as_ptr().cast::<()>(), + rest.len(), + )) + }; } - // Advance self.path to the next entry. - let len = cur.header.length; - let byte_ptr = (cur as *const DevicePath).cast::(); unsafe { - let next_path_ptr = byte_ptr.add(len as usize).cast::(); - self.path = &*next_path_ptr; + Some(&*ptr::from_raw_parts( + head.as_ptr().cast::<()>(), + head.len(), + )) } + } +} + +enum StopCondition { + AnyEndNode, + EndEntireNode, + NoMoreNodes, +} + +/// Iterator over [`DevicePathNode`]s. +/// +/// This struct is returned by [`DevicePath::node_iter`] and +/// [`DevicePathInstance::node_iter`]. +pub struct DevicePathNodeIterator<'a> { + nodes: &'a [u8], + stop_condition: StopCondition, +} - Some(cur) +impl<'a> Iterator for DevicePathNodeIterator<'a> { + type Item = &'a DevicePathNode; + + fn next(&mut self) -> Option { + if self.nodes.is_empty() { + return None; + } + + let node = + unsafe { DevicePathNode::from_ffi_ptr(self.nodes.as_ptr().cast::()) }; + + // Check if an early stop condition has been reached. + let stop = match self.stop_condition { + StopCondition::AnyEndNode => node.device_type() == DeviceType::END, + StopCondition::EndEntireNode => node.is_end_entire(), + StopCondition::NoMoreNodes => false, + }; + + if stop { + // Clear the remaining node data so that future calls to + // next() immediately return `None`. + self.nodes = &[]; + None + } else { + // Advance to next node. + let node_size = usize::from(node.length()); + self.nodes = &self.nodes[node_size..]; + Some(node) + } } } @@ -354,11 +583,116 @@ impl FilePathMediaDevicePath { mod tests { use super::*; use crate::CString16; + use alloc_api::vec::Vec; + + /// Create a node to `path` from raw data. + fn add_node(path: &mut Vec, device_type: u8, sub_type: u8, node_data: &[u8]) { + path.push(device_type); + path.push(sub_type); + path.extend( + u16::try_from(mem::size_of::() + node_data.len()) + .unwrap() + .to_le_bytes(), + ); + path.extend(node_data); + } + + /// Create a test device path list as raw bytes. + fn create_raw_device_path() -> Vec { + let mut raw_data = Vec::new(); + + // First path instance. + add_node(&mut raw_data, 0xa0, 0xb0, &[10, 11]); + add_node(&mut raw_data, 0xa1, 0xb1, &[20, 21, 22, 23]); + add_node( + &mut raw_data, + DeviceType::END.0, + DeviceSubType::END_INSTANCE.0, + &[], + ); + // Second path instance. + add_node(&mut raw_data, 0xa2, 0xb2, &[30, 31]); + add_node(&mut raw_data, 0xa3, 0xb3, &[40, 41, 42, 43]); + add_node( + &mut raw_data, + DeviceType::END.0, + DeviceSubType::END_ENTIRE.0, + &[], + ); + + raw_data + } + + /// Check that `node` has the expected content. + fn check_node(node: &DevicePathNode, device_type: u8, sub_type: u8, node_data: &[u8]) { + assert_eq!(node.device_type().0, device_type); + assert_eq!(node.sub_type().0, sub_type); + assert_eq!( + node.length(), + u16::try_from(mem::size_of::() + node_data.len()).unwrap() + ); + assert_eq!(&node.data, node_data); + } + + #[test] + fn test_device_path_nodes() { + let raw_data = create_raw_device_path(); + let dp = unsafe { DevicePath::from_ffi_ptr(raw_data.as_ptr().cast()) }; + + // Check that the size is the sum of the nodes' lengths. + assert_eq!(mem::size_of_val(dp), 6 + 8 + 4 + 6 + 8 + 4); + + // Check the list's node iter. + let nodes: Vec<_> = dp.node_iter().collect(); + check_node(nodes[0], 0xa0, 0xb0, &[10, 11]); + check_node(nodes[1], 0xa1, 0xb1, &[20, 21, 22, 23]); + check_node( + nodes[2], + DeviceType::END.0, + DeviceSubType::END_INSTANCE.0, + &[], + ); + check_node(nodes[3], 0xa2, 0xb2, &[30, 31]); + check_node(nodes[4], 0xa3, 0xb3, &[40, 41, 42, 43]); + // The end-entire node is not returned by the iterator. + assert_eq!(nodes.len(), 5); + } + + #[test] + fn test_device_path_instances() { + let raw_data = create_raw_device_path(); + let dp = unsafe { DevicePath::from_ffi_ptr(raw_data.as_ptr().cast()) }; + + // Check the list's instance iter. + let mut iter = dp.instance_iter(); + let mut instance = iter.next().unwrap(); + assert_eq!(mem::size_of_val(instance), 6 + 8 + 4); + + // Check the first instance's node iter. + let nodes: Vec<_> = instance.node_iter().collect(); + check_node(nodes[0], 0xa0, 0xb0, &[10, 11]); + check_node(nodes[1], 0xa1, 0xb1, &[20, 21, 22, 23]); + // The end node is not returned by the iterator. + assert_eq!(nodes.len(), 2); + + // Check second instance. + instance = iter.next().unwrap(); + assert_eq!(mem::size_of_val(instance), 6 + 8 + 4); + + let nodes: Vec<_> = instance.node_iter().collect(); + check_node(nodes[0], 0xa2, 0xb2, &[30, 31]); + check_node(nodes[1], 0xa3, 0xb3, &[40, 41, 42, 43]); + // The end node is not returned by the iterator. + assert_eq!(nodes.len(), 2); + + // Only two instances. + assert!(iter.next().is_none()); + } #[test] fn test_file_path_media() { // Manually create data for a `FilePathMediaDevicePath` node. - let mut raw_data: [u16; 7] = [ + let raw_data: [u16; 7] = [ // MEDIA | MEDIA_FILE_PATH 0x0404, // Length @@ -372,7 +706,7 @@ mod tests { ]; // Convert the raw data to a `DevicePath` node. - let dp: &DevicePath = unsafe { &*raw_data.as_mut_ptr().cast() }; + let dp = unsafe { DevicePathNode::from_ffi_ptr(raw_data.as_ptr().cast()) }; assert_eq!(dp.length(), 14); // Check that the `file_name` is correct. diff --git a/src/proto/device_path/text.rs b/src/proto/device_path/text.rs index 38c32a543..9930ea21f 100644 --- a/src/proto/device_path/text.rs +++ b/src/proto/device_path/text.rs @@ -1,7 +1,8 @@ //! `DevicePathToText` and `DevicePathFromText` Protocol use crate::{ - proto::{device_path::DevicePath, Protocol}, + proto::device_path::{DevicePath, DevicePathNode, FfiDevicePath}, + proto::Protocol, table::boot::BootServices, unsafe_guid, CStr16, Char16, }; @@ -77,12 +78,12 @@ impl Drop for PoolString<'_> { #[derive(Protocol)] pub struct DevicePathToText { convert_device_node_to_text: unsafe extern "efiapi" fn( - device_node: *const DevicePath, + device_node: *const FfiDevicePath, display_only: bool, allow_shortcuts: bool, ) -> *const Char16, convert_device_path_to_text: unsafe extern "efiapi" fn( - device_path: *const DevicePath, + device_path: *const FfiDevicePath, display_only: bool, allow_shortcuts: bool, ) -> *const Char16, @@ -96,12 +97,16 @@ impl DevicePathToText { pub fn convert_device_node_to_text<'boot>( &self, boot_services: &'boot BootServices, - device_node: &DevicePath, + device_node: &DevicePathNode, 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) + (self.convert_device_node_to_text)( + device_node.as_ffi_ptr(), + display_only.0, + allow_shortcuts.0, + ) }; PoolString::new(boot_services, text_device_node) } @@ -118,7 +123,11 @@ impl DevicePathToText { allow_shortcuts: AllowShortcuts, ) -> Option> { let text_device_path = unsafe { - (self.convert_device_path_to_text)(device_path, display_only.0, allow_shortcuts.0) + (self.convert_device_path_to_text)( + device_path.as_ffi_ptr(), + display_only.0, + allow_shortcuts.0, + ) }; PoolString::new(boot_services, text_device_path) } @@ -133,9 +142,9 @@ impl DevicePathToText { #[derive(Protocol)] pub struct DevicePathFromText { convert_text_to_device_node: - unsafe extern "efiapi" fn(text_device_node: *const Char16) -> *const DevicePath, + unsafe extern "efiapi" fn(text_device_node: *const Char16) -> *const FfiDevicePath, convert_text_to_device_path: - unsafe extern "efiapi" fn(text_device_path: *const Char16) -> *const DevicePath, + unsafe extern "efiapi" fn(text_device_path: *const Char16) -> *const FfiDevicePath, } impl DevicePathFromText { @@ -147,8 +156,18 @@ impl DevicePathFromText { /// /// 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() } + pub fn convert_text_to_device_node( + &self, + text_device_node: &CStr16, + ) -> Option<&DevicePathNode> { + unsafe { + let ptr = (self.convert_text_to_device_node)(text_device_node.as_ptr()); + if ptr.is_null() { + None + } else { + Some(DevicePathNode::from_ffi_ptr(ptr)) + } + } } /// Convert a text to its binary device path representation. @@ -160,6 +179,13 @@ impl DevicePathFromText { /// 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() } + unsafe { + let ptr = (self.convert_text_to_device_path)(text_device_path.as_ptr()); + if ptr.is_null() { + None + } else { + Some(DevicePath::from_ffi_ptr(ptr)) + } + } } } diff --git a/src/proto/loaded_image.rs b/src/proto/loaded_image.rs index cd2ce0757..d90037b79 100644 --- a/src/proto/loaded_image.rs +++ b/src/proto/loaded_image.rs @@ -2,7 +2,8 @@ use crate::{ data_types::FromSliceWithNulError, - proto::{device_path::DevicePath, Protocol}, + proto::device_path::{DevicePath, FfiDevicePath}, + proto::Protocol, table::boot::MemoryType, unsafe_guid, CStr16, Handle, Status, }; @@ -19,7 +20,7 @@ pub struct LoadedImage { // Source location of the image device_handle: Handle, - file_path: *const DevicePath, + file_path: *const FfiDevicePath, _reserved: *const c_void, // Image load options @@ -60,7 +61,11 @@ impl LoadedImage { /// Return `None` if the pointer to the file path portion specific to /// DeviceHandle that the EFI Image was loaded from is null. pub fn file_path(&self) -> Option<&DevicePath> { - unsafe { self.file_path.as_ref() } + if self.file_path.is_null() { + None + } else { + unsafe { Some(DevicePath::from_ffi_ptr(self.file_path)) } + } } /// Get the load options of the image as a [`&CStr16`]. diff --git a/src/proto/mod.rs b/src/proto/mod.rs index b7800f386..a87b7bf8e 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -10,6 +10,7 @@ //! [`BootServices`]: crate::table::boot::BootServices#accessing-protocols use crate::Identify; +use core::ffi::c_void; /// Common trait implemented by all standard UEFI protocols /// @@ -27,6 +28,40 @@ use crate::Identify; /// ``` pub trait Protocol: Identify {} +/// Trait for creating a protocol pointer from a [`c_void`] pointer. +/// +/// There is a blanket implementation for all [`Sized`] protocols that +/// simply casts the pointer to the appropriate type. Protocols that +/// are not sized must provide a custom implementation. +pub trait ProtocolPointer: Protocol { + /// Create a const pointer to a [`Protocol`] from a [`c_void`] pointer. + /// + /// # Safety + /// + /// The input pointer must point to valid data. + unsafe fn ptr_from_ffi(ptr: *const c_void) -> *const Self; + + /// Create a mutable pointer to a [`Protocol`] from a [`c_void`] pointer. + /// + /// # Safety + /// + /// The input pointer must point to valid data. + unsafe fn mut_ptr_from_ffi(ptr: *mut c_void) -> *mut Self; +} + +impl

ProtocolPointer for P +where + P: Protocol, +{ + unsafe fn ptr_from_ffi(ptr: *const c_void) -> *const Self { + ptr.cast::() + } + + unsafe fn mut_ptr_from_ffi(ptr: *mut c_void) -> *mut Self { + ptr.cast::() + } +} + pub use uefi_macros::Protocol; pub mod console; diff --git a/src/table/boot.rs b/src/table/boot.rs index 63353d10b..cd1d4463f 100644 --- a/src/table/boot.rs +++ b/src/table/boot.rs @@ -2,9 +2,10 @@ use super::Header; use crate::data_types::Align; -use crate::proto::{device_path::DevicePath, Protocol}; +use crate::proto::device_path::{DevicePath, FfiDevicePath}; #[cfg(feature = "exts")] use crate::proto::{loaded_image::LoadedImage, media::fs::SimpleFileSystem}; +use crate::proto::{Protocol, ProtocolPointer}; use crate::{Char16, Event, Guid, Handle, Result, Status}; #[cfg(feature = "exts")] use alloc_api::vec::Vec; @@ -127,7 +128,7 @@ pub struct BootServices { ) -> Status, locate_device_path: unsafe extern "efiapi" fn( proto: &Guid, - device_path: &mut &DevicePath, + device_path: &mut *const FfiDevicePath, out_handle: &mut MaybeUninit, ) -> Status, install_configuration_table: usize, @@ -136,7 +137,7 @@ pub struct BootServices { load_image: unsafe extern "efiapi" fn( boot_policy: u8, parent_image_handle: Handle, - device_path: *const DevicePath, + device_path: *const FfiDevicePath, source_buffer: *const u8, source_size: usize, image_handle: &mut MaybeUninit, @@ -170,7 +171,7 @@ pub struct BootServices { connect_controller: unsafe extern "efiapi" fn( controller: Handle, driver_image: Option, - remaining_device_path: *const DevicePath, + remaining_device_path: *const FfiDevicePath, recursive: bool, ) -> Status, disconnect_controller: unsafe extern "efiapi" fn( @@ -569,11 +570,14 @@ impl BootServices { /// /// [`open_protocol`]: BootServices::open_protocol #[deprecated(note = "it is recommended to use `open_protocol` instead")] - pub fn handle_protocol(&self, handle: Handle) -> Result<&UnsafeCell

> { + pub fn handle_protocol( + &self, + handle: Handle, + ) -> Result<&UnsafeCell

> { let mut ptr = ptr::null_mut(); - (self.handle_protocol)(handle, &P::GUID, &mut ptr).into_with_val(|| { - let ptr = ptr.cast::>(); - unsafe { &*ptr } + (self.handle_protocol)(handle, &P::GUID, &mut ptr).into_with_val(|| unsafe { + let ptr = P::mut_ptr_from_ffi(ptr) as *const UnsafeCell

; + &*ptr }) } @@ -626,9 +630,14 @@ impl BootServices { /// is a multi-instance device path, the function will operate on the first instance. pub fn locate_device_path(&self, device_path: &mut &DevicePath) -> Result { let mut handle = MaybeUninit::uninit(); + let mut device_path_ptr = device_path.as_ffi_ptr(); unsafe { - (self.locate_device_path)(&P::GUID, device_path, &mut handle) - .into_with_val(|| handle.assume_init()) + (self.locate_device_path)(&P::GUID, &mut device_path_ptr, &mut handle).into_with_val( + || { + *device_path = DevicePath::from_ffi_ptr(device_path_ptr); + handle.assume_init() + }, + ) } } @@ -664,7 +673,7 @@ impl BootServices { // Boot policy is ignored when loading from source buffer. boot_policy = 0; - device_path = file_path.map(|p| p as _).unwrap_or(ptr::null()); + device_path = file_path.map(|p| p.as_ffi_ptr()).unwrap_or(ptr::null()); source_buffer = buffer.as_ptr(); source_size = buffer.len(); } @@ -673,7 +682,7 @@ impl BootServices { from_boot_manager, } => { boot_policy = u8::from(from_boot_manager); - device_path = file_path; + device_path = file_path.as_ffi_ptr(); source_buffer = ptr::null(); source_size = 0; } @@ -811,7 +820,7 @@ impl BootServices { controller, driver_image, remaining_device_path - .map(|dp| dp as _) + .map(|dp| dp.as_ffi_ptr()) .unwrap_or(ptr::null()), recursive, ) @@ -852,7 +861,7 @@ impl BootServices { /// global `HashSet`. /// /// [`handle_protocol`]: BootServices::handle_protocol - pub fn open_protocol( + pub fn open_protocol( &self, params: OpenProtocolParams, attributes: OpenProtocolAttributes, @@ -866,14 +875,12 @@ impl BootServices { params.controller, attributes as u32, ) - .into_with_val(|| { - let interface = interface.cast::>(); - unsafe { - ScopedProtocol { - interface: &*interface, - open_params: params, - boot_services: self, - } + .into_with_val(|| unsafe { + let interface = P::mut_ptr_from_ffi(interface) as *const UnsafeCell

; + ScopedProtocol { + interface: &*interface, + open_params: params, + boot_services: self, } }) } @@ -944,11 +951,11 @@ impl BootServices { /// Returns a protocol implementation, if present on the system. /// /// The caveats of `BootServices::handle_protocol()` also apply here. - pub fn locate_protocol(&self) -> Result<&UnsafeCell

> { + pub fn locate_protocol(&self) -> Result<&UnsafeCell

> { let mut ptr = ptr::null_mut(); - (self.locate_protocol)(&P::GUID, ptr::null_mut(), &mut ptr).into_with_val(|| { - let ptr = ptr.cast::>(); - unsafe { &*ptr } + (self.locate_protocol)(&P::GUID, ptr::null_mut(), &mut ptr).into_with_val(|| unsafe { + let ptr = P::mut_ptr_from_ffi(ptr) as *const UnsafeCell

; + &*ptr }) } @@ -1314,7 +1321,7 @@ pub struct OpenProtocolParams { /// /// See also the [`BootServices`] documentation for details of how to open a /// protocol and why [`UnsafeCell`] is used. -pub struct ScopedProtocol<'a, P: Protocol> { +pub struct ScopedProtocol<'a, P: Protocol + ?Sized> { /// The protocol interface. pub interface: &'a UnsafeCell

, @@ -1322,7 +1329,7 @@ pub struct ScopedProtocol<'a, P: Protocol> { boot_services: &'a BootServices, } -impl<'a, P: Protocol> Drop for ScopedProtocol<'a, P> { +impl<'a, P: Protocol + ?Sized> Drop for ScopedProtocol<'a, P> { fn drop(&mut self) { let status = (self.boot_services.close_protocol)( self.open_params.handle, diff --git a/uefi-test-runner/src/proto/device_path.rs b/uefi-test-runner/src/proto/device_path.rs index 09d0966a5..3c783e141 100644 --- a/uefi-test-runner/src/proto/device_path.rs +++ b/uefi-test-runner/src/proto/device_path.rs @@ -40,7 +40,7 @@ pub fn test(image: Handle, bt: &BootServices) { .expect("Failed to open DevicePathFromText protocol"); let device_path_from_text = unsafe { &*device_path_from_text.get() }; - for path in device_path.iter() { + for path in device_path.node_iter() { info!( "path: type={:?}, subtype={:?}, length={}", path.device_type(), @@ -49,13 +49,13 @@ pub fn test(image: Handle, bt: &BootServices) { ); let text = device_path_to_text - .convert_device_path_to_text(bt, path, DisplayOnly(true), AllowShortcuts(false)) + .convert_device_node_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) + .convert_text_to_device_node(text) .expect("Failed to convert text to device path"); assert_eq!(path, convert); } diff --git a/xtask/src/cargo.rs b/xtask/src/cargo.rs index d74893d1a..6ee5943ae 100644 --- a/xtask/src/cargo.rs +++ b/xtask/src/cargo.rs @@ -129,6 +129,7 @@ impl Cargo { } } CargoAction::Miri => { + cmd.env("MIRIFLAGS", "-Zmiri-tag-raw-pointers"); action = "miri"; sub_action = Some("test"); }