nub_host_common/vmem.rs
1/*
2Copyright 2025 The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15 */
16
17#[path = "arch/amd64/vmem.rs"]
18mod arch;
19
20/// This is always the page size that the /guest/ is being compiled
21/// for, which may or may not be the same as the host page size.
22pub use arch::PAGE_SIZE;
23pub use arch::{PAGE_PRESENT, PAGE_TABLE_SIZE, PTE_ADDR_MASK, PageTableEntry, PhysAddr, VirtAddr};
24pub const PAGE_TABLE_ENTRIES_PER_TABLE: usize =
25 PAGE_TABLE_SIZE / core::mem::size_of::<PageTableEntry>();
26
27// Shared page table iterator infrastructure used by each arch module.
28
29/// Utility function to extract an (inclusive on both ends) bit range
30/// from a quadword.
31#[inline(always)]
32pub(in crate::vmem) fn bits<const HIGH_BIT: u8, const LOW_BIT: u8>(x: u64) -> u64 {
33 (x & ((1 << (HIGH_BIT + 1)) - 1)) >> LOW_BIT
34}
35
36/// Helper function to write a page table entry, updating the whole
37/// chain of tables back to the root if necessary.
38///
39/// # Safety
40/// Same requirements as [`TableOps::write_entry`].
41pub(in crate::vmem) unsafe fn write_entry_updating<
42 Op: TableOps,
43 P: UpdateParent<
44 Op,
45 TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
46 >,
47>(
48 op: &Op,
49 parent: P,
50 addr: Op::TableAddr,
51 entry: u64,
52) {
53 #[allow(clippy::useless_conversion)]
54 if let Some(again) = unsafe { op.write_entry(addr, entry as PageTableEntry) } {
55 parent.update_parent(op, again);
56 }
57}
58
59/// A helper trait that allows us to move a page table (e.g. from the
60/// snapshot to the scratch region), keeping track of the context that
61/// needs to be updated when that is moved (and potentially
62/// recursively updating, if necessary).
63///
64/// This is done via a trait so that the selected impl knows the exact
65/// nesting depth of tables, in order to assist
66/// inlining/specialisation in generating efficient code.
67///
68/// The trait definition only bounds its parameter by
69/// [`TableReadOps`], since [`UpdateParentNone`] does not need to be
70/// able to actually write to the tables.
71pub trait UpdateParent<Op: TableReadOps + ?Sized>: Copy {
72 /// The type of the information about a moved table which is
73 /// needed in order to update its parent.
74 type TableMoveInfo;
75 /// The [`UpdateParent`] type that should be used when going down
76 /// another level in the table, in order to add the current level
77 /// to the chain of ancestors to be updated.
78 type ChildType: UpdateParent<Op, TableMoveInfo = Self::TableMoveInfo>;
79 fn update_parent(self, op: &Op, new_ptr: Self::TableMoveInfo);
80 fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType;
81}
82
83/// A struct implementing [`UpdateParent`] that is impossible to use
84/// (since its [`UpdateParent::update_parent`] method takes [`Void`]),
85/// used when it is statically known that a table operation cannot
86/// result in a need to update ancestors.
87#[derive(Copy, Clone)]
88pub struct UpdateParentNone {}
89impl<Op: TableReadOps> UpdateParent<Op> for UpdateParentNone {
90 type TableMoveInfo = Void;
91 type ChildType = Self;
92 fn update_parent(self, _op: &Op, impossible: Void) {
93 match impossible {}
94 }
95 fn for_child_at_entry(self, _entry_ptr: Op::TableAddr) -> Self {
96 self
97 }
98}
99
100/// A helper structure indicating a mapping operation that needs to be
101/// performed.
102pub(in crate::vmem) struct MapRequest<Op: TableReadOps, P: UpdateParent<Op>> {
103 pub table_base: Op::TableAddr,
104 pub vmin: u64,
105 pub len: u64,
106 pub update_parent: P,
107}
108
109/// A helper structure indicating that a particular PTE needs to be
110/// modified.
111pub(in crate::vmem) struct MapResponse<Op: TableReadOps, P: UpdateParent<Op>> {
112 pub entry_ptr: Op::TableAddr,
113 pub vmin: u64,
114 pub len: u64,
115 pub update_parent: P,
116}
117
118/// Iterator that walks through page table entries at a specific level.
119///
120/// Given a virtual address range and a table base, this iterator yields
121/// `MapResponse` items for each page table entry that needs to be modified.
122/// The const generics `HIGH_BIT` and `LOW_BIT` specify which bits of the
123/// virtual address are used to index into this level's table.
124///
125/// For example on amd64:
126/// - PML4: HIGH_BIT=47, LOW_BIT=39 (9 bits = 512 entries, each covering 512GB)
127/// - PDPT: HIGH_BIT=38, LOW_BIT=30 (9 bits = 512 entries, each covering 1GB)
128/// - PD: HIGH_BIT=29, LOW_BIT=21 (9 bits = 512 entries, each covering 2MB)
129/// - PT: HIGH_BIT=20, LOW_BIT=12 (9 bits = 512 entries, each covering 4KB)
130///
131/// On i686:
132/// - PD: HIGH_BIT=31, LOW_BIT=22 (10 bits = 1024 entries, each covering 4MB)
133/// - PT: HIGH_BIT=21, LOW_BIT=12 (10 bits = 1024 entries, each covering 4KB)
134pub(in crate::vmem) struct ModifyPteIterator<
135 const HIGH_BIT: u8,
136 const LOW_BIT: u8,
137 Op: TableReadOps,
138 P: UpdateParent<Op>,
139> {
140 request: MapRequest<Op, P>,
141 n: u64,
142}
143impl<const HIGH_BIT: u8, const LOW_BIT: u8, Op: TableReadOps, P: UpdateParent<Op>> Iterator
144 for ModifyPteIterator<HIGH_BIT, LOW_BIT, Op, P>
145{
146 type Item = MapResponse<Op, P>;
147 fn next(&mut self) -> Option<Self::Item> {
148 // Each page table entry at this level covers a region of size
149 // (1 << LOW_BIT) bytes. For example, at the PT level
150 // (LOW_BIT=12), each entry covers 4KB (0x1000 bytes). At the
151 // PD level (LOW_BIT=21), each entry covers 2MB (0x200000
152 // bytes).
153 //
154 // This mask isolates the bits below this level's index bits,
155 // used for alignment.
156 let lower_bits_mask = (1u64 << LOW_BIT) - 1;
157
158 // Calculate the virtual address for this iteration.
159 // On the first iteration (n=0), start at the requested vmin.
160 // On subsequent iterations, advance to the next aligned boundary.
161 // This handles the case where vmin isn't aligned to this level's
162 // entry size.
163 let next_vmin = if self.n == 0 {
164 self.request.vmin
165 } else {
166 // Align to the next boundary by adding one entry's worth
167 // and masking off lower bits. Masking off before adding
168 // is safe, since n << LOW_BIT must always have zeros in
169 // these positions.
170 let aligned_min = self.request.vmin & !lower_bits_mask;
171 // Use checked_add because going past the end of the
172 // address space counts as "the next one would be out of
173 // range"
174 aligned_min.checked_add(self.n << LOW_BIT)?
175 };
176
177 // Check if we've processed the entire requested range
178 if next_vmin >= self.request.vmin + self.request.len {
179 return None;
180 }
181
182 // Calculate the pointer to this level's page table entry.
183 // bits::<HIGH_BIT, LOW_BIT> extracts the relevant index bits
184 // from the virtual address. Multiply by the PTE size to get
185 // the byte offset.
186 let pte_index = bits::<HIGH_BIT, LOW_BIT>(next_vmin);
187 let entry_ptr = Op::entry_addr(
188 self.request.table_base,
189 pte_index * core::mem::size_of::<PageTableEntry>() as u64,
190 );
191
192 // Calculate how many bytes remain to be mapped from this point.
193 let len_from_here = self.request.len - (next_vmin - self.request.vmin);
194 // Calculate the maximum bytes this single entry can cover.
195 // If next_vmin is aligned, this is the full entry size (1 << LOW_BIT).
196 // If not aligned (only possible on first iteration), it's the
197 // remaining space until the next boundary.
198 let max_len = (1u64 << LOW_BIT) - (next_vmin & lower_bits_mask);
199 // The actual length for this entry is the smaller of what's
200 // needed vs what fits.
201 let next_len = core::cmp::min(len_from_here, max_len);
202
203 // Advance iteration counter for next call
204 self.n += 1;
205
206 Some(MapResponse {
207 entry_ptr,
208 vmin: next_vmin,
209 len: next_len,
210 update_parent: self.request.update_parent,
211 })
212 }
213}
214
215pub(in crate::vmem) fn modify_ptes<
216 const HIGH_BIT: u8,
217 const LOW_BIT: u8,
218 Op: TableReadOps,
219 P: UpdateParent<Op>,
220>(
221 r: MapRequest<Op, P>,
222) -> ModifyPteIterator<HIGH_BIT, LOW_BIT, Op, P> {
223 ModifyPteIterator { request: r, n: 0 }
224}
225
226/// The read-only operations used to actually access the page table
227/// structures, used to allow the same code to be used in the host and
228/// the guest for page table setup. This is distinct from
229/// `TableWriteOps`, since there are some implementations for which
230/// writing does not make sense, and only reading is required.
231pub trait TableReadOps {
232 /// The type of table addresses
233 type TableAddr: Copy;
234
235 /// Offset the table address by the given offset in bytes.
236 ///
237 /// # Parameters
238 /// - `addr`: The base address of the table.
239 /// - `entry_offset`: The offset in **bytes** within the page table. This is
240 /// not an entry index; callers must multiply the entry index by the size
241 /// of a page table entry (typically 8 bytes) to obtain the correct byte offset.
242 ///
243 /// # Returns
244 /// The address of the entry at the given byte offset from the base address.
245 fn entry_addr(addr: Self::TableAddr, entry_offset: u64) -> Self::TableAddr;
246
247 /// Read a u64 from the given address, used to read existing page
248 /// table entries
249 ///
250 /// # Safety
251 /// This reads from the given memory address, and so all the usual
252 /// Rust things about raw pointers apply. This will also be used
253 /// to update guest page tables, so especially in the guest, it is
254 /// important to ensure that the page tables updates do not break
255 /// invariants. The implementor of the trait should ensure that
256 /// nothing else will be reading/writing the address at the same
257 /// time as mapping code using the trait.
258 unsafe fn read_entry(&self, addr: Self::TableAddr) -> PageTableEntry;
259
260 /// Convert an abstract table address to a concrete physical address (u64)
261 /// which can be e.g. written into a page table entry
262 fn to_phys(addr: Self::TableAddr) -> PhysAddr;
263
264 /// Convert a concrete physical address (u64) which may have been e.g. read
265 /// from a page table entry back into an abstract table address
266 fn from_phys(addr: PhysAddr) -> Self::TableAddr;
267
268 /// Return the address of the root page table
269 fn root_table(&self) -> Self::TableAddr;
270}
271
272/// Our own version of ! until it is stable. Used to avoid needing to
273/// implement [`TableOps::update_root`] for ops that never need
274/// to move a table.
275pub enum Void {}
276
277/// A marker struct, used by an implementation of [`TableOps`] to
278/// indicate that it may need to move existing page tables
279pub struct MayMoveTable {}
280/// A marker struct, used by an implementation of [`TableOps`] to
281/// indicate that it will be able to update existing page tables
282/// in-place, without moving them.
283pub struct MayNotMoveTable {}
284
285mod sealed {
286 use super::{MayMoveTable, MayNotMoveTable, TableReadOps, Void};
287
288 /// A (purposefully-not-exposed) internal implementation detail of the
289 /// logic around whether a [`TableOps`] implementation may or may not
290 /// move page tables.
291 pub trait TableMovabilityBase<Op: TableReadOps + ?Sized> {
292 type TableMoveInfo;
293 }
294 impl<Op: TableReadOps> TableMovabilityBase<Op> for MayMoveTable {
295 type TableMoveInfo = Op::TableAddr;
296 }
297 impl<Op: TableReadOps> TableMovabilityBase<Op> for MayNotMoveTable {
298 type TableMoveInfo = Void;
299 }
300}
301use sealed::*;
302
303/// A sealed trait used to collect some information about the marker structures [`MayMoveTable`] and [`MayNotMoveTable`]
304pub trait TableMovability<Op: TableReadOps + ?Sized>:
305 TableMovabilityBase<Op>
306 + arch::TableMovability<Op, <Self as TableMovabilityBase<Op>>::TableMoveInfo>
307{
308}
309impl<
310 Op: TableReadOps,
311 T: TableMovabilityBase<Op>
312 + arch::TableMovability<Op, <Self as TableMovabilityBase<Op>>::TableMoveInfo>,
313> TableMovability<Op> for T
314{
315}
316
317/// The operations used to actually access the page table structures
318/// that involve writing to them, used to allow the same code to be
319/// used in the host and the guest for page table setup.
320pub trait TableOps: TableReadOps {
321 /// This marker should be either [`MayMoveTable`] or
322 /// [`MayNotMoveTable`], as the case may be.
323 ///
324 /// If this is [`MayMoveTable`], the return type of
325 /// [`Self::write_entry`] and the parameter type of
326 /// [`Self::update_root`] will be `<Self as
327 /// TableReadOps>::TableAddr`. If it is [`MayNotMoveTable`], those
328 /// types will be [`Void`].
329 type TableMovability: TableMovability<Self>;
330
331 /// Allocate a zeroed table
332 ///
333 /// # Safety
334 /// The current implementations of this function are not
335 /// inherently unsafe, but the guest implementation will likely
336 /// become so in the future when a real physical page allocator is
337 /// implemented.
338 ///
339 /// Currently, callers should take care not to call this on
340 /// multiple threads at the same time.
341 ///
342 /// # Panics
343 /// This function may panic if:
344 /// - The Layout creation fails
345 /// - Memory allocation fails
346 unsafe fn alloc_table(&self) -> Self::TableAddr;
347
348 /// Write a u64 to the given address, used to write updated page
349 /// table entries. In some cases,the page table in which the entry
350 /// is located may need to be relocated in order for this to
351 /// succeed; if this is the case, the base address of the new
352 /// table is returned.
353 ///
354 /// # Safety
355 /// This writes to the given memory address, and so all the usual
356 /// Rust things about raw pointers apply. This will also be used
357 /// to update guest page tables, so especially in the guest, it is
358 /// important to ensure that the page tables updates do not break
359 /// invariants. The implementor of the trait should ensure that
360 /// nothing else will be reading/writing the address at the same
361 /// time as mapping code using the trait.
362 unsafe fn write_entry(
363 &self,
364 addr: Self::TableAddr,
365 entry: PageTableEntry,
366 ) -> Option<<Self::TableMovability as TableMovabilityBase<Self>>::TableMoveInfo>;
367
368 /// Change the root page table to one at a different address
369 ///
370 /// # Safety
371 /// This function will directly result in a change to virtual
372 /// memory translation, and so is inherently unsafe w.r.t. the
373 /// Rust memory model. All the caveats listed on [`map`] apply as
374 /// well.
375 unsafe fn update_root(
376 &self,
377 new_root: <Self::TableMovability as TableMovabilityBase<Self>>::TableMoveInfo,
378 );
379}
380
381#[derive(Debug, PartialEq, Clone, Copy)]
382pub struct BasicMapping {
383 pub readable: bool,
384 pub writable: bool,
385 pub executable: bool,
386}
387
388#[derive(Debug, PartialEq, Clone, Copy)]
389pub struct CowMapping {
390 pub readable: bool,
391 pub executable: bool,
392}
393
394#[derive(Debug, PartialEq, Clone, Copy)]
395pub enum MappingKind {
396 Unmapped,
397 Basic(BasicMapping),
398 Cow(CowMapping),
399 /* TODO: What useful things other than basic mappings actually
400 * require touching the tables? */
401}
402
403#[derive(Debug)]
404pub struct Mapping {
405 pub phys_base: u64,
406 pub virt_base: u64,
407 pub len: u64,
408 pub kind: MappingKind,
409 /// On architectures that support multiple privilege levels inside
410 /// the guest, whether the mapping is accessible to the
411 /// lower-privileged level (with the same permissions/behaviour as
412 /// the upper-privileged level, for now).
413 pub user_accessible: bool,
414}
415
416/// Assumption: all are page-aligned
417///
418/// # Safety
419/// This function modifies pages backing a virtual memory range which
420/// is inherently unsafe w.r.t. the Rust memory model.
421///
422/// When using this function, please note:
423/// - No locking is performed before touching page table data structures,
424/// as such do not use concurrently with any other page table operations
425/// - TLB invalidation is not performed, if previously-mapped ranges
426/// are being remapped, TLB invalidation may need to be performed
427/// afterwards.
428pub use arch::map;
429/// This function is presently used for reading the tracing data, also
430/// it is useful for debugging
431///
432/// # Safety
433/// This function traverses page table data structures, and should not
434/// be called concurrently with any other operations that modify the
435/// page table.
436pub use arch::virt_to_phys;