loader/
paravisor.rs

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