Skip to main content

loader/
paravisor.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Paravisor specific loader definitions and implementation.
5
6use crate::common::ChunkBuf;
7use crate::common::ImportFileRegion;
8use crate::common::ReadSeek;
9use crate::cpuid::HV_PSP_CPUID_PAGE;
10use crate::importer::Aarch64Register;
11use crate::importer::BootPageAcceptance;
12use crate::importer::IgvmParameterType;
13use crate::importer::ImageLoad;
14use crate::importer::IsolationConfig;
15use crate::importer::IsolationType;
16use crate::importer::SegmentRegister;
17use crate::importer::StartupMemoryType;
18use crate::importer::TableRegister;
19use crate::importer::X86Register;
20use crate::linux::InitrdAddressType;
21use crate::linux::InitrdConfig;
22use crate::linux::InitrdInfo;
23use crate::linux::KernelInfo;
24use crate::linux::load_kernel_and_initrd_arm64;
25use aarch64defs::Cpsr64;
26use aarch64defs::IntermPhysAddrSize;
27use aarch64defs::SctlrEl1;
28use aarch64defs::TranslationBaseEl1;
29use aarch64defs::TranslationControlEl1;
30use aarch64defs::TranslationGranule0;
31use aarch64defs::TranslationGranule1;
32use hvdef::HV_PAGE_SIZE;
33use hvdef::Vtl;
34use igvm::registers::AArch64Register;
35use loader_defs::paravisor::*;
36use loader_defs::shim::ShimParamsRaw;
37use memory_range::MemoryRange;
38use page_table::aarch64::Arm64PageSize;
39use page_table::aarch64::MemoryAttributeEl1;
40use page_table::aarch64::MemoryAttributeIndirectionEl1;
41use page_table::x64::MappedRange;
42use page_table::x64::PAGE_TABLE_MAX_BYTES;
43use page_table::x64::PAGE_TABLE_MAX_COUNT;
44use page_table::x64::PageTable;
45use page_table::x64::PageTableBuilder;
46use page_table::x64::X64_LARGE_PAGE_SIZE;
47use page_table::x64::align_up_to_large_page_size;
48use page_table::x64::align_up_to_page_size;
49use page_table::x64::calculate_pde_table_count;
50use std::io::Read;
51use std::io::Seek;
52use thiserror::Error;
53use x86defs::GdtEntry;
54use x86defs::SegmentSelector;
55use x86defs::X64_BUSY_TSS_SEGMENT_ATTRIBUTES;
56use x86defs::X64_DEFAULT_CODE_SEGMENT_ATTRIBUTES;
57use x86defs::X64_DEFAULT_DATA_SEGMENT_ATTRIBUTES;
58use x86defs::cpuid::CpuidFunction;
59use zerocopy::FromZeros;
60use zerocopy::IntoBytes;
61
62#[derive(Debug)]
63pub struct Vtl0Linux<'a> {
64    pub command_line: &'a std::ffi::CString,
65    pub load_info: crate::linux::LoadInfo,
66}
67
68#[derive(Debug)]
69pub struct Vtl0Config<'a> {
70    pub supports_pcat: bool,
71    /// The load info and the VP context page.
72    pub supports_uefi: Option<(crate::uefi::LoadInfo, Vec<u8>)>,
73    pub supports_linux: Option<Vtl0Linux<'a>>,
74}
75
76// See HclDefs.h
77pub const HCL_SECURE_VTL: Vtl = Vtl::Vtl2;
78
79/// Size of the persisted region (2MB).
80const PERSISTED_REGION_SIZE: u64 = 2 * 1024 * 1024;
81
82fn avoid_page_table_large_page_boundary(offset: u64, large_page_size: u64) -> u64 {
83    if offset.is_multiple_of(large_page_size) {
84        offset + HV_PAGE_SIZE
85    } else {
86        offset
87    }
88}
89
90#[derive(Debug, Error)]
91pub enum Error {
92    #[error("memory is unaligned: {0}")]
93    MemoryUnaligned(u64),
94    #[error("command line too large: {0}")]
95    CommandLineSize(usize),
96    #[error("kernel load error")]
97    Kernel(#[source] crate::linux::Error),
98    #[error("shim load error")]
99    Shim(#[source] crate::elf::Error),
100    #[error("invalid initrd size: {0}")]
101    InvalidInitrdSize(u64),
102    #[error("memory used: {0} is greater than available")]
103    NotEnoughMemory(u64),
104    #[error("importer error")]
105    Importer(#[from] anyhow::Error),
106    #[error("failed to import initrd")]
107    ImportInitrd(#[source] crate::common::ImportFileRegionError),
108    #[error("failed to read initrd for CRC")]
109    InitrdRead(#[source] std::io::Error),
110    #[error("PageTableBuilder: {0}")]
111    PageTableBuilder(#[from] page_table::Error),
112}
113
114/// Kernel Command line type.
115pub enum CommandLineType<'a> {
116    /// The command line is a static string.
117    Static(&'a str),
118    /// The command line is dynamic and host appendable via the chosen node in
119    /// device tree, with initial data specified by the provided CStr. An empty
120    /// base_string may be provided to allow the host to specify the full kernel
121    /// command line.
122    HostAppendable(&'a str),
123}
124
125/// Load the underhill kernel on x64.
126///
127/// An optional initrd may be specified.
128///
129/// An optional `memory_page_base` may be specified. This will disable
130/// relocation support for underhill.
131pub fn load_openhcl_x64<F>(
132    importer: &mut dyn ImageLoad<X86Register>,
133    kernel_image: &mut F,
134    shim: &mut F,
135    sidecar: Option<&mut F>,
136    command_line: CommandLineType<'_>,
137    mut initrd: Option<(&mut dyn ReadSeek, u64)>,
138    memory_page_base: Option<u64>,
139    memory_page_count: u64,
140    vtl0_config: Vtl0Config<'_>,
141) -> Result<(), Error>
142where
143    F: Read + Seek,
144{
145    let IsolationConfig {
146        isolation_type,
147        paravisor_present,
148        shared_gpa_boundary_bits,
149    } = importer.isolation_config();
150
151    // If no explicit memory base is specified, load with relocation support.
152    let with_relocation = memory_page_base.is_none() && isolation_type == IsolationType::None;
153
154    let memory_start_address = memory_page_base
155        .map(|page_number| page_number * HV_PAGE_SIZE)
156        .unwrap_or(PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS);
157
158    let memory_size = memory_page_count * HV_PAGE_SIZE;
159
160    // OpenHCL is laid out as the following:
161    // --- High Memory, 2MB aligned ---
162    // free space
163    //
164    // page tables
165    // 16 pages reserved for bootshim heap
166    // 8K bootshim logs
167    // IGVM parameters
168    // reserved vtl2 ranges
169    // initrd
170    // openhcl_boot
171    // sidecar, if configured
172    // - pad to next 2MB -
173    // kernel
174    // optional 2mb bounce buf for CVM
175    // persisted state region
176    // --- Low memory, 2MB aligned ---
177
178    // Paravisor memory ranges must be 2MB (large page) aligned.
179    if !memory_start_address.is_multiple_of(X64_LARGE_PAGE_SIZE) {
180        return Err(Error::MemoryUnaligned(memory_start_address));
181    }
182
183    if !memory_size.is_multiple_of(X64_LARGE_PAGE_SIZE) {
184        return Err(Error::MemoryUnaligned(memory_size));
185    }
186
187    // The whole memory range must be present and VTL2 protectable for the
188    // underhill kernel to work.
189    importer.verify_startup_memory_available(
190        memory_start_address / HV_PAGE_SIZE,
191        memory_page_count,
192        if paravisor_present {
193            StartupMemoryType::Vtl2ProtectableRam
194        } else {
195            StartupMemoryType::Ram
196        },
197    )?;
198
199    let kernel_acceptance = match isolation_type {
200        IsolationType::Snp | IsolationType::Tdx => BootPageAcceptance::Shared,
201        _ => BootPageAcceptance::Exclusive,
202    };
203
204    let mut offset = memory_start_address;
205
206    // Reserve the first 2MB for a potential persisted state region. The first
207    // 4K page is always the persisted state header, and the bootshim may decide
208    // to use the the remaining pages for the protobuf payload.
209    let persisted_region_base = offset;
210    let persisted_region_size = PERSISTED_REGION_SIZE;
211    offset += persisted_region_size;
212
213    // If hardware isolated, reserve a 2MB range for bounce buffering shared
214    // pages. This is done first because we know the start address is 2MB
215    // aligned, with the next consumers wanting 2MB aligned ranges. This is
216    // reserved at load time in order to guarantee the pagetables have entries
217    // for this identity mapping.
218    //
219    // Leave this as a gap, as there's no need to accept or describe this range
220    // in the IGVM file.
221    let bounce_buffer = if matches!(isolation_type, IsolationType::Snp | IsolationType::Tdx) {
222        let bounce_buffer_gpa = offset;
223        assert_eq!(bounce_buffer_gpa % X64_LARGE_PAGE_SIZE, 0);
224        let range = MemoryRange::new(bounce_buffer_gpa..bounce_buffer_gpa + X64_LARGE_PAGE_SIZE);
225
226        offset += range.len();
227        Some(range)
228    } else {
229        None
230    };
231
232    tracing::trace!(offset, "loading the kernel");
233
234    // The x86_64 uncompressed kernel we use doesn't show any difference
235    // in the code sections upon flipping CONFIG_RELOCATABLE. In total,
236    // there are 6 places where a difference is found: dates in the Linux
237    // banner, GNU build ID, and metadata entries in the empty initrd image
238    // (it always is embedded into the kernel). No sections with relocations
239    // appear if CONFIG_RELOCATABLE is set.
240    // Assume that at least the kernel entry contains PIC and no loader
241    // assistance with the relocations records (if any) is required.
242    let load_info = crate::elf::load_static_elf(
243        importer,
244        kernel_image,
245        offset,
246        0,
247        true,
248        kernel_acceptance,
249        "underhill-kernel",
250    )
251    .map_err(|e| Error::Kernel(crate::linux::Error::ElfLoader(e)))?;
252    tracing::trace!("Kernel loaded at {load_info:x?}");
253    let crate::elf::LoadInfo {
254        minimum_address_used: _min_addr,
255        next_available_address: mut offset,
256        entrypoint: kernel_entrypoint,
257    } = load_info;
258
259    assert_eq!(offset & (HV_PAGE_SIZE - 1), 0);
260
261    // If an AP kernel was provided, load it next.
262    let (sidecar_size, sidecar_entrypoint) = if let Some(sidecar) = sidecar {
263        // Sidecar load addr must be 2MB aligned
264        offset = align_up_to_large_page_size(offset);
265
266        let load_info = crate::elf::load_static_elf(
267            importer,
268            sidecar,
269            0,
270            offset,
271            false,
272            BootPageAcceptance::Exclusive,
273            "sidecar-kernel",
274        )
275        .map_err(|e| Error::Kernel(crate::linux::Error::ElfLoader(e)))?;
276
277        (
278            load_info.next_available_address - offset,
279            load_info.entrypoint,
280        )
281    } else {
282        (0, 0)
283    };
284
285    let sidecar_base = offset;
286    offset += sidecar_size;
287
288    let load_info = crate::elf::load_static_elf(
289        importer,
290        shim,
291        0,
292        offset,
293        false,
294        BootPageAcceptance::Exclusive,
295        "underhill-boot-shim",
296    )
297    .map_err(Error::Shim)?;
298    tracing::trace!("The boot shim loaded at {load_info:x?}");
299    let crate::elf::LoadInfo {
300        minimum_address_used: shim_base_addr,
301        next_available_address: mut offset,
302        entrypoint: shim_entry_address,
303    } = load_info;
304
305    // Compute initrd CRC before the file reference is consumed by the importer.
306    let mut buf = ChunkBuf::new();
307    let initrd_crc = if let Some((ref mut initrd_file, initrd_len)) = initrd {
308        buf.crc32(*initrd_file, initrd_len)
309            .map_err(Error::InitrdRead)?
310    } else {
311        crc32fast::hash(&[])
312    };
313
314    // Optionally import initrd if specified.
315    let ramdisk = if let Some((initrd_file, initrd_len)) = initrd {
316        let initrd_base = offset;
317        let initrd_size = align_up_to_page_size(initrd_len);
318
319        buf.import_file_region(
320            importer,
321            ImportFileRegion {
322                file: initrd_file,
323                file_offset: 0,
324                file_length: initrd_len,
325                gpa: initrd_base,
326                memory_length: initrd_len,
327                acceptance: kernel_acceptance,
328                tag: "underhill-initrd",
329            },
330        )
331        .map_err(Error::ImportInitrd)?;
332
333        offset += initrd_size;
334        Some((initrd_base, initrd_len))
335    } else {
336        None
337    };
338
339    let gdt_base_address = offset;
340    let gdt_size = HV_PAGE_SIZE;
341    offset += gdt_size;
342
343    let boot_params_base = offset;
344    let boot_params_size = HV_PAGE_SIZE;
345
346    offset += boot_params_size;
347
348    let cmdline_base = offset;
349    let (cmdline, policy) = match command_line {
350        CommandLineType::Static(val) => (val, CommandLinePolicy::STATIC),
351        CommandLineType::HostAppendable(val) => (val, CommandLinePolicy::APPEND_CHOSEN),
352    };
353
354    if cmdline.len() > COMMAND_LINE_SIZE {
355        return Err(Error::CommandLineSize(cmdline.len()));
356    }
357
358    let mut static_command_line = [0; COMMAND_LINE_SIZE];
359    static_command_line[..cmdline.len()].copy_from_slice(cmdline.as_bytes());
360    let paravisor_command_line = ParavisorCommandLine {
361        policy,
362        static_command_line_len: cmdline.len() as u16,
363        static_command_line,
364    };
365
366    importer.import_pages(
367        cmdline_base / HV_PAGE_SIZE,
368        1,
369        "underhill-command-line",
370        BootPageAcceptance::Exclusive,
371        paravisor_command_line.as_bytes(),
372    )?;
373
374    offset += HV_PAGE_SIZE;
375
376    // Reserve space for the VTL2 reserved region.
377    let reserved_region_size = PARAVISOR_RESERVED_VTL2_PAGE_COUNT_MAX * HV_PAGE_SIZE;
378    let reserved_region_start = offset;
379    offset += reserved_region_size;
380
381    tracing::debug!(reserved_region_start);
382
383    let parameter_region_size = PARAVISOR_VTL2_CONFIG_REGION_PAGE_COUNT_MAX * HV_PAGE_SIZE;
384    let parameter_region_start = offset;
385    offset += parameter_region_size;
386
387    tracing::debug!(parameter_region_start);
388
389    // Reserve 8K for the bootshim log buffer. Import these pages so they are
390    // available early without extra acceptance calls.
391    let bootshim_log_size = HV_PAGE_SIZE * 2;
392    let bootshim_log_start = offset;
393    offset += bootshim_log_size;
394
395    importer.import_pages(
396        bootshim_log_start / HV_PAGE_SIZE,
397        bootshim_log_size / HV_PAGE_SIZE,
398        "ohcl-boot-shim-log-buffer",
399        BootPageAcceptance::Exclusive,
400        &[],
401    )?;
402
403    // Reserve 16 pages for a bootshim heap. This is only used to parse the
404    // protobuf payload from the previous instance in a servicing boot.
405    //
406    // Import these pages as it greatly simplifies the early startup code in the
407    // bootshim for isolated guests. This allows the bootshim to use these pages
408    // early on without extra acceptance calls.
409    let heap_start = offset;
410    let heap_size = 16 * HV_PAGE_SIZE;
411    importer.import_pages(
412        heap_start / HV_PAGE_SIZE,
413        heap_size / HV_PAGE_SIZE,
414        "ohcl-boot-shim-heap",
415        BootPageAcceptance::Exclusive,
416        &[],
417    )?;
418    offset += heap_size;
419
420    // Some loaders only fix up identity map entries that overlap the relocation
421    // region, so keep the page table region in the same large page as it.
422    offset = avoid_page_table_large_page_boundary(offset, X64_LARGE_PAGE_SIZE);
423
424    // The end of memory used by the loader, excluding pagetables.
425    let end_of_underhill_mem = offset;
426
427    // Page tables live at the end of VTL2 ram used by the bootshim.
428    //
429    // Size the available page table memory as 5 pages + 2 * 1GB of memory. This
430    // allows underhill to be mapped across a 512 GB boundary when using more
431    // than 1 GB, as the PDPTE will span 2 PML4E entries. Each GB of memory
432    // mapped requires 1 page for 2MB pages. Give 2 extra base pages and 1
433    // additional page per GB of mapped memory to allow the page table
434    // relocation code to be simpler, and not need to reclaim free pages from
435    // tables that have no valid entries.
436    //
437    // FUTURE: It would be better to change it so the shim only needs to map
438    //         itself, kernel, initrd and IGVM parameters. This requires
439    //         changing how the e820 map is constructed for the kernel along
440    //         with changing the contract on where the IGVM parameters live
441    //         within VTL2's memory.
442    let local_map = match isolation_type {
443        IsolationType::Snp | IsolationType::Tdx => {
444            Some((PARAVISOR_LOCAL_MAP_VA, PARAVISOR_LOCAL_MAP_SIZE))
445        }
446        _ => None,
447    };
448
449    let page_table_base_page_count = 5;
450    let page_table_dynamic_page_count = {
451        // Double the count to allow for simpler reconstruction.
452        calculate_pde_table_count(memory_start_address, memory_size) * 2
453            + local_map.map_or(0, |v| calculate_pde_table_count(v.0, v.1))
454    };
455    let page_table_isolation_page_count = match isolation_type {
456        IsolationType::Tdx => {
457            // TDX requires up to an extra 3 pages to map the reset vector as a
458            // 4K page.
459            3
460        }
461        _ => 0,
462    };
463    let page_table_page_count = page_table_base_page_count
464        + page_table_dynamic_page_count
465        + page_table_isolation_page_count;
466    let page_table_region_size = HV_PAGE_SIZE * page_table_page_count;
467    let page_table_region_start = offset;
468    offset += page_table_region_size;
469
470    tracing::debug!(page_table_region_start, page_table_region_size);
471
472    // Construct the memory ranges that will be identity mapped
473    let mut ranges: Vec<MappedRange> = Vec::new();
474
475    ranges.push(MappedRange::new(
476        memory_start_address,
477        memory_start_address + memory_size,
478    ));
479
480    if let Some((local_map_start, size)) = local_map {
481        ranges.push(MappedRange::new(local_map_start, local_map_start + size));
482    }
483
484    if isolation_type == IsolationType::Tdx {
485        const RESET_VECTOR_ADDR: u64 = 0xffff_f000;
486        ranges.push(MappedRange::new(
487            RESET_VECTOR_ADDR,
488            RESET_VECTOR_ADDR + page_table::x64::X64_PAGE_SIZE,
489        ));
490    }
491
492    ranges.sort_by_key(|r| r.start());
493
494    // Initialize the page table builder, and build the page table
495    let mut page_table_work_buffer: Vec<PageTable> =
496        vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
497    let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
498    let mut page_table_builder = PageTableBuilder::new(
499        page_table_region_start,
500        page_table_work_buffer.as_mut_slice(),
501        page_table.as_mut_slice(),
502        ranges.as_slice(),
503    )?;
504
505    if isolation_type == IsolationType::Snp {
506        page_table_builder = page_table_builder.with_confidential_bit(51);
507    }
508
509    let page_table = page_table_builder.build()?;
510
511    assert!((page_table.len() as u64).is_multiple_of(HV_PAGE_SIZE));
512    let page_table_page_base = page_table_region_start / HV_PAGE_SIZE;
513    assert!(page_table.len() as u64 <= page_table_region_size);
514    let offset = offset;
515
516    if with_relocation {
517        // Indicate relocation information. Don't include page table region.
518        importer.relocation_region(
519            memory_start_address,
520            end_of_underhill_mem - memory_start_address,
521            X64_LARGE_PAGE_SIZE,
522            PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS,
523            1 << 48,
524            true,
525            true,
526            0, // BSP
527        )?;
528
529        // Tell the loader page table relocation information.
530        importer.page_table_relocation(
531            page_table_region_start,
532            page_table_region_size / HV_PAGE_SIZE,
533            page_table.len() as u64 / HV_PAGE_SIZE,
534            0,
535        )?;
536    }
537
538    // The memory used by the loader must be smaller than the memory available.
539    if offset > memory_start_address + memory_size {
540        return Err(Error::NotEnoughMemory(offset - memory_start_address));
541    }
542
543    let (initrd_base, initrd_size) = ramdisk.unwrap_or((0, 0));
544    // Shim parameters for locations are relative to the base of where the shim is loaded.
545    let calculate_shim_offset = |addr: u64| addr.wrapping_sub(shim_base_addr) as i64;
546    let shim_params = ShimParamsRaw {
547        kernel_entry_offset: calculate_shim_offset(kernel_entrypoint),
548        cmdline_offset: calculate_shim_offset(cmdline_base),
549        initrd_offset: calculate_shim_offset(initrd_base),
550        initrd_size,
551        initrd_crc,
552        supported_isolation_type: match isolation_type {
553            // To the shim, None and VBS isolation are the same. The shim
554            // queries CPUID when running to determine if page acceptance needs
555            // to be done.
556            IsolationType::None | IsolationType::Vbs => {
557                loader_defs::shim::SupportedIsolationType::VBS
558            }
559            IsolationType::Snp => loader_defs::shim::SupportedIsolationType::SNP,
560            IsolationType::Tdx => loader_defs::shim::SupportedIsolationType::TDX,
561        },
562        memory_start_offset: calculate_shim_offset(memory_start_address),
563        memory_size,
564        parameter_region_offset: calculate_shim_offset(parameter_region_start),
565        parameter_region_size,
566        vtl2_reserved_region_offset: calculate_shim_offset(reserved_region_start),
567        vtl2_reserved_region_size: reserved_region_size,
568        sidecar_offset: calculate_shim_offset(sidecar_base),
569        sidecar_size,
570        sidecar_entry_offset: calculate_shim_offset(sidecar_entrypoint),
571        used_start: calculate_shim_offset(memory_start_address),
572        used_end: calculate_shim_offset(offset),
573        bounce_buffer_start: bounce_buffer.map_or(0, |r| calculate_shim_offset(r.start())),
574        bounce_buffer_size: bounce_buffer.map_or(0, |r| r.len()),
575        log_buffer_start: calculate_shim_offset(bootshim_log_start),
576        log_buffer_size: bootshim_log_size,
577        heap_start_offset: calculate_shim_offset(heap_start),
578        heap_size,
579        persisted_state_region_offset: calculate_shim_offset(persisted_region_base),
580        persisted_state_region_size: persisted_region_size,
581    };
582
583    tracing::debug!(boot_params_base, "shim gpa");
584
585    importer
586        .import_pages(
587            boot_params_base / HV_PAGE_SIZE,
588            boot_params_size / HV_PAGE_SIZE,
589            "underhill-shim-params",
590            BootPageAcceptance::Exclusive,
591            shim_params.as_bytes(),
592        )
593        .map_err(Error::Importer)?;
594
595    importer.import_pages(
596        page_table_page_base,
597        page_table_page_count,
598        "underhill-page-tables",
599        BootPageAcceptance::Exclusive,
600        page_table,
601    )?;
602
603    // Set selectors and control registers
604    // Setup two selectors and segment registers.
605    // ds, es, fs, gs, ss are linearSelector
606    // cs is linearCode64Selector
607
608    // GDT is laid out as (counting by the small entries):
609    //  0: null descriptor,
610    //  1: null descriptor,
611    //  2: linear code64 descriptor,
612    //  3. linear descriptor for data
613    //  4: here you can add more descriptors.
614
615    let default_data_attributes: u16 = X64_DEFAULT_DATA_SEGMENT_ATTRIBUTES.into();
616    let default_code64_attributes: u16 = X64_DEFAULT_CODE_SEGMENT_ATTRIBUTES.into();
617    let gdt = [
618        // A large null descriptor.
619        GdtEntry::new_zeroed(),
620        GdtEntry::new_zeroed(),
621        // Code descriptor for the long mode.
622        GdtEntry {
623            limit_low: 0xffff,
624            attr_low: default_code64_attributes as u8,
625            attr_high: (default_code64_attributes >> 8) as u8,
626            ..GdtEntry::new_zeroed()
627        },
628        // Data descriptor.
629        GdtEntry {
630            limit_low: 0xffff,
631            attr_low: default_data_attributes as u8,
632            attr_high: (default_data_attributes >> 8) as u8,
633            ..GdtEntry::new_zeroed()
634        },
635    ];
636
637    const LINEAR_CODE64_DESCRIPTOR_INDEX: usize = 2;
638    const LINEAR_DATA_DESCRIPTOR_INDEX: usize = 3;
639    const RPL: u8 = 0x00; // requested priviledge level: the highest
640
641    let linear_code64_descriptor_selector =
642        SegmentSelector::from_gdt_index(LINEAR_CODE64_DESCRIPTOR_INDEX as u16, RPL);
643    let linear_data_descriptor_selector =
644        SegmentSelector::from_gdt_index(LINEAR_DATA_DESCRIPTOR_INDEX as u16, RPL);
645
646    importer.import_pages(
647        gdt_base_address / HV_PAGE_SIZE,
648        gdt_size / HV_PAGE_SIZE,
649        "underhill-gdt",
650        BootPageAcceptance::Exclusive,
651        gdt.as_bytes(),
652    )?;
653
654    let mut import_reg = |register| {
655        importer
656            .import_vp_register(register)
657            .map_err(Error::Importer)
658    };
659
660    // Import GDTR and selectors.
661    import_reg(X86Register::Gdtr(TableRegister {
662        base: gdt_base_address,
663        limit: (size_of_val(&gdt) - 1) as u16,
664    }))?;
665
666    let ds = SegmentRegister {
667        selector: linear_data_descriptor_selector.into_bits(),
668        base: 0,
669        limit: 0xffffffff,
670        attributes: default_data_attributes,
671    };
672    import_reg(X86Register::Ds(ds))?;
673    import_reg(X86Register::Es(ds))?;
674    import_reg(X86Register::Fs(ds))?;
675    import_reg(X86Register::Gs(ds))?;
676    import_reg(X86Register::Ss(ds))?;
677
678    let cs = SegmentRegister {
679        selector: linear_code64_descriptor_selector.into_bits(),
680        base: 0,
681        limit: 0xffffffff,
682        attributes: default_code64_attributes,
683    };
684    import_reg(X86Register::Cs(cs))?;
685
686    // TODO: Workaround an OS repo bug where enabling a higher VTL zeros TR
687    //       instead of setting it to the reset default state. Manually set it
688    //       to the reset default state until the OS repo is fixed.
689    //
690    //       In the future, we should just not set this at all.
691    import_reg(X86Register::Tr(SegmentRegister {
692        selector: 0x0000,
693        base: 0x00000000,
694        limit: 0x0000FFFF,
695        attributes: X64_BUSY_TSS_SEGMENT_ATTRIBUTES.into(),
696    }))?;
697
698    // Set system registers to state expected by the boot shim, 64 bit mode with
699    // paging enabled.
700
701    // Set CR0
702    import_reg(X86Register::Cr0(
703        x86defs::X64_CR0_PG | x86defs::X64_CR0_PE | x86defs::X64_CR0_NE,
704    ))?;
705
706    // Set CR3 to point to page table
707    import_reg(X86Register::Cr3(page_table_region_start))?;
708
709    // Set CR4
710    import_reg(X86Register::Cr4(
711        x86defs::X64_CR4_PAE | x86defs::X64_CR4_MCE | x86defs::X64_CR4_OSXSAVE,
712    ))?;
713
714    // Set EFER to LMA, LME, and NXE for 64 bit mode.
715    import_reg(X86Register::Efer(
716        x86defs::X64_EFER_LMA | x86defs::X64_EFER_LME | x86defs::X64_EFER_NXE,
717    ))?;
718
719    // Set PAT
720    import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?;
721
722    // Setup remaining registers
723    // Set %rsi to relative location of boot_params_base
724    let relative_boot_params_base = boot_params_base - shim_base_addr;
725    import_reg(X86Register::Rsi(relative_boot_params_base))?;
726
727    // Set %rip to the shim entry point.
728    import_reg(X86Register::Rip(shim_entry_address))?;
729
730    // Load parameter regions.
731    let config_region_page_base = parameter_region_start / HV_PAGE_SIZE;
732
733    // Slit
734    let slit_page_base = config_region_page_base + PARAVISOR_CONFIG_SLIT_PAGE_INDEX;
735    let slit_parameter_area = importer.create_parameter_area(
736        slit_page_base,
737        PARAVISOR_CONFIG_SLIT_SIZE_PAGES as u32,
738        "underhill-slit",
739    )?;
740    importer.import_parameter(slit_parameter_area, 0, IgvmParameterType::Slit)?;
741
742    // Pptt
743    let pptt_page_base = config_region_page_base + PARAVISOR_CONFIG_PPTT_PAGE_INDEX;
744    let pptt_parameter_area = importer.create_parameter_area(
745        pptt_page_base,
746        PARAVISOR_CONFIG_PPTT_SIZE_PAGES as u32,
747        "underhill-pptt",
748    )?;
749    importer.import_parameter(pptt_parameter_area, 0, IgvmParameterType::Pptt)?;
750
751    // device tree
752    let dt_page_base = config_region_page_base + PARAVISOR_CONFIG_DEVICE_TREE_PAGE_INDEX;
753    let dt_parameter_area = importer.create_parameter_area(
754        dt_page_base,
755        PARAVISOR_CONFIG_DEVICE_TREE_SIZE_PAGES as u32,
756        "underhill-device-tree",
757    )?;
758    importer.import_parameter(dt_parameter_area, 0, IgvmParameterType::DeviceTree)?;
759
760    if isolation_type == IsolationType::Snp {
761        let reserved_region_page_base = reserved_region_start / HV_PAGE_SIZE;
762        let secrets_page_base: u64 =
763            reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_SECRETS_PAGE_INDEX;
764        importer.import_pages(
765            secrets_page_base,
766            PARAVISOR_RESERVED_VTL2_SNP_SECRETS_SIZE_PAGES,
767            "underhill-snp-secrets-page",
768            BootPageAcceptance::SecretsPage,
769            &[],
770        )?;
771
772        let cpuid_page = create_snp_cpuid_page();
773        let cpuid_page_base =
774            reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_CPUID_PAGE_INDEX;
775        importer.import_pages(
776            cpuid_page_base,
777            1,
778            "underhill-snp-cpuid-page",
779            BootPageAcceptance::CpuidPage,
780            cpuid_page.as_bytes(),
781        )?;
782
783        importer.import_pages(
784            cpuid_page_base + 1,
785            1,
786            "underhill-snp-cpuid-extended-state-page",
787            BootPageAcceptance::CpuidExtendedStatePage,
788            &[],
789        )?;
790
791        let vmsa_page_base =
792            reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_VMSA_PAGE_INDEX;
793        importer.set_vp_context_page(vmsa_page_base)?;
794    }
795
796    // Load measured config.
797    // The measured config is at page 0. Free pages start at page 1.
798    let mut free_page = 1;
799    let mut measured_config = ParavisorMeasuredVtl0Config {
800        magic: ParavisorMeasuredVtl0Config::MAGIC,
801        ..FromZeros::new_zeroed()
802    };
803
804    let Vtl0Config {
805        supports_pcat,
806        supports_uefi,
807        supports_linux,
808    } = vtl0_config;
809
810    if supports_pcat {
811        measured_config.supported_vtl0.set_pcat_supported(true);
812    }
813
814    if let Some((uefi, vp_context)) = &supports_uefi {
815        measured_config.supported_vtl0.set_uefi_supported(true);
816        let vp_context_page = free_page;
817        free_page += 1;
818        measured_config.uefi_info = UefiInfo {
819            firmware: PageRegionDescriptor {
820                base_page_number: uefi.firmware_base / HV_PAGE_SIZE,
821                page_count: uefi.total_size / HV_PAGE_SIZE,
822            },
823            vtl0_vp_context: PageRegionDescriptor {
824                base_page_number: vp_context_page,
825                page_count: 1,
826            },
827        };
828
829        // Deposit the UEFI vp context.
830        importer.import_pages(
831            vp_context_page,
832            1,
833            "openhcl-uefi-vp-context",
834            BootPageAcceptance::Exclusive,
835            vp_context,
836        )?;
837    }
838
839    if let Some(linux) = supports_linux {
840        measured_config
841            .supported_vtl0
842            .set_linux_direct_supported(true);
843
844        let kernel_region = PageRegionDescriptor::new(
845            linux.load_info.kernel.gpa / HV_PAGE_SIZE,
846            align_up_to_page_size(linux.load_info.kernel.size) / HV_PAGE_SIZE,
847        );
848
849        let (initrd_region, initrd_size) = match linux.load_info.initrd {
850            Some(info) => {
851                if info.gpa % HV_PAGE_SIZE != 0 {
852                    return Err(Error::MemoryUnaligned(info.gpa));
853                }
854                (
855                    // initrd info is aligned up to the next page.
856                    PageRegionDescriptor::new(
857                        info.gpa / HV_PAGE_SIZE,
858                        align_up_to_page_size(info.size) / HV_PAGE_SIZE,
859                    ),
860                    info.size,
861                )
862            }
863            None => (PageRegionDescriptor::EMPTY, 0),
864        };
865
866        let command_line_page = free_page;
867        // free_page += 1;
868
869        // Import the command line as a C string.
870        importer
871            .import_pages(
872                command_line_page,
873                1,
874                "underhill-vtl0-linux-command-line",
875                BootPageAcceptance::Exclusive,
876                linux.command_line.as_bytes_with_nul(),
877            )
878            .map_err(Error::Importer)?;
879        let command_line = PageRegionDescriptor::new(command_line_page, 1);
880
881        measured_config.linux_info = LinuxInfo {
882            kernel_region,
883            kernel_entrypoint: linux.load_info.kernel.entrypoint,
884            initrd_region,
885            initrd_size,
886            command_line,
887        };
888    }
889
890    importer
891        .import_pages(
892            PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_X64,
893            1,
894            "underhill-measured-config",
895            BootPageAcceptance::Exclusive,
896            measured_config.as_bytes(),
897        )
898        .map_err(Error::Importer)?;
899
900    let vtl2_measured_config = ParavisorMeasuredVtl2Config {
901        magic: ParavisorMeasuredVtl2Config::MAGIC,
902        vtom_offset_bit: shared_gpa_boundary_bits.unwrap_or(0),
903        padding: [0; 7],
904    };
905
906    importer
907        .import_pages(
908            config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_INDEX,
909            PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES,
910            "underhill-vtl2-measured-config",
911            BootPageAcceptance::Exclusive,
912            vtl2_measured_config.as_bytes(),
913        )
914        .map_err(Error::Importer)?;
915
916    let imported_region_base =
917        config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_ACCEPTED_MEMORY_PAGE_INDEX;
918
919    importer.set_imported_regions_config_page(imported_region_base);
920
921    // Also announce the per-page expected-hashes region. The IGVM file
922    // loader populates it in finalize alongside the imported-regions page
923    // (both regions are derived from the same set of shared pages). See
924    // `openhcl_boot::verify_imported_regions_hash` diagnostic changes.
925    let expected_page_hashes_base =
926        config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_HASHES_PAGE_INDEX;
927    importer.set_expected_page_hashes_config_page(expected_page_hashes_base);
928    Ok(())
929}
930
931/// Create a hypervisor SNP CPUID page with the default values.
932fn create_snp_cpuid_page() -> HV_PSP_CPUID_PAGE {
933    let mut cpuid_page = HV_PSP_CPUID_PAGE::default();
934
935    // TODO SNP: The list used here is based earlier Microsoft projects.
936    // 1. ExtendedStateEnumeration should be part of BootPageAcceptance::CpuidExtendedStatePage,
937    // but it is unclear whether Linux supports a second page. The need for the second page is that
938    // the entries in it are actually based on supported features on a specific host.
939    // 2. ExtendedStateEnumeration should specify Xfem = 3
940    for (i, required_leaf) in crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR
941        .iter()
942        .enumerate()
943    {
944        let entry = &mut cpuid_page.cpuid_leaf_info[i];
945        entry.eax_in = required_leaf.eax;
946        entry.ecx_in = required_leaf.ecx;
947        if required_leaf.eax == CpuidFunction::ExtendedStateEnumeration.0 {
948            entry.xfem_in = 1;
949        }
950        cpuid_page.count += 1;
951    }
952
953    cpuid_page
954}
955
956/// Load the underhill kernel on arm64.
957///
958/// An optional initrd may be specified.
959///
960/// An optional `memory_page_base` may be specified. This will disable
961/// relocation support for underhill.
962pub fn load_openhcl_arm64<F>(
963    importer: &mut dyn ImageLoad<Aarch64Register>,
964    kernel_image: &mut F,
965    shim: &mut F,
966    command_line: CommandLineType<'_>,
967    mut initrd: Option<(&mut dyn ReadSeek, u64)>,
968    memory_page_base: Option<u64>,
969    memory_page_count: u64,
970    vtl0_config: Vtl0Config<'_>,
971) -> Result<(), Error>
972where
973    F: Read + Seek,
974{
975    let Vtl0Config {
976        supports_pcat,
977        supports_uefi,
978        supports_linux,
979    } = vtl0_config;
980
981    assert!(!supports_pcat);
982    assert!(supports_uefi.is_some() || supports_linux.is_some());
983
984    let paravisor_present = importer.isolation_config().paravisor_present;
985
986    // If no explicit memory base is specified, load with relocation support.
987    let with_relocation = memory_page_base.is_none();
988
989    let memory_start_address = memory_page_base
990        .map(|page_number| page_number * HV_PAGE_SIZE)
991        .unwrap_or(PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS);
992
993    let memory_size = memory_page_count * HV_PAGE_SIZE;
994
995    // Paravisor memory ranges must be 2MB (large page) aligned.
996    if !memory_start_address.is_multiple_of(u64::from(Arm64PageSize::Large)) {
997        return Err(Error::MemoryUnaligned(memory_start_address));
998    }
999
1000    if !memory_size.is_multiple_of(u64::from(Arm64PageSize::Large)) {
1001        return Err(Error::MemoryUnaligned(memory_size));
1002    }
1003
1004    // The whole memory range must be present and VTL2 protectable for the
1005    // underhill kernel to work.
1006    importer.verify_startup_memory_available(
1007        memory_start_address / HV_PAGE_SIZE,
1008        memory_page_count,
1009        if paravisor_present {
1010            StartupMemoryType::Vtl2ProtectableRam
1011        } else {
1012            StartupMemoryType::Ram
1013        },
1014    )?;
1015
1016    let mut next_addr = memory_start_address;
1017
1018    // Reserve the first 2MB for a potential persisted state region. The first
1019    // 4K page is always the persisted state header, and the bootshim may decide
1020    // to use the the remaining pages for the protobuf payload.
1021    let persisted_region_base = next_addr;
1022    let persisted_region_size = PERSISTED_REGION_SIZE;
1023    next_addr += persisted_region_size;
1024
1025    tracing::trace!(next_addr, "loading the kernel");
1026
1027    // Compute initrd CRC before the file reference is consumed by the loader.
1028    let initrd_crc = if let Some((ref mut initrd_file, initrd_len)) = initrd {
1029        ChunkBuf::new()
1030            .crc32(*initrd_file, initrd_len)
1031            .map_err(Error::InitrdRead)?
1032    } else {
1033        crc32fast::hash(&[])
1034    };
1035
1036    // The aarch64 Linux kernel image is most commonly found as a flat binary with a
1037    // header rather than an ELF.
1038    // DeviceTree is generated dynamically by the boot shim.
1039    let initrd_address_type = InitrdAddressType::AfterKernel;
1040    let initrd_config = initrd.map(|(initrd_file, initrd_size)| InitrdConfig {
1041        initrd_address: initrd_address_type,
1042        initrd: initrd_file,
1043        size: initrd_size,
1044    });
1045    let device_tree_blob = None;
1046    let crate::linux::LoadInfo {
1047        kernel:
1048            KernelInfo {
1049                gpa: kernel_base,
1050                size: kernel_size,
1051                entrypoint: kernel_entry_point,
1052            },
1053        initrd: initrd_info,
1054        dtb,
1055        ..
1056    } = load_kernel_and_initrd_arm64(
1057        importer,
1058        kernel_image,
1059        next_addr,
1060        initrd_config,
1061        device_tree_blob,
1062    )
1063    .map_err(Error::Kernel)?;
1064
1065    assert!(
1066        dtb.is_none(),
1067        "DeviceTree is generated dynamically by the boot shim."
1068    );
1069
1070    tracing::trace!(kernel_base, "kernel loaded");
1071
1072    let InitrdInfo {
1073        gpa: initrd_gpa,
1074        size: initrd_size,
1075    } = if let Some(initrd_info) = initrd_info {
1076        assert!(initrd_address_type == InitrdAddressType::AfterKernel);
1077        next_addr = initrd_info.gpa + initrd_info.size;
1078        initrd_info
1079    } else {
1080        next_addr = kernel_base + kernel_size;
1081        InitrdInfo { gpa: 0, size: 0 }
1082    };
1083
1084    next_addr = align_up_to_page_size(next_addr);
1085
1086    tracing::trace!(next_addr, "loading the boot shim");
1087
1088    let crate::elf::LoadInfo {
1089        minimum_address_used: shim_base_addr,
1090        next_available_address: mut next_addr,
1091        entrypoint: shim_entry_point,
1092    } = crate::elf::load_static_elf(
1093        importer,
1094        shim,
1095        0,
1096        next_addr,
1097        false,
1098        BootPageAcceptance::Exclusive,
1099        "underhill-boot-shim",
1100    )
1101    .map_err(Error::Shim)?;
1102
1103    tracing::trace!(shim_base_addr, "boot shim loaded");
1104
1105    tracing::trace!(next_addr, "loading the command line");
1106
1107    let cmdline_base = next_addr;
1108    let (cmdline, policy) = match command_line {
1109        CommandLineType::Static(val) => (val, CommandLinePolicy::STATIC),
1110        CommandLineType::HostAppendable(val) => (val, CommandLinePolicy::APPEND_CHOSEN),
1111    };
1112
1113    if cmdline.len() > COMMAND_LINE_SIZE {
1114        return Err(Error::CommandLineSize(cmdline.len()));
1115    }
1116
1117    let mut static_command_line = [0; COMMAND_LINE_SIZE];
1118    static_command_line[..cmdline.len()].copy_from_slice(cmdline.as_bytes());
1119    let paravisor_command_line = ParavisorCommandLine {
1120        policy,
1121        static_command_line_len: cmdline.len() as u16,
1122        static_command_line,
1123    };
1124
1125    importer.import_pages(
1126        cmdline_base / HV_PAGE_SIZE,
1127        1,
1128        "underhill-command-line",
1129        BootPageAcceptance::Exclusive,
1130        paravisor_command_line.as_bytes(),
1131    )?;
1132
1133    next_addr += HV_PAGE_SIZE;
1134
1135    tracing::trace!(next_addr, "loading the boot shim parameters");
1136
1137    let shim_params_base = next_addr;
1138    let shim_params_size = HV_PAGE_SIZE;
1139
1140    next_addr += shim_params_size;
1141
1142    let parameter_region_size = PARAVISOR_VTL2_CONFIG_REGION_PAGE_COUNT_MAX * HV_PAGE_SIZE;
1143    let parameter_region_start = next_addr;
1144    next_addr += parameter_region_size;
1145
1146    tracing::debug!(parameter_region_start);
1147
1148    // Reserve 8K for the bootshim log buffer.
1149    let bootshim_log_size = HV_PAGE_SIZE * 2;
1150    let bootshim_log_start = next_addr;
1151    next_addr += bootshim_log_size;
1152
1153    importer.import_pages(
1154        bootshim_log_start / HV_PAGE_SIZE,
1155        bootshim_log_size / HV_PAGE_SIZE,
1156        "ohcl-boot-shim-log-buffer",
1157        BootPageAcceptance::Exclusive,
1158        &[],
1159    )?;
1160
1161    // Reserve 16 pages for a bootshim heap. This is only used to parse the
1162    // protobuf payload from the previous instance in a servicing boot.
1163    //
1164    // Import these pages as it greatly simplifies the early startup code in the
1165    // bootshim for isolated guests. This allows the bootshim to use these pages
1166    // early on without extra acceptance calls.
1167    let heap_start = next_addr;
1168    let heap_size = 16 * HV_PAGE_SIZE;
1169    importer.import_pages(
1170        heap_start / HV_PAGE_SIZE,
1171        heap_size / HV_PAGE_SIZE,
1172        "ohcl-boot-shim-heap",
1173        BootPageAcceptance::Exclusive,
1174        &[],
1175    )?;
1176    next_addr += heap_size;
1177
1178    // Some loaders only fix up identity map entries that overlap the relocation
1179    // region, so keep the page table region in the same large page as it.
1180    next_addr = avoid_page_table_large_page_boundary(next_addr, u64::from(Arm64PageSize::Large));
1181
1182    // The end of memory used by the loader, excluding pagetables.
1183    let end_of_underhill_mem = next_addr;
1184
1185    // Page tables live at the end of the VTL2 imported region, which allows it
1186    // to be relocated separately.
1187    let page_table_base_page_count = 5;
1188    let page_table_dynamic_page_count = 2 * page_table_base_page_count;
1189    let page_table_page_count = page_table_base_page_count + page_table_dynamic_page_count;
1190    let page_table_region_size = HV_PAGE_SIZE * page_table_page_count;
1191    let page_table_region_start = next_addr;
1192    next_addr += page_table_region_size;
1193
1194    tracing::debug!(page_table_region_start, page_table_region_size);
1195
1196    let next_addr = next_addr;
1197
1198    // The memory used by the loader must be smaller than the memory available.
1199    if next_addr > memory_start_address + memory_size {
1200        return Err(Error::NotEnoughMemory(next_addr - memory_start_address));
1201    }
1202
1203    // Shim parameters for locations are relative to the base of where the shim is loaded.
1204    let calculate_shim_offset = |addr: u64| -> i64 { addr.wrapping_sub(shim_base_addr) as i64 };
1205    let shim_params = ShimParamsRaw {
1206        kernel_entry_offset: calculate_shim_offset(kernel_entry_point),
1207        cmdline_offset: calculate_shim_offset(cmdline_base),
1208        initrd_offset: calculate_shim_offset(initrd_gpa),
1209        initrd_size,
1210        initrd_crc,
1211        supported_isolation_type: match importer.isolation_config().isolation_type {
1212            IsolationType::None | IsolationType::Vbs => {
1213                loader_defs::shim::SupportedIsolationType::VBS
1214            }
1215            _ => panic!("only None and VBS are supported for ARM64"),
1216        },
1217        memory_start_offset: calculate_shim_offset(memory_start_address),
1218        memory_size,
1219        parameter_region_offset: calculate_shim_offset(parameter_region_start),
1220        parameter_region_size,
1221        vtl2_reserved_region_offset: 0,
1222        vtl2_reserved_region_size: 0,
1223        sidecar_offset: 0,
1224        sidecar_size: 0,
1225        sidecar_entry_offset: 0,
1226        used_start: calculate_shim_offset(memory_start_address),
1227        used_end: calculate_shim_offset(next_addr),
1228        bounce_buffer_start: 0,
1229        bounce_buffer_size: 0,
1230        log_buffer_start: calculate_shim_offset(bootshim_log_start),
1231        log_buffer_size: bootshim_log_size,
1232        heap_start_offset: calculate_shim_offset(heap_start),
1233        heap_size,
1234        persisted_state_region_offset: calculate_shim_offset(persisted_region_base),
1235        persisted_state_region_size: persisted_region_size,
1236    };
1237
1238    importer
1239        .import_pages(
1240            shim_params_base / HV_PAGE_SIZE,
1241            shim_params_size / HV_PAGE_SIZE,
1242            "underhill-shim-params",
1243            BootPageAcceptance::Exclusive,
1244            shim_params.as_bytes(),
1245        )
1246        .map_err(Error::Importer)?;
1247
1248    let mut measured_config = ParavisorMeasuredVtl0Config {
1249        magic: ParavisorMeasuredVtl0Config::MAGIC,
1250        ..FromZeros::new_zeroed()
1251    };
1252
1253    if let Some((uefi, vp_context)) = &supports_uefi {
1254        measured_config.supported_vtl0.set_uefi_supported(true);
1255        let vp_context_page = PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_AARCH64 + 1;
1256        measured_config.uefi_info = UefiInfo {
1257            firmware: PageRegionDescriptor {
1258                base_page_number: uefi.firmware_base / HV_PAGE_SIZE,
1259                page_count: uefi.total_size / HV_PAGE_SIZE,
1260            },
1261            vtl0_vp_context: PageRegionDescriptor {
1262                base_page_number: vp_context_page,
1263                page_count: 1,
1264            },
1265        };
1266
1267        // Deposit the UEFI vp context.
1268        importer.import_pages(
1269            vp_context_page,
1270            1,
1271            "openhcl-uefi-vp-context",
1272            BootPageAcceptance::Exclusive,
1273            vp_context,
1274        )?;
1275    }
1276
1277    importer
1278        .import_pages(
1279            PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_AARCH64,
1280            1,
1281            "underhill-measured-config",
1282            BootPageAcceptance::Exclusive,
1283            measured_config.as_bytes(),
1284        )
1285        .map_err(Error::Importer)?;
1286
1287    tracing::trace!(page_table_region_start, "loading the page tables");
1288
1289    let memory_attribute_indirection = MemoryAttributeIndirectionEl1([
1290        MemoryAttributeEl1::Device_nGnRnE,
1291        MemoryAttributeEl1::Normal_NonCacheable,
1292        MemoryAttributeEl1::Normal_WriteThrough,
1293        MemoryAttributeEl1::Normal_WriteBack,
1294        MemoryAttributeEl1::Device_nGnRnE,
1295        MemoryAttributeEl1::Device_nGnRnE,
1296        MemoryAttributeEl1::Device_nGnRnE,
1297        MemoryAttributeEl1::Device_nGnRnE,
1298    ]);
1299    let mut page_tables: Vec<u8> = vec![0; page_table_region_size as usize];
1300    let page_tables = page_table::aarch64::build_identity_page_tables_aarch64(
1301        page_table_region_start,
1302        memory_start_address,
1303        memory_size,
1304        memory_attribute_indirection,
1305        page_tables.as_mut_slice(),
1306    );
1307    assert!((page_tables.len() as u64).is_multiple_of(HV_PAGE_SIZE));
1308    let page_table_page_base = page_table_region_start / HV_PAGE_SIZE;
1309    assert!(page_tables.len() as u64 <= page_table_region_size);
1310    assert!(page_table_region_size as usize > page_tables.len());
1311
1312    if with_relocation {
1313        // Indicate relocation information. Don't include page table region.
1314        importer.relocation_region(
1315            memory_start_address,
1316            end_of_underhill_mem - memory_start_address,
1317            Arm64PageSize::Large.into(),
1318            PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS,
1319            1 << 48,
1320            true,
1321            false,
1322            0, // BSP
1323        )?;
1324
1325        // Tell the loader page table relocation information.
1326        importer.page_table_relocation(
1327            page_table_region_start,
1328            page_table_region_size / HV_PAGE_SIZE,
1329            page_tables.len() as u64 / HV_PAGE_SIZE,
1330            0,
1331        )?;
1332    }
1333
1334    importer.import_pages(
1335        page_table_page_base,
1336        page_table_page_count,
1337        "underhill-page-tables",
1338        BootPageAcceptance::Exclusive,
1339        page_tables,
1340    )?;
1341
1342    tracing::trace!("Importing register state");
1343
1344    let mut import_reg = |register| {
1345        importer
1346            .import_vp_register(register)
1347            .map_err(Error::Importer)
1348    };
1349
1350    // Set %X0 to relative location of boot_params_base
1351    let relative_boot_params_base = shim_params_base - shim_base_addr;
1352    import_reg(AArch64Register::X0(relative_boot_params_base).into())?;
1353
1354    // Set %pc to the shim entry point.
1355    import_reg(AArch64Register::Pc(shim_entry_point).into())?;
1356
1357    // System registers
1358
1359    import_reg(AArch64Register::Cpsr(Cpsr64::new().with_sp(true).with_el(1).into()).into())?;
1360
1361    // This is what Hyper-V uses. qemu/KVM, and qemu/max use slightly
1362    // different flags.
1363    // KVM sets these in addition to what the Hyper-V uses:
1364    //
1365    // .with_sa(true)
1366    // .with_itd(true)
1367    // .with_sed(true)
1368    //
1369    // Windows sets:
1370    //
1371    // .with_sa(true)
1372    // .with_sa0(true)
1373    // .with_n_aa(true)
1374    // .with_sed(true)
1375    // .with_dze(true)
1376    // .with_en_ib(true)
1377    // .with_dssbs(true)
1378    //
1379    // Maybe could enforce the `s`tack `a`lignment, here, too. Depends on
1380    // the compiler generating code aligned accesses for the stack.
1381    //
1382    // Hyper-V sets:
1383    import_reg(
1384        AArch64Register::SctlrEl1(
1385            SctlrEl1::new()
1386                // MMU enable for EL1&0 stage 1 address translation.
1387                // It can be turned off in VTL2 for debugging.
1388                // The family of the `at` instructions and the `PAR_EL1` register are
1389                // useful for debugging MMU issues.
1390                .with_m(true)
1391                // Stage 1 Cacheability control, for data accesses.
1392                .with_c(true)
1393                // Stage 1 Cacheability control, for code.
1394                .with_i(true)
1395                // Reserved flags, must be set
1396                .with_eos(true)
1397                .with_tscxt(true)
1398                .with_eis(true)
1399                .with_span(true)
1400                .with_n_tlsmd(true)
1401                .with_lsmaoe(true)
1402                .into(),
1403        )
1404        .into(),
1405    )?;
1406
1407    // Hyper-V UEFI and qemu/KVM use the same value for TCR_EL1.
1408    // They set `t0sz` to `28` as they map memory pretty low.
1409    // In the paravisor case, need more flexibility.
1410    // For the details, refer to the "Learning the architecture" series
1411    // on the ARM website.
1412    import_reg(
1413        AArch64Register::TcrEl1(
1414            TranslationControlEl1::new()
1415                .with_t0sz(0x11)
1416                .with_irgn0(1)
1417                .with_orgn0(1)
1418                .with_sh0(3)
1419                .with_tg0(TranslationGranule0::TG_4KB)
1420                // Disable TTBR1_EL1 walks (i.e. the upper half).
1421                .with_epd1(1)
1422                // Due to erratum #822227, need to set a valid TG1 regardless of EPD1.
1423                .with_tg1(TranslationGranule1::TG_4KB)
1424                .with_ips(IntermPhysAddrSize::IPA_48_BITS_256_TB)
1425                .into(),
1426        )
1427        .into(),
1428    )?;
1429
1430    // The Memory Attribute Indirection
1431    import_reg(AArch64Register::MairEl1(memory_attribute_indirection.into()).into())?;
1432    import_reg(
1433        AArch64Register::Ttbr0El1(
1434            TranslationBaseEl1::new()
1435                .with_baddr(page_table_region_start)
1436                .into(),
1437        )
1438        .into(),
1439    )?;
1440
1441    // VBAR is in the undefined state, setting it to 0 albeit
1442    // without the vector exception table. The shim can configure that on its own
1443    // if need be.
1444    import_reg(AArch64Register::VbarEl1(0).into())?;
1445
1446    // Load parameter regions.
1447    let config_region_page_base = parameter_region_start / HV_PAGE_SIZE;
1448
1449    // Slit
1450    let slit_page_base = config_region_page_base + PARAVISOR_CONFIG_SLIT_PAGE_INDEX;
1451    let slit_parameter_area = importer.create_parameter_area(
1452        slit_page_base,
1453        PARAVISOR_CONFIG_SLIT_SIZE_PAGES as u32,
1454        "underhill-slit",
1455    )?;
1456    importer.import_parameter(slit_parameter_area, 0, IgvmParameterType::Slit)?;
1457
1458    // Pptt
1459    let pptt_page_base = config_region_page_base + PARAVISOR_CONFIG_PPTT_PAGE_INDEX;
1460    let pptt_parameter_area = importer.create_parameter_area(
1461        pptt_page_base,
1462        PARAVISOR_CONFIG_PPTT_SIZE_PAGES as u32,
1463        "underhill-pptt",
1464    )?;
1465    importer.import_parameter(pptt_parameter_area, 0, IgvmParameterType::Pptt)?;
1466
1467    // device tree
1468    let dt_page_base = config_region_page_base + PARAVISOR_CONFIG_DEVICE_TREE_PAGE_INDEX;
1469    let dt_parameter_area = importer.create_parameter_area(
1470        dt_page_base,
1471        PARAVISOR_CONFIG_DEVICE_TREE_SIZE_PAGES as u32,
1472        "underhill-device-tree",
1473    )?;
1474    importer.import_parameter(dt_parameter_area, 0, IgvmParameterType::DeviceTree)?;
1475
1476    let vtl2_measured_config = ParavisorMeasuredVtl2Config {
1477        magic: ParavisorMeasuredVtl2Config::MAGIC,
1478        vtom_offset_bit: 0,
1479        padding: [0; 7],
1480    };
1481
1482    importer
1483        .import_pages(
1484            config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_INDEX,
1485            PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES,
1486            "underhill-vtl2-measured-config",
1487            BootPageAcceptance::Exclusive,
1488            vtl2_measured_config.as_bytes(),
1489        )
1490        .map_err(Error::Importer)?;
1491
1492    let imported_region_base =
1493        config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_ACCEPTED_MEMORY_PAGE_INDEX;
1494
1495    importer.set_imported_regions_config_page(imported_region_base);
1496
1497    // Also announce the per-page expected-hashes region (see comments in
1498    // the x86 sibling above).
1499    let expected_page_hashes_base =
1500        config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_HASHES_PAGE_INDEX;
1501    importer.set_expected_page_hashes_config_page(expected_page_hashes_base);
1502
1503    Ok(())
1504}
1505
1506#[cfg(test)]
1507mod page_table_layout_tests {
1508    use super::*;
1509
1510    #[test]
1511    fn page_table_region_avoids_large_page_boundary() {
1512        for large_page_size in [X64_LARGE_PAGE_SIZE, u64::from(Arm64PageSize::Large)] {
1513            assert_eq!(
1514                avoid_page_table_large_page_boundary(large_page_size, large_page_size),
1515                large_page_size + HV_PAGE_SIZE
1516            );
1517            assert_eq!(
1518                avoid_page_table_large_page_boundary(
1519                    large_page_size - HV_PAGE_SIZE,
1520                    large_page_size
1521                ),
1522                large_page_size - HV_PAGE_SIZE
1523            );
1524        }
1525    }
1526}