blob: 685c48f54aa3db8c274821d649af3a041f74d5b2 [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! MemoryMapper trait and basic impl for virtio-iommu implementation
//!
//! All the addr/range ends in this file are exclusive.
use std::any::Any;
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicU32, Ordering};
use anyhow::{anyhow, bail, Context, Result};
use base::{AsRawDescriptors, Protection, RawDescriptor};
use serde::{Deserialize, Serialize};
use vm_memory::GuestAddress;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemRegion {
pub gpa: GuestAddress,
pub len: u64,
pub prot: Protection,
}
/// Manages the mapping from a guest IO virtual address space to the guest physical address space
#[derive(Debug)]
pub struct MappingInfo {
pub iova: u64,
pub gpa: GuestAddress,
pub size: u64,
pub prot: Protection,
}
impl MappingInfo {
#[allow(dead_code)]
fn new(iova: u64, gpa: GuestAddress, size: u64, prot: Protection) -> Result<Self> {
if size == 0 {
bail!("can't create 0 sized region");
}
iova.checked_add(size).context("iova overflow")?;
gpa.checked_add(size).context("gpa overflow")?;
Ok(Self {
iova,
gpa,
size,
prot,
})
}
}
// A basic iommu. It is designed as a building block for virtio-iommu.
pub struct BasicMemoryMapper {
maps: BTreeMap<u64, MappingInfo>, // key = MappingInfo.iova
mask: u64,
id: u32,
}
#[derive(PartialEq, Debug)]
pub enum AddMapResult {
Ok,
OverlapFailure,
}
/// A generic interface for vfio and other iommu backends
pub trait MemoryMapper: Send {
/// Creates a new mapping. If the mapping overlaps with an existing
/// mapping, return Ok(false).
fn add_map(&mut self, new_map: MappingInfo) -> Result<AddMapResult>;
/// Removes all mappings within the specified range.
///
/// If a mapped region partially overlaps what is being unmapped, implementations
/// SHOULD return Ok(false) without removing any mappings.
fn remove_map(&mut self, iova_start: u64, size: u64) -> Result<bool>;
fn get_mask(&self) -> Result<u64>;
/// Whether or not endpoints can be safely detached from this mapper.
fn supports_detach(&self) -> bool;
/// Resets the mapper's domain back into its initial state. Only necessary
/// if |supports_detach| returns true.
fn reset_domain(&mut self) {}
/// Gets an identifier for the MemoryMapper instance. Must be unique among
/// instances of the same trait implementation.
fn id(&self) -> u32;
}
pub trait Translate {
/// Multiple MemRegions should be returned when the gpa is discontiguous or perms are different.
fn translate(&self, iova: u64, size: u64) -> Result<Vec<MemRegion>>;
}
pub trait MemoryMapperTrait: MemoryMapper + Translate + AsRawDescriptors + Any {}
impl<T: MemoryMapper + Translate + AsRawDescriptors + Any> MemoryMapperTrait for T {}
impl BasicMemoryMapper {
pub fn new(mask: u64) -> BasicMemoryMapper {
static NEXT_ID: AtomicU32 = AtomicU32::new(0);
BasicMemoryMapper {
maps: BTreeMap::new(),
mask,
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
}
}
#[cfg(test)]
pub fn len(&self) -> usize {
self.maps.len()
}
}
impl MemoryMapper for BasicMemoryMapper {
fn add_map(&mut self, new_map: MappingInfo) -> Result<AddMapResult> {
if new_map.size == 0 {
bail!("can't map 0 sized region");
}
let new_iova_end = new_map
.iova
.checked_add(new_map.size)
.context("iova overflow")?;
new_map
.gpa
.checked_add(new_map.size)
.context("gpa overflow")?;
let mut iter = self.maps.range(..new_iova_end);
if let Some((_, map)) = iter.next_back() {
if map.iova + map.size > new_map.iova {
return Ok(AddMapResult::OverlapFailure);
}
}
self.maps.insert(new_map.iova, new_map);
Ok(AddMapResult::Ok)
}
fn remove_map(&mut self, iova_start: u64, size: u64) -> Result<bool> {
if size == 0 {
bail!("can't unmap 0 sized region");
}
let iova_end = iova_start.checked_add(size).context("iova overflow")?;
// So that we invalid requests can be rejected w/o modifying things, check
// for partial overlap before removing the maps.
let mut to_be_removed = Vec::new();
for (key, map) in self.maps.range(..iova_end).rev() {
let map_iova_end = map.iova + map.size;
if map_iova_end <= iova_start {
// no overlap
break;
}
if iova_start <= map.iova && map_iova_end <= iova_end {
to_be_removed.push(*key);
} else {
return Ok(false);
}
}
for key in to_be_removed {
self.maps.remove(&key).expect("map should contain key");
}
Ok(true)
}
fn get_mask(&self) -> Result<u64> {
Ok(self.mask)
}
fn supports_detach(&self) -> bool {
true
}
fn reset_domain(&mut self) {
self.maps.clear();
}
fn id(&self) -> u32 {
self.id
}
}
impl Translate for BasicMemoryMapper {
/// Regions of contiguous iovas and gpas, and identical permission are merged
fn translate(&self, iova: u64, size: u64) -> Result<Vec<MemRegion>> {
if size == 0 {
bail!("can't translate 0 sized region");
}
let iova_end = iova.checked_add(size).context("iova overflow")?;
let mut iter = self.maps.range(..iova_end);
let mut last_iova = iova_end;
let mut regions: Vec<MemRegion> = Vec::new();
while let Some((_, map)) = iter.next_back() {
if last_iova > map.iova + map.size {
break;
}
let mut new_region = true;
// This is the last region to be inserted / first to be returned when iova >= map.iova
let region_len = last_iova - std::cmp::max::<u64>(map.iova, iova);
if let Some(last) = regions.last_mut() {
if map.gpa.unchecked_add(map.size) == last.gpa && map.prot == last.prot {
last.gpa = map.gpa;
last.len += region_len;
new_region = false;
}
}
if new_region {
// If this is the only region to be returned, region_len == size (arg of this
// function)
// iova_end = iova + size
// last_iova = iova_end
// region_len = last_iova - max(map.iova, iova)
// = iova + size - iova
// = size
regions.push(MemRegion {
gpa: map.gpa,
len: region_len,
prot: map.prot,
});
}
if iova >= map.iova {
regions.reverse();
// The gpa of the first region has to be offseted
regions[0].gpa = map
.gpa
.checked_add(iova - map.iova)
.context("gpa overflow")?;
return Ok(regions);
}
last_iova = map.iova;
}
Err(anyhow!("invalid iova {:x} {:x}", iova, size))
}
}
impl AsRawDescriptors for BasicMemoryMapper {
fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt::Debug;
#[test]
fn test_mapping_info() {
// Overflow
MappingInfo::new(u64::MAX - 1, GuestAddress(1), 2, Protection::read()).unwrap_err();
MappingInfo::new(1, GuestAddress(u64::MAX - 1), 2, Protection::read()).unwrap_err();
MappingInfo::new(u64::MAX, GuestAddress(1), 2, Protection::read()).unwrap_err();
MappingInfo::new(1, GuestAddress(u64::MAX), 2, Protection::read()).unwrap_err();
MappingInfo::new(5, GuestAddress(5), u64::MAX, Protection::read()).unwrap_err();
// size = 0
MappingInfo::new(1, GuestAddress(5), 0, Protection::read()).unwrap_err();
}
#[test]
fn test_map_overlap() {
let mut mapper = BasicMemoryMapper::new(u64::MAX);
mapper
.add_map(
MappingInfo::new(10, GuestAddress(1000), 10, Protection::read_write()).unwrap(),
)
.unwrap();
assert_eq!(
mapper
.add_map(
MappingInfo::new(14, GuestAddress(1000), 1, Protection::read_write()).unwrap()
)
.unwrap(),
AddMapResult::OverlapFailure
);
assert_eq!(
mapper
.add_map(
MappingInfo::new(0, GuestAddress(1000), 12, Protection::read_write()).unwrap()
)
.unwrap(),
AddMapResult::OverlapFailure
);
assert_eq!(
mapper
.add_map(
MappingInfo::new(16, GuestAddress(1000), 6, Protection::read_write()).unwrap()
)
.unwrap(),
AddMapResult::OverlapFailure
);
assert_eq!(
mapper
.add_map(
MappingInfo::new(5, GuestAddress(1000), 20, Protection::read_write()).unwrap()
)
.unwrap(),
AddMapResult::OverlapFailure
);
}
#[test]
// This test is taken from the virtio_iommu spec with translate() calls added
fn test_map_unmap() {
// #1
{
let mut mapper = BasicMemoryMapper::new(u64::MAX);
mapper.remove_map(0, 4).unwrap();
}
// #2
{
let mut mapper = BasicMemoryMapper::new(u64::MAX);
mapper
.add_map(
MappingInfo::new(0, GuestAddress(1000), 9, Protection::read_write()).unwrap(),
)
.unwrap();
assert_eq!(
mapper.translate(0, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(1000),
len: 1,
prot: Protection::read_write()
}
);
assert_eq!(
mapper.translate(8, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(1008),
len: 1,
prot: Protection::read_write()
}
);
mapper.translate(9, 1).unwrap_err();
mapper.remove_map(0, 9).unwrap();
mapper.translate(0, 1).unwrap_err();
}
// #3
{
let mut mapper = BasicMemoryMapper::new(u64::MAX);
mapper
.add_map(
MappingInfo::new(0, GuestAddress(1000), 4, Protection::read_write()).unwrap(),
)
.unwrap();
mapper
.add_map(
MappingInfo::new(5, GuestAddress(50), 4, Protection::read_write()).unwrap(),
)
.unwrap();
assert_eq!(
mapper.translate(0, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(1000),
len: 1,
prot: Protection::read_write()
}
);
assert_eq!(
mapper.translate(6, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(51),
len: 1,
prot: Protection::read_write()
}
);
mapper.remove_map(0, 9).unwrap();
mapper.translate(0, 1).unwrap_err();
mapper.translate(6, 1).unwrap_err();
}
// #4
{
let mut mapper = BasicMemoryMapper::new(u64::MAX);
mapper
.add_map(
MappingInfo::new(0, GuestAddress(1000), 9, Protection::read_write()).unwrap(),
)
.unwrap();
assert!(!mapper.remove_map(0, 4).unwrap());
assert_eq!(
mapper.translate(5, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(1005),
len: 1,
prot: Protection::read_write()
}
);
}
// #5
{
let mut mapper = BasicMemoryMapper::new(u64::MAX);
mapper
.add_map(
MappingInfo::new(0, GuestAddress(1000), 4, Protection::read_write()).unwrap(),
)
.unwrap();
mapper
.add_map(
MappingInfo::new(5, GuestAddress(50), 4, Protection::read_write()).unwrap(),
)
.unwrap();
assert_eq!(
mapper.translate(0, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(1000),
len: 1,
prot: Protection::read_write()
}
);
assert_eq!(
mapper.translate(5, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(50),
len: 1,
prot: Protection::read_write()
}
);
mapper.remove_map(0, 4).unwrap();
mapper.translate(0, 1).unwrap_err();
mapper.translate(4, 1).unwrap_err();
assert_eq!(
mapper.translate(5, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(50),
len: 1,
prot: Protection::read_write()
}
);
}
// #6
{
let mut mapper = BasicMemoryMapper::new(u64::MAX);
mapper
.add_map(
MappingInfo::new(0, GuestAddress(1000), 4, Protection::read_write()).unwrap(),
)
.unwrap();
assert_eq!(
mapper.translate(0, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(1000),
len: 1,
prot: Protection::read_write()
}
);
mapper.translate(9, 1).unwrap_err();
mapper.remove_map(0, 9).unwrap();
mapper.translate(0, 1).unwrap_err();
mapper.translate(9, 1).unwrap_err();
}
// #7
{
let mut mapper = BasicMemoryMapper::new(u64::MAX);
mapper
.add_map(MappingInfo::new(0, GuestAddress(1000), 4, Protection::read()).unwrap())
.unwrap();
mapper
.add_map(
MappingInfo::new(10, GuestAddress(50), 4, Protection::read_write()).unwrap(),
)
.unwrap();
assert_eq!(
mapper.translate(0, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(1000),
len: 1,
prot: Protection::read()
}
);
assert_eq!(
mapper.translate(3, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(1003),
len: 1,
prot: Protection::read()
}
);
mapper.translate(4, 1).unwrap_err();
assert_eq!(
mapper.translate(10, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(50),
len: 1,
prot: Protection::read_write()
}
);
assert_eq!(
mapper.translate(13, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(53),
len: 1,
prot: Protection::read_write()
}
);
mapper.remove_map(0, 14).unwrap();
mapper.translate(0, 1).unwrap_err();
mapper.translate(3, 1).unwrap_err();
mapper.translate(4, 1).unwrap_err();
mapper.translate(10, 1).unwrap_err();
mapper.translate(13, 1).unwrap_err();
}
}
#[test]
fn test_remove_map() {
let mut mapper = BasicMemoryMapper::new(u64::MAX);
mapper
.add_map(MappingInfo::new(1, GuestAddress(1000), 4, Protection::read()).unwrap())
.unwrap();
mapper
.add_map(MappingInfo::new(5, GuestAddress(50), 4, Protection::read_write()).unwrap())
.unwrap();
mapper
.add_map(MappingInfo::new(9, GuestAddress(50), 4, Protection::read_write()).unwrap())
.unwrap();
assert_eq!(mapper.len(), 3);
assert!(!mapper.remove_map(0, 6).unwrap());
assert_eq!(mapper.len(), 3);
assert!(!mapper.remove_map(1, 5).unwrap());
assert_eq!(mapper.len(), 3);
assert!(!mapper.remove_map(1, 9).unwrap());
assert_eq!(mapper.len(), 3);
assert!(!mapper.remove_map(6, 4).unwrap());
assert_eq!(mapper.len(), 3);
assert!(!mapper.remove_map(6, 14).unwrap());
assert_eq!(mapper.len(), 3);
mapper.remove_map(5, 4).unwrap();
assert_eq!(mapper.len(), 2);
assert!(!mapper.remove_map(1, 9).unwrap());
assert_eq!(mapper.len(), 2);
mapper.remove_map(0, 15).unwrap();
assert_eq!(mapper.len(), 0);
}
fn assert_vec_eq<T: std::cmp::PartialEq + Debug>(a: Vec<T>, b: Vec<T>) {
assert_eq!(a.len(), b.len());
for (x, y) in a.into_iter().zip(b.into_iter()) {
assert_eq!(x, y);
}
}
#[test]
fn test_translate_len() {
let mut mapper = BasicMemoryMapper::new(u64::MAX);
// [1, 5) -> [1000, 1004)
mapper
.add_map(MappingInfo::new(1, GuestAddress(1000), 4, Protection::read()).unwrap())
.unwrap();
mapper.translate(1, 0).unwrap_err();
assert_eq!(
mapper.translate(1, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(1000),
len: 1,
prot: Protection::read()
}
);
assert_eq!(
mapper.translate(1, 2).unwrap()[0],
MemRegion {
gpa: GuestAddress(1000),
len: 2,
prot: Protection::read()
}
);
assert_eq!(
mapper.translate(1, 3).unwrap()[0],
MemRegion {
gpa: GuestAddress(1000),
len: 3,
prot: Protection::read()
}
);
assert_eq!(
mapper.translate(2, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(1001),
len: 1,
prot: Protection::read()
}
);
assert_eq!(
mapper.translate(2, 2).unwrap()[0],
MemRegion {
gpa: GuestAddress(1001),
len: 2,
prot: Protection::read()
}
);
mapper.translate(1, 5).unwrap_err();
// [1, 9) -> [1000, 1008)
mapper
.add_map(MappingInfo::new(5, GuestAddress(1004), 4, Protection::read()).unwrap())
.unwrap();
// Spanned across 2 maps
assert_eq!(
mapper.translate(2, 5).unwrap()[0],
MemRegion {
gpa: GuestAddress(1001),
len: 5,
prot: Protection::read()
}
);
assert_eq!(
mapper.translate(2, 6).unwrap()[0],
MemRegion {
gpa: GuestAddress(1001),
len: 6,
prot: Protection::read()
}
);
assert_eq!(
mapper.translate(2, 7).unwrap()[0],
MemRegion {
gpa: GuestAddress(1001),
len: 7,
prot: Protection::read()
}
);
mapper.translate(2, 8).unwrap_err();
mapper.translate(3, 10).unwrap_err();
// [1, 9) -> [1000, 1008), [11, 17) -> [1010, 1016)
mapper
.add_map(MappingInfo::new(11, GuestAddress(1010), 6, Protection::read()).unwrap())
.unwrap();
// Discontiguous iova
mapper.translate(3, 10).unwrap_err();
// [1, 17) -> [1000, 1016)
mapper
.add_map(MappingInfo::new(9, GuestAddress(1008), 2, Protection::read()).unwrap())
.unwrap();
// Spanned across 4 maps
assert_eq!(
mapper.translate(3, 10).unwrap()[0],
MemRegion {
gpa: GuestAddress(1002),
len: 10,
prot: Protection::read()
}
);
assert_eq!(
mapper.translate(1, 16).unwrap()[0],
MemRegion {
gpa: GuestAddress(1000),
len: 16,
prot: Protection::read()
}
);
mapper.translate(1, 17).unwrap_err();
mapper.translate(0, 16).unwrap_err();
// [0, 1) -> [5, 6), [1, 17) -> [1000, 1016)
mapper
.add_map(MappingInfo::new(0, GuestAddress(5), 1, Protection::read()).unwrap())
.unwrap();
assert_eq!(
mapper.translate(0, 1).unwrap()[0],
MemRegion {
gpa: GuestAddress(5),
len: 1,
prot: Protection::read()
}
);
// Discontiguous gpa
assert_vec_eq(
mapper.translate(0, 2).unwrap(),
vec![
MemRegion {
gpa: GuestAddress(5),
len: 1,
prot: Protection::read(),
},
MemRegion {
gpa: GuestAddress(1000),
len: 1,
prot: Protection::read(),
},
],
);
assert_vec_eq(
mapper.translate(0, 16).unwrap(),
vec![
MemRegion {
gpa: GuestAddress(5),
len: 1,
prot: Protection::read(),
},
MemRegion {
gpa: GuestAddress(1000),
len: 15,
prot: Protection::read(),
},
],
);
// [0, 1) -> [5, 6), [1, 17) -> [1000, 1016), [17, 18) -> [1016, 1017) <RW>
mapper
.add_map(MappingInfo::new(17, GuestAddress(1016), 2, Protection::read_write()).unwrap())
.unwrap();
// Contiguous iova and gpa, but different perm
assert_vec_eq(
mapper.translate(1, 17).unwrap(),
vec![
MemRegion {
gpa: GuestAddress(1000),
len: 16,
prot: Protection::read(),
},
MemRegion {
gpa: GuestAddress(1016),
len: 1,
prot: Protection::read_write(),
},
],
);
// Contiguous iova and gpa, but different perm
assert_vec_eq(
mapper.translate(2, 16).unwrap(),
vec![
MemRegion {
gpa: GuestAddress(1001),
len: 15,
prot: Protection::read(),
},
MemRegion {
gpa: GuestAddress(1016),
len: 1,
prot: Protection::read_write(),
},
],
);
assert_vec_eq(
mapper.translate(2, 17).unwrap(),
vec![
MemRegion {
gpa: GuestAddress(1001),
len: 15,
prot: Protection::read(),
},
MemRegion {
gpa: GuestAddress(1016),
len: 2,
prot: Protection::read_write(),
},
],
);
mapper.translate(2, 500).unwrap_err();
mapper.translate(500, 5).unwrap_err();
}
}