openhcl_boot/arch/x86_64/
address_space.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Local map and limited virtual address space manipulation support for the
5//! bootshim.
6
7//! Certain configurations of the bootshim need the ability to map in arbitrary
8//! GPAs to process their contents in various ways. Additionally, certain VAs
9//! need to be made host visible for certain periods of time. This module
10//! provides the necessary support for manipulating the paging structures
11//! involved.
12
13use crate::single_threaded::SingleThreaded;
14use core::arch::asm;
15use core::cell::Cell;
16use core::marker::PhantomData;
17use core::sync::atomic::AtomicU64;
18use core::sync::atomic::Ordering;
19use core::sync::atomic::compiler_fence;
20use hvdef::HV_PAGE_SIZE;
21use memory_range::MemoryRange;
22use x86defs::X64_LARGE_PAGE_SIZE;
23use x86defs::tdx::TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
24use zerocopy::FromBytes;
25use zerocopy::Immutable;
26use zerocopy::IntoBytes;
27use zerocopy::KnownLayout;
28
29const X64_PTE_PRESENT: u64 = 1;
30const X64_PTE_READ_WRITE: u64 = 1 << 1;
31const X64_PTE_ACCESSED: u64 = 1 << 5;
32const X64_PTE_DIRTY: u64 = 1 << 6;
33const X64_PTE_LARGE_PAGE: u64 = 1 << 7;
34const X64_PTE_CONFIDENTIAL: u64 = 1 << 51;
35
36const PAGE_TABLE_ENTRY_COUNT: usize = 512;
37
38const X64_PAGE_SHIFT: u64 = 12;
39const X64_PTE_BITS: u64 = 9;
40
41#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
42#[repr(transparent)]
43struct PageTableEntry {
44    entry: u64,
45}
46#[derive(Debug, Copy, Clone)]
47pub enum PageTableEntryType {
48    Leaf2MbPage(u64),
49}
50
51impl PageTableEntry {
52    fn atomic_pte<'a>(&self) -> &'a AtomicU64 {
53        // SAFETY: Casting a u64 to an atomic u64 via pointer is safe. All accesses to the u64 are
54        // consistently performed using this method.
55        unsafe {
56            let ptr = &self.entry as *const u64;
57            &*ptr.cast()
58        }
59    }
60
61    fn write_pte(&mut self, val: u64) {
62        self.atomic_pte().store(val, Ordering::SeqCst);
63    }
64
65    fn read_pte(&self) -> u64 {
66        self.atomic_pte().load(Ordering::Relaxed)
67    }
68
69    /// Set an AMD64 PDE to either represent a leaf 2MB page or PDE.
70    /// This sets the PTE to preset, accessed, dirty, read write execute.
71    pub fn set_entry(&mut self, entry_type: PageTableEntryType, confidential: bool) {
72        let mut entry: u64 = X64_PTE_PRESENT | X64_PTE_ACCESSED | X64_PTE_READ_WRITE;
73        if confidential {
74            entry |= X64_PTE_CONFIDENTIAL;
75        }
76
77        match entry_type {
78            PageTableEntryType::Leaf2MbPage(address) => {
79                // Leaf entry, set like UEFI does for 2MB pages. Must be 2MB aligned.
80                assert!(address % X64_LARGE_PAGE_SIZE == 0);
81                entry |= address;
82                entry |= X64_PTE_LARGE_PAGE | X64_PTE_DIRTY;
83            }
84        }
85
86        self.write_pte(entry);
87    }
88
89    pub fn is_present(&self) -> bool {
90        self.read_pte() & X64_PTE_PRESENT == X64_PTE_PRESENT
91    }
92
93    pub fn is_large_page(&self) -> bool {
94        self.entry & X64_PTE_LARGE_PAGE == X64_PTE_LARGE_PAGE
95    }
96
97    pub fn get_addr(&self) -> u64 {
98        const VALID_BITS: u64 = 0x000f_ffff_ffff_f000;
99
100        self.read_pte() & VALID_BITS & !X64_PTE_CONFIDENTIAL
101    }
102
103    pub fn clear(&mut self) {
104        self.write_pte(0);
105    }
106
107    /// Check the TDX shared bit on a page table entry
108    pub fn tdx_is_shared(&mut self) -> bool {
109        let val = self.read_pte();
110        val & TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT == TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
111    }
112
113    /// Set the TDX shared bit on a page table entry
114    pub fn tdx_set_shared(&mut self) {
115        let mut val = self.read_pte();
116        val |= TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
117        self.write_pte(val);
118    }
119
120    /// Unset the TDX shared bit on a page table entry
121    pub fn tdx_set_private(&mut self) {
122        let mut val = self.read_pte();
123        val &= !TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
124        self.write_pte(val);
125    }
126}
127
128#[repr(C)]
129#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
130struct PageTable {
131    entries: [PageTableEntry; PAGE_TABLE_ENTRY_COUNT],
132}
133
134impl PageTable {
135    /// Treat this page table as a page table of a given level, and locate the
136    /// entry corresponding to a va.
137    pub fn entry(&mut self, gva: u64, level: u8) -> &mut PageTableEntry {
138        let index = get_amd64_pte_index(gva, level as u64) as usize;
139        &mut self.entries[index]
140    }
141}
142
143/// Get an AMD64 PTE index based on page table level.
144fn get_amd64_pte_index(gva: u64, page_map_level: u64) -> u64 {
145    let index = gva >> (X64_PAGE_SHIFT + page_map_level * X64_PTE_BITS);
146    index & ((1 << X64_PTE_BITS) - 1)
147}
148
149/// Local Map. Provides a VA region where arbitrary physical addresses can be
150/// mapped into the virtual address space on the current processor.
151pub struct LocalMap<'a> {
152    pte_ptr: *mut PageTableEntry,
153    va: u64,
154    _dummy: PhantomData<&'a ()>,
155}
156
157impl<'a> LocalMap<'a> {
158    /// Maps in a contiguous page range into the local VA space.
159    /// `range` specifies the address range to map.
160    /// `confidential` indicates whether a confidential mapping is required.
161    pub fn map_pages<'b>(
162        &'b mut self,
163        range: MemoryRange,
164        confidential: bool,
165    ) -> LocalMapMapping<'a, 'b> {
166        let offset = range.start() % X64_LARGE_PAGE_SIZE;
167        assert!(offset + range.len() <= X64_LARGE_PAGE_SIZE, "{range}");
168
169        let aligned_gpa = range.start() - offset;
170        let entry = self.local_map_entry();
171        assert!(!entry.is_present());
172        entry.set_entry(PageTableEntryType::Leaf2MbPage(aligned_gpa), confidential);
173        let va = self.va + offset;
174        // Prevent the compiler from moving any subsequent accesses to the local mapped pages to before
175        // the mapping has actually been established in the page tables.
176        compiler_fence(Ordering::SeqCst);
177        // SAFETY: The va for the local map is part of the measured build. We've validated the range
178        // is within bounds. We've checked that no entry is already present, so uniqueness is guaranteed.
179        let buffer =
180            unsafe { core::slice::from_raw_parts_mut(va as *mut u8, range.len() as usize) };
181        LocalMapMapping {
182            data: buffer,
183            local_map: self,
184        }
185    }
186
187    fn local_map_entry(&self) -> &'a mut PageTableEntry {
188        // SAFETY: Called only once the local map has been initialized.
189        unsafe { &mut *self.pte_ptr }
190    }
191}
192
193pub struct LocalMapMapping<'a, 'b> {
194    pub data: &'a mut [u8],
195    local_map: &'b mut LocalMap<'a>,
196}
197
198impl Drop for LocalMapMapping<'_, '_> {
199    fn drop(&mut self) {
200        unmap_page_helper(self.local_map);
201    }
202}
203
204fn unmap_page_helper(local_map: &LocalMap<'_>) {
205    // All accesses to the local map must complete before clearing the PTE.
206    compiler_fence(Ordering::SeqCst);
207    // SAFETY: Clearing the stored local map pde and issuing invlpg, which is a benign instruction.
208    // This routine must only be called once the local map has been initialized.
209    unsafe {
210        let entry = &mut *local_map.pte_ptr;
211        entry.clear();
212        let va = local_map.va;
213        asm!("invlpg [{0}]", in(reg) va);
214    }
215}
216
217/// Returns a reference to the page table page located at the specified physical
218/// address.
219///
220/// # Safety
221/// Caller ensures that the specified address is actually that of a page table.
222unsafe fn page_table_at_address(address: u64) -> &'static mut PageTable {
223    // SAFETY: Guaranteed by caller.
224    unsafe { &mut *(address as *mut u64).cast() }
225}
226
227/// Returns a reference to the PDE corresponding to a virtual address.
228///
229/// # Safety
230///
231/// This routine requires the caller to ensure that the VA is a valid one for which the paging
232/// hierarchy was configured by the file loader (the page directory must exist). If this is not
233/// true this routine will panic rather than corrupt the address space.
234unsafe fn get_pde_for_va(va: u64) -> &'static mut PageTableEntry {
235    let mut page_table_base: u64;
236
237    // SAFETY: See function comment.
238    unsafe {
239        asm!("mov {0}, cr3", out(reg) page_table_base);
240        let pml4 = page_table_at_address(page_table_base);
241        let entry = pml4.entry(va, 3);
242        assert!(entry.is_present());
243        let pdpt = page_table_at_address(entry.get_addr());
244        let entry = pdpt.entry(va, 2);
245        assert!(entry.is_present());
246        let pd = page_table_at_address(entry.get_addr());
247        let entry = pd.entry(va, 1);
248        entry
249    }
250}
251
252static LOCAL_MAP_INITIALIZED: SingleThreaded<Cell<bool>> = SingleThreaded(Cell::new(false));
253
254/// Initializes the local map. This function should only be called once.
255/// It returns a LocalMap structure with a static lifetime.
256/// `va` is the virtual address of the local map region. It must be 2MB aligned.
257pub fn init_local_map(va: u64) -> LocalMap<'static> {
258    assert!(va % X64_LARGE_PAGE_SIZE == 0);
259
260    // SAFETY: The va for the local map is part of the measured build. This routine will only be
261    // called once. The boot shim is a single threaded environment, the contained assertion is
262    // sufficient to enforce that the routine is not called more than once.
263    let local_map = unsafe {
264        assert!(!LOCAL_MAP_INITIALIZED.get());
265        LOCAL_MAP_INITIALIZED.set(true);
266        let entry = get_pde_for_va(va);
267        assert!(entry.is_present() && entry.is_large_page());
268
269        LocalMap {
270            pte_ptr: core::ptr::from_mut(entry),
271            va,
272            _dummy: PhantomData,
273        }
274    };
275
276    unmap_page_helper(&local_map);
277    local_map
278}
279
280/// A page used for TDX hypercalls
281/// This wrapper assures that the page is a large page, present in the
282/// paging hierarchy, aligned to 2MB, and shared with the hypervisor
283pub struct TdxHypercallPage(u64);
284
285impl TdxHypercallPage {
286    /// Validate that a virtual address is present in the paging hierarchy,
287    /// and that it is a large page
288    ///
289    /// # Safety
290    /// The caller ensures that the input is a virtual address with a valid page table
291    pub unsafe fn new(va: u64) -> Self {
292        // SAFETY: Caller has guaranteed the va is a valid pagetable mapping
293        unsafe {
294            let entry = get_pde_for_va(va);
295            assert!(entry.is_present() & entry.is_large_page());
296            assert!(va % X64_LARGE_PAGE_SIZE == 0);
297            assert!(entry.tdx_is_shared());
298            TdxHypercallPage(va)
299        }
300    }
301
302    /// Returns the VA of the large page containing the I/O buffers
303    pub fn base(&self) -> u64 {
304        self.0
305    }
306
307    /// Returns the VA of the hypercall input buffer
308    pub fn input(&self) -> u64 {
309        self.0
310    }
311
312    /// Returns the VA of the hypercall output buffer
313    pub fn output(&self) -> u64 {
314        self.0 + HV_PAGE_SIZE
315    }
316}
317
318/// Set the shared bit in the PDE of a large page in the local map for a given VA.
319///
320/// # Safety
321/// The va passed in is guaranteed by the type to be a present large page,
322/// the caller must ensure it is safe to share with the hypervisor
323pub unsafe fn tdx_share_large_page(va: u64) {
324    // SAFETY: See above
325    unsafe {
326        let entry = get_pde_for_va(va);
327        entry.tdx_set_shared();
328    }
329}
330
331/// Clear the shared bit in the PDE of the local map for a given VA.
332pub fn tdx_unshare_large_page(va: TdxHypercallPage) {
333    // SAFETY: The va passed in is guaranteed by the type to be a present large page,
334    // which is shared with the hypervisor
335    unsafe {
336        let entry = get_pde_for_va(va.base());
337        entry.tdx_set_private();
338    }
339}