|
| 1 | +// SPDX-License-Identifier: MIT OR Apache-2.0 |
| 2 | + |
| 3 | +//! Very basic tests for the BlockIo and BlockIo2 protocols. |
| 4 | +//! |
| 5 | +//! We look for some well-known data on a few well-known disks |
| 6 | +//! of our test environment. |
| 7 | +
|
| 8 | +use alloc::string::String; |
| 9 | +use core::ffi::c_void; |
| 10 | +use core::hint; |
| 11 | +use core::ptr::NonNull; |
| 12 | +use core::sync::atomic::{AtomicBool, Ordering}; |
| 13 | +use uefi::boot::{OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol}; |
| 14 | +use uefi::proto::Protocol; |
| 15 | +use uefi::proto::device_path::DevicePath; |
| 16 | +use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; |
| 17 | +use uefi::proto::media::block::{BlockIO, BlockIO2, BlockIO2Token}; |
| 18 | +use uefi::{CString16, Event, Handle, boot}; |
| 19 | +use uefi_raw::Status; |
| 20 | +use uefi_raw::protocol::device_path::DeviceSubType; |
| 21 | +use uefi_raw::table::boot::{EventType, Tpl}; |
| 22 | + |
| 23 | +fn verify_block_device(dvp: &DevicePath, first_block: &[u8]) { |
| 24 | + // We only look for storage technologies that we are interested in. |
| 25 | + let storage_device_types = [ |
| 26 | + DeviceSubType::MESSAGING_SCSI, |
| 27 | + DeviceSubType::MESSAGING_NVME_NAMESPACE, |
| 28 | + DeviceSubType::MESSAGING_SATA, |
| 29 | + ]; |
| 30 | + let maybe_storage_node = dvp |
| 31 | + .node_iter() |
| 32 | + .find(|x| storage_device_types.contains(&x.sub_type())); |
| 33 | + |
| 34 | + if maybe_storage_node.is_none() { |
| 35 | + // This happens on CI for the AArch64 target for a handle with |
| 36 | + // device path `PciRoot(0x0)/Pci(0x9,0x0)` only. |
| 37 | + return; |
| 38 | + } |
| 39 | + let storage_node = maybe_storage_node.unwrap(); |
| 40 | + |
| 41 | + let storage_node_string = storage_node |
| 42 | + .to_string(DisplayOnly(true), AllowShortcuts(true)) |
| 43 | + .unwrap(); |
| 44 | + debug!("Storage technology: {storage_node_string}"); |
| 45 | + |
| 46 | + //debug!("First 512 bytes: {first_block:?}"); |
| 47 | + match storage_node.sub_type() { |
| 48 | + DeviceSubType::MESSAGING_SCSI => { /* empty disks so far, nothing to check for */ } |
| 49 | + DeviceSubType::MESSAGING_NVME_NAMESPACE => { |
| 50 | + /* empty disks so far, nothing to check for */ |
| 51 | + } |
| 52 | + DeviceSubType::MESSAGING_SATA => { |
| 53 | + // We check that the right SATA disk indeed contains a correct |
| 54 | + // FAT16 volume. |
| 55 | + let expected = "MbrTestDisk"; |
| 56 | + let contains_volume_label = first_block |
| 57 | + .windows(expected.len()) |
| 58 | + .any(|w| w == expected.as_bytes()); |
| 59 | + let oem_name = { |
| 60 | + let bytes = &first_block[3..10]; |
| 61 | + String::from_utf8(bytes.to_vec()) |
| 62 | + }; |
| 63 | + let is_valid_fat = first_block[0] != 0 && oem_name.is_ok(); |
| 64 | + if is_valid_fat && storage_node.data() == [0x2, 0, 0xff, 0xff, 0x0, 0x0] { |
| 65 | + if !contains_volume_label { |
| 66 | + panic!( |
| 67 | + "Sata disk {storage_node_string} does not contain {expected} in its first 512 bytes" |
| 68 | + ) |
| 69 | + } else { |
| 70 | + debug!( |
| 71 | + "Found volume label {expected} with OEM name: {}", |
| 72 | + oem_name.unwrap() |
| 73 | + ); |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + _ => unreachable!(), |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +fn open_proto_and_dvp<P: Protocol>( |
| 82 | + handle: Handle, |
| 83 | +) -> (ScopedProtocol<P>, ScopedProtocol<DevicePath>, CString16) { |
| 84 | + let proto = unsafe { |
| 85 | + boot::open_protocol::<P>( |
| 86 | + OpenProtocolParams { |
| 87 | + handle, |
| 88 | + agent: boot::image_handle(), |
| 89 | + controller: None, |
| 90 | + }, |
| 91 | + OpenProtocolAttributes::GetProtocol, |
| 92 | + ) |
| 93 | + .unwrap() |
| 94 | + }; |
| 95 | + let dvp = unsafe { |
| 96 | + boot::open_protocol::<DevicePath>( |
| 97 | + OpenProtocolParams { |
| 98 | + handle, |
| 99 | + agent: boot::image_handle(), |
| 100 | + controller: None, |
| 101 | + }, |
| 102 | + OpenProtocolAttributes::GetProtocol, |
| 103 | + ) |
| 104 | + .unwrap() |
| 105 | + }; |
| 106 | + let dvp_string = dvp |
| 107 | + .to_string(DisplayOnly(true), AllowShortcuts(true)) |
| 108 | + .unwrap(); |
| 109 | + |
| 110 | + (proto, dvp, dvp_string) |
| 111 | +} |
| 112 | + |
| 113 | +fn test_blockio_protocol() { |
| 114 | + info!("Testing BLOCKIO protocol"); |
| 115 | + for handle in boot::find_handles::<BlockIO>().unwrap() { |
| 116 | + let (proto, dvp, dvp_string) = open_proto_and_dvp::<BlockIO>(handle); |
| 117 | + debug!("Found handle supporting protocol: {dvp_string}"); |
| 118 | + debug!("media: {:?}", proto.media()); |
| 119 | + let mut first_block = vec![0; 512]; |
| 120 | + proto |
| 121 | + .read_blocks(proto.media().media_id(), 0, &mut first_block) |
| 122 | + .unwrap(); |
| 123 | + |
| 124 | + verify_block_device(&dvp, first_block.as_slice()); |
| 125 | + } |
| 126 | +} |
| 127 | + |
| 128 | +fn test_blockio2_protocol() { |
| 129 | + info!("Testing BLOCKIO 2 protocol"); |
| 130 | + |
| 131 | + for handle in boot::find_handles::<BlockIO2>().unwrap() { |
| 132 | + let (proto, dvp, dvp_string) = open_proto_and_dvp::<BlockIO2>(handle); |
| 133 | + debug!("Found handle supporting protocol: {dvp_string}"); |
| 134 | + debug!("media: {:?}", proto.media()); |
| 135 | + |
| 136 | + // sync test |
| 137 | + { |
| 138 | + let mut first_block = vec![0; 512]; |
| 139 | + unsafe { |
| 140 | + proto |
| 141 | + .read_blocks_ex( |
| 142 | + proto.media().media_id(), |
| 143 | + 0, |
| 144 | + None, /* sync */ |
| 145 | + first_block.len(), |
| 146 | + first_block.as_mut_ptr(), |
| 147 | + ) |
| 148 | + .unwrap(); |
| 149 | + } |
| 150 | + |
| 151 | + verify_block_device(&dvp, first_block.as_slice()); |
| 152 | + } |
| 153 | + // async test |
| 154 | + { |
| 155 | + static ASYNC_READ_LOCK: AtomicBool = AtomicBool::new(false); |
| 156 | + |
| 157 | + let mut first_block = vec![0; 512 * 20]; |
| 158 | + extern "efiapi" fn callback(_event: Event, _context: Option<NonNull<c_void>>) { |
| 159 | + log::info!("event fired: block I/O 2 read_blocks_ex done"); |
| 160 | + ASYNC_READ_LOCK.store(true, Ordering::SeqCst); |
| 161 | + } |
| 162 | + let event = unsafe { |
| 163 | + boot::create_event( |
| 164 | + EventType::NOTIFY_SIGNAL, |
| 165 | + Tpl::CALLBACK, |
| 166 | + Some(callback), |
| 167 | + None, |
| 168 | + ) |
| 169 | + .expect("should create event") |
| 170 | + }; |
| 171 | + let mut token = BlockIO2Token::new(event, Status::NOT_READY); |
| 172 | + let token_ptr = NonNull::new(&raw mut token).unwrap(); |
| 173 | + unsafe { |
| 174 | + proto |
| 175 | + .read_blocks_ex( |
| 176 | + proto.media().media_id(), |
| 177 | + 0, |
| 178 | + Some(token_ptr), /* sync */ |
| 179 | + first_block.len(), |
| 180 | + first_block.as_mut_ptr(), |
| 181 | + ) |
| 182 | + .unwrap(); |
| 183 | + } |
| 184 | + |
| 185 | + // This works for some disks but the implementations behave |
| 186 | + // differently. |
| 187 | + // assert_ne!(token.transaction_status(), Status::SUCCESS); |
| 188 | + |
| 189 | + // Wait util callback notified us the read is done |
| 190 | + while !ASYNC_READ_LOCK.load(Ordering::SeqCst) { |
| 191 | + hint::spin_loop(); |
| 192 | + } |
| 193 | + ASYNC_READ_LOCK.store(false, Ordering::SeqCst); |
| 194 | + |
| 195 | + // No boot::check_event() here, doesn't work, invalid parameter. |
| 196 | + // Instead, one must use the notify function to perform further |
| 197 | + // action. |
| 198 | + |
| 199 | + assert_eq!(token.transaction_status(), Status::SUCCESS); |
| 200 | + verify_block_device(&dvp, &first_block.as_slice()[0..512]); |
| 201 | + } |
| 202 | + } |
| 203 | +} |
| 204 | + |
| 205 | +pub fn test() { |
| 206 | + test_blockio_protocol(); |
| 207 | + test_blockio2_protocol(); |
| 208 | +} |
0 commit comments