Skip to content

Commit 10a0ec8

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 10a0ec8

File tree

3 files changed

+247
-8
lines changed

3 files changed

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

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)