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