- Sponsor
-
Notifications
You must be signed in to change notification settings - Fork 166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add safe protocol wrapper for EFI_ATA_PASS_THRU_PROTOCOL #1595
Open
seijikun
wants to merge
2
commits into
rust-osdev:main
Choose a base branch
from
seijikun:mr-atapt
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+788
−26
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
mod pass_thru; | ||
|
||
pub fn test() { | ||
pass_thru::test(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
use uefi::boot; | ||
use uefi::boot::{OpenProtocolAttributes, OpenProtocolParams}; | ||
use uefi::proto::ata::pass_thru::AtaPassThru; | ||
use uefi::proto::ata::AtaRequestBuilder; | ||
|
||
pub fn test() { | ||
info!("Running ATA PassThru tests"); | ||
|
||
assert!(is_testdrive_present()); | ||
} | ||
|
||
fn is_testdrive_present() -> bool { | ||
let ata_ctrl_handles = boot::find_handles::<AtaPassThru>().unwrap(); | ||
assert_eq!(ata_ctrl_handles.len(), 1); | ||
|
||
for handle in ata_ctrl_handles { | ||
let params = OpenProtocolParams { | ||
handle, | ||
agent: boot::image_handle(), | ||
controller: None, | ||
}; | ||
let ata_pt = unsafe { | ||
// don't open exclusive! That would break other tests | ||
boot::open_protocol::<AtaPassThru>(params, OpenProtocolAttributes::GetProtocol).unwrap() | ||
}; | ||
for mut device in ata_pt.iter_devices() { | ||
// ATA IDENTIFY command | ||
let request = AtaRequestBuilder::read_udma(ata_pt.io_align(), 0xEC) | ||
.unwrap() | ||
.with_timeout(core::time::Duration::from_millis(500)) | ||
.with_read_buffer(255) | ||
.unwrap() | ||
.build(); | ||
if let Ok(result) = device.execute_command(request) { | ||
let bfr = result.read_buffer().unwrap(); | ||
// what the... | ||
seijikun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let mut serial_bfr = [0u8; 20]; | ||
bfr[20..40] | ||
.chunks_exact(2) | ||
.zip(serial_bfr.chunks_exact_mut(2)) | ||
.for_each(|(src, dst)| { | ||
dst[0] = src[1]; | ||
dst[1] = src[0]; | ||
}); | ||
let serial = core::str::from_utf8(&serial_bfr).unwrap().trim(); | ||
if serial == "AtaPassThru" { | ||
info!("Found Testdisk at handle: {:?}", handle); | ||
return true; // found our testdrive! | ||
} | ||
} | ||
} | ||
} | ||
|
||
false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
use alloc::alloc::{alloc, dealloc, Layout, LayoutError}; | ||
use core::error::Error; | ||
use core::fmt; | ||
|
||
/// Helper class to maintain the lifetime of a memory region allocated with a non-standard alignment. | ||
/// Facilitates RAII to properly deallocate when lifetime of the object ends. | ||
/// | ||
/// Note: This uses the global Rust allocator under the hood. | ||
#[allow(clippy::len_without_is_empty)] | ||
#[derive(Debug)] | ||
pub struct AlignedBuffer { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm fine with keeping this struct. However, the Therefore, I see better fit for Are you fine with keeping the AlignedBuffer struct, @nicholasbishop ? |
||
ptr: *mut u8, | ||
layout: Layout, | ||
} | ||
|
||
impl AlignedBuffer { | ||
/// Allocate a new memory region with the requested len and alignment. | ||
pub fn alloc(len: usize, alignment: usize) -> Result<Self, LayoutError> { | ||
let layout = Layout::from_size_align(len, alignment)?; | ||
let ptr = unsafe { alloc(layout) }; | ||
Ok(Self { ptr, layout }) | ||
} | ||
|
||
/// Get a pointer to the aligned memory region managed by this instance. | ||
#[must_use] | ||
pub const fn ptr(&self) -> *const u8 { | ||
self.ptr.cast_const() | ||
} | ||
|
||
/// Get a mutable pointer to the aligned memory region managed by this instance. | ||
#[must_use] | ||
pub fn ptr_mut(&mut self) -> *mut u8 { | ||
self.ptr | ||
} | ||
|
||
/// Get the size of the aligned memory region managed by this instance. | ||
#[must_use] | ||
pub const fn len(&self) -> usize { | ||
self.layout.size() | ||
} | ||
|
||
/// Fill the aligned memory region with data from the given buffer. | ||
pub fn copy_from(&mut self, data: &[u8]) { | ||
let len = data.len().min(self.len()); | ||
unsafe { | ||
self.ptr.copy_from(data.as_ptr(), len); | ||
} | ||
} | ||
|
||
/// Check the buffer's alignment against the `required_alignment`. | ||
pub fn check_alignment(&self, required_alignment: usize) -> Result<(), AlignmentError> { | ||
//TODO: use bfr.addr() when it's available | ||
if (self.ptr as usize) % required_alignment != 0 { | ||
return Err(AlignmentError); //TODO: use >is_aligned_to< when it's available | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl Drop for AlignedBuffer { | ||
fn drop(&mut self) { | ||
unsafe { | ||
dealloc(self.ptr, self.layout); | ||
} | ||
} | ||
} | ||
|
||
/// The `AlignmentError` is returned if a user-provided buffer doesn't fulfill alignment requirements. | ||
#[derive(Clone, PartialEq, Eq, Debug)] | ||
pub struct AlignmentError; | ||
impl Error for AlignmentError {} | ||
impl fmt::Display for AlignmentError { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.write_str("invalid parameters to Layout::from_size_align") | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::AlignedBuffer; | ||
|
||
#[test] | ||
fn test_invalid_arguments() { | ||
// invalid alignments, valid len | ||
for request_alignment in [0, 3, 5, 7, 9] { | ||
for request_len in [1, 32, 64, 128, 1024] { | ||
assert!(AlignedBuffer::alloc(request_len, request_alignment).is_err()); | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_allocation_alignment() { | ||
for request_alignment in [1, 2, 4, 8, 16, 32, 64, 128] { | ||
for request_len in [1 as usize, 32, 64, 128, 1024] { | ||
let buffer = AlignedBuffer::alloc(request_len, request_alignment).unwrap(); | ||
assert_eq!(buffer.ptr() as usize % request_alignment, 0); | ||
assert_eq!(buffer.len(), request_len); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,322 @@ | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
//! ATA Protocols. | ||
use crate::helpers::{AlignedBuffer, AlignmentError}; | ||
use core::alloc::LayoutError; | ||
use core::marker::PhantomData; | ||
use core::ptr; | ||
use core::time::Duration; | ||
use uefi_raw::protocol::ata::{ | ||
AtaCommandBlock, AtaPassThruCommandPacket, AtaPassThruLength, AtaStatusBlock, | ||
}; | ||
|
||
pub mod pass_thru; | ||
|
||
/// Represents the protocol for ATA Pass Thru command handling. | ||
/// | ||
/// This type defines the protocols supported for passing commands through the ATA controller. | ||
pub use uefi_raw::protocol::ata::AtaPassThruCommandProtocol; | ||
|
||
/// Represents an ATA request built for execution on an ATA controller. | ||
#[derive(Debug)] | ||
pub struct AtaRequest<'a> { | ||
io_align: u32, | ||
acb: AtaCommandBlock, | ||
packet: AtaPassThruCommandPacket, | ||
in_data_buffer: Option<AlignedBuffer>, | ||
out_data_buffer: Option<AlignedBuffer>, | ||
asb: AlignedBuffer, | ||
_phantom: PhantomData<&'a u8>, | ||
} | ||
|
||
/// Builder for creating and configuring an [`AtaRequest`]. | ||
/// | ||
/// This builder simplifies the creation of an [`AtaRequest`] by providing chainable methods for | ||
/// configuring fields like timeout, buffers, and ATA command details. | ||
#[derive(Debug)] | ||
pub struct AtaRequestBuilder<'a> { | ||
req: AtaRequest<'a>, | ||
} | ||
|
||
impl<'a> AtaRequestBuilder<'a> { | ||
/// Creates a new [`AtaRequestBuilder`] with the specified alignment, command, and protocol. | ||
/// | ||
/// # Parameters | ||
/// - `io_align`: The I/O buffer alignment required for the ATA controller. | ||
/// - `command`: The ATA command byte specifying the operation to execute. | ||
/// - `protocol`: The protocol type for the command (e.g., DMA, UDMA, etc.). | ||
/// | ||
/// # Returns | ||
/// `Result<Self, LayoutError>` indicating success or memory allocation failure. | ||
/// | ||
/// # Errors | ||
/// This method can fail due to alignment or memory allocation issues. | ||
fn new( | ||
io_align: u32, | ||
command: u8, | ||
protocol: AtaPassThruCommandProtocol, | ||
) -> Result<Self, LayoutError> { | ||
// status block has alignment requirements! | ||
let mut asb = AlignedBuffer::alloc(size_of::<AtaStatusBlock>(), io_align as usize)?; | ||
Ok(Self { | ||
req: AtaRequest { | ||
io_align, | ||
acb: AtaCommandBlock { | ||
command, | ||
..Default::default() | ||
}, | ||
packet: AtaPassThruCommandPacket { | ||
asb: asb.ptr_mut().cast(), | ||
acb: ptr::null(), // filled during execution | ||
timeout: 0, | ||
in_data_buffer: ptr::null_mut(), | ||
out_data_buffer: ptr::null(), | ||
in_transfer_length: 0, | ||
out_transfer_length: 0, | ||
protocol, | ||
length: AtaPassThruLength::BYTES, | ||
}, | ||
in_data_buffer: None, | ||
out_data_buffer: None, | ||
asb, | ||
_phantom: PhantomData, | ||
}, | ||
}) | ||
} | ||
|
||
/// Creates a builder for a UDMA read operation. | ||
/// | ||
/// # Parameters | ||
/// - `io_align`: The I/O buffer alignment required for the ATA controller. | ||
/// - `command`: The ATA command byte specifying the read operation. | ||
/// | ||
/// # Returns | ||
/// `Result<Self, LayoutError>` indicating success or memory allocation failure. | ||
/// | ||
/// # Errors | ||
/// This method can fail due to alignment or memory allocation issues. | ||
pub fn read_udma(io_align: u32, command: u8) -> Result<Self, LayoutError> { | ||
Self::new(io_align, command, AtaPassThruCommandProtocol::UDMA_DATA_IN) | ||
} | ||
|
||
/// Creates a builder for a UDMA write operation. | ||
/// | ||
/// # Parameters | ||
/// - `io_align`: The I/O buffer alignment required for the ATA controller. | ||
/// - `command`: The ATA command byte specifying the write operation. | ||
/// | ||
/// # Returns | ||
/// `Result<Self, LayoutError>` indicating success or memory allocation failure. | ||
/// | ||
/// # Errors | ||
/// This method can fail due to alignment or memory allocation issues. | ||
pub fn write_udma(io_align: u32, command: u8) -> Result<Self, LayoutError> { | ||
Self::new(io_align, command, AtaPassThruCommandProtocol::UDMA_DATA_OUT) | ||
} | ||
|
||
// ######################################################################## | ||
|
||
/// Configure the given timeout for this request. | ||
#[must_use] | ||
pub const fn with_timeout(mut self, timeout: Duration) -> Self { | ||
self.req.packet.timeout = (timeout.as_nanos() / 100) as u64; | ||
self | ||
} | ||
|
||
/// Configure the `features` field. | ||
#[must_use] | ||
pub const fn with_features(mut self, features: u8) -> Self { | ||
self.req.acb.features = features; | ||
self | ||
} | ||
|
||
/// Configure the `sector_number` field. | ||
#[must_use] | ||
pub const fn with_sector_number(mut self, sector_number: u8) -> Self { | ||
self.req.acb.sector_number = sector_number; | ||
self | ||
} | ||
|
||
/// Configure the `cylinder` fields (low and high combined). | ||
#[must_use] | ||
pub const fn with_cylinder(mut self, low: u8, high: u8) -> Self { | ||
self.req.acb.cylinder_low = low; | ||
self.req.acb.cylinder_high = high; | ||
self | ||
} | ||
|
||
/// Configure the `device_head` field. | ||
#[must_use] | ||
pub const fn with_device_head(mut self, device_head: u8) -> Self { | ||
self.req.acb.device_head = device_head; | ||
self | ||
} | ||
|
||
/// Configure the `sector_number_exp` field. | ||
#[must_use] | ||
pub const fn with_sector_number_exp(mut self, sector_number_exp: u8) -> Self { | ||
self.req.acb.sector_number_exp = sector_number_exp; | ||
self | ||
} | ||
|
||
/// Configure the `cylinder_exp` fields (low and high combined). | ||
#[must_use] | ||
pub const fn with_cylinder_exp(mut self, low_exp: u8, high_exp: u8) -> Self { | ||
self.req.acb.cylinder_low_exp = low_exp; | ||
self.req.acb.cylinder_high_exp = high_exp; | ||
self | ||
} | ||
|
||
/// Configure the `features_exp` field. | ||
#[must_use] | ||
pub const fn with_features_exp(mut self, features_exp: u8) -> Self { | ||
self.req.acb.features_exp = features_exp; | ||
self | ||
} | ||
|
||
/// Configure the `sector_count` field. | ||
#[must_use] | ||
pub const fn with_sector_count(mut self, sector_count: u8) -> Self { | ||
self.req.acb.sector_count = sector_count; | ||
self | ||
} | ||
|
||
/// Configure the `sector_count_exp` field. | ||
#[must_use] | ||
pub const fn with_sector_count_exp(mut self, sector_count_exp: u8) -> Self { | ||
self.req.acb.sector_count_exp = sector_count_exp; | ||
self | ||
} | ||
|
||
// # READ BUFFER | ||
// ######################################################################################## | ||
|
||
/// Uses a user-supplied buffer for reading data from the device. | ||
/// | ||
/// # Parameters | ||
/// - `bfr`: A mutable reference to an [`AlignedBuffer`] that will be used to store data read from the device. | ||
/// | ||
/// # Returns | ||
/// `Result<Self, AlignmentError>` indicating success or an alignment issue with the provided buffer. | ||
/// | ||
/// # Description | ||
/// This method checks the alignment of the buffer against the protocol's requirements and assigns it to | ||
/// the `in_data_buffer` of the underlying [`AtaRequest`]. | ||
pub fn use_read_buffer(mut self, bfr: &'a mut AlignedBuffer) -> Result<Self, AlignmentError> { | ||
// check alignment of externally supplied buffer | ||
bfr.check_alignment(self.req.io_align as usize)?; | ||
self.req.in_data_buffer = None; | ||
self.req.packet.in_data_buffer = bfr.ptr_mut().cast(); | ||
self.req.packet.in_transfer_length = bfr.len() as u32; | ||
Ok(self) | ||
} | ||
|
||
/// Adds a newly allocated read buffer to the built ATA request. | ||
/// | ||
/// # Parameters | ||
/// - `len`: The size of the buffer (in bytes) to allocate for receiving data. | ||
/// | ||
/// # Returns | ||
/// `Result<Self, LayoutError>` indicating success or a memory allocation error. | ||
pub fn with_read_buffer(mut self, len: usize) -> Result<Self, LayoutError> { | ||
let mut bfr = AlignedBuffer::alloc(len, self.req.io_align as usize)?; | ||
self.req.packet.in_data_buffer = bfr.ptr_mut().cast(); | ||
self.req.packet.in_transfer_length = bfr.len() as u32; | ||
self.req.in_data_buffer = Some(bfr); | ||
Ok(self) | ||
} | ||
|
||
// # WRITE BUFFER | ||
// ######################################################################################## | ||
|
||
/// Uses a user-supplied buffer for writing data to the device. | ||
/// | ||
/// # Parameters | ||
/// - `bfr`: A mutable reference to an [`AlignedBuffer`] containing the data to be written to the device. | ||
/// | ||
/// # Returns | ||
/// `Result<Self, AlignmentError>` indicating success or an alignment issue with the provided buffer. | ||
/// | ||
/// # Description | ||
/// This method checks the alignment of the buffer against the protocol's requirements and assigns it to | ||
/// the `out_data_buffer` of the underlying [`AtaRequest`]. | ||
pub fn use_write_buffer(mut self, bfr: &'a mut AlignedBuffer) -> Result<Self, AlignmentError> { | ||
// check alignment of externally supplied buffer | ||
bfr.check_alignment(self.req.io_align as usize)?; | ||
self.req.out_data_buffer = None; | ||
self.req.packet.out_data_buffer = bfr.ptr_mut().cast(); | ||
self.req.packet.out_transfer_length = bfr.len() as u32; | ||
Ok(self) | ||
} | ||
|
||
/// Adds a newly allocated write buffer to the built ATA request that is filled from the | ||
/// given data buffer. (Done for memory alignment and lifetime purposes) | ||
/// | ||
/// # Parameters | ||
/// - `data`: A slice of bytes representing the data to be written. | ||
/// | ||
/// # Returns | ||
/// `Result<Self, LayoutError>` indicating success or a memory allocation error. | ||
pub fn with_write_data(mut self, data: &[u8]) -> Result<Self, LayoutError> { | ||
let mut bfr = AlignedBuffer::alloc(data.len(), self.req.io_align as usize)?; | ||
bfr.copy_from(data); | ||
self.req.packet.out_data_buffer = bfr.ptr_mut().cast(); | ||
self.req.packet.out_transfer_length = bfr.len() as u32; | ||
self.req.out_data_buffer = Some(bfr); | ||
Ok(self) | ||
} | ||
|
||
/// Build the final [`AtaRequest`]. | ||
/// | ||
/// # Returns | ||
/// A fully-configured [`AtaRequest`] ready for execution. | ||
#[must_use] | ||
pub fn build(self) -> AtaRequest<'a> { | ||
self.req | ||
} | ||
} | ||
|
||
/// Represents a response from an ATA request. | ||
/// | ||
/// This structure provides access to the status block, read buffer, and other | ||
/// details returned by the ATA controller after executing a request. | ||
#[derive(Debug)] | ||
pub struct AtaResponse<'a> { | ||
req: AtaRequest<'a>, | ||
} | ||
|
||
impl<'a> AtaResponse<'a> { | ||
/// Retrieves the status block from the response. | ||
/// | ||
/// # Returns | ||
/// A reference to the [`AtaStatusBlock`] containing details about the status of the executed operation. | ||
#[must_use] | ||
pub fn status(&self) -> &'a AtaStatusBlock { | ||
unsafe { | ||
self.req | ||
.asb | ||
.ptr() | ||
.cast::<AtaStatusBlock>() | ||
.as_ref() | ||
.unwrap() | ||
} | ||
} | ||
|
||
/// Retrieves the buffer containing data read from the device (if available). | ||
/// | ||
/// # Returns | ||
/// `Option<&[u8]>`: A slice of the data read from the device, or `None` if no read buffer was used. | ||
#[must_use] | ||
pub fn read_buffer(&self) -> Option<&'a [u8]> { | ||
if self.req.packet.in_data_buffer.is_null() { | ||
return None; | ||
} | ||
unsafe { | ||
Some(core::slice::from_raw_parts( | ||
self.req.packet.in_data_buffer.cast(), | ||
self.req.packet.in_transfer_length as usize, | ||
)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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!"), | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but why? They do not run concurrently but sequentially
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea. Maybe it unloads a driver that's required for later tests when I open it exclusively.
Using
open_protocol_exclusive
here results in: