Skip to content

Commit 65b2147

Browse files
committed
Add IommuMemory
This `IoMemory` type provides an I/O virtual address space by adding an IOMMU translation layer to an underlying `GuestMemory` object. Signed-off-by: Hanna Czenczek <[email protected]>
1 parent 9dad343 commit 65b2147

File tree

2 files changed

+218
-2
lines changed

2 files changed

+218
-2
lines changed

src/iommu.rs

Lines changed: 217 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,17 @@
1212
//! IOTLB misses require sending a notification to the front-end and awaiting a reply that supplies
1313
//! the desired mapping.
1414
15-
use crate::{GuestAddress, Permissions};
15+
use crate::guest_memory::{
16+
Error as GuestMemoryError, GuestMemorySliceIterator, IoMemorySliceIterator,
17+
Result as GuestMemoryResult,
18+
};
19+
use crate::{bitmap, GuestAddress, GuestMemory, IoMemory, Permissions, VolatileSlice};
1620
use rangemap::RangeMap;
1721
use std::cmp;
1822
use std::fmt::Debug;
1923
use std::num::Wrapping;
2024
use std::ops::{Deref, Range};
25+
use std::sync::Arc;
2126

2227
/// Errors associated with IOMMU address translation.
2328
#[derive(Debug, thiserror::Error)]
@@ -172,6 +177,22 @@ pub struct IotlbFails {
172177
pub access_fails: Vec<IovaRange>,
173178
}
174179

180+
/// [`IoMemory`] type that consists of an underlying [`GuestMemory`] object plus an [`Iommu`].
181+
///
182+
/// The underlying [`GuestMemory`] is basically the physical memory, and the [`Iommu`] translates
183+
/// the I/O virtual address space that `IommuMemory` provides into that underlying physical address
184+
/// space.
185+
#[derive(Debug, Default)]
186+
pub struct IommuMemory<M: GuestMemory, I: Iommu> {
187+
/// Physical memory
188+
inner: M,
189+
/// IOMMU to translate IOVAs into physical addresses
190+
iommu: Arc<I>,
191+
/// Whether the IOMMU is even to be used or not; disabling it makes this a pass-through to
192+
/// `inner`.
193+
use_iommu: bool,
194+
}
195+
175196
impl IommuMapping {
176197
/// Create a new mapping.
177198
fn new(source_base: u64, target_base: u64, permissions: Permissions) -> Self {
@@ -330,3 +351,198 @@ impl TryFrom<Range<u64>> for IovaRange {
330351
})
331352
}
332353
}
354+
355+
impl<M: GuestMemory, I: Iommu> IommuMemory<M, I> {
356+
/// Create a new `IommuMemory` instance.
357+
pub fn new(inner: M, iommu: I, use_iommu: bool) -> Self {
358+
IommuMemory {
359+
inner,
360+
iommu: Arc::new(iommu),
361+
use_iommu,
362+
}
363+
}
364+
365+
/// Create a new version of `self` with the underlying physical memory replaced.
366+
///
367+
/// Note that the inner `Arc` reference to the IOMMU is cloned, i.e. both the existing and the
368+
/// new `IommuMemory` object will share an IOMMU instance. (The `use_iommu` flag however is
369+
/// copied, so is independent between the two instances.)
370+
pub fn inner_replaced(&self, inner: M) -> Self {
371+
IommuMemory {
372+
inner,
373+
iommu: Arc::clone(&self.iommu),
374+
use_iommu: self.use_iommu,
375+
}
376+
}
377+
378+
/// Enable or disable the IOMMU.
379+
///
380+
/// Disabling the IOMMU switches to pass-through mode, where every access is done directly on
381+
/// the underlying physical memory.
382+
pub fn set_iommu_enabled(&mut self, enabled: bool) {
383+
self.use_iommu = enabled;
384+
}
385+
386+
/// Return a reference to the IOMMU.
387+
pub fn iommu(&self) -> &Arc<I> {
388+
&self.iommu
389+
}
390+
391+
/// Return a reference to the inner physical memory object.
392+
pub fn inner(&self) -> &M {
393+
&self.inner
394+
}
395+
}
396+
397+
impl<M: GuestMemory + Clone, I: Iommu> Clone for IommuMemory<M, I> {
398+
fn clone(&self) -> Self {
399+
IommuMemory {
400+
inner: self.inner.clone(),
401+
iommu: Arc::clone(&self.iommu),
402+
use_iommu: self.use_iommu,
403+
}
404+
}
405+
}
406+
407+
impl<M: GuestMemory, I: Iommu> IoMemory for IommuMemory<M, I> {
408+
type PhysicalMemory = M;
409+
410+
fn check_range(&self, addr: GuestAddress, count: usize, access: Permissions) -> bool {
411+
if !self.use_iommu {
412+
return self.inner.check_range(addr, count);
413+
}
414+
415+
let Ok(mut translated_iter) = self.iommu.translate(addr, count, access) else {
416+
return false;
417+
};
418+
419+
translated_iter.all(|translated| self.inner.check_range(translated.base, translated.length))
420+
}
421+
422+
fn get_slices<'a>(
423+
&'a self,
424+
addr: GuestAddress,
425+
count: usize,
426+
access: Permissions,
427+
) -> GuestMemoryResult<impl IoMemorySliceIterator<'a, bitmap::MS<'a, M>>> {
428+
if self.use_iommu {
429+
IommuMemorySliceIterator::virt(self, addr, count, access)
430+
.map_err(GuestMemoryError::IommuError)
431+
} else {
432+
Ok(IommuMemorySliceIterator::phys(self, addr, count))
433+
}
434+
}
435+
436+
fn physical_memory(&self) -> Option<&Self::PhysicalMemory> {
437+
if self.use_iommu {
438+
None
439+
} else {
440+
Some(&self.inner)
441+
}
442+
}
443+
}
444+
445+
/// Iterates over [`VolatileSlice`]s that together form an area in an `IommuMemory`.
446+
///
447+
/// Returned by [`IommuMemory::get_slices()`]
448+
#[derive(Debug)]
449+
pub struct IommuMemorySliceIterator<'a, M: GuestMemory, I: Iommu + 'a> {
450+
/// Underlying physical memory (i.e. not the `IommuMemory`)
451+
phys_mem: &'a M,
452+
/// IOMMU translation result (i.e. remaining physical regions to visit)
453+
translation: Option<IotlbIterator<I::IotlbGuard<'a>>>,
454+
/// Iterator in the currently visited physical region
455+
current_translated_iter: Option<GuestMemorySliceIterator<'a, M>>,
456+
}
457+
458+
impl<'a, M: GuestMemory, I: Iommu> IommuMemorySliceIterator<'a, M, I> {
459+
/// Create an iterator over the physical region `[addr, addr + count)`.
460+
///
461+
/// “Physical” means that the IOMMU is not used to translate this address range. The resulting
462+
/// iterator is effectively the same as would be returned by [`GuestMemory::get_slices()`] on
463+
/// the underlying physical memory for the given address range.
464+
fn phys(mem: &'a IommuMemory<M, I>, addr: GuestAddress, count: usize) -> Self {
465+
IommuMemorySliceIterator {
466+
phys_mem: &mem.inner,
467+
translation: None,
468+
current_translated_iter: Some(mem.inner.get_slices(addr, count)),
469+
}
470+
}
471+
472+
/// Create an iterator over the IOVA region `[addr, addr + count)`.
473+
///
474+
/// This address range is translated using the IOMMU, and the resulting mappings are then
475+
/// separately visited via [`GuestMemory::get_slices()`].
476+
fn virt(
477+
mem: &'a IommuMemory<M, I>,
478+
addr: GuestAddress,
479+
count: usize,
480+
access: Permissions,
481+
) -> Result<Self, Error> {
482+
let translation = mem.iommu.translate(addr, count, access)?;
483+
Ok(IommuMemorySliceIterator {
484+
phys_mem: &mem.inner,
485+
translation: Some(translation),
486+
current_translated_iter: None,
487+
})
488+
}
489+
490+
/// Helper function for [`<Self as Iterator>::next()`].
491+
///
492+
/// Get the next slice and update the internal state. If there is an element left in
493+
/// `self.current_translated_iter`, return that; otherwise, move to the next mapping left in
494+
/// `self.translation` until there are no more mappings left.
495+
///
496+
/// If both fields are `None`, always return `None`.
497+
///
498+
/// # Safety
499+
///
500+
/// This function never resets `self.current_translated_iter` or `self.translation` to `None`,
501+
/// particularly not in case of error; calling this function with these fields not reset after
502+
/// an error is ill-defined, so the caller must check the return value, and in case of an
503+
/// error, reset these fields to `None`.
504+
///
505+
/// (This is why this function exists, so this reset can happen in a single central location.)
506+
unsafe fn do_next(
507+
&mut self,
508+
) -> Option<GuestMemoryResult<VolatileSlice<'a, bitmap::MS<'a, M>>>> {
509+
loop {
510+
if let Some(item) = self
511+
.current_translated_iter
512+
.as_mut()
513+
.and_then(|iter| iter.next())
514+
{
515+
return Some(item);
516+
}
517+
518+
let next_mapping = self.translation.as_mut()?.next()?;
519+
self.current_translated_iter = Some(
520+
self.phys_mem
521+
.get_slices(next_mapping.base, next_mapping.length),
522+
);
523+
}
524+
}
525+
}
526+
527+
impl<'a, M: GuestMemory, I: Iommu> Iterator for IommuMemorySliceIterator<'a, M, I> {
528+
type Item = GuestMemoryResult<VolatileSlice<'a, bitmap::MS<'a, M>>>;
529+
530+
fn next(&mut self) -> Option<Self::Item> {
531+
// SAFETY:
532+
// We reset `current_translated_iter` and `translation` to `None` in case of error
533+
match unsafe { self.do_next() } {
534+
Some(Ok(slice)) => Some(Ok(slice)),
535+
other => {
536+
// On error (or end), clear both so iteration remains stopped
537+
self.current_translated_iter.take();
538+
self.translation.take();
539+
other
540+
}
541+
}
542+
}
543+
}
544+
545+
impl<'a, M: GuestMemory, I: Iommu> IoMemorySliceIterator<'a, bitmap::MS<'a, M>>
546+
for IommuMemorySliceIterator<'a, M, I>
547+
{
548+
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ pub use io::{ReadVolatile, WriteVolatile};
6767
#[cfg(feature = "iommu")]
6868
pub mod iommu;
6969
#[cfg(feature = "iommu")]
70-
pub use iommu::{Iommu, Iotlb};
70+
pub use iommu::{Iommu, IommuMemory, Iotlb};
7171

7272
#[cfg(feature = "backend-mmap")]
7373
pub mod mmap;

0 commit comments

Comments
 (0)