Skip to main content

mmio/
vmo.rs

1// Copyright 2025 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
5//! MMIO regions backed by Fuchsia Virtual Memory Objects.
6
7use crate::memory::Memory;
8use crate::region::{MmioRegion, UnsafeMmio};
9use core::ptr::{NonNull, with_exposed_provenance_mut};
10use zx::{CachePolicy, VmarFlags, Vmo};
11use zx_status::Status;
12
13/// An active mapping of a Vmo in the root Vmar.
14///
15/// The mapped memory is kept mapped while the VmoMapping object is alive.
16pub struct VmoMapping {
17    map_addr: usize,
18    map_size: usize,
19}
20
21/// A Memory region backed by Vmo mapped memory.
22pub type VmoMemory = Memory<VmoMapping>;
23
24impl VmoMapping {
25    /// Map the specified memory range of the given Vmo in the root Vmar for this process and
26    /// return an object that maintains the mapping for its lifetime.
27    ///
28    /// Errors:
29    /// - [Status::OUT_OF_RANGE]: if `size > isize::MAX`.
30    /// - [Status::OUT_OF_RANGE]: if the requested region falls outside of the vmo's memory.
31    /// - An error returned by [zx::Vmar::map]: if the mapping fails.
32    pub fn map(offset: usize, size: usize, vmo: Vmo) -> Result<MmioRegion<VmoMemory>, Status> {
33        Self::map_with_cache_policy(offset, size, vmo, CachePolicy::UnCachedDevice)
34    }
35
36    /// Like [`VmoMapping::map`] but specifying a `cache_policy`.
37    ///
38    /// *NOTE*: Code targeting a real MMIO mapped region must *always* use
39    /// [`CachePolicy::UnCachedDevice`] and production code should prefer to use
40    /// [`VmoMapping::map`]. This is exposed *exclusively* to allow test code to
41    /// pick a different cache policy when `vmo` is not in fact backed by an
42    /// actual MMIO VMO.
43    pub fn map_with_cache_policy(
44        offset: usize,
45        size: usize,
46        vmo: Vmo,
47        cache_policy: CachePolicy,
48    ) -> Result<MmioRegion<VmoMemory>, Status> {
49        if size > isize::MAX as usize {
50            return Err(Status::OUT_OF_RANGE);
51        }
52
53        let page_size = zx::system_get_page_size() as usize;
54        // Determine how far the offset is into its containing page.
55        let page_offset = offset % page_size;
56
57        // Round the offset down to a page boundary.
58        let offset = (offset - page_offset) as u64;
59
60        // Round the mapped size up so it covers complete pages.
61        let map_size = (size + page_offset).next_multiple_of(page_size);
62
63        let info = vmo.info()?;
64        if info.cache_policy() != cache_policy {
65            vmo.set_cache_policy(cache_policy)?;
66        }
67        if offset.saturating_add(map_size as u64) > info.size_bytes {
68            return Err(Status::OUT_OF_RANGE);
69        }
70
71        let root_self = fuchsia_runtime::vmar_root_self();
72        let map_addr = root_self.map(
73            0,
74            &vmo,
75            offset,
76            map_size,
77            VmarFlags::PERM_READ | VmarFlags::PERM_WRITE | VmarFlags::MAP_RANGE,
78        )?;
79        let base_ptr =
80            NonNull::<u8>::new(with_exposed_provenance_mut(map_addr + page_offset)).unwrap();
81        let len = size;
82
83        let mapping = Self { map_addr, map_size };
84
85        // Safety:
86        // - the range from base_ptr to base_ptr + len is within the range exclusively owned by the
87        // mapping.
88        // - the mapping is used as the claim - this keeps the memory valid for the lifetime of the
89        // claim.
90        let memory = unsafe { Memory::new_unchecked(mapping, base_ptr, len) };
91        Ok(MmioRegion::new(memory))
92    }
93}
94
95/// Unmaps the memory.
96impl Drop for VmoMapping {
97    fn drop(&mut self) {
98        let root_self = fuchsia_runtime::vmar_root_self();
99        // Safety:
100        // - This object only exposes the mapped memory range through the `memory_range` function
101        // whose safety requirements require the caller to only use this memory while the
102        // VmoMapping is alive.
103        let _ = unsafe { root_self.unmap(self.map_addr, self.map_size) };
104    }
105}
106
107/// A type capable of mapping a [`zx::Vmo`] and returning an [`MmioRegion`] from
108/// it.
109pub trait VmoMapper: UnsafeMmio + Sized {
110    /// Map the specified memory range of the given Vmo in the root Vmar for
111    /// this process and return an object that maintains the mapping for its
112    /// lifetime.
113    fn map(offset: usize, size: usize, vmo: Vmo) -> Result<MmioRegion<Self>, Status>;
114}
115
116impl VmoMapper for VmoMemory {
117    fn map(offset: usize, size: usize, vmo: Vmo) -> Result<MmioRegion<Self>, Status> {
118        VmoMapping::map(offset, size, vmo)
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::Mmio;
126    use zx::Rights;
127
128    #[test]
129    fn test_mapping() {
130        const TEST_LEN: usize = 256;
131        let vmo = Vmo::create(TEST_LEN as u64).unwrap();
132
133        let mut mmio = VmoMapping::map(0, TEST_LEN, vmo).unwrap().into_split_send();
134
135        for i in 0..TEST_LEN {
136            assert_eq!(mmio.try_store8(i, i as u8), Ok(()));
137        }
138
139        for i in 0..TEST_LEN {
140            assert_eq!(mmio.try_load8(i), Ok(i as u8));
141        }
142    }
143
144    #[test]
145    fn test_page_offset() {
146        const VMO_SIZE: u64 = 1024;
147        let vmo = Vmo::create(VMO_SIZE).unwrap();
148
149        // Write the offset of every 16 bit location into that location.
150        for i in (0..VMO_SIZE).step_by(2) {
151            let addr = i as u16;
152            vmo.write(&addr.to_le_bytes(), i).unwrap();
153        }
154
155        const TEST_OFFSET: usize = 128;
156        const TEST_LEN: usize = 256;
157        let mmio = VmoMapping::map(TEST_OFFSET, TEST_LEN, vmo).unwrap();
158
159        for i in (0..TEST_LEN).step_by(2) {
160            assert_eq!(mmio.try_load16(i), Ok((i + TEST_OFFSET) as u16));
161        }
162    }
163
164    #[test]
165    fn test_mapping_unmaps() {
166        const TEST_LEN: usize = 256;
167        let vmo = Vmo::create(TEST_LEN as u64).unwrap();
168        let vmo_read_handle: Vmo = vmo.duplicate_handle(Rights::READ).unwrap();
169
170        {
171            let _mapping = VmoMapping::map(0, TEST_LEN, vmo).unwrap();
172            // The vmo should be mapped exactly once.
173            assert_eq!(vmo_read_handle.info().unwrap().num_mappings, 1);
174        }
175
176        // The mapping should have been unmapped by now.
177        assert_eq!(vmo_read_handle.info().unwrap().num_mappings, 0);
178    }
179}