Skip to content

Commit afa35f7

Browse files
committed
uefi: Add ATA request builder infrastructure
1 parent cf4f328 commit afa35f7

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed

uefi/src/proto/ata/mod.rs

+321
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
//! ATA Protocols.
4+
5+
use crate::mem::{AlignedBuffer, AlignmentError};
6+
use core::alloc::LayoutError;
7+
use core::marker::PhantomData;
8+
use core::ptr;
9+
use core::time::Duration;
10+
use uefi_raw::protocol::ata::{
11+
AtaCommandBlock, AtaPassThruCommandPacket, AtaPassThruLength, AtaStatusBlock,
12+
};
13+
14+
/// Represents the protocol for ATA Pass Thru command handling.
15+
///
16+
/// This type defines the protocols supported for passing commands through the ATA controller.
17+
pub use uefi_raw::protocol::ata::AtaPassThruCommandProtocol;
18+
19+
/// Represents an ATA request built for execution on an ATA controller.
20+
#[derive(Debug)]
21+
pub struct AtaRequest<'a> {
22+
io_align: u32,
23+
acb: AtaCommandBlock,
24+
packet: AtaPassThruCommandPacket,
25+
in_data_buffer: Option<AlignedBuffer>,
26+
out_data_buffer: Option<AlignedBuffer>,
27+
asb: AlignedBuffer,
28+
_phantom: PhantomData<&'a u8>,
29+
}
30+
31+
/// Builder for creating and configuring an [`AtaRequest`].
32+
///
33+
/// This builder simplifies the creation of an [`AtaRequest`] by providing chainable methods for
34+
/// configuring fields like timeout, buffers, and ATA command details.
35+
#[derive(Debug)]
36+
pub struct AtaRequestBuilder<'a> {
37+
req: AtaRequest<'a>,
38+
}
39+
40+
impl<'a> AtaRequestBuilder<'a> {
41+
/// Creates a new [`AtaRequestBuilder`] with the specified alignment, command, and protocol.
42+
///
43+
/// # Parameters
44+
/// - `io_align`: The I/O buffer alignment required for the ATA controller.
45+
/// - `command`: The ATA command byte specifying the operation to execute.
46+
/// - `protocol`: The protocol type for the command (e.g., DMA, UDMA, etc.).
47+
///
48+
/// # Returns
49+
/// `Result<Self, LayoutError>` indicating success or memory allocation failure.
50+
///
51+
/// # Errors
52+
/// This method can fail due to alignment or memory allocation issues.
53+
fn new(
54+
io_align: u32,
55+
command: u8,
56+
protocol: AtaPassThruCommandProtocol,
57+
) -> Result<Self, LayoutError> {
58+
// status block has alignment requirements!
59+
let mut asb =
60+
AlignedBuffer::from_size_align(size_of::<AtaStatusBlock>(), io_align as usize)?;
61+
Ok(Self {
62+
req: AtaRequest {
63+
io_align,
64+
acb: AtaCommandBlock {
65+
command,
66+
..Default::default()
67+
},
68+
packet: AtaPassThruCommandPacket {
69+
asb: asb.ptr_mut().cast(),
70+
acb: ptr::null(), // filled during execution
71+
timeout: 0,
72+
in_data_buffer: ptr::null_mut(),
73+
out_data_buffer: ptr::null(),
74+
in_transfer_length: 0,
75+
out_transfer_length: 0,
76+
protocol,
77+
length: AtaPassThruLength::BYTES,
78+
},
79+
in_data_buffer: None,
80+
out_data_buffer: None,
81+
asb,
82+
_phantom: PhantomData,
83+
},
84+
})
85+
}
86+
87+
/// Creates a builder for a UDMA read operation.
88+
///
89+
/// # Parameters
90+
/// - `io_align`: The I/O buffer alignment required for the ATA controller.
91+
/// - `command`: The ATA command byte specifying the read operation.
92+
///
93+
/// # Returns
94+
/// `Result<Self, LayoutError>` indicating success or memory allocation failure.
95+
///
96+
/// # Errors
97+
/// This method can fail due to alignment or memory allocation issues.
98+
pub fn read_udma(io_align: u32, command: u8) -> Result<Self, LayoutError> {
99+
Self::new(io_align, command, AtaPassThruCommandProtocol::UDMA_DATA_IN)
100+
}
101+
102+
/// Creates a builder for a UDMA write operation.
103+
///
104+
/// # Parameters
105+
/// - `io_align`: The I/O buffer alignment required for the ATA controller.
106+
/// - `command`: The ATA command byte specifying the write operation.
107+
///
108+
/// # Returns
109+
/// `Result<Self, LayoutError>` indicating success or memory allocation failure.
110+
///
111+
/// # Errors
112+
/// This method can fail due to alignment or memory allocation issues.
113+
pub fn write_udma(io_align: u32, command: u8) -> Result<Self, LayoutError> {
114+
Self::new(io_align, command, AtaPassThruCommandProtocol::UDMA_DATA_OUT)
115+
}
116+
117+
// ########################################################################
118+
119+
/// Configure the given timeout for this request.
120+
#[must_use]
121+
pub const fn with_timeout(mut self, timeout: Duration) -> Self {
122+
self.req.packet.timeout = (timeout.as_nanos() / 100) as u64;
123+
self
124+
}
125+
126+
/// Configure the `features` field.
127+
#[must_use]
128+
pub const fn with_features(mut self, features: u8) -> Self {
129+
self.req.acb.features = features;
130+
self
131+
}
132+
133+
/// Configure the `sector_number` field.
134+
#[must_use]
135+
pub const fn with_sector_number(mut self, sector_number: u8) -> Self {
136+
self.req.acb.sector_number = sector_number;
137+
self
138+
}
139+
140+
/// Configure the `cylinder` fields (low and high combined).
141+
#[must_use]
142+
pub const fn with_cylinder(mut self, low: u8, high: u8) -> Self {
143+
self.req.acb.cylinder_low = low;
144+
self.req.acb.cylinder_high = high;
145+
self
146+
}
147+
148+
/// Configure the `device_head` field.
149+
#[must_use]
150+
pub const fn with_device_head(mut self, device_head: u8) -> Self {
151+
self.req.acb.device_head = device_head;
152+
self
153+
}
154+
155+
/// Configure the `sector_number_exp` field.
156+
#[must_use]
157+
pub const fn with_sector_number_exp(mut self, sector_number_exp: u8) -> Self {
158+
self.req.acb.sector_number_exp = sector_number_exp;
159+
self
160+
}
161+
162+
/// Configure the `cylinder_exp` fields (low and high combined).
163+
#[must_use]
164+
pub const fn with_cylinder_exp(mut self, low_exp: u8, high_exp: u8) -> Self {
165+
self.req.acb.cylinder_low_exp = low_exp;
166+
self.req.acb.cylinder_high_exp = high_exp;
167+
self
168+
}
169+
170+
/// Configure the `features_exp` field.
171+
#[must_use]
172+
pub const fn with_features_exp(mut self, features_exp: u8) -> Self {
173+
self.req.acb.features_exp = features_exp;
174+
self
175+
}
176+
177+
/// Configure the `sector_count` field.
178+
#[must_use]
179+
pub const fn with_sector_count(mut self, sector_count: u8) -> Self {
180+
self.req.acb.sector_count = sector_count;
181+
self
182+
}
183+
184+
/// Configure the `sector_count_exp` field.
185+
#[must_use]
186+
pub const fn with_sector_count_exp(mut self, sector_count_exp: u8) -> Self {
187+
self.req.acb.sector_count_exp = sector_count_exp;
188+
self
189+
}
190+
191+
// # READ BUFFER
192+
// ########################################################################################
193+
194+
/// Uses a user-supplied buffer for reading data from the device.
195+
///
196+
/// # Parameters
197+
/// - `bfr`: A mutable reference to an [`AlignedBuffer`] that will be used to store data read from the device.
198+
///
199+
/// # Returns
200+
/// `Result<Self, AlignmentError>` indicating success or an alignment issue with the provided buffer.
201+
///
202+
/// # Description
203+
/// This method checks the alignment of the buffer against the protocol's requirements and assigns it to
204+
/// the `in_data_buffer` of the underlying [`AtaRequest`].
205+
pub fn use_read_buffer(mut self, bfr: &'a mut AlignedBuffer) -> Result<Self, AlignmentError> {
206+
// check alignment of externally supplied buffer
207+
bfr.check_alignment(self.req.io_align as usize)?;
208+
self.req.in_data_buffer = None;
209+
self.req.packet.in_data_buffer = bfr.ptr_mut().cast();
210+
self.req.packet.in_transfer_length = bfr.size() as u32;
211+
Ok(self)
212+
}
213+
214+
/// Adds a newly allocated read buffer to the built ATA request.
215+
///
216+
/// # Parameters
217+
/// - `len`: The size of the buffer (in bytes) to allocate for receiving data.
218+
///
219+
/// # Returns
220+
/// `Result<Self, LayoutError>` indicating success or a memory allocation error.
221+
pub fn with_read_buffer(mut self, len: usize) -> Result<Self, LayoutError> {
222+
let mut bfr = AlignedBuffer::from_size_align(len, self.req.io_align as usize)?;
223+
self.req.packet.in_data_buffer = bfr.ptr_mut().cast();
224+
self.req.packet.in_transfer_length = bfr.size() as u32;
225+
self.req.in_data_buffer = Some(bfr);
226+
Ok(self)
227+
}
228+
229+
// # WRITE BUFFER
230+
// ########################################################################################
231+
232+
/// Uses a user-supplied buffer for writing data to the device.
233+
///
234+
/// # Parameters
235+
/// - `bfr`: A mutable reference to an [`AlignedBuffer`] containing the data to be written to the device.
236+
///
237+
/// # Returns
238+
/// `Result<Self, AlignmentError>` indicating success or an alignment issue with the provided buffer.
239+
///
240+
/// # Description
241+
/// This method checks the alignment of the buffer against the protocol's requirements and assigns it to
242+
/// the `out_data_buffer` of the underlying [`AtaRequest`].
243+
pub fn use_write_buffer(mut self, bfr: &'a mut AlignedBuffer) -> Result<Self, AlignmentError> {
244+
// check alignment of externally supplied buffer
245+
bfr.check_alignment(self.req.io_align as usize)?;
246+
self.req.out_data_buffer = None;
247+
self.req.packet.out_data_buffer = bfr.ptr_mut().cast();
248+
self.req.packet.out_transfer_length = bfr.size() as u32;
249+
Ok(self)
250+
}
251+
252+
/// Adds a newly allocated write buffer to the built ATA request that is filled from the
253+
/// given data buffer. (Done for memory alignment and lifetime purposes)
254+
///
255+
/// # Parameters
256+
/// - `data`: A slice of bytes representing the data to be written.
257+
///
258+
/// # Returns
259+
/// `Result<Self, LayoutError>` indicating success or a memory allocation error.
260+
pub fn with_write_data(mut self, data: &[u8]) -> Result<Self, LayoutError> {
261+
let mut bfr = AlignedBuffer::from_size_align(data.len(), self.req.io_align as usize)?;
262+
bfr.copy_from_slice(data);
263+
self.req.packet.out_data_buffer = bfr.ptr_mut().cast();
264+
self.req.packet.out_transfer_length = bfr.size() as u32;
265+
self.req.out_data_buffer = Some(bfr);
266+
Ok(self)
267+
}
268+
269+
/// Build the final [`AtaRequest`].
270+
///
271+
/// # Returns
272+
/// A fully-configured [`AtaRequest`] ready for execution.
273+
#[must_use]
274+
pub fn build(self) -> AtaRequest<'a> {
275+
self.req
276+
}
277+
}
278+
279+
/// Represents a response from an ATA request.
280+
///
281+
/// This structure provides access to the status block, read buffer, and other
282+
/// details returned by the ATA controller after executing a request.
283+
#[derive(Debug)]
284+
pub struct AtaResponse<'a> {
285+
req: AtaRequest<'a>,
286+
}
287+
288+
impl<'a> AtaResponse<'a> {
289+
/// Retrieves the status block from the response.
290+
///
291+
/// # Returns
292+
/// A reference to the [`AtaStatusBlock`] containing details about the status of the executed operation.
293+
#[must_use]
294+
pub fn status(&self) -> &'a AtaStatusBlock {
295+
unsafe {
296+
self.req
297+
.asb
298+
.ptr()
299+
.cast::<AtaStatusBlock>()
300+
.as_ref()
301+
.unwrap()
302+
}
303+
}
304+
305+
/// Retrieves the buffer containing data read from the device (if available).
306+
///
307+
/// # Returns
308+
/// `Option<&[u8]>`: A slice of the data read from the device, or `None` if no read buffer was used.
309+
#[must_use]
310+
pub fn read_buffer(&self) -> Option<&'a [u8]> {
311+
if self.req.packet.in_data_buffer.is_null() {
312+
return None;
313+
}
314+
unsafe {
315+
Some(core::slice::from_raw_parts(
316+
self.req.packet.in_data_buffer.cast(),
317+
self.req.packet.in_transfer_length as usize,
318+
))
319+
}
320+
}
321+
}

uefi/src/proto/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//!
1111
//! [`boot`]: crate::boot#accessing-protocols
1212
13+
#[cfg(feature = "alloc")]
14+
pub mod ata;
1315
pub mod console;
1416
pub mod debug;
1517
pub mod device_path;

0 commit comments

Comments
 (0)