Skip to content
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

uefi: Implement safe wrapper for EFI_DISK_INFO_PROTOCOL #1590

Merged
merged 1 commit into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions uefi-raw/src/protocol/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
34 changes: 34 additions & 0 deletions uefi-test-runner/src/proto/media.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -343,6 +344,38 @@ fn test_raw_disk_io2(handle: Handle) {
}
}

fn test_disk_info() {
let disk_handles = uefi::boot::find_handles::<DiskInfo>().unwrap();

let mut found_drive = false;
for handle in disk_handles {
let disk_info = uefi::boot::open_protocol_exclusive::<DiskInfo>(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::<PartitionInfo>(disk_handle)
Expand Down Expand Up @@ -444,4 +477,5 @@ pub fn test() {

test_raw_disk_io(handle);
test_raw_disk_io2(handle);
test_disk_info();
}
1 change: 1 addition & 0 deletions uefi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
181 changes: 181 additions & 0 deletions uefi/src/proto/media/disk_info.rs
Original file line number Diff line number Diff line change
@@ -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<usize> {
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<usize> {
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<SenseDataInfo> {
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<DeviceLocationInfo> {
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,
},
)
}
}
}
1 change: 1 addition & 0 deletions uefi/src/proto/media/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
16 changes: 15 additions & 1 deletion xtask/src/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:");
Expand All @@ -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")?;

Expand Down