mirror of
https://github.com/rust-osdev/uefi-rs
synced 2026-06-08 17:17:36 +00:00
uefi-test-runner: add basic test for blockio protocols
- improve the ergonomics of the Blocks I/O 2 protocol - figure out how to use it at all and put that into a very basic test
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
//! Very basic tests for the BlockIo and BlockIo2 protocols.
|
||||
//!
|
||||
//! We look for some well-known data on a few well-known disks
|
||||
//! of our test environment.
|
||||
|
||||
use alloc::string::String;
|
||||
use core::ffi::c_void;
|
||||
use core::hint;
|
||||
use core::ptr::NonNull;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
use uefi::boot::{OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol};
|
||||
use uefi::proto::Protocol;
|
||||
use uefi::proto::device_path::DevicePath;
|
||||
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
|
||||
use uefi::proto::media::block::{BlockIO, BlockIO2, BlockIO2Token};
|
||||
use uefi::{CString16, Event, Handle, boot};
|
||||
use uefi_raw::Status;
|
||||
use uefi_raw::protocol::device_path::DeviceSubType;
|
||||
use uefi_raw::table::boot::{EventType, Tpl};
|
||||
|
||||
fn verify_block_device(dvp: &DevicePath, first_block: &[u8]) {
|
||||
// We only look for storage technologies that we are interested in.
|
||||
let storage_device_types = [
|
||||
DeviceSubType::MESSAGING_SCSI,
|
||||
DeviceSubType::MESSAGING_NVME_NAMESPACE,
|
||||
DeviceSubType::MESSAGING_SATA,
|
||||
];
|
||||
let maybe_storage_node = dvp
|
||||
.node_iter()
|
||||
.find(|x| storage_device_types.contains(&x.sub_type()));
|
||||
|
||||
if maybe_storage_node.is_none() {
|
||||
// This happens on CI for the AArch64 target for a handle with
|
||||
// device path `PciRoot(0x0)/Pci(0x9,0x0)` only.
|
||||
return;
|
||||
}
|
||||
let storage_node = maybe_storage_node.unwrap();
|
||||
|
||||
let storage_node_string = storage_node
|
||||
.to_string(DisplayOnly(true), AllowShortcuts(true))
|
||||
.unwrap();
|
||||
debug!("Storage technology: {storage_node_string}");
|
||||
|
||||
//debug!("First 512 bytes: {first_block:?}");
|
||||
match storage_node.sub_type() {
|
||||
DeviceSubType::MESSAGING_SCSI => { /* empty disks so far, nothing to check for */ }
|
||||
DeviceSubType::MESSAGING_NVME_NAMESPACE => {
|
||||
/* empty disks so far, nothing to check for */
|
||||
}
|
||||
DeviceSubType::MESSAGING_SATA => {
|
||||
// We check that the right SATA disk indeed contains a correct
|
||||
// FAT16 volume.
|
||||
let expected = "MbrTestDisk";
|
||||
let contains_volume_label = first_block
|
||||
.windows(expected.len())
|
||||
.any(|w| w == expected.as_bytes());
|
||||
let oem_name = {
|
||||
let bytes = &first_block[3..10];
|
||||
String::from_utf8(bytes.to_vec())
|
||||
};
|
||||
let is_valid_fat = first_block[0] != 0 && oem_name.is_ok();
|
||||
if is_valid_fat && storage_node.data() == [0x2, 0, 0xff, 0xff, 0x0, 0x0] {
|
||||
if !contains_volume_label {
|
||||
panic!(
|
||||
"Sata disk {storage_node_string} does not contain {expected} in its first 512 bytes"
|
||||
)
|
||||
} else {
|
||||
debug!(
|
||||
"Found volume label {expected} with OEM name: {}",
|
||||
oem_name.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn open_proto_and_dvp<P: Protocol>(
|
||||
handle: Handle,
|
||||
) -> (ScopedProtocol<P>, ScopedProtocol<DevicePath>, CString16) {
|
||||
let proto = unsafe {
|
||||
boot::open_protocol::<P>(
|
||||
OpenProtocolParams {
|
||||
handle,
|
||||
agent: boot::image_handle(),
|
||||
controller: None,
|
||||
},
|
||||
OpenProtocolAttributes::GetProtocol,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
let dvp = unsafe {
|
||||
boot::open_protocol::<DevicePath>(
|
||||
OpenProtocolParams {
|
||||
handle,
|
||||
agent: boot::image_handle(),
|
||||
controller: None,
|
||||
},
|
||||
OpenProtocolAttributes::GetProtocol,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
let dvp_string = dvp
|
||||
.to_string(DisplayOnly(true), AllowShortcuts(true))
|
||||
.unwrap();
|
||||
|
||||
(proto, dvp, dvp_string)
|
||||
}
|
||||
|
||||
fn test_blockio_protocol() {
|
||||
info!("Testing BLOCKIO protocol");
|
||||
for handle in boot::find_handles::<BlockIO>().unwrap() {
|
||||
let (proto, dvp, dvp_string) = open_proto_and_dvp::<BlockIO>(handle);
|
||||
debug!("Found handle supporting protocol: {dvp_string}");
|
||||
debug!("media: {:?}", proto.media());
|
||||
let mut first_block = vec![0; 512];
|
||||
proto
|
||||
.read_blocks(proto.media().media_id(), 0, &mut first_block)
|
||||
.unwrap();
|
||||
|
||||
verify_block_device(&dvp, first_block.as_slice());
|
||||
}
|
||||
}
|
||||
|
||||
fn test_blockio2_protocol() {
|
||||
info!("Testing BLOCKIO 2 protocol");
|
||||
|
||||
for handle in boot::find_handles::<BlockIO2>().unwrap() {
|
||||
let (proto, dvp, dvp_string) = open_proto_and_dvp::<BlockIO2>(handle);
|
||||
debug!("Found handle supporting protocol: {dvp_string}");
|
||||
debug!("media: {:?}", proto.media());
|
||||
|
||||
// sync test
|
||||
{
|
||||
let mut first_block = vec![0; 512];
|
||||
unsafe {
|
||||
proto
|
||||
.read_blocks_ex(
|
||||
proto.media().media_id(),
|
||||
0,
|
||||
None, /* sync */
|
||||
first_block.len(),
|
||||
first_block.as_mut_ptr(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
verify_block_device(&dvp, first_block.as_slice());
|
||||
}
|
||||
// async test
|
||||
{
|
||||
static ASYNC_READ_LOCK: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
let mut first_block = vec![0; 512];
|
||||
extern "efiapi" fn callback(_event: Event, _context: Option<NonNull<c_void>>) {
|
||||
log::info!("event fired: block I/O 2 read_blocks_ex done");
|
||||
ASYNC_READ_LOCK.store(true, Ordering::SeqCst);
|
||||
}
|
||||
let event = unsafe {
|
||||
boot::create_event(
|
||||
EventType::NOTIFY_SIGNAL,
|
||||
Tpl::CALLBACK,
|
||||
Some(callback),
|
||||
None,
|
||||
)
|
||||
.expect("should create event")
|
||||
};
|
||||
let mut token = BlockIO2Token::new(event, Status::NOT_READY);
|
||||
let token_ptr = NonNull::new(&raw mut token).unwrap();
|
||||
unsafe {
|
||||
proto
|
||||
.read_blocks_ex(
|
||||
proto.media().media_id(),
|
||||
0,
|
||||
Some(token_ptr), /* sync */
|
||||
first_block.len(),
|
||||
first_block.as_mut_ptr(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// This works for some disks but the implementations behave
|
||||
// differently.
|
||||
// assert_ne!(token.transaction_status(), Status::SUCCESS);
|
||||
|
||||
// Wait util callback notified us the read is done
|
||||
while !ASYNC_READ_LOCK.load(Ordering::SeqCst) {
|
||||
hint::spin_loop();
|
||||
}
|
||||
ASYNC_READ_LOCK.store(false, Ordering::SeqCst);
|
||||
|
||||
// No boot::check_event() here, doesn't work, invalid parameter.
|
||||
// Instead, one must use the notify function to perform further
|
||||
// action.
|
||||
|
||||
assert_eq!(token.transaction_status(), Status::SUCCESS);
|
||||
verify_block_device(&dvp, first_block.as_slice());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test() {
|
||||
test_blockio_protocol();
|
||||
test_blockio2_protocol();
|
||||
}
|
||||
@@ -9,10 +9,11 @@ pub fn test() {
|
||||
|
||||
console::test();
|
||||
|
||||
find_protocol();
|
||||
test_find_protocol_handles();
|
||||
test_protocols_per_handle();
|
||||
test_test_protocol();
|
||||
|
||||
block::test();
|
||||
debug::test();
|
||||
device_path::test();
|
||||
driver::test();
|
||||
@@ -46,7 +47,11 @@ pub fn test() {
|
||||
tcg::test();
|
||||
}
|
||||
|
||||
fn find_protocol() {
|
||||
/// Tests that the [`boot::find_handles`] wrapper can find handles implementing
|
||||
/// a certain protocol (here: [`Output`] protocol).
|
||||
///
|
||||
/// [`Output`]: proto::console::text::Output
|
||||
fn test_find_protocol_handles() {
|
||||
let handles = boot::find_handles::<proto::console::text::Output>()
|
||||
.expect("Failed to retrieve list of handles");
|
||||
|
||||
@@ -76,6 +81,7 @@ fn test_test_protocol() {
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
mod ata;
|
||||
mod block;
|
||||
mod console;
|
||||
mod debug;
|
||||
mod device_path;
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
//! Block I/O protocols [`BlockIO`] and [`BlockIO2`].
|
||||
|
||||
use core::ptr::NonNull;
|
||||
|
||||
use crate::proto::unsafe_protocol;
|
||||
use crate::util::opt_nonnull_to_ptr;
|
||||
use crate::{Event, Result, Status, StatusExt};
|
||||
use core::ptr::NonNull;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
pub use uefi_raw::protocol::block::{BlockIo2Protocol, BlockIoProtocol, Lba};
|
||||
|
||||
@@ -196,9 +196,39 @@ impl BlockIOMedia {
|
||||
pub struct BlockIO2Token {
|
||||
/// Event to be signalled when an asynchronous block I/O operation
|
||||
/// completes.
|
||||
pub event: Option<Event>,
|
||||
pub event: Event,
|
||||
/// Transaction status code.
|
||||
pub transaction_status: Status,
|
||||
// UEFI can change this at any time, so we need atomic access.
|
||||
pub transaction_status: AtomicUsize,
|
||||
}
|
||||
|
||||
impl BlockIO2Token {
|
||||
/// Creates a new token.
|
||||
#[must_use]
|
||||
pub const fn new(event: Event, status: Status) -> Self {
|
||||
Self {
|
||||
event,
|
||||
transaction_status: AtomicUsize::new(status.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the transaction current status.
|
||||
pub fn transaction_status(&self) -> Status {
|
||||
Status(self.transaction_status.load(Ordering::SeqCst))
|
||||
}
|
||||
|
||||
/// Clone this token.
|
||||
///
|
||||
/// # Safety
|
||||
/// The caller must ensure that any clones of a closed `Event` are never
|
||||
/// used again.
|
||||
#[must_use]
|
||||
pub unsafe fn unsafe_clone(&self) -> Self {
|
||||
Self {
|
||||
event: unsafe { self.event.unsafe_clone() },
|
||||
transaction_status: AtomicUsize::new(self.transaction_status.load(Ordering::SeqCst)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Block I/O 2 [`Protocol`].
|
||||
@@ -236,7 +266,8 @@ impl BlockIO2 {
|
||||
/// # Arguments
|
||||
/// * `media_id` - The media ID that the read request is for.
|
||||
/// * `lba` - The starting logical block address to read from on the device.
|
||||
/// * `token` - Transaction token for asynchronous read.
|
||||
/// * `token` - Transaction token for asynchronous read or `None` for
|
||||
/// synchronous operation.
|
||||
/// * `len` - Buffer size.
|
||||
/// * `buffer` - The target buffer of the read operation
|
||||
///
|
||||
@@ -269,7 +300,8 @@ impl BlockIO2 {
|
||||
/// # Arguments
|
||||
/// * `media_id` - The media ID that the write request is for.
|
||||
/// * `lba` - The starting logical block address to be written.
|
||||
/// * `token` - Transaction token for asynchronous write.
|
||||
/// * `token` - Transaction token for asynchronous write or `None` for
|
||||
/// synchronous operation
|
||||
/// * `len` - Buffer size.
|
||||
/// * `buffer` - Buffer to be written from.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user