diff --git a/uefi/src/proto/dma/iommu.rs b/uefi/src/proto/dma/iommu.rs new file mode 100644 index 000000000..bca2bc780 --- /dev/null +++ b/uefi/src/proto/dma/iommu.rs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! EDK2 IoMmu protocol. + +use core::ffi::c_void; +use uefi::data_types::PhysicalAddress; +use uefi::mem::memory_map::MemoryType; +use uefi::proto::unsafe_protocol; +use uefi::{Handle, Result, StatusExt}; +use uefi_raw::table::boot::AllocateType; + +pub use crate::proto::dma::{DmaBuffer, Mapping}; +pub use crate::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 + pub fn set_attribute( + &self, + device_handle: Handle, + mapping: &Mapping, + iommu_access: EdkiiIommuAccess, + ) -> Result { + let mapping_raw = mapping.as_ptr(); + 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 + pub fn map( + &self, + operation: EdkiiIommuOperation, + host_buffer: &DmaBuffer, + number_of_bytes: usize, + ) -> Result<(PhysicalAddress, Mapping<'_>, usize)> { + 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_ptr(); + + 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(|| { + let mapping = unsafe { Mapping::from_raw(mapping_raw, self) }; + (device_address, mapping, number_of_bytes) + }) + } + + /// Unmap a previously mapped buffer + pub(crate) fn unmap_raw(&self, mapping: *mut c_void) -> Result { + let status = unsafe { (self.0.unmap)(&self.0, mapping) }; + status.to_result() + } + + /// Allocate a buffer suitable for DMA operations + pub fn allocate_buffer( + &self, + memory_type: MemoryType, + pages: usize, + attributes: EdkiiIommuAttribute, + ) -> Result> { + let mut host_address: *mut c_void = core::ptr::null_mut(); + + // Must be ignored + let allocate_type = AllocateType::ANY_PAGES; + + let status = unsafe { + (self.0.allocate_buffer)( + &self.0, + allocate_type, + memory_type, + pages, + &mut host_address, + attributes, + ) + }; + + let dma_buffer = unsafe { DmaBuffer::from_raw(host_address, pages, self) }; + + status.to_result_with_val(|| dma_buffer) + } + + /// Free a buffer allocated with allocate_buffer + pub(crate) fn free_buffer_raw(&self, ptr: *mut c_void, pages: usize) -> Result { + let status = unsafe { (self.0.free_buffer)(&self.0, pages, ptr) }; + status.to_result() + } +} diff --git a/uefi/src/proto/dma/mod.rs b/uefi/src/proto/dma/mod.rs new file mode 100644 index 000000000..dbd1e3dbd --- /dev/null +++ b/uefi/src/proto/dma/mod.rs @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! EDK2 IoMmu protocol. + +use core::ffi::c_void; +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 +#[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 a valid pointer to memory allocated by the IOMMU protocol + /// - `pages` correctly represents the number of pages allocated + 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) -> *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] { + unsafe { core::slice::from_raw_parts(self.ptr as *const u8, self.pages * PAGE_SIZE) } + } +} + +impl<'a> DerefMut for DmaBuffer<'a> { + fn deref_mut(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.ptr.cast::(), self.pages * PAGE_SIZE) } + } +} + +impl<'a> Drop for DmaBuffer<'a> { + fn drop(&mut self) { + let ptr = self.ptr; + let pages = self.pages; + let _ = self.iommu.free_buffer_raw(ptr, pages); + } +} + +/// A smart pointer for IOMMU mappings +#[must_use] +#[derive(Debug)] +pub struct Mapping<'a> { + ptr: *mut c_void, + iommu: &'a Iommu, +} + +impl<'a> Mapping<'a> { + /// 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 + pub const unsafe fn from_raw(ptr: *mut c_void, iommu: &'a Iommu) -> Self { + Self { ptr, iommu } + } + + /// Get the raw mapping pointer + #[must_use] + pub const fn as_ptr(&self) -> *mut c_void { + self.ptr + } +} + +impl<'a> Drop for Mapping<'a> { + fn drop(&mut self) { + let ptr = self.ptr; + let _ = self.iommu.unmap_raw(ptr); + } +} diff --git a/uefi/src/proto/mod.rs b/uefi/src/proto/mod.rs index 4451aa113..a07cb2d6d 100644 --- a/uefi/src/proto/mod.rs +++ b/uefi/src/proto/mod.rs @@ -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;