Skip to content

Commit a2ece56

Browse files
committed
Neaten up page table operations structures
This splits `hyperlight_common::vmem::TableOps` into `TableReadOps` and `TableOps`, so that implementations like the one in snapshot.rs that cannot support writes don't have to provide the (extremely partial!) write functions. This is slightly involved, because we need to keep to track of the right information in the type system about whether writes are possible, and, if so, whether they can move existing page tables. The implementation of of writing to cr3 to update page tables is also moved into `hyperlight_guest_bin::paging`, where it belongs, which is a simple change. Signed-off-by: Lucy Menon <[email protected]>
1 parent 54cd065 commit a2ece56

File tree

6 files changed

+320
-164
lines changed

6 files changed

+320
-164
lines changed

src/hyperlight_common/src/arch/amd64/vmem.rs

Lines changed: 146 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ limitations under the License.
2525
//! The code uses an iterator-based approach to walk the page table hierarchy,
2626
//! allocating intermediate tables as needed and setting appropriate flags on leaf PTEs
2727
28-
use crate::vmem::{BasicMapping, Mapping, MappingKind, TableOps};
28+
use crate::vmem::{
29+
BasicMapping, Mapping, MappingKind, TableMovabilityBase, TableOps, TableReadOps, Void,
30+
};
2931

3032
// Paging Flags
3133
//
@@ -78,7 +80,7 @@ const fn page_nx_flag(executable: bool) -> u64 {
7880
/// # Safety
7981
/// The caller must ensure that `entry_ptr` points to a valid page table entry.
8082
#[inline(always)]
81-
unsafe fn read_pte_if_present<Op: TableOps>(op: &Op, entry_ptr: Op::TableAddr) -> Option<u64> {
83+
unsafe fn read_pte_if_present<Op: TableReadOps>(op: &Op, entry_ptr: Op::TableAddr) -> Option<u64> {
8284
let pte = unsafe { op.read_entry(entry_ptr) };
8385
if (pte & PAGE_PRESENT) != 0 {
8486
Some(pte)
@@ -107,9 +109,37 @@ fn pte_for_table<Op: TableOps>(table_addr: Op::TableAddr) -> u64 {
107109
PAGE_PRESENT // P - this entry is present
108110
}
109111

112+
/// This trait is used to select appropriate implementations of
113+
/// [`UpdateParent`] to be used, depending on whether a particular
114+
/// implementation needs the ability to move tables.
115+
pub trait TableMovability<Op: TableReadOps + ?Sized, TableMoveInfo> {
116+
type RootUpdateParent: UpdateParent<Op, TableMoveInfo = TableMoveInfo>;
117+
fn root_update_parent() -> Self::RootUpdateParent;
118+
}
119+
impl<Op: TableOps<TableMovability = crate::vmem::MayMoveTable>> TableMovability<Op, Op::TableAddr>
120+
for crate::vmem::MayMoveTable
121+
{
122+
type RootUpdateParent = UpdateParentRoot;
123+
fn root_update_parent() -> Self::RootUpdateParent {
124+
UpdateParentRoot {}
125+
}
126+
}
127+
impl<Op: TableReadOps> TableMovability<Op, Void> for crate::vmem::MayNotMoveTable {
128+
type RootUpdateParent = UpdateParentNone;
129+
fn root_update_parent() -> Self::RootUpdateParent {
130+
UpdateParentNone {}
131+
}
132+
}
133+
110134
/// Helper function to write a page table entry, updating the whole
111135
/// chain of tables back to the root if necessary
112-
unsafe fn write_entry_updating<Op: TableOps, P: UpdateParent<Op>>(
136+
unsafe fn write_entry_updating<
137+
Op: TableOps,
138+
P: UpdateParent<
139+
Op,
140+
TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
141+
>,
142+
>(
113143
op: &Op,
114144
parent: P,
115145
addr: Op::TableAddr,
@@ -128,14 +158,26 @@ unsafe fn write_entry_updating<Op: TableOps, P: UpdateParent<Op>>(
128158
/// This is done via a trait so that the selected impl knows the exact
129159
/// nesting depth of tables, in order to assist
130160
/// inlining/specialisation in generating efficient code.
131-
trait UpdateParent<Op: TableOps>: Copy {
132-
fn update_parent(self, op: &Op, new_ptr: Op::TableAddr);
161+
///
162+
/// The trait definition only bounds its parameter by
163+
/// [`TableReadOps`], since [`UpdateParentNone`] does not need to be
164+
/// able to actually write to the tables.
165+
pub trait UpdateParent<Op: TableReadOps + ?Sized>: Copy {
166+
/// The type of the information about a moved table which is
167+
/// needed in order to update its parent.
168+
type TableMoveInfo;
169+
/// The [`UpdateParent`] type that should be used when going down
170+
/// another level in the table, in order to add the current level
171+
/// to the chain of ancestors to be updated.
172+
type ChildType: UpdateParent<Op, TableMoveInfo = Self::TableMoveInfo>;
173+
fn update_parent(self, op: &Op, new_ptr: Self::TableMoveInfo);
174+
fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType;
133175
}
134176

135177
/// A struct implementing [`UpdateParent`] that keeps track of the
136178
/// fact that the parent table is itself another table, whose own
137179
/// ancestors may need to be recursively updated
138-
struct UpdateParentTable<Op: TableOps, P: UpdateParent<Op>> {
180+
pub struct UpdateParentTable<Op: TableOps, P: UpdateParent<Op>> {
139181
parent: P,
140182
entry_ptr: Op::TableAddr,
141183
}
@@ -150,45 +192,64 @@ impl<Op: TableOps, P: UpdateParent<Op>> UpdateParentTable<Op, P> {
150192
UpdateParentTable { parent, entry_ptr }
151193
}
152194
}
153-
impl<Op: TableOps, P: UpdateParent<Op>> UpdateParent<Op> for UpdateParentTable<Op, P> {
195+
impl<
196+
Op: TableOps<TableMovability = crate::vmem::MayMoveTable>,
197+
P: UpdateParent<Op, TableMoveInfo = Op::TableAddr>,
198+
> UpdateParent<Op> for UpdateParentTable<Op, P>
199+
{
200+
type TableMoveInfo = Op::TableAddr;
201+
type ChildType = UpdateParentTable<Op, Self>;
154202
fn update_parent(self, op: &Op, new_ptr: Op::TableAddr) {
155203
let pte = pte_for_table::<Op>(new_ptr);
156204
unsafe {
157205
write_entry_updating(op, self.parent, self.entry_ptr, pte);
158206
}
159207
}
208+
fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType {
209+
Self::ChildType::new(self, entry_ptr)
210+
}
160211
}
161212

162213
/// A struct implementing [`UpdateParent`] that keeps track of the
163-
/// fact that the parent "table" is actually the CR3 system register.
214+
/// fact that the parent "table" is actually the root (e.g. the value
215+
/// of CR3 in the guest)
164216
#[derive(Copy, Clone)]
165-
struct UpdateParentCR3 {}
166-
impl<Op: TableOps> UpdateParent<Op> for UpdateParentCR3 {
167-
fn update_parent(self, _op: &Op, new_ptr: Op::TableAddr) {
217+
pub struct UpdateParentRoot {}
218+
impl<Op: TableOps<TableMovability = crate::vmem::MayMoveTable>> UpdateParent<Op>
219+
for UpdateParentRoot
220+
{
221+
type TableMoveInfo = Op::TableAddr;
222+
type ChildType = UpdateParentTable<Op, Self>;
223+
fn update_parent(self, op: &Op, new_ptr: Op::TableAddr) {
168224
unsafe {
169-
core::arch::asm!("mov cr3, {}", in(reg) Op::to_phys(new_ptr));
225+
op.update_root(new_ptr);
170226
}
171227
}
228+
fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType {
229+
Self::ChildType::new(self, entry_ptr)
230+
}
172231
}
173232

174-
/// A struct implementing [`UpdateParent`] that panics when used, for
175-
/// the occasional situations in which we should never do an update
233+
/// A struct implementing [`UpdateParent`] that is impossible to use
234+
/// (since its [`update_parent`] method takes `Void`), used when it is
235+
/// statically known that a table operation cannot result in a need to
236+
/// update ancestors.
176237
#[derive(Copy, Clone)]
177-
struct UpdateParentNone {}
178-
impl<Op: TableOps> UpdateParent<Op> for UpdateParentNone {
179-
// This is only used in contexts which should absolutely never try
180-
// to update a page table, and so in no case should possibly ever
181-
// call an update_parent. Therefore, this is impossible unless an
182-
// extremely significant invariant violation has occurred.
183-
#[allow(clippy::panic)]
184-
fn update_parent(self, _op: &Op, _new_ptr: Op::TableAddr) {
185-
panic!("UpdateParentNone: tried to update parent");
238+
pub struct UpdateParentNone {}
239+
impl<Op: TableReadOps> UpdateParent<Op> for UpdateParentNone {
240+
type TableMoveInfo = Void;
241+
type ChildType = Self;
242+
fn update_parent(self, _op: &Op, impossible: Void) {
243+
match impossible {}
244+
}
245+
fn for_child_at_entry(self, _entry_ptr: Op::TableAddr) -> Self {
246+
self
186247
}
187248
}
188249

189250
/// A helper structure indicating a mapping operation that needs to be
190251
/// performed
191-
struct MapRequest<Op: TableOps, P: UpdateParent<Op>> {
252+
struct MapRequest<Op: TableReadOps, P: UpdateParent<Op>> {
192253
table_base: Op::TableAddr,
193254
vmin: VirtAddr,
194255
len: u64,
@@ -197,7 +258,7 @@ struct MapRequest<Op: TableOps, P: UpdateParent<Op>> {
197258

198259
/// A helper structure indicating that a particular PTE needs to be
199260
/// modified
200-
struct MapResponse<Op: TableOps, P: UpdateParent<Op>> {
261+
struct MapResponse<Op: TableReadOps, P: UpdateParent<Op>> {
201262
entry_ptr: Op::TableAddr,
202263
vmin: VirtAddr,
203264
len: u64,
@@ -216,11 +277,16 @@ struct MapResponse<Op: TableOps, P: UpdateParent<Op>> {
216277
/// - PDPT: HIGH_BIT=38, LOW_BIT=30 (9 bits = 512 entries, each covering 1GB)
217278
/// - PD: HIGH_BIT=29, LOW_BIT=21 (9 bits = 512 entries, each covering 2MB)
218279
/// - PT: HIGH_BIT=20, LOW_BIT=12 (9 bits = 512 entries, each covering 4KB)
219-
struct ModifyPteIterator<const HIGH_BIT: u8, const LOW_BIT: u8, Op: TableOps, P: UpdateParent<Op>> {
280+
struct ModifyPteIterator<
281+
const HIGH_BIT: u8,
282+
const LOW_BIT: u8,
283+
Op: TableReadOps,
284+
P: UpdateParent<Op>,
285+
> {
220286
request: MapRequest<Op, P>,
221287
n: u64,
222288
}
223-
impl<const HIGH_BIT: u8, const LOW_BIT: u8, Op: TableOps, P: UpdateParent<Op>> Iterator
289+
impl<const HIGH_BIT: u8, const LOW_BIT: u8, Op: TableReadOps, P: UpdateParent<Op>> Iterator
224290
for ModifyPteIterator<HIGH_BIT, LOW_BIT, Op, P>
225291
{
226292
type Item = MapResponse<Op, P>;
@@ -286,7 +352,7 @@ impl<const HIGH_BIT: u8, const LOW_BIT: u8, Op: TableOps, P: UpdateParent<Op>> I
286352
})
287353
}
288354
}
289-
fn modify_ptes<const HIGH_BIT: u8, const LOW_BIT: u8, Op: TableOps, P: UpdateParent<Op>>(
355+
fn modify_ptes<const HIGH_BIT: u8, const LOW_BIT: u8, Op: TableReadOps, P: UpdateParent<Op>>(
290356
r: MapRequest<Op, P>,
291357
) -> ModifyPteIterator<HIGH_BIT, LOW_BIT, Op, P> {
292358
ModifyPteIterator { request: r, n: 0 }
@@ -296,11 +362,20 @@ fn modify_ptes<const HIGH_BIT: u8, const LOW_BIT: u8, Op: TableOps, P: UpdatePar
296362
/// # Safety
297363
/// This function modifies page table data structures, and should not be called concurrently
298364
/// with any other operations that modify the page tables.
299-
unsafe fn alloc_pte_if_needed<Op: TableOps, P: UpdateParent<Op>>(
365+
unsafe fn alloc_pte_if_needed<
366+
Op: TableOps,
367+
P: UpdateParent<
368+
Op,
369+
TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
370+
>,
371+
>(
300372
op: &Op,
301373
x: MapResponse<Op, P>,
302-
) -> MapRequest<Op, UpdateParentTable<Op, P>> {
303-
let new_update_parent = UpdateParentTable::new(x.update_parent, x.entry_ptr);
374+
) -> MapRequest<Op, P::ChildType>
375+
where
376+
P::ChildType: UpdateParent<Op>,
377+
{
378+
let new_update_parent = x.update_parent.for_child_at_entry(x.entry_ptr);
304379
if let Some(pte) = unsafe { read_pte_if_present(op, x.entry_ptr) } {
305380
return MapRequest {
306381
table_base: Op::from_phys(pte & PTE_ADDR_MASK),
@@ -330,7 +405,13 @@ unsafe fn alloc_pte_if_needed<Op: TableOps, P: UpdateParent<Op>>(
330405
/// with any other operations that modify the page tables.
331406
#[allow(clippy::identity_op)]
332407
#[allow(clippy::precedence)]
333-
unsafe fn map_page<Op: TableOps, P: UpdateParent<Op>>(
408+
unsafe fn map_page<
409+
Op: TableOps,
410+
P: UpdateParent<
411+
Op,
412+
TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
413+
>,
414+
>(
334415
op: &Op,
335416
mapping: &Mapping,
336417
r: MapResponse<Op, P>,
@@ -383,7 +464,7 @@ pub unsafe fn map<Op: TableOps>(op: &Op, mapping: Mapping) {
383464
table_base: op.root_table(),
384465
vmin: mapping.virt_base,
385466
len: mapping.len,
386-
update_parent: UpdateParentCR3 {},
467+
update_parent: Op::TableMovability::root_update_parent(),
387468
})
388469
.map(|r| unsafe { alloc_pte_if_needed(op, r) })
389470
.flat_map(modify_ptes::<38, 30, Op, _>)
@@ -399,15 +480,18 @@ pub unsafe fn map<Op: TableOps>(op: &Op, mapping: Mapping) {
399480
/// This function traverses page table data structures, and should not
400481
/// be called concurrently with any other operations that modify the
401482
/// page table.
402-
unsafe fn require_pte_exist<Op: TableOps, P: UpdateParent<Op>>(
483+
unsafe fn require_pte_exist<Op: TableReadOps, P: UpdateParent<Op>>(
403484
op: &Op,
404485
x: MapResponse<Op, P>,
405-
) -> Option<MapRequest<Op, UpdateParentTable<Op, P>>> {
486+
) -> Option<MapRequest<Op, P::ChildType>>
487+
where
488+
P::ChildType: UpdateParent<Op>,
489+
{
406490
unsafe { read_pte_if_present(op, x.entry_ptr) }.map(|pte| MapRequest {
407491
table_base: Op::from_phys(pte & PTE_ADDR_MASK),
408492
vmin: x.vmin,
409493
len: x.len,
410-
update_parent: UpdateParentTable::new(x.update_parent, x.entry_ptr),
494+
update_parent: x.update_parent.for_child_at_entry(x.entry_ptr),
411495
})
412496
}
413497

@@ -429,7 +513,7 @@ unsafe fn require_pte_exist<Op: TableOps, P: UpdateParent<Op>>(
429513
/// operations structure owns a large buffer, a reference can instead
430514
/// be passed.
431515
#[allow(clippy::missing_safety_doc)]
432-
pub unsafe fn virt_to_phys<'a, Op: TableOps + 'a>(
516+
pub unsafe fn virt_to_phys<'a, Op: TableReadOps + 'a>(
433517
op: impl core::convert::AsRef<Op> + Copy + 'a,
434518
address: u64,
435519
len: u64,
@@ -480,7 +564,10 @@ mod tests {
480564
use core::cell::RefCell;
481565

482566
use super::*;
483-
use crate::vmem::{BasicMapping, Mapping, MappingKind, PAGE_TABLE_ENTRIES_PER_TABLE, TableOps};
567+
use crate::vmem::{
568+
BasicMapping, Mapping, MappingKind, MayNotMoveTable, PAGE_TABLE_ENTRIES_PER_TABLE,
569+
TableOps, TableReadOps, Void,
570+
};
484571

485572
/// A mock TableOps implementation for testing that stores page tables in memory
486573
/// needed because the `GuestPageTableBuffer` is in hyperlight_host which would cause a circular dependency
@@ -512,16 +599,9 @@ mod tests {
512599
}
513600
}
514601

515-
impl TableOps for MockTableOps {
602+
impl TableReadOps for MockTableOps {
516603
type TableAddr = (usize, usize); // (table_index, entry_index)
517604

518-
unsafe fn alloc_table(&self) -> Self::TableAddr {
519-
let mut tables = self.tables.borrow_mut();
520-
let idx = tables.len();
521-
tables.push([0u64; PAGE_TABLE_ENTRIES_PER_TABLE]);
522-
(idx, 0)
523-
}
524-
525605
fn entry_addr(addr: Self::TableAddr, entry_offset: u64) -> Self::TableAddr {
526606
// Convert to physical address, add offset, convert back
527607
let phys = Self::to_phys(addr) + entry_offset;
@@ -532,11 +612,6 @@ mod tests {
532612
self.tables.borrow()[addr.0][addr.1]
533613
}
534614

535-
unsafe fn write_entry(&self, addr: Self::TableAddr, entry: u64) -> Option<Self::TableAddr> {
536-
self.tables.borrow_mut()[addr.0][addr.1] = entry;
537-
None
538-
}
539-
540615
fn to_phys(addr: Self::TableAddr) -> PhysAddr {
541616
// Each table is 4KB, entries are 8 bytes
542617
(addr.0 as u64 * PAGE_TABLE_SIZE as u64) + (addr.1 as u64 * 8)
@@ -553,6 +628,26 @@ mod tests {
553628
}
554629
}
555630

631+
impl TableOps for MockTableOps {
632+
type TableMovability = MayNotMoveTable;
633+
634+
unsafe fn alloc_table(&self) -> Self::TableAddr {
635+
let mut tables = self.tables.borrow_mut();
636+
let idx = tables.len();
637+
tables.push([0u64; PAGE_TABLE_ENTRIES_PER_TABLE]);
638+
(idx, 0)
639+
}
640+
641+
unsafe fn write_entry(&self, addr: Self::TableAddr, entry: u64) -> Option<Void> {
642+
self.tables.borrow_mut()[addr.0][addr.1] = entry;
643+
None
644+
}
645+
646+
unsafe fn update_root(&self, impossible: Void) {
647+
match impossible {}
648+
}
649+
}
650+
556651
// ==================== bits() function tests ====================
557652

558653
#[test]

src/hyperlight_common/src/arch/i686/vmem.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ limitations under the License.
1717
// This file is just dummy definitions at the moment, in order to
1818
// allow compiling the guest for real mode boot scenarios.
1919

20-
use crate::vmem::{BasicMapping, Mapping, TableOps};
20+
use crate::vmem::{BasicMapping, Mapping, TableOps, TableReadOps, Void};
2121

2222
pub const PAGE_SIZE: usize = 4096;
2323
pub const PAGE_TABLE_SIZE: usize = 4096;
@@ -43,3 +43,10 @@ pub unsafe fn virt_to_phys<Op: TableOps>(
4343
#[allow(unreachable_code)]
4444
core::iter::empty()
4545
}
46+
47+
pub trait TableMovability<Op: TableReadOps + ?Sized, TableMoveInfo> {}
48+
impl<Op: TableOps<TableMovability = crate::vmem::MayMoveTable>> TableMovability<Op, Op::TableAddr>
49+
for crate::vmem::MayMoveTable
50+
{
51+
}
52+
impl<Op: TableReadOps> TableMovability<Op, Void> for crate::vmem::MayNotMoveTable {}

0 commit comments

Comments
 (0)