loader\uefi/
mod.rs

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