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