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}