-
-
Notifications
You must be signed in to change notification settings - Fork 169
/
Copy pathpass_thru.rs
242 lines (225 loc) · 9.56 KB
/
pass_thru.rs
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
// SPDX-License-Identifier: MIT OR Apache-2.0
//! ATA Pass Thru Protocol.
use super::{AtaRequest, AtaResponse};
use crate::helpers::AlignedBuffer;
use crate::StatusExt;
use core::alloc::LayoutError;
use core::ptr;
use uefi_macros::unsafe_protocol;
use uefi_raw::protocol::ata::AtaPassThruProtocol;
use uefi_raw::Status;
/// TODO: docs
pub type AtaPassThruMode = uefi_raw::protocol::ata::AtaPassThruMode;
/// The ATA Pass Thru Protocol.
///
/// One protocol instance represents one ATA controller connected to the machine.
///
/// # UEFI Spec Description
/// Provides services that allow ATA commands to be sent to ATA Devices attached to an ATA controller. Packet-
/// based commands would be sent to ATAPI devices only through the Extended SCSI Pass Thru Protocol. While
/// the ATA_PASS_THRU interface would expose an interface to the underlying ATA devices on an ATA controller,
/// EXT_SCSI_PASS_THRU is responsible for exposing a packet-based command interface for the ATAPI devices on
/// the same ATA controller.
#[derive(Debug)]
#[repr(transparent)]
#[unsafe_protocol(AtaPassThruProtocol::GUID)]
pub struct AtaPassThru(AtaPassThruProtocol);
impl AtaPassThru {
/// Retrieves the mode structure for the Extended SCSI Pass Thru protocol.
///
/// # Returns
/// The [`AtaPassThruMode`] structure containing configuration details of the protocol.
#[must_use]
pub fn mode(&self) -> AtaPassThruMode {
unsafe { (*self.0.mode).clone() }
}
/// Retrieves the I/O buffer alignment required by this SCSI channel.
///
/// # Returns
/// - A `u32` value representing the required I/O alignment in bytes.
#[must_use]
pub fn io_align(&self) -> u32 {
self.mode().io_align
}
/// Allocates an I/O buffer with the necessary alignment for this ATA Controller.
///
/// You can alternatively do this yourself using the [`AlignedBuffer`] helper directly.
/// The `ata` api will validate that your buffers have the correct alignment and error
/// if they don't.
///
/// # Parameters
/// - `len`: The size (in bytes) of the buffer to allocate.
///
/// # Returns
/// [`AlignedBuffer`] containing the allocated memory.
///
/// # Errors
/// This method can fail due to alignment or memory allocation issues.
pub fn alloc_io_buffer(&self, len: usize) -> Result<AlignedBuffer, LayoutError> {
AlignedBuffer::alloc(len, self.io_align() as usize)
}
/// Iterate over all potential ATA devices on this channel.
///
/// # Warning
/// Depending on the UEFI implementation, this does not only return all actually available devices.
/// Most implementations instead return a list of all possible fully-qualified device addresses.
/// You have to probe for availability yourself, using [`AtaDevice::execute_command`].
///
/// # Returns
/// [`AtaDeviceIterator`] to iterate through connected ATA devices.
#[must_use]
pub const fn iter_devices(&self) -> AtaDeviceIterator<'_> {
AtaDeviceIterator {
proto: &self.0,
end_of_port: true,
prev_port: 0xFFFF,
prev_pmp: 0xFFFF,
}
}
}
/// Represents an ATA device on a controller.
///
/// # Warning
/// This is only a potentially valid device address. Verify it by probing for an actually
/// available / connected device using [`AtaDevice::execute_command`] before doing anything meaningful.
#[derive(Debug)]
pub struct AtaDevice<'a> {
proto: &'a AtaPassThruProtocol,
port: u16,
pmp: u16,
}
impl AtaDevice<'_> {
fn proto_mut(&mut self) -> *mut AtaPassThruProtocol {
ptr::from_ref(self.proto).cast_mut()
}
/// Returns the port number of the device.
///
/// # Details
/// - For SATA: This is the port number on the motherboard or controller.
/// - For IDE: This is `0` for the primary bus and `1` for the secondary bus.
#[must_use]
pub const fn port(&self) -> u16 {
self.port
}
/// Returns the port multiplier port (PMP) number for the device.
///
/// # Details
/// - For SATA: `0xFFFF` indicates a direct connection to the port, while other values
/// indicate the port number on a port-multiplier device.
/// - For IDE: `0` represents the master device, and `1` represents the slave device.
#[must_use]
pub const fn port_multiplier_port(&self) -> u16 {
self.pmp
}
/// Resets the ATA device.
///
/// This method attempts to reset the specified ATA device, restoring it to its default state.
///
/// # Errors
/// - [`Status::UNSUPPORTED`] The ATA controller does not support a device reset operation.
/// - [`Status::INVALID_PARAMETER`] The `Port` or `PortMultiplierPort` values are invalid.
/// - [`Status::DEVICE_ERROR`] A device error occurred while attempting to reset the specified ATA device.
/// - [`Status::TIMEOUT`] A timeout occurred while attempting to reset the specified ATA device.
pub fn reset(&mut self) -> crate::Result<()> {
unsafe { (self.proto.reset_device)(self.proto_mut(), self.port, self.pmp).to_result() }
}
/// Executes a command on the device.
///
/// # Parameters
/// - `req`: The request structure containing details about the command to execute.
///
/// # Returns
/// [`AtaResponse`] containing the results of the operation, such as data and status.
///
/// # Errors
/// - [`Status::BAD_BUFFER_SIZE`] The ATA command was not executed because the buffer size exceeded the allowed transfer size.
/// The number of bytes that could be transferred is returned in `InTransferLength` or `OutTransferLength`.
/// - [`Status::NOT_READY`] The ATA command could not be sent because too many commands are already queued. Retry the operation later.
/// - [`Status::DEVICE_ERROR`] A device error occurred while attempting to send the ATA command. Refer to `Asb` for additional status details.
/// - [`Status::INVALID_PARAMETER`] The `Port`, `PortMultiplierPort`, or the contents of `Acb` are invalid.
/// The command was not sent, and no additional status information is available.
/// - [`Status::UNSUPPORTED`] The host adapter does not support the command described by the ATA command.
/// The command was not sent, and no additional status information is available.
/// - [`Status::TIMEOUT`] A timeout occurred while waiting for the ATA command to execute. Refer to `Asb` for additional status details.
pub fn execute_command<'req>(
&mut self,
mut req: AtaRequest<'req>,
) -> crate::Result<AtaResponse<'req>> {
req.packet.acb = &req.acb;
unsafe {
(self.proto.pass_thru)(
self.proto_mut(),
self.port,
self.pmp,
&mut req.packet,
ptr::null_mut(),
)
.to_result_with_val(|| AtaResponse { req })
}
}
}
/// An iterator over the drives connected to an ATA controller.
///
/// The iterator yields [`AtaDevice`] instances, each representing one *potential*
/// drive connected to the ATA controller. You have to probe whether the drive
/// is actually available and connected!
#[derive(Debug)]
pub struct AtaDeviceIterator<'a> {
proto: &'a AtaPassThruProtocol,
// when there are no more devices on this port -> get next port
end_of_port: bool,
prev_port: u16,
prev_pmp: u16,
}
impl<'a> Iterator for AtaDeviceIterator<'a> {
type Item = AtaDevice<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.end_of_port {
let result = unsafe { (self.proto.get_next_port)(self.proto, &mut self.prev_port) };
match result {
Status::SUCCESS => self.end_of_port = false,
Status::NOT_FOUND => return None, // no more ports / devices. End of list
_ => panic!("Must not happen according to spec!"),
}
}
// get next device on port
// The UEFI spec states, that:
// If there is no port multiplier detected on the given port, the initial query of get_next_device()
// is allowed to return either of:
// - EFI_SUCCESS & PMP = 0xFFFF
// - EFI_NOT_FOUND
// But even when there is no detected port multiplier, there might be a device directly connected
// to the port! Port without attached port-multiplier uses a pmp of 0xFFFF.
let was_first = self.prev_pmp == 0xFFFF;
let result = unsafe {
(self.proto.get_next_device)(self.proto, self.prev_port, &mut self.prev_pmp)
};
match result {
Status::SUCCESS => {
if self.prev_pmp == 0xFFFF {
self.end_of_port = true;
}
return Some(AtaDevice {
proto: self.proto,
port: self.prev_port,
pmp: self.prev_pmp,
});
}
Status::NOT_FOUND => {
self.end_of_port = true;
self.prev_pmp = 0xFFFF;
if was_first {
// no port multiplier on port, return valid device anyway.
return Some(AtaDevice {
proto: self.proto,
port: self.prev_port,
pmp: 0xFFFF,
});
}
}
_ => panic!("Must not happen according to spec!"),
}
}
}
}