Skip to main content

vm/
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 crate::arch_vm_aspace::{ArchMmuFlags, ArchVmAspace, NonTerminalAction, TerminalAction};
6use crate::vm_address_region::VmAddressRegion;
7use core::ffi::{CStr, c_char, c_void};
8use core::marker::{PhantomData, PhantomPinned};
9use core::ptr::NonNull;
10use fbl::RefPtr;
11use kalloc::AllocError;
12use kernel::thread::ThreadPtr;
13use kernel::types::PAddr;
14use zx_status::Status;
15
16#[repr(u8)]
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Type {
19    User = 0,
20    Kernel = 1,
21    /// You probably do not want to use `LowKernel`. It is primarily used for SMP bootstrap or mexec
22    /// to allow mappings of very low memory using the standard VMM subsystem.
23    LowKernel = 2,
24    /// Used to construct an address space representing hypervisor guest memory.
25    GuestPhysical = 3,
26}
27
28#[repr(i32)]
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum ShareOpt {
31    /// A normal independent address space initialized using [`ArchVmAspace::init`].
32    None = 0,
33    /// A restricted address space whose underlying [`ArchVmAspace`] will be initialized using
34    /// `init_restricted`.
35    Restricted = 1,
36    /// A shared address space whose underlying [`ArchVmAspace`] will be initialized using
37    /// `init_shared`.
38    Shared = 2,
39}
40
41unsafe extern "C" {
42    fn cpp_vm_aspace_get_ref_counted(aspace: *mut VmAspace) -> *mut fbl::RefCounted;
43    fn cpp_vm_aspace_create(type_: Type, name: *const c_char) -> *mut VmAspace;
44    fn cpp_vm_aspace_create_with_opts(
45        base: usize,
46        size: usize,
47        type_: Type,
48        name: *const c_char,
49        share_opt: ShareOpt,
50    ) -> *mut VmAspace;
51    fn cpp_vm_aspace_create_unified(
52        shared: *mut VmAspace,
53        restricted: *mut VmAspace,
54        name: *const c_char,
55    ) -> *mut VmAspace;
56    fn cpp_vm_aspace_arch_aspace(aspace: *mut VmAspace) -> *mut ArchVmAspace;
57    fn cpp_vm_aspace_kernel_aspace() -> *mut VmAspace;
58    fn cpp_vm_aspace_root_vmar(aspace: *mut VmAspace) -> *mut VmAddressRegion;
59    fn cpp_vm_aspace_base(aspace: *mut VmAspace) -> usize;
60    fn cpp_vm_aspace_size(aspace: *mut VmAspace) -> usize;
61    fn cpp_vm_aspace_name(aspace: *mut VmAspace) -> *const c_char;
62    fn cpp_vm_aspace_is_user(aspace: *mut VmAspace) -> bool;
63    fn cpp_vm_aspace_is_aslr_enabled(aspace: *mut VmAspace) -> bool;
64    fn cpp_vm_aspace_is_destroyed(aspace: *mut VmAspace) -> bool;
65    fn cpp_vm_aspace_destroy(aspace: *mut VmAspace) -> i32;
66    fn cpp_vm_aspace_rename(aspace: *mut VmAspace, name: *const c_char);
67    fn cpp_vm_aspace_dump(aspace: *mut VmAspace, verbose: bool);
68    fn cpp_vm_aspace_attach_to_thread(aspace: *mut VmAspace, thread: *mut c_void);
69    fn cpp_vm_aspace_vdso_base_address(aspace: *mut VmAspace) -> usize;
70    fn cpp_vm_aspace_vdso_code_address(aspace: *mut VmAspace) -> usize;
71    fn cpp_vm_aspace_is_high_memory_priority(aspace: *mut VmAspace) -> bool;
72    fn cpp_vm_aspace_accessed_fault(aspace: *mut VmAspace, va: usize) -> i32;
73    fn cpp_vm_aspace_page_fault(aspace: *mut VmAspace, va: usize, flags: u32) -> i32;
74    fn cpp_vm_aspace_soft_fault(aspace: *mut VmAspace, va: usize, flags: u32) -> i32;
75    fn cpp_vm_aspace_soft_fault_in_range(
76        aspace: *mut VmAspace,
77        va: usize,
78        flags: u32,
79        len: usize,
80    ) -> i32;
81    fn cpp_vm_aspace_drop_user_page_tables(aspace: *mut VmAspace);
82    fn cpp_vm_aspace_drop_all_user_page_tables();
83    fn cpp_vm_aspace_dump_all_aspaces(verbose: bool);
84    fn cpp_vm_aspace_harvest_all_user_accessed_bits(
85        non_terminal_action: NonTerminalAction,
86        terminal_action: TerminalAction,
87    );
88    fn cpp_vm_aspace_alloc_physical(
89        aspace: *mut VmAspace,
90        name: *const c_char,
91        size: usize,
92        ptr: *mut *mut c_void,
93        align_pow2: u8,
94        paddr: PAddr,
95        vmm_flags: u32,
96        arch_mmu_flags: ArchMmuFlags,
97    ) -> i32;
98    fn cpp_vm_aspace_alloc_contiguous(
99        aspace: *mut VmAspace,
100        name: *const c_char,
101        size: usize,
102        ptr: *mut *mut c_void,
103        align_pow2: u8,
104        vmm_flags: u32,
105        arch_mmu_flags: ArchMmuFlags,
106    ) -> i32;
107    fn cpp_vm_aspace_free_region(aspace: *mut VmAspace, va: usize) -> i32;
108    fn cpp_vm_aspace_free(aspace: *mut VmAspace);
109}
110
111#[repr(C)]
112pub struct VmAspace {
113    // TODO(https://fxbug.dev/529915725): Use zx::OpaqueBytes with the correct size and alignment of
114    // the underlying structure to allow for correct Rust allocation.
115    _data: [u8; 0],
116    // Mark with PhantomPinned as this is a shared C++ struct and this object is not trivially
117    // movable. Wrap it with PhantomData to allow for FFI usage.
118    _marker: PhantomData<PhantomPinned>,
119}
120
121impl VmAspace {
122    fn as_mut_ptr(&self) -> *mut VmAspace {
123        self as *const VmAspace as *mut VmAspace
124    }
125
126    /// Creates an address space of the type specified in `type_` with name `name`.
127    ///
128    /// Although reference counted, the returned [`VmAspace`] must be explicitly destroyed via
129    /// [`destroy`](Self::destroy).
130    ///
131    /// Returns `None` on failure (e.g. due to resource starvation).
132    pub fn create(type_: Type, name: &CStr) -> Option<RefPtr<VmAspace>> {
133        unsafe { RefPtr::try_from_raw(cpp_vm_aspace_create(type_, name.as_ptr())) }
134    }
135
136    /// Creates an address space of the type specified in `type_` with name `name`.
137    ///
138    /// The returned aspace will start at `base` and span `size`.
139    ///
140    /// If `share_opt` is [`ShareOpt::Shared`], we're creating a shared address space, and the
141    /// underlying [`ArchVmAspace`] will be initialized using the
142    /// [`init_shared`](ArchVmAspace::init_shared) method instead of the normal
143    /// [`init`](ArchVmAspace::init) method.
144    ///
145    /// If `share_opt` is [`ShareOpt::Restricted`], we're creating a restricted address space, and
146    /// the underlying [`ArchVmAspace`] will be initialized using the
147    /// [`init_restricted`](ArchVmAspace::init_restricted) method.
148    ///
149    /// Although reference counted, the returned [`VmAspace`] must be explicitly destroyed via
150    /// [`destroy`](Self::destroy).
151    ///
152    /// Returns `None` on failure (e.g. due to resource starvation).
153    pub fn create_with_opts(
154        base: usize,
155        size: usize,
156        type_: Type,
157        name: &CStr,
158        share_opt: ShareOpt,
159    ) -> Option<RefPtr<VmAspace>> {
160        unsafe {
161            RefPtr::try_from_raw(cpp_vm_aspace_create_with_opts(
162                base,
163                size,
164                type_,
165                name.as_ptr(),
166                share_opt,
167            ))
168        }
169    }
170
171    /// Creates a unified address space that consists of the given constituent address spaces.
172    ///
173    /// The passed in address spaces must meet the following criteria:
174    /// 1. They must manage non-overlapping regions.
175    /// 2. The shared [`VmAspace`] must have been created with the shared argument set to true.
176    ///
177    /// Although reference counted, the returned [`VmAspace`] must be explicitly destroyed via
178    /// [`destroy`](Self::destroy). Note that it must be destroyed before the shared and
179    /// restricted [`VmAspace`]s; destroying the constituent [`VmAspace`]s before destroying
180    /// this one will trigger asserts.
181    ///
182    /// Returns `None` on failure (e.g. due to resource starvation).
183    ///
184    /// # Safety
185    ///
186    /// The caller must ensure that `shared` and `restricted` are valid pointers to address
187    /// spaces initialized as shared and restricted respectively.
188    pub unsafe fn create_unified(
189        shared: *mut VmAspace,
190        restricted: *mut VmAspace,
191        name: &CStr,
192    ) -> Option<RefPtr<VmAspace>> {
193        unsafe {
194            RefPtr::try_from_raw(cpp_vm_aspace_create_unified(shared, restricted, name.as_ptr()))
195        }
196    }
197
198    /// Destroys this address space.
199    ///
200    /// `destroy` does not free this object, but rather allows it to be freed when the last
201    /// retaining `RefPtr` is destroyed.
202    pub fn destroy(&self) -> Result<(), Status> {
203        Status::ok(unsafe { cpp_vm_aspace_destroy(self.as_mut_ptr()) })
204    }
205
206    /// Renames this address space.
207    pub fn rename(&self, name: &CStr) {
208        unsafe { cpp_vm_aspace_rename(self.as_mut_ptr(), name.as_ptr()) }
209    }
210
211    /// Returns the base virtual address of this address space.
212    pub fn base(&self) -> usize {
213        unsafe { cpp_vm_aspace_base(self.as_mut_ptr()) }
214    }
215
216    /// Returns the size in bytes of this address space.
217    pub fn size(&self) -> usize {
218        unsafe { cpp_vm_aspace_size(self.as_mut_ptr()) }
219    }
220
221    /// Returns the name of this address space.
222    pub fn name(&self) -> &CStr {
223        unsafe { CStr::from_ptr(cpp_vm_aspace_name(self.as_mut_ptr())) }
224    }
225
226    /// Returns a reference to the architecturally specific part of the address space
227    /// (`ArchVmAspace`). This is internally locked and does not need to be guarded by `lock_`.
228    pub fn arch_aspace(&self) -> &ArchVmAspace {
229        unsafe { &*cpp_vm_aspace_arch_aspace(self.as_mut_ptr()) }
230    }
231
232    /// Returns true if this is a user address space (`Type::User`).
233    pub fn is_user(&self) -> bool {
234        unsafe { cpp_vm_aspace_is_user(self.as_mut_ptr()) }
235    }
236
237    /// Returns true if ASLR is enabled for this address space.
238    pub fn is_aslr_enabled(&self) -> bool {
239        unsafe { cpp_vm_aspace_is_aslr_enabled(self.as_mut_ptr()) }
240    }
241
242    /// Returns true if this address space has been destroyed.
243    pub fn is_destroyed(&self) -> bool {
244        unsafe { cpp_vm_aspace_is_destroyed(self.as_mut_ptr()) }
245    }
246
247    /// Returns the singleton kernel address space.
248    pub fn kernel_aspace() -> Option<RefPtr<VmAspace>> {
249        unsafe { RefPtr::try_from_raw(cpp_vm_aspace_kernel_aspace()) }
250    }
251
252    /// Returns the root address region (`RootVmar`) for this address space.
253    pub fn root_vmar(&self) -> Option<RefPtr<VmAddressRegion>> {
254        unsafe { RefPtr::try_from_raw(cpp_vm_aspace_root_vmar(self.as_mut_ptr())) }
255    }
256
257    /// Sets the per-thread address space pointer to this address space.
258    pub fn attach_to_thread(&self, thread: ThreadPtr) {
259        unsafe { cpp_vm_aspace_attach_to_thread(self.as_mut_ptr(), thread.as_raw()) }
260    }
261
262    /// Dumps information about this address space to the debug log.
263    pub fn dump(&self, verbose: bool) {
264        unsafe { cpp_vm_aspace_dump(self.as_mut_ptr(), verbose) }
265    }
266
267    /// Drops all user page tables across all user address spaces.
268    pub fn drop_all_user_page_tables() {
269        unsafe { cpp_vm_aspace_drop_all_user_page_tables() }
270    }
271
272    /// Drops all user page tables for this address space.
273    pub fn drop_user_page_tables(&self) {
274        unsafe { cpp_vm_aspace_drop_user_page_tables(self.as_mut_ptr()) }
275    }
276
277    /// Dumps all address spaces in the system.
278    pub fn dump_all_aspaces(verbose: bool) {
279        unsafe { cpp_vm_aspace_dump_all_aspaces(verbose) }
280    }
281
282    /// Harvests all accessed information across all user mappings and updates any page age
283    /// information for terminal mappings, and potentially harvests page tables depending on the
284    /// passed in action.
285    ///
286    /// This requires holding `aspaces_list_lock_` over the entire duration and
287    /// whilst not a commonly used lock this function should still only be called infrequently to
288    /// avoid monopolizing the lock.
289    pub fn harvest_all_user_accessed_bits(
290        non_terminal_action: NonTerminalAction,
291        terminal_action: TerminalAction,
292    ) {
293        unsafe {
294            cpp_vm_aspace_harvest_all_user_accessed_bits(non_terminal_action, terminal_action)
295        }
296    }
297
298    /// Generates a soft fault against this address space.
299    ///
300    /// This is similar to `page_fault` except:
301    /// * This address space may not currently be active and this does not have to be called from
302    ///   the hardware exception handler.
303    /// * May be invoked spuriously in situations where the hardware mappings would have prevented a
304    ///   real `page_fault` from occurring.
305    ///
306    /// May block on page requests and must be called without locks held.
307    pub fn soft_fault(&self, va: usize, flags: u32) -> Result<(), Status> {
308        Status::ok(unsafe { cpp_vm_aspace_soft_fault(self.as_mut_ptr(), va, flags) })
309    }
310
311    /// Similar to `soft_fault`, but additionally takes a length indicating that the range of
312    /// `[va, va+len)` is expected to be accessed with `flags` after resolving this fault. The
313    /// address space can take this range as a hint to attempt to preemptively avoid future faults.
314    ///
315    /// There are no alignment restrictions on `va` or `len`, although it is assumed that `len` is
316    /// greater than zero.
317    pub fn soft_fault_in_range(&self, va: usize, flags: u32, len: usize) -> Result<(), Status> {
318        Status::ok(unsafe { cpp_vm_aspace_soft_fault_in_range(self.as_mut_ptr(), va, flags, len) })
319    }
320
321    /// Generates an accessed flag fault against this address space.
322    ///
323    /// This is a specialized version of `soft_fault` that will only resolve a potential missing
324    /// access flag and nothing else.
325    pub fn accessed_fault(&self, va: usize) -> Result<(), Status> {
326        Status::ok(unsafe { cpp_vm_aspace_accessed_fault(self.as_mut_ptr(), va) })
327    }
328
329    /// Page fault routine.
330    ///
331    /// Should only be called by the hypervisor or by `Thread::Current::Fault`.
332    pub fn page_fault(&self, va: usize, flags: u32) -> Result<(), Status> {
333        Status::ok(unsafe { cpp_vm_aspace_page_fault(self.as_mut_ptr(), va, flags) })
334    }
335
336    /// Legacy function to assist in the transition to VMARs.
337    ///
338    /// Assumes a flat VMAR structure in which all VMOs are mapped as children of the root.
339    /// Will assert if used on user address spaces.
340    ///
341    /// # Safety
342    ///
343    /// The caller must ensure `ptr` points to a valid memory location that can hold the allocated
344    /// address or specific starting address.
345    pub unsafe fn alloc_physical(
346        &self,
347        name: &CStr,
348        size: usize,
349        ptr: *mut *mut c_void,
350        align_pow2: u8,
351        paddr: PAddr,
352        vmm_flags: u32,
353        arch_mmu_flags: ArchMmuFlags,
354    ) -> Result<(), Status> {
355        Status::ok(unsafe {
356            cpp_vm_aspace_alloc_physical(
357                self.as_mut_ptr(),
358                name.as_ptr(),
359                size,
360                ptr,
361                align_pow2,
362                paddr,
363                vmm_flags,
364                arch_mmu_flags,
365            )
366        })
367    }
368
369    /// Legacy function to assist in the transition to VMARs.
370    ///
371    /// Assumes a flat VMAR structure in which all VMOs are mapped as children of the root.
372    /// Will assert if used on user address spaces.
373    ///
374    /// # Safety
375    ///
376    /// The caller must ensure `ptr` points to a valid memory location that can hold the allocated
377    /// address or specific starting address.
378    pub unsafe fn alloc_contiguous(
379        &self,
380        name: &CStr,
381        size: usize,
382        ptr: *mut *mut c_void,
383        align_pow2: u8,
384        vmm_flags: u32,
385        arch_mmu_flags: ArchMmuFlags,
386    ) -> Result<(), Status> {
387        Status::ok(unsafe {
388            cpp_vm_aspace_alloc_contiguous(
389                self.as_mut_ptr(),
390                name.as_ptr(),
391                size,
392                ptr,
393                align_pow2,
394                vmm_flags,
395                arch_mmu_flags,
396            )
397        })
398    }
399
400    /// Legacy function to assist in the transition to VMARs.
401    ///
402    /// Assumes a flat VMAR structure in which all VMOs are mapped as children of the root.
403    /// Will assert if used on user address spaces.
404    ///
405    /// # Safety
406    ///
407    /// The caller must ensure that the virtual address range being freed is no longer in use.
408    pub unsafe fn free_region(&self, va: usize) -> Result<(), Status> {
409        Status::ok(unsafe { cpp_vm_aspace_free_region(self.as_mut_ptr(), va) })
410    }
411
412    /// Returns the vDSO base address for this address space.
413    pub fn vdso_base_address(&self) -> usize {
414        unsafe { cpp_vm_aspace_vdso_base_address(self.as_mut_ptr()) }
415    }
416
417    /// Returns the vDSO code address for this address space.
418    pub fn vdso_code_address(&self) -> usize {
419        unsafe { cpp_vm_aspace_vdso_code_address(self.as_mut_ptr()) }
420    }
421
422    /// Returns whether this address space is currently set to be a high memory priority.
423    pub fn is_high_memory_priority(&self) -> bool {
424        unsafe { cpp_vm_aspace_is_high_memory_priority(self.as_mut_ptr()) }
425    }
426}
427
428impl fbl::HasRefCount for VmAspace {
429    fn ref_count(&self) -> &fbl::RefCounted {
430        unsafe { &*cpp_vm_aspace_get_ref_counted(self.as_mut_ptr()) }
431    }
432}
433
434unsafe impl fbl::Recyclable for VmAspace {
435    unsafe fn recycle(ptr: NonNull<Self>) {
436        unsafe {
437            cpp_vm_aspace_free(ptr.as_ptr());
438        }
439    }
440
441    fn allocate(_value: Self) -> Result<NonNull<Self>, AllocError> {
442        Err(AllocError)
443    }
444}