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;