Skip to main content

page_table/
x64.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Methods to construct page tables on x64.
5
6use crate::Error;
7use crate::IdentityMapSize;
8use core::ops::RangeInclusive;
9use zerocopy::FromBytes;
10use zerocopy::Immutable;
11use zerocopy::IntoBytes;
12use zerocopy::KnownLayout;
13
14const X64_PTE_PRESENT: u64 = 1;
15const X64_PTE_READ_WRITE: u64 = 1 << 1;
16const X64_PTE_ACCESSED: u64 = 1 << 5;
17const X64_PTE_DIRTY: u64 = 1 << 6;
18const X64_PTE_LARGE_PAGE: u64 = 1 << 7;
19
20const PAGE_TABLE_ENTRY_COUNT: usize = 512;
21const PAGE_TABLE_ENTRY_SIZE: usize = 8;
22
23const X64_PAGE_SHIFT: u64 = 12;
24const X64_PTE_BITS: u64 = 9;
25const X64_PTE_ADDRESS_MASK: u64 = 0x000f_ffff_ffff_f000;
26const X64_PTE_ADDRESS_LAST_BIT: u8 = (u64::BITS - X64_PTE_ADDRESS_MASK.leading_zeros() - 1) as u8;
27
28/// Inclusive bit positions occupied by the physical address in an x64 page
29/// table entry.
30pub const X64_PTE_ADDRESS_BIT_RANGE: RangeInclusive<u8> =
31    X64_PAGE_SHIFT as u8..=X64_PTE_ADDRESS_LAST_BIT;
32
33/// Number of bytes in a page for X64.
34pub const X64_PAGE_SIZE: u64 = 4096;
35
36/// Number of bytes in a large page for X64.
37pub const X64_LARGE_PAGE_SIZE: u64 = 0x200000;
38
39/// Number of bytes in a 1GB page for X64.
40pub const X64_1GB_PAGE_SIZE: u64 = 0x40000000;
41
42/// An upper bound on the number of page tables that will be built for an x64 identity
43/// map. The builder will greedily map the largest possible page size, a cap of 20 tables
44/// is more than enough for mapping a few gigabytes with mostly large pages, which is
45/// sufficient for all of the current use cases of the identity map builder
46pub const PAGE_TABLE_MAX_COUNT: usize = 20;
47
48static_assertions::const_assert_eq!(
49    PAGE_TABLE_ENTRY_SIZE * PAGE_TABLE_ENTRY_COUNT,
50    X64_PAGE_SIZE as usize
51);
52const PAGE_TABLE_SIZE: usize = PAGE_TABLE_ENTRY_COUNT * PAGE_TABLE_ENTRY_SIZE;
53
54/// Maximum number of bytes needed to store an x64 identity map
55pub const PAGE_TABLE_MAX_BYTES: usize = PAGE_TABLE_MAX_COUNT * X64_PAGE_SIZE as usize;
56
57#[derive(Copy, Clone, PartialEq, Eq, IntoBytes, Immutable, KnownLayout, FromBytes)]
58#[repr(transparent)]
59/// An x64 page table entry
60pub struct PageTableEntry {
61    pub(crate) entry: u64,
62}
63
64/// A memory range to be mapped in a page table, and the associated permissions
65/// The default permissions bits are present, R/W, executable
66#[derive(Copy, Clone, Debug)]
67pub struct MappedRange {
68    start: u64,
69    end: u64,
70    permissions: u64,
71}
72
73impl MappedRange {
74    /// Create a new mapped range, with default permissions
75    pub fn new(start: u64, end: u64) -> Self {
76        Self {
77            start,
78            end,
79            permissions: X64_PTE_PRESENT | X64_PTE_ACCESSED | X64_PTE_READ_WRITE,
80        }
81    }
82
83    /// The start address of the mapped range
84    pub fn start(&self) -> u64 {
85        self.start
86    }
87
88    /// The end address of the mapped range
89    pub fn end(&self) -> u64 {
90        self.end
91    }
92
93    /// Consumes a mapped range, and returns the range without the writable bit set
94    pub fn read_only(mut self) -> Self {
95        self.permissions &= !X64_PTE_READ_WRITE;
96        self
97    }
98}
99
100impl core::fmt::Debug for PageTableEntry {
101    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102        f.debug_struct("PageTableEntry")
103            .field("entry", &self.entry)
104            .field("is_present", &self.is_present())
105            .field("gpa", &self.gpa())
106            .finish()
107    }
108}
109
110#[derive(Debug, Copy, Clone)]
111pub enum PageTableEntryType {
112    /// 1GB page in a PDPT
113    Leaf1GbPage(u64),
114    /// 2MB page in a PD
115    Leaf2MbPage(u64),
116    /// 4K page in a PT
117    Leaf4kPage(u64),
118    /// A link to a lower level page table in a PML4, PDPT, or PD
119    Pde(u64),
120}
121
122/// The depth of an x64 page table
123#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
124#[repr(u8)]
125pub enum EntryLevel {
126    Pml4 = 3,
127    Pdpt = 2,
128    Pd = 1,
129    Pt = 0,
130}
131
132impl EntryLevel {
133    /// The amount of memory that can be mapped by an entry in a page table.
134    /// If the entry is a leaf, this value represents the size of the entry,
135    /// otherwise it represents the maximum amount of physical memory space
136    /// that can be mapped if all mappings below this entry are used
137    pub fn mapping_size(self) -> u64 {
138        match self {
139            Self::Pml4 => X64_1GB_PAGE_SIZE * 512,
140            Self::Pdpt => X64_1GB_PAGE_SIZE,
141            Self::Pd => X64_LARGE_PAGE_SIZE,
142            Self::Pt => X64_PAGE_SIZE,
143        }
144    }
145
146    /// Returns a leaf entry for a virtual address in this level
147    pub fn leaf(self, va: u64) -> PageTableEntryType {
148        match self {
149            Self::Pml4 => panic!("cannot insert a leaf entry into a PML4 table"),
150            Self::Pdpt => PageTableEntryType::Leaf1GbPage(va),
151            Self::Pd => PageTableEntryType::Leaf2MbPage(va),
152            Self::Pt => PageTableEntryType::Leaf4kPage(va),
153        }
154    }
155
156    fn pa_mask(self) -> u64 {
157        match self {
158            Self::Pml4 => 0x000f_ffff_c000_0000,
159            Self::Pdpt => 0x000f_ffff_ffe0_0000,
160            Self::Pd => 0x000f_ffff_ffff_f000,
161            Self::Pt => 0x000f_ffff_ffff_f000,
162        }
163    }
164
165    /// Returns the physical address of the directory entry for a va at this level
166    /// Assumes that the directory is part of an identity mapping
167    pub fn directory_pa(self, va: u64) -> u64 {
168        va & self.pa_mask()
169    }
170}
171
172impl PageTableEntry {
173    const VALID_BITS: u64 = 0x000f_ffff_ffff_f000;
174
175    /// Set an AMD64 PDE to either represent a leaf 2MB page or PDE.
176    /// This sets the PTE to present, accessed, dirty, read write execute.
177    pub fn set_entry(&mut self, entry_type: PageTableEntryType) {
178        self.entry = X64_PTE_PRESENT | X64_PTE_ACCESSED | X64_PTE_READ_WRITE;
179
180        match entry_type {
181            PageTableEntryType::Leaf1GbPage(address) => {
182                // Must be 1GB aligned.
183                assert!(address % X64_1GB_PAGE_SIZE == 0);
184                self.entry |= address;
185                self.entry |= X64_PTE_LARGE_PAGE | X64_PTE_DIRTY;
186            }
187            PageTableEntryType::Leaf2MbPage(address) => {
188                // Leaf entry, set like UEFI does for 2MB pages. Must be 2MB aligned.
189                assert!(address % X64_LARGE_PAGE_SIZE == 0);
190                self.entry |= address;
191                self.entry |= X64_PTE_LARGE_PAGE | X64_PTE_DIRTY;
192            }
193            PageTableEntryType::Leaf4kPage(address) => {
194                // Must be 4K aligned.
195                assert!(address % X64_PAGE_SIZE == 0);
196                self.entry |= address;
197                self.entry |= X64_PTE_DIRTY;
198            }
199            PageTableEntryType::Pde(address) => {
200                // Points to another pagetable.
201                assert!(address % X64_PAGE_SIZE == 0);
202                self.entry |= address;
203            }
204        }
205    }
206
207    /// Checks if a page table entry is marked as present
208    pub fn is_present(&self) -> bool {
209        self.entry & X64_PTE_PRESENT == X64_PTE_PRESENT
210    }
211
212    /// Returns the GPA pointed to by a mapping, if it is present
213    pub fn gpa(&self) -> Option<u64> {
214        if self.is_present() {
215            // bits 51 to 12 describe the gpa of the next page table
216            Some(self.entry & Self::VALID_BITS)
217        } else {
218            None
219        }
220    }
221
222    /// Clears the address in an entry, and replaces it with the provided address
223    pub fn set_addr(&mut self, addr: u64) {
224        assert!(addr & !Self::VALID_BITS == 0);
225
226        // clear addr bits, set new addr
227        self.entry &= !Self::VALID_BITS;
228        self.entry |= addr;
229    }
230
231    /// Get the address pointed to by a page table entry, regardless of whether the entry is present
232    pub fn get_addr(&self) -> u64 {
233        self.entry & Self::VALID_BITS
234    }
235
236    /// Clear all bits of a page table entry
237    pub fn clear(&mut self) {
238        self.entry = 0;
239    }
240}
241
242#[repr(C)]
243#[derive(Clone, PartialEq, Eq, IntoBytes, Immutable, KnownLayout, FromBytes)]
244/// A single page table at any level of the page table hierarchy
245pub struct PageTable {
246    entries: [PageTableEntry; PAGE_TABLE_ENTRY_COUNT],
247}
248
249impl PageTable {
250    /// Returns a page table as a mutable iterator of page table entries
251    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut PageTableEntry> {
252        self.entries.iter_mut()
253    }
254
255    /// Treat this page table as a page table of a given level, and locate the entry corresponding to a va.
256    pub fn entry(&mut self, gva: u64, level: u8) -> &mut PageTableEntry {
257        let index = get_amd64_pte_index(gva, level as u64) as usize;
258        &mut self.entries[index]
259    }
260}
261
262impl core::ops::Index<usize> for PageTable {
263    type Output = PageTableEntry;
264
265    fn index(&self, index: usize) -> &Self::Output {
266        &self.entries[index]
267    }
268}
269
270impl core::ops::IndexMut<usize> for PageTable {
271    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
272        &mut self.entries[index]
273    }
274}
275
276/// Get an AMD64 PTE index based on page table level.
277pub fn get_amd64_pte_index(gva: u64, page_map_level: u64) -> u64 {
278    let index = gva >> (X64_PAGE_SHIFT + page_map_level * X64_PTE_BITS);
279    index & ((1 << X64_PTE_BITS) - 1)
280}
281
282/// Calculate the number of PDE page tables required to identity map a given gpa and size.
283pub fn calculate_pde_table_count(start_gpa: u64, size: u64) -> u64 {
284    let mut count = 0;
285
286    // Determine the number of bytes from start up to the next 1GB aligned
287    let start_aligned_up = align_up_to_1_gb_page_size(start_gpa);
288    let end_gpa = start_gpa + size;
289    let end_aligned_down = (end_gpa / X64_1GB_PAGE_SIZE) * X64_1GB_PAGE_SIZE;
290
291    // Ranges sized less than 1GB are treated differently.
292    if size < X64_1GB_PAGE_SIZE {
293        // A range either takes one or two pages depending on if it crosses a 1GB boundary.
294        if end_gpa > end_aligned_down && start_gpa < end_aligned_down {
295            count = 2;
296        } else {
297            count = 1;
298        }
299    } else {
300        // Count the first unaligned start up to an aligned 1GB range.
301        if start_gpa != start_aligned_up {
302            count += 1;
303        }
304
305        // Add the inner ranges that are 1GB aligned.
306        if end_aligned_down > start_aligned_up {
307            count += (end_aligned_down - start_aligned_up) / X64_1GB_PAGE_SIZE;
308        }
309
310        // Add any unaligned end range.
311        if end_gpa > end_aligned_down {
312            count += 1;
313        }
314    }
315
316    count
317}
318
319#[derive(Debug, Clone)]
320struct PageTableBuilderInner {
321    page_table_gpa: u64,
322    confidential_bit: Option<u32>,
323}
324
325/// A builder for an x64 identity-mapped page table
326pub struct PageTableBuilder<'a> {
327    /// parameters to the page table builder, stored seperately s.t. they can be easily cloned and copied
328    inner: PageTableBuilderInner,
329    /// a reference to a mutable slice of PageTables, used as working memory for constructing the page table
330    page_table: &'a mut [PageTable],
331    /// a reference to a mutable slice of u8s, used to store and return the final page table bytes
332    flattened_page_table: &'a mut [u8],
333    /// a reference to a slice of ranges to map in the page table
334    ranges: &'a [MappedRange],
335}
336
337impl PageTableBuilderInner {
338    fn get_addr_mask(&self) -> u64 {
339        X64_PTE_ADDRESS_MASK & !self.get_confidential_mask()
340    }
341
342    fn get_confidential_mask(&self) -> u64 {
343        if let Some(confidential_bit) = self.confidential_bit {
344            1u64 << confidential_bit
345        } else {
346            0
347        }
348    }
349
350    fn build_pte(&self, entry_type: PageTableEntryType, permissions: u64) -> PageTableEntry {
351        let mut entry: u64 = permissions;
352
353        match entry_type {
354            PageTableEntryType::Leaf1GbPage(address) => {
355                // Must be 1GB aligned.
356                assert_eq!(address % X64_1GB_PAGE_SIZE, 0);
357                entry |= address;
358                entry |= X64_PTE_LARGE_PAGE | X64_PTE_DIRTY;
359            }
360            PageTableEntryType::Leaf2MbPage(address) => {
361                // Leaf entry, set like UEFI does for 2MB pages. Must be 2MB aligned.
362                assert_eq!(address % X64_LARGE_PAGE_SIZE, 0);
363                entry |= address;
364                entry |= X64_PTE_LARGE_PAGE | X64_PTE_DIRTY;
365            }
366            PageTableEntryType::Leaf4kPage(address) => {
367                // Must be 4K aligned.
368                assert_eq!(address % X64_PAGE_SIZE, 0);
369                entry |= address;
370                entry |= X64_PTE_DIRTY;
371            }
372            PageTableEntryType::Pde(address) => {
373                // Points to another pagetable.
374                assert_eq!(address % X64_PAGE_SIZE, 0);
375                entry |= address;
376            }
377        }
378
379        let mask = self.get_confidential_mask();
380        if self.confidential_bit.is_some() {
381            entry |= mask;
382        } else {
383            entry &= !mask;
384        }
385
386        PageTableEntry { entry }
387    }
388
389    fn get_addr_from_pte(&self, pte: &PageTableEntry) -> u64 {
390        pte.entry & self.get_addr_mask()
391    }
392}
393
394impl<'a> PageTableBuilder<'a> {
395    /// Creates a new instance of the page table builder. The [PageTable] slice is working memory
396    /// for constructing the page table, and the [u8] slice is the memory used to output the
397    /// final bytes of the page table
398    ///
399    /// The working memory and output memory are taken as parameters to allow for the caller
400    /// to flexibly choose their allocation strategy, to support usage in no_std environments
401    /// like openhcl_boot
402    pub fn new(
403        page_table_gpa: u64,
404        page_table: &'a mut [PageTable],
405        flattened_page_table: &'a mut [u8],
406        ranges: &'a [MappedRange],
407    ) -> Result<Self, Error> {
408        // TODO: When const generic expressions are supported, the builder can take arrays
409        // instead of slices, and this validation can be moved to the types
410        if flattened_page_table.len() != (page_table.len() * PAGE_TABLE_SIZE) {
411            Err(Error::BadBufferSize {
412                bytes_buf: flattened_page_table.len(),
413                struct_buf: page_table.len() * PAGE_TABLE_SIZE,
414            })
415        } else {
416            for range in ranges.iter() {
417                if range.start() > range.end() {
418                    return Err(Error::InvalidRange);
419                }
420            }
421
422            for window in ranges.windows(2) {
423                let (l, r) = (&window[0], &window[1]);
424
425                if r.start() < l.start() {
426                    return Err(Error::UnsortedMappings);
427                }
428
429                if l.end() > r.start() {
430                    return Err(Error::OverlappingMappings);
431                }
432            }
433            Ok(PageTableBuilder {
434                inner: PageTableBuilderInner {
435                    page_table_gpa,
436                    confidential_bit: None,
437                },
438                page_table,
439                flattened_page_table,
440                ranges,
441            })
442        }
443    }
444
445    /// Builds the page tables with the confidential bit set
446    pub fn with_confidential_bit(mut self, bit_position: u32) -> Self {
447        self.inner.confidential_bit = Some(bit_position);
448        self
449    }
450
451    /// Build a set of X64 page tables identity mapping the given regions.
452    /// This creates up to 3+N page tables: 1 PML4E and up to 2 PDPTE tables, and N page tables counted at 1 per GB of size,
453    /// for 2MB mappings.
454    pub fn build(self) -> Result<&'a [u8], Error> {
455        let PageTableBuilder {
456            page_table,
457            flattened_page_table,
458            ranges,
459            inner,
460        } = self;
461
462        // Allocate single PML4E page table.
463        let (mut page_table_index, pml4_table_index) = (0, 0);
464
465        // Allocate and link table
466        let mut link_tables = |start_va: u64, end_va: u64, permissions: u64| -> Result<(), Error> {
467            let mut current_va = start_va;
468            let mut get_or_insert_entry = |table_index: usize,
469                                           entry_level: EntryLevel,
470                                           current_va: &mut u64|
471             -> Result<Option<usize>, Error> {
472                // If the current mapping can be inserted at this level as a leaf entry, do so
473                if (*current_va).is_multiple_of(entry_level.mapping_size())
474                    && (*current_va + entry_level.mapping_size() <= end_va)
475                {
476                    let entry = page_table[table_index].entry(*current_va, entry_level as u8);
477                    if entry.is_present() {
478                        // This error should be unreachable - something has gone horribly wrong
479                        return Err(Error::AttemptedEntryOverwrite);
480                    }
481
482                    #[cfg(feature = "tracing")]
483                    tracing::trace!(
484                        "inserting entry for va: {:#X} at level {:?}",
485                        current_va,
486                        entry_level
487                    );
488
489                    let new_entry = inner.build_pte(entry_level.leaf(*current_va), permissions);
490                    *entry = new_entry;
491                    *current_va += entry_level.mapping_size();
492
493                    Ok(None)
494                }
495                // The current mapping cannot be inserted as a leaf at this level
496                //
497                // Find or create the appropriate directory at this hierarchy level, and
498                // return the index
499                else {
500                    let directory_pa = entry_level.directory_pa(*current_va);
501                    let len = page_table.len();
502                    let entry = page_table[table_index].entry(directory_pa, entry_level as u8);
503
504                    if !entry.is_present() {
505                        page_table_index += 1;
506
507                        if page_table_index >= len {
508                            return Err(Error::NotEnoughMemory);
509                        }
510                        // Allocate and link a page directory
511                        let output_address =
512                            inner.page_table_gpa + page_table_index as u64 * X64_PAGE_SIZE;
513
514                        // Create the directory entry. Directory entries can be shared amongst
515                        // MappedRanges with different sets of permissions, so give a wide set of
516                        // permissions in the directory, and apply the MappedRange permissions in
517                        // the leaf entry
518                        let new_entry = inner.build_pte(
519                            PageTableEntryType::Pde(output_address),
520                            X64_PTE_PRESENT | X64_PTE_ACCESSED | X64_PTE_READ_WRITE,
521                        );
522
523                        #[cfg(feature = "tracing")]
524                        tracing::trace!(
525                            "creating directory for va: {:#X} at level {:?}",
526                            directory_pa,
527                            entry_level
528                        );
529                        *entry = new_entry;
530
531                        Ok(Some(page_table_index))
532                    } else {
533                        Ok(Some(
534                            ((inner.get_addr_from_pte(entry) - inner.page_table_gpa)
535                                / X64_PAGE_SIZE)
536                                .try_into()
537                                .expect("Valid page table index"),
538                        ))
539                    }
540                }
541            };
542
543            while current_va < end_va {
544                #[cfg(feature = "tracing")]
545                tracing::trace!("creating entry for va: {:#X}", current_va);
546                // For the current_va, insert entires as needed into the page table hierarchy,
547                // terminating when a leaf entry is inserted
548                let pdpt_table_index =
549                    get_or_insert_entry(pml4_table_index, EntryLevel::Pml4, &mut current_va)?;
550                if let Some(pdpt_table_index) = pdpt_table_index {
551                    let pd_table_index =
552                        get_or_insert_entry(pdpt_table_index, EntryLevel::Pdpt, &mut current_va)?;
553                    if let Some(pd_table_index) = pd_table_index {
554                        let pt_table_index =
555                            get_or_insert_entry(pd_table_index, EntryLevel::Pd, &mut current_va)?;
556                        if let Some(pt_table_index) = pt_table_index {
557                            get_or_insert_entry(pt_table_index, EntryLevel::Pt, &mut current_va)?;
558                        }
559                    }
560                }
561            }
562
563            Ok(())
564        };
565
566        for range in ranges {
567            link_tables(range.start, range.end, range.permissions)?;
568        }
569
570        // flatten the [page_table] into a [u8]
571        Ok(flatten_page_table(
572            page_table,
573            flattened_page_table,
574            page_table_index + 1,
575        ))
576    }
577}
578
579#[derive(Debug, Clone)]
580struct IdentityMapBuilderParams {
581    page_table_gpa: u64,
582    identity_map_size: IdentityMapSize,
583    address_bias: u64,
584    pml4e_link: Option<(u64, u64)>,
585    confidential_bit: Option<u32>,
586}
587
588/// An IdentityMap Builder, which builds either a 4GB or 8GB identity map of the lower address space
589/// FUTURE: This logic can merged with the PageTableBuilder, rather than maintaining two implementations
590pub struct IdentityMapBuilder<'a> {
591    params: IdentityMapBuilderParams,
592    /// a reference to a mutable slice of PageTables, used as working memory for constructing the page table
593    page_table: &'a mut [PageTable],
594    /// a reference to a mutable slice of u8s, used to store and return the final page table bytes
595    flattened_page_table: &'a mut [u8],
596}
597
598impl<'a> IdentityMapBuilder<'a> {
599    /// Creates a new instance of the IdentityMapBuilder. The [PageTable] slice is working memory
600    /// for constructing the page table, and the [u8] slice is the memory used to output the
601    /// final bytes of the page table
602    ///
603    /// The working memory and output memory are taken as parameters to allow for the caller
604    /// to flexibly choose their allocation strategy, to support usage in no_std environments
605    /// like openhcl_boot
606    pub fn new(
607        page_table_gpa: u64,
608        identity_map_size: IdentityMapSize,
609        page_table: &'a mut [PageTable],
610        flattened_page_table: &'a mut [u8],
611    ) -> Result<Self, Error> {
612        if flattened_page_table.len() != (page_table.len() * PAGE_TABLE_SIZE) {
613            Err(Error::BadBufferSize {
614                bytes_buf: flattened_page_table.len(),
615                struct_buf: page_table.len() * PAGE_TABLE_SIZE,
616            })
617        } else {
618            Ok(IdentityMapBuilder {
619                params: IdentityMapBuilderParams {
620                    page_table_gpa,
621                    identity_map_size,
622                    address_bias: 0,
623                    pml4e_link: None,
624                    confidential_bit: None,
625                },
626                page_table,
627                flattened_page_table,
628            })
629        }
630    }
631
632    /// Builds the page tables with an address bias, a fixed offset between the virtual
633    /// and physical addresses in the identity map
634    pub fn with_address_bias(mut self, address_bias: u64) -> Self {
635        self.params.address_bias = address_bias;
636        self
637    }
638
639    /// Builds the page tables with the confidential bit set.
640    pub fn with_confidential_bit(mut self, bit_position: u32) -> Self {
641        self.params.confidential_bit = Some(bit_position);
642        self
643    }
644
645    /// An optional PML4E entry may be linked, with arguments being (link_target_gpa, linkage_gpa).
646    /// link_target_gpa represents the GPA of the PML4E to link into the built page table.
647    /// linkage_gpa represents the GPA at which the linked PML4E should be linked.
648    pub fn with_pml4e_link(mut self, pml4e_link: (u64, u64)) -> Self {
649        self.params.pml4e_link = Some(pml4e_link);
650        self
651    }
652
653    /// Build a set of X64 page tables identity mapping the bottom address
654    /// space with an optional address bias.
655    pub fn build(self) -> &'a [u8] {
656        let IdentityMapBuilder {
657            page_table,
658            flattened_page_table,
659            params,
660        } = self;
661        let set_entry = |entry: &mut PageTableEntry, entry_type| {
662            entry.set_entry(entry_type);
663            if let Some(bit_position) = params.confidential_bit {
664                entry.entry |= 1u64 << bit_position;
665            }
666        };
667
668        // Allocate page tables. There are up to 6 total page tables:
669        //      1 PML4E (Level 4) (omitted if the address bias is non-zero)
670        //      1 PDPTE (Level 3)
671        //      4 or 8 PDE tables (Level 2)
672        // Note that there are no level 1 page tables, as 2MB pages are used.
673        let leaf_page_table_count = match params.identity_map_size {
674            IdentityMapSize::Size4Gb => 4,
675            IdentityMapSize::Size8Gb => 8,
676        };
677        let page_table_count = leaf_page_table_count + if params.address_bias == 0 { 2 } else { 1 };
678        let mut page_table_allocator = page_table.iter_mut().enumerate();
679
680        // Allocate single PDPTE table.
681        let pdpte_table = if params.address_bias == 0 {
682            // Allocate single PML4E page table.
683            let (_, pml4e_table) = page_table_allocator
684                .next()
685                .expect("pagetable should always be available, code bug if not");
686
687            // PDPTE table is the next pagetable.
688            let (pdpte_table_index, pdpte_table) = page_table_allocator
689                .next()
690                .expect("pagetable should always be available, code bug if not");
691
692            // Set PML4E entry linking PML4E to PDPTE.
693            let output_address = params.page_table_gpa + pdpte_table_index as u64 * X64_PAGE_SIZE;
694            set_entry(
695                &mut pml4e_table.entries[0],
696                PageTableEntryType::Pde(output_address),
697            );
698
699            // Set PML4E entry to link the additional entry if specified.
700            if let Some((link_target_gpa, linkage_gpa)) = params.pml4e_link {
701                assert!((linkage_gpa & 0x7FFFFFFFFF) == 0);
702                set_entry(
703                    &mut pml4e_table.entries[linkage_gpa as usize >> 39],
704                    PageTableEntryType::Pde(link_target_gpa),
705                );
706            }
707
708            pdpte_table
709        } else {
710            // PDPTE table is the first table, if no PML4E.
711            page_table_allocator
712                .next()
713                .expect("pagetable should always be available, code bug if not")
714                .1
715        };
716
717        // Build PDEs that point to 2 MB pages.
718        let top_address = params.identity_map_size.address_space_size();
719        let mut current_va = 0;
720
721        while current_va < top_address {
722            // Allocate a new PDE table
723            let (pde_table_index, pde_table) = page_table_allocator
724                .next()
725                .expect("pagetable should always be available, code bug if not");
726
727            // Link PDPTE table to PDE table (L3 to L2)
728            let pdpte_index = get_amd64_pte_index(current_va, 2);
729            let output_address = params.page_table_gpa + pde_table_index as u64 * X64_PAGE_SIZE;
730            let pdpte_entry = &mut pdpte_table.entries[pdpte_index as usize];
731            assert!(!pdpte_entry.is_present());
732            set_entry(pdpte_entry, PageTableEntryType::Pde(output_address));
733
734            // Set all 2MB entries in this PDE table.
735            for entry in pde_table.iter_mut() {
736                set_entry(
737                    entry,
738                    PageTableEntryType::Leaf2MbPage(current_va + params.address_bias),
739                );
740                current_va += X64_LARGE_PAGE_SIZE;
741            }
742        }
743
744        // Flatten [page_table] into [u8]
745        flatten_page_table(page_table, flattened_page_table, page_table_count)
746    }
747}
748
749/// Align an address up to the start of the next page.
750pub fn align_up_to_page_size(address: u64) -> u64 {
751    (address + X64_PAGE_SIZE - 1) & !(X64_PAGE_SIZE - 1)
752}
753
754/// Align an address up to the start of the next large (2MB) page.
755pub fn align_up_to_large_page_size(address: u64) -> u64 {
756    (address + X64_LARGE_PAGE_SIZE - 1) & !(X64_LARGE_PAGE_SIZE - 1)
757}
758
759/// Align an address up to the start of the next 1GB page.
760pub fn align_up_to_1_gb_page_size(address: u64) -> u64 {
761    (address + X64_1GB_PAGE_SIZE - 1) & !(X64_1GB_PAGE_SIZE - 1)
762}
763
764fn flatten_page_table<'a>(
765    page_table: &mut [PageTable],
766    flattened_page_table: &'a mut [u8],
767    page_table_count: usize,
768) -> &'a [u8] {
769    for (page_table, dst) in page_table
770        .iter()
771        .take(page_table_count)
772        .zip(flattened_page_table.chunks_mut(PAGE_TABLE_SIZE))
773    {
774        let src = page_table.as_bytes();
775        dst.copy_from_slice(src);
776    }
777
778    &flattened_page_table[0..PAGE_TABLE_SIZE * page_table_count]
779}
780
781#[cfg(test)]
782mod tests {
783    use std;
784    use std::vec;
785
786    use super::Error;
787    use super::IdentityMapBuilder;
788    use super::IdentityMapSize;
789    use super::MappedRange;
790    use super::PAGE_TABLE_MAX_BYTES;
791    use super::PAGE_TABLE_MAX_COUNT;
792    use super::PageTable;
793    use super::PageTableBuilder;
794    use super::X64_1GB_PAGE_SIZE;
795    use super::X64_PTE_PRESENT;
796    use super::align_up_to_large_page_size;
797    use super::align_up_to_page_size;
798    use super::calculate_pde_table_count;
799    use zerocopy::FromZeros;
800
801    #[test]
802    fn test_align_up() {
803        assert_eq!(align_up_to_page_size(4096), 4096);
804        assert_eq!(align_up_to_page_size(4095), 4096);
805        assert_eq!(align_up_to_page_size(4097), 8192);
806    }
807
808    #[test]
809    fn test_large_align_up() {
810        assert_eq!(align_up_to_large_page_size(0), 0);
811        assert_eq!(align_up_to_large_page_size(4096), 0x200000);
812        assert_eq!(align_up_to_large_page_size(0x200000), 0x200000);
813        assert_eq!(align_up_to_large_page_size(0x200001), 0x400000);
814    }
815
816    #[test]
817    fn test_pde_size_calc() {
818        assert_eq!(calculate_pde_table_count(0, 512), 1);
819        assert_eq!(calculate_pde_table_count(0, 1024 * 1024), 1);
820        assert_eq!(calculate_pde_table_count(512, 1024 * 1024), 1);
821        assert_eq!(calculate_pde_table_count(X64_1GB_PAGE_SIZE - 512, 1024), 2);
822        assert_eq!(calculate_pde_table_count(X64_1GB_PAGE_SIZE - 512, 512), 1);
823        assert_eq!(calculate_pde_table_count(0, X64_1GB_PAGE_SIZE), 1);
824        assert_eq!(calculate_pde_table_count(0, X64_1GB_PAGE_SIZE + 1), 2);
825        assert_eq!(calculate_pde_table_count(1, X64_1GB_PAGE_SIZE + 1), 2);
826        assert_eq!(calculate_pde_table_count(512, X64_1GB_PAGE_SIZE * 2), 3);
827
828        assert_eq!(calculate_pde_table_count(0, X64_1GB_PAGE_SIZE * 3), 3);
829        assert_eq!(
830            calculate_pde_table_count(X64_1GB_PAGE_SIZE, X64_1GB_PAGE_SIZE * 3),
831            3
832        );
833    }
834
835    fn check_page_table_count(ranges: &[MappedRange], count: usize) {
836        let mut page_table_work_buffer: Vec<PageTable> =
837            vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
838        let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
839
840        let page_table_builder = PageTableBuilder::new(
841            0,
842            page_table_work_buffer.as_mut_slice(),
843            page_table.as_mut_slice(),
844            ranges,
845        )
846        .expect("page table builder initialization should succeed");
847
848        let page_table = page_table_builder.build().expect("building should succeed");
849        assert_eq!(page_table.len(), count);
850    }
851
852    fn page_table_builder_error(ranges: &[MappedRange]) -> Option<Error> {
853        let mut page_table_work_buffer: Vec<PageTable> =
854            vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
855        let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
856
857        PageTableBuilder::new(
858            0,
859            page_table_work_buffer.as_mut_slice(),
860            page_table.as_mut_slice(),
861            ranges,
862        )
863        .err()
864    }
865
866    #[test]
867    fn test_page_table_entry_sizing() {
868        const ONE_GIG: u64 = 1024 * 1024 * 1024;
869        const TWO_MB: u64 = 1024 * 1024 * 2;
870        const FOUR_KB: u64 = 4096;
871
872        check_page_table_count(&[MappedRange::new(0, ONE_GIG)], 4096 * 2);
873        check_page_table_count(&[MappedRange::new(0, TWO_MB)], 4096 * 3);
874        check_page_table_count(&[MappedRange::new(0, FOUR_KB)], 4096 * 4);
875        check_page_table_count(&[MappedRange::new(FOUR_KB, ONE_GIG)], 4096 * 4);
876        check_page_table_count(&[MappedRange::new(TWO_MB, ONE_GIG)], 4096 * 3);
877        check_page_table_count(&[MappedRange::new(TWO_MB, ONE_GIG + FOUR_KB)], 4096 * 5);
878        check_page_table_count(&[MappedRange::new(TWO_MB, ONE_GIG + TWO_MB)], 4096 * 4);
879    }
880
881    #[test]
882    fn test_page_table_builder_overlapping_range() {
883        const ONE_GIG: u64 = 1024 * 1024 * 1024;
884        const TWO_MB: u64 = 1024 * 1024 * 2;
885        const FOUR_KB: u64 = 4096;
886
887        let err = page_table_builder_error(&[
888            MappedRange::new(FOUR_KB, ONE_GIG),
889            MappedRange::new(TWO_MB, ONE_GIG),
890        ])
891        .expect("must fail");
892        assert!(matches!(err, Error::OverlappingMappings));
893    }
894
895    #[test]
896    fn test_page_table_builder_invalid_range() {
897        const ONE_GIG: u64 = 1024 * 1024 * 1024;
898        const FOUR_KB: u64 = 4096;
899
900        let err =
901            page_table_builder_error(&[MappedRange::new(ONE_GIG, FOUR_KB)]).expect("must fail");
902        assert!(matches!(err, Error::InvalidRange));
903    }
904
905    #[test]
906    fn test_page_table_builder_oom() {
907        const ONE_GIG: u64 = 1024 * 1024 * 1024;
908
909        let mut page_table_work_buffer: Vec<PageTable> = vec![PageTable::new_zeroed(); 1];
910        let mut page_table: Vec<u8> = vec![0; 4096];
911
912        let err = PageTableBuilder::new(
913            0,
914            page_table_work_buffer.as_mut_slice(),
915            page_table.as_mut_slice(),
916            &[MappedRange::new(0, ONE_GIG)],
917        )
918        .expect("page table builder initialization should succeed")
919        .build()
920        .expect_err("building page tables should fail");
921
922        assert!(matches!(err, Error::NotEnoughMemory));
923    }
924
925    #[test]
926    fn test_page_table_builder_mismatched_buffers() {
927        const ONE_GIG: u64 = 1024 * 1024 * 1024;
928
929        let mut page_table_work_buffer: Vec<PageTable> = vec![PageTable::new_zeroed(); 4];
930        let mut page_table: Vec<u8> = vec![0; 4096 * 5];
931
932        let err = PageTableBuilder::new(
933            0,
934            page_table_work_buffer.as_mut_slice(),
935            page_table.as_mut_slice(),
936            &[MappedRange::new(0, ONE_GIG)],
937        )
938        .err()
939        .expect("building page tables should fail");
940
941        assert!(matches!(
942            err,
943            Error::BadBufferSize {
944                bytes_buf: _,
945                struct_buf: _
946            }
947        ));
948    }
949
950    #[test]
951    fn identity_map_builder_sets_confidential_bit() {
952        const C_BIT: u32 = 51;
953        let mut page_table_work_buffer: Vec<PageTable> =
954            vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
955        let mut page_table = vec![0; PAGE_TABLE_MAX_BYTES];
956
957        let page_table = IdentityMapBuilder::new(
958            0x4000,
959            IdentityMapSize::Size4Gb,
960            &mut page_table_work_buffer,
961            &mut page_table,
962        )
963        .unwrap()
964        .with_confidential_bit(C_BIT)
965        .build();
966
967        for entry in page_table.chunks_exact(8).map(|entry| {
968            u64::from_ne_bytes(entry.try_into().expect("page table entry is eight bytes"))
969        }) {
970            if entry & X64_PTE_PRESENT != 0 {
971                assert_ne!(entry & (1 << C_BIT), 0);
972            } else {
973                assert_eq!(entry, 0);
974            }
975        }
976    }
977}