Skip to main content

openhcl_boot/
main.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Bare-metal OpenHCL boot loader that prepares VTL2 before Linux starts.
5//!
6//! This measured payload validates its imported regions and host parameters,
7//! establishes the VTL2 address space and processor state, constructs Linux
8//! boot parameters and a device tree, and initializes the sidecar kernel when
9//! configured. It then transfers control to the OpenHCL Linux kernel.
10
11// See build.rs.
12#![cfg_attr(minimal_rt, no_std, no_main)]
13// UNSAFETY: Interacting with low level hardware and bootloader primitives.
14#![expect(unsafe_code)]
15
16mod arch;
17mod boot_logger;
18mod cmdline;
19mod dt;
20mod host_params;
21mod hypercall;
22mod memory;
23mod rt;
24mod sidecar;
25mod single_threaded;
26
27use crate::arch::setup_vtl2_memory;
28use crate::arch::setup_vtl2_vp;
29#[cfg(target_arch = "x86_64")]
30use crate::arch::tdx::get_tdx_tsc_reftime;
31use crate::arch::verify_imported_regions_hash;
32use crate::boot_logger::boot_logger_memory_init;
33use crate::boot_logger::boot_logger_runtime_init;
34use crate::hypercall::hvcall;
35use crate::memory::AddressSpaceManager;
36use crate::single_threaded::OffStackRef;
37use crate::single_threaded::off_stack;
38use arrayvec::ArrayString;
39use arrayvec::ArrayVec;
40use cmdline::BootCommandLineOptions;
41use core::fmt::Write;
42use dt::BootTimes;
43use dt::write_dt;
44use host_fdt_parser::ComInfo;
45use host_params::COMMAND_LINE_SIZE;
46use host_params::PartitionInfo;
47use host_params::shim_params::IsolationType;
48use host_params::shim_params::ShimParams;
49use hvdef::Vtl;
50use loader_defs::linux::SETUP_DTB;
51use loader_defs::linux::setup_data;
52use loader_defs::shim::ShimParamsRaw;
53use memory_range::RangeWalkResult;
54use memory_range::walk_ranges;
55use minimal_rt::enlightened_panic::enable_enlightened_panic;
56use sidecar::SidecarConfig;
57use sidecar_defs::SidecarOutput;
58use sidecar_defs::SidecarParams;
59use zerocopy::FromBytes;
60use zerocopy::FromZeros;
61use zerocopy::Immutable;
62use zerocopy::IntoBytes;
63use zerocopy::KnownLayout;
64
65#[derive(Debug)]
66struct CommandLineTooLong;
67
68impl From<core::fmt::Error> for CommandLineTooLong {
69    fn from(_: core::fmt::Error) -> Self {
70        Self
71    }
72}
73
74struct BuildKernelCommandLineParams<'a> {
75    params: &'a ShimParams,
76    cmdline: &'a mut ArrayString<COMMAND_LINE_SIZE>,
77    partition_info: &'a PartitionInfo,
78    can_trust_host: bool,
79    is_confidential_debug: bool,
80    sidecar: Option<&'a SidecarConfig<'a>>,
81    vtl2_pool_supported: bool,
82}
83
84/// Read and setup the underhill kernel command line into the specified buffer.
85fn build_kernel_command_line(
86    fn_params: BuildKernelCommandLineParams<'_>,
87) -> Result<(), CommandLineTooLong> {
88    let BuildKernelCommandLineParams {
89        params,
90        cmdline,
91        partition_info,
92        can_trust_host,
93        is_confidential_debug,
94        sidecar,
95        vtl2_pool_supported,
96    } = fn_params;
97
98    // For reference:
99    // https://www.kernel.org/doc/html/v5.15/admin-guide/kernel-parameters.html
100    const KERNEL_PARAMETERS: &[&str] = &[
101        // If a console is specified, then write everything to it.
102        "loglevel=8",
103        // Use a fixed 128KB log buffer by default.
104        "log_buf_len=128K",
105        // Enable time output on console for ohcldiag-dev.
106        "printk.time=1",
107        // Enable facility and level output on console for ohcldiag-dev.
108        "console_msg_format=syslog",
109        // Set uio parameter to configure vmbus ring buffer behavior.
110        "uio_hv_generic.no_mask=1",
111        // RELIABILITY: Dump anonymous pages and ELF headers only. Skip over
112        // huge pages and the shared pages.
113        "coredump_filter=0x33",
114        // PERF: No processor frequency governing.
115        "cpufreq.off=1",
116        // PERF: Disable the CPU idle time management entirely. It does not
117        // prevent the idle loop from running on idle CPUs, but it prevents
118        // the CPU idle time governors and drivers from being invoked.
119        "cpuidle.off=1",
120        // PERF: No perf checks for crypto algorithms to boot faster.
121        // Would have to evaluate the perf wins on the crypto manager vs
122        // delaying the boot up.
123        "cryptomgr.notests",
124        // PERF: Idle threads use HLT on x64 if there is no work.
125        // Believed to be a compromise between waking up the processor
126        // and the power consumption.
127        "idle=halt",
128        // WORKAROUND: Avoid init calls that assume presence of CMOS (Simple
129        // Boot Flag) or allocate the real-mode trampoline for APs.
130        "initcall_blacklist=init_real_mode,sbf_init",
131        // CONFIG-STATIC, PERF: Static loops-per-jiffy value to save time on boot.
132        "lpj=3000000",
133        // PERF: No broken timer check to boot faster.
134        "no_timer_check",
135        // CONFIG-STATIC, PERF: Using xsave makes VTL transitions being
136        // much slower. The xsave state is shared between VTLs, and we don't
137        // context switch it in the kernel when leaving/entering VTL2.
138        // Removing this will lead to corrupting register state and the
139        // undefined behaviour.
140        "noxsave",
141        // RELIABILITY: Panic on MCEs and faults in the kernel.
142        "oops=panic",
143        // RELIABILITY: Don't panic on kernel warnings.
144        "panic_on_warn=0",
145        // PERF, RELIABILITY: Don't print detailed information about the failing
146        // processes (memory maps, threads).
147        "panic_print=0",
148        // RELIABILITY: Reboot immediately on panic, no timeout.
149        "panic=-1",
150        // RELIABILITY: Don't print processor context information on a fatal
151        // signal. Our crash dump collection infrastructure seems reliable, and
152        // this information doesn't seem useful without a dump anyways.
153        // Additionally it may push important logs off the end of the kmsg
154        // page logged by the host.
155        //"print_fatal_signals=0",
156        // RELIABILITY: Unlimited logging to /dev/kmsg from userspace.
157        "printk.devkmsg=on",
158        // RELIABILITY: Reboot using a triple fault as the fastest method.
159        // That is also the method used for compatibility with earlier versions
160        // of the Microsoft HCL.
161        "reboot=t",
162        // CONFIG-STATIC: Type of the root file system.
163        "rootfstype=tmpfs",
164        // PERF: Deactivate kcompactd kernel thread, otherwise it will queue a
165        // scheduler timer periodically, which introduces jitters for VTL0.
166        "sysctl.vm.compaction_proactiveness=0",
167        // PERF: No TSC stability check when booting up to boot faster,
168        // also no validation during runtime.
169        "tsc=reliable",
170        // RELIABILITY: Panic on receiving an NMI.
171        "unknown_nmi_panic=1",
172        // Use vfio for MANA devices.
173        "vfio_pci.ids=1414:00ba",
174        // WORKAROUND: Enable no-IOMMU mode. This mode provides no device isolation,
175        // and no DMA translation.
176        "vfio.enable_unsafe_noiommu_mode=1",
177        // Specify the init path.
178        "rdinit=/underhill-init",
179        // Default to user-mode NVMe driver.
180        "OPENHCL_NVME_VFIO=1",
181        // The next three items reduce the memory overhead of the storvsc driver.
182        // Since it is only used for DVD, performance is not critical.
183        "hv_storvsc.storvsc_vcpus_per_sub_channel=2048",
184        // Fix number of hardware queues at 2.
185        "hv_storvsc.storvsc_max_hw_queues=2",
186        // Reduce the ring buffer size to 32K.
187        "hv_storvsc.storvsc_ringbuffer_size=0x8000",
188        // Disable eager mimalloc commit to prevent core dumps from being overly large
189        "MIMALLOC_ARENA_EAGER_COMMIT=0",
190        // Disable acpi runtime support. Unused in underhill, but some support
191        // is compiled in for the kernel (ie TDX mailbox protocol).
192        "acpi=off",
193    ];
194
195    const X86_KERNEL_PARAMETERS: &[&str] = &[
196        // Disable all attempts to use an IOMMU, including swiotlb.
197        "iommu=off",
198        // Don't probe for a PCI bus. PCI devices currently come from VPCI. When
199        // this changes, we will explicitly enumerate a PCI bus via devicetree.
200        "pci=off",
201    ];
202
203    const AARCH64_KERNEL_PARAMETERS: &[&str] = &[];
204
205    for p in KERNEL_PARAMETERS {
206        write!(cmdline, "{p} ")?;
207    }
208
209    let arch_parameters = if cfg!(target_arch = "x86_64") {
210        X86_KERNEL_PARAMETERS
211    } else {
212        AARCH64_KERNEL_PARAMETERS
213    };
214    for p in arch_parameters {
215        write!(cmdline, "{p} ")?;
216    }
217
218    const HARDWARE_ISOLATED_KERNEL_PARAMETERS: &[&str] = &[
219        // Even with iommu=off, the SWIOTLB is still allocated on AARCH64
220        // (iommu=off ignored entirely), and CVMs (memory encryption forces it
221        // on). Set it to a single area in 8MB. The first parameter controls the
222        // area size in slabs (2KB per slab), the second controls the number of
223        // areas (default is # of CPUs).
224        //
225        // This is set to 8MB on hardware isolated VMs since there are some
226        // scenarios, such as provisioning over DVD, which require a larger size
227        // since the buffer is being used.
228        "swiotlb=4096,1",
229    ];
230
231    const NON_HARDWARE_ISOLATED_KERNEL_PARAMETERS: &[&str] = &[
232        // Even with iommu=off, the SWIOTLB is still allocated on AARCH64
233        // (iommu=off ignored entirely). Set it to the minimum, saving ~63 MiB.
234        // The first parameter controls the area size, the second controls the
235        // number of areas (default is # of CPUs). Set them both to the minimum.
236        "swiotlb=1,1",
237    ];
238
239    if params.isolation_type.is_hardware_isolated() {
240        for p in HARDWARE_ISOLATED_KERNEL_PARAMETERS {
241            write!(cmdline, "{p} ")?;
242        }
243    } else {
244        for p in NON_HARDWARE_ISOLATED_KERNEL_PARAMETERS {
245            write!(cmdline, "{p} ")?;
246        }
247    }
248
249    // Enable the com3 console by default if it's available and we're not
250    // isolated, or if we are isolated but also have debugging enabled.
251    //
252    // Otherwise, set the console to ttynull so the kernel does not default to
253    // com1. This is overridden by any user customizations in the static or
254    // dynamic command line, as this console argument provided by the bootloader
255    // comes first.
256    write!(cmdline, "console=")?;
257    match (&partition_info.com3_serial, can_trust_host) {
258        (ComInfo::Ns16550 { current_speed, .. }, true) => {
259            write!(cmdline, "ttyS2,{current_speed} ")?
260        }
261        (ComInfo::Pl011 { current_speed, .. }, true) => {
262            write!(cmdline, "ttyAMA0,{current_speed} ")?
263        }
264        _ => write!(cmdline, "ttynull ")?,
265    }
266
267    if params.isolation_type != IsolationType::None {
268        write!(
269            cmdline,
270            "{}=1 ",
271            underhill_confidentiality::OPENHCL_CONFIDENTIAL_ENV_VAR_NAME
272        )?;
273    }
274
275    if is_confidential_debug {
276        write!(
277            cmdline,
278            "{}=1 ",
279            underhill_confidentiality::OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME
280        )?;
281    }
282
283    // Generate the NVMe keep alive command line which should look something
284    // like: OPENHCL_NVME_KEEP_ALIVE=disabled,host,privatepool
285    // TODO: Move from command line to device tree when stabilized.
286    write!(cmdline, "OPENHCL_NVME_KEEP_ALIVE=")?;
287
288    if partition_info.boot_options.disable_nvme_keep_alive {
289        write!(cmdline, "disabled,")?;
290    }
291
292    if partition_info.nvme_keepalive {
293        write!(cmdline, "host,")?;
294    } else {
295        write!(cmdline, "nohost,")?;
296    }
297
298    if vtl2_pool_supported {
299        write!(cmdline, "privatepool ")?;
300    } else {
301        write!(cmdline, "noprivatepool ")?;
302    }
303
304    if let Some(sidecar) = sidecar {
305        write!(cmdline, "{} ", sidecar.kernel_command_line())?;
306    }
307
308    // Prepend the computed parameters to the original command line.
309    cmdline.write_str(&partition_info.cmdline)?;
310
311    Ok(())
312}
313
314// The Linux kernel requires that the FDT fit within a single 256KB mapping, as
315// that is the maximum size the kernel can use during its early boot processes.
316// We also want our FDT to be as large as possible to support as many vCPUs as
317// possible. We set it to 256KB, but it must also be page-aligned, as leaving it
318// unaligned runs the possibility of it taking up 1 too many pages, resulting in
319// a 260KB mapping, which will fail.
320const FDT_SIZE: usize = 256 * 1024;
321
322#[repr(C, align(4096))]
323#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
324struct Fdt {
325    header: setup_data,
326    data: [u8; FDT_SIZE - size_of::<setup_data>()],
327}
328
329/// Raw shim parameters are provided via a relative offset from the base of
330/// where the shim is loaded. Return a ShimParams structure based on the raw
331/// offset based RawShimParams.
332fn shim_parameters(shim_params_raw_offset: isize) -> ShimParams {
333    unsafe extern "C" {
334        static __ehdr_start: u8;
335    }
336
337    let shim_base = core::ptr::addr_of!(__ehdr_start) as usize;
338
339    // SAFETY: The host is required to relocate everything by the same bias, so
340    //         the shim parameters should be at the build time specified offset
341    //         from the base address of the image.
342    let raw_shim_params = unsafe {
343        &*(shim_base.wrapping_add_signed(shim_params_raw_offset) as *const ShimParamsRaw)
344    };
345
346    ShimParams::new(shim_base as u64, raw_shim_params)
347}
348
349#[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))]
350mod x86_boot {
351    use crate::PageAlign;
352    use crate::memory::AddressSpaceManager;
353    use crate::single_threaded::OffStackRef;
354    use crate::single_threaded::off_stack;
355    use crate::zeroed;
356    use core::mem::size_of;
357    use core::ops::Range;
358    use core::ptr;
359    use loader_defs::linux::E820_RAM;
360    use loader_defs::linux::E820_RESERVED;
361    use loader_defs::linux::SETUP_E820_EXT;
362    use loader_defs::linux::boot_params;
363    use loader_defs::linux::e820entry;
364    use loader_defs::linux::setup_data;
365    use loader_defs::shim::MemoryVtlType;
366    use memory_range::MemoryRange;
367    use zerocopy::FromZeros;
368    use zerocopy::Immutable;
369    use zerocopy::KnownLayout;
370
371    #[repr(C)]
372    #[derive(FromZeros, Immutable, KnownLayout)]
373    pub struct E820Ext {
374        pub header: setup_data,
375        pub entries: [e820entry; 512],
376    }
377
378    fn add_e820_entry(
379        entry: Option<&mut e820entry>,
380        range: MemoryRange,
381        typ: u32,
382    ) -> Result<(), BuildE820MapError> {
383        *entry.ok_or(BuildE820MapError::OutOfE820Entries)? = e820entry {
384            addr: range.start().into(),
385            size: range.len().into(),
386            typ: typ.into(),
387        };
388        Ok(())
389    }
390
391    #[derive(Debug)]
392    pub enum BuildE820MapError {
393        /// Out of e820 entries.
394        OutOfE820Entries,
395    }
396
397    /// Build the e820 map for the kernel representing usable VTL2 ram.
398    pub fn build_e820_map(
399        boot_params: &mut boot_params,
400        ext: &mut E820Ext,
401        address_space: &AddressSpaceManager,
402    ) -> Result<bool, BuildE820MapError> {
403        boot_params.e820_entries = 0;
404        let mut entries = boot_params
405            .e820_map
406            .iter_mut()
407            .chain(ext.entries.iter_mut());
408
409        let mut n = 0;
410        for (range, typ) in address_space.vtl2_ranges() {
411            match typ {
412                MemoryVtlType::VTL2_RAM => {
413                    add_e820_entry(entries.next(), range, E820_RAM)?;
414                    n += 1;
415                }
416                MemoryVtlType::VTL2_CONFIG
417                | MemoryVtlType::VTL2_SIDECAR_IMAGE
418                | MemoryVtlType::VTL2_SIDECAR_NODE
419                | MemoryVtlType::VTL2_RESERVED
420                | MemoryVtlType::VTL2_GPA_POOL
421                | MemoryVtlType::VTL2_TDX_PAGE_TABLES
422                | MemoryVtlType::VTL2_BOOTSHIM_LOG_BUFFER
423                | MemoryVtlType::VTL2_PERSISTED_STATE_HEADER
424                | MemoryVtlType::VTL2_PERSISTED_STATE_PROTOBUF => {
425                    add_e820_entry(entries.next(), range, E820_RESERVED)?;
426                    n += 1;
427                }
428
429                _ => {
430                    panic!("unexpected vtl2 ram type {typ:?} for range {range:#?}");
431                }
432            }
433        }
434
435        let base = n.min(boot_params.e820_map.len());
436        boot_params.e820_entries = base as u8;
437
438        if base < n {
439            ext.header.len = ((n - base) * size_of::<e820entry>()) as u32;
440            Ok(true)
441        } else {
442            Ok(false)
443        }
444    }
445
446    pub fn build_boot_params(
447        address_space: &AddressSpaceManager,
448        initrd: Range<u64>,
449        cmdline: &str,
450        setup_data_head: *const setup_data,
451        setup_data_tail: &mut &mut setup_data,
452    ) -> OffStackRef<'static, PageAlign<boot_params>> {
453        let mut boot_params_storage = off_stack!(PageAlign<boot_params>, zeroed());
454        let boot_params = &mut boot_params_storage.0;
455        boot_params.hdr.type_of_loader = 0xff; // Unknown loader type
456
457        // HACK: A kernel change just in the Underhill kernel tree has a workaround
458        // to disable probe_roms and reserve_bios_regions when X86_SUBARCH_LGUEST
459        // (1) is set by the bootloader. This stops the kernel from reading VTL0
460        // memory during kernel boot, which can have catastrophic consequences
461        // during a servicing operation when VTL0 has written values to memory, or
462        // unaccepted page accesses in an isolated partition.
463        //
464        // This is only intended as a stopgap until a suitable upstreamable kernel
465        // patch is made.
466        boot_params.hdr.hardware_subarch = 1.into();
467
468        boot_params.hdr.ramdisk_image = (initrd.start as u32).into();
469        boot_params.ext_ramdisk_image = (initrd.start >> 32) as u32;
470        let initrd_len = initrd.end - initrd.start;
471        boot_params.hdr.ramdisk_size = (initrd_len as u32).into();
472        boot_params.ext_ramdisk_size = (initrd_len >> 32) as u32;
473
474        let e820_ext = OffStackRef::leak(off_stack!(E820Ext, zeroed()));
475
476        let used_ext = build_e820_map(boot_params, e820_ext, address_space)
477            .expect("building e820 map must succeed");
478
479        if used_ext {
480            e820_ext.header.ty = SETUP_E820_EXT;
481            setup_data_tail.next = ptr::from_ref(&e820_ext.header) as u64;
482            *setup_data_tail = &mut e820_ext.header;
483        }
484
485        let cmd_line_addr = cmdline.as_ptr() as u64;
486        boot_params.hdr.cmd_line_ptr = (cmd_line_addr as u32).into();
487        boot_params.ext_cmd_line_ptr = (cmd_line_addr >> 32) as u32;
488
489        boot_params.hdr.setup_data = (setup_data_head as u64).into();
490
491        boot_params_storage
492    }
493}
494
495/// Build the cc_blob containing the location of different parameters associated with SEV.
496#[cfg(target_arch = "x86_64")]
497fn build_cc_blob_sev_info(
498    cc_blob: &mut loader_defs::linux::cc_blob_sev_info,
499    shim_params: &ShimParams,
500) {
501    // TODO SNP: Currently only the first CPUID page is passed through.
502    // Consider changing this.
503    cc_blob.magic = loader_defs::linux::CC_BLOB_SEV_INFO_MAGIC;
504    cc_blob.version = 0;
505    cc_blob._reserved = 0;
506    cc_blob.secrets_phys = shim_params.secrets_start();
507    cc_blob.secrets_len = hvdef::HV_PAGE_SIZE as u32;
508    cc_blob._rsvd1 = 0;
509    cc_blob.cpuid_phys = shim_params.cpuid_start();
510    cc_blob.cpuid_len = hvdef::HV_PAGE_SIZE as u32;
511    cc_blob._rsvd2 = 0;
512}
513
514#[repr(C, align(4096))]
515#[derive(FromZeros, Immutable, KnownLayout)]
516struct PageAlign<T>(T);
517
518const fn zeroed<T: FromZeros>() -> T {
519    // SAFETY: `T` implements `FromZeros`, so this is a safe initialization of `T`.
520    unsafe { core::mem::MaybeUninit::<T>::zeroed().assume_init() }
521}
522
523fn get_ref_time(isolation: IsolationType) -> Option<u64> {
524    match isolation {
525        #[cfg(target_arch = "x86_64")]
526        IsolationType::Tdx => get_tdx_tsc_reftime(),
527        #[cfg(target_arch = "x86_64")]
528        IsolationType::Snp => None,
529        _ => Some(minimal_rt::reftime::reference_time()),
530    }
531}
532
533/// Dump diagnostics when initrd CRC32 does not match the build-time value.
534///
535/// Contents:
536///
537/// - `base` / `size` / `expected` (build-time) / `got` (first read)
538///   CRCs.
539/// - `got2` — a second CRC re-computed immediately from the same virtual
540///   range. If `got2 != got`, initrd memory is not being read consistently
541///   (typical symptom of stale/mismatched cache lines after the SNP shared
542///   -> private transition, rather than data being wrong in memory).
543/// - `head` / `tail` — first and last 16 bytes as hex, to fingerprint what
544///   is actually in memory.
545/// - `eighths` — CRC32 of eight roughly-equal slices of the initrd. This
546///   is a coarse "which region diverges" locator that is cheap to compute
547///   and stable across boots, so it can be compared with a known-good
548///   build without needing a per-page dump (which would blow the log
549///   budget for real-sized initrds).
550//
551// SNP TODO: temporary diagnostic; remove once the SNP initrd CRC mismatch
552// is root-caused.
553fn build_initrd_crc_diagnostic(p: &ShimParams, first_computed_crc: u32) -> ArrayString<384> {
554    let initrd_bytes = p.initrd();
555
556    // A second read from the same VA. If this differs from the first read,
557    // the initrd memory is not being read consistently, which typically
558    // indicates stale/mismatched cache lines rather than actual data
559    // corruption.
560    let second_computed_crc = crc32fast::hash(initrd_bytes);
561
562    // First 16 and last 16 bytes, as fixed-size arrays so we can rely on
563    // Debug's `{:02x?}` slice formatting.
564    let mut head = [0u8; 16];
565    let head_len = head.len().min(initrd_bytes.len());
566    head[..head_len].copy_from_slice(&initrd_bytes[..head_len]);
567
568    let mut tail = [0u8; 16];
569    let tail_len = tail.len().min(initrd_bytes.len());
570    if tail_len > 0 {
571        let start = initrd_bytes.len() - tail_len;
572        tail[..tail_len].copy_from_slice(&initrd_bytes[start..]);
573    }
574
575    // Split the initrd into (up to) 8 roughly-equal slices and CRC each.
576    // Bytes past the aligned slices go into the last chunk.
577    let mut eighths = [0u32; 8];
578    let n = initrd_bytes.len();
579    if n > 0 {
580        let step = n.div_ceil(8);
581        for (i, e) in eighths.iter_mut().enumerate() {
582            let start = i * step;
583            if start >= n {
584                break;
585            }
586            let end = ((i + 1) * step).min(n);
587            *e = crc32fast::hash(&initrd_bytes[start..end]);
588        }
589    }
590
591    let mut buf = ArrayString::<384>::new();
592    let _ = write!(
593        &mut buf,
594        "initrd crc mismatch: iso={:?} base={:#x} size={:#x} \
595         exp={:#x} got={:#x} got2={:#x} head={:02x?} tail={:02x?} \
596         eighths=[{:#x},{:#x},{:#x},{:#x},{:#x},{:#x},{:#x},{:#x}]",
597        p.isolation_type,
598        p.initrd_base,
599        p.initrd_size,
600        p.initrd_crc,
601        first_computed_crc,
602        second_computed_crc,
603        &head[..head_len],
604        &tail[..tail_len],
605        eighths[0],
606        eighths[1],
607        eighths[2],
608        eighths[3],
609        eighths[4],
610        eighths[5],
611        eighths[6],
612        eighths[7],
613    );
614    buf
615}
616
617fn shim_main(shim_params_raw_offset: isize) -> ! {
618    let p = shim_parameters(shim_params_raw_offset);
619    if p.isolation_type == IsolationType::None {
620        enable_enlightened_panic();
621    }
622
623    #[cfg(feature = "cvm_boot_log")]
624    arch::initialize_serial_io(&p);
625
626    // Enable the in-memory log.
627    boot_logger_memory_init(p.log_buffer);
628
629    // Enable global log crate.
630    log::set_logger(&boot_logger::BOOT_LOGGER).unwrap();
631    // TODO: allow overriding filter at runtime
632    log::set_max_level(log::LevelFilter::Info);
633
634    let boot_reftime = get_ref_time(p.isolation_type);
635
636    // The support code for the fast hypercalls does not set
637    // the Guest ID if it is not set yet as opposed to the slow
638    // hypercall code path where that is done automatically.
639    // Thus the fast hypercalls will fail as the the Guest ID has
640    // to be set first hence initialize hypercall support
641    // explicitly.
642    if !p.isolation_type.is_hardware_isolated() {
643        hvcall().initialize();
644    }
645
646    let mut static_options = BootCommandLineOptions::new();
647    if let Some(cmdline) = p.command_line().command_line() {
648        static_options.parse(cmdline);
649    }
650
651    let static_confidential_debug = static_options.confidential_debug;
652    let can_trust_host = p.isolation_type == IsolationType::None || static_confidential_debug;
653
654    let mut dt_storage = off_stack!(PartitionInfo, PartitionInfo::new());
655    let address_space = OffStackRef::leak(off_stack!(
656        AddressSpaceManager,
657        AddressSpaceManager::new_const()
658    ));
659    let partition_info = match PartitionInfo::read_from_dt(
660        &p,
661        &mut dt_storage,
662        address_space,
663        static_options,
664        can_trust_host,
665    ) {
666        Ok(val) => val,
667        Err(e) => panic!("unable to read device tree params {:?}", e),
668    };
669
670    // Enable logging ASAP. This is fine even when isolated, as we don't have
671    // any access to secrets in the boot shim.
672    boot_logger_runtime_init(p.isolation_type, partition_info.com3_serial.clone());
673    log::info!("openhcl_boot: logging enabled");
674    log::info!("serial configuration: {:#x?}", partition_info.com3_serial);
675
676    // Confidential debug will show up in boot_options only if included in the
677    // static command line, or if can_trust_host is true (so the dynamic command
678    // line has been parsed).
679    let is_confidential_debug =
680        static_confidential_debug || partition_info.boot_options.confidential_debug;
681
682    // Fill out the non-devicetree derived parts of PartitionInfo.
683    if !p.isolation_type.is_hardware_isolated()
684        && hvcall().vtl() == Vtl::Vtl2
685        && hvdef::HvRegisterVsmCapabilities::from(
686            hvcall()
687                .get_register(hvdef::HvAllArchRegisterName::VsmCapabilities.into())
688                .expect("failed to query vsm capabilities")
689                .as_u64(),
690        )
691        .vtl0_alias_map_available()
692    {
693        // If the vtl0 alias map was not provided in the devicetree, attempt to
694        // derive it from the architectural physical address bits.
695        //
696        // The value in the ID_AA64MMFR0_EL1 register used to determine the
697        // physical address bits can only represent multiples of 4. As a result,
698        // the Surface Pro X (and systems with similar CPUs) cannot properly
699        // report their address width of 39 bits. This causes the calculated
700        // alias map to be incorrect, which results in panics when trying to
701        // read memory and getting invalid data.
702        if partition_info.vtl0_alias_map.is_none() {
703            partition_info.vtl0_alias_map =
704                Some(1 << (arch::physical_address_bits(p.isolation_type) - 1));
705        }
706    } else {
707        // Ignore any devicetree-provided alias map if the conditions above
708        // aren't met.
709        partition_info.vtl0_alias_map = None;
710    }
711
712    // Rebind partition_info as no longer mutable.
713    let partition_info: &PartitionInfo = partition_info;
714
715    if partition_info.cpus.is_empty() {
716        panic!("no cpus");
717    }
718
719    validate_vp_hw_ids(partition_info);
720
721    setup_vtl2_memory(&p, partition_info, address_space);
722    setup_vtl2_vp(partition_info);
723
724    verify_imported_regions_hash(&p);
725
726    let mut sidecar_params = off_stack!(PageAlign<SidecarParams>, zeroed());
727    let mut sidecar_output = off_stack!(PageAlign<SidecarOutput>, zeroed());
728    let sidecar = sidecar::start_sidecar(
729        &p,
730        partition_info,
731        address_space,
732        &mut sidecar_params.0,
733        &mut sidecar_output.0,
734    );
735
736    // Rebind address_space as no longer mutable.
737    let address_space: &AddressSpaceManager = address_space;
738
739    let mut cmdline = off_stack!(ArrayString<COMMAND_LINE_SIZE>, ArrayString::new_const());
740    build_kernel_command_line(BuildKernelCommandLineParams {
741        params: &p,
742        cmdline: &mut cmdline,
743        partition_info,
744        can_trust_host,
745        is_confidential_debug,
746        sidecar: sidecar.as_ref(),
747        vtl2_pool_supported: address_space.has_vtl2_pool(),
748    })
749    .unwrap();
750
751    let mut fdt = off_stack!(Fdt, zeroed());
752    fdt.header.len = fdt.data.len() as u32;
753    fdt.header.ty = SETUP_DTB;
754
755    #[cfg(target_arch = "x86_64")]
756    let mut setup_data_tail = &mut fdt.header;
757    #[cfg(target_arch = "x86_64")]
758    let setup_data_head = core::ptr::from_ref(setup_data_tail);
759
760    #[cfg(target_arch = "x86_64")]
761    if p.isolation_type == IsolationType::Snp {
762        let cc_blob = OffStackRef::leak(off_stack!(loader_defs::linux::cc_blob_sev_info, zeroed()));
763        build_cc_blob_sev_info(cc_blob, &p);
764
765        let cc_data = OffStackRef::leak(off_stack!(loader_defs::linux::cc_setup_data, zeroed()));
766        cc_data.header.len = size_of::<loader_defs::linux::cc_setup_data>() as u32;
767        cc_data.header.ty = loader_defs::linux::SETUP_CC_BLOB;
768        cc_data.cc_blob_address = core::ptr::from_ref(&*cc_blob) as u32;
769
770        // Chain in the setup data.
771        setup_data_tail.next = core::ptr::from_ref(&*cc_data) as u64;
772        setup_data_tail = &mut cc_data.header;
773    }
774
775    let initrd = p.initrd_base..p.initrd_base + p.initrd_size;
776
777    // Validate the initrd crc matches what was put at file generation time.
778    let computed_crc = crc32fast::hash(p.initrd());
779    if computed_crc != p.initrd_crc && is_confidential_debug {
780        let diag = build_initrd_crc_diagnostic(&p, computed_crc);
781        log::error!("{}", diag.as_str());
782        panic!("{}", diag.as_str());
783    }
784    assert_eq!(
785        computed_crc, p.initrd_crc,
786        "computed initrd crc does not match build time calculated crc"
787    );
788
789    #[cfg(target_arch = "x86_64")]
790    let boot_params = x86_boot::build_boot_params(
791        address_space,
792        initrd.clone(),
793        &cmdline,
794        setup_data_head,
795        &mut setup_data_tail,
796    );
797
798    // Compute the ending boot time. This has to be before writing to device
799    // tree, so this is as late as we can do it.
800
801    let boot_times = boot_reftime.map(|start| BootTimes {
802        start,
803        end: get_ref_time(p.isolation_type).unwrap_or(0),
804    });
805
806    // Validate that no imported regions that are pending are not part of vtl2
807    // ram.
808    for (range, result) in walk_ranges(
809        partition_info.vtl2_ram.iter().map(|r| (r.range, ())),
810        p.imported_regions(),
811    ) {
812        match result {
813            RangeWalkResult::Neither | RangeWalkResult::Left(_) | RangeWalkResult::Both(_, _) => {}
814            RangeWalkResult::Right(accepted) => {
815                // Ranges that are not a part of VTL2 ram must have been
816                // preaccepted, as usermode expect that to be the case.
817                assert!(
818                    accepted,
819                    "range {:#x?} not in vtl2 ram was not preaccepted at launch",
820                    range
821                );
822            }
823        }
824    }
825
826    write_dt(
827        &mut fdt.data,
828        partition_info,
829        address_space,
830        p.imported_regions().map(|r| {
831            // Discard if the range was previously pending - the bootloader has
832            // accepted all pending ranges.
833            //
834            // NOTE: No VTL0 memory today is marked as pending. The check above
835            // validates that, and this code may need to change if this becomes
836            // no longer true.
837            r.0
838        }),
839        initrd,
840        &cmdline,
841        sidecar.as_ref(),
842        boot_times,
843        p.isolation_type,
844    )
845    .unwrap();
846
847    rt::verify_stack_cookie();
848
849    log::info!("uninitializing hypercalls");
850    #[cfg(not(feature = "cvm_boot_log"))]
851    log::info!("about to jump to kernel");
852
853    hvcall().uninitialize();
854
855    #[cfg(feature = "cvm_boot_log")]
856    {
857        log::info!("uninitializing serial io");
858        log::info!("about to jump to kernel");
859        arch::uninitialize_serial_io(&p);
860    }
861
862    cfg_select! {
863        target_arch = "x86_64" => {
864            // SAFETY: the parameter blob is trusted.
865            let kernel_entry: extern "C" fn(u64, &loader_defs::linux::boot_params) -> ! =
866                unsafe { core::mem::transmute(p.kernel_entry_address) };
867            kernel_entry(0, &boot_params.0)
868        }
869        target_arch = "aarch64" => {
870            // SAFETY: the parameter blob is trusted.
871            let kernel_entry: extern "C" fn(
872                fdt_data: *const u8,
873                mbz0: u64,
874                mbz1: u64,
875                mbz2: u64,
876            ) -> ! = unsafe { core::mem::transmute(p.kernel_entry_address) };
877            // Disable MMU for kernel boot without EFI, as required by the boot protocol.
878            // Flush (and invalidate) the caches, as that is required for disabling MMU.
879            // SAFETY: Just changing a bit in the register and then jumping to the kernel.
880            unsafe {
881                core::arch::asm!(
882                    "
883                    mrs     {0}, sctlr_el1
884                    bic     {0}, {0}, #0x1
885                    msr     sctlr_el1, {0}
886                    tlbi    vmalle1
887                    dsb     sy
888                    isb     sy",
889                    lateout(reg) _,
890                );
891            }
892            kernel_entry(fdt.data.as_ptr(), 0, 0, 0)
893        }
894        _ => {
895            panic!("unsupported arch")
896        }
897    }
898}
899
900/// Ensure that mshv VP indexes for the CPUs listed in the partition info
901/// correspond to the N in the cpu@N devicetree node name. OpenVMM assumes that
902/// this will be the case.
903fn validate_vp_hw_ids(partition_info: &PartitionInfo) {
904    use host_params::MAX_CPU_COUNT;
905    use hypercall::HwId;
906
907    if partition_info.isolation.is_hardware_isolated() {
908        // TODO TDX SNP: we don't have a GHCB/GHCI page set up to communicate
909        // with the hypervisor here, so we can't easily perform the check. Since
910        // there is no security impact to this check, we can skip it for now; if
911        // the VM fails to boot, then this is due to a host contract violation.
912        //
913        // For TDX, we could use ENUM TOPOLOGY to validate that the TD VCPU
914        // indexes correspond to the APIC IDs in the right order. I am not
915        // certain if there are places where we depend on this mapping today.
916        return;
917    }
918
919    if hvcall().vtl() != Vtl::Vtl2 {
920        // If we're not using guest VSM, then the guest won't communicate
921        // directly with the hypervisor, so we can choose the VP indexes
922        // ourselves.
923        return;
924    }
925
926    // Ensure the host and hypervisor agree on VP index ordering.
927
928    let mut hw_ids = off_stack!(ArrayVec<HwId, MAX_CPU_COUNT>, ArrayVec::new_const());
929    hw_ids.clear();
930    hw_ids.extend(partition_info.cpus.iter().map(|c| c.reg as _));
931    let mut vp_indexes = off_stack!(ArrayVec<u32, MAX_CPU_COUNT>, ArrayVec::new_const());
932    vp_indexes.clear();
933    if let Err(err) = hvcall().get_vp_index_from_hw_id(&hw_ids, &mut vp_indexes) {
934        panic!(
935            "failed to get VP index for hardware ID {:#x}: {}",
936            hw_ids[vp_indexes.len().min(hw_ids.len() - 1)],
937            err
938        );
939    }
940    if let Some((i, &vp_index)) = vp_indexes
941        .iter()
942        .enumerate()
943        .find(|&(i, vp_index)| i as u32 != *vp_index)
944    {
945        panic!(
946            "CPU hardware ID {:#x} does not correspond to VP index {}",
947            hw_ids[i], vp_index
948        );
949    }
950}
951
952// See build.rs. See `mod rt` for the actual bootstrap code required to invoke
953// shim_main.
954#[cfg(not(minimal_rt))]
955fn main() {
956    unimplemented!("build with MINIMAL_RT_BUILD to produce a working boot loader");
957}
958
959#[cfg(test)]
960mod test {
961    use super::x86_boot::E820Ext;
962    use super::x86_boot::build_e820_map;
963    use crate::cmdline::BootCommandLineOptions;
964    use crate::dt::write_dt;
965    use crate::host_params::MAX_CPU_COUNT;
966    use crate::host_params::PartitionInfo;
967    use crate::host_params::shim_params::IsolationType;
968    use crate::memory::AddressSpaceManager;
969    use crate::memory::AddressSpaceManagerBuilder;
970    use arrayvec::ArrayString;
971    use arrayvec::ArrayVec;
972    use core::ops::Range;
973    use host_fdt_parser::ComInfo;
974    use host_fdt_parser::CpuEntry;
975    use host_fdt_parser::MemoryEntry;
976    use host_fdt_parser::VmbusInfo;
977    use igvm_defs::MemoryMapEntryType;
978    use loader_defs::linux::E820_RAM;
979    use loader_defs::linux::E820_RESERVED;
980    use loader_defs::linux::boot_params;
981    use loader_defs::linux::e820entry;
982    use memory_range::MemoryRange;
983    use memory_range::subtract_ranges;
984    use sidecar_defs::PerCpuState;
985    use zerocopy::FromZeros;
986
987    const HIGH_MMIO_GAP_END: u64 = 0x1000000000; //  64 GiB
988    const VMBUS_MMIO_GAP_SIZE: u64 = 0x10000000; // 256 MiB
989    const HIGH_MMIO_GAP_START: u64 = HIGH_MMIO_GAP_END - VMBUS_MMIO_GAP_SIZE;
990
991    /// Create partition info with given cpu count enabled and sequential
992    /// apic_ids.
993    fn new_partition_info(cpu_count: usize) -> PartitionInfo {
994        let mut cpus: ArrayVec<CpuEntry, MAX_CPU_COUNT> = ArrayVec::new();
995
996        for id in 0..(cpu_count as u64) {
997            cpus.push(CpuEntry { reg: id, vnode: 0 });
998        }
999
1000        let mut mmio = ArrayVec::new();
1001        mmio.push(
1002            MemoryRange::try_new(HIGH_MMIO_GAP_START..HIGH_MMIO_GAP_END).expect("valid range"),
1003        );
1004
1005        PartitionInfo {
1006            vtl2_ram: ArrayVec::new(),
1007            partition_ram: ArrayVec::new(),
1008            isolation: IsolationType::None,
1009            bsp_reg: cpus[0].reg as u32,
1010            cpus,
1011            sidecar_cpu_overrides: PerCpuState {
1012                per_cpu_state_specified: false,
1013                sidecar_starts_cpu: [true; sidecar_defs::NUM_CPUS_SUPPORTED_FOR_PER_CPU_STATE],
1014            },
1015            cmdline: ArrayString::new(),
1016            vmbus_vtl2: VmbusInfo { mmio },
1017            vmbus_vtl0: VmbusInfo {
1018                mmio: ArrayVec::new(),
1019            },
1020            com3_serial: ComInfo::None,
1021            gic: None,
1022            pmu_gsiv: None,
1023            memory_allocation_mode: host_fdt_parser::MemoryAllocationMode::Host,
1024            entropy: None,
1025            vtl0_alias_map: None,
1026            nvme_keepalive: false,
1027            boot_options: BootCommandLineOptions::new(),
1028        }
1029    }
1030
1031    // ensure we can boot with a _lot_ of vcpus
1032    #[test]
1033    #[cfg_attr(
1034        target_arch = "aarch64",
1035        ignore = "TODO: investigate why this doesn't always work on ARM"
1036    )]
1037    fn fdt_cpu_scaling() {
1038        const MAX_CPUS: usize = 2048;
1039
1040        let mut buf = [0; 0x40000];
1041        write_dt(
1042            &mut buf,
1043            &new_partition_info(MAX_CPUS),
1044            &AddressSpaceManager::new_const(),
1045            [],
1046            0..0,
1047            &ArrayString::from("test").unwrap_or_default(),
1048            None,
1049            None,
1050            IsolationType::None,
1051        )
1052        .unwrap();
1053    }
1054
1055    // Must match the DeviceTree blob generated with the standard tooling
1056    // to ensure being compliant to the standards (or, at least, compatibility
1057    // with a widely used implementation).
1058    // For details on regenerating the test content, see `fdt_dtc_decompile`
1059    // below.
1060    #[test]
1061    #[ignore = "TODO: temporarily broken"]
1062    fn fdt_dtc_check_content() {
1063        const MAX_CPUS: usize = 2;
1064        const BUF_SIZE: usize = 0x1000;
1065
1066        // Rust cannot infer the type.
1067        let dtb_data_spans: [(usize, &[u8]); 2] = [
1068            (
1069                /* Span starts at offset */ 0,
1070                b"\xd0\x0d\xfe\xed\x00\x00\x10\x00\x00\x00\x04\x38\x00\x00\x00\x38\
1071                \x00\x00\x00\x28\x00\x00\x00\x11\x00\x00\x00\x10\x00\x00\x00\x00\
1072                \x00\x00\x00\x4a\x00\x00\x01\x6c\x00\x00\x00\x00\x00\x00\x00\x00\
1073                \x00\x00\x00\x00\x00\x00\x00\x00\x23\x61\x64\x64\x72\x65\x73\x73\
1074                \x2d\x63\x65\x6c\x6c\x73\x00\x23\x73\x69\x7a\x65\x2d\x63\x65\x6c\
1075                \x6c\x73\x00\x6d\x6f\x64\x65\x6c\x00\x72\x65\x67\x00\x64\x65\x76\
1076                \x69\x63\x65\x5f\x74\x79\x70\x65\x00\x73\x74\x61\x74\x75\x73\x00\
1077                \x63\x6f\x6d\x70\x61\x74\x69\x62\x6c\x65\x00\x72\x61\x6e\x67\x65\
1078                \x73",
1079            ),
1080            (
1081                /* Span starts at offset */ 0x430,
1082                b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
1083                \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x02\
1084                \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x0f\x00\x00\x00\x00\
1085                \x00\x00\x00\x03\x00\x00\x00\x0f\x00\x00\x00\x1b\x6d\x73\x66\x74\
1086                \x2c\x75\x6e\x64\x65\x72\x68\x69\x6c\x6c\x00\x00\x00\x00\x00\x01\
1087                \x63\x70\x75\x73\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\
1088                \x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x04\
1089                \x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x01\x63\x70\x75\x40\
1090                \x30\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x25\
1091                \x63\x70\x75\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x21\
1092                \x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x05\x00\x00\x00\x31\
1093                \x6f\x6b\x61\x79\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\
1094                \x63\x70\x75\x40\x31\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\
1095                \x00\x00\x00\x25\x63\x70\x75\x00\x00\x00\x00\x03\x00\x00\x00\x04\
1096                \x00\x00\x00\x21\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x05\
1097                \x00\x00\x00\x31\x6f\x6b\x61\x79\x00\x00\x00\x00\x00\x00\x00\x02\
1098                \x00\x00\x00\x02\x00\x00\x00\x01\x76\x6d\x62\x75\x73\x00\x00\x00\
1099                \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x02\
1100                \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x0f\x00\x00\x00\x01\
1101                \x00\x00\x00\x03\x00\x00\x00\x0b\x00\x00\x00\x38\x6d\x73\x66\x74\
1102                \x2c\x76\x6d\x62\x75\x73\x00\x00\x00\x00\x00\x03\x00\x00\x00\x14\
1103                \x00\x00\x00\x43\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\
1104                \xf0\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\
1105                \x00\x00\x00\x09",
1106            ),
1107        ];
1108
1109        let mut sample_buf = [0u8; BUF_SIZE];
1110        for (span_start, bytes) in dtb_data_spans {
1111            sample_buf[span_start..span_start + bytes.len()].copy_from_slice(bytes);
1112        }
1113
1114        let mut buf = [0u8; BUF_SIZE];
1115        write_dt(
1116            &mut buf,
1117            &new_partition_info(MAX_CPUS),
1118            &AddressSpaceManager::new_const(),
1119            [],
1120            0..0,
1121            &ArrayString::from("test").unwrap_or_default(),
1122            None,
1123            None,
1124            IsolationType::None,
1125        )
1126        .unwrap();
1127
1128        assert!(sample_buf == buf);
1129    }
1130
1131    // This test should be manually enabled when need to regenerate
1132    // the sample content above and validate spec compliance with `dtc`.
1133    // Before running the test, please install the DeviceTree compiler:
1134    // ```shell
1135    // sudo apt-get update && sudo apt-get install device-tree-compiler
1136    // ```
1137    #[test]
1138    #[ignore = "enabling the test requires installing additional software, \
1139                and developers will experience a break."]
1140    fn fdt_dtc_decompile() {
1141        const MAX_CPUS: usize = 2048;
1142
1143        let mut buf = [0; 0x40000];
1144        write_dt(
1145            &mut buf,
1146            &new_partition_info(MAX_CPUS),
1147            &AddressSpaceManager::new_const(),
1148            [],
1149            0..0,
1150            &ArrayString::from("test").unwrap_or_default(),
1151            None,
1152            None,
1153            IsolationType::None,
1154        )
1155        .unwrap();
1156
1157        let input_dtb_file_name = "openhcl_boot.dtb";
1158        let output_dts_file_name = "openhcl_boot.dts";
1159        std::fs::write(input_dtb_file_name, buf).unwrap();
1160        let success = std::process::Command::new("dtc")
1161            .args([input_dtb_file_name, "-I", "dtb", "-o", output_dts_file_name])
1162            .status()
1163            .unwrap()
1164            .success();
1165        assert!(success);
1166    }
1167
1168    fn new_address_space_manager(
1169        ram: &[MemoryRange],
1170        bootshim_used: MemoryRange,
1171        persisted_range: MemoryRange,
1172        parameter_range: MemoryRange,
1173        reclaim: Option<MemoryRange>,
1174    ) -> AddressSpaceManager {
1175        let ram = ram
1176            .iter()
1177            .cloned()
1178            .map(|range| MemoryEntry {
1179                range,
1180                mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1181                vnode: 0,
1182            })
1183            .collect::<Vec<_>>();
1184        let mut address_space = AddressSpaceManager::new_const();
1185        AddressSpaceManagerBuilder::new(
1186            &mut address_space,
1187            &ram,
1188            bootshim_used,
1189            persisted_range,
1190            subtract_ranges([parameter_range], reclaim),
1191        )
1192        .init()
1193        .unwrap();
1194        address_space
1195    }
1196
1197    fn check_e820(boot_params: &boot_params, ext: &E820Ext, expected: &[(Range<u64>, u32)]) {
1198        let actual = boot_params.e820_map[..boot_params.e820_entries as usize]
1199            .iter()
1200            .chain(
1201                ext.entries
1202                    .iter()
1203                    .take((ext.header.len as usize) / size_of::<e820entry>()),
1204            );
1205
1206        assert_eq!(actual.clone().count(), expected.len());
1207
1208        for (actual, (expected_range, expected_type)) in actual.zip(expected.iter()) {
1209            let addr: u64 = actual.addr.into();
1210            let size: u64 = actual.size.into();
1211            let typ: u32 = actual.typ.into();
1212            assert_eq!(addr, expected_range.start);
1213            assert_eq!(size, expected_range.end - expected_range.start);
1214            assert_eq!(typ, *expected_type);
1215        }
1216    }
1217
1218    const PAGE_SIZE: u64 = 0x1000;
1219    const ONE_MB: u64 = 0x10_0000;
1220
1221    #[test]
1222    fn test_e820_basic() {
1223        // memmap with no param reclaim
1224        let mut boot_params: boot_params = FromZeros::new_zeroed();
1225        let mut ext = FromZeros::new_zeroed();
1226        let bootshim_used = MemoryRange::try_new(ONE_MB..3 * ONE_MB).unwrap();
1227        let persisted_header_end = ONE_MB + PAGE_SIZE;
1228        let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1229        let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1230        let parameter_range = MemoryRange::try_new(2 * ONE_MB..3 * ONE_MB).unwrap();
1231        let address_space = new_address_space_manager(
1232            &[MemoryRange::new(ONE_MB..4 * ONE_MB)],
1233            bootshim_used,
1234            persisted_state,
1235            parameter_range,
1236            None,
1237        );
1238
1239        assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1240
1241        check_e820(
1242            &boot_params,
1243            &ext,
1244            &[
1245                (ONE_MB..(persisted_header_end), E820_RESERVED),
1246                (persisted_header_end..persisted_end, E820_RESERVED),
1247                (persisted_end..2 * ONE_MB, E820_RAM),
1248                (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1249                (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1250            ],
1251        );
1252
1253        // memmap with reclaim
1254        let mut boot_params: boot_params = FromZeros::new_zeroed();
1255        let mut ext = FromZeros::new_zeroed();
1256        let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1257        let persisted_header_end = ONE_MB + PAGE_SIZE;
1258        let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1259        let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1260        let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1261        let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1262        let address_space = new_address_space_manager(
1263            &[MemoryRange::new(ONE_MB..6 * ONE_MB)],
1264            bootshim_used,
1265            persisted_state,
1266            parameter_range,
1267            Some(reclaim),
1268        );
1269
1270        assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1271
1272        check_e820(
1273            &boot_params,
1274            &ext,
1275            &[
1276                (ONE_MB..(persisted_header_end), E820_RESERVED),
1277                (persisted_header_end..persisted_end, E820_RESERVED),
1278                (persisted_end..2 * ONE_MB, E820_RAM),
1279                (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1280                (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1281                (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1282                (5 * ONE_MB..6 * ONE_MB, E820_RAM),
1283            ],
1284        );
1285
1286        // two mem ranges
1287        let mut boot_params: boot_params = FromZeros::new_zeroed();
1288        let mut ext = FromZeros::new_zeroed();
1289        let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1290        let persisted_header_end = ONE_MB + PAGE_SIZE;
1291        let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1292        let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1293        let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1294        let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1295        let address_space = new_address_space_manager(
1296            &[
1297                MemoryRange::new(ONE_MB..4 * ONE_MB),
1298                MemoryRange::new(4 * ONE_MB..10 * ONE_MB),
1299            ],
1300            bootshim_used,
1301            persisted_state,
1302            parameter_range,
1303            Some(reclaim),
1304        );
1305
1306        assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1307
1308        check_e820(
1309            &boot_params,
1310            &ext,
1311            &[
1312                (ONE_MB..(persisted_header_end), E820_RESERVED),
1313                (persisted_header_end..persisted_end, E820_RESERVED),
1314                (persisted_end..2 * ONE_MB, E820_RAM),
1315                (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1316                (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1317                (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1318                (5 * ONE_MB..10 * ONE_MB, E820_RAM),
1319            ],
1320        );
1321
1322        // memmap in 1 mb chunks
1323        let mut boot_params: boot_params = FromZeros::new_zeroed();
1324        let mut ext = FromZeros::new_zeroed();
1325        let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1326        let persisted_header_end = ONE_MB + PAGE_SIZE;
1327        let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1328        let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1329        let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1330        let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1331        let address_space = new_address_space_manager(
1332            &[
1333                MemoryRange::new(ONE_MB..2 * ONE_MB),
1334                MemoryRange::new(2 * ONE_MB..3 * ONE_MB),
1335                MemoryRange::new(3 * ONE_MB..4 * ONE_MB),
1336                MemoryRange::new(4 * ONE_MB..5 * ONE_MB),
1337                MemoryRange::new(5 * ONE_MB..6 * ONE_MB),
1338                MemoryRange::new(6 * ONE_MB..7 * ONE_MB),
1339                MemoryRange::new(7 * ONE_MB..8 * ONE_MB),
1340            ],
1341            bootshim_used,
1342            persisted_state,
1343            parameter_range,
1344            Some(reclaim),
1345        );
1346
1347        assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1348
1349        check_e820(
1350            &boot_params,
1351            &ext,
1352            &[
1353                (ONE_MB..(persisted_header_end), E820_RESERVED),
1354                (persisted_header_end..persisted_end, E820_RESERVED),
1355                (persisted_end..2 * ONE_MB, E820_RAM),
1356                (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1357                (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1358                (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1359                (5 * ONE_MB..8 * ONE_MB, E820_RAM),
1360            ],
1361        );
1362    }
1363
1364    // test e820 with spillover into ext
1365    #[test]
1366    fn test_e820_huge() {
1367        use crate::memory::AllocationPolicy;
1368        use crate::memory::AllocationType;
1369
1370        // Create 64 RAM ranges, then allocate 256 ranges to test spillover
1371        // boot_params.e820_map has E820_MAX_ENTRIES_ZEROPAGE (128) entries
1372        const E820_MAX_ENTRIES_ZEROPAGE: usize = 128;
1373        const RAM_RANGES: usize = 64;
1374        const TOTAL_ALLOCATIONS: usize = 256;
1375
1376        // Create 64 large RAM ranges (64MB each = 64 * 1MB pages per range)
1377        let mut ranges = Vec::new();
1378        for i in 0..RAM_RANGES {
1379            let start = (i as u64) * 64 * ONE_MB;
1380            let end = start + 64 * ONE_MB;
1381            ranges.push(MemoryRange::new(start..end));
1382        }
1383
1384        let bootshim_used = MemoryRange::try_new(0..ONE_MB * 2).unwrap();
1385        let persisted_range = MemoryRange::try_new(0..ONE_MB).unwrap();
1386        let parameter_range = MemoryRange::try_new(ONE_MB..2 * ONE_MB).unwrap();
1387
1388        let mut address_space = {
1389            let ram = ranges
1390                .iter()
1391                .cloned()
1392                .map(|range| MemoryEntry {
1393                    range,
1394                    mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1395                    vnode: 0,
1396                })
1397                .collect::<Vec<_>>();
1398            let mut address_space = AddressSpaceManager::new_const();
1399            AddressSpaceManagerBuilder::new(
1400                &mut address_space,
1401                &ram,
1402                bootshim_used,
1403                persisted_range,
1404                core::iter::once(parameter_range),
1405            )
1406            .init()
1407            .unwrap();
1408            address_space
1409        };
1410
1411        for i in 0..TOTAL_ALLOCATIONS {
1412            // Intersperse sidecar node allocations with gpa pool allocations,
1413            // as otherwise the address space manager will collapse adjacent
1414            // ranges of the same type.
1415            let _allocated = address_space
1416                .allocate(
1417                    None,
1418                    ONE_MB,
1419                    if i % 2 == 0 {
1420                        AllocationType::GpaPool
1421                    } else {
1422                        AllocationType::SidecarNode
1423                    },
1424                    AllocationPolicy::LowMemory,
1425                )
1426                .expect("should be able to allocate sidecar node");
1427        }
1428
1429        let mut boot_params: boot_params = FromZeros::new_zeroed();
1430        let mut ext = FromZeros::new_zeroed();
1431        let total_ranges = address_space.vtl2_ranges().count();
1432
1433        let used_ext = build_e820_map(&mut boot_params, &mut ext, &address_space).unwrap();
1434
1435        // Verify that we used the extension
1436        assert!(used_ext, "should use extension when there are many ranges");
1437
1438        // Verify the standard e820_map is full
1439        assert_eq!(boot_params.e820_entries, E820_MAX_ENTRIES_ZEROPAGE as u8);
1440
1441        // Verify the extension has the overflow entries
1442        let ext_entries = (ext.header.len as usize) / size_of::<e820entry>();
1443        assert_eq!(ext_entries, total_ranges - E820_MAX_ENTRIES_ZEROPAGE);
1444
1445        // Verify we have the expected number of total ranges
1446        let total_e820_entries = boot_params.e820_entries as usize + ext_entries;
1447        assert_eq!(total_e820_entries, total_ranges);
1448    }
1449}