Skip to main content

page_buf/
lib.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//! Buffers backed by VMOs mapped into the root VMAR.
6
7use std::mem::MaybeUninit;
8use std::ptr::NonNull;
9use std::sync::Arc;
10
11const DEFAULT_VMO_NAME: zx::Name = zx::Name::from_bytes_lossy(b"starnix_page_buf");
12
13/// A buffer backed by a VMO mapped into the root VMAR.
14///
15/// Optionally maps the buffer into a second VMAR as read-only to allow pinning memory in advance
16/// of support for partial VMAR profiles or a similar feature.
17#[derive(Debug)]
18pub struct PageBuf<T> {
19    vmo: zx::Vmo,
20    base: NonNull<MaybeUninit<u8>>,
21    mapped_len: usize,
22    extra_vmar_and_base: Option<(Arc<zx::Vmar>, usize)>,
23    _ty: std::marker::PhantomData<T>,
24}
25
26impl<T> PageBuf<T> {
27    /// Create a new `PageBuf` in the calling process' root VMAR.
28    pub fn new(capacity: usize) -> Result<Self, zx::Status> {
29        Self::new_internal(capacity, None)
30    }
31
32    /// Create a new `PageBuf` in the calling process' root VMAR and also map the VMO into the
33    /// provided "extra" VMAR. If the extra VMAR has a memory profile this will
34    pub fn new_with_extra_vmar(
35        capacity: usize,
36        extra_vmar: Arc<zx::Vmar>,
37    ) -> Result<Self, zx::Status> {
38        Self::new_internal(capacity, Some(extra_vmar))
39    }
40
41    fn new_internal(
42        capacity: usize,
43        extra_vmar: Option<Arc<zx::Vmar>>,
44    ) -> Result<Self, zx::Status> {
45        let capacity_bytes = capacity * std::mem::size_of::<T>();
46        let vmo = zx::Vmo::create(capacity_bytes as u64)?;
47
48        let mapped_len = capacity_bytes.next_multiple_of(zx::system_get_page_size() as usize);
49        let addr = fuchsia_runtime::vmar_root_self().map(
50            0,
51            &vmo,
52            0,
53            mapped_len,
54            zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE | zx::VmarFlags::ALLOW_FAULTS,
55        )?;
56        let base =
57            NonNull::new(std::ptr::with_exposed_provenance_mut::<MaybeUninit<u8>>(addr)).unwrap();
58
59        let extra_vmar_and_base = if let Some(extra_vmar) = extra_vmar {
60            let extra_base = extra_vmar.map(0, &vmo, 0, mapped_len, zx::VmarFlags::PERM_READ)?;
61            Some((extra_vmar, extra_base))
62        } else {
63            None
64        };
65
66        let this =
67            Self { vmo, base, mapped_len, extra_vmar_and_base, _ty: std::marker::PhantomData };
68        this.set_name(&DEFAULT_VMO_NAME);
69        Ok(this)
70    }
71
72    /// Set the name of the buffer's VMO.
73    pub fn set_name(&self, name: &zx::Name) {
74        self.vmo.set_name(name).expect("default vmo rights must include ability to set name");
75    }
76
77    pub fn len(&self) -> usize {
78        self.len_bytes() / std::mem::size_of::<T>()
79    }
80
81    pub fn len_bytes(&self) -> usize {
82        self.mapped_len
83    }
84
85    /// Return a mutable reference to the underlying memory.
86    pub fn as_mut(&mut self) -> &mut [MaybeUninit<T>] {
87        assert!(
88            std::mem::align_of::<T>() <= zx::system_get_page_size() as usize,
89            "can't handle types with alignment greater than a page yet"
90        );
91
92        // SAFETY: The base address is always valid to access as MaybeUninit as long as self is
93        // live. The mutable reference receiver of this method ensures that we have exclusive
94        // access to the underlying mapping.
95        let bytes = unsafe { std::slice::from_raw_parts_mut(self.base.as_ptr(), self.mapped_len) };
96        let num_elems = bytes.len() / std::mem::size_of::<T>();
97        let bytes_as_t = bytes.as_mut_ptr().cast::<MaybeUninit<T>>();
98
99        // SAFETY: The whole MaybeUninit<T> doesn't have any requirements on the state of the
100        // backing memory and `num_elems` will produce a slice within the bounds of `bytes`.
101        unsafe { std::slice::from_raw_parts_mut(bytes_as_t, num_elems) }
102    }
103}
104
105// SAFETY: PageBuf can be dropped from any thread if T is Send
106unsafe impl<T: Send> Send for PageBuf<T> {}
107// SAFETY: no interior mutability if T is Sync
108unsafe impl<T: Sync> Sync for PageBuf<T> {}
109
110impl<T> Drop for PageBuf<T> {
111    fn drop(&mut self) {
112        // SAFETY: the zircon mapping is "owned" by this PageBuf, no other references to it exist
113        unsafe {
114            fuchsia_runtime::vmar_root_self()
115                .unmap(self.base.addr().into(), self.mapped_len)
116                .unwrap();
117        }
118
119        if let Some((extra_vmar, extra_base)) = &self.extra_vmar_and_base {
120            // SAFETY: the extra vmar mapping is also "owned" by this BufMapping
121            unsafe {
122                extra_vmar.unmap(*extra_base, self.mapped_len).unwrap();
123            }
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use fuchsia_runtime::vmar_root_self;
132
133    #[track_caller]
134    fn fill_buf(buf: &mut PageBuf<[u8; 16]>) {
135        for slot in buf.as_mut() {
136            slot.write([1u8; 16]);
137        }
138    }
139
140    #[track_caller]
141    fn find_zx_mappings(
142        infos: &[zx::MapInfo],
143        addr_range: std::ops::Range<usize>,
144    ) -> Vec<(zx::Name, std::ops::Range<usize>, zx::MappingDetails)> {
145        let mut found = vec![];
146        for info in infos {
147            let info_range = info.base..info.base + info.size;
148            if addr_range.contains(&info_range.start) || addr_range.contains(&info_range.end) {
149                if let Some(mapping) = info.details().as_mapping() {
150                    found.push((info.name, info_range, mapping.clone()));
151                }
152            }
153        }
154
155        found.sort_by_key(|m| m.1.start);
156        found
157    }
158
159    #[fuchsia::test]
160    fn basic() {
161        let mut buf = PageBuf::<[u8; 16]>::new(100).unwrap();
162        assert!(buf.len() >= 100);
163        fill_buf(&mut buf);
164
165        let buf_base = buf.base.addr().get();
166        let buf_len = buf.mapped_len;
167        let maps = vmar_root_self().maps_vec().unwrap();
168        let desired_range = buf_base..buf_base + buf_len;
169        let (name, range, details) = &find_zx_mappings(&maps, desired_range.clone())[0];
170
171        assert_eq!(name, &DEFAULT_VMO_NAME);
172        assert_eq!(range, &desired_range);
173        assert_eq!(details.vmo_koid, buf.vmo.koid().unwrap());
174
175        // Verify unmapping on drop
176        drop(buf);
177        let maps = vmar_root_self().maps_vec().unwrap();
178        assert_eq!(find_zx_mappings(&maps, desired_range), vec![]);
179    }
180
181    #[fuchsia::test]
182    fn setting_name_works() {
183        let buf = PageBuf::<[u8; 16]>::new(100).unwrap();
184        let vmo_name = zx::Name::from_bytes_lossy(b"setting_name_works");
185        buf.set_name(&vmo_name);
186
187        let buf_base = buf.base.addr().get();
188        let buf_len = buf.mapped_len;
189        let maps = vmar_root_self().maps_vec().unwrap();
190        let desired_range = buf_base..buf_base + buf_len;
191        let (name, _range, _details) = &find_zx_mappings(&maps, desired_range.clone())[0];
192
193        assert_eq!(name, &vmo_name);
194    }
195
196    #[fuchsia::test]
197    fn can_be_used_for_object_info() {
198        let (_, _, avail) = vmar_root_self().maps(&mut []).unwrap();
199        let mut buf = PageBuf::<zx::MapInfo>::new(avail * 2).unwrap();
200
201        let (maps, _, avail) = vmar_root_self().maps(buf.as_mut()).unwrap();
202        assert_eq!(maps.len(), avail, "should have consumed all of the mappings");
203    }
204
205    #[fuchsia::test]
206    fn can_be_used_for_vmo_reads() {
207        let len = 8 * zx::system_get_page_size() as usize;
208        let mut buf = PageBuf::<u8>::new(len).unwrap();
209
210        let source_contents = vec![42u8; len];
211        let source_vmo = zx::Vmo::create(len as u64).unwrap();
212        source_vmo.write(&source_contents, 0).unwrap();
213
214        let in_mapping = source_vmo.read_uninit(buf.as_mut(), 0).unwrap();
215        assert_eq!(in_mapping, source_contents);
216    }
217
218    #[fuchsia::test]
219    fn is_fixed_size() {
220        let buf = PageBuf::<[u8; 16]>::new(100).unwrap();
221        assert!(buf.len() >= 100);
222        let vmo_size = buf.vmo.get_size().unwrap();
223        assert!(vmo_size >= 100 * 16);
224        assert_eq!(buf.vmo.set_size(vmo_size * 2), Err(zx::Status::UNAVAILABLE));
225    }
226
227    #[fuchsia::test]
228    fn extra_vmar_is_mapped() {
229        let extra_vmar = Arc::new(
230            fuchsia_runtime::vmar_root_self().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
231        );
232        let buf = PageBuf::<[u8; 16]>::new_with_extra_vmar(100, extra_vmar).unwrap();
233        let extra_base = buf.extra_vmar_and_base.as_ref().unwrap().1;
234        let expected_range = extra_base..extra_base + buf.mapped_len;
235        let maps = fuchsia_runtime::vmar_root_self().maps_vec().unwrap();
236
237        let (_name, observed_range, details) = &find_zx_mappings(&maps, expected_range.clone())[0];
238        assert!(observed_range.contains(&expected_range.start));
239        assert!(
240            observed_range.contains(&expected_range.end)
241                || observed_range.end == expected_range.end
242        );
243        assert_eq!(details.vmo_koid, buf.vmo.koid().unwrap());
244        assert_eq!(details.vmo_offset, 0);
245        assert_eq!(
246            details.mmu_flags,
247            zx::VmarFlagsExtended::PERM_READ,
248            "extra mapping must not be writeable",
249        );
250
251        drop(buf);
252        let maps = fuchsia_runtime::vmar_root_self().maps_vec().unwrap();
253        assert_eq!(
254            find_zx_mappings(&maps, expected_range),
255            vec![],
256            "extra mapping must be dropped too"
257        );
258    }
259}