machina_virtio_device/mem.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use virtio_device::mem::{DeviceRange, DriverMem, DriverRange};
use virtio_device::queue::QueueMemory;
use virtio_device::ring;
/// Provide access to guest memory.
///
/// Takes a [`zx::Vmo`] that represents guest memory and provides an implementation of
/// [`DriverMem`].
// # Safety
// If mapping is not none then it must be the address of mapping in the root vmar, such that it may
// be unmapped according to drop. Once a mapping is set to Some it must not be changed until the
// object is dropped.
pub struct GuestMem {
mapping: Option<(usize, usize)>,
}
impl Drop for GuestMem {
fn drop(&mut self) {
if let Some((base, len)) = self.mapping.take() {
// If a mapping was set we know it is from a vmar::map and can unmap it.
unsafe { fuchsia_runtime::vmar_root_self().unmap(base, len) }.unwrap();
}
}
}
impl GuestMem {
/// Construct a new, empty, [`GuestMem`].
///
/// Before a [`GuestMem`] can be used to perform [`translations`](DriverMem) it needs to have
/// its mappings populated through [`give_vmo`](#give_vmo).
pub fn new() -> GuestMem {
// Initially no mapping.
GuestMem { mapping: None }
}
/// Initialize a [`GuestMem`] with a [`zx::Vmo`]
///
/// This takes a reference to the [`zx::Vmo`] provided in the `StartInfo` message to devices.
pub fn set_vmo(&mut self, vmo: &zx::Vmo) -> Result<(), zx::Status> {
if self.mapping.is_some() {
return Err(zx::Status::BAD_STATE);
}
let vmo_size = vmo.get_size()? as usize;
let addr = fuchsia_runtime::vmar_root_self().map(
0,
vmo,
0,
vmo_size,
zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE,
)?;
self.mapping = Some((addr, vmo_size));
Ok(())
}
pub fn get_mapping(&self) -> Option<(usize, usize)> {
self.mapping
}
}
impl DriverMem for GuestMem {
fn translate<'a>(&'a self, driver: DriverRange) -> Option<DeviceRange<'a>> {
self.mapping.as_ref().and_then(|&(base, len)| {
// If we found a mapping then we know this DeviceRange construction is safe since
// - The range is mapped to a valid VMO and has not been unmapped, as that can only on
// drop.
// - We mapped this range and therefore know that it cannot alias any other rust objects
// - We will not unmap this range until drop, hence the borrow and lifetime on this
// function ensure it remains valid.
let mem = unsafe { DeviceRange::new(base..(base + len)) };
// From all the memory attempt to split out the range requested.
Some(mem.split_at(driver.0.start)?.1.split_at(driver.len())?.0)
})
}
}
/// Construct a new [`GuestMem`] containing the given [`zx::Vmo`]
///
/// See [`GuestMem::give_vmo`] for details on how the [`zx::Vmo`] handle is used.
pub fn guest_mem_from_vmo(vmo: &zx::Vmo) -> Result<GuestMem, zx::Status> {
let mut mem = GuestMem::new();
mem.set_vmo(vmo)?;
Ok(mem)
}
/// Helper for constructing a [`QueueMemory`]
///
/// Takes a queue description, similar to the form of a [`ConfigureQueue`]
/// (fidl_fuchsia_virtualization_hardware::VirtioDeviceRequest::ConfigureQueue), and attempts to
/// [`translate`] each of the ranges to build a [`QueueMemory`].
pub fn translate_queue<'a, M: DriverMem>(
mem: &'a M,
size: u16,
desc: usize,
avail: usize,
used: usize,
) -> Option<QueueMemory<'a>> {
let desc_len = std::mem::size_of::<ring::Desc>() * size as usize;
let avail_len = ring::Driver::avail_len_for_queue_size(size as u16);
let used_len = ring::Device::used_len_for_queue_size(size as u16);
let desc = mem.translate((desc..desc.checked_add(desc_len)?).into())?;
let avail = mem.translate((avail..avail.checked_add(avail_len)?).into())?;
let used = mem.translate((used..used.checked_add(used_len)?).into())?;
Some(QueueMemory { desc, avail, used })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_translate() -> Result<(), anyhow::Error> {
let size = zx::system_get_page_size() as usize * 1024;
let vmo = zx::Vmo::create(size as u64)?;
let koid = vmo.info()?.koid;
let base;
let get_maps = || {
let process = fuchsia_runtime::process_self();
process.info_maps_vec()
};
{
let mem = guest_mem_from_vmo(&vmo)?;
// Translate an address so we can get the base.
base = mem.translate(DriverRange(0..1)).unwrap().get().start;
// Validate that there is a mapping of the right size.
assert!(get_maps()?
.into_iter()
.find(|info| match info.details {
zx::MapDetails::Mapping(map) => {
map.vmo_koid == koid && info.base == base && info.size >= size
}
_ => false,
})
.is_some());
// Check that we can translate some valid ranges.
assert_eq!(mem.translate(DriverRange(0..64)).unwrap().get(), base..(base + 64));
assert_eq!(
mem.translate(DriverRange(size - 64..size)).unwrap().get(),
(base + size - 64)..(base + size)
);
assert_eq!(mem.translate(DriverRange(49..80)).unwrap().get(), (base + 49)..(base + 80));
assert_eq!(mem.translate(DriverRange(0..size)).unwrap().get(), base..(base + size));
// Make sure no invalid ranges translate.
assert_eq!(mem.translate(DriverRange(0..(size + 1))), None);
assert_eq!(mem.translate(DriverRange((size - 64)..(size + 1))), None);
assert_eq!(mem.translate(DriverRange((size + 100)..(size + 200))), None);
}
// validate that the mapping has gone away and that our VMO is not mapped anywhere at all.
assert!(get_maps()?
.into_iter()
.filter_map(|x| x.details.as_mapping())
.find(|map| map.vmo_koid == koid)
.is_none());
Ok(())
}
}