1#![cfg_attr(minimal_rt, no_std, no_main)]
13#![expect(unsafe_code)]
15
16mod arch;
17mod boot_logger;
18mod cmdline;
19mod dt;
20mod host_params;
21mod hypercall;
22mod memory;
23mod rt;
24mod sidecar;
25mod single_threaded;
26
27use crate::arch::setup_vtl2_memory;
28use crate::arch::setup_vtl2_vp;
29#[cfg(target_arch = "x86_64")]
30use crate::arch::tdx::get_tdx_tsc_reftime;
31use crate::arch::verify_imported_regions_hash;
32use crate::boot_logger::boot_logger_memory_init;
33use crate::boot_logger::boot_logger_runtime_init;
34use crate::hypercall::hvcall;
35use crate::memory::AddressSpaceManager;
36use crate::single_threaded::OffStackRef;
37use crate::single_threaded::off_stack;
38use arrayvec::ArrayString;
39use arrayvec::ArrayVec;
40use cmdline::BootCommandLineOptions;
41use core::fmt::Write;
42use dt::BootTimes;
43use dt::write_dt;
44use host_fdt_parser::ComInfo;
45use host_params::COMMAND_LINE_SIZE;
46use host_params::PartitionInfo;
47use host_params::shim_params::IsolationType;
48use host_params::shim_params::ShimParams;
49use hvdef::Vtl;
50use loader_defs::linux::SETUP_DTB;
51use loader_defs::linux::setup_data;
52use loader_defs::shim::ShimParamsRaw;
53use memory_range::RangeWalkResult;
54use memory_range::walk_ranges;
55use minimal_rt::enlightened_panic::enable_enlightened_panic;
56use sidecar::SidecarConfig;
57use sidecar_defs::SidecarOutput;
58use sidecar_defs::SidecarParams;
59use zerocopy::FromBytes;
60use zerocopy::FromZeros;
61use zerocopy::Immutable;
62use zerocopy::IntoBytes;
63use zerocopy::KnownLayout;
64
65#[derive(Debug)]
66struct CommandLineTooLong;
67
68impl From<core::fmt::Error> for CommandLineTooLong {
69 fn from(_: core::fmt::Error) -> Self {
70 Self
71 }
72}
73
74struct BuildKernelCommandLineParams<'a> {
75 params: &'a ShimParams,
76 cmdline: &'a mut ArrayString<COMMAND_LINE_SIZE>,
77 partition_info: &'a PartitionInfo,
78 can_trust_host: bool,
79 is_confidential_debug: bool,
80 sidecar: Option<&'a SidecarConfig<'a>>,
81 vtl2_pool_supported: bool,
82}
83
84fn build_kernel_command_line(
86 fn_params: BuildKernelCommandLineParams<'_>,
87) -> Result<(), CommandLineTooLong> {
88 let BuildKernelCommandLineParams {
89 params,
90 cmdline,
91 partition_info,
92 can_trust_host,
93 is_confidential_debug,
94 sidecar,
95 vtl2_pool_supported,
96 } = fn_params;
97
98 const KERNEL_PARAMETERS: &[&str] = &[
101 "loglevel=8",
103 "log_buf_len=128K",
105 "printk.time=1",
107 "console_msg_format=syslog",
109 "uio_hv_generic.no_mask=1",
111 "coredump_filter=0x33",
114 "cpufreq.off=1",
116 "cpuidle.off=1",
120 "cryptomgr.notests",
124 "idle=halt",
128 "initcall_blacklist=init_real_mode,sbf_init",
131 "lpj=3000000",
133 "no_timer_check",
135 "noxsave",
141 "oops=panic",
143 "panic_on_warn=0",
145 "panic_print=0",
148 "panic=-1",
150 "printk.devkmsg=on",
158 "reboot=t",
162 "rootfstype=tmpfs",
164 "sysctl.vm.compaction_proactiveness=0",
167 "tsc=reliable",
170 "unknown_nmi_panic=1",
172 "vfio_pci.ids=1414:00ba",
174 "vfio.enable_unsafe_noiommu_mode=1",
177 "rdinit=/underhill-init",
179 "OPENHCL_NVME_VFIO=1",
181 "hv_storvsc.storvsc_vcpus_per_sub_channel=2048",
184 "hv_storvsc.storvsc_max_hw_queues=2",
186 "hv_storvsc.storvsc_ringbuffer_size=0x8000",
188 "MIMALLOC_ARENA_EAGER_COMMIT=0",
190 "acpi=off",
193 ];
194
195 const X86_KERNEL_PARAMETERS: &[&str] = &[
196 "iommu=off",
198 "pci=off",
201 ];
202
203 const AARCH64_KERNEL_PARAMETERS: &[&str] = &[];
204
205 for p in KERNEL_PARAMETERS {
206 write!(cmdline, "{p} ")?;
207 }
208
209 let arch_parameters = if cfg!(target_arch = "x86_64") {
210 X86_KERNEL_PARAMETERS
211 } else {
212 AARCH64_KERNEL_PARAMETERS
213 };
214 for p in arch_parameters {
215 write!(cmdline, "{p} ")?;
216 }
217
218 const HARDWARE_ISOLATED_KERNEL_PARAMETERS: &[&str] = &[
219 "swiotlb=4096,1",
229 ];
230
231 const NON_HARDWARE_ISOLATED_KERNEL_PARAMETERS: &[&str] = &[
232 "swiotlb=1,1",
237 ];
238
239 if params.isolation_type.is_hardware_isolated() {
240 for p in HARDWARE_ISOLATED_KERNEL_PARAMETERS {
241 write!(cmdline, "{p} ")?;
242 }
243 } else {
244 for p in NON_HARDWARE_ISOLATED_KERNEL_PARAMETERS {
245 write!(cmdline, "{p} ")?;
246 }
247 }
248
249 write!(cmdline, "console=")?;
257 match (&partition_info.com3_serial, can_trust_host) {
258 (ComInfo::Ns16550 { current_speed, .. }, true) => {
259 write!(cmdline, "ttyS2,{current_speed} ")?
260 }
261 (ComInfo::Pl011 { current_speed, .. }, true) => {
262 write!(cmdline, "ttyAMA0,{current_speed} ")?
263 }
264 _ => write!(cmdline, "ttynull ")?,
265 }
266
267 if params.isolation_type != IsolationType::None {
268 write!(
269 cmdline,
270 "{}=1 ",
271 underhill_confidentiality::OPENHCL_CONFIDENTIAL_ENV_VAR_NAME
272 )?;
273 }
274
275 if is_confidential_debug {
276 write!(
277 cmdline,
278 "{}=1 ",
279 underhill_confidentiality::OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME
280 )?;
281 }
282
283 write!(cmdline, "OPENHCL_NVME_KEEP_ALIVE=")?;
287
288 if partition_info.boot_options.disable_nvme_keep_alive {
289 write!(cmdline, "disabled,")?;
290 }
291
292 if partition_info.nvme_keepalive {
293 write!(cmdline, "host,")?;
294 } else {
295 write!(cmdline, "nohost,")?;
296 }
297
298 if vtl2_pool_supported {
299 write!(cmdline, "privatepool ")?;
300 } else {
301 write!(cmdline, "noprivatepool ")?;
302 }
303
304 if let Some(sidecar) = sidecar {
305 write!(cmdline, "{} ", sidecar.kernel_command_line())?;
306 }
307
308 cmdline.write_str(&partition_info.cmdline)?;
310
311 Ok(())
312}
313
314const FDT_SIZE: usize = 256 * 1024;
321
322#[repr(C, align(4096))]
323#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
324struct Fdt {
325 header: setup_data,
326 data: [u8; FDT_SIZE - size_of::<setup_data>()],
327}
328
329fn shim_parameters(shim_params_raw_offset: isize) -> ShimParams {
333 unsafe extern "C" {
334 static __ehdr_start: u8;
335 }
336
337 let shim_base = core::ptr::addr_of!(__ehdr_start) as usize;
338
339 let raw_shim_params = unsafe {
343 &*(shim_base.wrapping_add_signed(shim_params_raw_offset) as *const ShimParamsRaw)
344 };
345
346 ShimParams::new(shim_base as u64, raw_shim_params)
347}
348
349#[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))]
350mod x86_boot {
351 use crate::PageAlign;
352 use crate::memory::AddressSpaceManager;
353 use crate::single_threaded::OffStackRef;
354 use crate::single_threaded::off_stack;
355 use crate::zeroed;
356 use core::mem::size_of;
357 use core::ops::Range;
358 use core::ptr;
359 use loader_defs::linux::E820_RAM;
360 use loader_defs::linux::E820_RESERVED;
361 use loader_defs::linux::SETUP_E820_EXT;
362 use loader_defs::linux::boot_params;
363 use loader_defs::linux::e820entry;
364 use loader_defs::linux::setup_data;
365 use loader_defs::shim::MemoryVtlType;
366 use memory_range::MemoryRange;
367 use zerocopy::FromZeros;
368 use zerocopy::Immutable;
369 use zerocopy::KnownLayout;
370
371 #[repr(C)]
372 #[derive(FromZeros, Immutable, KnownLayout)]
373 pub struct E820Ext {
374 pub header: setup_data,
375 pub entries: [e820entry; 512],
376 }
377
378 fn add_e820_entry(
379 entry: Option<&mut e820entry>,
380 range: MemoryRange,
381 typ: u32,
382 ) -> Result<(), BuildE820MapError> {
383 *entry.ok_or(BuildE820MapError::OutOfE820Entries)? = e820entry {
384 addr: range.start().into(),
385 size: range.len().into(),
386 typ: typ.into(),
387 };
388 Ok(())
389 }
390
391 #[derive(Debug)]
392 pub enum BuildE820MapError {
393 OutOfE820Entries,
395 }
396
397 pub fn build_e820_map(
399 boot_params: &mut boot_params,
400 ext: &mut E820Ext,
401 address_space: &AddressSpaceManager,
402 ) -> Result<bool, BuildE820MapError> {
403 boot_params.e820_entries = 0;
404 let mut entries = boot_params
405 .e820_map
406 .iter_mut()
407 .chain(ext.entries.iter_mut());
408
409 let mut n = 0;
410 for (range, typ) in address_space.vtl2_ranges() {
411 match typ {
412 MemoryVtlType::VTL2_RAM => {
413 add_e820_entry(entries.next(), range, E820_RAM)?;
414 n += 1;
415 }
416 MemoryVtlType::VTL2_CONFIG
417 | MemoryVtlType::VTL2_SIDECAR_IMAGE
418 | MemoryVtlType::VTL2_SIDECAR_NODE
419 | MemoryVtlType::VTL2_RESERVED
420 | MemoryVtlType::VTL2_GPA_POOL
421 | MemoryVtlType::VTL2_TDX_PAGE_TABLES
422 | MemoryVtlType::VTL2_BOOTSHIM_LOG_BUFFER
423 | MemoryVtlType::VTL2_PERSISTED_STATE_HEADER
424 | MemoryVtlType::VTL2_PERSISTED_STATE_PROTOBUF => {
425 add_e820_entry(entries.next(), range, E820_RESERVED)?;
426 n += 1;
427 }
428
429 _ => {
430 panic!("unexpected vtl2 ram type {typ:?} for range {range:#?}");
431 }
432 }
433 }
434
435 let base = n.min(boot_params.e820_map.len());
436 boot_params.e820_entries = base as u8;
437
438 if base < n {
439 ext.header.len = ((n - base) * size_of::<e820entry>()) as u32;
440 Ok(true)
441 } else {
442 Ok(false)
443 }
444 }
445
446 pub fn build_boot_params(
447 address_space: &AddressSpaceManager,
448 initrd: Range<u64>,
449 cmdline: &str,
450 setup_data_head: *const setup_data,
451 setup_data_tail: &mut &mut setup_data,
452 ) -> OffStackRef<'static, PageAlign<boot_params>> {
453 let mut boot_params_storage = off_stack!(PageAlign<boot_params>, zeroed());
454 let boot_params = &mut boot_params_storage.0;
455 boot_params.hdr.type_of_loader = 0xff; boot_params.hdr.hardware_subarch = 1.into();
467
468 boot_params.hdr.ramdisk_image = (initrd.start as u32).into();
469 boot_params.ext_ramdisk_image = (initrd.start >> 32) as u32;
470 let initrd_len = initrd.end - initrd.start;
471 boot_params.hdr.ramdisk_size = (initrd_len as u32).into();
472 boot_params.ext_ramdisk_size = (initrd_len >> 32) as u32;
473
474 let e820_ext = OffStackRef::leak(off_stack!(E820Ext, zeroed()));
475
476 let used_ext = build_e820_map(boot_params, e820_ext, address_space)
477 .expect("building e820 map must succeed");
478
479 if used_ext {
480 e820_ext.header.ty = SETUP_E820_EXT;
481 setup_data_tail.next = ptr::from_ref(&e820_ext.header) as u64;
482 *setup_data_tail = &mut e820_ext.header;
483 }
484
485 let cmd_line_addr = cmdline.as_ptr() as u64;
486 boot_params.hdr.cmd_line_ptr = (cmd_line_addr as u32).into();
487 boot_params.ext_cmd_line_ptr = (cmd_line_addr >> 32) as u32;
488
489 boot_params.hdr.setup_data = (setup_data_head as u64).into();
490
491 boot_params_storage
492 }
493}
494
495#[cfg(target_arch = "x86_64")]
497fn build_cc_blob_sev_info(
498 cc_blob: &mut loader_defs::linux::cc_blob_sev_info,
499 shim_params: &ShimParams,
500) {
501 cc_blob.magic = loader_defs::linux::CC_BLOB_SEV_INFO_MAGIC;
504 cc_blob.version = 0;
505 cc_blob._reserved = 0;
506 cc_blob.secrets_phys = shim_params.secrets_start();
507 cc_blob.secrets_len = hvdef::HV_PAGE_SIZE as u32;
508 cc_blob._rsvd1 = 0;
509 cc_blob.cpuid_phys = shim_params.cpuid_start();
510 cc_blob.cpuid_len = hvdef::HV_PAGE_SIZE as u32;
511 cc_blob._rsvd2 = 0;
512}
513
514#[repr(C, align(4096))]
515#[derive(FromZeros, Immutable, KnownLayout)]
516struct PageAlign<T>(T);
517
518const fn zeroed<T: FromZeros>() -> T {
519 unsafe { core::mem::MaybeUninit::<T>::zeroed().assume_init() }
521}
522
523fn get_ref_time(isolation: IsolationType) -> Option<u64> {
524 match isolation {
525 #[cfg(target_arch = "x86_64")]
526 IsolationType::Tdx => get_tdx_tsc_reftime(),
527 #[cfg(target_arch = "x86_64")]
528 IsolationType::Snp => None,
529 _ => Some(minimal_rt::reftime::reference_time()),
530 }
531}
532
533fn build_initrd_crc_diagnostic(p: &ShimParams, first_computed_crc: u32) -> ArrayString<384> {
554 let initrd_bytes = p.initrd();
555
556 let second_computed_crc = crc32fast::hash(initrd_bytes);
561
562 let mut head = [0u8; 16];
565 let head_len = head.len().min(initrd_bytes.len());
566 head[..head_len].copy_from_slice(&initrd_bytes[..head_len]);
567
568 let mut tail = [0u8; 16];
569 let tail_len = tail.len().min(initrd_bytes.len());
570 if tail_len > 0 {
571 let start = initrd_bytes.len() - tail_len;
572 tail[..tail_len].copy_from_slice(&initrd_bytes[start..]);
573 }
574
575 let mut eighths = [0u32; 8];
578 let n = initrd_bytes.len();
579 if n > 0 {
580 let step = n.div_ceil(8);
581 for (i, e) in eighths.iter_mut().enumerate() {
582 let start = i * step;
583 if start >= n {
584 break;
585 }
586 let end = ((i + 1) * step).min(n);
587 *e = crc32fast::hash(&initrd_bytes[start..end]);
588 }
589 }
590
591 let mut buf = ArrayString::<384>::new();
592 let _ = write!(
593 &mut buf,
594 "initrd crc mismatch: iso={:?} base={:#x} size={:#x} \
595 exp={:#x} got={:#x} got2={:#x} head={:02x?} tail={:02x?} \
596 eighths=[{:#x},{:#x},{:#x},{:#x},{:#x},{:#x},{:#x},{:#x}]",
597 p.isolation_type,
598 p.initrd_base,
599 p.initrd_size,
600 p.initrd_crc,
601 first_computed_crc,
602 second_computed_crc,
603 &head[..head_len],
604 &tail[..tail_len],
605 eighths[0],
606 eighths[1],
607 eighths[2],
608 eighths[3],
609 eighths[4],
610 eighths[5],
611 eighths[6],
612 eighths[7],
613 );
614 buf
615}
616
617fn shim_main(shim_params_raw_offset: isize) -> ! {
618 let p = shim_parameters(shim_params_raw_offset);
619 if p.isolation_type == IsolationType::None {
620 enable_enlightened_panic();
621 }
622
623 #[cfg(feature = "cvm_boot_log")]
624 arch::initialize_serial_io(&p);
625
626 boot_logger_memory_init(p.log_buffer);
628
629 log::set_logger(&boot_logger::BOOT_LOGGER).unwrap();
631 log::set_max_level(log::LevelFilter::Info);
633
634 let boot_reftime = get_ref_time(p.isolation_type);
635
636 if !p.isolation_type.is_hardware_isolated() {
643 hvcall().initialize();
644 }
645
646 let mut static_options = BootCommandLineOptions::new();
647 if let Some(cmdline) = p.command_line().command_line() {
648 static_options.parse(cmdline);
649 }
650
651 let static_confidential_debug = static_options.confidential_debug;
652 let can_trust_host = p.isolation_type == IsolationType::None || static_confidential_debug;
653
654 let mut dt_storage = off_stack!(PartitionInfo, PartitionInfo::new());
655 let address_space = OffStackRef::leak(off_stack!(
656 AddressSpaceManager,
657 AddressSpaceManager::new_const()
658 ));
659 let partition_info = match PartitionInfo::read_from_dt(
660 &p,
661 &mut dt_storage,
662 address_space,
663 static_options,
664 can_trust_host,
665 ) {
666 Ok(val) => val,
667 Err(e) => panic!("unable to read device tree params {:?}", e),
668 };
669
670 boot_logger_runtime_init(p.isolation_type, partition_info.com3_serial.clone());
673 log::info!("openhcl_boot: logging enabled");
674 log::info!("serial configuration: {:#x?}", partition_info.com3_serial);
675
676 let is_confidential_debug =
680 static_confidential_debug || partition_info.boot_options.confidential_debug;
681
682 if !p.isolation_type.is_hardware_isolated()
684 && hvcall().vtl() == Vtl::Vtl2
685 && hvdef::HvRegisterVsmCapabilities::from(
686 hvcall()
687 .get_register(hvdef::HvAllArchRegisterName::VsmCapabilities.into())
688 .expect("failed to query vsm capabilities")
689 .as_u64(),
690 )
691 .vtl0_alias_map_available()
692 {
693 if partition_info.vtl0_alias_map.is_none() {
703 partition_info.vtl0_alias_map =
704 Some(1 << (arch::physical_address_bits(p.isolation_type) - 1));
705 }
706 } else {
707 partition_info.vtl0_alias_map = None;
710 }
711
712 let partition_info: &PartitionInfo = partition_info;
714
715 if partition_info.cpus.is_empty() {
716 panic!("no cpus");
717 }
718
719 validate_vp_hw_ids(partition_info);
720
721 setup_vtl2_memory(&p, partition_info, address_space);
722 setup_vtl2_vp(partition_info);
723
724 verify_imported_regions_hash(&p);
725
726 let mut sidecar_params = off_stack!(PageAlign<SidecarParams>, zeroed());
727 let mut sidecar_output = off_stack!(PageAlign<SidecarOutput>, zeroed());
728 let sidecar = sidecar::start_sidecar(
729 &p,
730 partition_info,
731 address_space,
732 &mut sidecar_params.0,
733 &mut sidecar_output.0,
734 );
735
736 let address_space: &AddressSpaceManager = address_space;
738
739 let mut cmdline = off_stack!(ArrayString<COMMAND_LINE_SIZE>, ArrayString::new_const());
740 build_kernel_command_line(BuildKernelCommandLineParams {
741 params: &p,
742 cmdline: &mut cmdline,
743 partition_info,
744 can_trust_host,
745 is_confidential_debug,
746 sidecar: sidecar.as_ref(),
747 vtl2_pool_supported: address_space.has_vtl2_pool(),
748 })
749 .unwrap();
750
751 let mut fdt = off_stack!(Fdt, zeroed());
752 fdt.header.len = fdt.data.len() as u32;
753 fdt.header.ty = SETUP_DTB;
754
755 #[cfg(target_arch = "x86_64")]
756 let mut setup_data_tail = &mut fdt.header;
757 #[cfg(target_arch = "x86_64")]
758 let setup_data_head = core::ptr::from_ref(setup_data_tail);
759
760 #[cfg(target_arch = "x86_64")]
761 if p.isolation_type == IsolationType::Snp {
762 let cc_blob = OffStackRef::leak(off_stack!(loader_defs::linux::cc_blob_sev_info, zeroed()));
763 build_cc_blob_sev_info(cc_blob, &p);
764
765 let cc_data = OffStackRef::leak(off_stack!(loader_defs::linux::cc_setup_data, zeroed()));
766 cc_data.header.len = size_of::<loader_defs::linux::cc_setup_data>() as u32;
767 cc_data.header.ty = loader_defs::linux::SETUP_CC_BLOB;
768 cc_data.cc_blob_address = core::ptr::from_ref(&*cc_blob) as u32;
769
770 setup_data_tail.next = core::ptr::from_ref(&*cc_data) as u64;
772 setup_data_tail = &mut cc_data.header;
773 }
774
775 let initrd = p.initrd_base..p.initrd_base + p.initrd_size;
776
777 let computed_crc = crc32fast::hash(p.initrd());
779 if computed_crc != p.initrd_crc && is_confidential_debug {
780 let diag = build_initrd_crc_diagnostic(&p, computed_crc);
781 log::error!("{}", diag.as_str());
782 panic!("{}", diag.as_str());
783 }
784 assert_eq!(
785 computed_crc, p.initrd_crc,
786 "computed initrd crc does not match build time calculated crc"
787 );
788
789 #[cfg(target_arch = "x86_64")]
790 let boot_params = x86_boot::build_boot_params(
791 address_space,
792 initrd.clone(),
793 &cmdline,
794 setup_data_head,
795 &mut setup_data_tail,
796 );
797
798 let boot_times = boot_reftime.map(|start| BootTimes {
802 start,
803 end: get_ref_time(p.isolation_type).unwrap_or(0),
804 });
805
806 for (range, result) in walk_ranges(
809 partition_info.vtl2_ram.iter().map(|r| (r.range, ())),
810 p.imported_regions(),
811 ) {
812 match result {
813 RangeWalkResult::Neither | RangeWalkResult::Left(_) | RangeWalkResult::Both(_, _) => {}
814 RangeWalkResult::Right(accepted) => {
815 assert!(
818 accepted,
819 "range {:#x?} not in vtl2 ram was not preaccepted at launch",
820 range
821 );
822 }
823 }
824 }
825
826 write_dt(
827 &mut fdt.data,
828 partition_info,
829 address_space,
830 p.imported_regions().map(|r| {
831 r.0
838 }),
839 initrd,
840 &cmdline,
841 sidecar.as_ref(),
842 boot_times,
843 p.isolation_type,
844 )
845 .unwrap();
846
847 rt::verify_stack_cookie();
848
849 log::info!("uninitializing hypercalls");
850 #[cfg(not(feature = "cvm_boot_log"))]
851 log::info!("about to jump to kernel");
852
853 hvcall().uninitialize();
854
855 #[cfg(feature = "cvm_boot_log")]
856 {
857 log::info!("uninitializing serial io");
858 log::info!("about to jump to kernel");
859 arch::uninitialize_serial_io(&p);
860 }
861
862 cfg_select! {
863 target_arch = "x86_64" => {
864 let kernel_entry: extern "C" fn(u64, &loader_defs::linux::boot_params) -> ! =
866 unsafe { core::mem::transmute(p.kernel_entry_address) };
867 kernel_entry(0, &boot_params.0)
868 }
869 target_arch = "aarch64" => {
870 let kernel_entry: extern "C" fn(
872 fdt_data: *const u8,
873 mbz0: u64,
874 mbz1: u64,
875 mbz2: u64,
876 ) -> ! = unsafe { core::mem::transmute(p.kernel_entry_address) };
877 unsafe {
881 core::arch::asm!(
882 "
883 mrs {0}, sctlr_el1
884 bic {0}, {0}, #0x1
885 msr sctlr_el1, {0}
886 tlbi vmalle1
887 dsb sy
888 isb sy",
889 lateout(reg) _,
890 );
891 }
892 kernel_entry(fdt.data.as_ptr(), 0, 0, 0)
893 }
894 _ => {
895 panic!("unsupported arch")
896 }
897 }
898}
899
900fn validate_vp_hw_ids(partition_info: &PartitionInfo) {
904 use host_params::MAX_CPU_COUNT;
905 use hypercall::HwId;
906
907 if partition_info.isolation.is_hardware_isolated() {
908 return;
917 }
918
919 if hvcall().vtl() != Vtl::Vtl2 {
920 return;
924 }
925
926 let mut hw_ids = off_stack!(ArrayVec<HwId, MAX_CPU_COUNT>, ArrayVec::new_const());
929 hw_ids.clear();
930 hw_ids.extend(partition_info.cpus.iter().map(|c| c.reg as _));
931 let mut vp_indexes = off_stack!(ArrayVec<u32, MAX_CPU_COUNT>, ArrayVec::new_const());
932 vp_indexes.clear();
933 if let Err(err) = hvcall().get_vp_index_from_hw_id(&hw_ids, &mut vp_indexes) {
934 panic!(
935 "failed to get VP index for hardware ID {:#x}: {}",
936 hw_ids[vp_indexes.len().min(hw_ids.len() - 1)],
937 err
938 );
939 }
940 if let Some((i, &vp_index)) = vp_indexes
941 .iter()
942 .enumerate()
943 .find(|&(i, vp_index)| i as u32 != *vp_index)
944 {
945 panic!(
946 "CPU hardware ID {:#x} does not correspond to VP index {}",
947 hw_ids[i], vp_index
948 );
949 }
950}
951
952#[cfg(not(minimal_rt))]
955fn main() {
956 unimplemented!("build with MINIMAL_RT_BUILD to produce a working boot loader");
957}
958
959#[cfg(test)]
960mod test {
961 use super::x86_boot::E820Ext;
962 use super::x86_boot::build_e820_map;
963 use crate::cmdline::BootCommandLineOptions;
964 use crate::dt::write_dt;
965 use crate::host_params::MAX_CPU_COUNT;
966 use crate::host_params::PartitionInfo;
967 use crate::host_params::shim_params::IsolationType;
968 use crate::memory::AddressSpaceManager;
969 use crate::memory::AddressSpaceManagerBuilder;
970 use arrayvec::ArrayString;
971 use arrayvec::ArrayVec;
972 use core::ops::Range;
973 use host_fdt_parser::ComInfo;
974 use host_fdt_parser::CpuEntry;
975 use host_fdt_parser::MemoryEntry;
976 use host_fdt_parser::VmbusInfo;
977 use igvm_defs::MemoryMapEntryType;
978 use loader_defs::linux::E820_RAM;
979 use loader_defs::linux::E820_RESERVED;
980 use loader_defs::linux::boot_params;
981 use loader_defs::linux::e820entry;
982 use memory_range::MemoryRange;
983 use memory_range::subtract_ranges;
984 use sidecar_defs::PerCpuState;
985 use zerocopy::FromZeros;
986
987 const HIGH_MMIO_GAP_END: u64 = 0x1000000000; const VMBUS_MMIO_GAP_SIZE: u64 = 0x10000000; const HIGH_MMIO_GAP_START: u64 = HIGH_MMIO_GAP_END - VMBUS_MMIO_GAP_SIZE;
990
991 fn new_partition_info(cpu_count: usize) -> PartitionInfo {
994 let mut cpus: ArrayVec<CpuEntry, MAX_CPU_COUNT> = ArrayVec::new();
995
996 for id in 0..(cpu_count as u64) {
997 cpus.push(CpuEntry { reg: id, vnode: 0 });
998 }
999
1000 let mut mmio = ArrayVec::new();
1001 mmio.push(
1002 MemoryRange::try_new(HIGH_MMIO_GAP_START..HIGH_MMIO_GAP_END).expect("valid range"),
1003 );
1004
1005 PartitionInfo {
1006 vtl2_ram: ArrayVec::new(),
1007 partition_ram: ArrayVec::new(),
1008 isolation: IsolationType::None,
1009 bsp_reg: cpus[0].reg as u32,
1010 cpus,
1011 sidecar_cpu_overrides: PerCpuState {
1012 per_cpu_state_specified: false,
1013 sidecar_starts_cpu: [true; sidecar_defs::NUM_CPUS_SUPPORTED_FOR_PER_CPU_STATE],
1014 },
1015 cmdline: ArrayString::new(),
1016 vmbus_vtl2: VmbusInfo { mmio },
1017 vmbus_vtl0: VmbusInfo {
1018 mmio: ArrayVec::new(),
1019 },
1020 com3_serial: ComInfo::None,
1021 gic: None,
1022 pmu_gsiv: None,
1023 memory_allocation_mode: host_fdt_parser::MemoryAllocationMode::Host,
1024 entropy: None,
1025 vtl0_alias_map: None,
1026 nvme_keepalive: false,
1027 boot_options: BootCommandLineOptions::new(),
1028 }
1029 }
1030
1031 #[test]
1033 #[cfg_attr(
1034 target_arch = "aarch64",
1035 ignore = "TODO: investigate why this doesn't always work on ARM"
1036 )]
1037 fn fdt_cpu_scaling() {
1038 const MAX_CPUS: usize = 2048;
1039
1040 let mut buf = [0; 0x40000];
1041 write_dt(
1042 &mut buf,
1043 &new_partition_info(MAX_CPUS),
1044 &AddressSpaceManager::new_const(),
1045 [],
1046 0..0,
1047 &ArrayString::from("test").unwrap_or_default(),
1048 None,
1049 None,
1050 IsolationType::None,
1051 )
1052 .unwrap();
1053 }
1054
1055 #[test]
1061 #[ignore = "TODO: temporarily broken"]
1062 fn fdt_dtc_check_content() {
1063 const MAX_CPUS: usize = 2;
1064 const BUF_SIZE: usize = 0x1000;
1065
1066 let dtb_data_spans: [(usize, &[u8]); 2] = [
1068 (
1069 0,
1070 b"\xd0\x0d\xfe\xed\x00\x00\x10\x00\x00\x00\x04\x38\x00\x00\x00\x38\
1071 \x00\x00\x00\x28\x00\x00\x00\x11\x00\x00\x00\x10\x00\x00\x00\x00\
1072 \x00\x00\x00\x4a\x00\x00\x01\x6c\x00\x00\x00\x00\x00\x00\x00\x00\
1073 \x00\x00\x00\x00\x00\x00\x00\x00\x23\x61\x64\x64\x72\x65\x73\x73\
1074 \x2d\x63\x65\x6c\x6c\x73\x00\x23\x73\x69\x7a\x65\x2d\x63\x65\x6c\
1075 \x6c\x73\x00\x6d\x6f\x64\x65\x6c\x00\x72\x65\x67\x00\x64\x65\x76\
1076 \x69\x63\x65\x5f\x74\x79\x70\x65\x00\x73\x74\x61\x74\x75\x73\x00\
1077 \x63\x6f\x6d\x70\x61\x74\x69\x62\x6c\x65\x00\x72\x61\x6e\x67\x65\
1078 \x73",
1079 ),
1080 (
1081 0x430,
1082 b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
1083 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x02\
1084 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x0f\x00\x00\x00\x00\
1085 \x00\x00\x00\x03\x00\x00\x00\x0f\x00\x00\x00\x1b\x6d\x73\x66\x74\
1086 \x2c\x75\x6e\x64\x65\x72\x68\x69\x6c\x6c\x00\x00\x00\x00\x00\x01\
1087 \x63\x70\x75\x73\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\
1088 \x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x04\
1089 \x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x01\x63\x70\x75\x40\
1090 \x30\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x25\
1091 \x63\x70\x75\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x21\
1092 \x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x05\x00\x00\x00\x31\
1093 \x6f\x6b\x61\x79\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\
1094 \x63\x70\x75\x40\x31\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\
1095 \x00\x00\x00\x25\x63\x70\x75\x00\x00\x00\x00\x03\x00\x00\x00\x04\
1096 \x00\x00\x00\x21\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x05\
1097 \x00\x00\x00\x31\x6f\x6b\x61\x79\x00\x00\x00\x00\x00\x00\x00\x02\
1098 \x00\x00\x00\x02\x00\x00\x00\x01\x76\x6d\x62\x75\x73\x00\x00\x00\
1099 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x02\
1100 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x0f\x00\x00\x00\x01\
1101 \x00\x00\x00\x03\x00\x00\x00\x0b\x00\x00\x00\x38\x6d\x73\x66\x74\
1102 \x2c\x76\x6d\x62\x75\x73\x00\x00\x00\x00\x00\x03\x00\x00\x00\x14\
1103 \x00\x00\x00\x43\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\
1104 \xf0\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\
1105 \x00\x00\x00\x09",
1106 ),
1107 ];
1108
1109 let mut sample_buf = [0u8; BUF_SIZE];
1110 for (span_start, bytes) in dtb_data_spans {
1111 sample_buf[span_start..span_start + bytes.len()].copy_from_slice(bytes);
1112 }
1113
1114 let mut buf = [0u8; BUF_SIZE];
1115 write_dt(
1116 &mut buf,
1117 &new_partition_info(MAX_CPUS),
1118 &AddressSpaceManager::new_const(),
1119 [],
1120 0..0,
1121 &ArrayString::from("test").unwrap_or_default(),
1122 None,
1123 None,
1124 IsolationType::None,
1125 )
1126 .unwrap();
1127
1128 assert!(sample_buf == buf);
1129 }
1130
1131 #[test]
1138 #[ignore = "enabling the test requires installing additional software, \
1139 and developers will experience a break."]
1140 fn fdt_dtc_decompile() {
1141 const MAX_CPUS: usize = 2048;
1142
1143 let mut buf = [0; 0x40000];
1144 write_dt(
1145 &mut buf,
1146 &new_partition_info(MAX_CPUS),
1147 &AddressSpaceManager::new_const(),
1148 [],
1149 0..0,
1150 &ArrayString::from("test").unwrap_or_default(),
1151 None,
1152 None,
1153 IsolationType::None,
1154 )
1155 .unwrap();
1156
1157 let input_dtb_file_name = "openhcl_boot.dtb";
1158 let output_dts_file_name = "openhcl_boot.dts";
1159 std::fs::write(input_dtb_file_name, buf).unwrap();
1160 let success = std::process::Command::new("dtc")
1161 .args([input_dtb_file_name, "-I", "dtb", "-o", output_dts_file_name])
1162 .status()
1163 .unwrap()
1164 .success();
1165 assert!(success);
1166 }
1167
1168 fn new_address_space_manager(
1169 ram: &[MemoryRange],
1170 bootshim_used: MemoryRange,
1171 persisted_range: MemoryRange,
1172 parameter_range: MemoryRange,
1173 reclaim: Option<MemoryRange>,
1174 ) -> AddressSpaceManager {
1175 let ram = ram
1176 .iter()
1177 .cloned()
1178 .map(|range| MemoryEntry {
1179 range,
1180 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1181 vnode: 0,
1182 })
1183 .collect::<Vec<_>>();
1184 let mut address_space = AddressSpaceManager::new_const();
1185 AddressSpaceManagerBuilder::new(
1186 &mut address_space,
1187 &ram,
1188 bootshim_used,
1189 persisted_range,
1190 subtract_ranges([parameter_range], reclaim),
1191 )
1192 .init()
1193 .unwrap();
1194 address_space
1195 }
1196
1197 fn check_e820(boot_params: &boot_params, ext: &E820Ext, expected: &[(Range<u64>, u32)]) {
1198 let actual = boot_params.e820_map[..boot_params.e820_entries as usize]
1199 .iter()
1200 .chain(
1201 ext.entries
1202 .iter()
1203 .take((ext.header.len as usize) / size_of::<e820entry>()),
1204 );
1205
1206 assert_eq!(actual.clone().count(), expected.len());
1207
1208 for (actual, (expected_range, expected_type)) in actual.zip(expected.iter()) {
1209 let addr: u64 = actual.addr.into();
1210 let size: u64 = actual.size.into();
1211 let typ: u32 = actual.typ.into();
1212 assert_eq!(addr, expected_range.start);
1213 assert_eq!(size, expected_range.end - expected_range.start);
1214 assert_eq!(typ, *expected_type);
1215 }
1216 }
1217
1218 const PAGE_SIZE: u64 = 0x1000;
1219 const ONE_MB: u64 = 0x10_0000;
1220
1221 #[test]
1222 fn test_e820_basic() {
1223 let mut boot_params: boot_params = FromZeros::new_zeroed();
1225 let mut ext = FromZeros::new_zeroed();
1226 let bootshim_used = MemoryRange::try_new(ONE_MB..3 * ONE_MB).unwrap();
1227 let persisted_header_end = ONE_MB + PAGE_SIZE;
1228 let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1229 let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1230 let parameter_range = MemoryRange::try_new(2 * ONE_MB..3 * ONE_MB).unwrap();
1231 let address_space = new_address_space_manager(
1232 &[MemoryRange::new(ONE_MB..4 * ONE_MB)],
1233 bootshim_used,
1234 persisted_state,
1235 parameter_range,
1236 None,
1237 );
1238
1239 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1240
1241 check_e820(
1242 &boot_params,
1243 &ext,
1244 &[
1245 (ONE_MB..(persisted_header_end), E820_RESERVED),
1246 (persisted_header_end..persisted_end, E820_RESERVED),
1247 (persisted_end..2 * ONE_MB, E820_RAM),
1248 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1249 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1250 ],
1251 );
1252
1253 let mut boot_params: boot_params = FromZeros::new_zeroed();
1255 let mut ext = FromZeros::new_zeroed();
1256 let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1257 let persisted_header_end = ONE_MB + PAGE_SIZE;
1258 let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1259 let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1260 let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1261 let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1262 let address_space = new_address_space_manager(
1263 &[MemoryRange::new(ONE_MB..6 * ONE_MB)],
1264 bootshim_used,
1265 persisted_state,
1266 parameter_range,
1267 Some(reclaim),
1268 );
1269
1270 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1271
1272 check_e820(
1273 &boot_params,
1274 &ext,
1275 &[
1276 (ONE_MB..(persisted_header_end), E820_RESERVED),
1277 (persisted_header_end..persisted_end, E820_RESERVED),
1278 (persisted_end..2 * ONE_MB, E820_RAM),
1279 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1280 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1281 (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1282 (5 * ONE_MB..6 * ONE_MB, E820_RAM),
1283 ],
1284 );
1285
1286 let mut boot_params: boot_params = FromZeros::new_zeroed();
1288 let mut ext = FromZeros::new_zeroed();
1289 let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1290 let persisted_header_end = ONE_MB + PAGE_SIZE;
1291 let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1292 let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1293 let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1294 let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1295 let address_space = new_address_space_manager(
1296 &[
1297 MemoryRange::new(ONE_MB..4 * ONE_MB),
1298 MemoryRange::new(4 * ONE_MB..10 * ONE_MB),
1299 ],
1300 bootshim_used,
1301 persisted_state,
1302 parameter_range,
1303 Some(reclaim),
1304 );
1305
1306 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1307
1308 check_e820(
1309 &boot_params,
1310 &ext,
1311 &[
1312 (ONE_MB..(persisted_header_end), E820_RESERVED),
1313 (persisted_header_end..persisted_end, E820_RESERVED),
1314 (persisted_end..2 * ONE_MB, E820_RAM),
1315 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1316 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1317 (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1318 (5 * ONE_MB..10 * ONE_MB, E820_RAM),
1319 ],
1320 );
1321
1322 let mut boot_params: boot_params = FromZeros::new_zeroed();
1324 let mut ext = FromZeros::new_zeroed();
1325 let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1326 let persisted_header_end = ONE_MB + PAGE_SIZE;
1327 let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1328 let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1329 let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1330 let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1331 let address_space = new_address_space_manager(
1332 &[
1333 MemoryRange::new(ONE_MB..2 * ONE_MB),
1334 MemoryRange::new(2 * ONE_MB..3 * ONE_MB),
1335 MemoryRange::new(3 * ONE_MB..4 * ONE_MB),
1336 MemoryRange::new(4 * ONE_MB..5 * ONE_MB),
1337 MemoryRange::new(5 * ONE_MB..6 * ONE_MB),
1338 MemoryRange::new(6 * ONE_MB..7 * ONE_MB),
1339 MemoryRange::new(7 * ONE_MB..8 * ONE_MB),
1340 ],
1341 bootshim_used,
1342 persisted_state,
1343 parameter_range,
1344 Some(reclaim),
1345 );
1346
1347 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1348
1349 check_e820(
1350 &boot_params,
1351 &ext,
1352 &[
1353 (ONE_MB..(persisted_header_end), E820_RESERVED),
1354 (persisted_header_end..persisted_end, E820_RESERVED),
1355 (persisted_end..2 * ONE_MB, E820_RAM),
1356 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1357 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1358 (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1359 (5 * ONE_MB..8 * ONE_MB, E820_RAM),
1360 ],
1361 );
1362 }
1363
1364 #[test]
1366 fn test_e820_huge() {
1367 use crate::memory::AllocationPolicy;
1368 use crate::memory::AllocationType;
1369
1370 const E820_MAX_ENTRIES_ZEROPAGE: usize = 128;
1373 const RAM_RANGES: usize = 64;
1374 const TOTAL_ALLOCATIONS: usize = 256;
1375
1376 let mut ranges = Vec::new();
1378 for i in 0..RAM_RANGES {
1379 let start = (i as u64) * 64 * ONE_MB;
1380 let end = start + 64 * ONE_MB;
1381 ranges.push(MemoryRange::new(start..end));
1382 }
1383
1384 let bootshim_used = MemoryRange::try_new(0..ONE_MB * 2).unwrap();
1385 let persisted_range = MemoryRange::try_new(0..ONE_MB).unwrap();
1386 let parameter_range = MemoryRange::try_new(ONE_MB..2 * ONE_MB).unwrap();
1387
1388 let mut address_space = {
1389 let ram = ranges
1390 .iter()
1391 .cloned()
1392 .map(|range| MemoryEntry {
1393 range,
1394 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1395 vnode: 0,
1396 })
1397 .collect::<Vec<_>>();
1398 let mut address_space = AddressSpaceManager::new_const();
1399 AddressSpaceManagerBuilder::new(
1400 &mut address_space,
1401 &ram,
1402 bootshim_used,
1403 persisted_range,
1404 core::iter::once(parameter_range),
1405 )
1406 .init()
1407 .unwrap();
1408 address_space
1409 };
1410
1411 for i in 0..TOTAL_ALLOCATIONS {
1412 let _allocated = address_space
1416 .allocate(
1417 None,
1418 ONE_MB,
1419 if i % 2 == 0 {
1420 AllocationType::GpaPool
1421 } else {
1422 AllocationType::SidecarNode
1423 },
1424 AllocationPolicy::LowMemory,
1425 )
1426 .expect("should be able to allocate sidecar node");
1427 }
1428
1429 let mut boot_params: boot_params = FromZeros::new_zeroed();
1430 let mut ext = FromZeros::new_zeroed();
1431 let total_ranges = address_space.vtl2_ranges().count();
1432
1433 let used_ext = build_e820_map(&mut boot_params, &mut ext, &address_space).unwrap();
1434
1435 assert!(used_ext, "should use extension when there are many ranges");
1437
1438 assert_eq!(boot_params.e820_entries, E820_MAX_ENTRIES_ZEROPAGE as u8);
1440
1441 let ext_entries = (ext.header.len as usize) / size_of::<e820entry>();
1443 assert_eq!(ext_entries, total_ranges - E820_MAX_ENTRIES_ZEROPAGE);
1444
1445 let total_e820_entries = boot_params.e820_entries as usize + ext_entries;
1447 assert_eq!(total_e820_entries, total_ranges);
1448 }
1449}