1use crate::common::ChunkBuf;
7use crate::common::ImportFileRegion;
8use crate::common::ImportFileRegionError;
9use crate::common::ReadSeek;
10use crate::common::import_default_gdt;
11use crate::elf::load_static_elf;
12use crate::importer::Aarch64Register;
13use crate::importer::BootPageAcceptance;
14use crate::importer::GuestArch;
15use crate::importer::ImageLoad;
16use crate::importer::X86Register;
17use aarch64defs::Cpsr64;
18use aarch64defs::IntermPhysAddrSize;
19use aarch64defs::SctlrEl1;
20use aarch64defs::TranslationBaseEl1;
21use aarch64defs::TranslationControlEl1;
22use aarch64defs::TranslationGranule0;
23use aarch64defs::TranslationGranule1;
24use bitfield_struct::bitfield;
25use hvdef::HV_PAGE_SIZE;
26use loader_defs::linux as defs;
27use memory_range::MemoryRange;
28use page_table::IdentityMapSize;
29use page_table::x64::IdentityMapBuilder;
30use page_table::x64::PAGE_TABLE_MAX_BYTES;
31use page_table::x64::PAGE_TABLE_MAX_COUNT;
32use page_table::x64::PageTable;
33use page_table::x64::align_up_to_large_page_size;
34use page_table::x64::align_up_to_page_size;
35use std::ffi::CString;
36use std::io::Read;
37use std::io::Seek;
38use std::io::SeekFrom;
39use std::mem::size_of;
40use thiserror::Error;
41use vm_topology::memory::MemoryLayout;
42use x86defs::cpuid::CpuidFunction;
43use zerocopy::FromBytes;
44use zerocopy::FromZeros;
45use zerocopy::Immutable;
46use zerocopy::IntoBytes;
47use zerocopy::KnownLayout;
48
49struct ZeroPageBuildResult {
50 boot_params: defs::boot_params,
51 additional_pages: Option<MemoryRange>,
52}
53
54fn build_zero_page(
56 mem_layout: &MemoryLayout,
57 acpi_len: usize,
58 smbios_struct_len: usize,
59 additional_page_count: u64,
60 cmdline: &CString,
61 initrd_base: u32,
62 initrd_size: u32,
63 bzimage_header: Option<&defs::setup_header>,
64) -> Result<ZeroPageBuildResult, Error> {
65 const LOADER_TYPE_UNREGISTERED: u8 = 0xff;
69
70 let mut hdr = match bzimage_header {
73 Some(orig) => *orig,
74 None => defs::setup_header {
75 boot_flag: 0xaa55.into(),
76 header: 0x53726448.into(),
77 kernel_alignment: 0x100000.into(),
78 ..FromZeros::new_zeroed()
79 },
80 };
81
82 hdr.type_of_loader = LOADER_TYPE_UNREGISTERED;
84 hdr.cmd_line_ptr = CMDLINE_BASE.try_into().expect("must fit in u32");
85 hdr.cmdline_size = (cmdline.as_bytes().len() as u64)
86 .try_into()
87 .expect("must fit in u32");
88 hdr.ramdisk_image = initrd_base.into();
89 hdr.ramdisk_size = initrd_size.into();
90
91 let mut p = defs::boot_params {
92 hdr,
93 ..FromZeros::new_zeroed()
94 };
95
96 let mut ram = mem_layout.ram().iter().cloned();
97 let range = ram.next().expect("at least one ram range");
98 assert_eq!(range.range.start(), 0);
99 assert!(range.range.end() >= 0x100000);
100
101 const ONE_MB: u64 = 0x100000;
120 let aligned_acpi_len = align_up_to_page_size(acpi_len as u64);
121 let acpi_end = ACPI_TABLES_BASE + aligned_acpi_len;
122 let aligned_smbios_len = align_up_to_page_size(smbios_struct_len as u64);
123 let smbios_end = acpi_end + aligned_smbios_len;
124 let additional_size = additional_page_count
125 .checked_mul(HV_PAGE_SIZE)
126 .ok_or(Error::LowTablesTooLarge(u64::MAX, RSDP_BASE))?;
127 let additional_end = smbios_end
128 .checked_add(additional_size)
129 .ok_or(Error::LowTablesTooLarge(u64::MAX, RSDP_BASE))?;
130 if additional_end > RSDP_BASE {
131 return Err(Error::LowTablesTooLarge(additional_end, RSDP_BASE));
132 }
133 let additional_pages =
134 (additional_size != 0).then(|| MemoryRange::new(smbios_end..additional_end));
135
136 let e820_cap = p.e820_map.len();
141 let mut n = 0;
142 let mut push = |addr: u64, size: u64, typ: u32| -> Result<(), Error> {
143 if size == 0 {
144 return Ok(());
145 }
146 let entry = p
147 .e820_map
148 .get_mut(n)
149 .ok_or(Error::TooManyMemoryRanges(e820_cap))?;
150 *entry = defs::e820entry {
151 addr: addr.into(),
152 size: size.into(),
153 typ: typ.into(),
154 };
155 n += 1;
156 Ok(())
157 };
158 push(0, ACPI_TABLES_BASE, defs::E820_RAM)?;
159 push(ACPI_TABLES_BASE, aligned_acpi_len, defs::E820_ACPI)?;
160 push(acpi_end, aligned_smbios_len, defs::E820_RESERVED)?;
161 push(smbios_end, additional_size, defs::E820_RESERVED)?;
162 push(additional_end, RSDP_BASE - additional_end, defs::E820_RAM)?;
163 push(RSDP_BASE, ONE_MB - RSDP_BASE, defs::E820_RESERVED)?;
164 push(ONE_MB, range.range.end() - ONE_MB, defs::E820_RAM)?;
165 for range in ram {
166 push(range.range.start(), range.range.len(), defs::E820_RAM)?;
167 }
168 p.e820_entries = n as u8;
169
170 Ok(ZeroPageBuildResult {
171 boot_params: p,
172 additional_pages,
173 })
174}
175
176#[derive(Debug, Error)]
177pub enum FlatLoaderError {
178 #[error("unsupported ELF File byte order")]
179 BigEndianElfOnLittle,
180 #[error("error reading kernel data structure")]
181 BadImageMagic,
182 #[error("big-endian kernel image is not supported")]
183 BigEndianKernelImage,
184 #[error("only images with 4K pages are supported")]
185 FourKibPageImageIsRequired,
186 #[error("the kernel is required to run in the low memory; not supported")]
187 LowMemoryKernel,
188 #[error("failed to read kernel image")]
189 ReadKernelImage,
190 #[error("failed to seek to file offset as pointed by the ELF program header")]
191 SeekKernelStart,
192 #[error("failed to seek to offset of kernel image")]
193 SeekKernelImage,
194}
195
196#[derive(Debug, Error)]
197pub enum Error {
198 #[error("elf loader error")]
199 ElfLoader(#[source] crate::elf::Error),
200 #[error("bzImage parse error")]
201 BzImage(#[source] crate::bzimage::Error),
202 #[error("flat loader error")]
203 FlatLoader(#[source] FlatLoaderError),
204 #[error("Address is not page aligned")]
205 UnalignedAddress(u64),
206 #[error("importer error")]
207 Importer(#[source] anyhow::Error),
208 #[error("failed to import initrd")]
209 ImportInitrd(#[source] ImportFileRegionError),
210 #[error("failed to import bzImage payload")]
211 ImportBzImage(#[source] ImportFileRegionError),
212 #[error("PageTableBuilder: {0}")]
213 PageTableBuilder(#[from] page_table::Error),
214 #[error("kernel command line ({0} bytes) exceeds its {1:#x}-byte slot")]
215 CommandLineTooLong(usize, u64),
216 #[error("low-memory tables and boot pages end at {0:#x}, past the reserved region at {1:#x}")]
217 LowTablesTooLarge(u64, u64),
218 #[error("SNP CC blob address {0:#x} does not fit in the Linux boot protocol field")]
219 SnpCcBlobAddressTooHigh(u64),
220 #[error("too many memory ranges to fit in the {0}-entry e820 map")]
221 TooManyMemoryRanges(usize),
222 #[error("acpi tables are empty")]
223 EmptyAcpiTables,
224}
225
226pub struct AcpiTables {
234 pub rsdp: Vec<u8>,
236 pub tables: Vec<u8>,
238}
239
240const GDT_BASE: u64 = 0x1000;
251const ZERO_PAGE_BASE: u64 = 0x2000;
252const CMDLINE_BASE: u64 = 0x3000;
253const CR3_BASE: u64 = 0x4000;
254const LOW_METADATA_END: u64 = CR3_BASE + PAGE_TABLE_MAX_BYTES as u64;
257const ACPI_TABLES_BASE: u64 = LOW_METADATA_END + 0x1000;
260const RSDP_BASE: u64 = 0xe0000;
264const SMBIOS_FSEGMENT_BASE: u64 = 0xf0000;
269const KERNEL_BASE: u64 = 0x100000;
271
272#[derive(Debug, Clone, Copy)]
274pub struct SnpBootConfig {
275 pub c_bit: u8,
277}
278
279const SNP_BOOT_PAGE_COUNT: u64 = 5;
280
281fn smbios_struct_table_base(acpi_tables_len: usize) -> u64 {
286 ACPI_TABLES_BASE + align_up_to_page_size(acpi_tables_len as u64)
287}
288
289const _: () = {
292 assert!(GDT_BASE < ZERO_PAGE_BASE);
293 assert!(ZERO_PAGE_BASE < CMDLINE_BASE);
294 assert!(CMDLINE_BASE < CR3_BASE);
295 assert!(ACPI_TABLES_BASE < RSDP_BASE);
296 assert!(RSDP_BASE < SMBIOS_FSEGMENT_BASE);
297 assert!(SMBIOS_FSEGMENT_BASE < KERNEL_BASE);
298};
299
300#[derive(Debug, PartialEq, Eq, Clone, Copy)]
301pub enum InitrdAddressType {
302 AfterKernel,
304 Address(u64),
306}
307
308pub struct InitrdConfig<'a> {
309 pub initrd_address: InitrdAddressType,
310 pub initrd: &'a mut dyn ReadSeek,
311 pub size: u64,
312}
313
314#[derive(Debug, Default)]
316pub struct KernelInfo {
317 pub gpa: u64,
319 pub size: u64,
321 pub entrypoint: u64,
323}
324
325#[derive(Debug, Default)]
327pub struct InitrdInfo {
328 pub gpa: u64,
330 pub size: u64,
332}
333
334#[derive(Debug, Default)]
336pub struct LoadInfo {
337 pub kernel: KernelInfo,
339 pub initrd: Option<InitrdInfo>,
341 pub dtb: Option<std::ops::Range<u64>>,
343 pub bzimage_setup_header: Option<defs::setup_header>,
347}
348
349fn import_snp_boot_pages(
350 importer: &mut impl ImageLoad<X86Register>,
351 range: MemoryRange,
352) -> Result<u64, Error> {
353 assert_eq!(range.len(), SNP_BOOT_PAGE_COUNT * HV_PAGE_SIZE);
354 let secrets_address = range.start();
355 let cpuid_address = range.start() + HV_PAGE_SIZE;
356 let cc_blob_address = range.start() + 2 * HV_PAGE_SIZE;
357 let cc_setup_data_address = range.start() + 3 * HV_PAGE_SIZE;
358 let vmsa_address = range.start() + 4 * HV_PAGE_SIZE;
359
360 importer
361 .import_pages(
362 secrets_address / HV_PAGE_SIZE,
363 1,
364 "linux-snp-secrets",
365 BootPageAcceptance::SecretsPage,
366 &[],
367 )
368 .map_err(Error::Importer)?;
369 let mut cpuid_page = crate::cpuid::HV_PSP_CPUID_PAGE::default();
370 for (index, leaf) in crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR
374 .iter()
375 .enumerate()
376 {
377 let entry = &mut cpuid_page.cpuid_leaf_info[index];
378 entry.eax_in = leaf.eax;
379 entry.ecx_in = leaf.ecx;
380 if leaf.eax == CpuidFunction::ExtendedStateEnumeration.0 && leaf.ecx <= 1 {
381 entry.xfem_in = 1;
382 }
383 }
384 cpuid_page.count = crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR.len() as u32;
385 importer
386 .import_pages(
387 cpuid_address / HV_PAGE_SIZE,
388 1,
389 "linux-snp-cpuid",
390 BootPageAcceptance::CpuidPage,
391 cpuid_page.as_bytes(),
392 )
393 .map_err(Error::Importer)?;
394
395 let cc_blob = defs::cc_blob_sev_info {
396 magic: defs::CC_BLOB_SEV_INFO_MAGIC,
397 version: 0,
398 _reserved: 0,
399 secrets_phys: secrets_address,
400 secrets_len: HV_PAGE_SIZE as u32,
401 _rsvd1: 0,
402 cpuid_phys: cpuid_address,
403 cpuid_len: HV_PAGE_SIZE as u32,
404 _rsvd2: 0,
405 };
406 importer
407 .import_pages(
408 cc_blob_address / HV_PAGE_SIZE,
409 1,
410 "linux-snp-cc-blob",
411 BootPageAcceptance::Exclusive,
412 cc_blob.as_bytes(),
413 )
414 .map_err(Error::Importer)?;
415
416 let cc_blob_address = u32::try_from(cc_blob_address)
417 .map_err(|_| Error::SnpCcBlobAddressTooHigh(cc_blob_address))?;
418 let cc_setup_data = defs::cc_setup_data {
419 header: defs::setup_data {
420 next: 0,
421 ty: defs::SETUP_CC_BLOB,
422 len: (size_of::<defs::cc_setup_data>() - size_of::<defs::setup_data>()) as u32,
423 },
424 cc_blob_address,
425 _padding: [0; 3],
426 };
427 importer
428 .import_pages(
429 cc_setup_data_address / HV_PAGE_SIZE,
430 1,
431 "linux-snp-cc-setup-data",
432 BootPageAcceptance::Exclusive,
433 cc_setup_data.as_bytes(),
434 )
435 .map_err(Error::Importer)?;
436
437 importer
438 .set_vp_context_page(vmsa_address / HV_PAGE_SIZE)
439 .map_err(Error::Importer)?;
440
441 Ok(cc_setup_data_address)
442}
443
444fn check_address_alignment(address: u64) -> Result<(), Error> {
446 if !address.is_multiple_of(HV_PAGE_SIZE) {
447 Err(Error::UnalignedAddress(address))
448 } else {
449 Ok(())
450 }
451}
452
453fn import_initrd<R: GuestArch>(
455 initrd: Option<InitrdConfig<'_>>,
456 next_addr: u64,
457 importer: &mut dyn ImageLoad<R>,
458) -> Result<Option<InitrdInfo>, Error> {
459 let initrd_info = match initrd {
460 Some(cfg) => {
461 let initrd_address = match cfg.initrd_address {
462 InitrdAddressType::AfterKernel => align_up_to_large_page_size(next_addr),
463 InitrdAddressType::Address(addr) => addr,
464 };
465
466 tracing::trace!(initrd_address, "loading initrd");
467 check_address_alignment(initrd_address)?;
468
469 ChunkBuf::new()
470 .import_file_region(
471 importer,
472 ImportFileRegion {
473 file: cfg.initrd,
474 file_offset: 0,
475 file_length: cfg.size,
476 gpa: initrd_address,
477 memory_length: cfg.size,
478 acceptance: BootPageAcceptance::Exclusive,
479 tag: "linux-initrd",
480 },
481 )
482 .map_err(Error::ImportInitrd)?;
483
484 Some(InitrdInfo {
485 gpa: initrd_address,
486 size: cfg.size,
487 })
488 }
489 None => None,
490 };
491 Ok(initrd_info)
492}
493
494pub fn load_kernel_and_initrd_x64<F>(
510 importer: &mut dyn ImageLoad<X86Register>,
511 kernel_image: &mut F,
512 kernel_minimum_start_address: u64,
513 initrd: Option<InitrdConfig<'_>>,
514) -> Result<LoadInfo, Error>
515where
516 F: Read + Seek,
517{
518 tracing::trace!(kernel_minimum_start_address, "loading x86_64 kernel");
519
520 if crate::bzimage::is_bzimage(kernel_image).map_err(Error::BzImage)? {
521 tracing::info!("detected bzImage format, loading via Linux boot protocol");
522 return load_bzimage(importer, kernel_image, kernel_minimum_start_address, initrd);
523 }
524
525 let elf_load_info = load_static_elf(
526 importer,
527 kernel_image,
528 kernel_minimum_start_address,
529 0,
530 false,
531 BootPageAcceptance::Exclusive,
532 "linux-kernel",
533 )
534 .map_err(Error::ElfLoader)?;
535
536 let crate::elf::LoadInfo {
537 minimum_address_used: min_addr,
538 next_available_address: next_addr,
539 entrypoint,
540 } = elf_load_info;
541 tracing::trace!(min_addr, next_addr, entrypoint, "loaded kernel");
542
543 let initrd_info = import_initrd(initrd, next_addr, importer)?;
544
545 Ok(LoadInfo {
546 kernel: KernelInfo {
547 gpa: min_addr,
548 size: next_addr - min_addr,
549 entrypoint,
550 },
551 initrd: initrd_info,
552 dtb: None,
553 bzimage_setup_header: None,
554 })
555}
556
557fn load_bzimage(
561 importer: &mut dyn ImageLoad<X86Register>,
562 kernel_image: &mut (impl Read + Seek),
563 kernel_start_address: u64,
564 initrd: Option<InitrdConfig<'_>>,
565) -> Result<LoadInfo, Error> {
566 let info = crate::bzimage::parse_bzimage(kernel_image).map_err(Error::BzImage)?;
567
568 check_address_alignment(kernel_start_address)?;
569
570 let payload_offset = (info.setup_sects as u64 + 1) * 512;
571 let payload_len = info.protected_mode_size;
572 let payload_memory_len = align_up_to_page_size(payload_len);
573 let entrypoint = kernel_start_address + info.entry_offset;
574
575 tracing::info!(
576 kernel_start_address = format_args!("{:#x}", kernel_start_address),
577 payload_offset,
578 payload_len,
579 entrypoint = format_args!("{:#x}", entrypoint),
580 "loading bzImage payload into guest memory"
581 );
582
583 ChunkBuf::new()
584 .import_file_region(
585 importer,
586 ImportFileRegion {
587 file: kernel_image,
588 file_offset: payload_offset,
589 file_length: payload_len,
590 gpa: kernel_start_address,
591 memory_length: payload_memory_len,
592 acceptance: BootPageAcceptance::Exclusive,
593 tag: "linux-kernel",
594 },
595 )
596 .map_err(Error::ImportBzImage)?;
597
598 let next_addr = kernel_start_address + payload_memory_len;
601 let pref_address: u64 = info.setup_header.pref_address.into();
602 let init_end = kernel_start_address
603 .max(pref_address)
604 .saturating_add(info.init_size as u64);
605 let next_addr = next_addr.max(init_end);
606 let initrd_info = import_initrd(initrd, next_addr, importer)?;
607
608 Ok(LoadInfo {
609 kernel: KernelInfo {
610 gpa: kernel_start_address,
611 size: payload_memory_len,
612 entrypoint,
613 },
614 initrd: initrd_info,
615 dtb: None,
616 bzimage_setup_header: Some(info.setup_header),
617 })
618}
619
620fn import_config(
627 importer: &mut impl ImageLoad<X86Register>,
628 load_info: &LoadInfo,
629 cmdline: &CString,
630 mem_layout: &MemoryLayout,
631 acpi: &AcpiTables,
632 smbios: Option<&crate::smbios::BuiltSmbios>,
633 snp_boot: Option<SnpBootConfig>,
634) -> Result<(), Error> {
635 let raw_cmdline = cmdline.as_bytes_with_nul();
638 if raw_cmdline.len() as u64 > CR3_BASE - CMDLINE_BASE {
639 return Err(Error::CommandLineTooLong(
640 raw_cmdline.len(),
641 CR3_BASE - CMDLINE_BASE,
642 ));
643 }
644 if raw_cmdline.len() > 1 {
645 let cmdline_size_pages = align_up_to_page_size(raw_cmdline.len() as u64) / HV_PAGE_SIZE;
646 importer
647 .import_pages(
648 CMDLINE_BASE / HV_PAGE_SIZE,
649 cmdline_size_pages,
650 "linux-commandline",
651 BootPageAcceptance::Exclusive,
652 raw_cmdline,
653 )
654 .map_err(Error::Importer)?;
655 }
656
657 import_default_gdt(importer, GDT_BASE / HV_PAGE_SIZE).map_err(Error::Importer)?;
658 let mut page_table_work_buffer: Vec<PageTable> =
659 vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
660 let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
661 let mut page_table_builder = IdentityMapBuilder::new(
662 CR3_BASE,
663 IdentityMapSize::Size4Gb,
664 page_table_work_buffer.as_mut_slice(),
665 page_table.as_mut_slice(),
666 )?;
667 if let Some(snp_boot) = snp_boot {
668 page_table_builder = page_table_builder.with_confidential_bit(snp_boot.c_bit.into());
669 }
670 let page_table = page_table_builder.build();
671 assert!((page_table.len() as u64).is_multiple_of(HV_PAGE_SIZE));
672 importer
673 .import_pages(
674 CR3_BASE / HV_PAGE_SIZE,
675 page_table.len() as u64 / HV_PAGE_SIZE,
676 "linux-pagetables",
677 BootPageAcceptance::Exclusive,
678 page_table,
679 )
680 .map_err(Error::Importer)?;
681
682 if acpi.tables.is_empty() {
683 return Err(Error::EmptyAcpiTables);
684 }
685 let acpi_tables_size_pages = align_up_to_page_size(acpi.tables.len() as u64) / HV_PAGE_SIZE;
686 importer
687 .import_pages(
688 RSDP_BASE / HV_PAGE_SIZE,
689 1,
690 "linux-rsdp",
691 BootPageAcceptance::Exclusive,
692 &acpi.rsdp,
693 )
694 .map_err(Error::Importer)?;
695 importer
696 .import_pages(
697 ACPI_TABLES_BASE / HV_PAGE_SIZE,
698 acpi_tables_size_pages,
699 "linux-acpi-tables",
700 BootPageAcceptance::Exclusive,
701 &acpi.tables,
702 )
703 .map_err(Error::Importer)?;
704
705 let requested_page_count = snp_boot.map_or(0, |_| SNP_BOOT_PAGE_COUNT);
706 let ZeroPageBuildResult {
707 mut boot_params,
708 additional_pages,
709 } = build_zero_page(
710 mem_layout,
711 acpi.tables.len(),
712 smbios.map_or(0, |s| s.structure_table.len()),
713 requested_page_count,
714 cmdline,
715 load_info.initrd.as_ref().map(|info| info.gpa).unwrap_or(0) as u32,
716 load_info.initrd.as_ref().map(|info| info.size).unwrap_or(0) as u32,
717 load_info.bzimage_setup_header.as_ref(),
718 )?;
719 if let Some(allocated_range) = additional_pages {
720 boot_params.hdr.setup_data = import_snp_boot_pages(importer, allocated_range)?.into();
721 }
722 importer
723 .import_pages(
724 ZERO_PAGE_BASE / HV_PAGE_SIZE,
725 1,
726 "linux-zeropage",
727 BootPageAcceptance::Exclusive,
728 boot_params.as_bytes(),
729 )
730 .map_err(Error::Importer)?;
731
732 let mut import_reg = |register| {
734 importer
735 .import_vp_register(register)
736 .map_err(Error::Importer)
737 };
738
739 import_reg(X86Register::Cr0(x86defs::X64_CR0_PG | x86defs::X64_CR0_PE))?;
740 import_reg(X86Register::Cr3(CR3_BASE))?;
741 import_reg(X86Register::Cr4(x86defs::X64_CR4_PAE))?;
742 import_reg(X86Register::Efer(
743 x86defs::X64_EFER_SCE
744 | x86defs::X64_EFER_LME
745 | x86defs::X64_EFER_LMA
746 | x86defs::X64_EFER_NXE,
747 ))?;
748 import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?;
749
750 import_reg(X86Register::Rip(load_info.kernel.entrypoint))?;
752 import_reg(X86Register::Rsi(ZERO_PAGE_BASE))?;
753
754 import_reg(X86Register::MtrrDefType(0xc00))?;
757 import_reg(X86Register::MtrrFix64k00000(0x0606060606060606))?;
758 import_reg(X86Register::MtrrFix16k80000(0x0606060606060606))?;
759
760 if let Some(smbios) = smbios {
761 let anchor_pages = align_up_to_page_size(smbios.entry_point.len() as u64) / HV_PAGE_SIZE;
765 importer
766 .import_pages(
767 SMBIOS_FSEGMENT_BASE / HV_PAGE_SIZE,
768 anchor_pages,
769 "linux-smbios-anchor",
770 BootPageAcceptance::Exclusive,
771 &smbios.entry_point,
772 )
773 .map_err(Error::Importer)?;
774
775 let table_base = smbios_struct_table_base(acpi.tables.len());
776 let table_pages = align_up_to_page_size(smbios.structure_table.len() as u64) / HV_PAGE_SIZE;
777 importer
778 .import_pages(
779 table_base / HV_PAGE_SIZE,
780 table_pages,
781 "linux-smbios-tables",
782 BootPageAcceptance::Exclusive,
783 &smbios.structure_table,
784 )
785 .map_err(Error::Importer)?;
786 }
787
788 Ok(())
789}
790
791pub fn load_config_x86(
807 importer: &mut impl ImageLoad<X86Register>,
808 load_info: &LoadInfo,
809 cmdline: &CString,
810 mem_layout: &MemoryLayout,
811 build_acpi: impl FnOnce(u64) -> AcpiTables,
812 smbios: Option<crate::smbios::SmbiosTables<'_>>,
813 snp_boot: Option<SnpBootConfig>,
814) -> Result<(), Error> {
815 let acpi_tables = build_acpi(LOW_METADATA_END);
819
820 let smbios = smbios.map(|tables| {
825 crate::smbios::build(&tables, smbios_struct_table_base(acpi_tables.tables.len()))
826 });
827
828 import_config(
829 importer,
830 load_info,
831 cmdline,
832 mem_layout,
833 &acpi_tables,
834 smbios.as_ref(),
835 snp_boot,
836 )
837}
838
839pub fn load_x86<F>(
846 importer: &mut impl ImageLoad<X86Register>,
847 kernel_image: &mut F,
848 initrd: Option<InitrdConfig<'_>>,
849 cmdline: &CString,
850 mem_layout: &MemoryLayout,
851 build_acpi: impl FnOnce(u64) -> AcpiTables,
852 smbios: Option<crate::smbios::SmbiosTables<'_>>,
853 snp_boot: Option<SnpBootConfig>,
854) -> Result<LoadInfo, Error>
855where
856 F: Read + Seek,
857{
858 let load_info = load_kernel_and_initrd_x64(importer, kernel_image, KERNEL_BASE, initrd)?;
859 load_config_x86(
860 importer, &load_info, cmdline, mem_layout, build_acpi, smbios, snp_boot,
861 )?;
862 Ok(load_info)
863}
864
865open_enum::open_enum! {
866 #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
867 pub enum Aarch64ImagePageSize: u64 {
868 UNSPECIFIED = 0,
869 PAGE4_K = 1,
870 PAGE16_K = 2,
871 PAGE64_K = 3,
872 }
873
874}
875
876impl Aarch64ImagePageSize {
877 const fn into_bits(self) -> u64 {
878 self.0
879 }
880
881 const fn from_bits(bits: u64) -> Self {
882 Self(bits)
883 }
884}
885
886#[bitfield(u64)]
888struct Aarch64ImageFlags {
889 #[bits(1)]
891 pub big_endian: bool,
892 #[bits(2)]
898 pub page_size: Aarch64ImagePageSize,
899 #[bits(1)]
906 pub any_start_address: bool,
907 #[bits(60)]
909 pub _padding: u64,
910}
911
912#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
915#[repr(C)]
916struct Aarch64ImageHeader {
917 _code0: u32,
919 _code1: u32,
921 text_offset: u64,
923 image_size: u64,
925 flags: u64,
927 _res2: u64,
929 _res3: u64,
931 _res4: u64,
933 magic: [u8; 4],
935 _res5: u32,
937}
938
939const AARCH64_MAGIC_NUMBER: &[u8] = b"ARM\x64";
940
941pub fn load_kernel_and_initrd_arm64<F>(
953 importer: &mut dyn ImageLoad<Aarch64Register>,
954 kernel_image: &mut F,
955 kernel_minimum_start_address: u64,
956 initrd: Option<InitrdConfig<'_>>,
957 device_tree_blob: Option<&[u8]>,
958) -> Result<LoadInfo, Error>
959where
960 F: Read + Seek,
961{
962 tracing::trace!(kernel_minimum_start_address, "loading aarch64 kernel");
963
964 assert_eq!(
965 kernel_minimum_start_address & ((1 << 21) - 1),
966 0,
967 "Start offset must be aligned on the 2MiB boundary"
968 );
969
970 kernel_image
971 .seek(SeekFrom::Start(0))
972 .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
973
974 let mut header = Aarch64ImageHeader::new_zeroed();
975 kernel_image
976 .read_exact(header.as_mut_bytes())
977 .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
978
979 tracing::debug!("aarch64 kernel header {header:x?}");
980
981 if header.magic != AARCH64_MAGIC_NUMBER {
982 return Err(Error::FlatLoader(FlatLoaderError::BadImageMagic));
983 }
984
985 let flags = Aarch64ImageFlags::from(header.flags);
986 if flags.big_endian() {
987 return Err(Error::FlatLoader(FlatLoaderError::BigEndianKernelImage));
988 }
989 if flags.page_size() != Aarch64ImagePageSize::PAGE4_K {
990 return Err(Error::FlatLoader(
991 FlatLoaderError::FourKibPageImageIsRequired,
992 ));
993 }
994 if !flags.any_start_address() {
995 return Err(Error::FlatLoader(FlatLoaderError::LowMemoryKernel));
996 }
997
998 kernel_image
1002 .seek(SeekFrom::Start(0))
1003 .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
1004
1005 let mut image = Vec::new();
1006 kernel_image
1007 .read_to_end(&mut image)
1008 .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
1009
1010 let kernel_load_offset = (kernel_minimum_start_address + header.text_offset) as usize;
1011 let kernel_size = if header.image_size != 0 {
1012 header.image_size
1013 } else {
1014 image.len() as u64
1015 };
1016
1017 let kernel_size = align_up_to_page_size(kernel_size);
1018 importer
1019 .import_pages(
1020 kernel_load_offset as u64 / HV_PAGE_SIZE,
1021 kernel_size / HV_PAGE_SIZE,
1022 "linux-kernel",
1023 BootPageAcceptance::Exclusive,
1024 &image,
1025 )
1026 .map_err(Error::Importer)?;
1027
1028 let next_addr = kernel_load_offset as u64 + kernel_size;
1029
1030 let (next_addr, dtb) = if let Some(device_tree_blob) = device_tree_blob {
1031 let dtb_addr = align_up_to_page_size(next_addr);
1032 tracing::trace!(dtb_addr, "loading device tree blob at {dtb_addr:x?}");
1033
1034 check_address_alignment(dtb_addr)?;
1035 let dtb_size_pages = align_up_to_page_size(device_tree_blob.len() as u64) / HV_PAGE_SIZE;
1036
1037 importer
1038 .import_pages(
1039 dtb_addr / HV_PAGE_SIZE,
1040 dtb_size_pages,
1041 "linux-device-tree",
1042 BootPageAcceptance::Exclusive,
1043 device_tree_blob,
1044 )
1045 .map_err(Error::Importer)?;
1046
1047 (
1048 dtb_addr + device_tree_blob.len() as u64,
1049 Some(dtb_addr..dtb_addr + device_tree_blob.len() as u64),
1050 )
1051 } else {
1052 (next_addr, None)
1053 };
1054
1055 let initrd_info = import_initrd(initrd, next_addr, importer)?;
1056
1057 Ok(LoadInfo {
1058 kernel: KernelInfo {
1059 gpa: kernel_minimum_start_address,
1060 size: kernel_size,
1061 entrypoint: kernel_load_offset as u64,
1062 },
1063 initrd: initrd_info,
1064 dtb,
1065 bzimage_setup_header: None,
1066 })
1067}
1068
1069pub fn set_direct_boot_registers_arm64(
1075 importer: &mut impl ImageLoad<Aarch64Register>,
1076 load_info: &LoadInfo,
1077) -> Result<(), Error> {
1078 let mut import_reg = |register| {
1079 importer
1080 .import_vp_register(register)
1081 .map_err(Error::Importer)
1082 };
1083
1084 import_reg(Aarch64Register::Pc(load_info.kernel.entrypoint))?;
1085 import_reg(Aarch64Register::Cpsr(
1086 Cpsr64::new()
1087 .with_sp(true)
1088 .with_el(1)
1089 .with_f(true)
1090 .with_i(true)
1091 .with_a(true)
1092 .with_d(true)
1093 .into(),
1094 ))?;
1095 import_reg(Aarch64Register::SctlrEl1(
1096 SctlrEl1::new()
1097 .with_m(false)
1101 .with_c(true)
1103 .with_i(true)
1105 .with_eos(true)
1107 .with_tscxt(true)
1108 .with_eis(true)
1109 .with_span(true)
1110 .with_n_tlsmd(true)
1111 .with_lsmaoe(true)
1112 .into(),
1113 ))?;
1114 import_reg(Aarch64Register::TcrEl1(
1115 TranslationControlEl1::new()
1116 .with_t0sz(0x11)
1117 .with_irgn0(1)
1118 .with_orgn0(1)
1119 .with_sh0(3)
1120 .with_tg0(TranslationGranule0::TG_4KB)
1121 .with_epd0(1)
1123 .with_epd1(1)
1125 .with_tg1(TranslationGranule1::TG_4KB)
1127 .with_ips(IntermPhysAddrSize::IPA_48_BITS_256_TB)
1128 .into(),
1129 ))?;
1130 import_reg(Aarch64Register::Ttbr0El1(TranslationBaseEl1::new().into()))?;
1131 import_reg(Aarch64Register::Ttbr1El1(TranslationBaseEl1::new().into()))?;
1132 import_reg(Aarch64Register::VbarEl1(0))?;
1133
1134 if let Some(dtb) = &load_info.dtb {
1135 import_reg(Aarch64Register::X0(dtb.start))?;
1136 }
1137
1138 Ok(())
1139}
1140#[cfg(test)]
1141mod tests {
1142 use super::*;
1143 use crate::importer::IgvmParameterType;
1144 use crate::importer::IsolationConfig;
1145 use crate::importer::ParameterAreaIndex;
1146 use crate::importer::StartupMemoryType;
1147 use test_with_tracing::test;
1148 use zerocopy::FromBytes;
1149
1150 const MB: u64 = 0x100000;
1151 const GB: u64 = 0x4000_0000;
1152
1153 fn make_layout(ram_size: u64) -> MemoryLayout {
1157 MemoryLayout::new(
1158 ram_size,
1159 &[MemoryRange::new(4 * GB - 128 * MB..4 * GB)],
1160 &[],
1161 &[],
1162 None,
1163 )
1164 .unwrap()
1165 }
1166
1167 fn assert_contiguous(p: &defs::boot_params, first_ram_end: u64) {
1172 let entries = p.e820_entries as usize;
1173 assert!(entries > 0);
1174 let mut expected_addr = 0u64;
1175 for i in 0..entries {
1176 let e = &p.e820_map[i];
1177 assert_ne!(u64::from(e.size), 0, "entry {i} is empty");
1178 assert_eq!(u64::from(e.addr), expected_addr, "gap/overlap at entry {i}");
1179 expected_addr = u64::from(e.addr) + u64::from(e.size);
1180 }
1181 assert_eq!(
1182 expected_addr, first_ram_end,
1183 "map ends at {expected_addr:#x}, expected {first_ram_end:#x}"
1184 );
1185 }
1186
1187 #[test]
1188 fn zero_page_layout_with_smbios() {
1189 let acpi_len = 0x1800; let smbios_len = 0x100; let p = build_zero_page(
1192 &make_layout(256 * MB),
1193 acpi_len,
1194 smbios_len,
1195 0,
1196 &CString::new("root=/dev/sda").unwrap(),
1197 0,
1198 0,
1199 None,
1200 )
1201 .unwrap()
1202 .boot_params;
1203
1204 let acpi_end = ACPI_TABLES_BASE + 0x2000;
1205 let smbios_end = acpi_end + 0x1000;
1206 let expected = [
1207 (0, ACPI_TABLES_BASE, defs::E820_RAM),
1208 (ACPI_TABLES_BASE, 0x2000, defs::E820_ACPI),
1209 (acpi_end, 0x1000, defs::E820_RESERVED),
1210 (smbios_end, RSDP_BASE - smbios_end, defs::E820_RAM),
1211 (RSDP_BASE, 0x100000 - RSDP_BASE, defs::E820_RESERVED),
1212 (0x100000, 256 * MB - 0x100000, defs::E820_RAM),
1213 ];
1214 assert_eq!(p.e820_entries as usize, expected.len());
1215 for (i, (addr, size, typ)) in expected.iter().enumerate() {
1216 let e = &p.e820_map[i];
1217 assert_eq!(u64::from(e.addr), *addr, "entry {i} addr");
1218 assert_eq!(u64::from(e.size), *size, "entry {i} size");
1219 assert_eq!(u32::from(e.typ), *typ, "entry {i} type");
1220 }
1221 assert_contiguous(&p, 256 * MB);
1222 }
1223
1224 #[test]
1225 fn zero_page_skips_empty_smbios_region() {
1226 let p = build_zero_page(
1229 &make_layout(256 * MB),
1230 0x1800,
1231 0,
1232 0,
1233 &CString::new("").unwrap(),
1234 0,
1235 0,
1236 None,
1237 )
1238 .unwrap()
1239 .boot_params;
1240
1241 let acpi_end = ACPI_TABLES_BASE + 0x2000;
1242 let expected = [
1243 (0, ACPI_TABLES_BASE, defs::E820_RAM),
1244 (ACPI_TABLES_BASE, 0x2000, defs::E820_ACPI),
1245 (acpi_end, RSDP_BASE - acpi_end, defs::E820_RAM),
1246 (RSDP_BASE, 0x100000 - RSDP_BASE, defs::E820_RESERVED),
1247 (0x100000, 256 * MB - 0x100000, defs::E820_RAM),
1248 ];
1249 assert_eq!(p.e820_entries as usize, expected.len());
1250 for (i, (addr, size, typ)) in expected.iter().enumerate() {
1251 let e = &p.e820_map[i];
1252 assert_eq!(u64::from(e.addr), *addr, "entry {i} addr");
1253 assert_eq!(u64::from(e.size), *size, "entry {i} size");
1254 assert_eq!(u32::from(e.typ), *typ, "entry {i} type");
1255 }
1256 assert_contiguous(&p, 256 * MB);
1257 }
1258
1259 #[test]
1260 fn zero_page_multiple_ram_ranges() {
1261 let p = build_zero_page(
1264 &make_layout(8 * GB),
1265 0x1000,
1266 0x1000,
1267 0,
1268 &CString::new("").unwrap(),
1269 0,
1270 0,
1271 None,
1272 )
1273 .unwrap()
1274 .boot_params;
1275 assert_eq!(p.e820_entries, 7);
1276 let last = &p.e820_map[6];
1277 assert_eq!(u64::from(last.addr), 4 * GB);
1278 assert_eq!(u32::from(last.typ), defs::E820_RAM);
1279 let below_gap = &p.e820_map[5];
1283 assert_eq!(
1284 u64::from(below_gap.addr) + u64::from(below_gap.size),
1285 4 * GB - 128 * MB
1286 );
1287 }
1288
1289 #[test]
1290 fn zero_page_tables_too_large() {
1291 let result = build_zero_page(
1293 &make_layout(256 * MB),
1294 (RSDP_BASE - ACPI_TABLES_BASE) as usize + 0x1000,
1295 0,
1296 0,
1297 &CString::new("").unwrap(),
1298 0,
1299 0,
1300 None,
1301 );
1302 match result {
1303 Err(Error::LowTablesTooLarge(..)) => {}
1304 other => panic!("expected LowTablesTooLarge, got {:?}", other.err()),
1305 }
1306 }
1307
1308 #[derive(Default)]
1312 struct RecordingImporter {
1313 pages: Vec<(String, u64, u64)>,
1315 imports: Vec<ImportRecord>,
1316 vp_context_page: Option<u64>,
1317 }
1318
1319 #[derive(Debug)]
1320 struct ImportRecord {
1321 page_base: u64,
1322 page_count: u64,
1323 tag: String,
1324 acceptance: BootPageAcceptance,
1325 data: Vec<u8>,
1326 }
1327
1328 impl RecordingImporter {
1329 fn page_base(&self, tag: &str) -> Option<u64> {
1330 self.pages
1331 .iter()
1332 .find(|(t, ..)| t == tag)
1333 .map(|(_, base, _)| *base)
1334 }
1335 }
1336
1337 impl ImageLoad<X86Register> for RecordingImporter {
1338 fn isolation_config(&self) -> IsolationConfig {
1339 IsolationConfig {
1340 paravisor_present: false,
1341 isolation_type: crate::importer::IsolationType::None,
1342 shared_gpa_boundary_bits: None,
1343 }
1344 }
1345
1346 fn create_parameter_area(
1347 &mut self,
1348 _page_base: u64,
1349 _page_count: u32,
1350 _debug_tag: &str,
1351 ) -> anyhow::Result<ParameterAreaIndex> {
1352 unimplemented!()
1353 }
1354
1355 fn create_parameter_area_with_data(
1356 &mut self,
1357 _page_base: u64,
1358 _page_count: u32,
1359 _debug_tag: &str,
1360 _initial_data: &[u8],
1361 ) -> anyhow::Result<ParameterAreaIndex> {
1362 unimplemented!()
1363 }
1364
1365 fn import_parameter(
1366 &mut self,
1367 _parameter_area: ParameterAreaIndex,
1368 _byte_offset: u32,
1369 _parameter_type: IgvmParameterType,
1370 ) -> anyhow::Result<()> {
1371 unimplemented!()
1372 }
1373
1374 fn import_pages(
1375 &mut self,
1376 page_base: u64,
1377 page_count: u64,
1378 debug_tag: &'static str,
1379 acceptance: BootPageAcceptance,
1380 data: &[u8],
1381 ) -> anyhow::Result<()> {
1382 self.pages
1383 .push((debug_tag.to_string(), page_base, page_count));
1384 self.imports.push(ImportRecord {
1385 page_base,
1386 page_count,
1387 tag: debug_tag.to_string(),
1388 acceptance,
1389 data: data.to_vec(),
1390 });
1391 Ok(())
1392 }
1393
1394 fn import_vp_register(&mut self, _register: X86Register) -> anyhow::Result<()> {
1395 Ok(())
1396 }
1397
1398 fn verify_startup_memory_available(
1399 &mut self,
1400 _page_base: u64,
1401 _page_count: u64,
1402 _memory_type: StartupMemoryType,
1403 ) -> anyhow::Result<()> {
1404 Ok(())
1405 }
1406
1407 fn set_vp_context_page(&mut self, page_base: u64) -> anyhow::Result<()> {
1408 self.vp_context_page = Some(page_base);
1409 Ok(())
1410 }
1411
1412 fn relocation_region(
1413 &mut self,
1414 _gpa: u64,
1415 _size_bytes: u64,
1416 _relocation_alignment: u64,
1417 _minimum_relocation_gpa: u64,
1418 _maximum_relocation_gpa: u64,
1419 _apply_rip_offset: bool,
1420 _apply_gdtr_offset: bool,
1421 _vp_index: u16,
1422 ) -> anyhow::Result<()> {
1423 unimplemented!()
1424 }
1425
1426 fn page_table_relocation(
1427 &mut self,
1428 _page_table_gpa: u64,
1429 _size_pages: u64,
1430 _used_pages: u64,
1431 _vp_index: u16,
1432 ) -> anyhow::Result<()> {
1433 unimplemented!()
1434 }
1435
1436 fn set_imported_regions_config_page(&mut self, _page_base: u64) {
1437 unimplemented!()
1438 }
1439 }
1440
1441 fn test_load_info() -> LoadInfo {
1442 LoadInfo {
1443 kernel: KernelInfo {
1444 gpa: KERNEL_BASE,
1445 size: 0x1000,
1446 entrypoint: KERNEL_BASE,
1447 },
1448 initrd: None,
1449 dtb: None,
1450 bzimage_setup_header: None,
1451 }
1452 }
1453
1454 #[test]
1455 fn import_config_places_tables_at_fixed_addresses() {
1456 let acpi = AcpiTables {
1457 rsdp: vec![0u8; 0x1000],
1458 tables: vec![0u8; 0x1800],
1459 };
1460 let smbios = crate::smbios::BuiltSmbios {
1461 entry_point: vec![0u8; crate::smbios::ENTRY_POINT_SIZE],
1462 structure_table: vec![0u8; 0x100],
1463 };
1464 let mut importer = RecordingImporter::default();
1465 import_config(
1466 &mut importer,
1467 &test_load_info(),
1468 &CString::new("console=ttyS0").unwrap(),
1469 &make_layout(256 * MB),
1470 &acpi,
1471 Some(&smbios),
1472 None,
1473 )
1474 .unwrap();
1475
1476 assert_eq!(
1479 importer.page_base("linux-rsdp"),
1480 Some(RSDP_BASE / HV_PAGE_SIZE)
1481 );
1482 assert_eq!(
1483 importer.page_base("linux-acpi-tables"),
1484 Some(ACPI_TABLES_BASE / HV_PAGE_SIZE)
1485 );
1486 assert_eq!(
1489 importer.page_base("linux-smbios-anchor"),
1490 Some(SMBIOS_FSEGMENT_BASE / HV_PAGE_SIZE)
1491 );
1492 assert_eq!(
1493 importer.page_base("linux-smbios-tables"),
1494 Some(smbios_struct_table_base(acpi.tables.len()) / HV_PAGE_SIZE)
1495 );
1496 assert_eq!(
1498 importer.page_base("linux-zeropage"),
1499 Some(ZERO_PAGE_BASE / HV_PAGE_SIZE)
1500 );
1501 assert_eq!(
1502 importer.page_base("linux-commandline"),
1503 Some(CMDLINE_BASE / HV_PAGE_SIZE)
1504 );
1505 assert_eq!(
1506 importer.page_base("linux-pagetables"),
1507 Some(CR3_BASE / HV_PAGE_SIZE)
1508 );
1509 }
1510
1511 #[test]
1512 fn import_config_rejects_oversized_command_line() {
1513 let acpi = AcpiTables {
1514 rsdp: vec![0u8; 0x1000],
1515 tables: vec![0u8; 0x1000],
1516 };
1517 let cmdline = CString::new(vec![b'a'; (CR3_BASE - CMDLINE_BASE) as usize]).unwrap();
1519 let mut importer = RecordingImporter::default();
1520 let err = import_config(
1521 &mut importer,
1522 &test_load_info(),
1523 &cmdline,
1524 &make_layout(256 * MB),
1525 &acpi,
1526 None,
1527 None,
1528 )
1529 .unwrap_err();
1530 assert!(matches!(err, Error::CommandLineTooLong(..)), "got {err:?}");
1531 assert!(importer.pages.is_empty(), "importer used before the check");
1532 }
1533
1534 #[test]
1535 fn import_config_sets_snp_c_bit_in_page_tables() {
1536 const C_BIT: u8 = 51;
1537 let acpi = AcpiTables {
1538 rsdp: vec![0u8; 0x1000],
1539 tables: vec![0u8; 0x1000],
1540 };
1541 let mut importer = RecordingImporter::default();
1542 import_config(
1543 &mut importer,
1544 &test_load_info(),
1545 &CString::new("").unwrap(),
1546 &make_layout(256 * MB),
1547 &acpi,
1548 None,
1549 Some(SnpBootConfig { c_bit: C_BIT }),
1550 )
1551 .unwrap();
1552
1553 let page_tables = importer
1554 .imports
1555 .iter()
1556 .find(|import| import.tag == "linux-pagetables")
1557 .unwrap();
1558 for entry in page_tables.data.chunks_exact(8).map(|entry| {
1559 u64::from_ne_bytes(entry.try_into().expect("page table entry is eight bytes"))
1560 }) {
1561 if entry & 1 != 0 {
1562 assert_ne!(entry & (1 << C_BIT), 0);
1563 }
1564 }
1565 }
1566
1567 #[test]
1568 fn import_config_rejects_empty_acpi_tables() {
1569 let acpi = AcpiTables {
1572 rsdp: vec![0u8; 0x1000],
1573 tables: Vec::new(),
1574 };
1575 let mut importer = RecordingImporter::default();
1576 let err = import_config(
1577 &mut importer,
1578 &test_load_info(),
1579 &CString::new("").unwrap(),
1580 &make_layout(256 * MB),
1581 &acpi,
1582 None,
1583 None,
1584 )
1585 .unwrap_err();
1586 assert!(matches!(err, Error::EmptyAcpiTables), "got {err:?}");
1587 }
1588
1589 #[test]
1590 fn imports_snp_boot_pages_with_linux_cc_blob() {
1591 let acpi_len = 0x1800;
1592 let smbios_len = 0x100;
1593 let ZeroPageBuildResult {
1594 boot_params,
1595 additional_pages,
1596 } = build_zero_page(
1597 &make_layout(256 * MB),
1598 acpi_len,
1599 smbios_len,
1600 SNP_BOOT_PAGE_COUNT,
1601 &CString::new("").unwrap(),
1602 0,
1603 0,
1604 None,
1605 )
1606 .unwrap();
1607 let allocated_range = additional_pages.unwrap();
1608 let mut importer = RecordingImporter::default();
1609
1610 let cc_setup_data_address = import_snp_boot_pages(&mut importer, allocated_range).unwrap();
1611
1612 assert_eq!(importer.imports.len(), 4);
1613 assert_eq!(
1614 allocated_range.start(),
1615 ACPI_TABLES_BASE
1616 + align_up_to_page_size(acpi_len as u64)
1617 + align_up_to_page_size(smbios_len as u64)
1618 );
1619 assert_eq!(
1620 importer.imports[0].page_base,
1621 allocated_range.start() / HV_PAGE_SIZE
1622 );
1623 assert_eq!(importer.imports[0].page_count, 1);
1624 assert_eq!(importer.imports[0].tag, "linux-snp-secrets");
1625 assert_eq!(
1626 importer.imports[0].acceptance,
1627 BootPageAcceptance::SecretsPage
1628 );
1629 assert_eq!(
1630 importer.imports[1].page_base,
1631 allocated_range.start() / HV_PAGE_SIZE + 1
1632 );
1633 assert_eq!(
1634 importer.imports[1].acceptance,
1635 BootPageAcceptance::CpuidPage
1636 );
1637 let cpuid_page =
1638 crate::cpuid::HV_PSP_CPUID_PAGE::read_from_bytes(&importer.imports[1].data).unwrap();
1639 assert_eq!(
1640 cpuid_page.count as usize,
1641 crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR.len()
1642 );
1643 for (entry, leaf) in cpuid_page
1644 .cpuid_leaf_info
1645 .iter()
1646 .zip(crate::cpuid::SNP_REQUIRED_CPUID_LEAF_LIST_PARAVISOR)
1647 {
1648 assert_eq!(entry.eax_in, leaf.eax);
1649 assert_eq!(entry.ecx_in, leaf.ecx);
1650 assert_eq!(
1651 entry.xfem_in,
1652 u64::from(leaf.eax == CpuidFunction::ExtendedStateEnumeration.0 && leaf.ecx <= 1)
1653 );
1654 assert_eq!(entry.xss_in, 0);
1655 }
1656
1657 let cc_blob = defs::cc_blob_sev_info::read_from_bytes(&importer.imports[2].data).unwrap();
1658 assert_eq!(cc_blob.magic, defs::CC_BLOB_SEV_INFO_MAGIC);
1659 assert_eq!(cc_blob.version, 0);
1660 assert_eq!(cc_blob.secrets_phys, allocated_range.start());
1661 assert_eq!(cc_blob.secrets_len, HV_PAGE_SIZE as u32);
1662 assert_eq!(cc_blob.cpuid_phys, allocated_range.start() + HV_PAGE_SIZE);
1663 assert_eq!(cc_blob.cpuid_len, HV_PAGE_SIZE as u32);
1664
1665 let cc_setup_data =
1666 defs::cc_setup_data::read_from_bytes(&importer.imports[3].data).unwrap();
1667 assert_eq!(cc_setup_data.header.next, 0);
1668 assert_eq!(cc_setup_data.header.ty, defs::SETUP_CC_BLOB);
1669 assert_eq!(
1670 cc_setup_data.header.len,
1671 (size_of::<defs::cc_setup_data>() - size_of::<defs::setup_data>()) as u32
1672 );
1673 assert_eq!(
1674 cc_setup_data.cc_blob_address,
1675 u32::try_from(allocated_range.start() + 2 * HV_PAGE_SIZE).unwrap()
1676 );
1677 assert_eq!(
1678 importer.vp_context_page,
1679 Some(allocated_range.start() / HV_PAGE_SIZE + 4)
1680 );
1681 assert_eq!(
1682 allocated_range.end(),
1683 cc_setup_data_address + 2 * HV_PAGE_SIZE,
1684 );
1685 assert!(allocated_range.end() <= RSDP_BASE);
1686
1687 let smbios_reserved = &boot_params.e820_map[2];
1688 let acpi_end = ACPI_TABLES_BASE + align_up_to_page_size(acpi_len as u64);
1689 assert_eq!(u64::from(smbios_reserved.addr), acpi_end);
1690 assert_eq!(
1691 u64::from(smbios_reserved.size),
1692 align_up_to_page_size(smbios_len as u64)
1693 );
1694 let additional_reserved = &boot_params.e820_map[3];
1695 assert_eq!(u64::from(additional_reserved.addr), allocated_range.start());
1696 assert_eq!(u64::from(additional_reserved.size), allocated_range.len());
1697
1698 assert!(matches!(
1699 build_zero_page(
1700 &make_layout(256 * MB),
1701 (RSDP_BASE - ACPI_TABLES_BASE) as usize,
1702 0,
1703 SNP_BOOT_PAGE_COUNT,
1704 &CString::new("").unwrap(),
1705 0,
1706 0,
1707 None,
1708 ),
1709 Err(Error::LowTablesTooLarge(..))
1710 ));
1711 }
1712}