Skip to main content

loader/
linux.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Linux specific loader definitions and implementation.
5
6use 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
54/// Construct a zero page from the following parameters.
55fn 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    // Loader type 0xff = unregistered bootloader, used for both ELF and
66    // bzImage paths since OpenVMM does not have a registered Linux
67    // bootloader ID.
68    const LOADER_TYPE_UNREGISTERED: u8 = 0xff;
69
70    // Start with the bzImage setup header if available, otherwise build
71    // a minimal default header.
72    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    // Set bootloader-owned fields regardless of kernel format.
83    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    // x86 low-memory layout for direct boot:
102    //   [0, acpi_base)          RAM       boot metadata: GDT, zero page, cmdline,
103    //                                     identity-map page tables
104    //   [acpi_base, acpi_end)   ACPI      RSDT/XSDT and all ACPI tables
105    //   [acpi_end, smbios_end)  RESERVED  SMBIOS structure table
106    //   [smbios_end, additional_end) RESERVED additional requested pages
107    //   [additional_end, 0xe0000)    RAM
108    //   [0xe0000, 0x100000)     RESERVED  legacy BIOS region holding the RSDP at
109    //                                     0xe0000 (found by the kernel's legacy
110    //                                     scan) and the SMBIOS _SM3_ anchor at
111    //                                     0xf0000 (found by the kernel's DMI scan)
112    //   [0x100000, end)         RAM
113    //
114    // The RSDP lives at the fixed 0xe0000 and the tables it points to live in
115    // reclaimable ACPI memory below; the kernel discovers the RSDP via its
116    // legacy scan, so no `acpi_rsdp_addr` (Linux 5.0+) is required. The SMBIOS
117    // structure table sits just above the ACPI tables in its own reserved
118    // region so it can grow well past the 64 KiB F-segment.
119    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    // Emit the e820 entries in ascending address order. Zero-length regions
137    // (e.g. the SMBIOS reserved region when no SMBIOS tables are present) are
138    // skipped, and the fixed-size map is bounds-checked so an over-long memory
139    // layout is reported rather than panicking.
140    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
226/// ACPI tables to place in guest memory: a one-page RSDP plus the tables it
227/// points to.
228///
229/// Produced by the caller-supplied builder passed to [`load_x86`] /
230/// [`load_config_x86`]. The builder is handed a nominal RSDP address `gpa` and
231/// must return `tables` that are self-consistent for placement at `gpa +
232/// 0x1000`; the loader then re-homes the RSDP to the fixed legacy-scan address.
233pub struct AcpiTables {
234    /// The RSDP. Given a whole page.
235    pub rsdp: Vec<u8>,
236    /// The remaining tables pointed to by the RSDP.
237    pub tables: Vec<u8>,
238}
239
240// The loader owns the entire sub-1 MB x86 direct-boot memory map so that
241// callers supply only table *contents*, never addresses. The resulting e820
242// map (see `build_zero_page`) is:
243//
244//   [0, ACPI_TABLES_BASE)          RAM       GDT, zero page, cmdline, page tables
245//   [ACPI_TABLES_BASE, acpi_end)   ACPI      RSDT/XSDT and all ACPI tables
246//   [acpi_end, smbios_end)         RESERVED  SMBIOS structure table
247//   [smbios_end, RSDP_BASE)        RAM
248//   [RSDP_BASE, 0x100000)          RESERVED  RSDP (0xe0000) + _SM3_ anchor (0xf0000)
249//   [0x100000, end)                RAM       kernel and beyond
250const GDT_BASE: u64 = 0x1000;
251const ZERO_PAGE_BASE: u64 = 0x2000;
252const CMDLINE_BASE: u64 = 0x3000;
253const CR3_BASE: u64 = 0x4000;
254/// The identity-map page tables occupy `[CR3_BASE, CR3_BASE + PAGE_TABLE_MAX_BYTES)`;
255/// the boot metadata ends there.
256const LOW_METADATA_END: u64 = CR3_BASE + PAGE_TABLE_MAX_BYTES as u64;
257/// The ACPI builder is handed `LOW_METADATA_END` as a nominal RSDP page, so its
258/// tables live one page above.
259const ACPI_TABLES_BASE: u64 = LOW_METADATA_END + 0x1000;
260/// The RSDP is pinned at the fixed 0xe0000 so the kernel's legacy RSDP scan of
261/// `[0xe0000, 0x100000)` finds it, with no dependency on
262/// `boot_params.acpi_rsdp_addr` (Linux 5.0+).
263const RSDP_BASE: u64 = 0xe0000;
264/// The x86 kernel brute-force scans the F-segment `[0xf0000, 0x100000)` for the
265/// SMBIOS `_SM3_` DMI anchor, so the 24-byte entry point is pinned there. Its
266/// 64-bit structure-table pointer lets the (potentially large) structure table
267/// live in the low reserved area instead — see [`smbios_struct_table_base`].
268const SMBIOS_FSEGMENT_BASE: u64 = 0xf0000;
269/// The Linux x86 kernel loads at the conventional 1 MB mark.
270const KERNEL_BASE: u64 = 0x100000;
271
272/// Enables allocation of the SEV-SNP Linux boot protocol pages.
273#[derive(Debug, Clone, Copy)]
274pub struct SnpBootConfig {
275    /// The page-table bit that marks private memory.
276    pub c_bit: u8,
277}
278
279const SNP_BOOT_PAGE_COUNT: u64 = 5;
280
281/// The GPA of the SMBIOS structure table: immediately above the ACPI tables in
282/// the low reserved area. Only the `_SM3_` anchor stays in the F-segment; the
283/// structure table lives here, reachable via the anchor's 64-bit pointer, so it
284/// can grow well past the 64 KiB F-segment.
285fn 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
289// Compile-time check that the fixed low-memory layout constants are ordered and
290// non-overlapping. A violation here is a code bug, caught at build time.
291const _: () = {
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    /// Load the initrd after the kernel at the next 2MB aligned address.
303    AfterKernel,
304    /// Load the initrd at the specified address.
305    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/// Information returned about the kernel loaded.
315#[derive(Debug, Default)]
316pub struct KernelInfo {
317    /// The base gpa the kernel was loaded at.
318    pub gpa: u64,
319    /// The size in bytes of the region the kernel was loaded at.
320    pub size: u64,
321    /// The gpa of the entrypoint of the kernel.
322    pub entrypoint: u64,
323}
324
325/// Information returned about the initrd loaded.
326#[derive(Debug, Default)]
327pub struct InitrdInfo {
328    /// The gpa the initrd was loaded at.
329    pub gpa: u64,
330    /// The size in bytes of the initrd loaded. Note that the region imported is aligned up to page size.
331    pub size: u64,
332}
333
334/// Information returned about where certain parts were loaded.
335#[derive(Debug, Default)]
336pub struct LoadInfo {
337    /// The information about the kernel loaded.
338    pub kernel: KernelInfo,
339    /// The information about the initrd loaded.
340    pub initrd: Option<InitrdInfo>,
341    /// The information about the device tree blob loaded.
342    pub dtb: Option<std::ops::Range<u64>>,
343    /// If a bzImage was loaded, the original setup header from the image.
344    /// This must be placed into the zero page so the kernel's startup code
345    /// can read its own configuration.
346    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    // TODO: Decide whether the paravisor leaf list is correct for enlightened
371    // direct-boot Linux SNP guests. It currently provides the extended-state
372    // subleaves Linux needs.
373    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
444/// Check if an address is aligned to a page.
445fn 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
453/// Import initrd
454fn 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
494/// Load only a Linux kernel and optional initrd to VTL0.
495/// This does not setup register state or any other config information.
496///
497/// The kernel image may be either an uncompressed ELF (`vmlinux`) or a
498/// compressed bzImage. If a bzImage is detected, the bzImage payload is
499/// loaded directly into guest memory and the kernel's own decompressor
500/// runs at boot time.
501///
502/// # Arguments
503///
504/// * `importer` - The importer to use.
505/// * `kernel_image` - Kernel image (uncompressed ELF or bzImage).
506/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
507///   It cannot contain an entrypoint or program headers that refer to memory below this address.
508/// * `initrd` - The initrd config, optional.
509pub 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
557/// Load a bzImage by placing its payload directly into guest memory at the
558/// load address and following the Linux boot protocol. The kernel's built-in
559/// decompressor handles the rest at boot time.
560fn 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    // Place initrd after the kernel's init_size region to avoid being
599    // overwritten during decompression.
600    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
620/// Import the boot metadata, ACPI/SMBIOS tables, zero page, and initial
621/// registers for a kernel already described by `load_info`.
622///
623/// Internal helper shared by [`load_x86`] and [`load_config_x86`]. All guest
624/// addresses come from the module-level layout constants; callers supply only
625/// the table contents.
626fn 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    // Only import the cmdline if it actually contains something.
636    // TODO: This should use the IGVM parameter instead?
637    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    // Set common X64 registers. Segments already set by default gdt.
733    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    // Set rip to entry point and rsi to zero page.
751    import_reg(X86Register::Rip(load_info.kernel.entrypoint))?;
752    import_reg(X86Register::Rsi(ZERO_PAGE_BASE))?;
753
754    // No firmware will set MTRR values for the BSP.  Replicate what UEFI does here.
755    // (enable MTRRs, default MTRR is uncached, and set lowest 640KB as WB)
756    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        // The `_SM3_` entry point (anchor) goes in the F-segment for the
762        // kernel's DMI scan; its 64-bit pointer targets the structure table in
763        // the low reserved area just above the ACPI tables.
764        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
791/// Place the ACPI tables, SMBIOS tables, boot metadata, zero page, and initial
792/// registers for a Linux kernel that has *already* been loaded into guest
793/// memory (as described by `load_info`).
794///
795/// The loader owns the entire sub-1 MB memory map; callers supply only
796/// contents, never addresses:
797///
798/// * `cmdline` - the kernel command line.
799/// * `mem_layout` - the guest memory layout, used to build the e820 map.
800/// * `build_acpi` - a builder handed the nominal RSDP address; it must return
801///   ACPI tables self-consistent for placement one page above that address.
802///   The loader re-homes the RSDP to the fixed 0xe0000 legacy-scan location.
803/// * `smbios` - an optional SMBIOS identity; when present the loader assembles
804///   the `_SM3_` entry point and structure table into the F-segment.
805/// * `snp_boot` - optionally allocate SEV-SNP Linux boot protocol pages.
806pub 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    // The builder lays out a nominal RSDP page at LOW_METADATA_END followed by
816    // the tables it points to; we keep only the tables (placed at
817    // ACPI_TABLES_BASE) and re-home the RSDP to the fixed scan location.
818    let acpi_tables = build_acpi(LOW_METADATA_END);
819
820    // Build the SMBIOS tables (if an identity was supplied) with the structure
821    // table addressed at its low-area home: the `_SM3_` anchor's 64-bit pointer
822    // references it there while the anchor itself lands in the F-segment for the
823    // kernel's DMI scan. See `smbios_struct_table_base` / `import_config`.
824    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
839/// Load a Linux kernel into VTL0 and place all of its supporting structures.
840///
841/// Loads the kernel (uncompressed ELF or bzImage) and optional initrd at the
842/// conventional 1 MB address, then delegates to [`load_config_x86`] to place
843/// the ACPI/SMBIOS tables, boot metadata, zero page, and initial registers.
844/// See [`load_config_x86`] for the `build_acpi`/`smbios` contract.
845pub 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/// Arm64 flat kernel `Image` flags.
887#[bitfield(u64)]
888struct Aarch64ImageFlags {
889    /// Bit 0:	Kernel endianness.  1 if BE, 0 if LE.
890    #[bits(1)]
891    pub big_endian: bool,
892    /// Bit 1-2:	Kernel Page size.
893    ///           0 - Unspecified.
894    ///           1 - 4K
895    ///           2 - 16K
896    ///           3 - 64K
897    #[bits(2)]
898    pub page_size: Aarch64ImagePageSize,
899    /// Bit 3:	Kernel physical placement
900    ///           0 - 2MB aligned base should be as close as possible
901    ///               to the base of DRAM, since memory below it is not
902    ///               accessible via the linear mapping
903    ///           1 - 2MB aligned base may be anywhere in physical
904    ///               memory
905    #[bits(1)]
906    pub any_start_address: bool,
907    /// Bits 4-63:	Reserved.
908    #[bits(60)]
909    pub _padding: u64,
910}
911
912// Kernel boot protocol is specified in the Linux kernel
913// Documentation/arm64/booting.txt.
914#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
915#[repr(C)]
916struct Aarch64ImageHeader {
917    /// Executable code
918    _code0: u32,
919    /// Executable code
920    _code1: u32,
921    /// Image load offset, little endian
922    text_offset: u64,
923    /// Effective Image size, little endian
924    image_size: u64,
925    /// kernel flags, little endian
926    flags: u64,
927    /// reserved
928    _res2: u64,
929    /// reserved
930    _res3: u64,
931    /// reserved
932    _res4: u64,
933    /// Magic number, little endian, "ARM\x64"
934    magic: [u8; 4],
935    /// reserved (used for PE COFF offset)
936    _res5: u32,
937}
938
939const AARCH64_MAGIC_NUMBER: &[u8] = b"ARM\x64";
940
941/// Load only an arm64 the flat Linux kernel `Image` and optional initrd.
942/// This does not setup register state or any other config information.
943///
944/// # Arguments
945///
946/// * `importer` - The importer to use.
947/// * `kernel_image` - Uncompressed ELF image for the kernel.
948/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
949///   It cannot contain an entrypoint or program headers that refer to memory below this address.
950/// * `initrd` - The initrd config, optional.
951/// * `device_tree_blob` - The device tree blob, optional.
952pub 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    // The `Image` must be placed `text_offset` bytes from a 2MB aligned base
999    // address anywhere in usable system RAM and called there.
1000
1001    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
1069/// Load the configuration info and registers for the Linux kernel based on the provided LoadInfo.
1070/// Parameters:
1071/// * `importer` - The importer to use.
1072/// * `load_info` - The kernel load info that contains information on where the kernel and initrd are.
1073/// * `vtl` - The target VTL.
1074pub 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            // MMU is disabled for EL1&0 stage 1 address translation.
1098            // The family of the `at` instructions and the `PAR_EL1` register are
1099            // useful for debugging MMU issues when it's on.
1100            .with_m(false)
1101            // Stage 1 Cacheability control, for data accesses.
1102            .with_c(true)
1103            // Stage 1 Cacheability control, for code.
1104            .with_i(true)
1105            // Reserved flags, must be set
1106            .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            // Disable TTBR0_EL1 walks (i.e. the lower half).
1122            .with_epd0(1)
1123            // Disable TTBR1_EL1 walks (i.e. the upper half).
1124            .with_epd1(1)
1125            // Due to erratum #822227, need to set a valid TG1 regardless of EPD1.
1126            .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    /// A guest memory layout with `ram_size` bytes of RAM and a 128 MB MMIO gap
1154    /// below 4 GiB (matching a typical x86_64 config). RAM up to the gap forms
1155    /// the first range `[0, min(ram_size, 4 GiB - 128 MB))`.
1156    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    /// Asserts that `map[..entries]` exactly covers `[0, first_ram_end)` with no
1168    /// gaps or overlaps and strictly ascending addresses, and that no entry is
1169    /// empty. The first entry must start at 0 and the last must end precisely at
1170    /// `first_ram_end`.
1171    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; // aligns up to 0x2000
1190        let smbios_len = 0x100; // aligns up to 0x1000
1191        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        // With no SMBIOS structure table, the reserved SMBIOS region collapses
1227        // to zero length and must not appear as an empty e820 entry.
1228        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        // 8 GiB of RAM splits around the 4 GiB MMIO gap into two ranges; the
1262        // second appears after the six fixed low-memory entries.
1263        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        // The six fixed low-memory entries are contiguous from 0; the second
1280        // RAM range sits above the 4 GiB MMIO gap, so contiguity legitimately
1281        // breaks there.
1282        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        // ACPI tables large enough to run past the RSDP reserved region.
1292        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    /// An importer that records `import_pages` placements and accepts registers,
1309    /// panicking on any other entry point (none of which the Linux config path
1310    /// exercises).
1311    #[derive(Default)]
1312    struct RecordingImporter {
1313        /// `(debug_tag, page_base, page_count)` for each imported region.
1314        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        // The RSDP is re-homed to the fixed legacy-scan address, while the
1477        // tables it points to stay at the loader's chosen base.
1478        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        // The SMBIOS anchor lands in the F-segment; the structure table sits
1487        // just above the ACPI tables.
1488        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        // Boot metadata at its fixed low-memory homes.
1497        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        // One byte too long once the NUL terminator is added.
1518        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        // Empty tables would import zero pages; the loader must reject them
1570        // rather than feed page_count == 0 into the importer.
1571        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}