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