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