Skip to content

Commit 61b36b5

Browse files
committed
uefi: Add safe protocol implementation for ATA_PASS_THRU_PROTOCOL
1 parent afa35f7 commit 61b36b5

File tree

3 files changed

+268
-1
lines changed

3 files changed

+268
-1
lines changed

Diff for: uefi/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Added `proto::device_path::DevicePath::append_node()`.
1111
- Added `proto::scsi::pass_thru::ExtScsiPassThru`.
1212
- Added `proto::nvme::pass_thru::NvmePassThru`.
13+
- Added `proto::ata::pass_thru::AtaPassThru`.
1314

1415
## Changed
1516
- **Breaking:** Removed `BootPolicyError` as `BootPolicy` construction is no

Diff for: uefi/src/proto/ata/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! ATA Protocols.
44
55
use crate::mem::{AlignedBuffer, AlignmentError};
6+
use crate::util::usize_from_u32;
67
use core::alloc::LayoutError;
78
use core::marker::PhantomData;
89
use core::ptr;
@@ -11,6 +12,8 @@ use uefi_raw::protocol::ata::{
1112
AtaCommandBlock, AtaPassThruCommandPacket, AtaPassThruLength, AtaStatusBlock,
1213
};
1314

15+
pub mod pass_thru;
16+
1417
/// Represents the protocol for ATA Pass Thru command handling.
1518
///
1619
/// This type defines the protocols supported for passing commands through the ATA controller.
@@ -57,7 +60,7 @@ impl<'a> AtaRequestBuilder<'a> {
5760
) -> Result<Self, LayoutError> {
5861
// status block has alignment requirements!
5962
let mut asb =
60-
AlignedBuffer::from_size_align(size_of::<AtaStatusBlock>(), io_align as usize)?;
63+
AlignedBuffer::from_size_align(size_of::<AtaStatusBlock>(), usize_from_u32(io_align))?;
6164
Ok(Self {
6265
req: AtaRequest {
6366
io_align,

Diff for: uefi/src/proto/ata/pass_thru.rs

+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
//! ATA Pass Thru Protocol.
4+
5+
use super::{AtaRequest, AtaResponse};
6+
use crate::mem::{AlignedBuffer, PoolAllocation};
7+
use crate::proto::device_path::PoolDevicePathNode;
8+
use crate::StatusExt;
9+
use core::alloc::LayoutError;
10+
use core::ptr::{self, NonNull};
11+
use uefi_macros::unsafe_protocol;
12+
use uefi_raw::protocol::ata::AtaPassThruProtocol;
13+
use uefi_raw::protocol::device_path::DevicePathProtocol;
14+
use uefi_raw::Status;
15+
16+
/// Mode structure with controller-specific information.
17+
pub type AtaPassThruMode = uefi_raw::protocol::ata::AtaPassThruMode;
18+
19+
/// The ATA Pass Thru Protocol.
20+
///
21+
/// One protocol instance represents one ATA controller connected to the machine.
22+
///
23+
/// This API offers a safe and convenient, yet still low-level interface to ATA devices.
24+
/// It is designed as a foundational layer, leaving higher-level abstractions responsible for implementing
25+
/// richer storage semantics, device-specific commands, and advanced use cases.
26+
///
27+
/// # UEFI Spec Description
28+
/// Provides services that allow ATA commands to be sent to ATA Devices attached to an ATA controller. Packet-
29+
/// based commands would be sent to ATAPI devices only through the Extended SCSI Pass Thru Protocol. While
30+
/// the ATA_PASS_THRU interface would expose an interface to the underlying ATA devices on an ATA controller,
31+
/// EXT_SCSI_PASS_THRU is responsible for exposing a packet-based command interface for the ATAPI devices on
32+
/// the same ATA controller.
33+
#[derive(Debug)]
34+
#[repr(transparent)]
35+
#[unsafe_protocol(AtaPassThruProtocol::GUID)]
36+
pub struct AtaPassThru(AtaPassThruProtocol);
37+
38+
impl AtaPassThru {
39+
/// Retrieves the mode structure for the Extended SCSI Pass Thru protocol.
40+
///
41+
/// # Returns
42+
/// The [`AtaPassThruMode`] structure containing configuration details of the protocol.
43+
#[must_use]
44+
pub fn mode(&self) -> AtaPassThruMode {
45+
unsafe { (*self.0.mode).clone() }
46+
}
47+
48+
/// Retrieves the I/O buffer alignment required by this SCSI channel.
49+
///
50+
/// # Returns
51+
/// - A `u32` value representing the required I/O alignment in bytes.
52+
#[must_use]
53+
pub fn io_align(&self) -> u32 {
54+
self.mode().io_align
55+
}
56+
57+
/// Allocates an I/O buffer with the necessary alignment for this ATA Controller.
58+
///
59+
/// You can alternatively do this yourself using the [`AlignedBuffer`] helper directly.
60+
/// The `ata` api will validate that your buffers have the correct alignment and error
61+
/// if they don't.
62+
///
63+
/// # Parameters
64+
/// - `len`: The size (in bytes) of the buffer to allocate.
65+
///
66+
/// # Returns
67+
/// [`AlignedBuffer`] containing the allocated memory.
68+
///
69+
/// # Errors
70+
/// This method can fail due to alignment or memory allocation issues.
71+
pub fn alloc_io_buffer(&self, len: usize) -> Result<AlignedBuffer, LayoutError> {
72+
AlignedBuffer::from_size_align(len, self.io_align() as usize)
73+
}
74+
75+
/// Iterate over all potential ATA devices on this channel.
76+
///
77+
/// # Warning
78+
/// Depending on the UEFI implementation, this does not only return all actually available devices.
79+
/// Most implementations instead return a list of all possible fully-qualified device addresses.
80+
/// You have to probe for availability yourself, using [`AtaDevice::execute_command`].
81+
///
82+
/// # Returns
83+
/// [`AtaDeviceIterator`] to iterate through connected ATA devices.
84+
#[must_use]
85+
pub const fn iter_devices(&self) -> AtaDeviceIterator<'_> {
86+
AtaDeviceIterator {
87+
proto: &self.0,
88+
end_of_port: true,
89+
prev_port: 0xFFFF,
90+
prev_pmp: 0xFFFF,
91+
}
92+
}
93+
}
94+
95+
/// Represents an ATA device on a controller.
96+
///
97+
/// # Warning
98+
/// This is only a potentially valid device address. Verify it by probing for an actually
99+
/// available / connected device using [`AtaDevice::execute_command`] before doing anything meaningful.
100+
#[derive(Debug)]
101+
pub struct AtaDevice<'a> {
102+
proto: &'a AtaPassThruProtocol,
103+
port: u16,
104+
pmp: u16,
105+
}
106+
107+
impl AtaDevice<'_> {
108+
fn proto_mut(&mut self) -> *mut AtaPassThruProtocol {
109+
ptr::from_ref(self.proto).cast_mut()
110+
}
111+
112+
/// Returns the port number of the device.
113+
///
114+
/// # Details
115+
/// - For SATA: This is the port number on the motherboard or controller.
116+
/// - For IDE: This is `0` for the primary bus and `1` for the secondary bus.
117+
#[must_use]
118+
pub const fn port(&self) -> u16 {
119+
self.port
120+
}
121+
122+
/// Returns the port multiplier port (PMP) number for the device.
123+
///
124+
/// # Details
125+
/// - For SATA: `0xFFFF` indicates a direct connection to the port, while other values
126+
/// indicate the port number on a port-multiplier device.
127+
/// - For IDE: `0` represents the master device, and `1` represents the slave device.
128+
#[must_use]
129+
pub const fn port_multiplier_port(&self) -> u16 {
130+
self.pmp
131+
}
132+
133+
/// Resets the ATA device.
134+
///
135+
/// This method attempts to reset the specified ATA device, restoring it to its default state.
136+
///
137+
/// # Errors
138+
/// - [`Status::UNSUPPORTED`] The ATA controller does not support a device reset operation.
139+
/// - [`Status::INVALID_PARAMETER`] The `Port` or `PortMultiplierPort` values are invalid.
140+
/// - [`Status::DEVICE_ERROR`] A device error occurred while attempting to reset the specified ATA device.
141+
/// - [`Status::TIMEOUT`] A timeout occurred while attempting to reset the specified ATA device.
142+
pub fn reset(&mut self) -> crate::Result<()> {
143+
unsafe { (self.proto.reset_device)(self.proto_mut(), self.port, self.pmp).to_result() }
144+
}
145+
146+
/// Get the final device path node for this device.
147+
///
148+
/// For a full [`crate::proto::device_path::DevicePath`] pointing to this device, this needs to be appended to
149+
/// the controller's device path.
150+
pub fn path_node(&self) -> crate::Result<PoolDevicePathNode> {
151+
unsafe {
152+
let mut path_ptr: *const DevicePathProtocol = ptr::null();
153+
(self.proto.build_device_path)(self.proto, self.port, self.pmp, &mut path_ptr)
154+
.to_result()?;
155+
NonNull::new(path_ptr.cast_mut())
156+
.map(|p| PoolDevicePathNode(PoolAllocation::new(p.cast())))
157+
.ok_or(Status::OUT_OF_RESOURCES.into())
158+
}
159+
}
160+
161+
/// Executes a command on the device.
162+
///
163+
/// # Parameters
164+
/// - `req`: The request structure containing details about the command to execute.
165+
///
166+
/// # Returns
167+
/// [`AtaResponse`] containing the results of the operation, such as data and status.
168+
///
169+
/// # Errors
170+
/// - [`Status::BAD_BUFFER_SIZE`] The ATA command was not executed because the buffer size exceeded the allowed transfer size.
171+
/// The number of bytes that could be transferred is returned in `InTransferLength` or `OutTransferLength`.
172+
/// - [`Status::NOT_READY`] The ATA command could not be sent because too many commands are already queued. Retry the operation later.
173+
/// - [`Status::DEVICE_ERROR`] A device error occurred while attempting to send the ATA command. Refer to `Asb` for additional status details.
174+
/// - [`Status::INVALID_PARAMETER`] The `Port`, `PortMultiplierPort`, or the contents of `Acb` are invalid.
175+
/// The command was not sent, and no additional status information is available.
176+
/// - [`Status::UNSUPPORTED`] The host adapter does not support the command described by the ATA command.
177+
/// The command was not sent, and no additional status information is available.
178+
/// - [`Status::TIMEOUT`] A timeout occurred while waiting for the ATA command to execute. Refer to `Asb` for additional status details.
179+
pub fn execute_command<'req>(
180+
&mut self,
181+
mut req: AtaRequest<'req>,
182+
) -> crate::Result<AtaResponse<'req>> {
183+
req.packet.acb = &req.acb;
184+
unsafe {
185+
(self.proto.pass_thru)(
186+
self.proto_mut(),
187+
self.port,
188+
self.pmp,
189+
&mut req.packet,
190+
ptr::null_mut(),
191+
)
192+
.to_result_with_val(|| AtaResponse { req })
193+
}
194+
}
195+
}
196+
197+
/// An iterator over the drives connected to an ATA controller.
198+
///
199+
/// The iterator yields [`AtaDevice`] instances, each representing one *potential*
200+
/// drive connected to the ATA controller. You have to probe whether the drive
201+
/// is actually available and connected!
202+
#[derive(Debug)]
203+
pub struct AtaDeviceIterator<'a> {
204+
proto: &'a AtaPassThruProtocol,
205+
// when there are no more devices on this port -> get next port
206+
end_of_port: bool,
207+
prev_port: u16,
208+
prev_pmp: u16,
209+
}
210+
211+
impl<'a> Iterator for AtaDeviceIterator<'a> {
212+
type Item = AtaDevice<'a>;
213+
214+
fn next(&mut self) -> Option<Self::Item> {
215+
loop {
216+
if self.end_of_port {
217+
let result = unsafe { (self.proto.get_next_port)(self.proto, &mut self.prev_port) };
218+
match result {
219+
Status::SUCCESS => self.end_of_port = false,
220+
Status::NOT_FOUND => return None, // no more ports / devices. End of list
221+
_ => panic!("Must not happen according to spec!"),
222+
}
223+
}
224+
// get next device on port
225+
// The UEFI spec states, that:
226+
// If there is no port multiplier detected on the given port, the initial query of get_next_device()
227+
// is allowed to return either of:
228+
// - EFI_SUCCESS & PMP = 0xFFFF
229+
// - EFI_NOT_FOUND
230+
// But even when there is no detected port multiplier, there might be a device directly connected
231+
// to the port! A port where the device is directly connected uses a pmp-value of 0xFFFF.
232+
let was_first = self.prev_pmp == 0xFFFF;
233+
let result = unsafe {
234+
(self.proto.get_next_device)(self.proto, self.prev_port, &mut self.prev_pmp)
235+
};
236+
match result {
237+
Status::SUCCESS => {
238+
if self.prev_pmp == 0xFFFF {
239+
self.end_of_port = true;
240+
}
241+
return Some(AtaDevice {
242+
proto: self.proto,
243+
port: self.prev_port,
244+
pmp: self.prev_pmp,
245+
});
246+
}
247+
Status::NOT_FOUND => {
248+
self.end_of_port = true;
249+
self.prev_pmp = 0xFFFF;
250+
if was_first {
251+
// no port multiplier on port, return valid device anyway.
252+
return Some(AtaDevice {
253+
proto: self.proto,
254+
port: self.prev_port,
255+
pmp: 0xFFFF,
256+
});
257+
}
258+
}
259+
_ => panic!("Must not happen according to spec!"),
260+
}
261+
}
262+
}
263+
}

0 commit comments

Comments
 (0)