machina_virtio_device/
mem.rs

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.
4
5use virtio_device::mem::{DeviceRange, DriverMem, DriverRange};
6use virtio_device::queue::QueueMemory;
7use virtio_device::ring;
8
9/// 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}
20
21impl Drop for GuestMem {
22    fn drop(&mut self) {
23        if 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.
25            unsafe { fuchsia_runtime::vmar_root_self().unmap(base, len) }.unwrap();
26        }
27    }
28}
29
30impl 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).
35    pub fn new() -> GuestMem {
36        // Initially no mapping.
37        GuestMem { mapping: None }
38    }
39
40    /// Initialize a [`GuestMem`] with a [`zx::Vmo`]
41    ///
42    /// This takes a reference to the [`zx::Vmo`] provided in the `StartInfo` message to devices.
43    pub fn set_vmo(&mut self, vmo: &zx::Vmo) -> Result<(), zx::Status> {
44        if self.mapping.is_some() {
45            return Err(zx::Status::BAD_STATE);
46        }
47        let vmo_size = vmo.get_size()? as usize;
48        let addr = fuchsia_runtime::vmar_root_self().map(
49            0,
50            vmo,
51            0,
52            vmo_size,
53            zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE,
54        )?;
55        self.mapping = Some((addr, vmo_size));
56        Ok(())
57    }
58
59    pub fn get_mapping(&self) -> Option<(usize, usize)> {
60        self.mapping
61    }
62}
63
64impl DriverMem for GuestMem {
65    fn translate<'a>(&'a self, driver: DriverRange) -> Option<DeviceRange<'a>> {
66        self.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.
73            let mem = unsafe { DeviceRange::new(base..(base + len)) };
74            // From all the memory attempt to split out the range requested.
75            Some(mem.split_at(driver.0.start)?.1.split_at(driver.len())?.0)
76        })
77    }
78}
79
80/// 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> {
84    let mut mem = GuestMem::new();
85    mem.set_vmo(vmo)?;
86    Ok(mem)
87}
88
89/// 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>> {
101    let desc_len = std::mem::size_of::<ring::Desc>() * size as usize;
102    let avail_len = ring::Driver::avail_len_for_queue_size(size as u16);
103    let used_len = ring::Device::used_len_for_queue_size(size as u16);
104    let desc = mem.translate((desc..desc.checked_add(desc_len)?).into())?;
105    let avail = mem.translate((avail..avail.checked_add(avail_len)?).into())?;
106    let used = mem.translate((used..used.checked_add(used_len)?).into())?;
107    Some(QueueMemory { desc, avail, used })
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn test_translate() -> Result<(), anyhow::Error> {
116        let size = zx::system_get_page_size() as usize * 1024;
117        let vmo = zx::Vmo::create(size as u64)?;
118        let koid = vmo.info()?.koid;
119        let base;
120
121        let get_maps = || {
122            let process = fuchsia_runtime::process_self();
123            process.info_maps_vec()
124        };
125
126        {
127            let mem = guest_mem_from_vmo(&vmo)?;
128            // Translate an address so we can get the base.
129            base = mem.translate(DriverRange(0..1)).unwrap().get().start;
130            // Validate that there is a mapping of the right size.
131            assert!(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());
140
141            // Check that we can translate some valid ranges.
142            assert_eq!(mem.translate(DriverRange(0..64)).unwrap().get(), base..(base + 64));
143            assert_eq!(
144                mem.translate(DriverRange(size - 64..size)).unwrap().get(),
145                (base + size - 64)..(base + size)
146            );
147            assert_eq!(mem.translate(DriverRange(49..80)).unwrap().get(), (base + 49)..(base + 80));
148            assert_eq!(mem.translate(DriverRange(0..size)).unwrap().get(), base..(base + size));
149
150            // Make sure no invalid ranges translate.
151            assert_eq!(mem.translate(DriverRange(0..(size + 1))), None);
152            assert_eq!(mem.translate(DriverRange((size - 64)..(size + 1))), None);
153            assert_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.
156        assert!(get_maps()?
157            .into_iter()
158            .find(|x| x.details().as_mapping().map_or(false, |map| map.vmo_koid == koid))
159            .is_none());
160        Ok(())
161    }
162}