use alloc::{boxed::Box, format, string::String, vec, vec::Vec};
use core::{mem::size_of_val, ptr::slice_from_raw_parts, slice::from_raw_parts};
use r_efi::protocols::device_path::{End, Hardware, Media};
use r_efi::efi;
pub fn device_path_node_count(
device_path: *const efi::protocols::device_path::Protocol,
) -> Result<(usize, usize), efi::Status> {
let mut node_count = 0;
let mut dev_path_size: usize = 0;
let mut current_node_ptr = device_path;
if current_node_ptr.is_null() {
debug_assert!(!current_node_ptr.is_null());
return Err(efi::Status::INVALID_PARAMETER);
}
loop {
let current_node = unsafe { current_node_ptr.read_unaligned() };
let current_length: usize = u16::from_le_bytes(current_node.length).into();
node_count += 1;
dev_path_size += current_length;
if current_node.r#type == efi::protocols::device_path::TYPE_END {
break;
}
let offset = current_length.try_into().map_err(|_| efi::Status::INVALID_PARAMETER)?;
current_node_ptr = unsafe { current_node_ptr.byte_offset(offset) };
}
Ok((node_count, dev_path_size))
}
pub fn copy_device_path_to_boxed_slice(
device_path: *const efi::protocols::device_path::Protocol,
) -> Result<Box<[u8]>, efi::Status> {
let dp_slice = device_path_as_slice(device_path)?;
Ok(dp_slice.to_vec().into_boxed_slice())
}
pub fn device_path_as_slice(
device_path: *const efi::protocols::device_path::Protocol,
) -> Result<&'static [u8], efi::Status> {
let (_, byte_count) = device_path_node_count(device_path)?;
unsafe { Ok(from_raw_parts(device_path as *const u8, byte_count)) }
}
pub unsafe fn remaining_device_path(
a: *const efi::protocols::device_path::Protocol,
b: *const efi::protocols::device_path::Protocol,
) -> Option<(*const efi::protocols::device_path::Protocol, usize)> {
let mut a_ptr = a;
let mut b_ptr = b;
let mut node_count = 0;
loop {
let (a_node, b_node) = unsafe { (*a_ptr, *b_ptr) };
if unsafe { is_device_path_end(&a_node) } {
return Some((b_ptr, node_count));
}
node_count += 1;
let a_length: usize = u16::from_le_bytes(a_node.length).into();
let b_length: usize = u16::from_le_bytes(b_node.length).into();
let a_slice = unsafe { slice_from_raw_parts(a_ptr as *const u8, a_length).as_ref() };
let b_slice = unsafe { slice_from_raw_parts(b_ptr as *const u8, b_length).as_ref() };
if a_slice != b_slice {
return None;
}
let a_offset: isize = a_length.try_into().ok()?;
let b_offset: isize = b_length.try_into().ok()?;
a_ptr = unsafe { a_ptr.byte_offset(a_offset) };
b_ptr = unsafe { b_ptr.byte_offset(b_offset) };
}
}
pub unsafe fn is_device_path_end(device_path: *const efi::protocols::device_path::Protocol) -> bool {
let node_ptr = device_path;
if let Some(device_path_node) = unsafe { node_ptr.as_ref() } {
device_path_node.r#type == efi::protocols::device_path::TYPE_END
&& device_path_node.sub_type == efi::protocols::device_path::End::SUBTYPE_ENTIRE
} else {
true
}
}
pub fn concat_device_path_to_boxed_slice(
a: *const efi::protocols::device_path::Protocol,
b: *const efi::protocols::device_path::Protocol,
) -> Result<Box<[u8]>, efi::Status> {
let a_slice = device_path_as_slice(a)?;
let b_slice = device_path_as_slice(b)?;
let end_path_size = core::mem::size_of::<efi::protocols::device_path::End>();
let mut out_bytes = vec![0u8; a_slice.len() + b_slice.len() - end_path_size];
out_bytes[..a_slice.len()].copy_from_slice(a_slice);
out_bytes[a_slice.len() - end_path_size..].copy_from_slice(b_slice);
Ok(out_bytes.into_boxed_slice())
}
#[derive(Debug)]
pub struct GenericDevicePathNode {
header: efi::protocols::device_path::Protocol,
data: Vec<u8>,
}
impl PartialEq for GenericDevicePathNode {
fn eq(&self, other: &Self) -> bool {
self.header.r#type == other.header.r#type
&& self.header.sub_type == other.header.sub_type
&& self.data == other.data
}
}
impl Eq for GenericDevicePathNode {}
impl GenericDevicePathNode {
pub unsafe fn new(node: *const efi::protocols::device_path::Protocol) -> Option<Self> {
let header = unsafe { core::ptr::read_unaligned(node) };
let node_len = u16::from_le_bytes(header.length);
let data_len = node_len.checked_sub(size_of_val(&header).try_into().ok()?)?;
let data_ptr = unsafe { node.byte_offset(size_of_val(&header).try_into().ok()?) } as *const u8;
let data = unsafe { from_raw_parts(data_ptr, data_len.into()).to_vec() };
Some(Self { header, data })
}
#[inline]
pub fn header(&self) -> &efi::protocols::device_path::Protocol {
&self.header
}
#[inline]
pub fn data(&self) -> &[u8] {
&self.data
}
fn len(&self) -> u16 {
u16::from_le_bytes(self.header.length)
}
}
pub struct DevicePathWalker {
next_node: Option<*const efi::protocols::device_path::Protocol>,
}
impl From<DevicePathWalker> for String {
fn from(device_path_walker: DevicePathWalker) -> Self {
let mut result = String::new();
for node in device_path_walker {
if unsafe { is_device_path_end(&node.header) } {
break;
}
result.push_str(protocol_to_subtype_str(node.header));
if !node.data.is_empty() {
result.push_str(": ");
for (i, byte) in node.data.iter().enumerate() {
if i > 0 {
result.push(',');
}
result.push_str(&format!("0x{byte:02x}"));
}
result.push('/');
}
}
result
}
}
impl DevicePathWalker {
pub unsafe fn new(device_path: *const efi::protocols::device_path::Protocol) -> Self {
Self { next_node: Some(device_path) }
}
}
impl Iterator for DevicePathWalker {
type Item = GenericDevicePathNode;
fn next(&mut self) -> Option<Self::Item> {
match self.next_node {
Some(node) => {
let current = unsafe { GenericDevicePathNode::new(node)? };
if unsafe { is_device_path_end(node) } {
self.next_node = None;
} else {
self.next_node = Some(unsafe { node.byte_offset(current.len().try_into().ok()?) });
}
Some(current)
}
None => None,
}
}
}
fn protocol_to_subtype_str(protocol: efi::protocols::device_path::Protocol) -> &'static str {
match protocol.r#type {
r_efi::protocols::device_path::TYPE_HARDWARE => match protocol.sub_type {
Hardware::SUBTYPE_PCI => "Pci",
Hardware::SUBTYPE_PCCARD => "PcCard",
Hardware::SUBTYPE_MMAP => "MemMap",
Hardware::SUBTYPE_VENDOR => "Vendor",
Hardware::SUBTYPE_CONTROLLER => "Controller",
Hardware::SUBTYPE_BMC => "Bmc",
_ => "UnknownHardware",
},
r_efi::protocols::device_path::TYPE_ACPI => "Acpi",
r_efi::protocols::device_path::TYPE_MESSAGING => "Msg",
r_efi::protocols::device_path::TYPE_BIOS => "Bios",
r_efi::protocols::device_path::TYPE_MEDIA => match protocol.sub_type {
Media::SUBTYPE_HARDDRIVE => "HardDrive",
Media::SUBTYPE_CDROM => "CdRom",
Media::SUBTYPE_VENDOR => "Vendor",
Media::SUBTYPE_FILE_PATH => "FilePath",
Media::SUBTYPE_MEDIA_PROTOCOL => "MediaProtocol",
Media::SUBTYPE_PIWG_FIRMWARE_FILE => "FirmwareFile",
Media::SUBTYPE_PIWG_FIRMWARE_VOLUME => "FirmwareVolume",
Media::SUBTYPE_RELATIVE_OFFSET_RANGE => "RelativeOffsetRange",
Media::SUBTYPE_RAM_DISK => "RamDisk",
_ => "UnknownMedia",
},
r_efi::protocols::device_path::TYPE_END => match protocol.sub_type {
End::SUBTYPE_INSTANCE => "EndInstance",
End::SUBTYPE_ENTIRE => "EndEntire",
_ => "UnknownEnd",
},
_ => "UnknownType",
}
}
#[cfg(test)]
#[coverage(off)]
mod tests {
use core::mem::size_of;
use efi::protocols::device_path::{End, Hardware, TYPE_END, TYPE_HARDWARE};
use r_efi::protocols::device_path::{TYPE_ACPI, TYPE_MEDIA};
use super::*;
#[test]
fn device_path_node_count_should_return_the_right_number_of_nodes_and_length() {
let device_path_bytes = [
TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x1C, TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x0, TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x2, 0x0, TYPE_END,
End::SUBTYPE_ENTIRE,
0x4, 0x00, ];
let device_path_ptr = device_path_bytes.as_ptr() as *const efi::protocols::device_path::Protocol;
let (nodes, length) = device_path_node_count(device_path_ptr).unwrap();
assert_eq!(nodes, 4);
assert_eq!(length, device_path_bytes.len());
}
#[test]
fn remaining_device_path_should_return_remaining_device_path() {
let device_path_a_bytes = [
TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x1C, TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x0, TYPE_END,
End::SUBTYPE_ENTIRE,
0x4, 0x00, ];
let device_path_a = device_path_a_bytes.as_ptr() as *const efi::protocols::device_path::Protocol;
let device_path_b_bytes = [
TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x1C, TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x0, TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x2, 0x0, TYPE_END,
End::SUBTYPE_ENTIRE,
0x4, 0x00, ];
let device_path_b = device_path_b_bytes.as_ptr() as *const efi::protocols::device_path::Protocol;
let device_path_c_bytes = [
TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x0A, TYPE_END,
End::SUBTYPE_ENTIRE,
0x4, 0x00, ];
let device_path_c = device_path_c_bytes.as_ptr() as *const efi::protocols::device_path::Protocol;
let result = unsafe { remaining_device_path(device_path_a, device_path_b) };
assert!(result.is_some());
let result = result.unwrap();
let a_path_length = device_path_node_count(device_path_a).unwrap();
let offset = a_path_length.1 - size_of::<efi::protocols::device_path::End>();
let offset = offset.try_into().unwrap();
let expected_ptr =
unsafe { device_path_b_bytes.as_ptr().byte_offset(offset) } as *const efi::protocols::device_path::Protocol;
assert_eq!(result, (expected_ptr, a_path_length.0 - 1));
let result = unsafe { remaining_device_path(device_path_b, device_path_b) };
assert!(result.is_some());
let result = result.unwrap();
let b_path_length = device_path_node_count(device_path_b).unwrap();
let offset = b_path_length.1 - size_of::<efi::protocols::device_path::End>();
let offset = offset.try_into().unwrap();
let expected_ptr =
unsafe { device_path_b_bytes.as_ptr().byte_offset(offset) } as *const efi::protocols::device_path::Protocol;
assert_eq!(result, (expected_ptr, b_path_length.0 - 1));
let result = unsafe { remaining_device_path(device_path_a, device_path_c) };
assert!(result.is_none());
let result = unsafe { remaining_device_path(device_path_b, device_path_a) };
assert!(result.is_none());
}
#[test]
fn device_path_walker_should_return_correct_device_path_nodes() {
let device_path_bytes = [
TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x1C, TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x0, TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x2, 0x0, TYPE_END,
End::SUBTYPE_ENTIRE,
0x4, 0x00, ];
let device_path_ptr = device_path_bytes.as_ptr() as *const efi::protocols::device_path::Protocol;
let mut device_path_walker = unsafe { DevicePathWalker::new(device_path_ptr) };
let node = device_path_walker.next().unwrap();
assert_eq!(node.header.r#type, TYPE_HARDWARE);
assert_eq!(node.header.sub_type, Hardware::SUBTYPE_PCI);
assert_eq!(node.data, vec![0x0u8, 0x1C]);
let node = device_path_walker.next().unwrap();
assert_eq!(node.header.r#type, TYPE_HARDWARE);
assert_eq!(node.header.sub_type, Hardware::SUBTYPE_PCI);
assert_eq!(node.data, vec![0x0u8, 0x0]);
let node = device_path_walker.next().unwrap();
assert_eq!(node.header.r#type, TYPE_HARDWARE);
assert_eq!(node.header.sub_type, Hardware::SUBTYPE_PCI);
assert_eq!(node.data, vec![0x02u8, 0x0]);
let node = device_path_walker.next().unwrap();
assert_eq!(node.header.r#type, TYPE_END);
assert_eq!(node.header.sub_type, End::SUBTYPE_ENTIRE);
assert_eq!(node.data, Vec::<u8>::new());
assert_eq!(device_path_walker.next(), None);
}
#[test]
fn device_path_nodes_can_be_compared_for_equality() {
let device_path_bytes = [
TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x0, TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x0, TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x2, 0x0, TYPE_END,
End::SUBTYPE_ENTIRE,
0x4, 0x00, ];
let device_path_ptr = device_path_bytes.as_ptr() as *const efi::protocols::device_path::Protocol;
let device_path_walker = unsafe { DevicePathWalker::new(device_path_ptr) };
let nodes: Vec<GenericDevicePathNode> = device_path_walker.collect();
assert_eq!(nodes[0], nodes[0]);
assert_eq!(nodes[0], nodes[1]);
assert_ne!(nodes[0], nodes[2]);
assert_ne!(nodes[0], nodes[3]);
assert_ne!(nodes[1], nodes[2]);
assert_ne!(nodes[1], nodes[3]);
assert_ne!(nodes[2], nodes[3]);
}
#[test]
fn device_path_node_can_be_converted_to_boxed_slice() {
let device_path_bytes = [
TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x0, TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x0, TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x2, 0x0, TYPE_END,
End::SUBTYPE_ENTIRE,
0x4, 0x00, ];
let device_path_ptr = device_path_bytes.as_ptr() as *const efi::protocols::device_path::Protocol;
let boxed_device_path = copy_device_path_to_boxed_slice(device_path_ptr);
assert_eq!(boxed_device_path.unwrap().to_vec(), device_path_bytes.to_vec());
}
#[test]
fn device_path_walker_can_be_converted_to_string() {
let device_path_bytes = [
TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, 0x0, 0x1C, TYPE_ACPI,
0x0, 0xC, 0x0, 0x0,
0x1,
0x2,
0x3,
0x4,
0x5,
0x6,
0x7,
TYPE_END,
End::SUBTYPE_ENTIRE,
0x4, 0x00, ];
let device_path_ptr = device_path_bytes.as_ptr() as *const efi::protocols::device_path::Protocol;
let device_path_walker = unsafe { DevicePathWalker::new(device_path_ptr) };
let string: String = device_path_walker.into();
assert_eq!(string, "Pci: 0x00,0x1c/Acpi: 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07/");
}
#[test]
fn test_protocol_to_subtype_str() {
let mut protocol = efi::protocols::device_path::Protocol {
r#type: TYPE_HARDWARE,
sub_type: Hardware::SUBTYPE_PCI,
length: [0, 0],
};
assert_eq!(protocol_to_subtype_str(protocol), "Pci");
protocol.sub_type = Hardware::SUBTYPE_PCCARD;
assert_eq!(protocol_to_subtype_str(protocol), "PcCard");
protocol.sub_type = Hardware::SUBTYPE_MMAP;
assert_eq!(protocol_to_subtype_str(protocol), "MemMap");
protocol.sub_type = Hardware::SUBTYPE_VENDOR;
assert_eq!(protocol_to_subtype_str(protocol), "Vendor");
protocol.sub_type = Hardware::SUBTYPE_CONTROLLER;
assert_eq!(protocol_to_subtype_str(protocol), "Controller");
protocol.sub_type = Hardware::SUBTYPE_BMC;
assert_eq!(protocol_to_subtype_str(protocol), "Bmc");
protocol.sub_type = 99; assert_eq!(protocol_to_subtype_str(protocol), "UnknownHardware");
protocol.r#type = TYPE_MEDIA;
protocol.sub_type = Media::SUBTYPE_HARDDRIVE;
assert_eq!(protocol_to_subtype_str(protocol), "HardDrive");
protocol.r#type = TYPE_END;
protocol.sub_type = End::SUBTYPE_INSTANCE;
assert_eq!(protocol_to_subtype_str(protocol), "EndInstance");
protocol.r#type = 99; assert_eq!(protocol_to_subtype_str(protocol), "UnknownType");
}
}