mirror of
https://github.com/rust-osdev/uefi-rs
synced 2026-08-26 18:26:05 -04:00
commit
838b978c7d
8 changed files with 676 additions and 2 deletions
|
|
@ -72,7 +72,7 @@ pub const EDKII_IOMMU_PROTOCOL_REVISION: u64 = 0x0001_0000;
|
|||
bitflags! {
|
||||
/// EDKII IOMMU attribute flags
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct EdkiiIommuAttribute: u64 {
|
||||
/// Memory is write-combined
|
||||
const MEMORY_WRITE_COMBINE = 0x0080;
|
||||
|
|
@ -99,7 +99,7 @@ impl EdkiiIommuAttribute {
|
|||
bitflags! {
|
||||
/// EDKII IOMMU access flags for SetAttribute
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct EdkiiIommuAccess: u64 {
|
||||
/// Read access
|
||||
const READ = 0x1;
|
||||
|
|
|
|||
358
uefi-test-runner/src/proto/iommu.rs
Normal file
358
uefi-test-runner/src/proto/iommu.rs
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use uefi::boot;
|
||||
use uefi::mem::memory_map::MemoryType;
|
||||
use uefi::proto::dma::iommu::{EdkiiIommuAccess, EdkiiIommuAttribute, EdkiiIommuOperation, Iommu};
|
||||
|
||||
/// Runs the IOMMU protocol integration tests against the firmware-provided
|
||||
/// protocol instance. These checks cover allocation, buffer access, mapping
|
||||
/// operations, attributes, and representative buffer sizes.
|
||||
pub fn test() {
|
||||
info!("Running IOMMU protocol test");
|
||||
|
||||
let handle =
|
||||
boot::get_handle_for_protocol::<Iommu>().expect("Failed to get IOMMU protocol handle");
|
||||
let iommu =
|
||||
boot::open_protocol_exclusive::<Iommu>(handle).expect("Failed to open IOMMU protocol");
|
||||
|
||||
let revision = iommu.revision();
|
||||
info!("Revision: {revision:#x}");
|
||||
|
||||
test_allocate_buffer(&iommu);
|
||||
test_buffer_read_write(&iommu);
|
||||
test_map_operations(&iommu);
|
||||
test_map_64bit_operations(&iommu);
|
||||
test_multiple_mappings(&iommu);
|
||||
test_different_attributes(&iommu);
|
||||
test_multiple_buffer_sizes(&iommu);
|
||||
test_reject_oversized_mapping(&iommu);
|
||||
}
|
||||
|
||||
/// Tests that the IOMMU protocol can allocate a one-page DMA buffer.
|
||||
/// It verifies both the recorded page count and the byte size exposed by the
|
||||
/// safe wrapper.
|
||||
fn test_allocate_buffer(iommu: &Iommu) {
|
||||
let pages = 1;
|
||||
let attributes = EdkiiIommuAttribute::MEMORY_CACHED;
|
||||
|
||||
let buffer = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, pages, attributes)
|
||||
.expect("Failed to allocate IOMMU buffer");
|
||||
|
||||
assert_eq!(buffer.pages(), pages);
|
||||
assert_eq!(buffer.size(), 4096);
|
||||
}
|
||||
|
||||
/// Tests that an allocated DMA buffer can be accessed as a mutable byte slice.
|
||||
fn test_buffer_read_write(iommu: &Iommu) {
|
||||
let pages = 1;
|
||||
let attributes = EdkiiIommuAttribute::MEMORY_CACHED;
|
||||
|
||||
let mut buffer = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, pages, attributes)
|
||||
.expect("Failed to allocate IOMMU buffer");
|
||||
|
||||
for (i, byte) in buffer.iter_mut().enumerate() {
|
||||
*byte = (i & 0xFF) as u8;
|
||||
}
|
||||
|
||||
for (i, byte) in buffer.iter().enumerate() {
|
||||
assert_eq!(*byte, (i & 0xFF) as u8, "Buffer mismatch at offset {}", i);
|
||||
}
|
||||
|
||||
buffer[0] = 0xAA;
|
||||
buffer[100] = 0xBB;
|
||||
buffer[4095] = 0xCC;
|
||||
|
||||
assert_eq!(buffer[0], 0xAA);
|
||||
assert_eq!(buffer[100], 0xBB);
|
||||
assert_eq!(buffer[4095], 0xCC);
|
||||
}
|
||||
|
||||
/// Tests basic mapping operations on an allocated IOMMU buffer.
|
||||
/// It verifies that the buffer can be mapped successfully for device access
|
||||
/// and that the mapped size matches the requested size.
|
||||
fn test_map_operations(iommu: &Iommu) {
|
||||
let pages = 1;
|
||||
let attributes = EdkiiIommuAttribute::MEMORY_CACHED;
|
||||
|
||||
let mut buffer = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, pages, attributes)
|
||||
.expect("Failed to allocate IOMMU buffer");
|
||||
|
||||
let buffer_size = buffer.size();
|
||||
let (_device_address, mut mapping, mapped_bytes) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_READ,
|
||||
&mut buffer,
|
||||
buffer_size,
|
||||
)
|
||||
.expect("Failed to map buffer for BUS_MASTER_READ");
|
||||
assert_eq!(mapped_bytes, buffer_size);
|
||||
|
||||
let image_handle = boot::image_handle();
|
||||
match iommu.set_attribute(image_handle, &mut mapping, EdkiiIommuAccess::READ) {
|
||||
Ok(()) => {}
|
||||
Err(e) if e.status() == uefi::Status::UNSUPPORTED => {}
|
||||
Err(e) => panic!("set_attribute failed with unexpected error: {:?}", e),
|
||||
}
|
||||
|
||||
drop(mapping);
|
||||
|
||||
let (_device_address, mapping, mapped_bytes) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_WRITE,
|
||||
&mut buffer,
|
||||
buffer_size,
|
||||
)
|
||||
.expect("Failed to map buffer for BUS_MASTER_WRITE");
|
||||
assert_eq!(mapped_bytes, buffer_size);
|
||||
drop(mapping);
|
||||
|
||||
let (_device_address, _mapping, mapped_bytes) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_COMMON_BUFFER,
|
||||
&mut buffer,
|
||||
buffer_size,
|
||||
)
|
||||
.expect("Failed to map buffer for BUS_MASTER_COMMON_BUFFER");
|
||||
assert_eq!(mapped_bytes, buffer_size);
|
||||
}
|
||||
|
||||
/// Tests 64-bit mapping operations on an allocated IOMMU buffer.
|
||||
/// This ensures the IOMMU correctly handles requests specifically requiring 64-bit device addresses.
|
||||
fn test_map_64bit_operations(iommu: &Iommu) {
|
||||
let pages = 1;
|
||||
let attributes = EdkiiIommuAttribute::MEMORY_CACHED;
|
||||
|
||||
let mut buffer = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, pages, attributes)
|
||||
.expect("Failed to allocate IOMMU buffer");
|
||||
|
||||
let buffer_size = buffer.size();
|
||||
let (_device_address, mapping, mapped_bytes) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_READ64,
|
||||
&mut buffer,
|
||||
buffer_size,
|
||||
)
|
||||
.expect("Failed to map buffer for BUS_MASTER_READ64");
|
||||
assert_eq!(mapped_bytes, buffer_size);
|
||||
drop(mapping);
|
||||
|
||||
let (_device_address, mapping, mapped_bytes) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_WRITE64,
|
||||
&mut buffer,
|
||||
buffer_size,
|
||||
)
|
||||
.expect("Failed to map buffer for BUS_MASTER_WRITE64");
|
||||
assert_eq!(mapped_bytes, buffer_size);
|
||||
drop(mapping);
|
||||
|
||||
let (_device_address, _mapping, mapped_bytes) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_COMMON_BUFFER64,
|
||||
&mut buffer,
|
||||
buffer_size,
|
||||
)
|
||||
.expect("Failed to map buffer for BUS_MASTER_COMMON_BUFFER64");
|
||||
assert_eq!(mapped_bytes, buffer_size);
|
||||
}
|
||||
|
||||
/// Tests that mapping multiple distinct buffers yields unique device addresses.
|
||||
/// This verifies the IOMMU allocator's ability to handle concurrent mappings
|
||||
/// without address space collisions.
|
||||
fn test_multiple_mappings(iommu: &Iommu) {
|
||||
let pages = 1;
|
||||
let attributes = EdkiiIommuAttribute::MEMORY_CACHED;
|
||||
|
||||
let mut buffer1 = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, pages, attributes)
|
||||
.expect("Failed to allocate buffer 1");
|
||||
|
||||
let mut buffer2 = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, pages, attributes)
|
||||
.expect("Failed to allocate buffer 2");
|
||||
|
||||
let mut buffer3 = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, pages, attributes)
|
||||
.expect("Failed to allocate buffer 3");
|
||||
|
||||
let buffer1_size = buffer1.size();
|
||||
let buffer2_size = buffer2.size();
|
||||
let buffer3_size = buffer3.size();
|
||||
|
||||
let (addr1, mapping1, _) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_READ,
|
||||
&mut buffer1,
|
||||
buffer1_size,
|
||||
)
|
||||
.expect("Failed to map buffer 1");
|
||||
|
||||
let (addr2, mapping2, _) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_WRITE,
|
||||
&mut buffer2,
|
||||
buffer2_size,
|
||||
)
|
||||
.expect("Failed to map buffer 2");
|
||||
|
||||
let (addr3, mapping3, _) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_COMMON_BUFFER,
|
||||
&mut buffer3,
|
||||
buffer3_size,
|
||||
)
|
||||
.expect("Failed to map buffer 3");
|
||||
|
||||
assert_ne!(
|
||||
addr1, addr2,
|
||||
"Mapping 1 and 2 should have different addresses"
|
||||
);
|
||||
assert_ne!(
|
||||
addr2, addr3,
|
||||
"Mapping 2 and 3 should have different addresses"
|
||||
);
|
||||
assert_ne!(
|
||||
addr1, addr3,
|
||||
"Mapping 1 and 3 should have different addresses"
|
||||
);
|
||||
|
||||
drop(mapping1);
|
||||
drop(mapping2);
|
||||
drop(mapping3);
|
||||
}
|
||||
|
||||
/// Tests allocation and mapping with several IOMMU memory attributes.
|
||||
/// This verifies that cached, write-combine, and combined attributes can pass
|
||||
/// through the safe API to firmware.
|
||||
fn test_different_attributes(iommu: &Iommu) {
|
||||
let pages = 1;
|
||||
|
||||
let mut buffer_cached = iommu
|
||||
.allocate_buffer(
|
||||
MemoryType::BOOT_SERVICES_DATA,
|
||||
pages,
|
||||
EdkiiIommuAttribute::MEMORY_CACHED,
|
||||
)
|
||||
.expect("Failed to allocate MEMORY_CACHED buffer");
|
||||
|
||||
let mut buffer_wc = iommu
|
||||
.allocate_buffer(
|
||||
MemoryType::BOOT_SERVICES_DATA,
|
||||
pages,
|
||||
EdkiiIommuAttribute::MEMORY_WRITE_COMBINE,
|
||||
)
|
||||
.expect("Failed to allocate MEMORY_WRITE_COMBINE buffer");
|
||||
|
||||
let combined_attrs =
|
||||
EdkiiIommuAttribute::MEMORY_CACHED | EdkiiIommuAttribute::DUAL_ADDRESS_CYCLE;
|
||||
let mut buffer_combined = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, pages, combined_attrs)
|
||||
.expect("Failed to allocate buffer with combined attributes");
|
||||
|
||||
let buffer_cached_size = buffer_cached.size();
|
||||
let buffer_wc_size = buffer_wc.size();
|
||||
let buffer_combined_size = buffer_combined.size();
|
||||
|
||||
let (_addr, _mapping, _) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_READ,
|
||||
&mut buffer_cached,
|
||||
buffer_cached_size,
|
||||
)
|
||||
.expect("Failed to map cached buffer");
|
||||
|
||||
let (_addr, _mapping, _) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_READ,
|
||||
&mut buffer_wc,
|
||||
buffer_wc_size,
|
||||
)
|
||||
.expect("Failed to map write-combine buffer");
|
||||
|
||||
let (_addr, _mapping, _) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_READ,
|
||||
&mut buffer_combined,
|
||||
buffer_combined_size,
|
||||
)
|
||||
.expect("Failed to map combined attributes buffer");
|
||||
}
|
||||
|
||||
/// Tests allocation and mapping for representative one-page and multi-page
|
||||
/// buffers. It verifies that wrapper size calculations stay consistent across
|
||||
/// different page counts.
|
||||
fn test_multiple_buffer_sizes(iommu: &Iommu) {
|
||||
let attributes = EdkiiIommuAttribute::MEMORY_CACHED;
|
||||
|
||||
let mut buffer_1pg = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, 1, attributes)
|
||||
.expect("Failed to allocate 1-page buffer");
|
||||
assert_eq!(buffer_1pg.pages(), 1);
|
||||
assert_eq!(buffer_1pg.size(), 4096);
|
||||
|
||||
let mut buffer_4pg = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, 4, attributes)
|
||||
.expect("Failed to allocate 4-page buffer");
|
||||
assert_eq!(buffer_4pg.pages(), 4);
|
||||
assert_eq!(buffer_4pg.size(), 16384);
|
||||
|
||||
let mut buffer_16pg = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, 16, attributes)
|
||||
.expect("Failed to allocate 16-page buffer");
|
||||
assert_eq!(buffer_16pg.pages(), 16);
|
||||
assert_eq!(buffer_16pg.size(), 65536);
|
||||
|
||||
let buffer_1pg_size = buffer_1pg.size();
|
||||
let buffer_4pg_size = buffer_4pg.size();
|
||||
let buffer_16pg_size = buffer_16pg.size();
|
||||
|
||||
let (_, _mapping, mapped) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_READ,
|
||||
&mut buffer_1pg,
|
||||
buffer_1pg_size,
|
||||
)
|
||||
.expect("Failed to map 1-page buffer");
|
||||
assert_eq!(mapped, buffer_1pg_size);
|
||||
|
||||
let (_, _mapping, mapped) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_READ,
|
||||
&mut buffer_4pg,
|
||||
buffer_4pg_size,
|
||||
)
|
||||
.expect("Failed to map 4-page buffer");
|
||||
assert_eq!(mapped, buffer_4pg_size);
|
||||
|
||||
let (_, _mapping, mapped) = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_READ,
|
||||
&mut buffer_16pg,
|
||||
buffer_16pg_size,
|
||||
)
|
||||
.expect("Failed to map 16-page buffer");
|
||||
assert_eq!(mapped, buffer_16pg_size);
|
||||
}
|
||||
|
||||
/// Tests that the safe mapping API rejects lengths larger than the DMA buffer.
|
||||
fn test_reject_oversized_mapping(iommu: &Iommu) {
|
||||
let attributes = EdkiiIommuAttribute::MEMORY_CACHED;
|
||||
let mut buffer = iommu
|
||||
.allocate_buffer(MemoryType::BOOT_SERVICES_DATA, 1, attributes)
|
||||
.expect("Failed to allocate IOMMU buffer");
|
||||
let oversized_len = buffer.size() + 1;
|
||||
|
||||
let err = iommu
|
||||
.map(
|
||||
EdkiiIommuOperation::BUS_MASTER_READ,
|
||||
&mut buffer,
|
||||
oversized_len,
|
||||
)
|
||||
.expect_err("IOMMU map accepted an oversized buffer length");
|
||||
|
||||
assert_eq!(err.status(), uefi::Status::BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
|
@ -30,6 +30,9 @@ pub fn test() {
|
|||
usb::test();
|
||||
misc::test();
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
iommu::test();
|
||||
|
||||
// disable the ATA test on aarch64 for now. The aarch64 UEFI Firmware does not yet seem
|
||||
// to support SATA controllers (and providing an AtaPassThru protocol instance for them).
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
|
|
@ -111,6 +114,8 @@ mod console;
|
|||
mod debug;
|
||||
mod device_path;
|
||||
mod driver;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod iommu;
|
||||
mod load;
|
||||
mod loaded_image;
|
||||
mod media;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
set_attributes(), set_attributes_with_range()}`
|
||||
- Added `memory()` and `io()` address space access to `PciRootBridgeIo`
|
||||
protocol.
|
||||
- Added `proto::dma::iommu::Iommu` for IOMMU-based DMA buffer management.
|
||||
|
||||
## Changed
|
||||
- MSRV increased from 1.88 to 1.91.
|
||||
|
|
|
|||
162
uefi/src/proto/dma/iommu.rs
Normal file
162
uefi/src/proto/dma/iommu.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
//! EDKII IOMMU protocol.
|
||||
|
||||
use crate::data_types::PhysicalAddress;
|
||||
use crate::mem::memory_map::MemoryType;
|
||||
use crate::proto::unsafe_protocol;
|
||||
use crate::{Handle, Result, Status, StatusExt};
|
||||
use core::ffi::c_void;
|
||||
use uefi_raw::table::boot::AllocateType;
|
||||
|
||||
pub use crate::proto::dma::{DmaBuffer, Mapping};
|
||||
pub use uefi_raw::protocol::iommu::{
|
||||
EdkiiIommuAccess, EdkiiIommuAttribute, EdkiiIommuOperation, EdkiiIommuProtocol,
|
||||
};
|
||||
|
||||
/// EDK2 IoMmu [`Protocol`].
|
||||
///
|
||||
/// [`Protocol`]: uefi::proto::Protocol
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
#[unsafe_protocol(EdkiiIommuProtocol::GUID)]
|
||||
pub struct Iommu(EdkiiIommuProtocol);
|
||||
|
||||
impl Iommu {
|
||||
/// Get the IOMMU protocol revision
|
||||
#[must_use]
|
||||
pub const fn revision(&self) -> u64 {
|
||||
self.0.revision
|
||||
}
|
||||
|
||||
/// Set access attributes for a mapping.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * [`crate::Status::INVALID_PARAMETER`]: invalid device handle, mapping, or access flags
|
||||
/// * [`crate::Status::UNSUPPORTED`]: operation not supported by this IOMMU
|
||||
/// * [`crate::Status::OUT_OF_RESOURCES`]: insufficient resources to modify IOMMU access
|
||||
/// * [`crate::Status::DEVICE_ERROR`]: IOMMU device reported an error
|
||||
pub fn set_attribute(
|
||||
&self,
|
||||
device_handle: Handle,
|
||||
mapping: &mut Mapping<'_, '_>,
|
||||
iommu_access: EdkiiIommuAccess,
|
||||
) -> Result {
|
||||
let mapping_raw = mapping.as_mut_ptr();
|
||||
// SAFETY: `mapping_raw` comes from a live `Mapping`, and `device_handle`
|
||||
// is an opaque firmware handle passed through unchanged.
|
||||
let status = unsafe {
|
||||
(self.0.set_attribute)(&self.0, device_handle.as_ptr(), mapping_raw, iommu_access)
|
||||
};
|
||||
|
||||
status.to_result()
|
||||
}
|
||||
|
||||
/// Map a buffer for DMA operations.
|
||||
///
|
||||
/// Returns the device address, mapping handle, and actual number of bytes mapped.
|
||||
/// The mapping is tied to `host_buffer` and will be automatically unmapped when
|
||||
/// dropped.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * [`crate::Status::INVALID_PARAMETER`]: invalid operation or buffer
|
||||
/// * [`crate::Status::BAD_BUFFER_SIZE`]: `number_of_bytes` is larger than `host_buffer`
|
||||
/// * [`crate::Status::UNSUPPORTED`]: host address cannot be mapped as a common buffer
|
||||
/// * [`crate::Status::OUT_OF_RESOURCES`]: insufficient resources
|
||||
/// * [`crate::Status::DEVICE_ERROR`]: system hardware could not map the requested address
|
||||
pub fn map<'iommu, 'buf>(
|
||||
&'iommu self,
|
||||
operation: EdkiiIommuOperation,
|
||||
host_buffer: &'buf mut DmaBuffer<'iommu>,
|
||||
number_of_bytes: usize,
|
||||
) -> Result<(PhysicalAddress, Mapping<'iommu, 'buf>, usize)> {
|
||||
if number_of_bytes > host_buffer.size() {
|
||||
return Err(Status::BAD_BUFFER_SIZE.into());
|
||||
}
|
||||
|
||||
let mut number_of_bytes = number_of_bytes;
|
||||
|
||||
let mut mapping_raw: *mut c_void = core::ptr::null_mut();
|
||||
let mut device_address: u64 = 0;
|
||||
|
||||
let host_address: *mut c_void = host_buffer.as_mut_ptr();
|
||||
|
||||
// SAFETY: `host_address` points into `host_buffer`, which is valid for
|
||||
// `number_of_bytes` because oversized lengths were rejected above.
|
||||
let status = unsafe {
|
||||
(self.0.map)(
|
||||
&self.0,
|
||||
operation,
|
||||
host_address,
|
||||
&mut number_of_bytes,
|
||||
&mut device_address,
|
||||
&mut mapping_raw,
|
||||
)
|
||||
};
|
||||
|
||||
status.to_result_with_val(|| {
|
||||
// SAFETY: `mapping_raw` was returned by this IOMMU protocol, and
|
||||
// `host_buffer` remains mutably borrowed for the mapping lifetime.
|
||||
let mapping = unsafe { Mapping::from_raw(mapping_raw, self, host_buffer) };
|
||||
(device_address, mapping, number_of_bytes)
|
||||
})
|
||||
}
|
||||
|
||||
/// Unmap a previously mapped buffer
|
||||
pub(crate) fn unmap_raw(&self, mapping: *mut c_void) -> Result {
|
||||
// SAFETY: The safe `Mapping` API only stores active mapping
|
||||
// pointers returned by this protocol, and `Drop` calls this once.
|
||||
let status = unsafe { (self.0.unmap)(&self.0, mapping) };
|
||||
status.to_result()
|
||||
}
|
||||
|
||||
/// Allocate a buffer suitable for DMA operations.
|
||||
///
|
||||
/// The buffer will be automatically freed when dropped.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * [`crate::Status::INVALID_PARAMETER`]: invalid memory type or attributes
|
||||
/// * [`crate::Status::UNSUPPORTED`]: unsupported attributes
|
||||
/// * [`crate::Status::OUT_OF_RESOURCES`]: memory pages could not be allocated
|
||||
pub fn allocate_buffer(
|
||||
&self,
|
||||
memory_type: MemoryType,
|
||||
pages: usize,
|
||||
attributes: EdkiiIommuAttribute,
|
||||
) -> Result<DmaBuffer<'_>> {
|
||||
let mut host_address: *mut c_void = core::ptr::null_mut();
|
||||
|
||||
// Per spec, AllocateType is ignored by the IOMMU allocate_buffer implementation.
|
||||
let allocate_type = AllocateType::ANY_PAGES;
|
||||
|
||||
// SAFETY: `host_address` is a valid out-pointer, and all other
|
||||
// arguments are plain values forwarded to firmware.
|
||||
let status = unsafe {
|
||||
(self.0.allocate_buffer)(
|
||||
&self.0,
|
||||
allocate_type,
|
||||
memory_type,
|
||||
pages,
|
||||
&mut host_address,
|
||||
attributes,
|
||||
)
|
||||
};
|
||||
|
||||
status.to_result_with_val(|| {
|
||||
// SAFETY: On success, firmware initialized `host_address` with a
|
||||
// buffer allocated by this IOMMU protocol for `pages` pages.
|
||||
unsafe { DmaBuffer::from_raw(host_address, pages, self) }
|
||||
})
|
||||
}
|
||||
|
||||
/// Free a buffer allocated with allocate_buffer
|
||||
pub(crate) fn free_buffer_raw(&self, ptr: *mut c_void, pages: usize) -> Result {
|
||||
// SAFETY: `DmaBuffer` calls this only for buffers allocated by this
|
||||
// protocol, preserving the original page count.
|
||||
let status = unsafe { (self.0.free_buffer)(&self.0, pages, ptr) };
|
||||
status.to_result()
|
||||
}
|
||||
}
|
||||
144
uefi/src/proto/dma/mod.rs
Normal file
144
uefi/src/proto/dma/mod.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
//! EDK2 IOMMU protocol.
|
||||
|
||||
use core::ffi::c_void;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
use uefi_raw::table::boot::PAGE_SIZE;
|
||||
|
||||
use crate::proto::dma::iommu::Iommu;
|
||||
|
||||
pub mod iommu;
|
||||
|
||||
/// A smart pointer for DMA buffers allocated through the IOMMU protocol.
|
||||
///
|
||||
/// The buffer can be accessed as a byte slice and is returned to firmware when
|
||||
/// dropped.
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct DmaBuffer<'a> {
|
||||
ptr: *mut c_void,
|
||||
pages: usize,
|
||||
iommu: &'a Iommu,
|
||||
}
|
||||
|
||||
impl<'a> DmaBuffer<'a> {
|
||||
/// Create a new DmaBuffer from a raw pointer and page count.
|
||||
///
|
||||
/// # Safety
|
||||
/// The caller must ensure that:
|
||||
/// - `ptr` is valid for `pages * PAGE_SIZE` bytes allocated by the IOMMU protocol.
|
||||
/// - `pages` correctly represents the number of pages allocated.
|
||||
/// - `iommu` is the protocol instance that allocated `ptr`.
|
||||
/// - This `DmaBuffer` is the unique owner responsible for freeing `ptr`.
|
||||
pub const unsafe fn from_raw(ptr: *mut c_void, pages: usize, iommu: &'a Iommu) -> Self {
|
||||
Self { ptr, pages, iommu }
|
||||
}
|
||||
|
||||
/// Get the raw pointer to the buffer.
|
||||
#[must_use]
|
||||
pub const fn as_ptr(&self) -> *const c_void {
|
||||
self.ptr.cast_const()
|
||||
}
|
||||
|
||||
/// Get the raw mutable pointer to the buffer.
|
||||
#[must_use]
|
||||
pub const fn as_mut_ptr(&mut self) -> *mut c_void {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Get the number of pages in the buffer.
|
||||
#[must_use]
|
||||
pub const fn pages(&self) -> usize {
|
||||
self.pages
|
||||
}
|
||||
|
||||
/// Get the size of the buffer in bytes.
|
||||
#[must_use]
|
||||
pub const fn size(&self) -> usize {
|
||||
self.pages * PAGE_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for DmaBuffer<'a> {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &[u8] {
|
||||
// SAFETY: `DmaBuffer::from_raw` requires `ptr` to be valid for
|
||||
// `pages * PAGE_SIZE` bytes for this buffer's lifetime.
|
||||
unsafe { core::slice::from_raw_parts(self.ptr.cast(), self.pages * PAGE_SIZE) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for DmaBuffer<'a> {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
// SAFETY: `&mut self` guarantees unique access to the owned DMA buffer,
|
||||
// whose raw memory is valid for `pages * PAGE_SIZE` bytes.
|
||||
unsafe { core::slice::from_raw_parts_mut(self.ptr.cast::<u8>(), self.pages * PAGE_SIZE) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for DmaBuffer<'a> {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = self.iommu.free_buffer_raw(self.ptr, self.pages) {
|
||||
log::error!("IOMMU free_buffer failed: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A smart pointer for active DMA buffer mappings.
|
||||
///
|
||||
/// The mapping keeps the firmware mapping alive and unmaps it when
|
||||
/// dropped.
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct Mapping<'a, 'buf> {
|
||||
ptr: *mut c_void,
|
||||
iommu: &'a Iommu,
|
||||
_buffer: PhantomData<&'buf mut DmaBuffer<'a>>,
|
||||
}
|
||||
|
||||
impl<'a, 'buf> Mapping<'a, 'buf> {
|
||||
/// Create a new Mapping from a raw pointer.
|
||||
///
|
||||
/// # Safety
|
||||
/// The caller must ensure that:
|
||||
/// - `ptr` is a valid mapping pointer returned by the IOMMU protocol.
|
||||
/// - The mapping is currently active and valid.
|
||||
/// - `iommu` is the protocol instance that created `ptr`.
|
||||
/// - `_buffer` is the `DmaBuffer` used to create this mapping and remains
|
||||
/// exclusively borrowed for the returned mapping's lifetime.
|
||||
pub const unsafe fn from_raw(
|
||||
ptr: *mut c_void,
|
||||
iommu: &'a Iommu,
|
||||
_buffer: &'buf mut DmaBuffer<'a>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ptr,
|
||||
iommu,
|
||||
_buffer: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw mapping pointer.
|
||||
#[must_use]
|
||||
pub const fn as_ptr(&self) -> *const c_void {
|
||||
self.ptr.cast_const()
|
||||
}
|
||||
|
||||
/// Get the raw mutable mapping pointer.
|
||||
#[must_use]
|
||||
pub const fn as_mut_ptr(&mut self) -> *mut c_void {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'buf> Drop for Mapping<'a, 'buf> {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = self.iommu.unmap_raw(self.as_mut_ptr()) {
|
||||
log::error!("IOMMU unmap failed: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ pub mod ata;
|
|||
pub mod console;
|
||||
pub mod debug;
|
||||
pub mod device_path;
|
||||
pub mod dma;
|
||||
pub mod driver;
|
||||
pub mod hii;
|
||||
pub mod loaded_image;
|
||||
|
|
|
|||
|
|
@ -425,6 +425,9 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
|
|||
UefiArch::IA32 | UefiArch::X86_64 => {
|
||||
// Use a modern machine.
|
||||
cmd.args(["-machine", "q35"]);
|
||||
if arch == UefiArch::X86_64 {
|
||||
cmd.args(["-device", "intel-iommu"]);
|
||||
}
|
||||
|
||||
// Multi-processor services protocol test needs exactly 4 CPUs.
|
||||
cmd.args(["-smp", "4"]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue