Skip to content

test-runner: Clean up device path tests #1527

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 1 commit into from
Jan 25, 2025
Merged
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
318 changes: 207 additions & 111 deletions uefi-test-runner/src/proto/device_path.rs
Original file line number Diff line number Diff line change
@@ -1,123 +1,219 @@
use alloc::string::ToString;
use alloc::boxed::Box;
use alloc::vec::Vec;
use uefi::boot;
use uefi::proto::device_path::text::*;
use uefi::proto::device_path::{DevicePath, LoadedImageDevicePath};
use uefi::proto::device_path::build::{self, DevicePathBuilder};
use uefi::proto::device_path::text::{
AllowShortcuts, DevicePathFromText, DevicePathToText, DisplayOnly,
};
use uefi::proto::device_path::{messaging, DevicePath, DevicePathNode, LoadedImageDevicePath};
use uefi::proto::loaded_image::LoadedImage;
use uefi::proto::media::disk::DiskIo;
use uefi::{boot, cstr16};

pub fn test() {
info!("Running device path protocol test");
info!("Running device path tests");

// test 1/2: test low-level API by directly opening all protocols
test_convert_device_path_to_text();
test_device_path_to_string();

test_convert_device_node_to_text();
test_device_path_node_to_string();

test_convert_text_to_device_path();
test_convert_text_to_device_node();

// Get the current executable's device path via the `LoadedImage` protocol.
let loaded_image = boot::open_protocol_exclusive::<LoadedImage>(boot::image_handle()).unwrap();
let device_path =
boot::open_protocol_exclusive::<DevicePath>(loaded_image.device().unwrap()).unwrap();

// Get the `LoadedImageDevicePath`. Verify it start with the same nodes as
// `device_path`.
let loaded_image_device_path =
boot::open_protocol_exclusive::<LoadedImageDevicePath>(boot::image_handle()).unwrap();
for (n1, n2) in device_path
.node_iter()
.zip(loaded_image_device_path.node_iter())
{
let loaded_image = boot::open_protocol_exclusive::<LoadedImage>(boot::image_handle())
.expect("Failed to open LoadedImage protocol");

let device_path =
boot::open_protocol_exclusive::<DevicePath>(loaded_image.device().unwrap())
.expect("Failed to open DevicePath protocol");

let device_path_to_text = boot::open_protocol_exclusive::<DevicePathToText>(
boot::get_handle_for_protocol::<DevicePathToText>()
.expect("Failed to get DevicePathToText handle"),
)
.expect("Failed to open DevicePathToText protocol");

let device_path_from_text = boot::open_protocol_exclusive::<DevicePathFromText>(
boot::get_handle_for_protocol::<DevicePathFromText>()
.expect("Failed to get DevicePathFromText handle"),
)
.expect("Failed to open DevicePathFromText protocol");

// Test round-trip conversion from path to text and back.
let device_path_string = device_path_to_text
.convert_device_path_to_text(&device_path, DisplayOnly(false), AllowShortcuts(false))
.unwrap();
assert_eq!(
*device_path_from_text
.convert_text_to_device_path(&device_path_string)
.unwrap(),
*device_path
);

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

let text = device_path_to_text
.convert_device_node_to_text(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_node(text)
.expect("Failed to convert text to device path");
assert_eq!(*path, *convert);
}

// Get the `LoadedImageDevicePath`. Verify it start with the same nodes as
// `device_path`.
let loaded_image_device_path =
boot::open_protocol_exclusive::<LoadedImageDevicePath>(boot::image_handle())
.expect("Failed to open LoadedImageDevicePath protocol");

for (n1, n2) in device_path
.node_iter()
.zip(loaded_image_device_path.node_iter())
{
assert_eq!(n1, n2);
}

// Test `locate_device_path`.
let mut dp = &*device_path;
boot::locate_device_path::<DiskIo>(&mut dp).unwrap();
assert_eq!(n1, n2);
}

// test 2/2: test high-level to-string api
{
let loaded_image_device_path =
boot::open_protocol_exclusive::<LoadedImageDevicePath>(boot::image_handle())
.expect("Failed to open LoadedImageDevicePath protocol");
let device_path: &DevicePath = &loaded_image_device_path;

let path_components = device_path
.node_iter()
.map(|node| node.to_string(DisplayOnly(false), AllowShortcuts(false)))
.map(|str| str.unwrap().to_string())
.collect::<Vec<_>>();

let expected_device_path_str_components = &[
"PciRoot(0x0)",
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
"Pci(0x1F,0x2)",
#[cfg(target_arch = "aarch64")]
"Pci(0x4,0x0)",
// Sata device only used on x86.
// See xtask utility.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
"Sata(0x0,0xFFFF,0x0)",
"HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)",
"\\efi\\boot\\test_runner.efi",
];
let expected_device_path_str = expected_device_path_str_components.join("/");

assert_eq!(
path_components.as_slice(),
expected_device_path_str_components
);

// Test that to_string works for device_paths
let path = device_path
.to_string(DisplayOnly(false), AllowShortcuts(false))
// Test finding a handle by device path.
let mut dp = &*device_path;
boot::locate_device_path::<DiskIo>(&mut dp).unwrap();
}

fn create_test_device_path() -> Box<DevicePath> {
let mut v = Vec::new();
DevicePathBuilder::with_vec(&mut v)
// Add an ATAPI node because edk2 displays it differently depending on
// the value of `DisplayOnly`.
.push(&build::messaging::Atapi {
primary_secondary: messaging::PrimarySecondary::PRIMARY,
master_slave: messaging::MasterSlave::MASTER,
logical_unit_number: 1,
})
.unwrap()
// Add a messaging::vendor node because edk2 displays it differently
// depending on the value of `AllowShortcuts`.
.push(&build::messaging::Vendor {
vendor_guid: messaging::Vendor::PC_ANSI,
vendor_defined_data: &[],
})
.unwrap()
.finalize()
.unwrap()
.to_boxed()
}

/// Test `DevicePathToText::convert_device_path_to_text`.
fn test_convert_device_path_to_text() {
let path = create_test_device_path();

let proto = boot::open_protocol_exclusive::<DevicePathToText>(
boot::get_handle_for_protocol::<DevicePathToText>().unwrap(),
)
.unwrap();

let to_text = |display_only, allow_shortcuts| {
proto
.convert_device_path_to_text(&path, display_only, allow_shortcuts)
.unwrap()
.to_string();
};

assert_eq!(
&*to_text(DisplayOnly(true), AllowShortcuts(true)),
cstr16!("Ata(0x1)/VenPcAnsi()")
);
assert_eq!(
&*to_text(DisplayOnly(true), AllowShortcuts(false)),
cstr16!("Ata(0x1)/VenMsg(E0C14753-F9BE-11D2-9A0C-0090273FC14D)")
);
assert_eq!(
&*to_text(DisplayOnly(false), AllowShortcuts(true)),
cstr16!("Ata(Primary,Master,0x1)/VenPcAnsi()")
);
assert_eq!(
&*to_text(DisplayOnly(false), AllowShortcuts(false)),
cstr16!("Ata(Primary,Master,0x1)/VenMsg(E0C14753-F9BE-11D2-9A0C-0090273FC14D)")
);
}

assert_eq!(path, expected_device_path_str);
}
/// Test `DevicePath::to_string`.
fn test_device_path_to_string() {
let path = create_test_device_path();

let to_text =
|display_only, allow_shortcuts| path.to_string(display_only, allow_shortcuts).unwrap();

assert_eq!(
&*to_text(DisplayOnly(true), AllowShortcuts(true)),
cstr16!("Ata(0x1)/VenPcAnsi()")
);
assert_eq!(
&*to_text(DisplayOnly(true), AllowShortcuts(false)),
cstr16!("Ata(0x1)/VenMsg(E0C14753-F9BE-11D2-9A0C-0090273FC14D)")
);
assert_eq!(
&*to_text(DisplayOnly(false), AllowShortcuts(true)),
cstr16!("Ata(Primary,Master,0x1)/VenPcAnsi()")
);
assert_eq!(
&*to_text(DisplayOnly(false), AllowShortcuts(false)),
cstr16!("Ata(Primary,Master,0x1)/VenMsg(E0C14753-F9BE-11D2-9A0C-0090273FC14D)")
);
}

/// Test `DevicePathToText::convert_device_node_to_text`.
fn test_convert_device_node_to_text() {
let path = create_test_device_path();
let nodes: Vec<_> = path.node_iter().collect();

let proto = boot::open_protocol_exclusive::<DevicePathToText>(
boot::get_handle_for_protocol::<DevicePathToText>().unwrap(),
)
.unwrap();

let to_text = |node, display_only, allow_shortcuts| {
proto
.convert_device_node_to_text(node, display_only, allow_shortcuts)
.unwrap()
};

assert_eq!(
&*to_text(nodes[0], DisplayOnly(true), AllowShortcuts(true)),
cstr16!("Ata(0x1)")
);
assert_eq!(
&*to_text(nodes[0], DisplayOnly(false), AllowShortcuts(true)),
cstr16!("Ata(Primary,Master,0x1)")
);
assert_eq!(
&*to_text(nodes[1], DisplayOnly(false), AllowShortcuts(true)),
cstr16!("VenPcAnsi()")
);
assert_eq!(
&*to_text(nodes[1], DisplayOnly(false), AllowShortcuts(false)),
cstr16!("VenMsg(E0C14753-F9BE-11D2-9A0C-0090273FC14D)")
);
}

/// Test `DevicePathNode::to_string`.
fn test_device_path_node_to_string() {
let path = create_test_device_path();
let nodes: Vec<_> = path.node_iter().collect();

let to_text = |node: &DevicePathNode, display_only, allow_shortcuts| {
node.to_string(display_only, allow_shortcuts).unwrap()
};

assert_eq!(
&*to_text(nodes[0], DisplayOnly(true), AllowShortcuts(true)),
cstr16!("Ata(0x1)")
);
assert_eq!(
&*to_text(nodes[0], DisplayOnly(false), AllowShortcuts(true)),
cstr16!("Ata(Primary,Master,0x1)")
);
assert_eq!(
&*to_text(nodes[1], DisplayOnly(false), AllowShortcuts(true)),
cstr16!("VenPcAnsi()")
);
assert_eq!(
&*to_text(nodes[1], DisplayOnly(false), AllowShortcuts(false)),
cstr16!("VenMsg(E0C14753-F9BE-11D2-9A0C-0090273FC14D)")
);
}

/// Test `DevicePathFromText::convert_text_to_device_path`.
fn test_convert_text_to_device_path() {
let text = cstr16!("Ata(Primary,Master,0x1)/VenMsg(E0C14753-F9BE-11D2-9A0C-0090273FC14D)");
let expected_path = create_test_device_path();

let proto = boot::open_protocol_exclusive::<DevicePathFromText>(
boot::get_handle_for_protocol::<DevicePathFromText>().unwrap(),
)
.unwrap();

assert_eq!(
&*proto.convert_text_to_device_path(text).unwrap(),
&*expected_path
);
}

/// Test `DevicePathFromText::convert_text_to_device_node`.
fn test_convert_text_to_device_node() {
let path = create_test_device_path();
let expected_node = path.node_iter().next().unwrap();

let proto = boot::open_protocol_exclusive::<DevicePathFromText>(
boot::get_handle_for_protocol::<DevicePathFromText>().unwrap(),
)
.unwrap();

assert_eq!(
&*proto
.convert_text_to_device_node(cstr16!("Ata(Primary,Master,0x1)"))
.unwrap(),
expected_node,
);
}
Loading