Skip to main content

openhcl_boot/
main.rs

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