Skip to content

Commit 250d209

Browse files
committed
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
1 parent 920e50b commit 250d209

File tree

3 files changed

+254
-8
lines changed

3 files changed

+254
-8
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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+
}

uefi-test-runner/src/proto/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ pub fn test() {
99

1010
console::test();
1111

12-
find_protocol();
12+
test_find_protocol_handles();
1313
test_protocols_per_handle();
1414
test_test_protocol();
1515

16+
block::test();
1617
debug::test();
1718
device_path::test();
1819
driver::test();
@@ -46,7 +47,11 @@ pub fn test() {
4647
tcg::test();
4748
}
4849

49-
fn find_protocol() {
50+
/// Tests that the [`boot::find_handles`] wrapper can find handles implementing
51+
/// a certain protocol (here: [`Output`] protocol).
52+
///
53+
/// [`Output`]: proto::console::text::Output
54+
fn test_find_protocol_handles() {
5055
let handles = boot::find_handles::<proto::console::text::Output>()
5156
.expect("Failed to retrieve list of handles");
5257

@@ -76,6 +81,7 @@ fn test_test_protocol() {
7681

7782
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
7883
mod ata;
84+
mod block;
7985
mod console;
8086
mod debug;
8187
mod device_path;

uefi/src/proto/media/block.rs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
//! Block I/O protocols [`BlockIO`] and [`BlockIO2`].
44
5-
use core::ptr::NonNull;
6-
75
use crate::proto::unsafe_protocol;
86
use crate::util::opt_nonnull_to_ptr;
97
use crate::{Event, Result, Status, StatusExt};
8+
use core::ptr::NonNull;
9+
use core::sync::atomic::{AtomicUsize, Ordering};
1010

1111
pub use uefi_raw::protocol::block::{BlockIo2Protocol, BlockIoProtocol, Lba};
1212

@@ -196,9 +196,39 @@ impl BlockIOMedia {
196196
pub struct BlockIO2Token {
197197
/// Event to be signalled when an asynchronous block I/O operation
198198
/// completes.
199-
pub event: Option<Event>,
199+
pub event: Event,
200200
/// Transaction status code.
201-
pub transaction_status: Status,
201+
// UEFI can change this at any time, so we need atomic access.
202+
pub transaction_status: AtomicUsize,
203+
}
204+
205+
impl BlockIO2Token {
206+
/// Creates a new token.
207+
#[must_use]
208+
pub const fn new(event: Event, status: Status) -> Self {
209+
Self {
210+
event,
211+
transaction_status: AtomicUsize::new(status.0),
212+
}
213+
}
214+
215+
/// Returns the transaction current status.
216+
pub fn transaction_status(&self) -> Status {
217+
Status(self.transaction_status.load(Ordering::SeqCst))
218+
}
219+
220+
/// Clone this token.
221+
///
222+
/// # Safety
223+
/// The caller must ensure that any clones of a closed `Event` are never
224+
/// used again.
225+
#[must_use]
226+
pub unsafe fn unsafe_clone(&self) -> Self {
227+
Self {
228+
event: unsafe { self.event.unsafe_clone() },
229+
transaction_status: AtomicUsize::new(self.transaction_status.load(Ordering::SeqCst)),
230+
}
231+
}
202232
}
203233

204234
/// Block I/O 2 [`Protocol`].
@@ -236,7 +266,8 @@ impl BlockIO2 {
236266
/// # Arguments
237267
/// * `media_id` - The media ID that the read request is for.
238268
/// * `lba` - The starting logical block address to read from on the device.
239-
/// * `token` - Transaction token for asynchronous read.
269+
/// * `token` - Transaction token for asynchronous read or `None` for
270+
/// synchronous operation.
240271
/// * `len` - Buffer size.
241272
/// * `buffer` - The target buffer of the read operation
242273
///
@@ -269,7 +300,8 @@ impl BlockIO2 {
269300
/// # Arguments
270301
/// * `media_id` - The media ID that the write request is for.
271302
/// * `lba` - The starting logical block address to be written.
272-
/// * `token` - Transaction token for asynchronous write.
303+
/// * `token` - Transaction token for asynchronous write or `None` for
304+
/// synchronous operation
273305
/// * `len` - Buffer size.
274306
/// * `buffer` - Buffer to be written from.
275307
///

0 commit comments

Comments
 (0)