Skip to main content

vm/
arch_vm_aspace.rs

1// Copyright 2026 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 core::marker::{PhantomData, PhantomPinned};
6use kernel::types::PAddr;
7use zx_status::Status;
8
9// TODO(https://fxbug.dev/529507187): Use bitflags! or equivalent once available.
10pub type ArchMmuFlags = u8;
11
12pub const ARCH_MMU_FLAG_CACHED: ArchMmuFlags = 0 << 0;
13pub const ARCH_MMU_FLAG_UNCACHED: ArchMmuFlags = 1 << 0;
14pub const ARCH_MMU_FLAG_UNCACHED_DEVICE: ArchMmuFlags = 2 << 0;
15pub const ARCH_MMU_FLAG_WRITE_COMBINING: ArchMmuFlags = 3 << 0;
16pub const ARCH_MMU_FLAG_CACHE_MASK: ArchMmuFlags = 3 << 0;
17pub const ARCH_MMU_FLAG_PERM_USER: ArchMmuFlags = 1 << 2;
18pub const ARCH_MMU_FLAG_PERM_READ: ArchMmuFlags = 1 << 3;
19pub const ARCH_MMU_FLAG_PERM_WRITE: ArchMmuFlags = 1 << 4;
20pub const ARCH_MMU_FLAG_PERM_EXECUTE: ArchMmuFlags = 1 << 5;
21pub const ARCH_MMU_FLAG_PERM_RWX_MASK: ArchMmuFlags =
22    ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE | ARCH_MMU_FLAG_PERM_EXECUTE;
23pub const ARCH_MMU_FLAG_NS: ArchMmuFlags = 1 << 6;
24pub const ARCH_MMU_FLAG_INVALID: ArchMmuFlags = 1 << 7;
25
26// TODO(https://fxbug.dev/529507187): Use bitflags! or equivalent once available.
27pub type ArchAspaceFlags = u8;
28
29pub const ARCH_ASPACE_FLAG_KERNEL: ArchAspaceFlags = 1 << 0;
30pub const ARCH_ASPACE_FLAG_GUEST: ArchAspaceFlags = 1 << 1;
31
32// TODO(https://fxbug.dev/529507187): Use bitflags! or equivalent once available.
33// Options for unmapping the given virtual address range.
34pub type ArchUnmapOptions = u8;
35
36pub const ARCH_UNMAP_OPTION_NONE: ArchUnmapOptions = 0;
37// Controls whether the unmap region can be extended to be larger, or if only the exact region may
38// be unmapped. The unmap region might be extended, even if only temporarily, if large pages need to
39// be split.
40pub const ARCH_UNMAP_OPTION_ENLARGE: ArchUnmapOptions = 1 << 0;
41// Requests that the accessed bit be harvested, and the page queues updated.
42pub const ARCH_UNMAP_OPTION_HARVEST: ArchUnmapOptions = 1 << 1;
43
44/// Returns true if the MMU flags specify an uncached memory type.
45#[inline]
46pub const fn arch_mmu_flags_uncached(mmu_flags: ArchMmuFlags) -> bool {
47    (mmu_flags & (ARCH_MMU_FLAG_UNCACHED | ARCH_MMU_FLAG_UNCACHED_DEVICE)) != 0
48}
49
50#[repr(u8)]
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum ExistingEntryAction {
53    Skip = 0,
54    Error = 1,
55    Upgrade = 2,
56}
57
58#[repr(u8)]
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum NonTerminalAction {
61    /// If a non-terminal entry has no accessed information, unmap and free it. If it has accessed
62    /// information, just remove the flag.
63    FreeUnaccessed = 0,
64    /// Retain both the non-terminal mappings and any accessed information.
65    Retain = 1,
66}
67
68#[repr(u8)]
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum TerminalAction {
71    /// If the page is accessed update its age in the page queues, and remove the accessed flag.
72    UpdateAgeAndHarvest = 0,
73    /// If the page is accessed update its age in the page queues, but do not clear the flag.
74    UpdateAge = 1,
75}
76
77unsafe extern "C" {
78    fn cpp_arch_vm_aspace_init(aspace: *mut ArchVmAspace) -> i32;
79    fn cpp_arch_vm_aspace_init_shared(aspace: *mut ArchVmAspace) -> i32;
80    fn cpp_arch_vm_aspace_init_restricted(aspace: *mut ArchVmAspace) -> i32;
81    fn cpp_arch_vm_aspace_init_unified(
82        aspace: *mut ArchVmAspace,
83        shared: *mut ArchVmAspace,
84        restricted: *mut ArchVmAspace,
85    ) -> i32;
86    fn cpp_arch_vm_aspace_disable_updates(aspace: *mut ArchVmAspace);
87    fn cpp_arch_vm_aspace_destroy(aspace: *mut ArchVmAspace) -> i32;
88    fn cpp_arch_vm_aspace_map_contiguous(
89        aspace: *mut ArchVmAspace,
90        vaddr: usize,
91        paddr: PAddr,
92        count: usize,
93        mmu_flags: ArchMmuFlags,
94    ) -> i32;
95    fn cpp_arch_vm_aspace_map(
96        aspace: *mut ArchVmAspace,
97        vaddr: usize,
98        phys: *mut PAddr,
99        count: usize,
100        mmu_flags: ArchMmuFlags,
101        existing_action: ExistingEntryAction,
102    ) -> i32;
103    fn cpp_arch_vm_aspace_unmap(
104        aspace: *mut ArchVmAspace,
105        vaddr: usize,
106        count: usize,
107        enlarge: ArchUnmapOptions,
108    ) -> i32;
109    fn cpp_arch_vm_aspace_unmap_only_enlarge_on_oom(aspace: *mut ArchVmAspace) -> bool;
110    fn cpp_arch_vm_aspace_protect(
111        aspace: *mut ArchVmAspace,
112        vaddr: usize,
113        count: usize,
114        mmu_flags: ArchMmuFlags,
115        enlarge: ArchUnmapOptions,
116    ) -> i32;
117    fn cpp_arch_vm_aspace_query(
118        aspace: *mut ArchVmAspace,
119        vaddr: usize,
120        paddr: *mut PAddr,
121        mmu_flags: *mut ArchMmuFlags,
122    ) -> i32;
123    fn cpp_arch_vm_aspace_pick_spot(
124        aspace: *mut ArchVmAspace,
125        base: usize,
126        end: usize,
127        align: usize,
128        size: usize,
129        mmu_flags: ArchMmuFlags,
130    ) -> usize;
131    fn cpp_arch_vm_aspace_harvest_accessed(
132        aspace: *mut ArchVmAspace,
133        vaddr: usize,
134        count: usize,
135        non_terminal_action: NonTerminalAction,
136        terminal_action: TerminalAction,
137    ) -> i32;
138    fn cpp_arch_vm_aspace_mark_accessed(
139        aspace: *mut ArchVmAspace,
140        vaddr: usize,
141        count: usize,
142    ) -> i32;
143    fn cpp_arch_vm_aspace_accessed_since_last_check(aspace: *mut ArchVmAspace, clear: bool)
144    -> bool;
145    fn cpp_arch_vm_aspace_arch_table_phys(aspace: *mut ArchVmAspace) -> PAddr;
146}
147
148#[repr(C)]
149pub struct ArchVmAspace {
150    // TODO(https://fxbug.dev/529915725): Use zx::OpaqueBytes with the correct size and alignment of
151    // the underlying arch specific aspace structure to allow for correct Rust allocation.
152    _data: [u8; 0],
153    // Mark with PhantomPinned as this is a shared C++ struct and this object is not trivially
154    // movable. Wrap it with PhantomData to allow for FFI usage.
155    _marker: PhantomData<PhantomPinned>,
156}
157
158impl ArchVmAspace {
159    fn as_mut_ptr(&self) -> *mut ArchVmAspace {
160        self as *const ArchVmAspace as *mut ArchVmAspace
161    }
162
163    /// This is used to create a regular address space with no special features. In
164    /// architectures that do not support unified address spaces, it is also used to create
165    /// shared and restricted address spaces. However, when unified address spaces are
166    /// supported, the shared and restricted address spaces should be created with `init_shared`
167    /// and `init_restricted`.
168    pub fn init(&self) -> Result<(), Status> {
169        Status::ok(unsafe { cpp_arch_vm_aspace_init(self.as_mut_ptr()) })
170    }
171
172    /// This is used to create a shared address space, whose contents can be
173    /// accessed from multiple unified address spaces. These address spaces have a statically
174    /// initialized top level page.
175    pub fn init_shared(&self) -> Result<(), Status> {
176        Status::ok(unsafe { cpp_arch_vm_aspace_init_shared(self.as_mut_ptr()) })
177    }
178
179    /// This is used to create a restricted address space, whose contents can be
180    /// accessed from a single unified address space.
181    pub fn init_restricted(&self) -> Result<(), Status> {
182        Status::ok(unsafe { cpp_arch_vm_aspace_init_restricted(self.as_mut_ptr()) })
183    }
184
185    /// `init_unified`: This is used to create a unified address space. This type of address space
186    /// owns no mappings of its own; rather, it is composed of a shared address space and a
187    /// restricted address space. As a result, it expects `init_shared` to have been called
188    /// on the shared address space, and expects `init_restricted` to have been called on the
189    /// restricted address space.
190    pub fn init_unified(
191        &self,
192        shared: &ArchVmAspace,
193        restricted: &ArchVmAspace,
194    ) -> Result<(), Status> {
195        Status::ok(unsafe {
196            cpp_arch_vm_aspace_init_unified(
197                self.as_mut_ptr(),
198                shared.as_mut_ptr(),
199                restricted.as_mut_ptr(),
200            )
201        })
202    }
203
204    /// This method puts the instance into read-only mode and asserts that it contains no mappings.
205    ///
206    /// Note, this method may be a no-op on some architectures. See https://fxbug.dev/42159319.
207    ///
208    /// It is an error to call this method on an instance that contains mappings. Once called,
209    /// subsequent operations that modify the page table will trigger a panic.
210    ///
211    /// The purpose of this method is to help enforce lifecycle and state transitions of VmAspace
212    /// and ArchVmAspaceInterface.
213    pub fn disable_updates(&self) {
214        unsafe { cpp_arch_vm_aspace_disable_updates(self.as_mut_ptr()) }
215    }
216
217    /// Destroy expects the aspace to be fully unmapped, as any mapped regions indicate incomplete
218    /// cleanup at the higher layers. Note that this does not apply to unified aspaces, which may
219    /// still contain some mappings when destroy() is called.
220    ///
221    /// It is safe to call destroy even if init, init_shared, init_restricted, or init_unified
222    /// failed. Once destroy has been called it is a user error to call any of the other methods on
223    /// the aspace, unless specifically stated otherwise, and doing so may cause a panic.
224    pub fn destroy(&self) -> Result<(), Status> {
225        Status::ok(unsafe { cpp_arch_vm_aspace_destroy(self.as_mut_ptr()) })
226    }
227
228    /// Map a physically contiguous region into the virtual address space. This is allowed to use
229    /// any page size the architecture allows given the input parameters.
230    ///
231    /// # Safety
232    ///
233    /// The caller must ensure the virtual and physical range are valid to map.
234    pub unsafe fn map_contiguous(
235        &self,
236        vaddr: usize,
237        paddr: PAddr,
238        count: usize,
239        mmu_flags: ArchMmuFlags,
240    ) -> Result<(), Status> {
241        Status::ok(unsafe {
242            cpp_arch_vm_aspace_map_contiguous(self.as_mut_ptr(), vaddr, paddr, count, mmu_flags)
243        })
244    }
245
246    // Map the given array of pages into the virtual address space starting at
247    // `vaddr`, in the order they appear in `phys`.
248    //
249    // If any address in the range [vaddr, vaddr + count * kPageSize) is already
250    // mapped when this is called, `existing_action` controls the behavior used:
251    //  - `Skip` - Skip updating any existing mappings.
252    //  - `Error` - Existing mappings result in a ZX_ERR_ALREADY_EXISTS error.
253    //  - `Upgrade` - Upgrade any existing mappings, meaning a read-only mapping
254    //                can be converted to read-write, or the mapping can have its
255    //                paddr changed.
256    //
257    // On error none of the provided pages will be mapped. In the case of `Upgrade` the state of any
258    // previous mappings is undefined, and could either still be present or be unmapped.
259    ///
260    /// # Safety
261    ///
262    /// The caller must ensure `phys` points to an array of at least `count` physical addresses and
263    /// that the virtual and physical ranges are valid to map.
264    pub unsafe fn map(
265        &self,
266        vaddr: usize,
267        phys: *mut PAddr,
268        count: usize,
269        mmu_flags: ArchMmuFlags,
270        existing_action: ExistingEntryAction,
271    ) -> Result<(), Status> {
272        Status::ok(unsafe {
273            cpp_arch_vm_aspace_map(
274                self.as_mut_ptr(),
275                vaddr,
276                phys,
277                count,
278                mmu_flags,
279                existing_action,
280            )
281        })
282    }
283
284    /// Unmaps the given virtual address range.
285    ///
286    /// # Safety
287    ///
288    /// The caller must ensure the specified virtual range is no longer in use.
289    pub unsafe fn unmap(
290        &self,
291        vaddr: usize,
292        count: usize,
293        enlarge: ArchUnmapOptions,
294    ) -> Result<(), Status> {
295        Status::ok(unsafe { cpp_arch_vm_aspace_unmap(self.as_mut_ptr(), vaddr, count, enlarge) })
296    }
297
298    /// Returns whether or not an unmap might need to enlarge an operation for reasons other than
299    /// being out of memory. If this returns true, then unmapping a partial large page will fail
300    /// always require an enlarged operation.
301    pub fn unmap_only_enlarge_on_oom(&self) -> bool {
302        unsafe { cpp_arch_vm_aspace_unmap_only_enlarge_on_oom(self.as_mut_ptr()) }
303    }
304
305    /// Change the page protections on the given virtual address range
306    ///
307    /// May return ZX_ERR_NO_MEMORY if the operation requires splitting
308    /// a large page and the next level page table allocation fails. In
309    /// this case, mappings in the input range may be a mix of the old and
310    /// new flags.
311    /// ArchUnmapOptions controls whether a larger range than requested is permitted to experience
312    /// a temporary permissions change. A temporary change may be required if a break-before-make
313    /// style unmap -> remap of the large page is required.
314    ///
315    /// # Safety
316    ///
317    /// The caller must ensure the permissions for the given virtual address range are valid.
318    pub unsafe fn protect(
319        &self,
320        vaddr: usize,
321        count: usize,
322        mmu_flags: ArchMmuFlags,
323        enlarge: ArchUnmapOptions,
324    ) -> Result<(), Status> {
325        Status::ok(unsafe {
326            cpp_arch_vm_aspace_protect(self.as_mut_ptr(), vaddr, count, mmu_flags, enlarge)
327        })
328    }
329
330    /// Queries the translation for `vaddr`.
331    pub fn query(&self, vaddr: usize) -> Result<(PAddr, ArchMmuFlags), Status> {
332        let mut paddr = PAddr(0);
333        let mut mmu_flags: ArchMmuFlags = 0;
334        Status::ok(unsafe {
335            cpp_arch_vm_aspace_query(
336                self.as_mut_ptr(),
337                vaddr,
338                &mut paddr as *mut PAddr,
339                &mut mmu_flags as *mut ArchMmuFlags,
340            )
341        })
342        .map(|_| (paddr, mmu_flags))
343    }
344
345    /// Picks a spot in the virtual address space.
346    pub fn pick_spot(
347        &self,
348        base: usize,
349        end: usize,
350        align: usize,
351        size: usize,
352        mmu_flags: ArchMmuFlags,
353    ) -> usize {
354        unsafe {
355            cpp_arch_vm_aspace_pick_spot(self.as_mut_ptr(), base, end, align, size, mmu_flags)
356        }
357    }
358
359    /// Walks the given range of pages and for any pages that are mapped and have their access bit
360    /// set:
361    ///  * Tells the page queues it has been accessed via PageQueues::MarkAccessed
362    ///  * Potentially removes the accessed flag.
363    ///  * Potentially frees unaccessed page tables.
364    pub fn harvest_accessed(
365        &self,
366        vaddr: usize,
367        count: usize,
368        non_terminal_action: NonTerminalAction,
369        terminal_action: TerminalAction,
370    ) -> Result<(), Status> {
371        Status::ok(unsafe {
372            cpp_arch_vm_aspace_harvest_accessed(
373                self.as_mut_ptr(),
374                vaddr,
375                count,
376                non_terminal_action,
377                terminal_action,
378            )
379        })
380    }
381
382    /// Marks any pages in the given virtual address range as being accessed.
383    pub fn mark_accessed(&self, vaddr: usize, count: usize) -> Result<(), Status> {
384        Status::ok(unsafe { cpp_arch_vm_aspace_mark_accessed(self.as_mut_ptr(), vaddr, count) })
385    }
386
387    /// Returns whether or not this aspace might have additional accessed information since the last
388    /// time this method was called with clear=true. If this returns `false` then, modulo races,
389    /// harvest_accessed is defined to not find any set bits and not call PageQueues::MarkAccessed.
390    ///
391    /// This is intended for use by the harvester to avoid scanning for any accessed or dirty bits
392    /// if the aspace has not been accessed at all.
393    ///
394    /// Note that restricted and shared ArchVmAspace's will report that they have been accessed if
395    /// an associated unified ArchVmAspace has been accessed. However, the reverse is not true; the
396    /// unified ArchVmAspace will not return true if the associated shared/restricted aspaces have
397    /// been accessed.
398    ///
399    /// The `clear` flag controls whether the aspace having been accessed should be cleared or not.
400    /// Not clearing makes this function const and not modify any state. If `clear` is true then
401    /// this method is only thread-compatible and must be externally synchronized.
402    pub fn accessed_since_last_check(&self, clear: bool) -> bool {
403        unsafe { cpp_arch_vm_aspace_accessed_since_last_check(self.as_mut_ptr(), clear) }
404    }
405
406    /// Physical address of the backing data structure used for translation.
407    ///
408    /// This should be treated as an opaque value outside of
409    /// architecture-specific components.
410    pub fn arch_table_phys(&self) -> PAddr {
411        unsafe { cpp_arch_vm_aspace_arch_table_phys(self.as_mut_ptr()) }
412    }
413}