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 } = load_kernel_and_initrd_arm64(
1036 importer,
1037 kernel_image,
1038 next_addr,
1039 initrd_config,
1040 device_tree_blob,
1041 )
1042 .map_err(Error::Kernel)?;
1043
1044 assert!(
1045 dtb.is_none(),
1046 "DeviceTree is generated dynamically by the boot shim."
1047 );
1048
1049 tracing::trace!(kernel_base, "kernel loaded");
1050
1051 let InitrdInfo {
1052 gpa: initrd_gpa,
1053 size: initrd_size,
1054 } = if let Some(initrd_info) = initrd_info {
1055 assert!(initrd_address_type == InitrdAddressType::AfterKernel);
1056 next_addr = initrd_info.gpa + initrd_info.size;
1057 initrd_info
1058 } else {
1059 next_addr = kernel_base + kernel_size;
1060 InitrdInfo { gpa: 0, size: 0 }
1061 };
1062
1063 next_addr = align_up_to_page_size(next_addr);
1064
1065 tracing::trace!(next_addr, "loading the boot shim");
1066
1067 let crate::elf::LoadInfo {
1068 minimum_address_used: shim_base_addr,
1069 next_available_address: mut next_addr,
1070 entrypoint: shim_entry_point,
1071 } = crate::elf::load_static_elf(
1072 importer,
1073 shim,
1074 0,
1075 next_addr,
1076 false,
1077 BootPageAcceptance::Exclusive,
1078 "underhill-boot-shim",
1079 )
1080 .map_err(Error::Shim)?;
1081
1082 tracing::trace!(shim_base_addr, "boot shim loaded");
1083
1084 tracing::trace!(next_addr, "loading the command line");
1085
1086 let cmdline_base = next_addr;
1087 let (cmdline, policy) = match command_line {
1088 CommandLineType::Static(val) => (val, CommandLinePolicy::STATIC),
1089 CommandLineType::HostAppendable(val) => (val, CommandLinePolicy::APPEND_CHOSEN),
1090 };
1091
1092 if cmdline.len() > COMMAND_LINE_SIZE {
1093 return Err(Error::CommandLineSize(cmdline.len()));
1094 }
1095
1096 let mut static_command_line = [0; COMMAND_LINE_SIZE];
1097 static_command_line[..cmdline.len()].copy_from_slice(cmdline.as_bytes());
1098 let paravisor_command_line = ParavisorCommandLine {
1099 policy,
1100 static_command_line_len: cmdline.len() as u16,
1101 static_command_line,
1102 };
1103
1104 importer.import_pages(
1105 cmdline_base / HV_PAGE_SIZE,
1106 1,
1107 "underhill-command-line",
1108 BootPageAcceptance::Exclusive,
1109 paravisor_command_line.as_bytes(),
1110 )?;
1111
1112 next_addr += HV_PAGE_SIZE;
1113
1114 tracing::trace!(next_addr, "loading the boot shim parameters");
1115
1116 let shim_params_base = next_addr;
1117 let shim_params_size = HV_PAGE_SIZE;
1118
1119 next_addr += shim_params_size;
1120
1121 let parameter_region_size = PARAVISOR_VTL2_CONFIG_REGION_PAGE_COUNT_MAX * HV_PAGE_SIZE;
1122 let parameter_region_start = next_addr;
1123 next_addr += parameter_region_size;
1124
1125 tracing::debug!(parameter_region_start);
1126
1127 let bootshim_log_size = HV_PAGE_SIZE * 2;
1129 let bootshim_log_start = next_addr;
1130 next_addr += bootshim_log_size;
1131
1132 importer.import_pages(
1133 bootshim_log_start / HV_PAGE_SIZE,
1134 bootshim_log_size / HV_PAGE_SIZE,
1135 "ohcl-boot-shim-log-buffer",
1136 BootPageAcceptance::Exclusive,
1137 &[],
1138 )?;
1139
1140 let heap_start = next_addr;
1147 let heap_size = 16 * HV_PAGE_SIZE;
1148 importer.import_pages(
1149 heap_start / HV_PAGE_SIZE,
1150 heap_size / HV_PAGE_SIZE,
1151 "ohcl-boot-shim-heap",
1152 BootPageAcceptance::Exclusive,
1153 &[],
1154 )?;
1155 next_addr += heap_size;
1156
1157 let end_of_underhill_mem = next_addr;
1159
1160 let page_table_base_page_count = 5;
1163 let page_table_dynamic_page_count = 2 * page_table_base_page_count;
1164 let page_table_page_count = page_table_base_page_count + page_table_dynamic_page_count;
1165 let page_table_region_size = HV_PAGE_SIZE * page_table_page_count;
1166 let page_table_region_start = next_addr;
1167 next_addr += page_table_region_size;
1168
1169 tracing::debug!(page_table_region_start, page_table_region_size);
1170
1171 let next_addr = next_addr;
1172
1173 if next_addr > memory_start_address + memory_size {
1175 return Err(Error::NotEnoughMemory(next_addr - memory_start_address));
1176 }
1177
1178 let calculate_shim_offset = |addr: u64| -> i64 { addr.wrapping_sub(shim_base_addr) as i64 };
1180 let shim_params = ShimParamsRaw {
1181 kernel_entry_offset: calculate_shim_offset(kernel_entry_point),
1182 cmdline_offset: calculate_shim_offset(cmdline_base),
1183 initrd_offset: calculate_shim_offset(initrd_gpa),
1184 initrd_size,
1185 initrd_crc,
1186 supported_isolation_type: match importer.isolation_config().isolation_type {
1187 IsolationType::None | IsolationType::Vbs => {
1188 loader_defs::shim::SupportedIsolationType::VBS
1189 }
1190 _ => panic!("only None and VBS are supported for ARM64"),
1191 },
1192 memory_start_offset: calculate_shim_offset(memory_start_address),
1193 memory_size,
1194 parameter_region_offset: calculate_shim_offset(parameter_region_start),
1195 parameter_region_size,
1196 vtl2_reserved_region_offset: 0,
1197 vtl2_reserved_region_size: 0,
1198 sidecar_offset: 0,
1199 sidecar_size: 0,
1200 sidecar_entry_offset: 0,
1201 used_start: calculate_shim_offset(memory_start_address),
1202 used_end: calculate_shim_offset(next_addr),
1203 bounce_buffer_start: 0,
1204 bounce_buffer_size: 0,
1205 log_buffer_start: calculate_shim_offset(bootshim_log_start),
1206 log_buffer_size: bootshim_log_size,
1207 heap_start_offset: calculate_shim_offset(heap_start),
1208 heap_size,
1209 persisted_state_region_offset: calculate_shim_offset(persisted_region_base),
1210 persisted_state_region_size: persisted_region_size,
1211 };
1212
1213 importer
1214 .import_pages(
1215 shim_params_base / HV_PAGE_SIZE,
1216 shim_params_size / HV_PAGE_SIZE,
1217 "underhill-shim-params",
1218 BootPageAcceptance::Exclusive,
1219 shim_params.as_bytes(),
1220 )
1221 .map_err(Error::Importer)?;
1222
1223 let mut measured_config = ParavisorMeasuredVtl0Config {
1224 magic: ParavisorMeasuredVtl0Config::MAGIC,
1225 ..FromZeros::new_zeroed()
1226 };
1227
1228 if let Some((uefi, vp_context)) = &supports_uefi {
1229 measured_config.supported_vtl0.set_uefi_supported(true);
1230 let vp_context_page = PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_AARCH64 + 1;
1231 measured_config.uefi_info = UefiInfo {
1232 firmware: PageRegionDescriptor {
1233 base_page_number: uefi.firmware_base / HV_PAGE_SIZE,
1234 page_count: uefi.total_size / HV_PAGE_SIZE,
1235 },
1236 vtl0_vp_context: PageRegionDescriptor {
1237 base_page_number: vp_context_page,
1238 page_count: 1,
1239 },
1240 };
1241
1242 importer.import_pages(
1244 vp_context_page,
1245 1,
1246 "openhcl-uefi-vp-context",
1247 BootPageAcceptance::Exclusive,
1248 vp_context,
1249 )?;
1250 }
1251
1252 importer
1253 .import_pages(
1254 PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_AARCH64,
1255 1,
1256 "underhill-measured-config",
1257 BootPageAcceptance::Exclusive,
1258 measured_config.as_bytes(),
1259 )
1260 .map_err(Error::Importer)?;
1261
1262 tracing::trace!(page_table_region_start, "loading the page tables");
1263
1264 let memory_attribute_indirection = MemoryAttributeIndirectionEl1([
1265 MemoryAttributeEl1::Device_nGnRnE,
1266 MemoryAttributeEl1::Normal_NonCacheable,
1267 MemoryAttributeEl1::Normal_WriteThrough,
1268 MemoryAttributeEl1::Normal_WriteBack,
1269 MemoryAttributeEl1::Device_nGnRnE,
1270 MemoryAttributeEl1::Device_nGnRnE,
1271 MemoryAttributeEl1::Device_nGnRnE,
1272 MemoryAttributeEl1::Device_nGnRnE,
1273 ]);
1274 let mut page_tables: Vec<u8> = vec![0; page_table_region_size as usize];
1275 let page_tables = page_table::aarch64::build_identity_page_tables_aarch64(
1276 page_table_region_start,
1277 memory_start_address,
1278 memory_size,
1279 memory_attribute_indirection,
1280 page_tables.as_mut_slice(),
1281 );
1282 assert!((page_tables.len() as u64).is_multiple_of(HV_PAGE_SIZE));
1283 let page_table_page_base = page_table_region_start / HV_PAGE_SIZE;
1284 assert!(page_tables.len() as u64 <= page_table_region_size);
1285 assert!(page_table_region_size as usize > page_tables.len());
1286
1287 if with_relocation {
1288 importer.relocation_region(
1290 memory_start_address,
1291 end_of_underhill_mem - memory_start_address,
1292 Arm64PageSize::Large.into(),
1293 PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS,
1294 1 << 48,
1295 true,
1296 false,
1297 0, )?;
1299
1300 importer.page_table_relocation(
1302 page_table_region_start,
1303 page_table_region_size / HV_PAGE_SIZE,
1304 page_tables.len() as u64 / HV_PAGE_SIZE,
1305 0,
1306 )?;
1307 }
1308
1309 importer.import_pages(
1310 page_table_page_base,
1311 page_table_page_count,
1312 "underhill-page-tables",
1313 BootPageAcceptance::Exclusive,
1314 page_tables,
1315 )?;
1316
1317 tracing::trace!("Importing register state");
1318
1319 let mut import_reg = |register| {
1320 importer
1321 .import_vp_register(register)
1322 .map_err(Error::Importer)
1323 };
1324
1325 let relative_boot_params_base = shim_params_base - shim_base_addr;
1327 import_reg(AArch64Register::X0(relative_boot_params_base).into())?;
1328
1329 import_reg(AArch64Register::Pc(shim_entry_point).into())?;
1331
1332 import_reg(AArch64Register::Cpsr(Cpsr64::new().with_sp(true).with_el(1).into()).into())?;
1335
1336 import_reg(
1359 AArch64Register::SctlrEl1(
1360 SctlrEl1::new()
1361 .with_m(true)
1366 .with_c(true)
1368 .with_i(true)
1370 .with_eos(true)
1372 .with_tscxt(true)
1373 .with_eis(true)
1374 .with_span(true)
1375 .with_n_tlsmd(true)
1376 .with_lsmaoe(true)
1377 .into(),
1378 )
1379 .into(),
1380 )?;
1381
1382 import_reg(
1388 AArch64Register::TcrEl1(
1389 TranslationControlEl1::new()
1390 .with_t0sz(0x11)
1391 .with_irgn0(1)
1392 .with_orgn0(1)
1393 .with_sh0(3)
1394 .with_tg0(TranslationGranule0::TG_4KB)
1395 .with_epd1(1)
1397 .with_tg1(TranslationGranule1::TG_4KB)
1399 .with_ips(IntermPhysAddrSize::IPA_48_BITS_256_TB)
1400 .into(),
1401 )
1402 .into(),
1403 )?;
1404
1405 import_reg(AArch64Register::MairEl1(memory_attribute_indirection.into()).into())?;
1407 import_reg(
1408 AArch64Register::Ttbr0El1(
1409 TranslationBaseEl1::new()
1410 .with_baddr(page_table_region_start)
1411 .into(),
1412 )
1413 .into(),
1414 )?;
1415
1416 import_reg(AArch64Register::VbarEl1(0).into())?;
1420
1421 let config_region_page_base = parameter_region_start / HV_PAGE_SIZE;
1423
1424 let slit_page_base = config_region_page_base + PARAVISOR_CONFIG_SLIT_PAGE_INDEX;
1426 let slit_parameter_area = importer.create_parameter_area(
1427 slit_page_base,
1428 PARAVISOR_CONFIG_SLIT_SIZE_PAGES as u32,
1429 "underhill-slit",
1430 )?;
1431 importer.import_parameter(slit_parameter_area, 0, IgvmParameterType::Slit)?;
1432
1433 let pptt_page_base = config_region_page_base + PARAVISOR_CONFIG_PPTT_PAGE_INDEX;
1435 let pptt_parameter_area = importer.create_parameter_area(
1436 pptt_page_base,
1437 PARAVISOR_CONFIG_PPTT_SIZE_PAGES as u32,
1438 "underhill-pptt",
1439 )?;
1440 importer.import_parameter(pptt_parameter_area, 0, IgvmParameterType::Pptt)?;
1441
1442 let dt_page_base = config_region_page_base + PARAVISOR_CONFIG_DEVICE_TREE_PAGE_INDEX;
1444 let dt_parameter_area = importer.create_parameter_area(
1445 dt_page_base,
1446 PARAVISOR_CONFIG_DEVICE_TREE_SIZE_PAGES as u32,
1447 "underhill-device-tree",
1448 )?;
1449 importer.import_parameter(dt_parameter_area, 0, IgvmParameterType::DeviceTree)?;
1450
1451 let vtl2_measured_config = ParavisorMeasuredVtl2Config {
1452 magic: ParavisorMeasuredVtl2Config::MAGIC,
1453 vtom_offset_bit: 0,
1454 padding: [0; 7],
1455 };
1456
1457 importer
1458 .import_pages(
1459 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_PAGE_INDEX,
1460 PARAVISOR_MEASURED_VTL2_CONFIG_SIZE_PAGES,
1461 "underhill-vtl2-measured-config",
1462 BootPageAcceptance::Exclusive,
1463 vtl2_measured_config.as_bytes(),
1464 )
1465 .map_err(Error::Importer)?;
1466
1467 let imported_region_base =
1468 config_region_page_base + PARAVISOR_MEASURED_VTL2_CONFIG_ACCEPTED_MEMORY_PAGE_INDEX;
1469
1470 importer.set_imported_regions_config_page(imported_region_base);
1471
1472 Ok(())
1473}