1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//! Disk I/O protocols.

use crate::proto::unsafe_protocol;
use crate::util::opt_nonnull_to_ptr;
use crate::{Event, Result, Status, StatusExt};
use core::ptr::NonNull;
use uefi_raw::protocol::disk::{DiskIo2Protocol, DiskIoProtocol};

/// The disk I/O protocol.
///
/// This protocol is used to abstract the block accesses of the block I/O
/// protocol to a more general offset-length protocol. Firmware is
/// responsible for adding this protocol to any block I/O interface that
/// appears in the system that does not already have a disk I/O protocol.
#[derive(Debug)]
#[repr(transparent)]
#[unsafe_protocol(DiskIoProtocol::GUID)]
pub struct DiskIo(DiskIoProtocol);

impl DiskIo {
    /// Reads bytes from the disk device.
    ///
    /// # Arguments:
    /// * `media_id` - ID of the medium to be read.
    /// * `offset` - Starting byte offset on the logical block I/O device to read from.
    /// * `buffer` - Pointer to a buffer to read into.
    ///
    /// # Errors:
    /// * `uefi::status::INVALID_PARAMETER` The read request contains device addresses that
    ///                                     are not valid for the device.
    /// * `uefi::status::DEVICE_ERROR`      The device reported an error while performing
    ///                                     the read operation.
    /// * `uefi::status::NO_MEDIA`          There is no medium in the device.
    /// * `uefi::status::MEDIA_CHANGED`     `media_id` is not for the current medium.
    pub fn read_disk(&self, media_id: u32, offset: u64, buffer: &mut [u8]) -> Result {
        unsafe {
            (self.0.read_disk)(
                &self.0,
                media_id,
                offset,
                buffer.len(),
                buffer.as_mut_ptr().cast(),
            )
        }
        .to_result()
    }

    /// Writes bytes to the disk device.
    ///
    /// # Arguments:
    /// * `media_id` - ID of the medium to be written.
    /// * `offset` - Starting byte offset on the logical block I/O device to write to.
    /// * `buffer` - Pointer to a buffer to write from.
    ///
    /// # Errors:
    /// * `uefi::status::INVALID_PARAMETER` The write request contains device addresses that
    ///                                     are not valid for the device.
    /// * `uefi::status::DEVICE_ERROR`      The device reported an error while performing
    ///                                     the write operation.
    /// * `uefi::status::NO_MEDIA`          There is no medium in the device.
    /// * `uefi::status::MEDIA_CHANGED`     `media_id` is not for the current medium.
    /// * `uefi::status::WRITE_PROTECTED`   The device cannot be written to.
    pub fn write_disk(&mut self, media_id: u32, offset: u64, buffer: &[u8]) -> Result {
        unsafe {
            (self.0.write_disk)(
                &mut self.0,
                media_id,
                offset,
                buffer.len(),
                buffer.as_ptr().cast(),
            )
        }
        .to_result()
    }
}

/// Asynchronous transaction token for disk I/O 2 operations.
#[repr(C)]
#[derive(Debug)]
pub struct DiskIo2Token {
    /// Event to be signalled when an asynchronous disk I/O operation completes.
    pub event: Option<Event>,
    /// Transaction status code.
    pub transaction_status: Status,
}

/// The disk I/O 2 protocol.
///
/// This protocol provides an extension to the disk I/O protocol to enable
/// non-blocking / asynchronous byte-oriented disk operation.
#[derive(Debug)]
#[repr(transparent)]
#[unsafe_protocol(DiskIo2Protocol::GUID)]
pub struct DiskIo2(DiskIo2Protocol);

impl DiskIo2 {
    /// Terminates outstanding asynchronous requests to the device.
    ///
    /// # Errors:
    /// * `uefi::status::DEVICE_ERROR`  The device reported an error while performing
    ///                                 the cancel operation.
    pub fn cancel(&mut self) -> Result {
        unsafe { (self.0.cancel)(&mut self.0) }.to_result()
    }

    /// Reads bytes from the disk device.
    ///
    /// # Arguments:
    /// * `media_id` - ID of the medium to be read from.
    /// * `offset` - Starting byte offset on the logical block I/O device to read from.
    /// * `token` - Transaction token for asynchronous read.
    /// * `len` - Buffer size.
    /// * `buffer` - Buffer to read into.
    ///
    /// # Safety
    ///
    /// Because of the asynchronous nature of the disk transaction, manual lifetime
    /// tracking is required.
    ///
    /// # Errors:
    /// * `uefi::status::INVALID_PARAMETER` The read request contains device addresses
    ///                                     that are not valid for the device.
    /// * `uefi::status::OUT_OF_RESOURCES`  The request could not be completed due to
    ///                                     a lack of resources.
    /// * `uefi::status::MEDIA_CHANGED`     `media_id` is not for the current medium.
    /// * `uefi::status::NO_MEDIA`          There is no medium in the device.
    /// * `uefi::status::DEVICE_ERROR`      The device reported an error while performing
    ///                                     the read operation.
    pub unsafe fn read_disk_raw(
        &self,
        media_id: u32,
        offset: u64,
        token: Option<NonNull<DiskIo2Token>>,
        len: usize,
        buffer: *mut u8,
    ) -> Result {
        let token = opt_nonnull_to_ptr(token);
        (self.0.read_disk_ex)(&self.0, media_id, offset, token.cast(), len, buffer.cast())
            .to_result()
    }

    /// Writes bytes to the disk device.
    ///
    /// # Arguments:
    /// * `media_id` - ID of the medium to write to.
    /// * `offset` - Starting byte offset on the logical block I/O device to write to.
    /// * `token` - Transaction token for asynchronous write.
    /// * `len` - Buffer size.
    /// * `buffer` - Buffer to write from.
    ///
    /// # Safety
    ///
    /// Because of the asynchronous nature of the disk transaction, manual lifetime
    /// tracking is required.
    ///
    /// # Errors:
    /// * `uefi::status::INVALID_PARAMETER` The write request contains device addresses
    ///                                     that are not valid for the device.
    /// * `uefi::status::OUT_OF_RESOURCES`  The request could not be completed due to
    ///                                     a lack of resources.
    /// * `uefi::status::MEDIA_CHANGED`     `media_id` is not for the current medium.
    /// * `uefi::status::NO_MEDIA`          There is no medium in the device.
    /// * `uefi::status::DEVICE_ERROR`      The device reported an error while performing
    ///                                     the write operation.
    /// * `uefi::status::WRITE_PROTECTED`   The device cannot be written to.
    pub unsafe fn write_disk_raw(
        &mut self,
        media_id: u32,
        offset: u64,
        token: Option<NonNull<DiskIo2Token>>,
        len: usize,
        buffer: *const u8,
    ) -> Result {
        let token = opt_nonnull_to_ptr(token);
        (self.0.write_disk_ex)(
            &mut self.0,
            media_id,
            offset,
            token.cast(),
            len,
            buffer.cast(),
        )
        .to_result()
    }

    /// Flushes all modified data to the physical device.
    ///
    /// # Arguments:
    /// * `token` - Transaction token for the asynchronous flush.
    ///
    /// # Errors:
    /// * `uefi::status::OUT_OF_RESOURCES`  The request could not be completed due to
    ///                                     a lack of resources.
    /// * `uefi::status::MEDIA_CHANGED`     The medium in the device has changed since
    ///                                     the last access.
    /// * `uefi::status::NO_MEDIA`          There is no medium in the device.
    /// * `uefi::status::DEVICE_ERROR`      The device reported an error while performing
    ///                                     the flush operation.
    /// * `uefi::status::WRITE_PROTECTED`   The device cannot be written to.
    pub fn flush_disk(&mut self, token: Option<NonNull<DiskIo2Token>>) -> Result {
        let token = opt_nonnull_to_ptr(token);
        unsafe { (self.0.flush_disk_ex)(&mut self.0, token.cast()) }.to_result()
    }
}