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
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
87fn 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 const KERNEL_PARAMETERS: &[&str] = &[
104 "loglevel=8",
106 "log_buf_len=128K",
108 "printk.time=1",
110 "console_msg_format=syslog",
112 "uio_hv_generic.no_mask=1",
114 "coredump_filter=0x33",
117 "cpufreq.off=1",
119 "cpuidle.off=1",
123 "cryptomgr.notests",
127 "idle=halt",
131 "initcall_blacklist=init_real_mode,sbf_init",
134 "lpj=3000000",
136 "no_timer_check",
138 "noxsave",
144 "oops=panic",
146 "panic_on_warn=0",
148 "panic_print=0",
151 "panic=-1",
153 "printk.devkmsg=on",
161 "reboot=t",
165 "rootfstype=tmpfs",
167 "sysctl.vm.compaction_proactiveness=0",
170 "tsc=reliable",
173 "unknown_nmi_panic=1",
175 "vfio_pci.ids=1414:00ba",
177 "vfio.enable_unsafe_noiommu_mode=1",
180 "rdinit=/underhill-init",
182 "OPENHCL_NVME_VFIO=1",
184 "hv_storvsc.storvsc_vcpus_per_sub_channel=2048",
187 "hv_storvsc.storvsc_max_hw_queues=2",
189 "hv_storvsc.storvsc_ringbuffer_size=0x8000",
191 "MIMALLOC_ARENA_EAGER_COMMIT=0",
193 "acpi=off",
196 ];
197
198 const X86_KERNEL_PARAMETERS: &[&str] = &[
199 "iommu=off",
201 "pci=off",
204 ];
205
206 const AARCH64_KERNEL_PARAMETERS: &[&str] = &[];
207
208 for p in KERNEL_PARAMETERS {
209 write!(cmdline, "{p} ")?;
210 }
211
212 let arch_parameters = if cfg!(target_arch = "x86_64") {
213 X86_KERNEL_PARAMETERS
214 } else {
215 AARCH64_KERNEL_PARAMETERS
216 };
217 for p in arch_parameters {
218 write!(cmdline, "{p} ")?;
219 }
220
221 const HARDWARE_ISOLATED_KERNEL_PARAMETERS: &[&str] = &[
222 "swiotlb=4096,1",
232 ];
233
234 const NON_HARDWARE_ISOLATED_KERNEL_PARAMETERS: &[&str] = &[
235 "swiotlb=1,1",
240 ];
241
242 if params.isolation_type.is_hardware_isolated() {
243 for p in HARDWARE_ISOLATED_KERNEL_PARAMETERS {
244 write!(cmdline, "{p} ")?;
245 }
246 } else {
247 for p in NON_HARDWARE_ISOLATED_KERNEL_PARAMETERS {
248 write!(cmdline, "{p} ")?;
249 }
250 }
251
252 write!(cmdline, "console=")?;
260 match (&partition_info.com3_serial, can_trust_host) {
261 (ComInfo::Ns16550 { current_speed, .. }, true) => {
262 write!(cmdline, "ttyS2,{current_speed} ")?
263 }
264 (ComInfo::Pl011 { current_speed, .. }, true) => {
265 write!(cmdline, "ttyAMA0,{current_speed} ")?
266 }
267 _ => write!(cmdline, "ttynull ")?,
268 }
269
270 if params.isolation_type != IsolationType::None {
271 write!(
272 cmdline,
273 "{}=1 ",
274 underhill_confidentiality::OPENHCL_CONFIDENTIAL_ENV_VAR_NAME
275 )?;
276 }
277
278 if is_confidential_debug {
279 write!(
280 cmdline,
281 "{}=1 ",
282 underhill_confidentiality::OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME
283 )?;
284 }
285
286 write!(cmdline, "OPENHCL_NVME_KEEP_ALIVE=")?;
290
291 if partition_info.boot_options.disable_nvme_keep_alive {
292 write!(cmdline, "disabled,")?;
293 }
294
295 if partition_info.nvme_keepalive {
296 write!(cmdline, "host,")?;
297 } else {
298 write!(cmdline, "nohost,")?;
299 }
300
301 if vtl2_pool_supported {
302 write!(cmdline, "privatepool ")?;
303 } else {
304 write!(cmdline, "noprivatepool ")?;
305 }
306
307 if let Some(sidecar) = sidecar {
308 write!(cmdline, "{} ", sidecar.kernel_command_line())?;
309 }
310
311 if !cmdline.contains("hv_vmbus.message_connection_id") {
312 write!(
318 cmdline,
319 "hv_vmbus.message_connection_id=0x{:x} ",
320 partition_info.vmbus_vtl2.connection_id
321 )?;
322 }
323
324 cmdline.write_str(&partition_info.cmdline)?;
326
327 Ok(())
328}
329
330const FDT_SIZE: usize = 256 * 1024;
337
338#[repr(C, align(4096))]
339#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
340struct Fdt {
341 header: setup_data,
342 data: [u8; FDT_SIZE - size_of::<setup_data>()],
343}
344
345fn shim_parameters(shim_params_raw_offset: isize) -> ShimParams {
349 unsafe extern "C" {
350 static __ehdr_start: u8;
351 }
352
353 let shim_base = core::ptr::addr_of!(__ehdr_start) as usize;
354
355 let raw_shim_params = unsafe {
359 &*(shim_base.wrapping_add_signed(shim_params_raw_offset) as *const ShimParamsRaw)
360 };
361
362 ShimParams::new(shim_base as u64, raw_shim_params)
363}
364
365#[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))]
366mod x86_boot {
367 use crate::PageAlign;
368 use crate::memory::AddressSpaceManager;
369 use crate::single_threaded::OffStackRef;
370 use crate::single_threaded::off_stack;
371 use crate::zeroed;
372 use core::mem::size_of;
373 use core::ops::Range;
374 use core::ptr;
375 use loader_defs::linux::E820_RAM;
376 use loader_defs::linux::E820_RESERVED;
377 use loader_defs::linux::SETUP_E820_EXT;
378 use loader_defs::linux::boot_params;
379 use loader_defs::linux::e820entry;
380 use loader_defs::linux::setup_data;
381 use loader_defs::shim::MemoryVtlType;
382 use memory_range::MemoryRange;
383 use zerocopy::FromZeros;
384 use zerocopy::Immutable;
385 use zerocopy::KnownLayout;
386
387 #[repr(C)]
388 #[derive(FromZeros, Immutable, KnownLayout)]
389 pub struct E820Ext {
390 pub header: setup_data,
391 pub entries: [e820entry; 512],
392 }
393
394 fn add_e820_entry(
395 entry: Option<&mut e820entry>,
396 range: MemoryRange,
397 typ: u32,
398 ) -> Result<(), BuildE820MapError> {
399 *entry.ok_or(BuildE820MapError::OutOfE820Entries)? = e820entry {
400 addr: range.start().into(),
401 size: range.len().into(),
402 typ: typ.into(),
403 };
404 Ok(())
405 }
406
407 #[derive(Debug)]
408 pub enum BuildE820MapError {
409 OutOfE820Entries,
411 }
412
413 pub fn build_e820_map(
415 boot_params: &mut boot_params,
416 ext: &mut E820Ext,
417 address_space: &AddressSpaceManager,
418 ) -> Result<bool, BuildE820MapError> {
419 boot_params.e820_entries = 0;
420 let mut entries = boot_params
421 .e820_map
422 .iter_mut()
423 .chain(ext.entries.iter_mut());
424
425 let mut n = 0;
426 for (range, typ) in address_space.vtl2_ranges() {
427 match typ {
428 MemoryVtlType::VTL2_RAM => {
429 add_e820_entry(entries.next(), range, E820_RAM)?;
430 n += 1;
431 }
432 MemoryVtlType::VTL2_CONFIG
433 | MemoryVtlType::VTL2_SIDECAR_IMAGE
434 | MemoryVtlType::VTL2_SIDECAR_NODE
435 | MemoryVtlType::VTL2_RESERVED
436 | MemoryVtlType::VTL2_GPA_POOL
437 | MemoryVtlType::VTL2_TDX_PAGE_TABLES
438 | MemoryVtlType::VTL2_BOOTSHIM_LOG_BUFFER
439 | MemoryVtlType::VTL2_PERSISTED_STATE_HEADER
440 | MemoryVtlType::VTL2_PERSISTED_STATE_PROTOBUF => {
441 add_e820_entry(entries.next(), range, E820_RESERVED)?;
442 n += 1;
443 }
444
445 _ => {
446 panic!("unexpected vtl2 ram type {typ:?} for range {range:#?}");
447 }
448 }
449 }
450
451 let base = n.min(boot_params.e820_map.len());
452 boot_params.e820_entries = base as u8;
453
454 if base < n {
455 ext.header.len = ((n - base) * size_of::<e820entry>()) as u32;
456 Ok(true)
457 } else {
458 Ok(false)
459 }
460 }
461
462 pub fn build_boot_params(
463 address_space: &AddressSpaceManager,
464 initrd: Range<u64>,
465 cmdline: &str,
466 setup_data_head: *const setup_data,
467 setup_data_tail: &mut &mut setup_data,
468 ) -> OffStackRef<'static, PageAlign<boot_params>> {
469 let mut boot_params_storage = off_stack!(PageAlign<boot_params>, zeroed());
470 let boot_params = &mut boot_params_storage.0;
471 boot_params.hdr.type_of_loader = 0xff; boot_params.hdr.hardware_subarch = 1.into();
483
484 boot_params.hdr.ramdisk_image = (initrd.start as u32).into();
485 boot_params.ext_ramdisk_image = (initrd.start >> 32) as u32;
486 let initrd_len = initrd.end - initrd.start;
487 boot_params.hdr.ramdisk_size = (initrd_len as u32).into();
488 boot_params.ext_ramdisk_size = (initrd_len >> 32) as u32;
489
490 let e820_ext = OffStackRef::leak(off_stack!(E820Ext, zeroed()));
491
492 let used_ext = build_e820_map(boot_params, e820_ext, address_space)
493 .expect("building e820 map must succeed");
494
495 if used_ext {
496 e820_ext.header.ty = SETUP_E820_EXT;
497 setup_data_tail.next = ptr::from_ref(&e820_ext.header) as u64;
498 *setup_data_tail = &mut e820_ext.header;
499 }
500
501 let cmd_line_addr = cmdline.as_ptr() as u64;
502 boot_params.hdr.cmd_line_ptr = (cmd_line_addr as u32).into();
503 boot_params.ext_cmd_line_ptr = (cmd_line_addr >> 32) as u32;
504
505 boot_params.hdr.setup_data = (setup_data_head as u64).into();
506
507 boot_params_storage
508 }
509}
510
511#[cfg(target_arch = "x86_64")]
513fn build_cc_blob_sev_info(
514 cc_blob: &mut loader_defs::linux::cc_blob_sev_info,
515 shim_params: &ShimParams,
516) {
517 cc_blob.magic = loader_defs::linux::CC_BLOB_SEV_INFO_MAGIC;
520 cc_blob.version = 0;
521 cc_blob._reserved = 0;
522 cc_blob.secrets_phys = shim_params.secrets_start();
523 cc_blob.secrets_len = hvdef::HV_PAGE_SIZE as u32;
524 cc_blob._rsvd1 = 0;
525 cc_blob.cpuid_phys = shim_params.cpuid_start();
526 cc_blob.cpuid_len = hvdef::HV_PAGE_SIZE as u32;
527 cc_blob._rsvd2 = 0;
528}
529
530#[repr(C, align(4096))]
531#[derive(FromZeros, Immutable, KnownLayout)]
532struct PageAlign<T>(T);
533
534const fn zeroed<T: FromZeros>() -> T {
535 unsafe { core::mem::MaybeUninit::<T>::zeroed().assume_init() }
537}
538
539fn get_ref_time(isolation: IsolationType) -> Option<u64> {
540 match isolation {
541 #[cfg(target_arch = "x86_64")]
542 IsolationType::Tdx => get_tdx_tsc_reftime(),
543 #[cfg(target_arch = "x86_64")]
544 IsolationType::Snp => None,
545 _ => Some(minimal_rt::reftime::reference_time()),
546 }
547}
548
549fn build_initrd_crc_diagnostic(p: &ShimParams, first_computed_crc: u32) -> ArrayString<384> {
570 let initrd_bytes = p.initrd();
571
572 let second_computed_crc = crc32fast::hash(initrd_bytes);
577
578 let mut head = [0u8; 16];
581 let head_len = head.len().min(initrd_bytes.len());
582 head[..head_len].copy_from_slice(&initrd_bytes[..head_len]);
583
584 let mut tail = [0u8; 16];
585 let tail_len = tail.len().min(initrd_bytes.len());
586 if tail_len > 0 {
587 let start = initrd_bytes.len() - tail_len;
588 tail[..tail_len].copy_from_slice(&initrd_bytes[start..]);
589 }
590
591 let mut eighths = [0u32; 8];
594 let n = initrd_bytes.len();
595 if n > 0 {
596 let step = n.div_ceil(8);
597 for (i, e) in eighths.iter_mut().enumerate() {
598 let start = i * step;
599 if start >= n {
600 break;
601 }
602 let end = ((i + 1) * step).min(n);
603 *e = crc32fast::hash(&initrd_bytes[start..end]);
604 }
605 }
606
607 let mut buf = ArrayString::<384>::new();
608 let _ = write!(
609 &mut buf,
610 "initrd crc mismatch: iso={:?} base={:#x} size={:#x} \
611 exp={:#x} got={:#x} got2={:#x} head={:02x?} tail={:02x?} \
612 eighths=[{:#x},{:#x},{:#x},{:#x},{:#x},{:#x},{:#x},{:#x}]",
613 p.isolation_type,
614 p.initrd_base,
615 p.initrd_size,
616 p.initrd_crc,
617 first_computed_crc,
618 second_computed_crc,
619 &head[..head_len],
620 &tail[..tail_len],
621 eighths[0],
622 eighths[1],
623 eighths[2],
624 eighths[3],
625 eighths[4],
626 eighths[5],
627 eighths[6],
628 eighths[7],
629 );
630 buf
631}
632
633fn shim_main(shim_params_raw_offset: isize) -> ! {
634 let p = shim_parameters(shim_params_raw_offset);
635 if p.isolation_type == IsolationType::None {
636 enable_enlightened_panic();
637 }
638
639 #[cfg(feature = "cvm_boot_log")]
640 arch::initialize_serial_io(&p);
641
642 boot_logger_memory_init(p.log_buffer);
644
645 log::set_logger(&boot_logger::BOOT_LOGGER).unwrap();
647 log::set_max_level(log::LevelFilter::Info);
649
650 let boot_reftime = get_ref_time(p.isolation_type);
651
652 if !p.isolation_type.is_hardware_isolated() {
659 hvcall().initialize();
660 }
661
662 let mut static_options = BootCommandLineOptions::new();
663 if let Some(cmdline) = p.command_line().command_line() {
664 static_options.parse(cmdline);
665 }
666
667 let static_confidential_debug = static_options.confidential_debug;
668 let can_trust_host = p.isolation_type == IsolationType::None || static_confidential_debug;
669
670 let mut dt_storage = off_stack!(PartitionInfo, PartitionInfo::new());
671 let address_space = OffStackRef::leak(off_stack!(
672 AddressSpaceManager,
673 AddressSpaceManager::new_const()
674 ));
675 let partition_info = match PartitionInfo::read_from_dt(
676 &p,
677 &mut dt_storage,
678 address_space,
679 static_options,
680 can_trust_host,
681 ) {
682 Ok(val) => val,
683 Err(e) => panic!("unable to read device tree params {:?}", e),
684 };
685
686 boot_logger_runtime_init(p.isolation_type, partition_info.com3_serial.clone());
689 log::info!("openhcl_boot: logging enabled");
690 log::info!("serial configuration: {:#x?}", partition_info.com3_serial);
691
692 let is_confidential_debug =
696 static_confidential_debug || partition_info.boot_options.confidential_debug;
697
698 if !p.isolation_type.is_hardware_isolated()
700 && hvcall().vtl() == Vtl::Vtl2
701 && hvdef::HvRegisterVsmCapabilities::from(
702 hvcall()
703 .get_register(hvdef::HvAllArchRegisterName::VsmCapabilities.into())
704 .expect("failed to query vsm capabilities")
705 .as_u64(),
706 )
707 .vtl0_alias_map_available()
708 {
709 if partition_info.vtl0_alias_map.is_none() {
719 partition_info.vtl0_alias_map =
720 Some(1 << (arch::physical_address_bits(p.isolation_type) - 1));
721 }
722 } else {
723 partition_info.vtl0_alias_map = None;
726 }
727
728 let partition_info: &PartitionInfo = partition_info;
730
731 if partition_info.cpus.is_empty() {
732 panic!("no cpus");
733 }
734
735 validate_vp_hw_ids(partition_info);
736
737 setup_vtl2_memory(&p, partition_info, address_space);
738 setup_vtl2_vp(partition_info);
739
740 verify_imported_regions_hash(&p);
741
742 let mut sidecar_params = off_stack!(PageAlign<SidecarParams>, zeroed());
743 let mut sidecar_output = off_stack!(PageAlign<SidecarOutput>, zeroed());
744 let sidecar = sidecar::start_sidecar(
745 &p,
746 partition_info,
747 address_space,
748 &mut sidecar_params.0,
749 &mut sidecar_output.0,
750 );
751
752 let address_space: &AddressSpaceManager = address_space;
754
755 let mut cmdline = off_stack!(ArrayString<COMMAND_LINE_SIZE>, ArrayString::new_const());
756 build_kernel_command_line(BuildKernelCommandLineParams {
757 params: &p,
758 cmdline: &mut cmdline,
759 partition_info,
760 can_trust_host,
761 is_confidential_debug,
762 sidecar: sidecar.as_ref(),
763 vtl2_pool_supported: address_space.has_vtl2_pool(),
764 })
765 .unwrap();
766
767 let mut fdt = off_stack!(Fdt, zeroed());
768 fdt.header.len = fdt.data.len() as u32;
769 fdt.header.ty = SETUP_DTB;
770
771 #[cfg(target_arch = "x86_64")]
772 let mut setup_data_tail = &mut fdt.header;
773 #[cfg(target_arch = "x86_64")]
774 let setup_data_head = core::ptr::from_ref(setup_data_tail);
775
776 #[cfg(target_arch = "x86_64")]
777 if p.isolation_type == IsolationType::Snp {
778 let cc_blob = OffStackRef::leak(off_stack!(loader_defs::linux::cc_blob_sev_info, zeroed()));
779 build_cc_blob_sev_info(cc_blob, &p);
780
781 let cc_data = OffStackRef::leak(off_stack!(loader_defs::linux::cc_setup_data, zeroed()));
782 cc_data.header.len = size_of::<loader_defs::linux::cc_setup_data>() as u32;
783 cc_data.header.ty = loader_defs::linux::SETUP_CC_BLOB;
784 cc_data.cc_blob_address = core::ptr::from_ref(&*cc_blob) as u32;
785
786 setup_data_tail.next = core::ptr::from_ref(&*cc_data) as u64;
788 setup_data_tail = &mut cc_data.header;
789 }
790
791 let initrd = p.initrd_base..p.initrd_base + p.initrd_size;
792
793 let computed_crc = crc32fast::hash(p.initrd());
795 if computed_crc != p.initrd_crc && is_confidential_debug {
796 let diag = build_initrd_crc_diagnostic(&p, computed_crc);
797 log::error!("{}", diag.as_str());
798 panic!("{}", diag.as_str());
799 }
800 assert_eq!(
801 computed_crc, p.initrd_crc,
802 "computed initrd crc does not match build time calculated crc"
803 );
804
805 #[cfg(target_arch = "x86_64")]
806 let boot_params = x86_boot::build_boot_params(
807 address_space,
808 initrd.clone(),
809 &cmdline,
810 setup_data_head,
811 &mut setup_data_tail,
812 );
813
814 let boot_times = boot_reftime.map(|start| BootTimes {
818 start,
819 end: get_ref_time(p.isolation_type).unwrap_or(0),
820 });
821
822 for (range, result) in walk_ranges(
825 partition_info.vtl2_ram.iter().map(|r| (r.range, ())),
826 p.imported_regions(),
827 ) {
828 match result {
829 RangeWalkResult::Neither | RangeWalkResult::Left(_) | RangeWalkResult::Both(_, _) => {}
830 RangeWalkResult::Right(accepted) => {
831 assert!(
834 accepted,
835 "range {:#x?} not in vtl2 ram was not preaccepted at launch",
836 range
837 );
838 }
839 }
840 }
841
842 write_dt(
843 &mut fdt.data,
844 partition_info,
845 address_space,
846 p.imported_regions().map(|r| {
847 r.0
854 }),
855 initrd,
856 &cmdline,
857 sidecar.as_ref(),
858 boot_times,
859 p.isolation_type,
860 )
861 .unwrap();
862
863 rt::verify_stack_cookie();
864
865 log::info!("uninitializing hypercalls");
866 #[cfg(not(feature = "cvm_boot_log"))]
867 log::info!("about to jump to kernel");
868
869 hvcall().uninitialize();
870
871 #[cfg(feature = "cvm_boot_log")]
872 {
873 log::info!("uninitializing serial io");
874 log::info!("about to jump to kernel");
875 arch::uninitialize_serial_io(&p);
876 }
877
878 cfg_if::cfg_if! {
879 if #[cfg(target_arch = "x86_64")] {
880 let kernel_entry: extern "C" fn(u64, &loader_defs::linux::boot_params) -> ! =
882 unsafe { core::mem::transmute(p.kernel_entry_address) };
883 kernel_entry(0, &boot_params.0)
884 } else if #[cfg(target_arch = "aarch64")] {
885 let kernel_entry: extern "C" fn(fdt_data: *const u8, mbz0: u64, mbz1: u64, mbz2: u64) -> ! =
887 unsafe { core::mem::transmute(p.kernel_entry_address) };
888 unsafe {
892 core::arch::asm!(
893 "
894 mrs {0}, sctlr_el1
895 bic {0}, {0}, #0x1
896 msr sctlr_el1, {0}
897 tlbi vmalle1
898 dsb sy
899 isb sy",
900 lateout(reg) _,
901 );
902 }
903 kernel_entry(fdt.data.as_ptr(), 0, 0, 0)
904 } else {
905 panic!("unsupported arch")
906 }
907 }
908}
909
910fn validate_vp_hw_ids(partition_info: &PartitionInfo) {
914 use host_params::MAX_CPU_COUNT;
915 use hypercall::HwId;
916
917 if partition_info.isolation.is_hardware_isolated() {
918 return;
927 }
928
929 if hvcall().vtl() != Vtl::Vtl2 {
930 return;
934 }
935
936 let mut hw_ids = off_stack!(ArrayVec<HwId, MAX_CPU_COUNT>, ArrayVec::new_const());
939 hw_ids.clear();
940 hw_ids.extend(partition_info.cpus.iter().map(|c| c.reg as _));
941 let mut vp_indexes = off_stack!(ArrayVec<u32, MAX_CPU_COUNT>, ArrayVec::new_const());
942 vp_indexes.clear();
943 if let Err(err) = hvcall().get_vp_index_from_hw_id(&hw_ids, &mut vp_indexes) {
944 panic!(
945 "failed to get VP index for hardware ID {:#x}: {}",
946 hw_ids[vp_indexes.len().min(hw_ids.len() - 1)],
947 err
948 );
949 }
950 if let Some((i, &vp_index)) = vp_indexes
951 .iter()
952 .enumerate()
953 .find(|&(i, vp_index)| i as u32 != *vp_index)
954 {
955 panic!(
956 "CPU hardware ID {:#x} does not correspond to VP index {}",
957 hw_ids[i], vp_index
958 );
959 }
960}
961
962#[cfg(not(minimal_rt))]
965fn main() {
966 unimplemented!("build with MINIMAL_RT_BUILD to produce a working boot loader");
967}
968
969#[cfg(test)]
970mod test {
971 use super::x86_boot::E820Ext;
972 use super::x86_boot::build_e820_map;
973 use crate::cmdline::BootCommandLineOptions;
974 use crate::dt::write_dt;
975 use crate::host_params::MAX_CPU_COUNT;
976 use crate::host_params::PartitionInfo;
977 use crate::host_params::shim_params::IsolationType;
978 use crate::memory::AddressSpaceManager;
979 use crate::memory::AddressSpaceManagerBuilder;
980 use arrayvec::ArrayString;
981 use arrayvec::ArrayVec;
982 use core::ops::Range;
983 use host_fdt_parser::ComInfo;
984 use host_fdt_parser::CpuEntry;
985 use host_fdt_parser::MemoryEntry;
986 use host_fdt_parser::VmbusInfo;
987 use igvm_defs::MemoryMapEntryType;
988 use loader_defs::linux::E820_RAM;
989 use loader_defs::linux::E820_RESERVED;
990 use loader_defs::linux::boot_params;
991 use loader_defs::linux::e820entry;
992 use memory_range::MemoryRange;
993 use memory_range::subtract_ranges;
994 use sidecar_defs::PerCpuState;
995 use zerocopy::FromZeros;
996
997 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;
1000
1001 fn new_partition_info(cpu_count: usize) -> PartitionInfo {
1004 let mut cpus: ArrayVec<CpuEntry, MAX_CPU_COUNT> = ArrayVec::new();
1005
1006 for id in 0..(cpu_count as u64) {
1007 cpus.push(CpuEntry { reg: id, vnode: 0 });
1008 }
1009
1010 let mut mmio = ArrayVec::new();
1011 mmio.push(
1012 MemoryRange::try_new(HIGH_MMIO_GAP_START..HIGH_MMIO_GAP_END).expect("valid range"),
1013 );
1014
1015 PartitionInfo {
1016 vtl2_ram: ArrayVec::new(),
1017 partition_ram: ArrayVec::new(),
1018 isolation: IsolationType::None,
1019 bsp_reg: cpus[0].reg as u32,
1020 cpus,
1021 sidecar_cpu_overrides: PerCpuState {
1022 per_cpu_state_specified: false,
1023 sidecar_starts_cpu: [true; sidecar_defs::NUM_CPUS_SUPPORTED_FOR_PER_CPU_STATE],
1024 },
1025 cmdline: ArrayString::new(),
1026 vmbus_vtl2: VmbusInfo {
1027 mmio,
1028 connection_id: 0,
1029 },
1030 vmbus_vtl0: VmbusInfo {
1031 mmio: ArrayVec::new(),
1032 connection_id: 0,
1033 },
1034 com3_serial: ComInfo::None,
1035 gic: None,
1036 pmu_gsiv: None,
1037 memory_allocation_mode: host_fdt_parser::MemoryAllocationMode::Host,
1038 entropy: None,
1039 vtl0_alias_map: None,
1040 nvme_keepalive: false,
1041 boot_options: BootCommandLineOptions::new(),
1042 }
1043 }
1044
1045 #[test]
1047 #[cfg_attr(
1048 target_arch = "aarch64",
1049 ignore = "TODO: investigate why this doesn't always work on ARM"
1050 )]
1051 fn fdt_cpu_scaling() {
1052 const MAX_CPUS: usize = 2048;
1053
1054 let mut buf = [0; 0x40000];
1055 write_dt(
1056 &mut buf,
1057 &new_partition_info(MAX_CPUS),
1058 &AddressSpaceManager::new_const(),
1059 [],
1060 0..0,
1061 &ArrayString::from("test").unwrap_or_default(),
1062 None,
1063 None,
1064 IsolationType::None,
1065 )
1066 .unwrap();
1067 }
1068
1069 #[test]
1075 #[ignore = "TODO: temporarily broken"]
1076 fn fdt_dtc_check_content() {
1077 const MAX_CPUS: usize = 2;
1078 const BUF_SIZE: usize = 0x1000;
1079
1080 let dtb_data_spans: [(usize, &[u8]); 2] = [
1082 (
1083 0,
1084 b"\xd0\x0d\xfe\xed\x00\x00\x10\x00\x00\x00\x04\x38\x00\x00\x00\x38\
1085 \x00\x00\x00\x28\x00\x00\x00\x11\x00\x00\x00\x10\x00\x00\x00\x00\
1086 \x00\x00\x00\x4a\x00\x00\x01\x6c\x00\x00\x00\x00\x00\x00\x00\x00\
1087 \x00\x00\x00\x00\x00\x00\x00\x00\x23\x61\x64\x64\x72\x65\x73\x73\
1088 \x2d\x63\x65\x6c\x6c\x73\x00\x23\x73\x69\x7a\x65\x2d\x63\x65\x6c\
1089 \x6c\x73\x00\x6d\x6f\x64\x65\x6c\x00\x72\x65\x67\x00\x64\x65\x76\
1090 \x69\x63\x65\x5f\x74\x79\x70\x65\x00\x73\x74\x61\x74\x75\x73\x00\
1091 \x63\x6f\x6d\x70\x61\x74\x69\x62\x6c\x65\x00\x72\x61\x6e\x67\x65\
1092 \x73",
1093 ),
1094 (
1095 0x430,
1096 b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
1097 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x02\
1098 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x0f\x00\x00\x00\x00\
1099 \x00\x00\x00\x03\x00\x00\x00\x0f\x00\x00\x00\x1b\x6d\x73\x66\x74\
1100 \x2c\x75\x6e\x64\x65\x72\x68\x69\x6c\x6c\x00\x00\x00\x00\x00\x01\
1101 \x63\x70\x75\x73\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\
1102 \x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x04\
1103 \x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x01\x63\x70\x75\x40\
1104 \x30\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x25\
1105 \x63\x70\x75\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x21\
1106 \x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x05\x00\x00\x00\x31\
1107 \x6f\x6b\x61\x79\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\
1108 \x63\x70\x75\x40\x31\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\
1109 \x00\x00\x00\x25\x63\x70\x75\x00\x00\x00\x00\x03\x00\x00\x00\x04\
1110 \x00\x00\x00\x21\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x05\
1111 \x00\x00\x00\x31\x6f\x6b\x61\x79\x00\x00\x00\x00\x00\x00\x00\x02\
1112 \x00\x00\x00\x02\x00\x00\x00\x01\x76\x6d\x62\x75\x73\x00\x00\x00\
1113 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x02\
1114 \x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x0f\x00\x00\x00\x01\
1115 \x00\x00\x00\x03\x00\x00\x00\x0b\x00\x00\x00\x38\x6d\x73\x66\x74\
1116 \x2c\x76\x6d\x62\x75\x73\x00\x00\x00\x00\x00\x03\x00\x00\x00\x14\
1117 \x00\x00\x00\x43\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\
1118 \xf0\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\
1119 \x00\x00\x00\x09",
1120 ),
1121 ];
1122
1123 let mut sample_buf = [0u8; BUF_SIZE];
1124 for (span_start, bytes) in dtb_data_spans {
1125 sample_buf[span_start..span_start + bytes.len()].copy_from_slice(bytes);
1126 }
1127
1128 let mut buf = [0u8; BUF_SIZE];
1129 write_dt(
1130 &mut buf,
1131 &new_partition_info(MAX_CPUS),
1132 &AddressSpaceManager::new_const(),
1133 [],
1134 0..0,
1135 &ArrayString::from("test").unwrap_or_default(),
1136 None,
1137 None,
1138 IsolationType::None,
1139 )
1140 .unwrap();
1141
1142 assert!(sample_buf == buf);
1143 }
1144
1145 #[test]
1152 #[ignore = "enabling the test requires installing additional software, \
1153 and developers will experience a break."]
1154 fn fdt_dtc_decompile() {
1155 const MAX_CPUS: usize = 2048;
1156
1157 let mut buf = [0; 0x40000];
1158 write_dt(
1159 &mut buf,
1160 &new_partition_info(MAX_CPUS),
1161 &AddressSpaceManager::new_const(),
1162 [],
1163 0..0,
1164 &ArrayString::from("test").unwrap_or_default(),
1165 None,
1166 None,
1167 IsolationType::None,
1168 )
1169 .unwrap();
1170
1171 let input_dtb_file_name = "openhcl_boot.dtb";
1172 let output_dts_file_name = "openhcl_boot.dts";
1173 std::fs::write(input_dtb_file_name, buf).unwrap();
1174 let success = std::process::Command::new("dtc")
1175 .args([input_dtb_file_name, "-I", "dtb", "-o", output_dts_file_name])
1176 .status()
1177 .unwrap()
1178 .success();
1179 assert!(success);
1180 }
1181
1182 fn new_address_space_manager(
1183 ram: &[MemoryRange],
1184 bootshim_used: MemoryRange,
1185 persisted_range: MemoryRange,
1186 parameter_range: MemoryRange,
1187 reclaim: Option<MemoryRange>,
1188 ) -> AddressSpaceManager {
1189 let ram = ram
1190 .iter()
1191 .cloned()
1192 .map(|range| MemoryEntry {
1193 range,
1194 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1195 vnode: 0,
1196 })
1197 .collect::<Vec<_>>();
1198 let mut address_space = AddressSpaceManager::new_const();
1199 AddressSpaceManagerBuilder::new(
1200 &mut address_space,
1201 &ram,
1202 bootshim_used,
1203 persisted_range,
1204 subtract_ranges([parameter_range], reclaim),
1205 )
1206 .init()
1207 .unwrap();
1208 address_space
1209 }
1210
1211 fn check_e820(boot_params: &boot_params, ext: &E820Ext, expected: &[(Range<u64>, u32)]) {
1212 let actual = boot_params.e820_map[..boot_params.e820_entries as usize]
1213 .iter()
1214 .chain(
1215 ext.entries
1216 .iter()
1217 .take((ext.header.len as usize) / size_of::<e820entry>()),
1218 );
1219
1220 assert_eq!(actual.clone().count(), expected.len());
1221
1222 for (actual, (expected_range, expected_type)) in actual.zip(expected.iter()) {
1223 let addr: u64 = actual.addr.into();
1224 let size: u64 = actual.size.into();
1225 let typ: u32 = actual.typ.into();
1226 assert_eq!(addr, expected_range.start);
1227 assert_eq!(size, expected_range.end - expected_range.start);
1228 assert_eq!(typ, *expected_type);
1229 }
1230 }
1231
1232 const PAGE_SIZE: u64 = 0x1000;
1233 const ONE_MB: u64 = 0x10_0000;
1234
1235 #[test]
1236 fn test_e820_basic() {
1237 let mut boot_params: boot_params = FromZeros::new_zeroed();
1239 let mut ext = FromZeros::new_zeroed();
1240 let bootshim_used = MemoryRange::try_new(ONE_MB..3 * ONE_MB).unwrap();
1241 let persisted_header_end = ONE_MB + PAGE_SIZE;
1242 let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1243 let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1244 let parameter_range = MemoryRange::try_new(2 * ONE_MB..3 * ONE_MB).unwrap();
1245 let address_space = new_address_space_manager(
1246 &[MemoryRange::new(ONE_MB..4 * ONE_MB)],
1247 bootshim_used,
1248 persisted_state,
1249 parameter_range,
1250 None,
1251 );
1252
1253 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1254
1255 check_e820(
1256 &boot_params,
1257 &ext,
1258 &[
1259 (ONE_MB..(persisted_header_end), E820_RESERVED),
1260 (persisted_header_end..persisted_end, E820_RESERVED),
1261 (persisted_end..2 * ONE_MB, E820_RAM),
1262 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1263 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1264 ],
1265 );
1266
1267 let mut boot_params: boot_params = FromZeros::new_zeroed();
1269 let mut ext = FromZeros::new_zeroed();
1270 let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1271 let persisted_header_end = ONE_MB + PAGE_SIZE;
1272 let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1273 let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1274 let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1275 let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1276 let address_space = new_address_space_manager(
1277 &[MemoryRange::new(ONE_MB..6 * ONE_MB)],
1278 bootshim_used,
1279 persisted_state,
1280 parameter_range,
1281 Some(reclaim),
1282 );
1283
1284 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1285
1286 check_e820(
1287 &boot_params,
1288 &ext,
1289 &[
1290 (ONE_MB..(persisted_header_end), E820_RESERVED),
1291 (persisted_header_end..persisted_end, E820_RESERVED),
1292 (persisted_end..2 * ONE_MB, E820_RAM),
1293 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1294 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1295 (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1296 (5 * ONE_MB..6 * ONE_MB, E820_RAM),
1297 ],
1298 );
1299
1300 let mut boot_params: boot_params = FromZeros::new_zeroed();
1302 let mut ext = FromZeros::new_zeroed();
1303 let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1304 let persisted_header_end = ONE_MB + PAGE_SIZE;
1305 let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1306 let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1307 let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1308 let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1309 let address_space = new_address_space_manager(
1310 &[
1311 MemoryRange::new(ONE_MB..4 * ONE_MB),
1312 MemoryRange::new(4 * ONE_MB..10 * ONE_MB),
1313 ],
1314 bootshim_used,
1315 persisted_state,
1316 parameter_range,
1317 Some(reclaim),
1318 );
1319
1320 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1321
1322 check_e820(
1323 &boot_params,
1324 &ext,
1325 &[
1326 (ONE_MB..(persisted_header_end), E820_RESERVED),
1327 (persisted_header_end..persisted_end, E820_RESERVED),
1328 (persisted_end..2 * ONE_MB, E820_RAM),
1329 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1330 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1331 (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1332 (5 * ONE_MB..10 * ONE_MB, E820_RAM),
1333 ],
1334 );
1335
1336 let mut boot_params: boot_params = FromZeros::new_zeroed();
1338 let mut ext = FromZeros::new_zeroed();
1339 let bootshim_used = MemoryRange::try_new(ONE_MB..5 * ONE_MB).unwrap();
1340 let persisted_header_end = ONE_MB + PAGE_SIZE;
1341 let persisted_end = ONE_MB + 4 * PAGE_SIZE;
1342 let persisted_state = MemoryRange::try_new(ONE_MB..persisted_end).unwrap();
1343 let parameter_range = MemoryRange::try_new(2 * ONE_MB..5 * ONE_MB).unwrap();
1344 let reclaim = MemoryRange::try_new(3 * ONE_MB..4 * ONE_MB).unwrap();
1345 let address_space = new_address_space_manager(
1346 &[
1347 MemoryRange::new(ONE_MB..2 * ONE_MB),
1348 MemoryRange::new(2 * ONE_MB..3 * ONE_MB),
1349 MemoryRange::new(3 * ONE_MB..4 * ONE_MB),
1350 MemoryRange::new(4 * ONE_MB..5 * ONE_MB),
1351 MemoryRange::new(5 * ONE_MB..6 * ONE_MB),
1352 MemoryRange::new(6 * ONE_MB..7 * ONE_MB),
1353 MemoryRange::new(7 * ONE_MB..8 * ONE_MB),
1354 ],
1355 bootshim_used,
1356 persisted_state,
1357 parameter_range,
1358 Some(reclaim),
1359 );
1360
1361 assert!(build_e820_map(&mut boot_params, &mut ext, &address_space).is_ok());
1362
1363 check_e820(
1364 &boot_params,
1365 &ext,
1366 &[
1367 (ONE_MB..(persisted_header_end), E820_RESERVED),
1368 (persisted_header_end..persisted_end, E820_RESERVED),
1369 (persisted_end..2 * ONE_MB, E820_RAM),
1370 (2 * ONE_MB..3 * ONE_MB, E820_RESERVED),
1371 (3 * ONE_MB..4 * ONE_MB, E820_RAM),
1372 (4 * ONE_MB..5 * ONE_MB, E820_RESERVED),
1373 (5 * ONE_MB..8 * ONE_MB, E820_RAM),
1374 ],
1375 );
1376 }
1377
1378 #[test]
1380 fn test_e820_huge() {
1381 use crate::memory::AllocationPolicy;
1382 use crate::memory::AllocationType;
1383
1384 const E820_MAX_ENTRIES_ZEROPAGE: usize = 128;
1387 const RAM_RANGES: usize = 64;
1388 const TOTAL_ALLOCATIONS: usize = 256;
1389
1390 let mut ranges = Vec::new();
1392 for i in 0..RAM_RANGES {
1393 let start = (i as u64) * 64 * ONE_MB;
1394 let end = start + 64 * ONE_MB;
1395 ranges.push(MemoryRange::new(start..end));
1396 }
1397
1398 let bootshim_used = MemoryRange::try_new(0..ONE_MB * 2).unwrap();
1399 let persisted_range = MemoryRange::try_new(0..ONE_MB).unwrap();
1400 let parameter_range = MemoryRange::try_new(ONE_MB..2 * ONE_MB).unwrap();
1401
1402 let mut address_space = {
1403 let ram = ranges
1404 .iter()
1405 .cloned()
1406 .map(|range| MemoryEntry {
1407 range,
1408 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1409 vnode: 0,
1410 })
1411 .collect::<Vec<_>>();
1412 let mut address_space = AddressSpaceManager::new_const();
1413 AddressSpaceManagerBuilder::new(
1414 &mut address_space,
1415 &ram,
1416 bootshim_used,
1417 persisted_range,
1418 core::iter::once(parameter_range),
1419 )
1420 .init()
1421 .unwrap();
1422 address_space
1423 };
1424
1425 for i in 0..TOTAL_ALLOCATIONS {
1426 let _allocated = address_space
1430 .allocate(
1431 None,
1432 ONE_MB,
1433 if i % 2 == 0 {
1434 AllocationType::GpaPool
1435 } else {
1436 AllocationType::SidecarNode
1437 },
1438 AllocationPolicy::LowMemory,
1439 )
1440 .expect("should be able to allocate sidecar node");
1441 }
1442
1443 let mut boot_params: boot_params = FromZeros::new_zeroed();
1444 let mut ext = FromZeros::new_zeroed();
1445 let total_ranges = address_space.vtl2_ranges().count();
1446
1447 let used_ext = build_e820_map(&mut boot_params, &mut ext, &address_space).unwrap();
1448
1449 assert!(used_ext, "should use extension when there are many ranges");
1451
1452 assert_eq!(boot_params.e820_entries, E820_MAX_ENTRIES_ZEROPAGE as u8);
1454
1455 let ext_entries = (ext.header.len as usize) / size_of::<e820entry>();
1457 assert_eq!(ext_entries, total_ranges - E820_MAX_ENTRIES_ZEROPAGE);
1458
1459 let total_e820_entries = boot_params.e820_entries as usize + ext_entries;
1461 assert_eq!(total_e820_entries, total_ranges);
1462 }
1463}