Skip to main content

memory_mapped_vmo/
memory_mapped_vmo.rs

1// Copyright 2023 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 std::mem::{ManuallyDrop, align_of, size_of};
6use std::ops::Deref;
7use zerocopy::FromBytes;
8
9use crate::Error;
10
11/// Memory-maps a VMO and mediates access to its memory.
12#[repr(C)]
13pub struct MemoryMappedVmo<const WRITABLE: bool> {
14    map_addr: usize,
15    vmo_size: usize,
16}
17
18impl<const WRITABLE: bool> MemoryMappedVmo<WRITABLE> {
19    /// Maps a VMO into memory (read-only if WRITABLE is false, read-write if WRITABLE is true).
20    ///
21    /// # Safety
22    /// The caller must either guarantee that the VMO is not modified by others while the returned
23    /// instance is alive or that accesses are synchronized in application-specific ways.
24    pub unsafe fn new(vmo: &zx::Vmo) -> Result<Self, zx::Status> {
25        let vmo_size = vmo.get_content_size()? as usize;
26
27        let mut flags = zx::VmarFlags::PERM_READ
28            | zx::VmarFlags::ALLOW_FAULTS
29            | zx::VmarFlags::REQUIRE_NON_RESIZABLE;
30        if WRITABLE {
31            flags |= zx::VmarFlags::PERM_WRITE;
32        }
33
34        let map_addr = fuchsia_runtime::vmar_root_self().map(0, vmo, 0, vmo_size, flags)?;
35        Ok(MemoryMappedVmo { map_addr, vmo_size })
36    }
37
38    /// Creates a MemoryMappedVmo pointing to the *same* VMAR mapping.
39    ///
40    /// # Safety
41    /// The same requirements as new() still apply. On top of that, the caller now becomes
42    /// responsible for ensuring that Drop is only executed once and after all the MemoryMappedVmo
43    /// referencing the same mapping have gone out of scope.
44    pub(crate) unsafe fn create_alias_with_manually_drop(&self) -> ManuallyDrop<Self> {
45        ManuallyDrop::new(MemoryMappedVmo { map_addr: self.map_addr, vmo_size: self.vmo_size })
46    }
47
48    /// Returns the number of usable bytes in the VMO (i.e. its ZX_PROP_VMO_CONTENT_SIZE property,
49    /// which is not rounded to the page size).
50    pub fn vmo_size(&self) -> usize {
51        self.vmo_size
52    }
53
54    /// Given an element type, a base offset within the VMO and a number of elements, verifies that
55    /// the offset is suitably aligned and that the range of the elements fits in the VMO bounds. If
56    /// both conditions are satisfied, return a const pointer to its first element.
57    pub(crate) fn validate_and_get_ptr<T>(
58        &self,
59        byte_offset: usize,
60        num_elements: usize,
61    ) -> Result<*const T, Error> {
62        if byte_offset % align_of::<T>() == 0 {
63            if let Some(num_bytes) = size_of::<T>().checked_mul(num_elements) {
64                if let Some(end) = byte_offset.checked_add(num_bytes) {
65                    if end <= self.vmo_size {
66                        return Ok((self.map_addr + byte_offset) as *const T);
67                    }
68                }
69            }
70        }
71
72        Err(Error::InvalidInput)
73    }
74
75    /// Returns a reference to a slice of elements in the VMO.
76    ///
77    /// This method validates the alignment and the bounds against the VMO size.
78    pub fn get_slice<'a, T: FromBytes>(
79        &'a self,
80        byte_offset: usize,
81        num_elements: usize,
82    ) -> Result<&'a [T], Error> {
83        let ptr = self.validate_and_get_ptr(byte_offset, num_elements)?;
84        unsafe { Ok(std::slice::from_raw_parts(ptr, num_elements)) }
85    }
86
87    /// Returns a reference to an element in the VMO.
88    ///
89    /// This method validates the alignment and the bounds against the VMO size.
90    pub fn get_object<'a, T: FromBytes>(&'a self, byte_offset: usize) -> Result<&'a T, Error> {
91        let ptr = self.validate_and_get_ptr(byte_offset, 1)?;
92        unsafe { Ok(&*ptr) }
93    }
94}
95
96impl MemoryMappedVmo<true> {
97    /// Like validate_and_get_ptr, but returns a mut pointer.
98    pub(crate) fn validate_and_get_mut_ptr<T>(
99        &mut self,
100        byte_offset: usize,
101        num_elements: usize,
102    ) -> Result<*mut T, Error> {
103        Ok(self.validate_and_get_ptr::<T>(byte_offset, num_elements)? as *mut T)
104    }
105
106    /// Returns a mutable reference to a slice of elements in the VMO.
107    ///
108    /// This method validates the alignment and the bounds against the VMO size.
109    pub fn get_slice_mut<'a, T: FromBytes>(
110        &'a mut self,
111        byte_offset: usize,
112        num_elements: usize,
113    ) -> Result<&'a mut [T], Error> {
114        let ptr = self.validate_and_get_mut_ptr(byte_offset, num_elements)?;
115        unsafe { Ok(std::slice::from_raw_parts_mut(ptr, num_elements)) }
116    }
117
118    /// Returns a mutable reference to an element in the VMO.
119    ///
120    /// This method validates the alignment and the bounds against the VMO size.
121    pub fn get_object_mut<'a, T: FromBytes>(
122        &'a mut self,
123        byte_offset: usize,
124    ) -> Result<&'a mut T, Error> {
125        let ptr = self.validate_and_get_mut_ptr(byte_offset, 1)?;
126        unsafe { Ok(&mut *ptr) }
127    }
128
129    /// Downgrades a read-write mapping handle into a read-only mapping handle without unmapping.
130    pub fn into_readonly(self) -> MemoryMappedVmo<false> {
131        let this = ManuallyDrop::new(self);
132        MemoryMappedVmo { map_addr: this.map_addr, vmo_size: this.vmo_size }
133    }
134}
135
136impl Deref for MemoryMappedVmo<true> {
137    type Target = MemoryMappedVmo<false>;
138
139    fn deref(&self) -> &Self::Target {
140        // SAFETY: MemoryMappedVmo<true> and MemoryMappedVmo<false> have identical #[repr(C)]
141        // layout, and a read-write mapping is always valid for read-only operations.
142        unsafe { &*(self as *const MemoryMappedVmo<true> as *const MemoryMappedVmo<false>) }
143    }
144}
145
146impl<const WRITABLE: bool> Drop for MemoryMappedVmo<WRITABLE> {
147    fn drop(&mut self) {
148        // SAFETY: We owned the mapping.
149        unsafe {
150            fuchsia_runtime::vmar_root_self()
151                .unmap(self.map_addr, self.vmo_size)
152                .expect("failed to unmap MemoryMappedVmo");
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use crate::{Error, ReadOnlyMemoryMappedVmo, ReadWriteMemoryMappedVmo};
160    use assert_matches::assert_matches;
161    use std::mem::size_of;
162
163    // Test data used by some of the following tests.
164    const TEST_DATA: [u64; 4] = [11, 22, 33, 44];
165    const TEST_DATA_SIZE: usize = size_of::<u64>() * TEST_DATA.len();
166
167    #[test]
168    fn test_vmo_size() {
169        let vmo = zx::Vmo::create(TEST_DATA_SIZE as u64).unwrap();
170        let m = unsafe { ReadWriteMemoryMappedVmo::new(&vmo) }.unwrap();
171
172        assert_eq!(m.vmo_size(), TEST_DATA_SIZE);
173    }
174
175    #[test]
176    fn test_write_objects_read_slice() {
177        let vmo = zx::Vmo::create(TEST_DATA_SIZE as u64).unwrap();
178
179        // Fill VMO with test data as individual objects.
180        {
181            let mut m = unsafe { ReadWriteMemoryMappedVmo::new(&vmo) }.unwrap();
182            for (i, val) in TEST_DATA.iter().enumerate() {
183                *m.get_object_mut(size_of::<u64>() * i).unwrap() = *val;
184            }
185        }
186
187        // Verify that we can read them back correctly as a slice.
188        {
189            let m = unsafe { ReadOnlyMemoryMappedVmo::new(&vmo) }.unwrap();
190            assert_eq!(*m.get_slice::<u64>(0, 4).unwrap(), TEST_DATA);
191        }
192    }
193
194    #[test]
195    fn test_write_slice_read_objects() {
196        let vmo = zx::Vmo::create(TEST_DATA_SIZE as u64).unwrap();
197
198        // Fill VMO with test data as a slice.
199        {
200            let mut m = unsafe { ReadWriteMemoryMappedVmo::new(&vmo) }.unwrap();
201            m.get_slice_mut(0, 4).unwrap().copy_from_slice(&TEST_DATA);
202        }
203
204        // Verify that we can read it back correctly as individual objects.
205        {
206            let m = unsafe { ReadOnlyMemoryMappedVmo::new(&vmo) }.unwrap();
207            for (i, expected_val) in TEST_DATA.iter().enumerate() {
208                let actual_val: &u64 = m.get_object(size_of::<u64>() * i).unwrap();
209                assert_eq!(*actual_val, *expected_val, "value mismatch at i={}", i);
210            }
211        }
212    }
213
214    #[test]
215    fn test_write_slice_read_subslices() {
216        const COUNT: usize = 4;
217        let vmo = zx::Vmo::create((size_of::<u64>() * COUNT) as u64).unwrap();
218
219        // Fill VMO with test data.
220        let mut m = unsafe { ReadWriteMemoryMappedVmo::new(&vmo) }.unwrap();
221        m.get_slice_mut::<u64>(0, COUNT).unwrap().copy_from_slice(&[11, 22, 33, 44]);
222
223        // Verify that we can read subslices correctly.
224        const SECOND_ELEM_BYTE_OFFSET: usize = size_of::<u64>();
225        assert_eq!(*m.get_slice::<u64>(SECOND_ELEM_BYTE_OFFSET, 0).unwrap(), []);
226        assert_eq!(*m.get_slice::<u64>(SECOND_ELEM_BYTE_OFFSET, 1).unwrap(), [22]);
227        assert_eq!(*m.get_slice::<u64>(SECOND_ELEM_BYTE_OFFSET, 2).unwrap(), [22, 33]);
228        assert_eq!(*m.get_slice::<u64>(SECOND_ELEM_BYTE_OFFSET, 3).unwrap(), [22, 33, 44]);
229    }
230
231    #[test]
232    fn test_uninitialized_is_zero() {
233        const COUNT: usize = 4;
234        let vmo = zx::Vmo::create((size_of::<u64>() * COUNT) as u64).unwrap();
235        let m = unsafe { ReadOnlyMemoryMappedVmo::new(&vmo) }.unwrap();
236
237        // Verify that the value of uninitialized data is zero.
238        assert_eq!(*m.get_slice::<u64>(0, COUNT).unwrap(), [0; COUNT]);
239    }
240
241    #[test]
242    fn test_range_errors() {
243        const COUNT: usize = 4;
244        let vmo = zx::Vmo::create((size_of::<u64>() * COUNT) as u64).unwrap();
245        let m = unsafe { ReadOnlyMemoryMappedVmo::new(&vmo) }.unwrap();
246
247        // Reading at a misaligned offset should fail.
248        const MISALIGNED_OFFSET: usize = size_of::<u64>() - 1;
249        assert_matches!(m.get_object::<u64>(MISALIGNED_OFFSET), Err(Error::InvalidInput));
250
251        // Reading an out-of-bounds range should fail.
252        const SECOND_ELEM_BYTE_OFFSET: usize = size_of::<u64>();
253        assert_matches!(
254            m.get_slice::<u64>(SECOND_ELEM_BYTE_OFFSET, COUNT),
255            Err(Error::InvalidInput)
256        );
257    }
258
259    #[test]
260    fn test_into_readonly() {
261        let vmo = zx::Vmo::create(TEST_DATA_SIZE as u64).unwrap();
262        let mut m = unsafe { ReadWriteMemoryMappedVmo::new(&vmo) }.unwrap();
263        m.get_slice_mut(0, 4).unwrap().copy_from_slice(&TEST_DATA);
264
265        let m: ReadOnlyMemoryMappedVmo = m.into_readonly();
266        assert_eq!(*m.get_slice::<u64>(0, 4).unwrap(), TEST_DATA);
267    }
268}