Skip to content

Commit 8833ff9

Browse files
Merge pull request #467 from e820/protocol-disk-io
Add `DiskIo` and `DiskIo2` protocols
2 parents 77dc2fd + 5f67272 commit 8833ff9

File tree

3 files changed

+332
-1
lines changed

3 files changed

+332
-1
lines changed

src/proto/media/disk.rs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
//! Disk I/O protocols.
2+
3+
use crate::proto::Protocol;
4+
use crate::{unsafe_guid, Event, Result, Status};
5+
use core::ptr::NonNull;
6+
7+
/// The disk I/O protocol.
8+
///
9+
/// This protocol is used to abstract the block accesses of the block I/O
10+
/// protocol to a more general offset-length protocol. Firmware is
11+
/// reponsible for adding this protocol to any block I/O interface that
12+
/// appears in the system that does not already have a disk I/O protocol.
13+
#[repr(C)]
14+
#[unsafe_guid("ce345171-ba0b-11d2-8e4f-00a0c969723b")]
15+
#[derive(Protocol)]
16+
pub struct DiskIo {
17+
revision: u64,
18+
read_disk: extern "efiapi" fn(
19+
this: &DiskIo,
20+
media_id: u32,
21+
offset: u64,
22+
len: usize,
23+
buffer: *mut u8,
24+
) -> Status,
25+
write_disk: extern "efiapi" fn(
26+
this: &mut DiskIo,
27+
media_id: u32,
28+
offset: u64,
29+
len: usize,
30+
buffer: *const u8,
31+
) -> Status,
32+
}
33+
34+
impl DiskIo {
35+
/// Reads bytes from the disk device.
36+
///
37+
/// # Arguments:
38+
/// * `media_id` - ID of the medium to be read.
39+
/// * `offset` - Starting byte offset on the logical block I/O device to read from.
40+
/// * `buffer` - Pointer to a buffer to read into.
41+
///
42+
/// # Errors:
43+
/// * `uefi::status::INVALID_PARAMETER` The read request contains device addresses that
44+
/// are not valid for the device.
45+
/// * `uefi::status::DEVICE_ERROR` The device reported an error while performing
46+
/// the read operation.
47+
/// * `uefi::status::NO_MEDIA` There is no medium in the device.
48+
/// * `uefi::status::MEDIA_CHANGED` `media_id` is not for the current medium.
49+
pub fn read_disk(&self, media_id: u32, offset: u64, buffer: &mut [u8]) -> Result {
50+
(self.read_disk)(self, media_id, offset, buffer.len(), buffer.as_mut_ptr()).into()
51+
}
52+
53+
/// Writes bytes to the disk device.
54+
///
55+
/// # Arguments:
56+
/// * `media_id` - ID of the medium to be written.
57+
/// * `offset` - Starting byte offset on the logical block I/O device to write to.
58+
/// * `buffer` - Pointer to a buffer to write from.
59+
///
60+
/// # Errors:
61+
/// * `uefi::status::INVALID_PARAMETER` The write request contains device addresses that
62+
/// are not valid for the device.
63+
/// * `uefi::status::DEVICE_ERROR` The device reported an error while performing
64+
/// the write operation.
65+
/// * `uefi::status::NO_MEDIA` There is no medium in the device.
66+
/// * `uefi::status::MEDIA_CHANGED` `media_id` is not for the current medium.
67+
/// * `uefi::status::WRITE_PROTECTED` The device cannot be written to.
68+
pub fn write_disk(&mut self, media_id: u32, offset: u64, buffer: &[u8]) -> Result {
69+
(self.write_disk)(self, media_id, offset, buffer.len(), buffer.as_ptr()).into()
70+
}
71+
}
72+
73+
/// Asynchronous transaction token for disk I/O 2 operations.
74+
#[repr(C)]
75+
pub struct DiskIo2Token {
76+
/// Event to be signalled when an asynchronous disk I/O operation completes.
77+
pub event: Event,
78+
/// Transaction status code.
79+
pub transaction_status: Status,
80+
}
81+
82+
/// The disk I/O 2 protocol.
83+
///
84+
/// This protocol provides an extension to the disk I/O protocol to enable
85+
/// non-blocking / asynchronous byte-oriented disk operation.
86+
#[repr(C)]
87+
#[unsafe_guid("151c8eae-7f2c-472c-9e54-9828194f6a88")]
88+
#[derive(Protocol)]
89+
pub struct DiskIo2 {
90+
revision: u64,
91+
cancel: extern "efiapi" fn(this: &mut DiskIo2) -> Status,
92+
read_disk_ex: extern "efiapi" fn(
93+
this: &DiskIo2,
94+
media_id: u32,
95+
offset: u64,
96+
token: Option<NonNull<DiskIo2Token>>,
97+
len: usize,
98+
buffer: *mut u8,
99+
) -> Status,
100+
write_disk_ex: extern "efiapi" fn(
101+
this: &mut DiskIo2,
102+
media_id: u32,
103+
offset: u64,
104+
token: Option<NonNull<DiskIo2Token>>,
105+
len: usize,
106+
buffer: *const u8,
107+
) -> Status,
108+
flush_disk_ex:
109+
extern "efiapi" fn(this: &mut DiskIo2, token: Option<NonNull<DiskIo2Token>>) -> Status,
110+
}
111+
112+
impl DiskIo2 {
113+
/// Terminates outstanding asynchronous requests to the device.
114+
///
115+
/// # Errors:
116+
/// * `uefi::status::DEVICE_ERROR` The device reported an error while performing
117+
/// the cancel operation.
118+
pub fn cancel(&mut self) -> Result {
119+
(self.cancel)(self).into()
120+
}
121+
122+
/// Reads bytes from the disk device.
123+
///
124+
/// # Arguments:
125+
/// * `media_id` - ID of the medium to be read from.
126+
/// * `offset` - Starting byte offset on the logical block I/O device to read from.
127+
/// * `token` - Transaction token for asynchronous read.
128+
/// * `len` - Buffer size.
129+
/// * `buffer` - Buffer to read into.
130+
///
131+
/// # Safety
132+
///
133+
/// Because of the asynchronous nature of the disk transaction, manual lifetime
134+
/// tracking is required.
135+
///
136+
/// # Errors:
137+
/// * `uefi::status::INVALID_PARAMETER` The read request contains device addresses
138+
/// that are not valid for the device.
139+
/// * `uefi::status::OUT_OF_RESOURCES` The request could not be completed due to
140+
/// a lack of resources.
141+
/// * `uefi::status::MEDIA_CHANGED` `media_id` is not for the current medium.
142+
/// * `uefi::status::NO_MEDIA` There is no medium in the device.
143+
/// * `uefi::status::DEVICE_ERROR` The device reported an error while performing
144+
/// the read operation.
145+
pub unsafe fn read_disk_raw(
146+
&self,
147+
media_id: u32,
148+
offset: u64,
149+
token: Option<NonNull<DiskIo2Token>>,
150+
len: usize,
151+
buffer: *mut u8,
152+
) -> Result {
153+
(self.read_disk_ex)(self, media_id, offset, token, len, buffer).into()
154+
}
155+
156+
/// Writes bytes to the disk device.
157+
///
158+
/// # Arguments:
159+
/// * `media_id` - ID of the medium to write to.
160+
/// * `offset` - Starting byte offset on the logical block I/O device to write to.
161+
/// * `token` - Transaction token for asynchronous write.
162+
/// * `len` - Buffer size.
163+
/// * `buffer` - Buffer to write from.
164+
///
165+
/// # Safety
166+
///
167+
/// Because of the asynchronous nature of the disk transaction, manual lifetime
168+
/// tracking is required.
169+
///
170+
/// # Errors:
171+
/// * `uefi::status::INVALID_PARAMETER` The write request contains device addresses
172+
/// that are not valid for the device.
173+
/// * `uefi::status::OUT_OF_RESOURCES` The request could not be completed due to
174+
/// a lack of resources.
175+
/// * `uefi::status::MEDIA_CHANGED` `media_id` is not for the current medium.
176+
/// * `uefi::status::NO_MEDIA` There is no medium in the device.
177+
/// * `uefi::status::DEVICE_ERROR` The device reported an error while performing
178+
/// the write operation.
179+
/// * `uefi::status::WRITE_PROTECTED` The device cannot be written to.
180+
pub unsafe fn write_disk_raw(
181+
&mut self,
182+
media_id: u32,
183+
offset: u64,
184+
token: Option<NonNull<DiskIo2Token>>,
185+
len: usize,
186+
buffer: *const u8,
187+
) -> Result {
188+
(self.write_disk_ex)(self, media_id, offset, token, len, buffer).into()
189+
}
190+
191+
/// Flushes all modified data to the physical device.
192+
///
193+
/// # Arguments:
194+
/// * `token` - Transaction token for the asynchronous flush.
195+
///
196+
/// # Errors:
197+
/// * `uefi::status::OUT_OF_RESOURCES` The request could not be completed due to
198+
/// a lack of resources.
199+
/// * `uefi::status::MEDIA_CHANGED` The medium in the device has changed since
200+
/// the last access.
201+
/// * `uefi::status::NO_MEDIA` There is no medium in the device.
202+
/// * `uefi::status::DEVICE_ERROR` The device reported an error while performing
203+
/// the flush operation.
204+
/// * `uefi::status::WRITE_PROTECTED` The device cannot be written to.
205+
pub fn flush_disk(&mut self, token: Option<NonNull<DiskIo2Token>>) -> Result {
206+
(self.flush_disk_ex)(self, token).into()
207+
}
208+
}

src/proto/media/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
pub mod file;
88

99
pub mod block;
10+
pub mod disk;
1011
pub mod fs;
1112
pub mod partition;

uefi-test-runner/src/proto/media/known_disk.rs

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use alloc::string::ToString;
2+
use core::ptr::NonNull;
23
use uefi::prelude::*;
4+
use uefi::proto::media::block::BlockIO;
5+
use uefi::proto::media::disk::{DiskIo, DiskIo2, DiskIo2Token};
36
use uefi::proto::media::file::{
47
Directory, File, FileAttribute, FileInfo, FileMode, FileSystemInfo,
58
};
69
use uefi::proto::media::fs::SimpleFileSystem;
7-
use uefi::table::boot::{OpenProtocolAttributes, OpenProtocolParams};
10+
use uefi::table::boot::{EventType, OpenProtocolAttributes, OpenProtocolParams, Tpl};
811
use uefi::table::runtime::{Daylight, Time, TimeParams};
912

1013
/// Test directory entry iteration.
@@ -138,6 +141,121 @@ fn test_create_file(directory: &mut Directory) {
138141
file.write(b"test output data").unwrap();
139142
}
140143

144+
/// Tests raw disk I/O.
145+
fn test_raw_disk_io(handle: Handle, image: Handle, bt: &BootServices) {
146+
info!("Testing raw disk I/O");
147+
148+
// Open the block I/O protocol on the handle
149+
let block_io = bt
150+
.open_protocol::<BlockIO>(
151+
OpenProtocolParams {
152+
handle,
153+
agent: image,
154+
controller: None,
155+
},
156+
OpenProtocolAttributes::GetProtocol,
157+
)
158+
.expect("Failed to get block I/O protocol");
159+
160+
// Open the disk I/O protocol on the input handle
161+
let disk_io = bt
162+
.open_protocol::<DiskIo>(
163+
OpenProtocolParams {
164+
handle,
165+
agent: image,
166+
controller: None,
167+
},
168+
OpenProtocolAttributes::GetProtocol,
169+
)
170+
.expect("Failed to get disk I/O protocol");
171+
172+
// Read from the first sector of the disk into the buffer
173+
let mut buf = vec![0; 512];
174+
disk_io
175+
.read_disk(block_io.media().media_id(), 0, &mut buf)
176+
.expect("Failed to read from disk");
177+
178+
// Verify that the disk's MBR signature is correct
179+
assert_eq!(buf[510], 0x55);
180+
assert_eq!(buf[511], 0xaa);
181+
182+
info!("Raw disk I/O succeeded");
183+
}
184+
185+
/// Asynchronous disk I/O task context
186+
#[repr(C)]
187+
struct DiskIoTask {
188+
/// Token for the transaction
189+
token: DiskIo2Token,
190+
/// Buffer holding the read data
191+
buffer: [u8; 512],
192+
}
193+
194+
/// Tests raw disk I/O through the DiskIo2 protocol.
195+
fn test_raw_disk_io2(handle: Handle, image: Handle, bt: &BootServices) {
196+
info!("Testing raw disk I/O 2");
197+
198+
// Open the disk I/O protocol on the input handle
199+
if let Ok(disk_io2) = bt.open_protocol::<DiskIo2>(
200+
OpenProtocolParams {
201+
handle,
202+
agent: image,
203+
controller: None,
204+
},
205+
OpenProtocolAttributes::GetProtocol,
206+
) {
207+
// Open the block I/O protocol on the handle
208+
let block_io = bt
209+
.open_protocol::<BlockIO>(
210+
OpenProtocolParams {
211+
handle,
212+
agent: image,
213+
controller: None,
214+
},
215+
OpenProtocolAttributes::GetProtocol,
216+
)
217+
.expect("Failed to get block I/O protocol");
218+
219+
unsafe {
220+
// Create the completion event
221+
let mut event = bt
222+
.create_event(EventType::empty(), Tpl::NOTIFY, None, None)
223+
.expect("Failed to create disk I/O completion event");
224+
225+
// Initialise the task context
226+
let mut task = DiskIoTask {
227+
token: DiskIo2Token {
228+
event: event.unsafe_clone(),
229+
transaction_status: uefi::Status::NOT_READY,
230+
},
231+
buffer: [0; 512],
232+
};
233+
234+
// Initiate the asynchronous read operation
235+
disk_io2
236+
.read_disk_raw(
237+
block_io.media().media_id(),
238+
0,
239+
NonNull::new(&mut task.token as _),
240+
task.buffer.len(),
241+
task.buffer.as_mut_ptr(),
242+
)
243+
.expect("Failed to initiate asynchronous disk I/O read");
244+
245+
// Wait for the transaction to complete
246+
bt.wait_for_event(core::slice::from_mut(&mut event))
247+
.expect("Failed to wait on completion event");
248+
249+
// Verify that the disk's MBR signature is correct
250+
assert_eq!(task.token.transaction_status, uefi::Status::SUCCESS);
251+
assert_eq!(task.buffer[510], 0x55);
252+
assert_eq!(task.buffer[511], 0xaa);
253+
254+
info!("Raw disk I/O 2 succeeded");
255+
}
256+
}
257+
}
258+
141259
/// Run various tests on a special test disk. The disk is created by
142260
/// xtask/src/disk.rs.
143261
pub fn test_known_disk(image: Handle, bt: &BootServices) {
@@ -178,6 +296,10 @@ pub fn test_known_disk(image: Handle, bt: &BootServices) {
178296
continue;
179297
}
180298

299+
// Test raw disk I/O first
300+
test_raw_disk_io(handle, image, bt);
301+
test_raw_disk_io2(handle, image, bt);
302+
181303
assert!(!fs_info.read_only());
182304
assert_eq!(fs_info.volume_size(), 512 * 1192);
183305
assert_eq!(fs_info.free_space(), 512 * 1190);

0 commit comments

Comments
 (0)