1use crate::common::ChunkBuf;
7use crate::common::ImportFileRegion;
8use crate::common::ReadSeek;
9use crate::cpuid::HV_PSP_CPUID_PAGE;
10use crate::importer::Aarch64Register;
11use crate::importer::BootPageAcceptance;
12use crate::importer::IgvmParameterType;
13use crate::importer::ImageLoad;
14use crate::importer::IsolationConfig;
15use crate::importer::IsolationType;
16use crate::importer::SegmentRegister;
17use crate::importer::StartupMemoryType;
18use crate::importer::TableRegister;
19use crate::importer::X86Register;
20use crate::linux::InitrdAddressType;
21use crate::linux::InitrdConfig;
22use crate::linux::InitrdInfo;
23use crate::linux::KernelInfo;
24use crate::linux::load_kernel_and_initrd_arm64;
25use aarch64defs::Cpsr64;
26use aarch64defs::IntermPhysAddrSize;
27use aarch64defs::SctlrEl1;
28use aarch64defs::TranslationBaseEl1;
29use aarch64defs::TranslationControlEl1;
30use aarch64defs::TranslationGranule0;
31use aarch64defs::TranslationGranule1;
32use hvdef::HV_PAGE_SIZE;
33use hvdef::Vtl;
34use igvm::registers::AArch64Register;
35use loader_defs::paravisor::*;
36use loader_defs::shim::ShimParamsRaw;
37use memory_range::MemoryRange;
38use page_table::aarch64::Arm64PageSize;
39use page_table::aarch64::MemoryAttributeEl1;
40use page_table::aarch64::MemoryAttributeIndirectionEl1;
41use page_table::x64::MappedRange;
42use page_table::x64::PAGE_TABLE_MAX_BYTES;
43use page_table::x64::PAGE_TABLE_MAX_COUNT;
44use page_table::x64::PageTable;
45use page_table::x64::PageTableBuilder;
46use page_table::x64::X64_LARGE_PAGE_SIZE;
47use page_table::x64::align_up_to_large_page_size;
48use page_table::x64::align_up_to_page_size;
49use page_table::x64::calculate_pde_table_count;
50use std::io::Read;
51use std::io::Seek;
52use thiserror::Error;
53use x86defs::GdtEntry;
54use x86defs::SegmentSelector;
55use x86defs::X64_BUSY_TSS_SEGMENT_ATTRIBUTES;
56use x86defs::X64_DEFAULT_CODE_SEGMENT_ATTRIBUTES;
57use x86defs::X64_DEFAULT_DATA_SEGMENT_ATTRIBUTES;
58use x86defs::cpuid::CpuidFunction;
59use zerocopy::FromZeros;
60use zerocopy::IntoBytes;
61
62#[derive(Debug)]
63pub struct Vtl0Linux<'a> {
64 pub command_line: &'a std::ffi::CString,
65 pub load_info: crate::linux::LoadInfo,
66}
67
68#[derive(Debug)]
69pub struct Vtl0Config<'a> {
70 pub supports_pcat: bool,
71 pub supports_uefi: Option<(crate::uefi::LoadInfo, Vec<u8>)>,
73 pub supports_linux: Option<Vtl0Linux<'a>>,
74}
75
76pub const HCL_SECURE_VTL: Vtl = Vtl::Vtl2;
78
79const PERSISTED_REGION_SIZE: u64 = 2 * 1024 * 1024;
81
82#[derive(Debug, Error)]
83pub enum Error {
84 #[error("memory is unaligned: {0}")]
85 MemoryUnaligned(u64),
86 #[error("command line too large: {0}")]
87 CommandLineSize(usize),
88 #[error("kernel load error")]
89 Kernel(#[source] crate::linux::Error),
90 #[error("shim load error")]
91 Shim(#[source] crate::elf::Error),
92 #[error("invalid initrd size: {0}")]
93 InvalidInitrdSize(u64),
94 #[error("memory used: {0} is greater than available")]
95 NotEnoughMemory(u64),
96 #[error("importer error")]
97 Importer(#[from] anyhow::Error),
98 #[error("failed to import initrd")]
99 ImportInitrd(#[source] crate::common::ImportFileRegionError),
100 #[error("failed to read initrd for CRC")]
101 InitrdRead(#[source] std::io::Error),
102 #[error("PageTableBuilder: {0}")]
103 PageTableBuilder(#[from] page_table::Error),
104}
105
106pub enum CommandLineType<'a> {
108 Static(&'a str),
110 HostAppendable(&'a str),
115}
116
117pub fn load_openhcl_x64<F>(
124 importer: &mut dyn ImageLoad<X86Register>,
125 kernel_image: &mut F,
126 shim: &mut F,
127 sidecar: Option<&mut F>,
128 command_line: CommandLineType<'_>,
129 mut initrd: Option<(&mut dyn ReadSeek, u64)>,
130 memory_page_base: Option<u64>,
131 memory_page_count: u64,
132 vtl0_config: Vtl0Config<'_>,
133) -> Result<(), Error>
134where
135 F: Read + Seek,
136{
137 let IsolationConfig {
138 isolation_type,
139 paravisor_present,
140 shared_gpa_boundary_bits,
141 } = importer.isolation_config();
142
143 let with_relocation = memory_page_base.is_none() && isolation_type == IsolationType::None;
145
146 let memory_start_address = memory_page_base
147 .map(|page_number| page_number * HV_PAGE_SIZE)
148 .unwrap_or(PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS);
149
150 let memory_size = memory_page_count * HV_PAGE_SIZE;
151
152 if !memory_start_address.is_multiple_of(X64_LARGE_PAGE_SIZE) {
172 return Err(Error::MemoryUnaligned(memory_start_address));
173 }
174
175 if !memory_size.is_multiple_of(X64_LARGE_PAGE_SIZE) {
176 return Err(Error::MemoryUnaligned(memory_size));
177 }
178
179 importer.verify_startup_memory_available(
182 memory_start_address / HV_PAGE_SIZE,
183 memory_page_count,
184 if paravisor_present {
185 StartupMemoryType::Vtl2ProtectableRam
186 } else {
187 StartupMemoryType::Ram
188 },
189 )?;
190
191 let kernel_acceptance = match isolation_type {
192 IsolationType::Snp | IsolationType::Tdx => BootPageAcceptance::Shared,
193 _ => BootPageAcceptance::Exclusive,
194 };
195
196 let mut offset = memory_start_address;
197
198 let persisted_region_base = offset;
202 let persisted_region_size = PERSISTED_REGION_SIZE;
203 offset += persisted_region_size;
204
205 let bounce_buffer = if matches!(isolation_type, IsolationType::Snp | IsolationType::Tdx) {
214 let bounce_buffer_gpa = offset;
215 assert_eq!(bounce_buffer_gpa % X64_LARGE_PAGE_SIZE, 0);
216 let range = MemoryRange::new(bounce_buffer_gpa..bounce_buffer_gpa + X64_LARGE_PAGE_SIZE);
217
218 offset += range.len();
219 Some(range)
220 } else {
221 None
222 };
223
224 tracing::trace!(offset, "loading the kernel");
225
226 let load_info = crate::elf::load_static_elf(
235 importer,
236 kernel_image,
237 offset,
238 0,
239 true,
240 kernel_acceptance,
241 "underhill-kernel",
242 )
243 .map_err(|e| Error::Kernel(crate::linux::Error::ElfLoader(e)))?;
244 tracing::trace!("Kernel loaded at {load_info:x?}");
245 let crate::elf::LoadInfo {
246 minimum_address_used: _min_addr,
247 next_available_address: mut offset,
248 entrypoint: kernel_entrypoint,
249 } = load_info;
250
251 assert_eq!(offset & (HV_PAGE_SIZE - 1), 0);
252
253 let (sidecar_size, sidecar_entrypoint) = if let Some(sidecar) = sidecar {
255 offset = align_up_to_large_page_size(offset);
257
258 let load_info = crate::elf::load_static_elf(
259 importer,
260 sidecar,
261 0,
262 offset,
263 false,
264 BootPageAcceptance::Exclusive,
265 "sidecar-kernel",
266 )
267 .map_err(|e| Error::Kernel(crate::linux::Error::ElfLoader(e)))?;
268
269 (
270 load_info.next_available_address - offset,
271 load_info.entrypoint,
272 )
273 } else {
274 (0, 0)
275 };
276
277 let sidecar_base = offset;
278 offset += sidecar_size;
279
280 let load_info = crate::elf::load_static_elf(
281 importer,
282 shim,
283 0,
284 offset,
285 false,
286 BootPageAcceptance::Exclusive,
287 "underhill-boot-shim",
288 )
289 .map_err(Error::Shim)?;
290 tracing::trace!("The boot shim loaded at {load_info:x?}");
291 let crate::elf::LoadInfo {
292 minimum_address_used: shim_base_addr,
293 next_available_address: mut offset,
294 entrypoint: shim_entry_address,
295 } = load_info;
296
297 let mut buf = ChunkBuf::new();
299 let initrd_crc = if let Some((ref mut initrd_file, initrd_len)) = initrd {
300 buf.crc32(*initrd_file, initrd_len)
301 .map_err(Error::InitrdRead)?
302 } else {
303 crc32fast::hash(&[])
304 };
305
306 let ramdisk = if let Some((initrd_file, initrd_len)) = initrd {
308 let initrd_base = offset;
309 let initrd_size = align_up_to_page_size(initrd_len);
310
311 buf.import_file_region(
312 importer,
313 ImportFileRegion {
314 file: initrd_file,
315 file_offset: 0,
316 file_length: initrd_len,
317 gpa: initrd_base,
318 memory_length: initrd_len,
319 acceptance: kernel_acceptance,
320 tag: "underhill-initrd",
321 },
322 )
323 .map_err(Error::ImportInitrd)?;
324
325 offset += initrd_size;
326 Some((initrd_base, initrd_len))
327 } else {
328 None
329 };
330
331 let gdt_base_address = offset;
332 let gdt_size = HV_PAGE_SIZE;
333 offset += gdt_size;
334
335 let boot_params_base = offset;
336 let boot_params_size = HV_PAGE_SIZE;
337
338 offset += boot_params_size;
339
340 let cmdline_base = offset;
341 let (cmdline, policy) = match command_line {
342 CommandLineType::Static(val) => (val, CommandLinePolicy::STATIC),
343 CommandLineType::HostAppendable(val) => (val, CommandLinePolicy::APPEND_CHOSEN),
344 };
345
346 if cmdline.len() > COMMAND_LINE_SIZE {
347 return Err(Error::CommandLineSize(cmdline.len()));
348 }
349
350 let mut static_command_line = [0; COMMAND_LINE_SIZE];
351 static_command_line[..cmdline.len()].copy_from_slice(cmdline.as_bytes());
352 let paravisor_command_line = ParavisorCommandLine {
353 policy,
354 static_command_line_len: cmdline.len() as u16,
355 static_command_line,
356 };
357
358 importer.import_pages(
359 cmdline_base / HV_PAGE_SIZE,
360 1,
361 "underhill-command-line",
362 BootPageAcceptance::Exclusive,
363 paravisor_command_line.as_bytes(),
364 )?;
365
366 offset += HV_PAGE_SIZE;
367
368 let reserved_region_size = PARAVISOR_RESERVED_VTL2_PAGE_COUNT_MAX * HV_PAGE_SIZE;
370 let reserved_region_start = offset;
371 offset += reserved_region_size;
372
373 tracing::debug!(reserved_region_start);
374
375 let parameter_region_size = PARAVISOR_VTL2_CONFIG_REGION_PAGE_COUNT_MAX * HV_PAGE_SIZE;
376 let parameter_region_start = offset;
377 offset += parameter_region_size;
378
379 tracing::debug!(parameter_region_start);
380
381 let bootshim_log_size = HV_PAGE_SIZE * 2;
384 let bootshim_log_start = offset;
385 offset += bootshim_log_size;
386
387 importer.import_pages(
388 bootshim_log_start / HV_PAGE_SIZE,
389 bootshim_log_size / HV_PAGE_SIZE,
390 "ohcl-boot-shim-log-buffer",
391 BootPageAcceptance::Exclusive,
392 &[],
393 )?;
394
395 let heap_start = offset;
402 let heap_size = 16 * HV_PAGE_SIZE;
403 importer.import_pages(
404 heap_start / HV_PAGE_SIZE,
405 heap_size / HV_PAGE_SIZE,
406 "ohcl-boot-shim-heap",
407 BootPageAcceptance::Exclusive,
408 &[],
409 )?;
410 offset += heap_size;
411
412 let end_of_underhill_mem = offset;
414
415 let local_map = match isolation_type {
431 IsolationType::Snp | IsolationType::Tdx => {
432 Some((PARAVISOR_LOCAL_MAP_VA, PARAVISOR_LOCAL_MAP_SIZE))
433 }
434 _ => None,
435 };
436
437 let page_table_base_page_count = 5;
438 let page_table_dynamic_page_count = {
439 calculate_pde_table_count(memory_start_address, memory_size) * 2
441 + local_map.map_or(0, |v| calculate_pde_table_count(v.0, v.1))
442 };
443 let page_table_isolation_page_count = match isolation_type {
444 IsolationType::Tdx => {
445 3
448 }
449 _ => 0,
450 };
451 let page_table_page_count = page_table_base_page_count
452 + page_table_dynamic_page_count
453 + page_table_isolation_page_count;
454 let page_table_region_size = HV_PAGE_SIZE * page_table_page_count;
455 let page_table_region_start = offset;
456 offset += page_table_region_size;
457
458 tracing::debug!(page_table_region_start, page_table_region_size);
459
460 let mut ranges: Vec<MappedRange> = Vec::new();
462
463 ranges.push(MappedRange::new(
464 memory_start_address,
465 memory_start_address + memory_size,
466 ));
467
468 if let Some((local_map_start, size)) = local_map {
469 ranges.push(MappedRange::new(local_map_start, local_map_start + size));
470 }
471
472 if isolation_type == IsolationType::Tdx {
473 const RESET_VECTOR_ADDR: u64 = 0xffff_f000;
474 ranges.push(MappedRange::new(
475 RESET_VECTOR_ADDR,
476 RESET_VECTOR_ADDR + page_table::x64::X64_PAGE_SIZE,
477 ));
478 }
479
480 ranges.sort_by_key(|r| r.start());
481
482 let mut page_table_work_buffer: Vec<PageTable> =
484 vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
485 let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
486 let mut page_table_builder = PageTableBuilder::new(
487 page_table_region_start,
488 page_table_work_buffer.as_mut_slice(),
489 page_table.as_mut_slice(),
490 ranges.as_slice(),
491 )?;
492
493 if isolation_type == IsolationType::Snp {
494 page_table_builder = page_table_builder.with_confidential_bit(51);
495 }
496
497 let page_table = page_table_builder.build()?;
498
499 assert!((page_table.len() as u64).is_multiple_of(HV_PAGE_SIZE));
500 let page_table_page_base = page_table_region_start / HV_PAGE_SIZE;
501 assert!(page_table.len() as u64 <= page_table_region_size);
502 let offset = offset;
503
504 if with_relocation {
505 importer.relocation_region(
507 memory_start_address,
508 end_of_underhill_mem - memory_start_address,
509 X64_LARGE_PAGE_SIZE,
510 PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS,
511 1 << 48,
512 true,
513 true,
514 0, )?;
516
517 importer.page_table_relocation(
519 page_table_region_start,
520 page_table_region_size / HV_PAGE_SIZE,
521 page_table.len() as u64 / HV_PAGE_SIZE,
522 0,
523 )?;
524 }
525
526 if offset > memory_start_address + memory_size {
528 return Err(Error::NotEnoughMemory(offset - memory_start_address));
529 }
530
531 let (initrd_base, initrd_size) = ramdisk.unwrap_or((0, 0));
532 let calculate_shim_offset = |addr: u64| addr.wrapping_sub(shim_base_addr) as i64;
534 let shim_params = ShimParamsRaw {
535 kernel_entry_offset: calculate_shim_offset(kernel_entrypoint),
536 cmdline_offset: calculate_shim_offset(cmdline_base),
537 initrd_offset: calculate_shim_offset(initrd_base),
538 initrd_size,
539 initrd_crc,
540 supported_isolation_type: match isolation_type {
541 IsolationType::None | IsolationType::Vbs => {
545 loader_defs::shim::SupportedIsolationType::VBS
546 }
547 IsolationType::Snp => loader_defs::shim::SupportedIsolationType::SNP,
548 IsolationType::Tdx => loader_defs::shim::SupportedIsolationType::TDX,
549 },
550 memory_start_offset: calculate_shim_offset(memory_start_address),
551 memory_size,
552 parameter_region_offset: calculate_shim_offset(parameter_region_start),
553 parameter_region_size,
554 vtl2_reserved_region_offset: calculate_shim_offset(reserved_region_start),
555 vtl2_reserved_region_size: reserved_region_size,
556 sidecar_offset: calculate_shim_offset(sidecar_base),
557 sidecar_size,
558 sidecar_entry_offset: calculate_shim_offset(sidecar_entrypoint),
559 used_start: calculate_shim_offset(memory_start_address),
560 used_end: calculate_shim_offset(offset),
561 bounce_buffer_start: bounce_buffer.map_or(0, |r| calculate_shim_offset(r.start())),
562 bounce_buffer_size: bounce_buffer.map_or(0, |r| r.len()),
563 log_buffer_start: calculate_shim_offset(bootshim_log_start),
564 log_buffer_size: bootshim_log_size,
565 heap_start_offset: calculate_shim_offset(heap_start),
566 heap_size,
567 persisted_state_region_offset: calculate_shim_offset(persisted_region_base),
568 persisted_state_region_size: persisted_region_size,
569 };
570
571 tracing::debug!(boot_params_base, "shim gpa");
572
573 importer
574 .import_pages(
575 boot_params_base / HV_PAGE_SIZE,
576 boot_params_size / HV_PAGE_SIZE,
577 "underhill-shim-params",
578 BootPageAcceptance::Exclusive,
579 shim_params.as_bytes(),
580 )
581 .map_err(Error::Importer)?;
582
583 importer.import_pages(
584 page_table_page_base,
585 page_table_page_count,
586 "underhill-page-tables",
587 BootPageAcceptance::Exclusive,
588 page_table,
589 )?;
590
591 let default_data_attributes: u16 = X64_DEFAULT_DATA_SEGMENT_ATTRIBUTES.into();
604 let default_code64_attributes: u16 = X64_DEFAULT_CODE_SEGMENT_ATTRIBUTES.into();
605 let gdt = [
606 GdtEntry::new_zeroed(),
608 GdtEntry::new_zeroed(),
609 GdtEntry {
611 limit_low: 0xffff,
612 attr_low: default_code64_attributes as u8,
613 attr_high: (default_code64_attributes >> 8) as u8,
614 ..GdtEntry::new_zeroed()
615 },
616 GdtEntry {
618 limit_low: 0xffff,
619 attr_low: default_data_attributes as u8,
620 attr_high: (default_data_attributes >> 8) as u8,
621 ..GdtEntry::new_zeroed()
622 },
623 ];
624
625 const LINEAR_CODE64_DESCRIPTOR_INDEX: usize = 2;
626 const LINEAR_DATA_DESCRIPTOR_INDEX: usize = 3;
627 const RPL: u8 = 0x00; let linear_code64_descriptor_selector =
630 SegmentSelector::from_gdt_index(LINEAR_CODE64_DESCRIPTOR_INDEX as u16, RPL);
631 let linear_data_descriptor_selector =
632 SegmentSelector::from_gdt_index(LINEAR_DATA_DESCRIPTOR_INDEX as u16, RPL);
633
634 importer.import_pages(
635 gdt_base_address / HV_PAGE_SIZE,
636 gdt_size / HV_PAGE_SIZE,
637 "underhill-gdt",
638 BootPageAcceptance::Exclusive,
639 gdt.as_bytes(),
640 )?;
641
642 let mut import_reg = |register| {
643 importer
644 .import_vp_register(register)
645 .map_err(Error::Importer)
646 };
647
648 import_reg(X86Register::Gdtr(TableRegister {
650 base: gdt_base_address,
651 limit: (size_of_val(&gdt) - 1) as u16,
652 }))?;
653
654 let ds = SegmentRegister {
655 selector: linear_data_descriptor_selector.into_bits(),
656 base: 0,
657 limit: 0xffffffff,
658 attributes: default_data_attributes,
659 };
660 import_reg(X86Register::Ds(ds))?;
661 import_reg(X86Register::Es(ds))?;
662 import_reg(X86Register::Fs(ds))?;
663 import_reg(X86Register::Gs(ds))?;
664 import_reg(X86Register::Ss(ds))?;
665
666 let cs = SegmentRegister {
667 selector: linear_code64_descriptor_selector.into_bits(),
668 base: 0,
669 limit: 0xffffffff,
670 attributes: default_code64_attributes,
671 };
672 import_reg(X86Register::Cs(cs))?;
673
674 import_reg(X86Register::Tr(SegmentRegister {
680 selector: 0x0000,
681 base: 0x00000000,
682 limit: 0x0000FFFF,
683 attributes: X64_BUSY_TSS_SEGMENT_ATTRIBUTES.into(),
684 }))?;
685
686 import_reg(X86Register::Cr0(
691 x86defs::X64_CR0_PG | x86defs::X64_CR0_PE | x86defs::X64_CR0_NE,
692 ))?;
693
694 import_reg(X86Register::Cr3(page_table_region_start))?;
696
697 import_reg(X86Register::Cr4(
699 x86defs::X64_CR4_PAE | x86defs::X64_CR4_MCE | x86defs::X64_CR4_OSXSAVE,
700 ))?;
701
702 import_reg(X86Register::Efer(
704 x86defs::X64_EFER_LMA | x86defs::X64_EFER_LME | x86defs::X64_EFER_NXE,
705 ))?;
706
707 import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?;
709
710 let relative_boot_params_base = boot_params_base - shim_base_addr;
713 import_reg(X86Register::Rsi(relative_boot_params_base))?;
714
715 import_reg(X86Register::Rip(shim_entry_address))?;
717
718 let config_region_page_base = parameter_region_start / HV_PAGE_SIZE;
720
721 let slit_page_base = config_region_page_base + PARAVISOR_CONFIG_SLIT_PAGE_INDEX;
723 let slit_parameter_area = importer.create_parameter_area(
724 slit_page_base,
725 PARAVISOR_CONFIG_SLIT_SIZE_PAGES as u32,
726 "underhill-slit",
727 )?;
728 importer.import_parameter(slit_parameter_area, 0, IgvmParameterType::Slit)?;
729
730 let pptt_page_base = config_region_page_base + PARAVISOR_CONFIG_PPTT_PAGE_INDEX;
732 let pptt_parameter_area = importer.create_parameter_area(
733 pptt_page_base,
734 PARAVISOR_CONFIG_PPTT_SIZE_PAGES as u32,
735 "underhill-pptt",
736 )?;
737 importer.import_parameter(pptt_parameter_area, 0, IgvmParameterType::Pptt)?;
738
739 let dt_page_base = config_region_page_base + PARAVISOR_CONFIG_DEVICE_TREE_PAGE_INDEX;
741 let dt_parameter_area = importer.create_parameter_area(
742 dt_page_base,
743 PARAVISOR_CONFIG_DEVICE_TREE_SIZE_PAGES as u32,
744 "underhill-device-tree",
745 )?;
746 importer.import_parameter(dt_parameter_area, 0, IgvmParameterType::DeviceTree)?;
747
748 if isolation_type == IsolationType::Snp {
749 let reserved_region_page_base = reserved_region_start / HV_PAGE_SIZE;
750 let secrets_page_base: u64 =
751 reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_SECRETS_PAGE_INDEX;
752 importer.import_pages(
753 secrets_page_base,
754 PARAVISOR_RESERVED_VTL2_SNP_SECRETS_SIZE_PAGES,
755 "underhill-snp-secrets-page",
756 BootPageAcceptance::SecretsPage,
757 &[],
758 )?;
759
760 let cpuid_page = create_snp_cpuid_page();
761 let cpuid_page_base =
762 reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_CPUID_PAGE_INDEX;
763 importer.import_pages(
764 cpuid_page_base,
765 1,
766 "underhill-snp-cpuid-page",
767 BootPageAcceptance::CpuidPage,
768 cpuid_page.as_bytes(),
769 )?;
770
771 importer.import_pages(
772 cpuid_page_base + 1,
773 1,
774 "underhill-snp-cpuid-extended-state-page",
775 BootPageAcceptance::CpuidExtendedStatePage,
776 &[],
777 )?;
778
779 let vmsa_page_base =
780 reserved_region_page_base + PARAVISOR_RESERVED_VTL2_SNP_VMSA_PAGE_INDEX;
781 importer.set_vp_context_page(vmsa_page_base)?;
782 }
783
784 let mut free_page = 1;
787 let mut measured_config = ParavisorMeasuredVtl0Config {
788 magic: ParavisorMeasuredVtl0Config::MAGIC,
789 ..FromZeros::new_zeroed()
790 };
791
792 let Vtl0Config {
793 supports_pcat,
794 supports_uefi,
795 supports_linux,
796 } = vtl0_config;
797
798 if supports_pcat {
799 measured_config.supported_vtl0.set_pcat_supported(true);
800 }
801
802 if let Some((uefi, vp_context)) = &supports_uefi {
803 measured_config.supported_vtl0.set_uefi_supported(true);
804 let vp_context_page = free_page;
805 free_page += 1;
806 measured_config.uefi_info = UefiInfo {
807 firmware: PageRegionDescriptor {
808 base_page_number: uefi.firmware_base / HV_PAGE_SIZE,
809 page_count: uefi.total_size / HV_PAGE_SIZE,
810 },
811 vtl0_vp_context: PageRegionDescriptor {
812 base_page_number: vp_context_page,
813 page_count: 1,
814 },
815 };
816
817 importer.import_pages(
819 vp_context_page,
820 1,
821 "openhcl-uefi-vp-context",
822 BootPageAcceptance::Exclusive,
823 vp_context,
824 )?;
825 }
826
827 if let Some(linux) = supports_linux {
828 measured_config
829 .supported_vtl0
830 .set_linux_direct_supported(true);
831
832 let kernel_region = PageRegionDescriptor::new(
833 linux.load_info.kernel.gpa / HV_PAGE_SIZE,
834 align_up_to_page_size(linux.load_info.kernel.size) / HV_PAGE_SIZE,
835 );
836
837 let (initrd_region, initrd_size) = match linux.load_info.initrd {
838 Some(info) => {
839 if info.gpa % HV_PAGE_SIZE != 0 {
840 return Err(Error::MemoryUnaligned(info.gpa));
841 }
842 (
843 PageRegionDescriptor::new(
845 info.gpa / HV_PAGE_SIZE,
846 align_up_to_page_size(info.size) / HV_PAGE_SIZE,
847 ),
848 info.size,
849 )
850 }
851 None => (PageRegionDescriptor::EMPTY, 0),
852 };
853
854 let command_line_page = free_page;
855 importer
859 .import_pages(
860 command_line_page,
861 1,
862 "underhill-vtl0-linux-command-line",
863 BootPageAcceptance::Exclusive,
864 linux.command_line.as_bytes_with_nul(),
865 )
866 .map_err(Error::Importer)?;
867 let command_line = PageRegionDescriptor::new(command_line_page, 1);
868
869 measured_config.linux_info = LinuxInfo {
870 kernel_region,
871 kernel_entrypoint: linux.load_info.kernel.entrypoint,
872 initrd_region,
873 initrd_size,
874 command_line,
875 };
876 }
877
878 importer
879 .import_pages(
880 PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_X64,
881 1,
882 "underhill-measured-config",
883 BootPageAcceptance::Exclusive,
884 measured_config.as_bytes(),
885 )
886 .map_err(Error::Importer)?;
887
888 let vtl2_measured_config = ParavisorMeasuredVtl2Config {
889 magic: ParavisorMeasuredVtl2Config::MAGIC,
890 vtom_offset_bit: shared_gpa_boundary_bits.unwrap_or(0),
891 padding: [0; 7],
892 };
893
894 importer
895 .import_pages(
896 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_INDEX,
897 PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES,
898 "underhill-vtl2-measured-config",
899 BootPageAcceptance::Exclusive,
900 vtl2_measured_config.as_bytes(),
901 )
902 .map_err(Error::Importer)?;
903
904 let imported_region_base =
905 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_ACCEPTED_MEMORY_PAGE_INDEX;
906
907 importer.set_imported_regions_config_page(imported_region_base);
908 Ok(())
909}
910
911fn create_snp_cpuid_page() -> HV_PSP_CPUID_PAGE {
913 let mut cpuid_page = HV_PSP_CPUID_PAGE::default();
914
915 for (i, required_leaf) in crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR
921 .iter()
922 .enumerate()
923 {
924 let entry = &mut cpuid_page.cpuid_leaf_info[i];
925 entry.eax_in = required_leaf.eax;
926 entry.ecx_in = required_leaf.ecx;
927 if required_leaf.eax == CpuidFunction::ExtendedStateEnumeration.0 {
928 entry.xfem_in = 1;
929 }
930 cpuid_page.count += 1;
931 }
932
933 cpuid_page
934}
935
936pub fn load_openhcl_arm64<F>(
943 importer: &mut dyn ImageLoad<Aarch64Register>,
944 kernel_image: &mut F,
945 shim: &mut F,
946 command_line: CommandLineType<'_>,
947 mut initrd: Option<(&mut dyn ReadSeek, u64)>,
948 memory_page_base: Option<u64>,
949 memory_page_count: u64,
950 vtl0_config: Vtl0Config<'_>,
951) -> Result<(), Error>
952where
953 F: Read + Seek,
954{
955 let Vtl0Config {
956 supports_pcat,
957 supports_uefi,
958 supports_linux,
959 } = vtl0_config;
960
961 assert!(!supports_pcat);
962 assert!(supports_uefi.is_some() || supports_linux.is_some());
963
964 let paravisor_present = importer.isolation_config().paravisor_present;
965
966 let with_relocation = memory_page_base.is_none();
968
969 let memory_start_address = memory_page_base
970 .map(|page_number| page_number * HV_PAGE_SIZE)
971 .unwrap_or(PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS);
972
973 let memory_size = memory_page_count * HV_PAGE_SIZE;
974
975 if !memory_start_address.is_multiple_of(u64::from(Arm64PageSize::Large)) {
977 return Err(Error::MemoryUnaligned(memory_start_address));
978 }
979
980 if !memory_size.is_multiple_of(u64::from(Arm64PageSize::Large)) {
981 return Err(Error::MemoryUnaligned(memory_size));
982 }
983
984 importer.verify_startup_memory_available(
987 memory_start_address / HV_PAGE_SIZE,
988 memory_page_count,
989 if paravisor_present {
990 StartupMemoryType::Vtl2ProtectableRam
991 } else {
992 StartupMemoryType::Ram
993 },
994 )?;
995
996 let mut next_addr = memory_start_address;
997
998 let persisted_region_base = next_addr;
1002 let persisted_region_size = PERSISTED_REGION_SIZE;
1003 next_addr += persisted_region_size;
1004
1005 tracing::trace!(next_addr, "loading the kernel");
1006
1007 let initrd_crc = if let Some((ref mut initrd_file, initrd_len)) = initrd {
1009 ChunkBuf::new()
1010 .crc32(*initrd_file, initrd_len)
1011 .map_err(Error::InitrdRead)?
1012 } else {
1013 crc32fast::hash(&[])
1014 };
1015
1016 let initrd_address_type = InitrdAddressType::AfterKernel;
1020 let initrd_config = initrd.map(|(initrd_file, initrd_size)| InitrdConfig {
1021 initrd_address: initrd_address_type,
1022 initrd: initrd_file,
1023 size: initrd_size,
1024 });
1025 let device_tree_blob = None;
1026 let crate::linux::LoadInfo {
1027 kernel:
1028 KernelInfo {
1029 gpa: kernel_base,
1030 size: kernel_size,
1031 entrypoint: kernel_entry_point,
1032 },
1033 initrd: initrd_info,
1034 dtb,
1035 ..
1036 } = load_kernel_and_initrd_arm64(
1037 importer,
1038 kernel_image,
1039 next_addr,
1040 initrd_config,
1041 device_tree_blob,
1042 )
1043 .map_err(Error::Kernel)?;
1044
1045 assert!(
1046 dtb.is_none(),
1047 "DeviceTree is generated dynamically by the boot shim."
1048 );
1049
1050 tracing::trace!(kernel_base, "kernel loaded");
1051
1052 let InitrdInfo {
1053 gpa: initrd_gpa,
1054 size: initrd_size,
1055 } = if let Some(initrd_info) = initrd_info {
1056 assert!(initrd_address_type == InitrdAddressType::AfterKernel);
1057 next_addr = initrd_info.gpa + initrd_info.size;
1058 initrd_info
1059 } else {
1060 next_addr = kernel_base + kernel_size;
1061 InitrdInfo { gpa: 0, size: 0 }
1062 };
1063
1064 next_addr = align_up_to_page_size(next_addr);
1065
1066 tracing::trace!(next_addr, "loading the boot shim");
1067
1068 let crate::elf::LoadInfo {
1069 minimum_address_used: shim_base_addr,
1070 next_available_address: mut next_addr,
1071 entrypoint: shim_entry_point,
1072 } = crate::elf::load_static_elf(
1073 importer,
1074 shim,
1075 0,
1076 next_addr,
1077 false,
1078 BootPageAcceptance::Exclusive,
1079 "underhill-boot-shim",
1080 )
1081 .map_err(Error::Shim)?;
1082
1083 tracing::trace!(shim_base_addr, "boot shim loaded");
1084
1085 tracing::trace!(next_addr, "loading the command line");
1086
1087 let cmdline_base = next_addr;
1088 let (cmdline, policy) = match command_line {
1089 CommandLineType::Static(val) => (val, CommandLinePolicy::STATIC),
1090 CommandLineType::HostAppendable(val) => (val, CommandLinePolicy::APPEND_CHOSEN),
1091 };
1092
1093 if cmdline.len() > COMMAND_LINE_SIZE {
1094 return Err(Error::CommandLineSize(cmdline.len()));
1095 }
1096
1097 let mut static_command_line = [0; COMMAND_LINE_SIZE];
1098 static_command_line[..cmdline.len()].copy_from_slice(cmdline.as_bytes());
1099 let paravisor_command_line = ParavisorCommandLine {
1100 policy,
1101 static_command_line_len: cmdline.len() as u16,
1102 static_command_line,
1103 };
1104
1105 importer.import_pages(
1106 cmdline_base / HV_PAGE_SIZE,
1107 1,
1108 "underhill-command-line",
1109 BootPageAcceptance::Exclusive,
1110 paravisor_command_line.as_bytes(),
1111 )?;
1112
1113 next_addr += HV_PAGE_SIZE;
1114
1115 tracing::trace!(next_addr, "loading the boot shim parameters");
1116
1117 let shim_params_base = next_addr;
1118 let shim_params_size = HV_PAGE_SIZE;
1119
1120 next_addr += shim_params_size;
1121
1122 let parameter_region_size = PARAVISOR_VTL2_CONFIG_REGION_PAGE_COUNT_MAX * HV_PAGE_SIZE;
1123 let parameter_region_start = next_addr;
1124 next_addr += parameter_region_size;
1125
1126 tracing::debug!(parameter_region_start);
1127
1128 let bootshim_log_size = HV_PAGE_SIZE * 2;
1130 let bootshim_log_start = next_addr;
1131 next_addr += bootshim_log_size;
1132
1133 importer.import_pages(
1134 bootshim_log_start / HV_PAGE_SIZE,
1135 bootshim_log_size / HV_PAGE_SIZE,
1136 "ohcl-boot-shim-log-buffer",
1137 BootPageAcceptance::Exclusive,
1138 &[],
1139 )?;
1140
1141 let heap_start = next_addr;
1148 let heap_size = 16 * HV_PAGE_SIZE;
1149 importer.import_pages(
1150 heap_start / HV_PAGE_SIZE,
1151 heap_size / HV_PAGE_SIZE,
1152 "ohcl-boot-shim-heap",
1153 BootPageAcceptance::Exclusive,
1154 &[],
1155 )?;
1156 next_addr += heap_size;
1157
1158 let end_of_underhill_mem = next_addr;
1160
1161 let page_table_base_page_count = 5;
1164 let page_table_dynamic_page_count = 2 * page_table_base_page_count;
1165 let page_table_page_count = page_table_base_page_count + page_table_dynamic_page_count;
1166 let page_table_region_size = HV_PAGE_SIZE * page_table_page_count;
1167 let page_table_region_start = next_addr;
1168 next_addr += page_table_region_size;
1169
1170 tracing::debug!(page_table_region_start, page_table_region_size);
1171
1172 let next_addr = next_addr;
1173
1174 if next_addr > memory_start_address + memory_size {
1176 return Err(Error::NotEnoughMemory(next_addr - memory_start_address));
1177 }
1178
1179 let calculate_shim_offset = |addr: u64| -> i64 { addr.wrapping_sub(shim_base_addr) as i64 };
1181 let shim_params = ShimParamsRaw {
1182 kernel_entry_offset: calculate_shim_offset(kernel_entry_point),
1183 cmdline_offset: calculate_shim_offset(cmdline_base),
1184 initrd_offset: calculate_shim_offset(initrd_gpa),
1185 initrd_size,
1186 initrd_crc,
1187 supported_isolation_type: match importer.isolation_config().isolation_type {
1188 IsolationType::None | IsolationType::Vbs => {
1189 loader_defs::shim::SupportedIsolationType::VBS
1190 }
1191 _ => panic!("only None and VBS are supported for ARM64"),
1192 },
1193 memory_start_offset: calculate_shim_offset(memory_start_address),
1194 memory_size,
1195 parameter_region_offset: calculate_shim_offset(parameter_region_start),
1196 parameter_region_size,
1197 vtl2_reserved_region_offset: 0,
1198 vtl2_reserved_region_size: 0,
1199 sidecar_offset: 0,
1200 sidecar_size: 0,
1201 sidecar_entry_offset: 0,
1202 used_start: calculate_shim_offset(memory_start_address),
1203 used_end: calculate_shim_offset(next_addr),
1204 bounce_buffer_start: 0,
1205 bounce_buffer_size: 0,
1206 log_buffer_start: calculate_shim_offset(bootshim_log_start),
1207 log_buffer_size: bootshim_log_size,
1208 heap_start_offset: calculate_shim_offset(heap_start),
1209 heap_size,
1210 persisted_state_region_offset: calculate_shim_offset(persisted_region_base),
1211 persisted_state_region_size: persisted_region_size,
1212 };
1213
1214 importer
1215 .import_pages(
1216 shim_params_base / HV_PAGE_SIZE,
1217 shim_params_size / HV_PAGE_SIZE,
1218 "underhill-shim-params",
1219 BootPageAcceptance::Exclusive,
1220 shim_params.as_bytes(),
1221 )
1222 .map_err(Error::Importer)?;
1223
1224 let mut measured_config = ParavisorMeasuredVtl0Config {
1225 magic: ParavisorMeasuredVtl0Config::MAGIC,
1226 ..FromZeros::new_zeroed()
1227 };
1228
1229 if let Some((uefi, vp_context)) = &supports_uefi {
1230 measured_config.supported_vtl0.set_uefi_supported(true);
1231 let vp_context_page = PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_AARCH64 + 1;
1232 measured_config.uefi_info = UefiInfo {
1233 firmware: PageRegionDescriptor {
1234 base_page_number: uefi.firmware_base / HV_PAGE_SIZE,
1235 page_count: uefi.total_size / HV_PAGE_SIZE,
1236 },
1237 vtl0_vp_context: PageRegionDescriptor {
1238 base_page_number: vp_context_page,
1239 page_count: 1,
1240 },
1241 };
1242
1243 importer.import_pages(
1245 vp_context_page,
1246 1,
1247 "openhcl-uefi-vp-context",
1248 BootPageAcceptance::Exclusive,
1249 vp_context,
1250 )?;
1251 }
1252
1253 importer
1254 .import_pages(
1255 PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_AARCH64,
1256 1,
1257 "underhill-measured-config",
1258 BootPageAcceptance::Exclusive,
1259 measured_config.as_bytes(),
1260 )
1261 .map_err(Error::Importer)?;
1262
1263 tracing::trace!(page_table_region_start, "loading the page tables");
1264
1265 let memory_attribute_indirection = MemoryAttributeIndirectionEl1([
1266 MemoryAttributeEl1::Device_nGnRnE,
1267 MemoryAttributeEl1::Normal_NonCacheable,
1268 MemoryAttributeEl1::Normal_WriteThrough,
1269 MemoryAttributeEl1::Normal_WriteBack,
1270 MemoryAttributeEl1::Device_nGnRnE,
1271 MemoryAttributeEl1::Device_nGnRnE,
1272 MemoryAttributeEl1::Device_nGnRnE,
1273 MemoryAttributeEl1::Device_nGnRnE,
1274 ]);
1275 let mut page_tables: Vec<u8> = vec![0; page_table_region_size as usize];
1276 let page_tables = page_table::aarch64::build_identity_page_tables_aarch64(
1277 page_table_region_start,
1278 memory_start_address,
1279 memory_size,
1280 memory_attribute_indirection,
1281 page_tables.as_mut_slice(),
1282 );
1283 assert!((page_tables.len() as u64).is_multiple_of(HV_PAGE_SIZE));
1284 let page_table_page_base = page_table_region_start / HV_PAGE_SIZE;
1285 assert!(page_tables.len() as u64 <= page_table_region_size);
1286 assert!(page_table_region_size as usize > page_tables.len());
1287
1288 if with_relocation {
1289 importer.relocation_region(
1291 memory_start_address,
1292 end_of_underhill_mem - memory_start_address,
1293 Arm64PageSize::Large.into(),
1294 PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS,
1295 1 << 48,
1296 true,
1297 false,
1298 0, )?;
1300
1301 importer.page_table_relocation(
1303 page_table_region_start,
1304 page_table_region_size / HV_PAGE_SIZE,
1305 page_tables.len() as u64 / HV_PAGE_SIZE,
1306 0,
1307 )?;
1308 }
1309
1310 importer.import_pages(
1311 page_table_page_base,
1312 page_table_page_count,
1313 "underhill-page-tables",
1314 BootPageAcceptance::Exclusive,
1315 page_tables,
1316 )?;
1317
1318 tracing::trace!("Importing register state");
1319
1320 let mut import_reg = |register| {
1321 importer
1322 .import_vp_register(register)
1323 .map_err(Error::Importer)
1324 };
1325
1326 let relative_boot_params_base = shim_params_base - shim_base_addr;
1328 import_reg(AArch64Register::X0(relative_boot_params_base).into())?;
1329
1330 import_reg(AArch64Register::Pc(shim_entry_point).into())?;
1332
1333 import_reg(AArch64Register::Cpsr(Cpsr64::new().with_sp(true).with_el(1).into()).into())?;
1336
1337 import_reg(
1360 AArch64Register::SctlrEl1(
1361 SctlrEl1::new()
1362 .with_m(true)
1367 .with_c(true)
1369 .with_i(true)
1371 .with_eos(true)
1373 .with_tscxt(true)
1374 .with_eis(true)
1375 .with_span(true)
1376 .with_n_tlsmd(true)
1377 .with_lsmaoe(true)
1378 .into(),
1379 )
1380 .into(),
1381 )?;
1382
1383 import_reg(
1389 AArch64Register::TcrEl1(
1390 TranslationControlEl1::new()
1391 .with_t0sz(0x11)
1392 .with_irgn0(1)
1393 .with_orgn0(1)
1394 .with_sh0(3)
1395 .with_tg0(TranslationGranule0::TG_4KB)
1396 .with_epd1(1)
1398 .with_tg1(TranslationGranule1::TG_4KB)
1400 .with_ips(IntermPhysAddrSize::IPA_48_BITS_256_TB)
1401 .into(),
1402 )
1403 .into(),
1404 )?;
1405
1406 import_reg(AArch64Register::MairEl1(memory_attribute_indirection.into()).into())?;
1408 import_reg(
1409 AArch64Register::Ttbr0El1(
1410 TranslationBaseEl1::new()
1411 .with_baddr(page_table_region_start)
1412 .into(),
1413 )
1414 .into(),
1415 )?;
1416
1417 import_reg(AArch64Register::VbarEl1(0).into())?;
1421
1422 let config_region_page_base = parameter_region_start / HV_PAGE_SIZE;
1424
1425 let slit_page_base = config_region_page_base + PARAVISOR_CONFIG_SLIT_PAGE_INDEX;
1427 let slit_parameter_area = importer.create_parameter_area(
1428 slit_page_base,
1429 PARAVISOR_CONFIG_SLIT_SIZE_PAGES as u32,
1430 "underhill-slit",
1431 )?;
1432 importer.import_parameter(slit_parameter_area, 0, IgvmParameterType::Slit)?;
1433
1434 let pptt_page_base = config_region_page_base + PARAVISOR_CONFIG_PPTT_PAGE_INDEX;
1436 let pptt_parameter_area = importer.create_parameter_area(
1437 pptt_page_base,
1438 PARAVISOR_CONFIG_PPTT_SIZE_PAGES as u32,
1439 "underhill-pptt",
1440 )?;
1441 importer.import_parameter(pptt_parameter_area, 0, IgvmParameterType::Pptt)?;
1442
1443 let dt_page_base = config_region_page_base + PARAVISOR_CONFIG_DEVICE_TREE_PAGE_INDEX;
1445 let dt_parameter_area = importer.create_parameter_area(
1446 dt_page_base,
1447 PARAVISOR_CONFIG_DEVICE_TREE_SIZE_PAGES as u32,
1448 "underhill-device-tree",
1449 )?;
1450 importer.import_parameter(dt_parameter_area, 0, IgvmParameterType::DeviceTree)?;
1451
1452 let vtl2_measured_config = ParavisorMeasuredVtl2Config {
1453 magic: ParavisorMeasuredVtl2Config::MAGIC,
1454 vtom_offset_bit: 0,
1455 padding: [0; 7],
1456 };
1457
1458 importer
1459 .import_pages(
1460 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_INDEX,
1461 PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES,
1462 "underhill-vtl2-measured-config",
1463 BootPageAcceptance::Exclusive,
1464 vtl2_measured_config.as_bytes(),
1465 )
1466 .map_err(Error::Importer)?;
1467
1468 let imported_region_base =
1469 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_ACCEPTED_MEMORY_PAGE_INDEX;
1470
1471 importer.set_imported_regions_config_page(imported_region_base);
1472
1473 Ok(())
1474}