1#![cfg_attr(minimal_rt, no_std, no_main)]
9#![expect(unsafe_code)]
11#![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
30extern crate alloc;
31
32use crate::arch::setup_vtl2_memory;
33use crate::arch::setup_vtl2_vp;
34#[cfg(target_arch = "x86_64")]
35use crate::arch::tdx::get_tdx_tsc_reftime;
36use crate::arch::verify_imported_regions_hash;
37use crate::boot_logger::boot_logger_memory_init;
38use crate::boot_logger::boot_logger_runtime_init;
39use crate::boot_logger::log;
40use crate::hypercall::hvcall;
41use crate::memory::AddressSpaceManager;
42use crate::single_threaded::OffStackRef;
43use crate::single_threaded::off_stack;
44use arrayvec::ArrayString;
45use arrayvec::ArrayVec;
46use cmdline::BootCommandLineOptions;
47use core::fmt::Write;
48use dt::BootTimes;
49use dt::write_dt;
50use host_params::COMMAND_LINE_SIZE;
51use host_params::PartitionInfo;
52use host_params::shim_params::IsolationType;
53use host_params::shim_params::ShimParams;
54use hvdef::Vtl;
55use loader_defs::linux::SETUP_DTB;
56use loader_defs::linux::setup_data;
57use loader_defs::shim::ShimParamsRaw;
58use memory_range::RangeWalkResult;
59use memory_range::walk_ranges;
60use minimal_rt::enlightened_panic::enable_enlightened_panic;
61use sidecar::SidecarConfig;
62use sidecar_defs::SidecarOutput;
63use sidecar_defs::SidecarParams;
64use zerocopy::FromBytes;
65use zerocopy::FromZeros;
66use zerocopy::Immutable;
67use zerocopy::IntoBytes;
68use zerocopy::KnownLayout;
69
70#[derive(Debug)]
71struct CommandLineTooLong;
72
73impl From<core::fmt::Error> for CommandLineTooLong {
74 fn from(_: core::fmt::Error) -> Self {
75 Self
76 }
77}
78
79struct BuildKernelCommandLineParams<'a> {
80 params: &'a ShimParams,
81 cmdline: &'a mut ArrayString<COMMAND_LINE_SIZE>,
82 partition_info: &'a PartitionInfo,
83 can_trust_host: bool,
84 is_confidential_debug: bool,
85 sidecar: Option<&'a SidecarConfig<'a>>,
86 vtl2_pool_supported: bool,
87 disable_keep_alive: bool,
88}
89
90fn build_kernel_command_line(
92 fn_params: BuildKernelCommandLineParams<'_>,
93) -> Result<(), CommandLineTooLong> {
94 let BuildKernelCommandLineParams {
95 params,
96 cmdline,
97 partition_info,
98 can_trust_host,
99 is_confidential_debug,
100 sidecar,
101 vtl2_pool_supported,
102 disable_keep_alive,
103 } = fn_params;
104
105 const KERNEL_PARAMETERS: &[&str] = &[
108 "loglevel=8",
110 "log_buf_len=128K",
112 "printk.time=1",
114 "console_msg_format=syslog",
116 "uio_hv_generic.no_mask=1",
118 "coredump_filter=0x33",
121 "cpufreq.off=1",
123 "cpuidle.off=1",
127 "cryptomgr.notests",
131 "idle=halt",
135 "initcall_blacklist=init_real_mode,sbf_init",
138 "lpj=3000000",
140 "no_timer_check",
142 "noxsave",
148 "oops=panic",
150 "panic_on_warn=0",
152 "panic_print=0",
155 "panic=-1",
157 "printk.devkmsg=on",
165 "reboot=t",
169 "rootfstype=tmpfs",
171 "sysctl.vm.compaction_proactiveness=0",
174 "tsc=reliable",
177 "unknown_nmi_panic=1",
179 "vfio_pci.ids=1414:00ba",
181 "vfio.enable_unsafe_noiommu_mode=1",
184 "rdinit=/underhill-init",
186 "OPENHCL_NVME_VFIO=1",
188 "hv_storvsc.storvsc_vcpus_per_sub_channel=2048",
191 "hv_storvsc.storvsc_max_hw_queues=2",
193 "hv_storvsc.storvsc_ringbuffer_size=0x8000",
195 "MIMALLOC_ARENA_EAGER_COMMIT=0",
197 ];
198
199 const X86_KERNEL_PARAMETERS: &[&str] = &[
200 "clearcpuid=pcid",
209 "iommu=off",
211 "pci=off",
214 ];
215
216 const AARCH64_KERNEL_PARAMETERS: &[&str] = &[];
217
218 for p in KERNEL_PARAMETERS {
219 write!(cmdline, "{p} ")?;
220 }
221
222 let arch_parameters = if cfg!(target_arch = "x86_64") {
223 X86_KERNEL_PARAMETERS
224 } else {
225 AARCH64_KERNEL_PARAMETERS
226 };
227 for p in arch_parameters {
228 write!(cmdline, "{p} ")?;
229 }
230
231 const HARDWARE_ISOLATED_KERNEL_PARAMETERS: &[&str] = &[
232 "swiotlb=4096,1",
242 ];
243
244 const NON_HARDWARE_ISOLATED_KERNEL_PARAMETERS: &[&str] = &[
245 "swiotlb=1,1",
250 ];
251
252 if params.isolation_type.is_hardware_isolated() {
253 for p in HARDWARE_ISOLATED_KERNEL_PARAMETERS {
254 write!(cmdline, "{p} ")?;
255 }
256 } else {
257 for p in NON_HARDWARE_ISOLATED_KERNEL_PARAMETERS {
258 write!(cmdline, "{p} ")?;
259 }
260 }
261
262 let console = if partition_info.com3_serial_available && can_trust_host {
270 "ttyS2,115200"
271 } else {
272 "ttynull"
273 };
274 write!(cmdline, "console={console} ")?;
275
276 if params.isolation_type != IsolationType::None {
277 write!(
278 cmdline,
279 "{}=1 ",
280 underhill_confidentiality::OPENHCL_CONFIDENTIAL_ENV_VAR_NAME
281 )?;
282 }
283
284 if is_confidential_debug {
285 write!(
286 cmdline,
287 "{}=1 ",
288 underhill_confidentiality::OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME
289 )?;
290 }
291
292 if partition_info.nvme_keepalive && vtl2_pool_supported && !disable_keep_alive {
295 write!(cmdline, "OPENHCL_NVME_KEEP_ALIVE=1 ")?;
296 }
297
298 if let Some(sidecar) = sidecar {
299 write!(cmdline, "{} ", sidecar.kernel_command_line())?;
300 }
301
302 write!(
307 cmdline,
308 "hv_vmbus.message_connection_id=0x{:x} ",
309 partition_info.vmbus_vtl2.connection_id
310 )?;
311
312 if can_trust_host {
314 cmdline.write_str(&partition_info.cmdline)?;
316 }
317
318 Ok(())
319}
320
321const FDT_SIZE: usize = 256 * 1024;
328
329#[repr(C, align(4096))]
330#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
331struct Fdt {
332 header: setup_data,
333 data: [u8; FDT_SIZE - size_of::<setup_data>()],
334}
335
336fn shim_parameters(shim_params_raw_offset: isize) -> ShimParams {
340 unsafe extern "C" {
341 static __ehdr_start: u8;
342 }
343
344 let shim_base = core::ptr::addr_of!(__ehdr_start) as usize;
345
346 let raw_shim_params = unsafe {
350 &*(shim_base.wrapping_add_signed(shim_params_raw_offset) as *const ShimParamsRaw)
351 };
352
353 ShimParams::new(shim_base as u64, raw_shim_params)
354}
355
356#[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))]
357mod x86_boot {
358 use crate::PageAlign;
359 use crate::memory::AddressSpaceManager;
360 use crate::single_threaded::OffStackRef;
361 use crate::single_threaded::off_stack;
362 use crate::zeroed;
363 use core::mem::size_of;
364 use core::ops::Range;
365 use core::ptr;
366 use loader_defs::linux::E820_RAM;
367 use loader_defs::linux::E820_RESERVED;
368 use loader_defs::linux::SETUP_E820_EXT;
369 use loader_defs::linux::boot_params;
370 use loader_defs::linux::e820entry;
371 use loader_defs::linux::setup_data;
372 use loader_defs::shim::MemoryVtlType;
373 use memory_range::MemoryRange;
374 use zerocopy::FromZeros;
375 use zerocopy::Immutable;
376 use zerocopy::KnownLayout;
377
378 #[repr(C)]
379 #[derive(FromZeros, Immutable, KnownLayout)]
380 pub struct E820Ext {
381 pub header: setup_data,
382 pub entries: [e820entry; 512],
383 }
384
385 fn add_e820_entry(
386 entry: Option<&mut e820entry>,
387 range: MemoryRange,
388 typ: u32,
389 ) -> Result<(), BuildE820MapError> {
390 *entry.ok_or(BuildE820MapError::OutOfE820Entries)? = e820entry {
391 addr: range.start().into(),
392 size: range.len().into(),
393 typ: typ.into(),
394 };
395 Ok(())
396 }
397
398 #[derive(Debug)]
399 pub enum BuildE820MapError {
400 OutOfE820Entries,
402 }
403
404 pub fn build_e820_map(
406 boot_params: &mut boot_params,
407 ext: &mut E820Ext,
408 address_space: &AddressSpaceManager,
409 ) -> Result<bool, BuildE820MapError> {
410 boot_params.e820_entries = 0;
411 let mut entries = boot_params
412 .e820_map
413 .iter_mut()
414 .chain(ext.entries.iter_mut());
415
416 let mut n = 0;
417 for (range, typ) in address_space.vtl2_ranges() {
418 match typ {
419 MemoryVtlType::VTL2_RAM => {
420 add_e820_entry(entries.next(), range, E820_RAM)?;
421 n += 1;
422 }
423 MemoryVtlType::VTL2_CONFIG
424 | MemoryVtlType::VTL2_SIDECAR_IMAGE
425 | MemoryVtlType::VTL2_SIDECAR_NODE
426 | MemoryVtlType::VTL2_RESERVED
427 | MemoryVtlType::VTL2_GPA_POOL
428 | MemoryVtlType::VTL2_TDX_PAGE_TABLES
429 | MemoryVtlType::VTL2_BOOTSHIM_LOG_BUFFER => {
430 add_e820_entry(entries.next(), range, E820_RESERVED)?;
431 n += 1;
432 }
433
434 _ => {
435 panic!("unexpected vtl2 ram type {typ:?} for range {range:#?}");
436 }
437 }
438 }
439
440 let base = n.min(boot_params.e820_map.len());
441 boot_params.e820_entries = base as u8;
442
443 if base < n {
444 ext.header.len = ((n - base) * size_of::<e820entry>()) as u32;
445 Ok(true)
446 } else {
447 Ok(false)
448 }
449 }
450
451 pub fn build_boot_params(
452 address_space: &AddressSpaceManager,
453 initrd: Range<u64>,
454 cmdline: &str,
455 setup_data_head: *const setup_data,
456 setup_data_tail: &mut &mut setup_data,
457 ) -> OffStackRef<'static, PageAlign<boot_params>> {
458 let mut boot_params_storage = off_stack!(PageAlign<boot_params>, zeroed());
459 let boot_params = &mut boot_params_storage.0;
460 boot_params.hdr.type_of_loader = 0xff; boot_params.hdr.hardware_subarch = 1.into();
472
473 boot_params.hdr.ramdisk_image = (initrd.start as u32).into();
474 boot_params.ext_ramdisk_image = (initrd.start >> 32) as u32;
475 let initrd_len = initrd.end - initrd.start;
476 boot_params.hdr.ramdisk_size = (initrd_len as u32).into();
477 boot_params.ext_ramdisk_size = (initrd_len >> 32) as u32;
478
479 let e820_ext = OffStackRef::leak(off_stack!(E820Ext, zeroed()));
480
481 let used_ext = build_e820_map(boot_params, e820_ext, address_space)
482 .expect("building e820 map must succeed");
483
484 if used_ext {
485 e820_ext.header.ty = SETUP_E820_EXT;
486 setup_data_tail.next = ptr::from_ref(&e820_ext.header) as u64;
487 *setup_data_tail = &mut e820_ext.header;
488 }
489
490 let cmd_line_addr = cmdline.as_ptr() as u64;
491 boot_params.hdr.cmd_line_ptr = (cmd_line_addr as u32).into();
492 boot_params.ext_cmd_line_ptr = (cmd_line_addr >> 32) as u32;
493
494 boot_params.hdr.setup_data = (setup_data_head as u64).into();
495
496 boot_params_storage
497 }
498}
499
500#[cfg(target_arch = "x86_64")]
502fn build_cc_blob_sev_info(
503 cc_blob: &mut loader_defs::linux::cc_blob_sev_info,
504 shim_params: &ShimParams,
505) {
506 cc_blob.magic = loader_defs::linux::CC_BLOB_SEV_INFO_MAGIC;
509 cc_blob.version = 0;
510 cc_blob._reserved = 0;
511 cc_blob.secrets_phys = shim_params.secrets_start();
512 cc_blob.secrets_len = hvdef::HV_PAGE_SIZE as u32;
513 cc_blob._rsvd1 = 0;
514 cc_blob.cpuid_phys = shim_params.cpuid_start();
515 cc_blob.cpuid_len = hvdef::HV_PAGE_SIZE as u32;
516 cc_blob._rsvd2 = 0;
517}
518
519#[repr(C, align(4096))]
520#[derive(FromZeros, Immutable, KnownLayout)]
521struct PageAlign<T>(T);
522
523const fn zeroed<T: FromZeros>() -> T {
524 unsafe { core::mem::MaybeUninit::<T>::zeroed().assume_init() }
526}
527
528fn get_ref_time(isolation: IsolationType) -> Option<u64> {
529 match isolation {
530 #[cfg(target_arch = "x86_64")]
531 IsolationType::Tdx => get_tdx_tsc_reftime(),
532 #[cfg(target_arch = "x86_64")]
533 IsolationType::Snp => None,
534 _ => Some(minimal_rt::reftime::reference_time()),
535 }
536}
537
538fn shim_main(shim_params_raw_offset: isize) -> ! {
539 let p = shim_parameters(shim_params_raw_offset);
540 if p.isolation_type == IsolationType::None {
541 enable_enlightened_panic();
542 }
543
544 boot_logger_memory_init(p.log_buffer);
546
547 let boot_reftime = get_ref_time(p.isolation_type);
548
549 if !p.isolation_type.is_hardware_isolated() {
556 hvcall().initialize();
557 }
558
559 let mut static_options = BootCommandLineOptions::new();
560 if let Some(cmdline) = p.command_line().command_line() {
561 static_options.parse(cmdline);
562 }
563
564 let static_confidential_debug = static_options.confidential_debug;
565 let can_trust_host = p.isolation_type == IsolationType::None || static_confidential_debug;
566
567 let mut dt_storage = off_stack!(PartitionInfo, PartitionInfo::new());
568 let address_space = OffStackRef::leak(off_stack!(
569 AddressSpaceManager,
570 AddressSpaceManager::new_const()
571 ));
572 let partition_info = match PartitionInfo::read_from_dt(
573 &p,
574 &mut dt_storage,
575 address_space,
576 static_options,
577 can_trust_host,
578 ) {
579 Ok(val) => val,
580 Err(e) => panic!("unable to read device tree params {}", e),
581 };
582
583 boot_logger_runtime_init(p.isolation_type, partition_info.com3_serial_available);
586 log!("openhcl_boot: logging enabled");
587
588 let is_confidential_debug =
592 static_confidential_debug || partition_info.boot_options.confidential_debug;
593
594 if !p.isolation_type.is_hardware_isolated()
596 && hvcall().vtl() == Vtl::Vtl2
597 && hvdef::HvRegisterVsmCapabilities::from(
598 hvcall()
599 .get_register(hvdef::HvAllArchRegisterName::VsmCapabilities.into())
600 .expect("failed to query vsm capabilities")
601 .as_u64(),
602 )
603 .vtl0_alias_map_available()
604 {
605 if partition_info.vtl0_alias_map.is_none() {
615 partition_info.vtl0_alias_map =
616 Some(1 << (arch::physical_address_bits(p.isolation_type) - 1));
617 }
618 } else {
619 partition_info.vtl0_alias_map = None;
622 }
623
624 let partition_info: &PartitionInfo = partition_info;
626
627 if partition_info.cpus.is_empty() {
628 panic!("no cpus");
629 }
630
631 validate_vp_hw_ids(partition_info);
632
633 setup_vtl2_memory(&p, partition_info);
634 setup_vtl2_vp(partition_info);
635
636 verify_imported_regions_hash(&p);
637
638 let mut sidecar_params = off_stack!(PageAlign<SidecarParams>, zeroed());
639 let mut sidecar_output = off_stack!(PageAlign<SidecarOutput>, zeroed());
640 let sidecar = sidecar::start_sidecar(
641 &p,
642 partition_info,
643 address_space,
644 &mut sidecar_params.0,
645 &mut sidecar_output.0,
646 );
647
648 let address_space: &AddressSpaceManager = address_space;
650
651 let mut cmdline = off_stack!(ArrayString<COMMAND_LINE_SIZE>, ArrayString::new_const());
652 build_kernel_command_line(BuildKernelCommandLineParams {
653 params: &p,
654 cmdline: &mut cmdline,
655 partition_info,
656 can_trust_host,
657 is_confidential_debug,
658 sidecar: sidecar.as_ref(),
659 vtl2_pool_supported: address_space.has_vtl2_pool(),
660 disable_keep_alive: partition_info.boot_options.disable_nvme_keep_alive,
661 })
662 .unwrap();
663
664 let mut fdt = off_stack!(Fdt, zeroed());
665 fdt.header.len = fdt.data.len() as u32;
666 fdt.header.ty = SETUP_DTB;
667
668 #[cfg(target_arch = "x86_64")]
669 let mut setup_data_tail = &mut fdt.header;
670 #[cfg(target_arch = "x86_64")]
671 let setup_data_head = core::ptr::from_ref(setup_data_tail);
672
673 #[cfg(target_arch = "x86_64")]
674 if p.isolation_type == IsolationType::Snp {
675 let cc_blob = OffStackRef::leak(off_stack!(loader_defs::linux::cc_blob_sev_info, zeroed()));
676 build_cc_blob_sev_info(cc_blob, &p);
677
678 let cc_data = OffStackRef::leak(off_stack!(loader_defs::linux::cc_setup_data, zeroed()));
679 cc_data.header.len = size_of::<loader_defs::linux::cc_setup_data>() as u32;
680 cc_data.header.ty = loader_defs::linux::SETUP_CC_BLOB;
681 cc_data.cc_blob_address = core::ptr::from_ref(&*cc_blob) as u32;
682
683 setup_data_tail.next = core::ptr::from_ref(&*cc_data) as u64;
685 setup_data_tail = &mut cc_data.header;
686 }
687
688 let initrd = p.initrd_base..p.initrd_base + p.initrd_size;
689
690 let computed_crc = crc32fast::hash(p.initrd());
692 assert_eq!(
693 computed_crc, p.initrd_crc,
694 "computed initrd crc does not match build time calculated crc"
695 );
696
697 #[cfg(target_arch = "x86_64")]
698 let boot_params = x86_boot::build_boot_params(
699 address_space,
700 initrd.clone(),
701 &cmdline,
702 setup_data_head,
703 &mut setup_data_tail,
704 );
705
706 let boot_times = boot_reftime.map(|start| BootTimes {
710 start,
711 end: get_ref_time(p.isolation_type).unwrap_or(0),
712 });
713
714 for (range, result) in walk_ranges(
717 partition_info.vtl2_ram.iter().map(|r| (r.range, ())),
718 p.imported_regions(),
719 ) {
720 match result {
721 RangeWalkResult::Neither | RangeWalkResult::Left(_) | RangeWalkResult::Both(_, _) => {}
722 RangeWalkResult::Right(accepted) => {
723 assert!(
726 accepted,
727 "range {:#x?} not in vtl2 ram was not preaccepted at launch",
728 range
729 );
730 }
731 }
732 }
733
734 write_dt(
735 &mut fdt.data,
736 partition_info,
737 address_space,
738 p.imported_regions().map(|r| {
739 r.0
746 }),
747 initrd,
748 &cmdline,
749 sidecar.as_ref(),
750 boot_times,
751 p.isolation_type,
752 )
753 .unwrap();
754
755 rt::verify_stack_cookie();
756
757 log!("uninitializing hypercalls, about to jump to kernel");
758 hvcall().uninitialize();
759
760 cfg_if::cfg_if! {
761 if #[cfg(target_arch = "x86_64")] {
762 let kernel_entry: extern "C" fn(u64, &loader_defs::linux::boot_params) -> ! =
764 unsafe { core::mem::transmute(p.kernel_entry_address) };
765 kernel_entry(0, &boot_params.0)
766 } else if #[cfg(target_arch = "aarch64")] {
767 let kernel_entry: extern "C" fn(fdt_data: *const u8, mbz0: u64, mbz1: u64, mbz2: u64) -> ! =
769 unsafe { core::mem::transmute(p.kernel_entry_address) };
770 unsafe {
774 core::arch::asm!(
775 "
776 mrs {0}, sctlr_el1
777 bic {0}, {0}, #0x1
778 msr sctlr_el1, {0}
779 tlbi vmalle1
780 dsb sy
781 isb sy",
782 lateout(reg) _,
783 );
784 }
785 kernel_entry(fdt.data.as_ptr(), 0, 0, 0)
786 } else {
787 panic!("unsupported arch")
788 }
789 }
790}
791
792fn validate_vp_hw_ids(partition_info: &PartitionInfo) {
796 use host_params::MAX_CPU_COUNT;
797 use hypercall::HwId;
798
799 if partition_info.isolation.is_hardware_isolated() {
800 return;
809 }
810
811 if hvcall().vtl() != Vtl::Vtl2 {
812 return;
816 }
817
818 let mut hw_ids = off_stack!(ArrayVec<HwId, MAX_CPU_COUNT>, ArrayVec::new_const());
821 hw_ids.clear();
822 hw_ids.extend(partition_info.cpus.iter().map(|c| c.reg as _));
823 let mut vp_indexes = off_stack!(ArrayVec<u32, MAX_CPU_COUNT>, ArrayVec::new_const());
824 vp_indexes.clear();
825 if let Err(err) = hvcall().get_vp_index_from_hw_id(&hw_ids, &mut vp_indexes) {
826 panic!(
827 "failed to get VP index for hardware ID {:#x}: {}",
828 hw_ids[vp_indexes.len().min(hw_ids.len() - 1)],
829 err
830 );
831 }
832 if let Some((i, &vp_index)) = vp_indexes
833 .iter()
834 .enumerate()
835 .find(|&(i, vp_index)| i as u32 != *vp_index)
836 {
837 panic!(
838 "CPU hardware ID {:#x} does not correspond to VP index {}",
839 hw_ids[i], vp_index
840 );
841 }
842}
843
844#[cfg(not(minimal_rt))]
847fn main() {
848 unimplemented!("build with MINIMAL_RT_BUILD to produce a working boot loader");
849}
850
851#[cfg(test)]
852mod test {
853 use super::x86_boot::E820Ext;
854 use super::x86_boot::build_e820_map;
855 use crate::cmdline::BootCommandLineOptions;
856 use crate::dt::write_dt;
857 use crate::host_params::MAX_CPU_COUNT;
858 use crate::host_params::PartitionInfo;
859 use crate::host_params::shim_params::IsolationType;
860 use crate::memory::AddressSpaceManager;
861 use crate::memory::AddressSpaceManagerBuilder;
862 use arrayvec::ArrayString;
863 use arrayvec::ArrayVec;
864 use core::ops::Range;
865 use host_fdt_parser::CpuEntry;
866 use host_fdt_parser::MemoryEntry;
867 use host_fdt_parser::VmbusInfo;
868 use igvm_defs::MemoryMapEntryType;
869 use loader_defs::linux::E820_RAM;
870 use loader_defs::linux::E820_RESERVED;
871 use loader_defs::linux::boot_params;
872 use loader_defs::linux::e820entry;
873 use memory_range::MemoryRange;
874 use memory_range::subtract_ranges;
875 use zerocopy::FromZeros;
876
877 const HIGH_MMIO_GAP_END: u64 = 0x1000000000; const VMBUS_MMIO_GAP_SIZE: u64 = 0x10000000; const HIGH_MMIO_GAP_START: u64 = HIGH_MMIO_GAP_END - VMBUS_MMIO_GAP_SIZE;
880
881 fn new_partition_info(cpu_count: usize) -> PartitionInfo {
884 let mut cpus: ArrayVec<CpuEntry, MAX_CPU_COUNT> = ArrayVec::new();
885
886 for id in 0..(cpu_count as u64) {
887 cpus.push(CpuEntry { reg: id, vnode: 0 });
888 }
889
890 let mut mmio = ArrayVec::new();
891 mmio.push(
892 MemoryRange::try_new(HIGH_MMIO_GAP_START..HIGH_MMIO_GAP_END).expect("valid range"),
893 );
894
895 PartitionInfo {
896 vtl2_ram: ArrayVec::new(),
897 partition_ram: ArrayVec::new(),
898 isolation: IsolationType::None,
899 bsp_reg: cpus[0].reg as u32,
900 cpus,
901 cmdline: ArrayString::new(),
902 vmbus_vtl2: VmbusInfo {
903 mmio,
904 connection_id: 0,
905 },
906 vmbus_vtl0: VmbusInfo {
907 mmio: ArrayVec::new(),
908 connection_id: 0,
909 },
910 com3_serial_available: false,
911 gic: None,
912 pmu_gsiv: None,
913 memory_allocation_mode: host_fdt_parser::MemoryAllocationMode::Host,
914 entropy: None,
915 vtl0_alias_map: None,
916 nvme_keepalive: false,
917 boot_options: BootCommandLineOptions::new(),
918 }
919 }
920
921 #[test]
923 #[cfg_attr(
924 target_arch = "aarch64",
925 ignore = "TODO: investigate why this doesn't always work on ARM"
926 )]
927 fn fdt_cpu_scaling() {
928 const MAX_CPUS: usize = 2048;
929
930 let mut buf = [0; 0x40000];
931 write_dt(
932 &mut buf,
933 &new_partition_info(MAX_CPUS),
934 &AddressSpaceManager::new_const(),
935 [],
936 0..0,
937 &ArrayString::from("test").unwrap_or_default(),
938 None,
939 None,
940 IsolationType::None,
941 )
942 .unwrap();
943 }
944
945 #[test]
951 #[ignore = "TODO: temporarily broken"]
952 fn fdt_dtc_check_content() {
953 const MAX_CPUS: usize = 2;
954 const BUF_SIZE: usize = 0x1000;
955
956 let dtb_data_spans: [(usize, &[u8]); 2] = [
958 (
959 0,
960 b"\xd0\x0d\xfe\xed\x00\x00\x10\x00\x00\x00\x04\x38\x00\x00\x00\x38\
961 \x00\x00\x00\x28\x00\x00\x00\x11\x00\x00\x00\x10\x00\x00\x00\x00\
962 \x00\x00\x00\x4a\x00\x00\x01\x6c\x00\x00\x00\x00\x00\x00\x00\x00\
963 \x00\x00\x00\x00\x00\x00\x00\x00\x23\x61\x64\x64\x72\x65\x73\x73\
964 \x2d\x63\x65\x6c\x6c\x73\x00\x23\x73\x69\x7a\x65\x2d\x63\x65\x6c\
965 \x6c\x73\x00\x6d\x6f\x64\x65\x6c\x00\x72\x65\x67\x00\x64\x65\x76\
966 \x69\x63\x65\x5f\x74\x79\x70\x65\x00\x73\x74\x61\x74\x75\x73\x00\
967 \x63\x6f\x6d\x70\x61\x74\x69\x62\x6c\x65\x00\x72\x61\x6e\x67\x65\
968 \x73",
969 ),
970 (
971 0x430,
972 b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
973 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x02\
974 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x0f\x00\x00\x00\x00\
975 \x00\x00\x00\x03\x00\x00\x00\x0f\x00\x00\x00\x1b\x6d\x73\x66\x74\
976 \x2c\x75\x6e\x64\x65\x72\x68\x69\x6c\x6c\x00\x00\x00\x00\x00\x01\
977 \x63\x70\x75\x73\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\
978 \x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x04\
979 \x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x01\x63\x70\x75\x40\
980 \x30\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x25\
981 \x63\x70\x75\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x21\
982 \x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x05\x00\x00\x00\x31\
983 \x6f\x6b\x61\x79\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\
984 \x63\x70\x75\x40\x31\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\
985 \x00\x00\x00\x25\x63\x70\x75\x00\x00\x00\x00\x03\x00\x00\x00\x04\
986 \x00\x00\x00\x21\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x05\
987 \x00\x00\x00\x31\x6f\x6b\x61\x79\x00\x00\x00\x00\x00\x00\x00\x02\
988 \x00\x00\x00\x02\x00\x00\x00\x01\x76\x6d\x62\x75\x73\x00\x00\x00\
989 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x02\
990 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x0f\x00\x00\x00\x01\
991 \x00\x00\x00\x03\x00\x00\x00\x0b\x00\x00\x00\x38\x6d\x73\x66\x74\
992 \x2c\x76\x6d\x62\x75\x73\x00\x00\x00\x00\x00\x03\x00\x00\x00\x14\
993 \x00\x00\x00\x43\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\
994 \xf0\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\
995 \x00\x00\x00\x09",
996 ),
997 ];
998
999 let mut sample_buf = [0u8; BUF_SIZE];
1000 for (span_start, bytes) in dtb_data_spans {
1001 sample_buf[span_start..span_start + bytes.len()].copy_from_slice(bytes);
1002 }
1003
1004 let mut buf = [0u8; BUF_SIZE];
1005 write_dt(
1006 &mut buf,
1007 &new_partition_info(MAX_CPUS),
1008 &AddressSpaceManager::new_const(),
1009 [],
1010 0..0,
1011 &ArrayString::from("test").unwrap_or_default(),
1012 None,
1013 None,
1014 IsolationType::None,
1015 )
1016 .unwrap();
1017
1018 assert!(sample_buf == buf);
1019 }
1020
1021 #[test]
1028 #[ignore = "enabling the test requires installing additional software, \
1029 and developers will experience a break."]
1030 fn fdt_dtc_decompile() {
1031 const MAX_CPUS: usize = 2048;
1032
1033 let mut buf = [0; 0x40000];
1034 write_dt(
1035 &mut buf,
1036 &new_partition_info(MAX_CPUS),
1037 &AddressSpaceManager::new_const(),
1038 [],
1039 0..0,
1040 &ArrayString::from("test").unwrap_or_default(),
1041 None,
1042 None,
1043 IsolationType::None,
1044 )
1045 .unwrap();
1046
1047 let input_dtb_file_name = "openhcl_boot.dtb";
1048 let output_dts_file_name = "openhcl_boot.dts";
1049 std::fs::write(input_dtb_file_name, buf).unwrap();
1050 let success = std::process::Command::new("dtc")
1051 .args([input_dtb_file_name, "-I", "dtb", "-o", output_dts_file_name])
1052 .status()
1053 .unwrap()
1054 .success();
1055 assert!(success);
1056 }
1057
1058 fn new_address_space_manager(
1059 ram: &[MemoryRange],
1060 bootshim_used: MemoryRange,
1061 parameter_range: MemoryRange,
1062 reclaim: Option<MemoryRange>,
1063 ) -> AddressSpaceManager {
1064 let ram = ram
1065 .iter()
1066 .cloned()
1067 .map(|range| MemoryEntry {
1068 range,
1069 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1070 vnode: 0,
1071 })
1072 .collect::<Vec<_>>();
1073 let mut address_space = AddressSpaceManager::new_const();
1074 AddressSpaceManagerBuilder::new(
1075 &mut address_space,
1076 &ram,
1077 bootshim_used,
1078 subtract_ranges([parameter_range], reclaim),
1079 )
1080 .init()
1081 .unwrap();
1082 address_space
1083 }
1084
1085 fn check_e820(boot_params: &boot_params, ext: &E820Ext, expected: &[(Range<u64>, u32)]) {
1086 let actual = boot_params.e820_map[..boot_params.e820_entries as usize]
1087 .iter()
1088 .chain(
1089 ext.entries
1090 .iter()
1091 .take((ext.header.len as usize) / size_of::<e820entry>()),
1092 );
1093
1094 assert_eq!(actual.clone().count(), expected.len());
1095
1096 for (actual, (expected_range, expected_type)) in actual.zip(expected.iter()) {
1097 let addr: u64 = actual.addr.into();
1098 let size: u64 = actual.size.into();
1099 let typ: u32 = actual.typ.into();
1100 assert_eq!(addr, expected_range.start);
1101 assert_eq!(size, expected_range.end - expected_range.start);
1102 assert_eq!(typ, *expected_type);
1103 }
1104 }
1105
1106 const ONE_MB: u64 = 0x10_0000;
1107
1108 #[test]
1109 fn test_e820_basic() {
1110 let mut boot_params: boot_params = FromZeros::new_zeroed();
1112 let mut ext = FromZeros::new_zeroed();
1113 let bootshim_used = MemoryRange::try_new(ONE_MB..3 * ONE_MB).unwrap();
1114 let parameter_range = MemoryRange::try_new(2 * ONE_MB..3 * ONE_MB).unwrap();
1115 let address_space = new_address_space_manager(
1116 &[MemoryRange::new(ONE_MB..4 * ONE_MB)],
1117 bootshim_used,
1118 parameter_range,
1119 None,
1120 );
1121
1122 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1123
1124 check_e820(
1125 &boot_params,
1126 &ext,
1127 &[
1128 (ONE_MB..2 * ONE_MB, E820_RAM),
1129 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1130 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1131 ],
1132 );
1133
1134 let mut boot_params: boot_params = FromZeros::new_zeroed();
1136 let mut ext = FromZeros::new_zeroed();
1137 let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1138 let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1139 let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1140 let address_space = new_address_space_manager(
1141 &[MemoryRange::new(ONE_MB..6 * ONE_MB)],
1142 bootshim_used,
1143 parameter_range,
1144 Some(reclaim),
1145 );
1146
1147 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1148
1149 check_e820(
1150 &boot_params,
1151 &ext,
1152 &[
1153 (ONE_MB..2 * ONE_MB, E820_RAM),
1154 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1155 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1156 (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1157 (5 * ONE_MB..6 * ONE_MB, E820_RAM),
1158 ],
1159 );
1160
1161 let mut boot_params: boot_params = FromZeros::new_zeroed();
1163 let mut ext = FromZeros::new_zeroed();
1164 let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1165 let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1166 let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1167 let address_space = new_address_space_manager(
1168 &[
1169 MemoryRange::new(ONE_MB..4 * ONE_MB),
1170 MemoryRange::new(4 * ONE_MB..10 * ONE_MB),
1171 ],
1172 bootshim_used,
1173 parameter_range,
1174 Some(reclaim),
1175 );
1176
1177 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1178
1179 check_e820(
1180 &boot_params,
1181 &ext,
1182 &[
1183 (ONE_MB..2 * ONE_MB, E820_RAM),
1184 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1185 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1186 (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1187 (5 * ONE_MB..10 * ONE_MB, E820_RAM),
1188 ],
1189 );
1190
1191 let mut boot_params: boot_params = FromZeros::new_zeroed();
1193 let mut ext = FromZeros::new_zeroed();
1194 let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1195 let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1196 let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1197 let address_space = new_address_space_manager(
1198 &[
1199 MemoryRange::new(ONE_MB..2 * ONE_MB),
1200 MemoryRange::new(2 * ONE_MB..3 * ONE_MB),
1201 MemoryRange::new(3 * ONE_MB..4 * ONE_MB),
1202 MemoryRange::new(4 * ONE_MB..5 * ONE_MB),
1203 MemoryRange::new(5 * ONE_MB..6 * ONE_MB),
1204 MemoryRange::new(6 * ONE_MB..7 * ONE_MB),
1205 MemoryRange::new(7 * ONE_MB..8 * ONE_MB),
1206 ],
1207 bootshim_used,
1208 parameter_range,
1209 Some(reclaim),
1210 );
1211
1212 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1213
1214 check_e820(
1215 &boot_params,
1216 &ext,
1217 &[
1218 (ONE_MB..2 * ONE_MB, E820_RAM),
1219 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1220 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1221 (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1222 (5 * ONE_MB..8 * ONE_MB, E820_RAM),
1223 ],
1224 );
1225 }
1226
1227 #[test]
1229 fn test_e820_huge() {
1230 use crate::memory::AllocationPolicy;
1231 use crate::memory::AllocationType;
1232
1233 const E820_MAX_ENTRIES_ZEROPAGE: usize = 128;
1236 const RAM_RANGES: usize = 64;
1237 const TOTAL_ALLOCATIONS: usize = 256;
1238
1239 let mut ranges = Vec::new();
1241 for i in 0..RAM_RANGES {
1242 let start = (i as u64) * 64 * ONE_MB;
1243 let end = start + 64 * ONE_MB;
1244 ranges.push(MemoryRange::new(start..end));
1245 }
1246
1247 let bootshim_used = MemoryRange::try_new(0..ONE_MB).unwrap();
1248 let parameter_range = MemoryRange::try_new(0..ONE_MB).unwrap();
1249
1250 let mut address_space = {
1251 let ram = ranges
1252 .iter()
1253 .cloned()
1254 .map(|range| MemoryEntry {
1255 range,
1256 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1257 vnode: 0,
1258 })
1259 .collect::<Vec<_>>();
1260 let mut address_space = AddressSpaceManager::new_const();
1261 AddressSpaceManagerBuilder::new(
1262 &mut address_space,
1263 &ram,
1264 bootshim_used,
1265 core::iter::once(parameter_range),
1266 )
1267 .init()
1268 .unwrap();
1269 address_space
1270 };
1271
1272 for i in 0..TOTAL_ALLOCATIONS {
1273 let _allocated = address_space
1277 .allocate(
1278 None,
1279 ONE_MB,
1280 if i % 2 == 0 {
1281 AllocationType::GpaPool
1282 } else {
1283 AllocationType::SidecarNode
1284 },
1285 AllocationPolicy::LowMemory,
1286 )
1287 .expect("should be able to allocate sidecar node");
1288 }
1289
1290 let mut boot_params: boot_params = FromZeros::new_zeroed();
1291 let mut ext = FromZeros::new_zeroed();
1292 let total_ranges = address_space.vtl2_ranges().count();
1293
1294 let used_ext = build_e820_map(&mut boot_params, &mut ext, &address_space).unwrap();
1295
1296 assert!(used_ext, "should use extension when there are many ranges");
1298
1299 assert_eq!(boot_params.e820_entries, E820_MAX_ENTRIES_ZEROPAGE as u8);
1301
1302 let ext_entries = (ext.header.len as usize) / size_of::<e820entry>();
1304 assert_eq!(ext_entries, total_ranges - E820_MAX_ENTRIES_ZEROPAGE);
1305
1306 let total_e820_entries = boot_params.e820_entries as usize + ext_entries;
1308 assert_eq!(total_e820_entries, total_ranges);
1309 }
1310}