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