diff --git a/uefi-raw/src/protocol/disk.rs b/uefi-raw/src/protocol/disk.rs index a9ff3b6bf..1f97c32a8 100644 --- a/uefi-raw/src/protocol/disk.rs +++ b/uefi-raw/src/protocol/disk.rs @@ -65,6 +65,9 @@ impl DiskIo2Protocol { pub const REVISION: u64 = 0x00020000; } +/// DiskInfo protocol (EFI_DISK_INFO_PROTOCOL) +/// +/// See: UEFI Platform Initialization Specification #[derive(Debug)] #[repr(C)] pub struct DiskInfoProtocol { diff --git a/uefi-test-runner/src/proto/media.rs b/uefi-test-runner/src/proto/media.rs index ebe9ff577..49b00d4e3 100644 --- a/uefi-test-runner/src/proto/media.rs +++ b/uefi-test-runner/src/proto/media.rs @@ -10,6 +10,7 @@ use uefi::data_types::Align; use uefi::prelude::*; use uefi::proto::media::block::BlockIO; use uefi::proto::media::disk::{DiskIo, DiskIo2, DiskIo2Token}; +use uefi::proto::media::disk_info::{DiskInfo, DiskInfoInterface}; use uefi::proto::media::file::{ Directory, File, FileAttribute, FileInfo, FileMode, FileSystemInfo, FileSystemVolumeLabel, }; @@ -343,6 +344,38 @@ fn test_raw_disk_io2(handle: Handle) { } } +fn test_disk_info() { + let disk_handles = uefi::boot::find_handles::().unwrap(); + + let mut found_drive = false; + for handle in disk_handles { + let disk_info = uefi::boot::open_protocol_exclusive::(handle).unwrap(); + info!( + "DiskInfo at: {:?} (interface= {:?})", + handle, + disk_info.interface() + ); + // Find our disk + if disk_info.interface() != DiskInfoInterface::SCSI { + continue; + } + let mut inquiry_bfr = [0; 128]; + let Ok(len) = disk_info.inquiry(&mut inquiry_bfr) else { + continue; + }; + // SCSI Spec states: The standard INQUIRY data (see table 59) shall contain at least 36 bytes + assert!(len >= 36); + let vendor_id = core::str::from_utf8(&inquiry_bfr[8..16]).unwrap().trim(); + let product_id = core::str::from_utf8(&inquiry_bfr[16..32]).unwrap().trim(); + if vendor_id == "uefi-rs" && product_id == "ExtScsiPassThru" { + info!("Found Testdisk at Handle: {:?}", handle); + found_drive = true; + } + } + + assert!(found_drive); +} + /// Check that `disk_handle` points to the expected MBR partition. fn test_partition_info(disk_handle: Handle) { let pi = boot::open_protocol_exclusive::(disk_handle) @@ -444,4 +477,5 @@ pub fn test() { test_raw_disk_io(handle); test_raw_disk_io2(handle); + test_disk_info(); } diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index d653d820c..e13eda4e9 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -4,6 +4,7 @@ - Added `boot::signal_event`. - Added conversions between `proto::network::IpAddress` and `core::net` types. - Added conversions between `proto::network::MacAddress` and the `[u8; 6]` type that's more commonly used to represent MAC addresses. +- Added `proto::media::disk_info::DiskInfo`. ## Changed - **Breaking:** Removed `BootPolicyError` as `BootPolicy` construction is no diff --git a/uefi/src/proto/media/disk_info.rs b/uefi/src/proto/media/disk_info.rs new file mode 100644 index 000000000..13a3dbdb4 --- /dev/null +++ b/uefi/src/proto/media/disk_info.rs @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! DiskInfo protocol. + +use crate::StatusExt; +use uefi_macros::unsafe_protocol; +use uefi_raw::protocol::disk::DiskInfoProtocol; + +/// Enum representing the interface type of the disk. +/// +/// This protocol abstracts various disk interfaces, including IDE, USB, AHCI, NVME, and more. +/// Unknown indicates an unrecognized or not yet implemented interface type. +#[derive(Debug, Eq, PartialEq)] +pub enum DiskInfoInterface { + /// Unrecognized or unsupported interface. + Unknown, + /// Integrated Drive Electronics (IDE) interface. + IDE, + /// Universal Flash Storage (UFS) interface. + UFS, + /// Universal Serial Bus (USB) interface. + USB, + /// Advanced Host Controller Interface (AHCI) interface. + AHCI, + /// Non-Volatile Memory Express (NVME) interface. + NVME, + /// Small Computer System Interface (SCSI). + SCSI, + /// Secure Digital Memory Card (SDMMC) interface. + SDMMC, +} + +/// Structure containing metadata about the result for a call to [`DiskInfo::sense_data`]. +#[derive(Debug)] +pub struct SenseDataInfo { + /// Amount of bytes returned by the [`DiskInfo::sense_data`]. + pub bytes: usize, + /// Number of sense data messages contained in the resulting buffer from calling [`DiskInfo::sense_data`]. + pub number: u8, +} + +/// Structure containing information about the physical device location on the bus. +/// +/// This is not supported by all interface types. +#[derive(Debug)] +pub struct DeviceLocationInfo { + /// For IDE, this addresses the channel (primary or secondary). + /// For AHCI, this returns the port. + pub channel: u32, + /// For IDE, this contains whether the device is master or slave. + /// For AHCI, this returns the port-multiplier. + pub device: u32, +} + +/// DiskInfo protocol. +/// +/// This allows querying hardware information for detected disks in a simple way. +/// Originally, this was designed for IDE and it shows. +/// But support for a wide range of interfaces was retrofitted. +/// +/// Not all operations are supported by all interface types! +/// Either use [`DiskInfo::interface`] to determine what should be possible, or simply +/// try and handle the [`crate::Status::UNSUPPORTED`] error return value. +/// +/// # UEFI Spec Description +/// Provides the basic interfaces to abstract platform information regarding an IDE controller. +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(DiskInfoProtocol::GUID)] +pub struct DiskInfo(DiskInfoProtocol); + +impl DiskInfo { + /// Retrieves the interface type of the disk device. + /// + /// # Returns + /// [`DiskInfoInterface`] value representing the disk interface (e.g., IDE, USB, NVME, etc.). + #[must_use] + pub const fn interface(&self) -> DiskInfoInterface { + match self.0.interface { + DiskInfoProtocol::IDE_INTERFACE_GUID => DiskInfoInterface::IDE, + DiskInfoProtocol::UFS_INTERFACE_GUID => DiskInfoInterface::UFS, + DiskInfoProtocol::USB_INTERFACE_GUID => DiskInfoInterface::USB, + DiskInfoProtocol::AHCI_INTERFACE_GUID => DiskInfoInterface::AHCI, + DiskInfoProtocol::NVME_INTERFACE_GUID => DiskInfoInterface::NVME, + DiskInfoProtocol::SCSI_INTERFACE_GUID => DiskInfoInterface::SCSI, + DiskInfoProtocol::SD_MMC_INTERFACE_GUID => DiskInfoInterface::SDMMC, + _ => DiskInfoInterface::Unknown, + } + } + + /// Performs an inquiry command on the disk device. + /// + /// # Parameters + /// - `bfr`: A mutable byte buffer to store the inquiry data. + /// + /// # Returns + /// Length of the response (amount of bytes that were written to the given buffer). + /// + /// # Errors + /// - [`crate::Status::SUCCESS`] The command was accepted without any errors. + /// - [`crate::Status::NOT_FOUND`] The device does not support this data class. + /// - [`crate::Status::DEVICE_ERROR`] An error occurred while reading the InquiryData from the device. + /// - [`crate::Status::BUFFER_TOO_SMALL`] The provided InquiryDataSize buffer is not large enough to store the required data. + pub fn inquiry(&self, bfr: &mut [u8]) -> crate::Result { + let mut len: u32 = bfr.len() as u32; + unsafe { + (self.0.inquiry)(&self.0, bfr.as_mut_ptr().cast(), &mut len) + .to_result_with_val(|| len as usize) + } + } + + /// Performs an identify command on the disk device. + /// + /// # Parameters + /// - `bfr`: A mutable byte buffer to store the identification data. + /// + /// # Returns + /// Length of the response (amount of bytes that were written to the given buffer). + /// + /// # Errors + /// - [`crate::Status::SUCCESS`] The command was accepted without any errors. + /// - [`crate::Status::NOT_FOUND`] The device does not support this data class. + /// - [`crate::Status::DEVICE_ERROR`] An error occurred while reading the IdentifyData from the device. + /// - [`crate::Status::BUFFER_TOO_SMALL`] The provided IdentifyDataSize buffer is not large enough to store the required data. + pub fn identify(&self, bfr: &mut [u8]) -> crate::Result { + let mut len: u32 = bfr.len() as u32; + unsafe { + (self.0.identify)(&self.0, bfr.as_mut_ptr().cast(), &mut len) + .to_result_with_val(|| len as usize) + } + } + + /// Retrieves sense data from the disk device. + /// + /// # Parameters + /// - `bfr`: A mutable byte buffer to store the sense data. + /// + /// # Returns + /// [`SenseDataInfo`] struct containing the number of bytes of sense data and the number of sense data structures. + /// + /// # Errors + /// - [`crate::Status::SUCCESS`] The command was accepted without any errors. + /// - [`crate::Status::NOT_FOUND`] The device does not support this data class. + /// - [`crate::Status::DEVICE_ERROR`] An error occurred while reading the SenseData from the device. + /// - [`crate::Status::BUFFER_TOO_SMALL`] The provided SenseDataSize buffer is not large enough to store the required data. + pub fn sense_data(&self, bfr: &mut [u8]) -> crate::Result { + let mut len: u32 = bfr.len() as u32; + let mut number: u8 = 0; + unsafe { + (self.0.sense_data)(&self.0, bfr.as_mut_ptr().cast(), &mut len, &mut number) + .to_result_with_val(|| SenseDataInfo { + bytes: len as usize, + number, + }) + } + } + + /// Retrieves the physical location of the device on the bus. + /// + /// This operation provides information about the channel and device identifiers, which can + /// help determine the device's physical connection point. + /// + /// # Returns + /// [`DeviceLocationInfo`] struct containing the channel and device numbers. + /// + /// # Errors + /// - [`crate::Status::SUCCESS`] The `IdeChannel` and `IdeDevice` values are valid. + /// - [`crate::Status::UNSUPPORTED`] Not supported by this disk's interface type. + pub fn bus_location(&self) -> crate::Result { + let mut ide_channel: u32 = 0; // called ide, but also useful for other interfaces + let mut ide_device: u32 = 0; + unsafe { + (self.0.which_ide)(&self.0, &mut ide_channel, &mut ide_device).to_result_with_val( + || DeviceLocationInfo { + channel: ide_channel, + device: ide_device, + }, + ) + } + } +} diff --git a/uefi/src/proto/media/mod.rs b/uefi/src/proto/media/mod.rs index 579cec25c..4eb029eb5 100644 --- a/uefi/src/proto/media/mod.rs +++ b/uefi/src/proto/media/mod.rs @@ -10,6 +10,7 @@ pub mod file; pub mod block; pub mod disk; +pub mod disk_info; pub mod fs; pub mod load_file; pub mod partition; diff --git a/xtask/src/qemu.rs b/xtask/src/qemu.rs index ee6d18161..ec658a565 100644 --- a/xtask/src/qemu.rs +++ b/xtask/src/qemu.rs @@ -456,6 +456,10 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> { add_pflash_args(&mut cmd, &ovmf_paths.code, PflashMode::ReadOnly); add_pflash_args(&mut cmd, &ovmf_vars, PflashMode::ReadWrite); + // Configure SCSI Controller + cmd.arg("-device"); + cmd.arg("virtio-scsi-pci"); + // Mount a local directory as a FAT partition. cmd.arg("-drive"); let mut drive_arg = OsString::from("format=raw,file=fat:rw:"); @@ -467,14 +471,24 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> { cmd.args(["-display", "none"]); } + // Second (FAT) disk let test_disk = tmp_dir.join("test_disk.fat.img"); create_mbr_test_disk(&test_disk)?; - cmd.arg("-drive"); let mut drive_arg = OsString::from("format=raw,file="); drive_arg.push(test_disk.clone()); cmd.arg(drive_arg); + // Third (SCSI) disk for ExtScsiPassThru tests + let scsi_test_disk = tmp_dir.join("test_disk2.empty.img"); + std::fs::File::create(&scsi_test_disk)?.set_len(1024 * 1024 * 10)?; + cmd.arg("-drive"); + let mut drive_arg = OsString::from("if=none,id=scsidisk0,format=raw,file="); + drive_arg.push(scsi_test_disk.clone()); + cmd.arg(drive_arg); + cmd.arg("-device"); // attach disk to SCSI controller + cmd.arg("scsi-hd,drive=scsidisk0,vendor=uefi-rs,product=ExtScsiPassThru"); + let qemu_monitor_pipe = Pipe::new(tmp_dir, "qemu-monitor")?; let serial_pipe = Pipe::new(tmp_dir, "serial")?;