Skip to main content

hyperlight_common/
vmem.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4#[cfg_attr(target_arch = "x86_64", path = "arch/amd64/vmem.rs")]
5#[cfg_attr(target_arch = "aarch64", path = "arch/aarch64/vmem.rs")]
6mod arch;
7
8/// This is always the page size that the /guest/ is being compiled
9/// for, which may or may not be the same as the host page size.
10pub use arch::PAGE_SIZE;
11pub use arch::{PAGE_PRESENT, PAGE_TABLE_SIZE, PTE_ADDR_MASK, PageTableEntry, PhysAddr, VirtAddr};
12pub const PAGE_TABLE_ENTRIES_PER_TABLE: usize =
13    PAGE_TABLE_SIZE / core::mem::size_of::<PageTableEntry>();
14
15// It would be nice not to have any arch-dependent re-exports here,
16// but on arm64 the MAIR indices used need to be synced between the
17// descriptor creation code and the register initialisation code to
18// make sure that MAIR is set up properly.
19#[cfg(target_arch = "aarch64")]
20pub use arch::ATTR_INDEX_NORMAL;
21
22// Shared page table iterator infrastructure used by each arch module.
23
24/// Utility function to extract an (inclusive on both ends) bit range
25/// from a quadword.
26#[inline(always)]
27pub(crate) fn bits<const HIGH_BIT: u8, const LOW_BIT: u8>(x: u64) -> u64 {
28    (x & ((1 << (HIGH_BIT + 1)) - 1)) >> LOW_BIT
29}
30
31/// Helper function to write a page table entry, updating the whole
32/// chain of tables back to the root if necessary.
33///
34/// # Safety
35/// Same requirements as [`TableOps::write_entry`].
36pub(in crate::vmem) unsafe fn write_entry_updating<
37    Op: TableOps,
38    P: UpdateParent<
39            Op,
40            TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
41        >,
42>(
43    op: &Op,
44    parent: P,
45    addr: Op::TableAddr,
46    entry: u64,
47) {
48    #[allow(clippy::useless_conversion)]
49    if let Some(again) = unsafe { op.write_entry(addr, entry as PageTableEntry) } {
50        parent.update_parent(op, again);
51    }
52}
53
54/// A helper trait that allows us to move a page table (e.g. from the
55/// snapshot to the scratch region), keeping track of the context that
56/// needs to be updated when that is moved (and potentially
57/// recursively updating, if necessary).
58///
59/// This is done via a trait so that the selected impl knows the exact
60/// nesting depth of tables, in order to assist
61/// inlining/specialisation in generating efficient code.
62///
63/// The trait definition only bounds its parameter by
64/// [`TableReadOps`], since [`UpdateParentNone`] does not need to be
65/// able to actually write to the tables.
66pub trait UpdateParent<Op: TableReadOps + ?Sized>: Copy {
67    /// The type of the information about a moved table which is
68    /// needed in order to update its parent.
69    type TableMoveInfo;
70    /// The [`UpdateParent`] type that should be used when going down
71    /// another level in the table, in order to add the current level
72    /// to the chain of ancestors to be updated.
73    type ChildType: UpdateParent<Op, TableMoveInfo = Self::TableMoveInfo>;
74    fn update_parent(self, op: &Op, new_ptr: Self::TableMoveInfo);
75    fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType;
76}
77
78/// A struct implementing [`UpdateParent`] that is impossible to use
79/// (since its [`UpdateParent::update_parent`] method takes [`Void`]),
80/// used when it is statically known that a table operation cannot
81/// result in a need to update ancestors.
82#[derive(Copy, Clone)]
83pub struct UpdateParentNone {}
84impl<Op: TableReadOps> UpdateParent<Op> for UpdateParentNone {
85    type TableMoveInfo = Void;
86    type ChildType = Self;
87    fn update_parent(self, _op: &Op, impossible: Void) {
88        match impossible {}
89    }
90    fn for_child_at_entry(self, _entry_ptr: Op::TableAddr) -> Self {
91        self
92    }
93}
94
95/// A struct implementing [`UpdateParent`] to be used when a table's
96/// parent is another table that needs to be updated recursively.
97#[allow(unused)] // not all architectures use UpdateParentTable
98pub(in crate::vmem) struct UpdateParentTable<Op: TableOps, P: UpdateParent<Op>> {
99    pub(in crate::vmem) parent: P,
100    pub(in crate::vmem) entry_ptr: Op::TableAddr,
101}
102impl<Op: TableOps, P: UpdateParent<Op>> Clone for UpdateParentTable<Op, P> {
103    fn clone(&self) -> Self {
104        *self
105    }
106}
107impl<Op: TableOps, P: UpdateParent<Op>> Copy for UpdateParentTable<Op, P> {}
108impl<Op: TableOps, P: UpdateParent<Op>> UpdateParentTable<Op, P> {
109    #[allow(unused)] // not all architectures use UpdateParentTable
110    pub(in crate::vmem) fn new(parent: P, entry_ptr: Op::TableAddr) -> Self {
111        UpdateParentTable { parent, entry_ptr }
112    }
113}
114
115/// A struct implementing [`UpdateParent`] to be used when a table's
116/// parent is the "root table" (with access to that root pointer
117/// provided in an architecture/environment-insensitive manner via
118/// `TableOps`)
119#[derive(Copy, Clone)]
120pub struct UpdateParentRoot {}
121
122/// A helper structure indicating a mapping operation that needs to be
123/// performed.
124pub(in crate::vmem) struct MapRequest<Op: TableReadOps, P: UpdateParent<Op>> {
125    pub table_base: Op::TableAddr,
126    pub vmin: u64,
127    pub len: u64,
128    pub update_parent: P,
129}
130
131/// A helper structure indicating that a particular PTE needs to be
132/// modified.
133pub(in crate::vmem) struct MapResponse<Op: TableReadOps, P: UpdateParent<Op>> {
134    pub entry_ptr: Op::TableAddr,
135    pub vmin: u64,
136    pub len: u64,
137    pub update_parent: P,
138}
139
140/// Iterator that walks through page table entries at a specific level.
141///
142/// Given a virtual address range and a table base, this iterator yields
143/// `MapResponse` items for each page table entry that needs to be modified.
144/// The const generics `HIGH_BIT` and `LOW_BIT` specify which bits of the
145/// virtual address are used to index into this level's table.
146///
147/// For example on amd64:
148/// - PML4: HIGH_BIT=47, LOW_BIT=39 (9 bits = 512 entries, each covering 512GB)
149/// - PDPT: HIGH_BIT=38, LOW_BIT=30 (9 bits = 512 entries, each covering 1GB)
150/// - PD:   HIGH_BIT=29, LOW_BIT=21 (9 bits = 512 entries, each covering 2MB)
151/// - PT:   HIGH_BIT=20, LOW_BIT=12 (9 bits = 512 entries, each covering 4KB)
152pub(in crate::vmem) struct ModifyPteIterator<
153    const HIGH_BIT: u8,
154    const LOW_BIT: u8,
155    Op: TableReadOps,
156    P: UpdateParent<Op>,
157> {
158    request: MapRequest<Op, P>,
159    n: u64,
160}
161impl<const HIGH_BIT: u8, const LOW_BIT: u8, Op: TableReadOps, P: UpdateParent<Op>> Iterator
162    for ModifyPteIterator<HIGH_BIT, LOW_BIT, Op, P>
163{
164    type Item = MapResponse<Op, P>;
165    fn next(&mut self) -> Option<Self::Item> {
166        // Each page table entry at this level covers a region of size
167        // (1 << LOW_BIT) bytes. For example, at the PT level
168        // (LOW_BIT=12), each entry covers 4KB (0x1000 bytes). At the
169        // PD level (LOW_BIT=21), each entry covers 2MB (0x200000
170        // bytes).
171        //
172        // This mask isolates the bits below this level's index bits,
173        // used for alignment.
174        let lower_bits_mask = (1u64 << LOW_BIT) - 1;
175
176        // Calculate the virtual address for this iteration.
177        // On the first iteration (n=0), start at the requested vmin.
178        // On subsequent iterations, advance to the next aligned boundary.
179        // This handles the case where vmin isn't aligned to this level's
180        // entry size.
181        let next_vmin = if self.n == 0 {
182            self.request.vmin
183        } else {
184            // Align to the next boundary by adding one entry's worth
185            // and masking off lower bits. Masking off before adding
186            // is safe, since n << LOW_BIT must always have zeros in
187            // these positions.
188            let aligned_min = self.request.vmin & !lower_bits_mask;
189            // Use checked_add because going past the end of the
190            // address space counts as "the next one would be out of
191            // range"
192            aligned_min.checked_add(self.n << LOW_BIT)?
193        };
194
195        // Check if we've processed the entire requested range
196        if next_vmin >= self.request.vmin + self.request.len {
197            return None;
198        }
199
200        // Calculate the pointer to this level's page table entry.
201        // bits::<HIGH_BIT, LOW_BIT> extracts the relevant index bits
202        // from the virtual address. Multiply by the PTE size to get
203        // the byte offset.
204        let pte_index = bits::<HIGH_BIT, LOW_BIT>(next_vmin);
205        let entry_ptr = Op::entry_addr(
206            self.request.table_base,
207            pte_index * core::mem::size_of::<PageTableEntry>() as u64,
208        );
209
210        // Calculate how many bytes remain to be mapped from this point.
211        let len_from_here = self.request.len - (next_vmin - self.request.vmin);
212        // Calculate the maximum bytes this single entry can cover.
213        // If next_vmin is aligned, this is the full entry size (1 << LOW_BIT).
214        // If not aligned (only possible on first iteration), it's the
215        // remaining space until the next boundary.
216        let max_len = (1u64 << LOW_BIT) - (next_vmin & lower_bits_mask);
217        // The actual length for this entry is the smaller of what's
218        // needed vs what fits.
219        let next_len = core::cmp::min(len_from_here, max_len);
220
221        // Advance iteration counter for next call
222        self.n += 1;
223
224        Some(MapResponse {
225            entry_ptr,
226            vmin: next_vmin,
227            len: next_len,
228            update_parent: self.request.update_parent,
229        })
230    }
231}
232
233pub(in crate::vmem) fn modify_ptes<
234    const HIGH_BIT: u8,
235    const LOW_BIT: u8,
236    Op: TableReadOps,
237    P: UpdateParent<Op>,
238>(
239    r: MapRequest<Op, P>,
240) -> ModifyPteIterator<HIGH_BIT, LOW_BIT, Op, P> {
241    ModifyPteIterator { request: r, n: 0 }
242}
243
244/// The read-only operations used to actually access the page table
245/// structures, used to allow the same code to be used in the host and
246/// the guest for page table setup.  This is distinct from
247/// `TableWriteOps`, since there are some implementations for which
248/// writing does not make sense, and only reading is required.
249pub trait TableReadOps {
250    /// The type of table addresses
251    type TableAddr: Copy;
252
253    /// Offset the table address by the given offset in bytes.
254    ///
255    /// # Parameters
256    /// - `addr`: The base address of the table.
257    /// - `entry_offset`: The offset in **bytes** within the page table. This is
258    ///   not an entry index; callers must multiply the entry index by the size
259    ///   of a page table entry (typically 8 bytes) to obtain the correct byte offset.
260    ///
261    /// # Returns
262    /// The address of the entry at the given byte offset from the base address.
263    fn entry_addr(addr: Self::TableAddr, entry_offset: u64) -> Self::TableAddr;
264
265    /// Read a u64 from the given address, used to read existing page
266    /// table entries
267    ///
268    /// # Safety
269    /// This reads from the given memory address, and so all the usual
270    /// Rust things about raw pointers apply. This will also be used
271    /// to update guest page tables, so especially in the guest, it is
272    /// important to ensure that the page tables updates do not break
273    /// invariants. The implementor of the trait should ensure that
274    /// nothing else will be reading/writing the address at the same
275    /// time as mapping code using the trait.
276    unsafe fn read_entry(&self, addr: Self::TableAddr) -> PageTableEntry;
277
278    /// Convert an abstract table address to a concrete physical address (u64)
279    /// which can be e.g. written into a page table entry
280    fn to_phys(addr: Self::TableAddr) -> PhysAddr;
281
282    /// Convert a concrete physical address (u64) which may have been e.g. read
283    /// from a page table entry back into an abstract table address
284    fn from_phys(addr: PhysAddr) -> Self::TableAddr;
285
286    /// Return the address of the root page table
287    fn root_table(&self) -> Self::TableAddr;
288}
289
290/// Our own version of ! until it is stable. Used to avoid needing to
291/// implement [`TableOps::update_root`] for ops that never need
292/// to move a table.
293pub enum Void {}
294
295/// A marker struct, used by an implementation of [`TableOps`] to
296/// indicate that it may need to move existing page tables
297pub struct MayMoveTable {}
298/// A marker struct, used by an implementation of [`TableOps`] to
299/// indicate that it will be able to update existing page tables
300/// in-place, without moving them.
301pub struct MayNotMoveTable {}
302
303mod sealed {
304    use super::{MayMoveTable, MayNotMoveTable, TableReadOps, Void};
305
306    /// A (purposefully-not-exposed) internal implementation detail of the
307    /// logic around whether a [`TableOps`] implementation may or may not
308    /// move page tables.
309    pub trait TableMovabilityBase<Op: TableReadOps + ?Sized> {
310        type TableMoveInfo;
311    }
312    impl<Op: TableReadOps> TableMovabilityBase<Op> for MayMoveTable {
313        type TableMoveInfo = Op::TableAddr;
314    }
315    impl<Op: TableReadOps> TableMovabilityBase<Op> for MayNotMoveTable {
316        type TableMoveInfo = Void;
317    }
318}
319use sealed::*;
320
321/// A sealed trait used to collect some information about the marker structures [`MayMoveTable`] and [`MayNotMoveTable`]
322#[allow(private_bounds)] // this trait is intentionally sealed
323pub trait TableMovability<Op: TableReadOps + ?Sized>:
324    TableMovabilityBase<Op>
325    + arch::TableMovability<Op, <Self as TableMovabilityBase<Op>>::TableMoveInfo>
326{
327}
328impl<
329    Op: TableReadOps,
330    T: TableMovabilityBase<Op>
331        + arch::TableMovability<Op, <Self as TableMovabilityBase<Op>>::TableMoveInfo>,
332> TableMovability<Op> for T
333{
334}
335
336/// The operations used to actually access the page table structures
337/// that involve writing to them, used to allow the same code to be
338/// used in the host and the guest for page table setup.
339pub trait TableOps: TableReadOps {
340    /// This marker should be either [`MayMoveTable`] or
341    /// [`MayNotMoveTable`], as the case may be.
342    ///
343    /// If this is [`MayMoveTable`], the return type of
344    /// [`Self::write_entry`] and the parameter type of
345    /// [`Self::update_root`] will be `<Self as
346    /// TableReadOps>::TableAddr`. If it is [`MayNotMoveTable`], those
347    /// types will be [`Void`].
348    type TableMovability: TableMovability<Self>;
349
350    /// Allocate a zeroed table
351    ///
352    /// # Safety
353    /// The current implementations of this function are not
354    /// inherently unsafe, but the guest implementation will likely
355    /// become so in the future when a real physical page allocator is
356    /// implemented.
357    ///
358    /// Currently, callers should take care not to call this on
359    /// multiple threads at the same time.
360    ///
361    /// # Panics
362    /// This function may panic if:
363    /// - The Layout creation fails
364    /// - Memory allocation fails
365    unsafe fn alloc_table(&self) -> Self::TableAddr;
366
367    /// Write a u64 to the given address, used to write updated page
368    /// table entries. In some cases,the page table in which the entry
369    /// is located may need to be relocated in order for this to
370    /// succeed; if this is the case, the base address of the new
371    /// table is returned.
372    ///
373    /// # Safety
374    /// This writes to the given memory address, and so all the usual
375    /// Rust things about raw pointers apply. This will also be used
376    /// to update guest page tables, so especially in the guest, it is
377    /// important to ensure that the page tables updates do not break
378    /// invariants. The implementor of the trait should ensure that
379    /// nothing else will be reading/writing the address at the same
380    /// time as mapping code using the trait.
381    unsafe fn write_entry(
382        &self,
383        addr: Self::TableAddr,
384        entry: PageTableEntry,
385    ) -> Option<<Self::TableMovability as TableMovabilityBase<Self>>::TableMoveInfo>;
386
387    /// Change the root page table to one at a different address
388    ///
389    /// # Safety
390    /// This function will directly result in a change to virtual
391    /// memory translation, and so is inherently unsafe w.r.t. the
392    /// Rust memory model.  All the caveats listed on [`map`] apply as
393    /// well.
394    unsafe fn update_root(
395        &self,
396        new_root: <Self::TableMovability as TableMovabilityBase<Self>>::TableMoveInfo,
397    );
398}
399
400#[derive(Debug, PartialEq, Clone, Copy)]
401pub struct BasicMapping {
402    pub readable: bool,
403    pub writable: bool,
404    pub executable: bool,
405}
406
407#[derive(Debug, PartialEq, Clone, Copy)]
408pub struct CowMapping {
409    pub readable: bool,
410    pub executable: bool,
411}
412
413#[derive(Debug, PartialEq, Clone, Copy)]
414pub enum MappingKind {
415    Unmapped,
416    Basic(BasicMapping),
417    Cow(CowMapping),
418    /* TODO: What useful things other than basic mappings actually
419     * require touching the tables? */
420}
421
422#[derive(Debug)]
423pub struct Mapping {
424    pub phys_base: u64,
425    pub virt_base: u64,
426    pub len: u64,
427    pub kind: MappingKind,
428}
429
430/// Assumption: all are page-aligned
431///
432/// # Safety
433/// This function modifies pages backing a virtual memory range which
434/// is inherently unsafe w.r.t.  the Rust memory model.
435///
436/// When using this function, please note:
437/// - No locking is performed before touching page table data structures,
438///   as such do not use concurrently with any other page table operations
439/// - TLB invalidation is not performed, if previously-mapped ranges
440///   are being remapped, TLB invalidation may need to be performed
441///   afterwards.
442pub use arch::map;
443/// This function is presently used for reading the tracing data, also
444/// it is useful for debugging
445///
446/// # Safety
447/// This function traverses page table data structures, and should not
448/// be called concurrently with any other operations that modify the
449/// page table.
450pub use arch::virt_to_phys;
451
452//==================================================================================================
453// Multi-space (aliased page-table) walking
454//==================================================================================================
455
456/// Identifier for a virtual address space, used by the multi-space
457/// walker to describe which space "owns" a shared intermediate table.
458/// Implementations typically use the physical address of the root
459/// page table (which is unique per space).
460pub type SpaceId = u64;
461
462/// A reference from one address space to an intermediate page table
463/// that lives in a different space. Produced by [`walk_va_spaces`] when
464/// the walker encounters an intermediate table (at some `depth` below
465/// the root) whose physical address was already seen via an earlier
466/// root — i.e. the two spaces alias that sub-tree.
467///
468/// Semantics: the level-`depth` block in **our** space that contains
469/// VAs starting at `our_va` is aliased to the level-`depth` block in
470/// `space` that contains VAs starting at `their_va`. Everything below
471/// that sub-tree — PDEs, PTEs, leaf mappings — is shared wholesale.
472///
473/// `depth` is counted from the root:
474/// - `depth = 1, 2, 3` on amd64: PDPT, PD, or PT respectively.
475#[derive(Debug, Clone, Copy)]
476pub struct SpaceReferenceMapping {
477    /// Depth from the root at which the alias starts (1-based).
478    pub depth: usize,
479    /// The "owning" space — the first root that visited this
480    /// intermediate PA during [`walk_va_spaces`].
481    pub space: SpaceId,
482    /// Start VA of the aliased sub-tree in OUR space.
483    pub our_va: u64,
484    /// Start VA of the aliased sub-tree in the owning space. Usually
485    /// equal to `our_va` (kernel mappings at the same VA across
486    /// processes) but the design permits different VAs.
487    pub their_va: u64,
488}
489
490/// Either a normal leaf mapping in the current space, or a reference
491/// to an intermediate table in another space. The compaction loop in
492/// the host snapshotting code treats these two cases differently:
493///
494/// - `ThisSpace(m)` is rebuilt like any other leaf mapping: the
495///   backing page is compacted into the new snapshot blob, the PTE is
496///   written, and intermediate tables are allocated on demand.
497/// - `AnotherSpace(r)` is rebuilt by *linking*: the entry in our
498///   rebuilt root at depth `r.depth - 1` for `r.our_va` is made to
499///   point at whatever table the owning space ended up with at
500///   `r.their_va`. See [`space_aware_map`].
501#[derive(Debug)]
502pub enum SpaceAwareMapping {
503    ThisSpace(Mapping),
504    AnotherSpace(SpaceReferenceMapping),
505}
506
507/// Counterpart of [`walk_va_spaces`]'s `AnotherSpace` entries on the
508/// write side: installs a link in `op`'s root PT tree at `ref_map.our_va`
509/// that points at whatever intermediate table the owning space ended
510/// up with at `ref_map.their_va` (in `built_roots[ref_map.space]`).
511///
512/// Callers must ensure that `built_roots` contains populated page
513/// tables for any other space referenced by the mapping.
514///
515/// # Safety
516/// Same invariants as [`map`]: the caller owns the concurrency story
517/// around the page tables being written, and must invalidate TLBs
518/// afterwards if they were live.
519pub use arch::space_aware_map;
520/// Walk multiple page-table roots together, emitting either a normal
521/// leaf mapping (`ThisSpace`) or a reference to an alias that was
522/// already seen via an earlier root (`AnotherSpace`).
523///
524/// The caller passes `roots` in their preferred order of primacy. The
525/// first root to visit a particular intermediate PA becomes the
526/// "owner" of that sub-table — subsequent roots that alias it receive
527/// `AnotherSpace` entries referencing the owner.
528///
529/// The returned `Vec` is ordered the same way `roots` was passed — so
530/// by construction the result is topologically sorted: every
531/// `AnotherSpace` reference points to a space that appears earlier in
532/// the list. This lets a rebuilder process roots in iteration order
533/// without a separate sort pass, and guarantees that the
534/// [`space_aware_map`] invariant is met.
535///
536/// # Safety
537/// Same invariants as [`virt_to_phys`]. Callers must ensure the page
538/// tables are not being mutated concurrently.
539pub use arch::walk_va_spaces;