diff --git a/uefi-raw/src/protocol/ata.rs b/uefi-raw/src/protocol/ata.rs index c982f4d66..b49b86e37 100644 --- a/uefi-raw/src/protocol/ata.rs +++ b/uefi-raw/src/protocol/ata.rs @@ -31,7 +31,7 @@ bitflags::bitflags! { } } -#[derive(Debug)] +#[derive(Clone, Debug)] #[repr(C)] pub struct AtaPassThruMode { pub attributes: AtaPassThruAttributes, @@ -76,37 +76,37 @@ newtype_enum! { #[repr(C)] pub struct AtaStatusBlock { pub reserved1: [u8; 2], - pub ata_status: u8, - pub ata_error: u8, - pub ata_sector_number: u8, - pub ata_cylinder_low: u8, - pub ata_cylinder_high: u8, - pub ata_device_head: u8, - pub ata_sector_number_exp: u8, - pub ata_cylinder_low_exp: u8, - pub ata_cylinder_high_exp: u8, + pub status: u8, + pub error: u8, + pub sector_number: u8, + pub cylinder_low: u8, + pub cylinder_high: u8, + pub device_head: u8, + pub sector_number_exp: u8, + pub cylinder_low_exp: u8, + pub cylinder_high_exp: u8, pub reserved2: u8, - pub ata_sector_count: u8, - pub ata_sector_count_exp: u8, + pub sector_count: u8, + pub sector_count_exp: u8, pub reserved3: [u8; 6], } -#[derive(Debug)] +#[derive(Debug, Default)] #[repr(C)] pub struct AtaCommandBlock { pub reserved1: [u8; 2], - pub ata_command: u8, - pub ata_features: u8, - pub ata_sector_number: u8, - pub ata_cylinder_low: u8, - pub ata_cylinder_high: u8, - pub ata_device_head: u8, - pub ata_sector_number_exp: u8, - pub ata_cylinder_low_exp: u8, - pub ata_cylinder_high_exp: u8, - pub ata_features_exp: u8, - pub ata_sector_count: u8, - pub ata_sector_count_exp: u8, + pub command: u8, + pub features: u8, + pub sector_number: u8, + pub cylinder_low: u8, + pub cylinder_high: u8, + pub device_head: u8, + pub sector_number_exp: u8, + pub cylinder_low_exp: u8, + pub cylinder_high_exp: u8, + pub features_exp: u8, + pub sector_count: u8, + pub sector_count_exp: u8, pub reserved2: [u8; 6], } @@ -118,6 +118,8 @@ pub struct AtaPassThruCommandPacket { pub timeout: u64, pub in_data_buffer: *mut c_void, pub out_data_buffer: *const c_void, + pub in_transfer_length: u32, + pub out_transfer_length: u32, pub protocol: AtaPassThruCommandProtocol, pub length: AtaPassThruLength, } @@ -131,7 +133,7 @@ pub struct AtaPassThruProtocol { port: u16, port_multiplier_port: u16, packet: *mut AtaPassThruCommandPacket, - event: *mut Event, + event: Event, ) -> Status, pub get_next_port: unsafe extern "efiapi" fn(this: *const Self, port: *mut u16) -> Status, pub get_next_device: unsafe extern "efiapi" fn( @@ -143,7 +145,7 @@ pub struct AtaPassThruProtocol { this: *const Self, port: u16, port_multiplier_port: u16, - device_path: *mut *mut DevicePathProtocol, + device_path: *mut *const DevicePathProtocol, ) -> Status, pub get_device: unsafe extern "efiapi" fn( this: *const Self, diff --git a/uefi-test-runner/src/proto/ata/mod.rs b/uefi-test-runner/src/proto/ata/mod.rs new file mode 100644 index 000000000..1c28fa962 --- /dev/null +++ b/uefi-test-runner/src/proto/ata/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +mod pass_thru; + +pub fn test() { + pass_thru::test(); +} diff --git a/uefi-test-runner/src/proto/ata/pass_thru.rs b/uefi-test-runner/src/proto/ata/pass_thru.rs new file mode 100644 index 000000000..12d82c1c5 --- /dev/null +++ b/uefi-test-runner/src/proto/ata/pass_thru.rs @@ -0,0 +1,59 @@ +// 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()); +} + +const ATACMD_IDENTIFY: u8 = 0xEC; + +fn is_testdrive_present() -> bool { + let ata_ctrl_handles = boot::find_handles::().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::(params, OpenProtocolAttributes::GetProtocol).unwrap() + }; + for mut device in ata_pt.iter_devices() { + // ATA IDENTIFY command + let request = AtaRequestBuilder::read_udma(ata_pt.io_align(), ATACMD_IDENTIFY) + .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... + 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 +} diff --git a/uefi-test-runner/src/proto/media.rs b/uefi-test-runner/src/proto/media.rs index 49b00d4e3..f07203d03 100644 --- a/uefi-test-runner/src/proto/media.rs +++ b/uefi-test-runner/src/proto/media.rs @@ -398,7 +398,11 @@ fn test_partition_info(disk_handle: Handle) { fn find_test_disk() -> (Handle, ScopedProtocol) { let handles = boot::find_handles::() .expect("Failed to get handles for `SimpleFileSystem` protocol"); + + #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] assert_eq!(handles.len(), 2); + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + assert_eq!(handles.len(), 3); for handle in handles { let mut sfs = boot::open_protocol_exclusive::(handle) diff --git a/uefi-test-runner/src/proto/mod.rs b/uefi-test-runner/src/proto/mod.rs index e39b525fd..3b16b8982 100644 --- a/uefi-test-runner/src/proto/mod.rs +++ b/uefi-test-runner/src/proto/mod.rs @@ -13,6 +13,8 @@ pub fn test() { test_protocols_per_handle(); test_test_protocol(); + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + ata::test(); debug::test(); device_path::test(); driver::test(); @@ -62,6 +64,8 @@ fn test_test_protocol() { .unwrap()); } +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod ata; mod console; mod debug; mod device_path; diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 943acc636..3e5457ba4 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -8,6 +8,7 @@ - Added `mem::AlignedBuffer`. - Added `proto::device_path::DevicePath::append_path()`. - Added `proto::device_path::DevicePath::append_node()`. +- Added `proto::ata::pass_thru::AtaPassThru`. ## Changed - **Breaking:** Removed `BootPolicyError` as `BootPolicy` construction is no diff --git a/uefi/src/proto/ata/mod.rs b/uefi/src/proto/ata/mod.rs new file mode 100644 index 000000000..e5511c5d7 --- /dev/null +++ b/uefi/src/proto/ata/mod.rs @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! ATA Protocols. + +use crate::mem::{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, + out_data_buffer: Option, + 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` 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 { + // status block has alignment requirements! + let mut asb = + AlignedBuffer::from_size_align(size_of::(), 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` 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::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` 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::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` 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 { + // 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.size() 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` indicating success or a memory allocation error. + pub fn with_read_buffer(mut self, len: usize) -> Result { + let mut bfr = AlignedBuffer::from_size_align(len, self.req.io_align as usize)?; + self.req.packet.in_data_buffer = bfr.ptr_mut().cast(); + self.req.packet.in_transfer_length = bfr.size() 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` 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 { + // 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.size() 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` indicating success or a memory allocation error. + pub fn with_write_data(mut self, data: &[u8]) -> Result { + let mut bfr = AlignedBuffer::from_size_align(data.len(), self.req.io_align as usize)?; + bfr.copy_from_slice(data); + self.req.packet.out_data_buffer = bfr.ptr_mut().cast(); + self.req.packet.out_transfer_length = bfr.size() 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::() + .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, + )) + } + } +} diff --git a/uefi/src/proto/ata/pass_thru.rs b/uefi/src/proto/ata/pass_thru.rs new file mode 100644 index 000000000..24af90be0 --- /dev/null +++ b/uefi/src/proto/ata/pass_thru.rs @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! ATA Pass Thru Protocol. + +use super::{AtaRequest, AtaResponse}; +use crate::mem::{AlignedBuffer, PoolAllocation}; +use crate::proto::device_path::PoolDevicePathNode; +use crate::StatusExt; +use core::alloc::LayoutError; +use core::ptr::{self, NonNull}; +use uefi_macros::unsafe_protocol; +use uefi_raw::protocol::ata::AtaPassThruProtocol; +use uefi_raw::protocol::device_path::DevicePathProtocol; +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. +/// +/// This API offers a safe and convenient, yet still low-level interface to ATA devices. +/// It is designed as a foundational layer, leaving higher-level abstractions responsible for implementing +/// richer storage semantics, device-specific commands, and advanced use cases. +/// +/// # 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::from_size_align(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() } + } + + /// Get the final device path node for this device. + /// + /// For a full [`crate::proto::device_path::DevicePath`] pointing to this device, this needs to be appended to + /// the controller's device path. + pub fn path_node(&self) -> crate::Result { + unsafe { + let mut path_ptr: *const DevicePathProtocol = ptr::null(); + (self.proto.build_device_path)(self.proto, self.port, self.pmp, &mut path_ptr) + .to_result()?; + NonNull::new(path_ptr.cast_mut()) + .map(|p| PoolDevicePathNode(PoolAllocation::new(p.cast()))) + .ok_or(Status::OUT_OF_RESOURCES.into()) + } + } + + /// 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> { + 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 { + 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!"), + } + } + } +} diff --git a/uefi/src/proto/mod.rs b/uefi/src/proto/mod.rs index e77f856a0..69c212b83 100644 --- a/uefi/src/proto/mod.rs +++ b/uefi/src/proto/mod.rs @@ -10,6 +10,8 @@ //! //! [`boot`]: crate::boot#accessing-protocols +#[cfg(feature = "alloc")] +pub mod ata; pub mod console; pub mod debug; pub mod device_path; diff --git a/xtask/src/qemu.rs b/xtask/src/qemu.rs index ec658a565..1c798ce5e 100644 --- a/xtask/src/qemu.rs +++ b/xtask/src/qemu.rs @@ -489,6 +489,18 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> { cmd.arg("-device"); // attach disk to SCSI controller cmd.arg("scsi-hd,drive=scsidisk0,vendor=uefi-rs,product=ExtScsiPassThru"); + if arch == UefiArch::IA32 || arch == UefiArch::X86_64 { + // Fourth (ATA) disk + let ata_test_disk = tmp_dir.join("test_disk3.empty.img"); + create_mbr_test_disk(&ata_test_disk)?; + cmd.arg("-drive"); + let mut drive_arg = OsString::from("if=none,format=raw,id=satadisk0,file="); + drive_arg.push(ata_test_disk.clone()); + cmd.arg(drive_arg); + cmd.arg("-device"); + cmd.arg("ide-hd,drive=satadisk0,bus=ide.2,serial=AtaPassThru,model=AtaPassThru"); + } + let qemu_monitor_pipe = Pipe::new(tmp_dir, "qemu-monitor")?; let serial_pipe = Pipe::new(tmp_dir, "serial")?;