Skip to main content

loader/uefi/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! UEFI specific loader definitions and implementation.
5
6pub mod config;
7pub mod firmware_version;
8
9#[cfg(guest_arch = "aarch64")]
10use aarch64 as arch;
11#[cfg(guest_arch = "x86_64")]
12use x86_64 as arch;
13
14pub use arch::CONFIG_BLOB_GPA_BASE;
15pub use arch::IMAGE_SIZE;
16pub use arch::load;
17
18use thiserror::Error;
19use uefi_specs::uefi::firmware_volume;
20use zerocopy::FromBytes;
21use zerocopy::Immutable;
22use zerocopy::IntoBytes;
23use zerocopy::KnownLayout;
24
25// Constant defining the offset within the image where the SEC volume starts.
26// TODO: Revisit this when we reorganize the firmware layout. One option
27// would be to just put the SEC volume at the start of the image, so no need
28// for this offset.
29const SEC_FIRMWARE_VOLUME_OFFSET: u64 = 0x005E0000;
30
31/// Expand a 3 byte sequence into little-endian integer.
32fn expand_3byte_integer(size: [u8; 3]) -> u64 {
33    ((size[2] as u64) << 16) + ((size[1] as u64) << 8) + size[0] as u64
34}
35
36const fn signature_16(v: &[u8; 2]) -> u16 {
37    v[0] as u16 | (v[1] as u16) << 8
38}
39
40const IMAGE_DOS_SIGNATURE: u16 = 0x5A4D; // MZ
41const IMAGE_NT_SIGNATURE: u32 = 0x00004550; // PE00
42const TE_IMAGE_HEADER_SIGNATURE: u16 = signature_16(b"VZ");
43
44#[repr(C)]
45#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
46struct ImageDosHeader {
47    e_magic: u16,      // Magic number
48    e_cblp: u16,       // Bytes on last page of file
49    e_cp: u16,         // Pages in file
50    e_crlc: u16,       // Relocations
51    e_cparhdr: u16,    // Size of header in paragraphs
52    e_minalloc: u16,   // Minimum extra paragraphs needed
53    e_maxalloc: u16,   // Maximum extra paragraphs needed
54    e_ss: u16,         // Initial (relative) SS value
55    e_sp: u16,         // Initial SP value
56    e_csum: u16,       // Checksum
57    e_ip: u16,         // Initial IP value
58    e_cs: u16,         // Initial (relative) CS value
59    e_lfarlc: u16,     // File address of relocation table
60    e_ovno: u16,       // Overlay number
61    e_res: [u16; 4],   // Reserved words
62    e_oemid: u16,      // OEM identifier (for e_oeminfo)
63    e_oeminfo: u16,    // OEM information; e_oemid specific
64    e_res2: [u16; 10], // Reserved words
65    e_lfanew: i32,     // File address of new exe header
66}
67
68#[repr(C)]
69#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
70struct TeImageHeader {
71    signature: u16,
72    machine: u16,
73    number_of_sections: u8,
74    subsystem: u8,
75    stripped_size: u16,
76    address_of_entry_point: u32,
77    base_of_code: u32,
78    image_base: u64,
79    data_directory: [ImageDataDirectory; 2],
80}
81
82#[repr(C)]
83#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
84struct ImageNtHeaders32 {
85    signature: u32,
86    file_header: ImageFileHeader,
87    optional_header: ImageOptionalHeader32,
88}
89
90#[repr(C)]
91#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
92struct ImageFileHeader {
93    machine: u16,
94    number_of_sections: u16,
95    time_date_stamp: u32,
96    pointer_to_symbol_table: u32,
97    number_of_symbols: u32,
98    size_of_optional_header: u16,
99    characteristics: u16,
100}
101
102#[repr(C)]
103#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
104struct ImageOptionalHeader32 {
105    magic: u16,
106    major_linker_version: u8,
107    minor_linker_version: u8,
108    size_of_code: u32,
109    size_of_initialized_data: u32,
110    size_of_uninitialized_data: u32,
111    address_of_entry_point: u32,
112    base_of_code: u32,
113    base_of_data: u32,
114    image_base: u32,
115    section_alignment: u32,
116    file_alignment: u32,
117    major_operating_system_version: u16,
118    minor_operating_system_version: u16,
119    major_image_version: u16,
120    minor_image_version: u16,
121    major_subsystem_version: u16,
122    minor_subsystem_version: u16,
123    win32_version_value: u32,
124    size_of_image: u32,
125    size_of_headers: u32,
126    check_sum: u32,
127    subsystem: u16,
128    dll_characteristics: u16,
129    size_of_stack_reserve: u32,
130    size_of_stack_commit: u32,
131    size_of_heap_reserve: u32,
132    size_of_heap_commit: u32,
133    loader_flags: u32,
134    number_of_rva_and_sizes: u32,
135    data_directory: [ImageDataDirectory; 16],
136}
137
138#[repr(C)]
139#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
140struct ImageDataDirectory {
141    virtual_address: u32,
142    size: u32,
143}
144
145fn pe_get_entry_point_offset(pe32_data: &[u8]) -> Option<u32> {
146    let dos_header = ImageDosHeader::read_from_prefix(pe32_data).ok()?.0; // TODO: zerocopy: use-rest-of-range, option-to-error (https://github.com/microsoft/openvmm/issues/759)
147    let nt_headers_offset = if dos_header.e_magic == IMAGE_DOS_SIGNATURE {
148        // DOS image header is present, so read the PE header after the DOS image header.
149        dos_header.e_lfanew as usize
150    } else {
151        // DOS image header is not present, so PE header is at the image base.
152        0
153    };
154
155    let signature = u32::read_from_prefix(&pe32_data[nt_headers_offset..])
156        .ok()?
157        .0; // TODO: zerocopy: use-rest-of-range, option-to-error (https://github.com/microsoft/openvmm/issues/759)
158
159    // Calculate the entry point relative to the start of the image.
160    // AddressOfEntryPoint is common for PE32 & PE32+
161    if signature as u16 == TE_IMAGE_HEADER_SIGNATURE {
162        let te = TeImageHeader::read_from_prefix(&pe32_data[nt_headers_offset..])
163            .ok()?
164            .0; // TODO: zerocopy: use-rest-of-range, option-to-error (https://github.com/microsoft/openvmm/issues/759)
165        Some(te.address_of_entry_point + size_of_val(&te) as u32 - te.stripped_size as u32)
166    } else if signature == IMAGE_NT_SIGNATURE {
167        let pe = ImageNtHeaders32::read_from_prefix(&pe32_data[nt_headers_offset..])
168            .ok()?
169            .0; // TODO: zerocopy: use-rest-of-range, option-to-error (https://github.com/microsoft/openvmm/issues/759)
170        Some(pe.optional_header.address_of_entry_point)
171    } else {
172        None
173    }
174}
175
176/// Get the SEC entry point offset from the firmware base.
177fn get_sec_entry_point_offset(image: &[u8]) -> Option<u64> {
178    // Skip to SEC volume start.
179    let mut image_offset = SEC_FIRMWARE_VOLUME_OFFSET;
180
181    // Expect a firmware volume header for SEC volume.
182    let fvh =
183        firmware_volume::FirmwareVolumeHeader::read_from_prefix(&image[image_offset as usize..])
184            .ok()?
185            .0; // TODO: zerocopy: use-rest-of-range, option-to-error (https://github.com/microsoft/openvmm/issues/759)
186    if fvh.signature != firmware_volume::FV_SIGNATURE {
187        return None;
188    }
189
190    // Skip past firmware volume header to beginning of firmware volume.
191    image_offset += fvh.header_length as u64;
192
193    // Find the first SEC CORE file type.
194    let mut sec_core_file_header = None;
195    let mut volume_offset = 0;
196    while volume_offset < fvh.fv_length {
197        let new_volume_offset = (volume_offset + 7) & !7;
198        if new_volume_offset > volume_offset {
199            image_offset += new_volume_offset - volume_offset;
200            volume_offset = new_volume_offset;
201        }
202        let fh = firmware_volume::FfsFileHeader::read_from_prefix(&image[image_offset as usize..])
203            .ok()?
204            .0; // TODO: zerocopy: use-rest-of-range, option-to-error (https://github.com/microsoft/openvmm/issues/759)
205        if fh.file_type == firmware_volume::FILETYPE_SECURITY_CORE {
206            sec_core_file_header = Some(fh);
207            break;
208        }
209
210        image_offset += expand_3byte_integer(fh.size);
211        volume_offset += expand_3byte_integer(fh.size);
212    }
213
214    // There should always be a Security Core file.
215    let sec_core_file_header = sec_core_file_header?;
216    let sec_core_file_size = expand_3byte_integer(sec_core_file_header.size);
217
218    // Move past the firmware file header.
219    image_offset += size_of::<firmware_volume::FfsFileHeader>() as u64;
220    volume_offset += size_of::<firmware_volume::FfsFileHeader>() as u64;
221
222    // Loop through the firmware file sections looking for PE section.
223    let mut file_offset = volume_offset;
224    while file_offset < sec_core_file_size {
225        //
226        // Section headers are 8 byte aligned with respect to the beginning of the file stream.
227        //
228        let new_file_offset = (file_offset + 3) & !3;
229        if new_file_offset > file_offset {
230            image_offset += new_file_offset - file_offset;
231            file_offset += new_file_offset - file_offset;
232        }
233
234        let sh =
235            firmware_volume::CommonSectionHeader::read_from_prefix(&image[image_offset as usize..])
236                .ok()?
237                .0; // TODO: zerocopy: use-rest-of-range, option-to-error (https://github.com/microsoft/openvmm/issues/759)
238        if sh.section_type == firmware_volume::SECTION_PE32 {
239            let pe_offset = pe_get_entry_point_offset(
240                &image[image_offset as usize + size_of::<firmware_volume::CommonSectionHeader>()..],
241            )?;
242            image_offset +=
243                size_of::<firmware_volume::CommonSectionHeader>() as u64 + pe_offset as u64;
244            break;
245        }
246        image_offset += expand_3byte_integer(sh.size);
247        file_offset += expand_3byte_integer(sh.size);
248    }
249
250    Some(image_offset)
251}
252
253/// Definitions shared by UEFI and the loader when loaded with parameters passed in IGVM format.
254mod igvm {
255    use zerocopy::FromBytes;
256
257    use zerocopy::Immutable;
258    use zerocopy::IntoBytes;
259    use zerocopy::KnownLayout;
260
261    /// The structure used to tell UEFI where the IGVM loaded parameters are.
262    #[repr(C)]
263    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
264    pub struct UEFI_IGVM_PARAMETER_INFO {
265        pub parameter_page_count: u32,
266        pub cpuid_pages_offset: u32,
267        pub vp_context_page_number: u64,
268        pub loader_block_offset: u32,
269        pub command_line_offset: u32,
270        pub command_line_page_count: u32,
271        pub memory_map_offset: u32,
272        pub memory_map_page_count: u32,
273        pub madt_offset: u32,
274        pub madt_page_count: u32,
275        pub srat_offset: u32,
276        pub srat_page_count: u32,
277        pub maximum_processor_count: u32,
278        pub uefi_memory_map_offset: u32,
279        pub uefi_memory_map_page_count: u32,
280    }
281
282    pub const UEFI_IGVM_LOADER_BLOCK_NUMBER_OF_PROCESSORS_FIELD_OFFSET: usize = 0;
283}
284
285#[derive(Debug, Error)]
286pub enum Error {
287    #[error("Firmware size invalid")]
288    InvalidImageSize,
289    #[error("Unable to find SEC volume entry point")]
290    NoSecEntryPoint,
291    #[error("Invalid shared gpa boundary")]
292    InvalidSharedGpaBoundary,
293    #[error("Invalid config type")]
294    InvalidConfigType(String),
295    #[error("x86_64 UEFI firmware requires the hypervisor to be enabled")]
296    HvRequired,
297    #[error("Importer error")]
298    Importer(#[source] anyhow::Error),
299    #[error("PageTableBuilder: {0}")]
300    PageTableBuilder(#[from] page_table::Error),
301}
302
303#[derive(Debug)]
304pub enum ConfigType {
305    ConfigBlob(config::Blob),
306    Igvm,
307    None,
308}
309
310#[derive(Debug)]
311pub struct LoadInfo {
312    /// The GPA the firmware was loaded at.
313    pub firmware_base: u64,
314    /// The size of the firmware image loaded, in bytes.
315    pub firmware_size: u64,
316    /// The total size used by the loader starting at the firmware_base,
317    /// including the firmware image and misc data, in bytes.
318    pub total_size: u64,
319}
320
321pub mod x86_64 {
322    use super::ConfigType;
323    use super::Error;
324    use super::LoadInfo;
325    use crate::common::DEFAULT_GDT_SIZE;
326    use crate::common::import_default_gdt;
327    use crate::cpuid::HV_PSP_CPUID_PAGE;
328    use crate::importer::BootPageAcceptance;
329    use crate::importer::IgvmParameterType;
330    use crate::importer::ImageLoad;
331    use crate::importer::IsolationType;
332    use crate::importer::StartupMemoryType;
333    use crate::importer::X86Register;
334    use crate::uefi::SEC_FIRMWARE_VOLUME_OFFSET;
335    use crate::uefi::get_sec_entry_point_offset;
336    use hvdef::HV_PAGE_SIZE;
337    use page_table::IdentityMapSize;
338    use page_table::x64::IdentityMapBuilder;
339    use page_table::x64::PAGE_TABLE_MAX_BYTES;
340    use page_table::x64::PAGE_TABLE_MAX_COUNT;
341    use page_table::x64::PageTable;
342    use page_table::x64::align_up_to_page_size;
343    use zerocopy::FromZeros;
344    use zerocopy::IntoBytes;
345
346    pub const IMAGE_SIZE: u64 = 0x00600000; // 6 MB. See MsvmPkg\MsvmPkgX64.fdf
347    const IMAGE_GPA_BASE: u64 = 0x100000; // 1MB
348    const PAGE_TABLE_GPA_BASE: u64 = IMAGE_GPA_BASE + IMAGE_SIZE; // 7MB - 0x700000
349    const PAGE_TABLE_SIZE: u64 = HV_PAGE_SIZE * 6;
350    const GDT_GPA_BASE: u64 = PAGE_TABLE_GPA_BASE + PAGE_TABLE_SIZE; // 0x707000
351    const MISC_PAGES_GPA_BASE: u64 = GDT_GPA_BASE + DEFAULT_GDT_SIZE; // 0x707000
352    const MISC_PAGES_SIZE: u64 = HV_PAGE_SIZE * 2;
353    pub const CONFIG_BLOB_GPA_BASE: u64 = MISC_PAGES_GPA_BASE + MISC_PAGES_SIZE; // 0x709000
354
355    /// Load a UEFI image with the provided config type.
356    ///
357    /// On x86_64 the firmware always runs under Hyper-V, so `hv_enabled` must
358    /// be `true`.
359    pub fn load(
360        importer: &mut dyn ImageLoad<X86Register>,
361        image: &[u8],
362        config: ConfigType,
363        hv_enabled: bool,
364    ) -> Result<LoadInfo, Error> {
365        if !hv_enabled {
366            return Err(Error::HvRequired);
367        }
368
369        if image.len() != IMAGE_SIZE as usize {
370            return Err(Error::InvalidImageSize);
371        }
372
373        let sec_entry_point = get_sec_entry_point_offset(image).ok_or(Error::NoSecEntryPoint)?;
374
375        let isolation = importer.isolation_config();
376
377        // Build the page tables. This depends on if we have a paravisor present or not:
378        //      - If this is an SNP VM with no paravisor, then build a set of page tables
379        //        to map the bottom 4GB of memory with shared visibility.
380        //      - Otherwise, build the standard UEFI page tables. Bottom 4GB of address space,
381        //        identity mapped with 2 MB pages.
382        let mut page_table_work_buffer: Vec<PageTable> =
383            vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
384        let mut page_tables: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
385        let page_table_builder = IdentityMapBuilder::new(
386            PAGE_TABLE_GPA_BASE,
387            IdentityMapSize::Size4Gb,
388            page_table_work_buffer.as_mut_slice(),
389            page_tables.as_mut_slice(),
390        )?;
391        let mut shared_vis_page_table_work_buffer: Vec<PageTable> = Vec::new();
392        let mut shared_vis_page_tables: Vec<u8> = Vec::new();
393        let (page_tables, shared_vis_page_tables) =
394            if isolation.isolation_type == IsolationType::Snp && !isolation.paravisor_present {
395                if let ConfigType::ConfigBlob(_) = config {
396                    return Err(Error::InvalidConfigType(
397                        "Enlightened UEFI must use IGVM parameters".into(),
398                    ));
399                }
400
401                let shared_vis_page_table_gpa = CONFIG_BLOB_GPA_BASE + HV_PAGE_SIZE;
402                let shared_gpa_boundary_bits = isolation
403                    .shared_gpa_boundary_bits
404                    .ok_or(Error::InvalidSharedGpaBoundary)?;
405                let shared_gpa_boundary = 1 << shared_gpa_boundary_bits;
406
407                shared_vis_page_table_work_buffer
408                    .resize(PAGE_TABLE_MAX_COUNT, PageTable::new_zeroed());
409                shared_vis_page_tables.resize(PAGE_TABLE_MAX_BYTES, 0);
410                let shared_vis_builder = IdentityMapBuilder::new(
411                    shared_vis_page_table_gpa,
412                    IdentityMapSize::Size4Gb,
413                    shared_vis_page_table_work_buffer.as_mut_slice(),
414                    shared_vis_page_tables.as_mut_slice(),
415                )?
416                .with_address_bias(shared_gpa_boundary);
417
418                // The extra page tables are placed after the first config blob
419                // page.  They will be accounted for when the IGVM parameters are
420                // built.
421                let shared_vis_page_tables = shared_vis_builder.build();
422
423                let page_tables = page_table_builder
424                    .with_pml4e_link((shared_vis_page_table_gpa, shared_gpa_boundary))
425                    .build();
426
427                (page_tables, Some(shared_vis_page_tables))
428            } else {
429                let page_tables = page_table_builder.build();
430                (page_tables, None)
431            };
432
433        // Size must match expected compiled constant
434        assert_eq!(page_tables.len(), PAGE_TABLE_SIZE as usize);
435
436        // Import image, page tables, GDT entries.
437        let image_page_count = image.len() as u64 / HV_PAGE_SIZE;
438        importer
439            .import_pages(
440                IMAGE_GPA_BASE / HV_PAGE_SIZE,
441                image_page_count,
442                "uefi-image",
443                BootPageAcceptance::Exclusive,
444                image,
445            )
446            .map_err(Error::Importer)?;
447
448        let mut total_page_count = IMAGE_GPA_BASE / HV_PAGE_SIZE + image_page_count;
449
450        importer
451            .import_pages(
452                PAGE_TABLE_GPA_BASE / HV_PAGE_SIZE,
453                PAGE_TABLE_SIZE / HV_PAGE_SIZE,
454                "uefi-page-tables",
455                BootPageAcceptance::Exclusive,
456                page_tables,
457            )
458            .map_err(Error::Importer)?;
459
460        total_page_count += PAGE_TABLE_SIZE / HV_PAGE_SIZE;
461
462        // The default GDT is used with a page count of one.
463        assert_eq!(DEFAULT_GDT_SIZE, HV_PAGE_SIZE);
464        import_default_gdt(importer, GDT_GPA_BASE / HV_PAGE_SIZE).map_err(Error::Importer)?;
465        total_page_count += DEFAULT_GDT_SIZE / HV_PAGE_SIZE;
466
467        // Reserve free pages. Currently these are only used by UEFI PEI for making hypercalls.
468        importer
469            .import_pages(
470                MISC_PAGES_GPA_BASE / HV_PAGE_SIZE,
471                MISC_PAGES_SIZE / HV_PAGE_SIZE,
472                "uefi-misc-pages",
473                BootPageAcceptance::Exclusive,
474                &[],
475            )
476            .map_err(Error::Importer)?;
477
478        total_page_count += MISC_PAGES_SIZE / HV_PAGE_SIZE;
479
480        // Import the config blobg, if set. Some callers may not load UEFI
481        // configuration at this time, such as if running with a paravisor.
482        match config {
483            ConfigType::Igvm => {
484                total_page_count += set_igvm_parameters(
485                    importer,
486                    CONFIG_BLOB_GPA_BASE / HV_PAGE_SIZE,
487                    match isolation.isolation_type {
488                        IsolationType::Snp => shared_vis_page_tables
489                            .as_ref()
490                            .expect("should be shared vis page tables"),
491                        _ => &[],
492                    },
493                )?
494            }
495            ConfigType::ConfigBlob(config) => {
496                let data = config.complete();
497                assert!(!data.is_empty());
498                let config_blob_page_count = (data.len() as u64).div_ceil(HV_PAGE_SIZE);
499                importer
500                    .import_pages(
501                        CONFIG_BLOB_GPA_BASE / HV_PAGE_SIZE,
502                        config_blob_page_count,
503                        "uefi-config-blob",
504                        BootPageAcceptance::Exclusive,
505                        &data,
506                    )
507                    .map_err(Error::Importer)?;
508
509                total_page_count += config_blob_page_count;
510            }
511            ConfigType::None => {}
512        }
513
514        // UEFI expects that the memory from GPA 0 up until the end of the config
515        // blob is present, at a minimum. Note that ImageGpaBase is not 0.
516        importer
517            .verify_startup_memory_available(0, total_page_count, StartupMemoryType::Ram)
518            .map_err(Error::Importer)?;
519
520        let mut import_reg = |register| {
521            importer
522                .import_vp_register(register)
523                .map_err(Error::Importer)
524        };
525
526        // Set CR0
527        import_reg(X86Register::Cr0(
528            x86defs::X64_CR0_PG | x86defs::X64_CR0_NE | x86defs::X64_CR0_MP | x86defs::X64_CR0_PE,
529        ))?;
530
531        // Set CR3 to point to page table which starts right after the image.
532        import_reg(X86Register::Cr3(PAGE_TABLE_GPA_BASE))?;
533
534        // Set CR4
535        import_reg(X86Register::Cr4(
536            x86defs::X64_CR4_PAE
537                | x86defs::X64_CR4_MCE
538                | x86defs::X64_CR4_FXSR
539                | x86defs::X64_CR4_XMMEXCPT,
540        ))?;
541
542        // Set EFER to LME, LMA, and NXE for 64 bit mode.
543        import_reg(X86Register::Efer(
544            x86defs::X64_EFER_LMA | x86defs::X64_EFER_LME | x86defs::X64_EFER_NXE,
545        ))?;
546
547        // Set PAT
548        import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?;
549
550        // Set register state to values SEC entry point expects.
551        // RBP - start of BFV (sec FV)
552        import_reg(X86Register::Rbp(
553            IMAGE_GPA_BASE + SEC_FIRMWARE_VOLUME_OFFSET,
554        ))?;
555
556        // Set RIP to SEC entry point.
557        import_reg(X86Register::Rip(IMAGE_GPA_BASE + sec_entry_point))?;
558
559        // Set R8-R11 to the hypervisor isolation CPUID leaf values.
560        let isolation_cpuid = isolation.get_cpuid();
561
562        import_reg(X86Register::R8(isolation_cpuid.eax as u64))?;
563        import_reg(X86Register::R9(isolation_cpuid.ebx as u64))?;
564        import_reg(X86Register::R10(isolation_cpuid.ecx as u64))?;
565        import_reg(X86Register::R11(isolation_cpuid.edx as u64))?;
566
567        // Enable MTRRs, default MTRR is uncached, and set lowest 640KB as WB
568        import_reg(X86Register::MtrrDefType(0xc00))?;
569        import_reg(X86Register::MtrrFix64k00000(0x0606060606060606))?;
570        import_reg(X86Register::MtrrFix16k80000(0x0606060606060606))?;
571
572        Ok(LoadInfo {
573            firmware_base: IMAGE_GPA_BASE,
574            firmware_size: image.len() as u64,
575            total_size: total_page_count * HV_PAGE_SIZE,
576        })
577    }
578
579    /// A simple page allocator that supports allocating pages counting up from a base page.
580    struct PageAllocator {
581        base: u32,
582        total_count: u32,
583    }
584
585    impl PageAllocator {
586        /// Create a `PageAllocator` starting at the given page `base`.
587        fn new(base: u32) -> PageAllocator {
588            PageAllocator {
589                base,
590                total_count: 0,
591            }
592        }
593
594        /// Allocate `count` number of pages. Returns the base page number for the allocation.
595        fn allocate(&mut self, count: u32) -> u32 {
596            let allocation = self.base + self.total_count;
597            self.total_count += count;
598
599            allocation
600        }
601
602        /// Get the total number of pages allocated.
603        fn total(&self) -> u32 {
604            self.total_count
605        }
606    }
607
608    /// Construct the UEFI parameter information in IGVM format. `config_area_base_page` specifies the GPA page number
609    /// at the start of the config region. The number of pages used in the config region is returned.
610    fn set_igvm_parameters(
611        importer: &mut dyn ImageLoad<X86Register>,
612        config_area_base_page: u64,
613        shared_visibility_page_tables: &[u8],
614    ) -> Result<u64, Error> {
615        let mut parameter_info = super::igvm::UEFI_IGVM_PARAMETER_INFO::new_zeroed();
616
617        // IGVM UEFI_IGVM_PARAMETER_INFO page offsets are relative to 1, as the first page is taken by the
618        // UEFI_IGVM_PARAMETER_INFO structure. Allocate a page for the UEFI_IGVM_PARAMETER_INFO structure.
619        let mut allocator = PageAllocator::new(0);
620        allocator.allocate(1);
621
622        // Set up the parameter info structure with offsets to each of the
623        // additional parameters. Each table allocates a constant number of
624        // pages.
625        let table_page_count = 20;
626
627        // The first structure is the loader block, which happens after the parameter info structure and shared
628        // visibility page tables.
629        let page_table_page_count =
630            align_up_to_page_size(shared_visibility_page_tables.len() as u64) / HV_PAGE_SIZE;
631        let page_table_offset = allocator.allocate(page_table_page_count as u32);
632        parameter_info.loader_block_offset = allocator.allocate(1);
633
634        let command_line_page_count = 1;
635        parameter_info.command_line_offset = allocator.allocate(command_line_page_count);
636        parameter_info.command_line_page_count = command_line_page_count;
637
638        parameter_info.memory_map_offset = allocator.allocate(table_page_count);
639        parameter_info.memory_map_page_count = table_page_count;
640
641        parameter_info.madt_offset = allocator.allocate(table_page_count);
642        parameter_info.madt_page_count = table_page_count;
643
644        parameter_info.srat_offset = allocator.allocate(table_page_count);
645        parameter_info.srat_page_count = table_page_count;
646
647        // Reserve additional pre-accepted pages for UEFI to use to reconstruct
648        // portions of the config blob.
649        parameter_info.uefi_memory_map_offset = allocator.allocate(table_page_count);
650        parameter_info.uefi_memory_map_page_count = table_page_count;
651
652        // If this is an SNP image with no paravisor, then reserve additional pages as required.
653        let isolation = importer.isolation_config();
654        if isolation.isolation_type == IsolationType::Snp {
655            // NOTE: Currently UEFI expects this parameter load style to have no paravisor. Disallow that here.
656            if isolation.paravisor_present {
657                return Err(Error::InvalidConfigType(
658                    "IGVM ConfigType specified but paravisor is present.".into(),
659                ));
660            }
661
662            // Supply the address of the parameter info block so it can be used
663            // before PEI parses the config information.
664            importer
665                .import_vp_register(X86Register::R12(config_area_base_page * HV_PAGE_SIZE))
666                .map_err(Error::Importer)?;
667
668            // Reserve two pages to hold CPUID information. The first CPUID page
669            // contains initialized data to query CPUID leaves. The second page
670            // contains no data, as it will be populated by the host when the
671            // image is loaded.
672            parameter_info.cpuid_pages_offset = allocator.allocate(2);
673
674            let cpuid_page = create_snp_cpuid_page();
675
676            importer
677                .import_pages(
678                    config_area_base_page + parameter_info.cpuid_pages_offset as u64,
679                    1,
680                    "uefi-cpuid-page",
681                    BootPageAcceptance::CpuidPage,
682                    cpuid_page.as_bytes(),
683                )
684                .map_err(Error::Importer)?;
685
686            importer
687                .import_pages(
688                    config_area_base_page + parameter_info.cpuid_pages_offset as u64 + 1,
689                    1,
690                    "uefi-cpuid-extended-page",
691                    BootPageAcceptance::CpuidExtendedStatePage,
692                    &[],
693                )
694                .map_err(Error::Importer)?;
695
696            // Reserve a page to use to hold the VMSA.  This must be reported to
697            // UEFI so that the page can be marked as a permanent firmware
698            // allocation.
699            //
700            // Note that this page must not be counted within the size of the
701            // config block, since it has different memory protection properties.
702            // The first page following the config block is chosen for the
703            // allocation.
704            let vp_context_page_number = config_area_base_page + allocator.total() as u64;
705            importer
706                .set_vp_context_page(vp_context_page_number)
707                .map_err(Error::Importer)?;
708
709            parameter_info.vp_context_page_number = vp_context_page_number;
710        } else {
711            // If this is not an SNP image, then the VP context page does not
712            // need to be reported to UEFI. Put in the TDX reset page value for
713            // consistency with old code; this probably is unnecessary (or the
714            // UEFI firmware should just be improved to not need this).
715            parameter_info.vp_context_page_number = 0xfffff;
716        }
717
718        // Encode the total amount of pages used by all parameters.
719        parameter_info.parameter_page_count = allocator.total();
720
721        importer
722            .import_pages(
723                config_area_base_page,
724                1,
725                "uefi-config-base-page",
726                BootPageAcceptance::Exclusive,
727                parameter_info.as_bytes(),
728            )
729            .map_err(Error::Importer)?;
730
731        importer
732            .import_pages(
733                config_area_base_page + parameter_info.uefi_memory_map_offset as u64,
734                parameter_info.uefi_memory_map_page_count as u64,
735                "uefi-memory-map-scratch",
736                BootPageAcceptance::ExclusiveUnmeasured,
737                &[],
738            )
739            .map_err(Error::Importer)?;
740
741        let loader_block = importer
742            .create_parameter_area(
743                config_area_base_page + parameter_info.loader_block_offset as u64,
744                1,
745                "uefi-loader-block",
746            )
747            .map_err(Error::Importer)?;
748        importer
749            .import_parameter(
750                loader_block,
751                super::igvm::UEFI_IGVM_LOADER_BLOCK_NUMBER_OF_PROCESSORS_FIELD_OFFSET as u32,
752                IgvmParameterType::VpCount,
753            )
754            .map_err(Error::Importer)?;
755
756        let command_line = importer
757            .create_parameter_area(
758                config_area_base_page + parameter_info.command_line_offset as u64,
759                parameter_info.command_line_page_count,
760                "uefi-command-line",
761            )
762            .map_err(Error::Importer)?;
763        importer
764            .import_parameter(command_line, 0, IgvmParameterType::CommandLine)
765            .map_err(Error::Importer)?;
766
767        let memory_map = importer
768            .create_parameter_area(
769                config_area_base_page + parameter_info.memory_map_offset as u64,
770                parameter_info.memory_map_page_count,
771                "uefi-memory-map",
772            )
773            .map_err(Error::Importer)?;
774        importer
775            .import_parameter(memory_map, 0, IgvmParameterType::MemoryMap)
776            .map_err(Error::Importer)?;
777
778        let madt = importer
779            .create_parameter_area(
780                config_area_base_page + parameter_info.madt_offset as u64,
781                parameter_info.madt_page_count,
782                "uefi-madt",
783            )
784            .map_err(Error::Importer)?;
785        importer
786            .import_parameter(madt, 0, IgvmParameterType::Madt)
787            .map_err(Error::Importer)?;
788
789        let srat = importer
790            .create_parameter_area(
791                config_area_base_page + parameter_info.srat_offset as u64,
792                parameter_info.srat_page_count,
793                "uefi-srat",
794            )
795            .map_err(Error::Importer)?;
796        importer
797            .import_parameter(srat, 0, IgvmParameterType::Srat)
798            .map_err(Error::Importer)?;
799
800        if page_table_page_count != 0 {
801            importer
802                .import_pages(
803                    config_area_base_page + page_table_offset as u64,
804                    page_table_page_count,
805                    "uefi-igvm-page-tables",
806                    BootPageAcceptance::Exclusive,
807                    shared_visibility_page_tables,
808                )
809                .map_err(Error::Importer)?;
810        }
811
812        Ok(allocator.total() as u64)
813    }
814
815    /// Create a hypervisor SNP CPUID page with the default values.
816    fn create_snp_cpuid_page() -> HV_PSP_CPUID_PAGE {
817        let mut cpuid_page = HV_PSP_CPUID_PAGE::default();
818
819        for (i, required_leaf) in crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_UEFI
820            .iter()
821            .enumerate()
822        {
823            cpuid_page.cpuid_leaf_info[i].eax_in = required_leaf.eax;
824            cpuid_page.cpuid_leaf_info[i].ecx_in = required_leaf.ecx;
825            cpuid_page.count += 1;
826        }
827
828        cpuid_page
829    }
830}
831
832pub mod aarch64 {
833    use super::ConfigType;
834    use super::Error;
835    use super::LoadInfo;
836    use crate::importer::Aarch64Register;
837    use crate::importer::BootPageAcceptance;
838    use crate::importer::ImageLoad;
839    use aarch64defs::Cpsr64;
840    use hvdef::HV_PAGE_SIZE;
841
842    use zerocopy::IntoBytes;
843
844    pub const IMAGE_SIZE: u64 = 0x800000;
845    pub const CONFIG_BLOB_GPA_BASE: u64 = 0x824000;
846
847    /// Load a UEFI image with the provided config type.
848    ///
849    /// `hv_enabled` selects the SEC platform type passed to the firmware in
850    /// `x2`: Hyper-V when enlightenments are exposed, or generic when they are
851    /// not.
852    pub fn load(
853        importer: &mut dyn ImageLoad<Aarch64Register>,
854        image: &[u8],
855        config: ConfigType,
856        hv_enabled: bool,
857    ) -> Result<LoadInfo, Error> {
858        if image.len() != IMAGE_SIZE as usize {
859            return Err(Error::InvalidImageSize);
860        }
861
862        const BYTES_2MB: u64 = 0x200000;
863
864        let image_size = (image.len() as u64 + BYTES_2MB - 1) & !(BYTES_2MB - 1);
865        importer
866            .import_pages(
867                0,
868                image_size / HV_PAGE_SIZE,
869                "uefi-image",
870                BootPageAcceptance::Exclusive,
871                image,
872            )
873            .map_err(Error::Importer)?;
874
875        // The stack.
876        let stack_offset = image_size;
877        let stack_size = 32 * HV_PAGE_SIZE;
878        let stack_end = stack_offset + stack_size;
879        importer
880            .import_pages(
881                stack_offset / HV_PAGE_SIZE,
882                stack_size / HV_PAGE_SIZE,
883                "uefi-stack",
884                BootPageAcceptance::Exclusive,
885                &[],
886            )
887            .map_err(Error::Importer)?;
888
889        // The page tables.
890        let page_table_offset = stack_end;
891        let page_tables = page_tables(page_table_offset, 1 << 30 /* TODO */);
892        importer
893            .import_pages(
894                page_table_offset / HV_PAGE_SIZE,
895                page_tables.as_bytes().len() as u64 / HV_PAGE_SIZE,
896                "uefi-page-tables",
897                BootPageAcceptance::Exclusive,
898                page_tables.as_bytes(),
899            )
900            .map_err(Error::Importer)?;
901
902        let blob_offset = CONFIG_BLOB_GPA_BASE;
903
904        // The config blob.
905        let blob_size = match config {
906            ConfigType::ConfigBlob(blob) => {
907                let blob = blob.complete();
908                let blob_size = (blob.len() as u64 + HV_PAGE_SIZE - 1) & !(HV_PAGE_SIZE - 1);
909                importer
910                    .import_pages(
911                        blob_offset / HV_PAGE_SIZE,
912                        blob_size / HV_PAGE_SIZE,
913                        "uefi-config-blob",
914                        BootPageAcceptance::Exclusive,
915                        &blob,
916                    )
917                    .map_err(Error::Importer)?;
918
919                blob_size
920            }
921            ConfigType::None => 0,
922            ConfigType::Igvm => {
923                return Err(Error::InvalidConfigType("igvm not supported".to_owned()));
924            }
925        };
926
927        let total_size = blob_offset + blob_size;
928
929        let mut import_reg = |reg| importer.import_vp_register(reg).map_err(Error::Importer);
930
931        import_reg(Aarch64Register::Cpsr(
932            Cpsr64::new().with_sp(true).with_el(1).into(),
933        ))?;
934        import_reg(Aarch64Register::X0(0x1000))?;
935        import_reg(Aarch64Register::Pc(0x1000))?;
936        import_reg(Aarch64Register::X1(stack_end))?;
937        let platform_type = if hv_enabled {
938            loader_defs::uefi::SecPlatformType::HYPERV
939        } else {
940            loader_defs::uefi::SecPlatformType::GENERIC
941        };
942        import_reg(Aarch64Register::X2(platform_type.0))?;
943
944        import_reg(Aarch64Register::Ttbr0El1(page_table_offset))?;
945
946        // Memory attribute indirection register.
947        const ARM64_MAIR_CACHE_WBWA: u64 = 0xff;
948        const ARM64_MAIR_CACHE_NC: u64 = 0x00;
949        const ARM64_MAIR_CACHE_WTNA: u64 = 0xaa;
950        const ARM64_MAIR_CACHE_WC: u64 = 0x44;
951
952        import_reg(Aarch64Register::MairEl1(
953            ARM64_MAIR_CACHE_WBWA
954                | (ARM64_MAIR_CACHE_NC << 8)
955                | (ARM64_MAIR_CACHE_WTNA << 16)
956                | (ARM64_MAIR_CACHE_WC << 24)
957                | (ARM64_MAIR_CACHE_WBWA << 32)
958                | (ARM64_MAIR_CACHE_NC << 40)
959                | (ARM64_MAIR_CACHE_WTNA << 48)
960                | (ARM64_MAIR_CACHE_WC << 56),
961        ))?;
962
963        // System control register.
964        const ARM64_SCTLR_M: u64 = 0x00000001;
965        const ARM64_SCTLR_C: u64 = 0x00000004;
966        const ARM64_SCTLR_RES1_11: u64 = 0x00000800;
967        const ARM64_SCTLR_I: u64 = 0x00001000;
968        const ARM64_SCTLR_RES1_20: u64 = 0x00100000;
969        const ARM64_SCTLR_RES1_22: u64 = 0x00400000;
970        const ARM64_SCTLR_RES1_23: u64 = 0x00800000;
971        const ARM64_SCTLR_RES1_28: u64 = 0x10000000;
972        const ARM64_SCTLR_RES1_29: u64 = 0x20000000;
973
974        import_reg(Aarch64Register::SctlrEl1(
975            ARM64_SCTLR_M
976                | ARM64_SCTLR_C
977                | ARM64_SCTLR_I
978                | ARM64_SCTLR_RES1_11
979                | ARM64_SCTLR_RES1_20
980                | ARM64_SCTLR_RES1_22
981                | ARM64_SCTLR_RES1_23
982                | ARM64_SCTLR_RES1_28
983                | ARM64_SCTLR_RES1_29,
984        ))?;
985
986        // Translation control register.
987
988        const ARM64_TCR_IRGN0_WBWA: u64 = 0x0000000000000100;
989        const ARM64_TCR_ORGN0_WBWA: u64 = 0x0000000000000400;
990        const ARM64_TCR_SH0_INNER_SHARED: u64 = 0x0000000000003000;
991        const ARM64_TCR_TG0_4K: u64 = 0x0000000000000000;
992        const ARM64_TCR_EPD1: u64 = 0x0000000000800000;
993        const ARM64_TCR_T0SZ_SHIFT: u32 = 0;
994        const ARM64_TCR_T1SZ_SHIFT: u32 = 16;
995
996        import_reg(Aarch64Register::TcrEl1(
997            ARM64_TCR_EPD1
998                | ARM64_TCR_TG0_4K
999                | ARM64_TCR_SH0_INNER_SHARED
1000                | ARM64_TCR_ORGN0_WBWA
1001                | ARM64_TCR_IRGN0_WBWA
1002                | (16 << ARM64_TCR_T0SZ_SHIFT)
1003                | (16 << ARM64_TCR_T1SZ_SHIFT),
1004        ))?;
1005
1006        Ok(LoadInfo {
1007            firmware_base: 0,
1008            firmware_size: image.len() as u64,
1009            total_size,
1010        })
1011    }
1012
1013    const PTE_VALID: u64 = 1 << 0;
1014    const PTE_NOT_LARGE: u64 = 1 << 1;
1015    const PTE_MAIR_WB: u64 = 0 << 2;
1016    const PTE_MAIR_UC: u64 = 1 << 2;
1017    const PTE_SHARABILITY_INNER: u64 = 3 << 8;
1018    const PTE_ACCESSED: u64 = 1 << 10;
1019    const PTE_USER_NX: u64 = 1 << 54;
1020
1021    fn large_leaf_entry(normal: bool, address: u64) -> u64 {
1022        address
1023            | PTE_VALID
1024            | PTE_ACCESSED
1025            | PTE_SHARABILITY_INNER
1026            | PTE_USER_NX
1027            | if normal { PTE_MAIR_WB } else { PTE_MAIR_UC }
1028    }
1029
1030    fn non_leaf_entry(address: u64) -> u64 {
1031        address | PTE_VALID | PTE_NOT_LARGE
1032    }
1033
1034    fn leaf_entry(normal: bool, address: u64) -> u64 {
1035        address
1036            | PTE_VALID
1037            | PTE_ACCESSED
1038            | PTE_NOT_LARGE
1039            | PTE_SHARABILITY_INNER
1040            | PTE_USER_NX
1041            | if normal { PTE_MAIR_WB } else { PTE_MAIR_UC }
1042    }
1043
1044    fn table_index(va: u64, level: u32) -> usize {
1045        let index = va >> (9 * (3 - level) + 12);
1046        let index = index & ((1 << 9) - 1);
1047        index as usize
1048    }
1049
1050    fn page_tables(address: u64, end_of_ram: u64) -> Vec<[u64; 512]> {
1051        const PT_SIZE: u64 = 4096;
1052        const VA_4GB: u64 = 1 << 32;
1053        const VA_1GB: u64 = 1 << 30;
1054        const VA_2MB: u64 = 2 << 20;
1055        const VA_4KB: u64 = 4 << 10;
1056
1057        let mut buffer = vec![[0u64; PT_SIZE as usize / 8]; 4];
1058        let [level0, level1, level2, level3] = buffer.as_mut_slice() else {
1059            unreachable!()
1060        };
1061
1062        // Allocate temporary buffer to hold page tables. We need 4 page tables:
1063        // - PML4 table (level 0 table in ARM terminology).
1064        // - PDP table (level 1 table).
1065        // - PD table (level 2 table) to map the 1 GB region that contains the
1066        //   split between normal and device memory.
1067        // - PT table (level 3 table) to map the 2 MB region that contains the
1068        //   split between normal and device memory.
1069
1070        // Link level 1 translation table.
1071        level0[0] = non_leaf_entry(address + PT_SIZE);
1072
1073        // Create an identity map for the address space from 0 to 4 GB.
1074        // The range [0, 4GB - MMIO Space Size) is mapped as normal memory, the
1075        // range [4 GB - MMIO Space Size, 4 GB) is mapped as device memory.
1076
1077        let mut normal = true;
1078        let mut va = 0;
1079        let mut end_va = end_of_ram;
1080        while va < VA_4GB {
1081            //
1082            // Switch to device memory if we are are within the MMIO space.
1083            //
1084            if normal && va == end_va {
1085                normal = false;
1086                end_va = VA_4GB;
1087                continue;
1088            }
1089
1090            // Try to use a 1 GB page (level 1 block entry) if possible.
1091            let level1_index = table_index(va, 1);
1092            if level1[level1_index] & PTE_VALID == 0
1093                && ((va & (VA_1GB - 1)) == 0)
1094                && (end_va - va >= VA_1GB)
1095            {
1096                level1[level1_index] = large_leaf_entry(normal, va);
1097                va += VA_1GB;
1098                continue;
1099            }
1100
1101            //
1102            // Allocate and link level 2 translation table (PD) if it does not yet
1103            // exist.
1104            //
1105            if level1[level1_index] & PTE_VALID == 0 {
1106                level1[level1_index] = non_leaf_entry(address + PT_SIZE * 2);
1107            }
1108
1109            //
1110            // Try to use a 2 MB page (level 2 block entry) if possible.
1111            //
1112            let level2_index = table_index(va, 2);
1113            if level2[level2_index] & PTE_VALID == 0
1114                && ((va & (VA_2MB - 1)) == 0)
1115                && (end_va - va >= VA_2MB)
1116            {
1117                level2[level2_index] = large_leaf_entry(normal, va);
1118                va += VA_2MB;
1119                continue;
1120            }
1121
1122            //
1123            // Allocate and link level 1 translation table (PT) if it does not yet
1124            // exist.
1125            //
1126            if level2[level2_index] & PTE_VALID == 0 {
1127                level2[level2_index] = non_leaf_entry(address + PT_SIZE * 3);
1128            }
1129
1130            let level3_index = table_index(va, 3);
1131            level3[level3_index] = leaf_entry(normal, va);
1132            va += VA_4KB;
1133        }
1134
1135        buffer
1136    }
1137}