diff --git a/uefi-test-runner/src/proto/block.rs b/uefi-test-runner/src/proto/block.rs new file mode 100644 index 000000000..c0e2b2df1 --- /dev/null +++ b/uefi-test-runner/src/proto/block.rs @@ -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( + handle: Handle, +) -> (ScopedProtocol

, ScopedProtocol, CString16) { + let proto = unsafe { + boot::open_protocol::

( + OpenProtocolParams { + handle, + agent: boot::image_handle(), + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + .unwrap() + }; + let dvp = unsafe { + boot::open_protocol::( + 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::().unwrap() { + let (proto, dvp, dvp_string) = open_proto_and_dvp::(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::().unwrap() { + let (proto, dvp, dvp_string) = open_proto_and_dvp::(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>) { + 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(); +} diff --git a/uefi-test-runner/src/proto/mod.rs b/uefi-test-runner/src/proto/mod.rs index 5d66587c4..80cf40d80 100644 --- a/uefi-test-runner/src/proto/mod.rs +++ b/uefi-test-runner/src/proto/mod.rs @@ -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::() .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; diff --git a/uefi/src/proto/media/block.rs b/uefi/src/proto/media/block.rs index 7dc3ecb92..729d77ad5 100644 --- a/uefi/src/proto/media/block.rs +++ b/uefi/src/proto/media/block.rs @@ -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, + 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. ///