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(())
    }
}