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 zerocopy::FromBytes;
43use zerocopy::FromZeros;
44use zerocopy::Immutable;
45use zerocopy::IntoBytes;
46use zerocopy::KnownLayout;
47
48struct ZeroPageBuildResult {
49    boot_params: defs::boot_params,
50    additional_pages: Option<MemoryRange>,
51}
52
53/// Construct a zero page from the following parameters.
54fn build_zero_page(
55    mem_layout: &MemoryLayout,
56    acpi_len: usize,
57    smbios_struct_len: usize,
58    additional_page_count: u64,
59    cmdline: &CString,
60    initrd_base: u32,
61    initrd_size: u32,
62    bzimage_header: Option<&defs::setup_header>,
63) -> Result<ZeroPageBuildResult, Error> {
64    // Loader type 0xff = unregistered bootloader, used for both ELF and
65    // bzImage paths since OpenVMM does not have a registered Linux
66    // bootloader ID.
67    const LOADER_TYPE_UNREGISTERED: u8 = 0xff;
68
69    // Start with the bzImage setup header if available, otherwise build
70    // a minimal default header.
71    let mut hdr = match bzimage_header {
72        Some(orig) => *orig,
73        None => defs::setup_header {
74            boot_flag: 0xaa55.into(),
75            header: 0x53726448.into(),
76            kernel_alignment: 0x100000.into(),
77            ..FromZeros::new_zeroed()
78        },
79    };
80
81    // Set bootloader-owned fields regardless of kernel format.
82    hdr.type_of_loader = LOADER_TYPE_UNREGISTERED;
83    hdr.cmd_line_ptr = CMDLINE_BASE.try_into().expect("must fit in u32");
84    hdr.cmdline_size = (cmdline.as_bytes().len() as u64)
85        .try_into()
86        .expect("must fit in u32");
87    hdr.ramdisk_image = initrd_base.into();
88    hdr.ramdisk_size = initrd_size.into();
89
90    let mut p = defs::boot_params {
91        hdr,
92        ..FromZeros::new_zeroed()
93    };
94
95    let mut ram = mem_layout.ram().iter().cloned();
96    let range = ram.next().expect("at least one ram range");
97    assert_eq!(range.range.start(), 0);
98    assert!(range.range.end() >= 0x100000);
99
100    // x86 low-memory layout for direct boot:
101    //   [0, acpi_base)          RAM       boot metadata: GDT, zero page, cmdline,
102    //                                     identity-map page tables
103    //   [acpi_base, acpi_end)   ACPI      RSDT/XSDT and all ACPI tables
104    //   [acpi_end, smbios_end)  RESERVED  SMBIOS structure table
105    //   [smbios_end, additional_end) RESERVED additional requested pages
106    //   [additional_end, 0xe0000)    RAM
107    //   [0xe0000, 0x100000)     RESERVED  legacy BIOS region holding the RSDP at
108    //                                     0xe0000 (found by the kernel's legacy
109    //                                     scan) and the SMBIOS _SM3_ anchor at
110    //                                     0xf0000 (found by the kernel's DMI scan)
111    //   [0x100000, end)         RAM
112    //
113    // The RSDP lives at the fixed 0xe0000 and the tables it points to live in
114    // reclaimable ACPI memory below; the kernel discovers the RSDP via its
115    // legacy scan, so no `acpi_rsdp_addr` (Linux 5.0+) is required. The SMBIOS
116    // structure table sits just above the ACPI tables in its own reserved
117    // region so it can grow well past the 64 KiB F-segment.
118    const ONE_MB: u64 = 0x100000;
119    let aligned_acpi_len = align_up_to_page_size(acpi_len as u64);
120    let acpi_end = ACPI_TABLES_BASE + aligned_acpi_len;
121    let aligned_smbios_len = align_up_to_page_size(smbios_struct_len as u64);
122    let smbios_end = acpi_end + aligned_smbios_len;
123    let additional_size = additional_page_count
124        .checked_mul(HV_PAGE_SIZE)
125        .ok_or(Error::LowTablesTooLarge(u64::MAX, RSDP_BASE))?;
126    let additional_end = smbios_end
127        .checked_add(additional_size)
128        .ok_or(Error::LowTablesTooLarge(u64::MAX, RSDP_BASE))?;
129    if additional_end > RSDP_BASE {
130        return Err(Error::LowTablesTooLarge(additional_end, RSDP_BASE));
131    }
132    let additional_pages =
133        (additional_size != 0).then(|| MemoryRange::new(smbios_end..additional_end));
134
135    // Emit the e820 entries in ascending address order. Zero-length regions
136    // (e.g. the SMBIOS reserved region when no SMBIOS tables are present) are
137    // skipped, and the fixed-size map is bounds-checked so an over-long memory
138    // layout is reported rather than panicking.
139    let e820_cap = p.e820_map.len();
140    let mut n = 0;
141    let mut push = |addr: u64, size: u64, typ: u32| -> Result<(), Error> {
142        if size == 0 {
143            return Ok(());
144        }
145        let entry = p
146            .e820_map
147            .get_mut(n)
148            .ok_or(Error::TooManyMemoryRanges(e820_cap))?;
149        *entry = defs::e820entry {
150            addr: addr.into(),
151            size: size.into(),
152            typ: typ.into(),
153        };
154        n += 1;
155        Ok(())
156    };
157    push(0, ACPI_TABLES_BASE, defs::E820_RAM)?;
158    push(ACPI_TABLES_BASE, aligned_acpi_len, defs::E820_ACPI)?;
159    push(acpi_end, aligned_smbios_len, defs::E820_RESERVED)?;
160    push(smbios_end, additional_size, defs::E820_RESERVED)?;
161    push(additional_end, RSDP_BASE - additional_end, defs::E820_RAM)?;
162    push(RSDP_BASE, ONE_MB - RSDP_BASE, defs::E820_RESERVED)?;
163    push(ONE_MB, range.range.end() - ONE_MB, defs::E820_RAM)?;
164    for range in ram {
165        push(range.range.start(), range.range.len(), defs::E820_RAM)?;
166    }
167    p.e820_entries = n as u8;
168
169    Ok(ZeroPageBuildResult {
170        boot_params: p,
171        additional_pages,
172    })
173}
174
175#[derive(Debug, Error)]
176pub enum FlatLoaderError {
177    #[error("unsupported ELF File byte order")]
178    BigEndianElfOnLittle,
179    #[error("error reading kernel data structure")]
180    BadImageMagic,
181    #[error("big-endian kernel image is not supported")]
182    BigEndianKernelImage,
183    #[error("only images with 4K pages are supported")]
184    FourKibPageImageIsRequired,
185    #[error("the kernel is required to run in the low memory; not supported")]
186    LowMemoryKernel,
187    #[error("failed to read kernel image")]
188    ReadKernelImage,
189    #[error("failed to seek to file offset as pointed by the ELF program header")]
190    SeekKernelStart,
191    #[error("failed to seek to offset of kernel image")]
192    SeekKernelImage,
193}
194
195#[derive(Debug, Error)]
196pub enum Error {
197    #[error("elf loader error")]
198    ElfLoader(#[source] crate::elf::Error),
199    #[error("bzImage parse error")]
200    BzImage(#[source] crate::bzimage::Error),
201    #[error("flat loader error")]
202    FlatLoader(#[source] FlatLoaderError),
203    #[error("Address is not page aligned")]
204    UnalignedAddress(u64),
205    #[error("importer error")]
206    Importer(#[source] anyhow::Error),
207    #[error("failed to import initrd")]
208    ImportInitrd(#[source] ImportFileRegionError),
209    #[error("failed to import bzImage payload")]
210    ImportBzImage(#[source] ImportFileRegionError),
211    #[error("PageTableBuilder: {0}")]
212    PageTableBuilder(#[from] page_table::Error),
213    #[error("kernel command line ({0} bytes) exceeds its {1:#x}-byte slot")]
214    CommandLineTooLong(usize, u64),
215    #[error("low-memory tables and boot pages end at {0:#x}, past the reserved region at {1:#x}")]
216    LowTablesTooLarge(u64, u64),
217    #[error("SNP CC blob address {0:#x} does not fit in the Linux boot protocol field")]
218    SnpCcBlobAddressTooHigh(u64),
219    #[error("too many memory ranges to fit in the {0}-entry e820 map")]
220    TooManyMemoryRanges(usize),
221    #[error("acpi tables are empty")]
222    EmptyAcpiTables,
223}
224
225/// ACPI tables to place in guest memory: a one-page RSDP plus the tables it
226/// points to.
227///
228/// Produced by the caller-supplied builder passed to [`load_x86`] /
229/// [`load_config_x86`]. The builder is handed a nominal RSDP address `gpa` and
230/// must return `tables` that are self-consistent for placement at `gpa +
231/// 0x1000`; the loader then re-homes the RSDP to the fixed legacy-scan address.
232pub struct AcpiTables {
233    /// The RSDP. Given a whole page.
234    pub rsdp: Vec<u8>,
235    /// The remaining tables pointed to by the RSDP.
236    pub tables: Vec<u8>,
237}
238
239// The loader owns the entire sub-1 MB x86 direct-boot memory map so that
240// callers supply only table *contents*, never addresses. The resulting e820
241// map (see `build_zero_page`) is:
242//
243//   [0, ACPI_TABLES_BASE)          RAM       GDT, zero page, cmdline, page tables
244//   [ACPI_TABLES_BASE, acpi_end)   ACPI      RSDT/XSDT and all ACPI tables
245//   [acpi_end, smbios_end)         RESERVED  SMBIOS structure table
246//   [smbios_end, RSDP_BASE)        RAM
247//   [RSDP_BASE, 0x100000)          RESERVED  RSDP (0xe0000) + _SM3_ anchor (0xf0000)
248//   [0x100000, end)                RAM       kernel and beyond
249const GDT_BASE: u64 = 0x1000;
250const ZERO_PAGE_BASE: u64 = 0x2000;
251const CMDLINE_BASE: u64 = 0x3000;
252const CR3_BASE: u64 = 0x4000;
253/// The identity-map page tables occupy `[CR3_BASE, CR3_BASE + PAGE_TABLE_MAX_BYTES)`;
254/// the boot metadata ends there.
255const LOW_METADATA_END: u64 = CR3_BASE + PAGE_TABLE_MAX_BYTES as u64;
256/// The ACPI builder is handed `LOW_METADATA_END` as a nominal RSDP page, so its
257/// tables live one page above.
258const ACPI_TABLES_BASE: u64 = LOW_METADATA_END + 0x1000;
259/// The RSDP is pinned at the fixed 0xe0000 so the kernel's legacy RSDP scan of
260/// `[0xe0000, 0x100000)` finds it, with no dependency on
261/// `boot_params.acpi_rsdp_addr` (Linux 5.0+).
262const RSDP_BASE: u64 = 0xe0000;
263/// The x86 kernel brute-force scans the F-segment `[0xf0000, 0x100000)` for the
264/// SMBIOS `_SM3_` DMI anchor, so the 24-byte entry point is pinned there. Its
265/// 64-bit structure-table pointer lets the (potentially large) structure table
266/// live in the low reserved area instead — see [`smbios_struct_table_base`].
267const SMBIOS_FSEGMENT_BASE: u64 = 0xf0000;
268/// The Linux x86 kernel loads at the conventional 1 MB mark.
269const KERNEL_BASE: u64 = 0x100000;
270
271/// Enables allocation of the SEV-SNP Linux boot protocol pages.
272#[derive(Debug, Clone, Copy)]
273pub struct SnpBootConfig;
274
275const SNP_BOOT_PAGE_COUNT: u64 = 4;
276
277/// The GPA of the SMBIOS structure table: immediately above the ACPI tables in
278/// the low reserved area. Only the `_SM3_` anchor stays in the F-segment; the
279/// structure table lives here, reachable via the anchor's 64-bit pointer, so it
280/// can grow well past the 64 KiB F-segment.
281fn smbios_struct_table_base(acpi_tables_len: usize) -> u64 {
282    ACPI_TABLES_BASE + align_up_to_page_size(acpi_tables_len as u64)
283}
284
285// Compile-time check that the fixed low-memory layout constants are ordered and
286// non-overlapping. A violation here is a code bug, caught at build time.
287const _: () = {
288    assert!(GDT_BASE < ZERO_PAGE_BASE);
289    assert!(ZERO_PAGE_BASE < CMDLINE_BASE);
290    assert!(CMDLINE_BASE < CR3_BASE);
291    assert!(ACPI_TABLES_BASE < RSDP_BASE);
292    assert!(RSDP_BASE < SMBIOS_FSEGMENT_BASE);
293    assert!(SMBIOS_FSEGMENT_BASE < KERNEL_BASE);
294};
295
296#[derive(Debug, PartialEq, Eq, Clone, Copy)]
297pub enum InitrdAddressType {
298    /// Load the initrd after the kernel at the next 2MB aligned address.
299    AfterKernel,
300    /// Load the initrd at the specified address.
301    Address(u64),
302}
303
304pub struct InitrdConfig<'a> {
305    pub initrd_address: InitrdAddressType,
306    pub initrd: &'a mut dyn ReadSeek,
307    pub size: u64,
308}
309
310/// Information returned about the kernel loaded.
311#[derive(Debug, Default)]
312pub struct KernelInfo {
313    /// The base gpa the kernel was loaded at.
314    pub gpa: u64,
315    /// The size in bytes of the region the kernel was loaded at.
316    pub size: u64,
317    /// The gpa of the entrypoint of the kernel.
318    pub entrypoint: u64,
319}
320
321/// Information returned about the initrd loaded.
322#[derive(Debug, Default)]
323pub struct InitrdInfo {
324    /// The gpa the initrd was loaded at.
325    pub gpa: u64,
326    /// The size in bytes of the initrd loaded. Note that the region imported is aligned up to page size.
327    pub size: u64,
328}
329
330/// Information returned about where certain parts were loaded.
331#[derive(Debug, Default)]
332pub struct LoadInfo {
333    /// The information about the kernel loaded.
334    pub kernel: KernelInfo,
335    /// The information about the initrd loaded.
336    pub initrd: Option<InitrdInfo>,
337    /// The information about the device tree blob loaded.
338    pub dtb: Option<std::ops::Range<u64>>,
339    /// If a bzImage was loaded, the original setup header from the image.
340    /// This must be placed into the zero page so the kernel's startup code
341    /// can read its own configuration.
342    pub bzimage_setup_header: Option<defs::setup_header>,
343}
344
345fn import_snp_boot_pages(
346    importer: &mut impl ImageLoad<X86Register>,
347    range: MemoryRange,
348) -> Result<u64, Error> {
349    assert_eq!(range.len(), SNP_BOOT_PAGE_COUNT * HV_PAGE_SIZE);
350    let secrets_address = range.start();
351    let cpuid_address = range.start() + HV_PAGE_SIZE;
352    let cc_blob_address = range.start() + 2 * HV_PAGE_SIZE;
353    let cc_setup_data_address = range.start() + 3 * HV_PAGE_SIZE;
354
355    importer
356        .import_pages(
357            secrets_address / HV_PAGE_SIZE,
358            1,
359            "linux-snp-secrets",
360            BootPageAcceptance::SecretsPage,
361            &[],
362        )
363        .map_err(Error::Importer)?;
364    importer
365        .import_pages(
366            cpuid_address / HV_PAGE_SIZE,
367            1,
368            "linux-snp-cpuid",
369            BootPageAcceptance::CpuidPage,
370            &[],
371        )
372        .map_err(Error::Importer)?;
373
374    let cc_blob = defs::cc_blob_sev_info {
375        magic: defs::CC_BLOB_SEV_INFO_MAGIC,
376        version: 0,
377        _reserved: 0,
378        secrets_phys: secrets_address,
379        secrets_len: HV_PAGE_SIZE as u32,
380        _rsvd1: 0,
381        cpuid_phys: cpuid_address,
382        cpuid_len: HV_PAGE_SIZE as u32,
383        _rsvd2: 0,
384    };
385    importer
386        .import_pages(
387            cc_blob_address / HV_PAGE_SIZE,
388            1,
389            "linux-snp-cc-blob",
390            BootPageAcceptance::Exclusive,
391            cc_blob.as_bytes(),
392        )
393        .map_err(Error::Importer)?;
394
395    let cc_blob_address = u32::try_from(cc_blob_address)
396        .map_err(|_| Error::SnpCcBlobAddressTooHigh(cc_blob_address))?;
397    let cc_setup_data = defs::cc_setup_data {
398        header: defs::setup_data {
399            next: 0,
400            ty: defs::SETUP_CC_BLOB,
401            len: (size_of::<defs::cc_setup_data>() - size_of::<defs::setup_data>()) as u32,
402        },
403        cc_blob_address,
404        _padding: [0; 3],
405    };
406    importer
407        .import_pages(
408            cc_setup_data_address / HV_PAGE_SIZE,
409            1,
410            "linux-snp-cc-setup-data",
411            BootPageAcceptance::Exclusive,
412            cc_setup_data.as_bytes(),
413        )
414        .map_err(Error::Importer)?;
415
416    Ok(cc_setup_data_address)
417}
418
419/// Check if an address is aligned to a page.
420fn check_address_alignment(address: u64) -> Result<(), Error> {
421    if !address.is_multiple_of(HV_PAGE_SIZE) {
422        Err(Error::UnalignedAddress(address))
423    } else {
424        Ok(())
425    }
426}
427
428/// Import initrd
429fn import_initrd<R: GuestArch>(
430    initrd: Option<InitrdConfig<'_>>,
431    next_addr: u64,
432    importer: &mut dyn ImageLoad<R>,
433) -> Result<Option<InitrdInfo>, Error> {
434    let initrd_info = match initrd {
435        Some(cfg) => {
436            let initrd_address = match cfg.initrd_address {
437                InitrdAddressType::AfterKernel => align_up_to_large_page_size(next_addr),
438                InitrdAddressType::Address(addr) => addr,
439            };
440
441            tracing::trace!(initrd_address, "loading initrd");
442            check_address_alignment(initrd_address)?;
443
444            ChunkBuf::new()
445                .import_file_region(
446                    importer,
447                    ImportFileRegion {
448                        file: cfg.initrd,
449                        file_offset: 0,
450                        file_length: cfg.size,
451                        gpa: initrd_address,
452                        memory_length: cfg.size,
453                        acceptance: BootPageAcceptance::Exclusive,
454                        tag: "linux-initrd",
455                    },
456                )
457                .map_err(Error::ImportInitrd)?;
458
459            Some(InitrdInfo {
460                gpa: initrd_address,
461                size: cfg.size,
462            })
463        }
464        None => None,
465    };
466    Ok(initrd_info)
467}
468
469/// Load only a Linux kernel and optional initrd to VTL0.
470/// This does not setup register state or any other config information.
471///
472/// The kernel image may be either an uncompressed ELF (`vmlinux`) or a
473/// compressed bzImage. If a bzImage is detected, the bzImage payload is
474/// loaded directly into guest memory and the kernel's own decompressor
475/// runs at boot time.
476///
477/// # Arguments
478///
479/// * `importer` - The importer to use.
480/// * `kernel_image` - Kernel image (uncompressed ELF or bzImage).
481/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
482///   It cannot contain an entrypoint or program headers that refer to memory below this address.
483/// * `initrd` - The initrd config, optional.
484pub fn load_kernel_and_initrd_x64<F>(
485    importer: &mut dyn ImageLoad<X86Register>,
486    kernel_image: &mut F,
487    kernel_minimum_start_address: u64,
488    initrd: Option<InitrdConfig<'_>>,
489) -> Result<LoadInfo, Error>
490where
491    F: Read + Seek,
492{
493    tracing::trace!(kernel_minimum_start_address, "loading x86_64 kernel");
494
495    if crate::bzimage::is_bzimage(kernel_image).map_err(Error::BzImage)? {
496        tracing::info!("detected bzImage format, loading via Linux boot protocol");
497        return load_bzimage(importer, kernel_image, kernel_minimum_start_address, initrd);
498    }
499
500    let elf_load_info = load_static_elf(
501        importer,
502        kernel_image,
503        kernel_minimum_start_address,
504        0,
505        false,
506        BootPageAcceptance::Exclusive,
507        "linux-kernel",
508    )
509    .map_err(Error::ElfLoader)?;
510
511    let crate::elf::LoadInfo {
512        minimum_address_used: min_addr,
513        next_available_address: next_addr,
514        entrypoint,
515    } = elf_load_info;
516    tracing::trace!(min_addr, next_addr, entrypoint, "loaded kernel");
517
518    let initrd_info = import_initrd(initrd, next_addr, importer)?;
519
520    Ok(LoadInfo {
521        kernel: KernelInfo {
522            gpa: min_addr,
523            size: next_addr - min_addr,
524            entrypoint,
525        },
526        initrd: initrd_info,
527        dtb: None,
528        bzimage_setup_header: None,
529    })
530}
531
532/// Load a bzImage by placing its payload directly into guest memory at the
533/// load address and following the Linux boot protocol. The kernel's built-in
534/// decompressor handles the rest at boot time.
535fn load_bzimage(
536    importer: &mut dyn ImageLoad<X86Register>,
537    kernel_image: &mut (impl Read + Seek),
538    kernel_start_address: u64,
539    initrd: Option<InitrdConfig<'_>>,
540) -> Result<LoadInfo, Error> {
541    let info = crate::bzimage::parse_bzimage(kernel_image).map_err(Error::BzImage)?;
542
543    check_address_alignment(kernel_start_address)?;
544
545    let payload_offset = (info.setup_sects as u64 + 1) * 512;
546    let payload_len = info.protected_mode_size;
547    let payload_memory_len = align_up_to_page_size(payload_len);
548    let entrypoint = kernel_start_address + info.entry_offset;
549
550    tracing::info!(
551        kernel_start_address = format_args!("{:#x}", kernel_start_address),
552        payload_offset,
553        payload_len,
554        entrypoint = format_args!("{:#x}", entrypoint),
555        "loading bzImage payload into guest memory"
556    );
557
558    ChunkBuf::new()
559        .import_file_region(
560            importer,
561            ImportFileRegion {
562                file: kernel_image,
563                file_offset: payload_offset,
564                file_length: payload_len,
565                gpa: kernel_start_address,
566                memory_length: payload_memory_len,
567                acceptance: BootPageAcceptance::Exclusive,
568                tag: "linux-kernel",
569            },
570        )
571        .map_err(Error::ImportBzImage)?;
572
573    // Place initrd after the kernel's init_size region to avoid being
574    // overwritten during decompression.
575    let next_addr = kernel_start_address + payload_memory_len;
576    let pref_address: u64 = info.setup_header.pref_address.into();
577    let init_end = kernel_start_address
578        .max(pref_address)
579        .saturating_add(info.init_size as u64);
580    let next_addr = next_addr.max(init_end);
581    let initrd_info = import_initrd(initrd, next_addr, importer)?;
582
583    Ok(LoadInfo {
584        kernel: KernelInfo {
585            gpa: kernel_start_address,
586            size: payload_memory_len,
587            entrypoint,
588        },
589        initrd: initrd_info,
590        dtb: None,
591        bzimage_setup_header: Some(info.setup_header),
592    })
593}
594
595/// Import the boot metadata, ACPI/SMBIOS tables, zero page, and initial
596/// registers for a kernel already described by `load_info`.
597///
598/// Internal helper shared by [`load_x86`] and [`load_config_x86`]. All guest
599/// addresses come from the module-level layout constants; callers supply only
600/// the table contents.
601fn import_config(
602    importer: &mut impl ImageLoad<X86Register>,
603    load_info: &LoadInfo,
604    cmdline: &CString,
605    mem_layout: &MemoryLayout,
606    acpi: &AcpiTables,
607    smbios: Option<&crate::smbios::BuiltSmbios>,
608    snp_boot: Option<SnpBootConfig>,
609) -> Result<(), Error> {
610    // Only import the cmdline if it actually contains something.
611    // TODO: This should use the IGVM parameter instead?
612    let raw_cmdline = cmdline.as_bytes_with_nul();
613    if raw_cmdline.len() as u64 > CR3_BASE - CMDLINE_BASE {
614        return Err(Error::CommandLineTooLong(
615            raw_cmdline.len(),
616            CR3_BASE - CMDLINE_BASE,
617        ));
618    }
619    if raw_cmdline.len() > 1 {
620        let cmdline_size_pages = align_up_to_page_size(raw_cmdline.len() as u64) / HV_PAGE_SIZE;
621        importer
622            .import_pages(
623                CMDLINE_BASE / HV_PAGE_SIZE,
624                cmdline_size_pages,
625                "linux-commandline",
626                BootPageAcceptance::Exclusive,
627                raw_cmdline,
628            )
629            .map_err(Error::Importer)?;
630    }
631
632    import_default_gdt(importer, GDT_BASE / HV_PAGE_SIZE).map_err(Error::Importer)?;
633    let mut page_table_work_buffer: Vec<PageTable> =
634        vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
635    let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
636    let page_table_builder = IdentityMapBuilder::new(
637        CR3_BASE,
638        IdentityMapSize::Size4Gb,
639        page_table_work_buffer.as_mut_slice(),
640        page_table.as_mut_slice(),
641    )?;
642    let page_table = page_table_builder.build();
643    assert!((page_table.len() as u64).is_multiple_of(HV_PAGE_SIZE));
644    importer
645        .import_pages(
646            CR3_BASE / HV_PAGE_SIZE,
647            page_table.len() as u64 / HV_PAGE_SIZE,
648            "linux-pagetables",
649            BootPageAcceptance::Exclusive,
650            page_table,
651        )
652        .map_err(Error::Importer)?;
653
654    if acpi.tables.is_empty() {
655        return Err(Error::EmptyAcpiTables);
656    }
657    let acpi_tables_size_pages = align_up_to_page_size(acpi.tables.len() as u64) / HV_PAGE_SIZE;
658    importer
659        .import_pages(
660            RSDP_BASE / HV_PAGE_SIZE,
661            1,
662            "linux-rsdp",
663            BootPageAcceptance::Exclusive,
664            &acpi.rsdp,
665        )
666        .map_err(Error::Importer)?;
667    importer
668        .import_pages(
669            ACPI_TABLES_BASE / HV_PAGE_SIZE,
670            acpi_tables_size_pages,
671            "linux-acpi-tables",
672            BootPageAcceptance::Exclusive,
673            &acpi.tables,
674        )
675        .map_err(Error::Importer)?;
676
677    let requested_page_count = snp_boot.map_or(0, |_| SNP_BOOT_PAGE_COUNT);
678    let ZeroPageBuildResult {
679        mut boot_params,
680        additional_pages,
681    } = build_zero_page(
682        mem_layout,
683        acpi.tables.len(),
684        smbios.map_or(0, |s| s.structure_table.len()),
685        requested_page_count,
686        cmdline,
687        load_info.initrd.as_ref().map(|info| info.gpa).unwrap_or(0) as u32,
688        load_info.initrd.as_ref().map(|info| info.size).unwrap_or(0) as u32,
689        load_info.bzimage_setup_header.as_ref(),
690    )?;
691    if let Some(allocated_range) = additional_pages {
692        boot_params.hdr.setup_data = import_snp_boot_pages(importer, allocated_range)?.into();
693    }
694    importer
695        .import_pages(
696            ZERO_PAGE_BASE / HV_PAGE_SIZE,
697            1,
698            "linux-zeropage",
699            BootPageAcceptance::Exclusive,
700            boot_params.as_bytes(),
701        )
702        .map_err(Error::Importer)?;
703
704    // Set common X64 registers. Segments already set by default gdt.
705    let mut import_reg = |register| {
706        importer
707            .import_vp_register(register)
708            .map_err(Error::Importer)
709    };
710
711    import_reg(X86Register::Cr0(x86defs::X64_CR0_PG | x86defs::X64_CR0_PE))?;
712    import_reg(X86Register::Cr3(CR3_BASE))?;
713    import_reg(X86Register::Cr4(x86defs::X64_CR4_PAE))?;
714    import_reg(X86Register::Efer(
715        x86defs::X64_EFER_SCE
716            | x86defs::X64_EFER_LME
717            | x86defs::X64_EFER_LMA
718            | x86defs::X64_EFER_NXE,
719    ))?;
720    import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?;
721
722    // Set rip to entry point and rsi to zero page.
723    import_reg(X86Register::Rip(load_info.kernel.entrypoint))?;
724    import_reg(X86Register::Rsi(ZERO_PAGE_BASE))?;
725
726    // No firmware will set MTRR values for the BSP.  Replicate what UEFI does here.
727    // (enable MTRRs, default MTRR is uncached, and set lowest 640KB as WB)
728    import_reg(X86Register::MtrrDefType(0xc00))?;
729    import_reg(X86Register::MtrrFix64k00000(0x0606060606060606))?;
730    import_reg(X86Register::MtrrFix16k80000(0x0606060606060606))?;
731
732    if let Some(smbios) = smbios {
733        // The `_SM3_` entry point (anchor) goes in the F-segment for the
734        // kernel's DMI scan; its 64-bit pointer targets the structure table in
735        // the low reserved area just above the ACPI tables.
736        let anchor_pages = align_up_to_page_size(smbios.entry_point.len() as u64) / HV_PAGE_SIZE;
737        importer
738            .import_pages(
739                SMBIOS_FSEGMENT_BASE / HV_PAGE_SIZE,
740                anchor_pages,
741                "linux-smbios-anchor",
742                BootPageAcceptance::Exclusive,
743                &smbios.entry_point,
744            )
745            .map_err(Error::Importer)?;
746
747        let table_base = smbios_struct_table_base(acpi.tables.len());
748        let table_pages = align_up_to_page_size(smbios.structure_table.len() as u64) / HV_PAGE_SIZE;
749        importer
750            .import_pages(
751                table_base / HV_PAGE_SIZE,
752                table_pages,
753                "linux-smbios-tables",
754                BootPageAcceptance::Exclusive,
755                &smbios.structure_table,
756            )
757            .map_err(Error::Importer)?;
758    }
759
760    Ok(())
761}
762
763/// Place the ACPI tables, SMBIOS tables, boot metadata, zero page, and initial
764/// registers for a Linux kernel that has *already* been loaded into guest
765/// memory (as described by `load_info`).
766///
767/// The loader owns the entire sub-1 MB memory map; callers supply only
768/// contents, never addresses:
769///
770/// * `cmdline` - the kernel command line.
771/// * `mem_layout` - the guest memory layout, used to build the e820 map.
772/// * `build_acpi` - a builder handed the nominal RSDP address; it must return
773///   ACPI tables self-consistent for placement one page above that address.
774///   The loader re-homes the RSDP to the fixed 0xe0000 legacy-scan location.
775/// * `smbios` - an optional SMBIOS identity; when present the loader assembles
776///   the `_SM3_` entry point and structure table into the F-segment.
777/// * `snp_boot` - optionally allocate SEV-SNP Linux boot protocol pages.
778pub fn load_config_x86(
779    importer: &mut impl ImageLoad<X86Register>,
780    load_info: &LoadInfo,
781    cmdline: &CString,
782    mem_layout: &MemoryLayout,
783    build_acpi: impl FnOnce(u64) -> AcpiTables,
784    smbios: Option<crate::smbios::SmbiosTables<'_>>,
785    snp_boot: Option<SnpBootConfig>,
786) -> Result<(), Error> {
787    // The builder lays out a nominal RSDP page at LOW_METADATA_END followed by
788    // the tables it points to; we keep only the tables (placed at
789    // ACPI_TABLES_BASE) and re-home the RSDP to the fixed scan location.
790    let acpi_tables = build_acpi(LOW_METADATA_END);
791
792    // Build the SMBIOS tables (if an identity was supplied) with the structure
793    // table addressed at its low-area home: the `_SM3_` anchor's 64-bit pointer
794    // references it there while the anchor itself lands in the F-segment for the
795    // kernel's DMI scan. See `smbios_struct_table_base` / `import_config`.
796    let smbios = smbios.map(|tables| {
797        crate::smbios::build(&tables, smbios_struct_table_base(acpi_tables.tables.len()))
798    });
799
800    import_config(
801        importer,
802        load_info,
803        cmdline,
804        mem_layout,
805        &acpi_tables,
806        smbios.as_ref(),
807        snp_boot,
808    )
809}
810
811/// Load a Linux kernel into VTL0 and place all of its supporting structures.
812///
813/// Loads the kernel (uncompressed ELF or bzImage) and optional initrd at the
814/// conventional 1 MB address, then delegates to [`load_config_x86`] to place
815/// the ACPI/SMBIOS tables, boot metadata, zero page, and initial registers.
816/// See [`load_config_x86`] for the `build_acpi`/`smbios` contract.
817pub fn load_x86<F>(
818    importer: &mut impl ImageLoad<X86Register>,
819    kernel_image: &mut F,
820    initrd: Option<InitrdConfig<'_>>,
821    cmdline: &CString,
822    mem_layout: &MemoryLayout,
823    build_acpi: impl FnOnce(u64) -> AcpiTables,
824    smbios: Option<crate::smbios::SmbiosTables<'_>>,
825    snp_boot: Option<SnpBootConfig>,
826) -> Result<LoadInfo, Error>
827where
828    F: Read + Seek,
829{
830    let load_info = load_kernel_and_initrd_x64(importer, kernel_image, KERNEL_BASE, initrd)?;
831    load_config_x86(
832        importer, &load_info, cmdline, mem_layout, build_acpi, smbios, snp_boot,
833    )?;
834    Ok(load_info)
835}
836
837open_enum::open_enum! {
838    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
839    pub enum Aarch64ImagePageSize: u64 {
840        UNSPECIFIED = 0,
841        PAGE4_K = 1,
842        PAGE16_K = 2,
843        PAGE64_K = 3,
844    }
845
846}
847
848impl Aarch64ImagePageSize {
849    const fn into_bits(self) -> u64 {
850        self.0
851    }
852
853    const fn from_bits(bits: u64) -> Self {
854        Self(bits)
855    }
856}
857
858/// Arm64 flat kernel `Image` flags.
859#[bitfield(u64)]
860struct Aarch64ImageFlags {
861    /// Bit 0:	Kernel endianness.  1 if BE, 0 if LE.
862    #[bits(1)]
863    pub big_endian: bool,
864    /// Bit 1-2:	Kernel Page size.
865    ///           0 - Unspecified.
866    ///           1 - 4K
867    ///           2 - 16K
868    ///           3 - 64K
869    #[bits(2)]
870    pub page_size: Aarch64ImagePageSize,
871    /// Bit 3:	Kernel physical placement
872    ///           0 - 2MB aligned base should be as close as possible
873    ///               to the base of DRAM, since memory below it is not
874    ///               accessible via the linear mapping
875    ///           1 - 2MB aligned base may be anywhere in physical
876    ///               memory
877    #[bits(1)]
878    pub any_start_address: bool,
879    /// Bits 4-63:	Reserved.
880    #[bits(60)]
881    pub _padding: u64,
882}
883
884// Kernel boot protocol is specified in the Linux kernel
885// Documentation/arm64/booting.txt.
886#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
887#[repr(C)]
888struct Aarch64ImageHeader {
889    /// Executable code
890    _code0: u32,
891    /// Executable code
892    _code1: u32,
893    /// Image load offset, little endian
894    text_offset: u64,
895    /// Effective Image size, little endian
896    image_size: u64,
897    /// kernel flags, little endian
898    flags: u64,
899    /// reserved
900    _res2: u64,
901    /// reserved
902    _res3: u64,
903    /// reserved
904    _res4: u64,
905    /// Magic number, little endian, "ARM\x64"
906    magic: [u8; 4],
907    /// reserved (used for PE COFF offset)
908    _res5: u32,
909}
910
911const AARCH64_MAGIC_NUMBER: &[u8] = b"ARM\x64";
912
913/// Load only an arm64 the flat Linux kernel `Image` and optional initrd.
914/// This does not setup register state or any other config information.
915///
916/// # Arguments
917///
918/// * `importer` - The importer to use.
919/// * `kernel_image` - Uncompressed ELF image for the kernel.
920/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
921///   It cannot contain an entrypoint or program headers that refer to memory below this address.
922/// * `initrd` - The initrd config, optional.
923/// * `device_tree_blob` - The device tree blob, optional.
924pub fn load_kernel_and_initrd_arm64<F>(
925    importer: &mut dyn ImageLoad<Aarch64Register>,
926    kernel_image: &mut F,
927    kernel_minimum_start_address: u64,
928    initrd: Option<InitrdConfig<'_>>,
929    device_tree_blob: Option<&[u8]>,
930) -> Result<LoadInfo, Error>
931where
932    F: Read + Seek,
933{
934    tracing::trace!(kernel_minimum_start_address, "loading aarch64 kernel");
935
936    assert_eq!(
937        kernel_minimum_start_address & ((1 << 21) - 1),
938        0,
939        "Start offset must be aligned on the 2MiB boundary"
940    );
941
942    kernel_image
943        .seek(SeekFrom::Start(0))
944        .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
945
946    let mut header = Aarch64ImageHeader::new_zeroed();
947    kernel_image
948        .read_exact(header.as_mut_bytes())
949        .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
950
951    tracing::debug!("aarch64 kernel header {header:x?}");
952
953    if header.magic != AARCH64_MAGIC_NUMBER {
954        return Err(Error::FlatLoader(FlatLoaderError::BadImageMagic));
955    }
956
957    let flags = Aarch64ImageFlags::from(header.flags);
958    if flags.big_endian() {
959        return Err(Error::FlatLoader(FlatLoaderError::BigEndianKernelImage));
960    }
961    if flags.page_size() != Aarch64ImagePageSize::PAGE4_K {
962        return Err(Error::FlatLoader(
963            FlatLoaderError::FourKibPageImageIsRequired,
964        ));
965    }
966    if !flags.any_start_address() {
967        return Err(Error::FlatLoader(FlatLoaderError::LowMemoryKernel));
968    }
969
970    // The `Image` must be placed `text_offset` bytes from a 2MB aligned base
971    // address anywhere in usable system RAM and called there.
972
973    kernel_image
974        .seek(SeekFrom::Start(0))
975        .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
976
977    let mut image = Vec::new();
978    kernel_image
979        .read_to_end(&mut image)
980        .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
981
982    let kernel_load_offset = (kernel_minimum_start_address + header.text_offset) as usize;
983    let kernel_size = if header.image_size != 0 {
984        header.image_size
985    } else {
986        image.len() as u64
987    };
988
989    let kernel_size = align_up_to_page_size(kernel_size);
990    importer
991        .import_pages(
992            kernel_load_offset as u64 / HV_PAGE_SIZE,
993            kernel_size / HV_PAGE_SIZE,
994            "linux-kernel",
995            BootPageAcceptance::Exclusive,
996            &image,
997        )
998        .map_err(Error::Importer)?;
999
1000    let next_addr = kernel_load_offset as u64 + kernel_size;
1001
1002    let (next_addr, dtb) = if let Some(device_tree_blob) = device_tree_blob {
1003        let dtb_addr = align_up_to_page_size(next_addr);
1004        tracing::trace!(dtb_addr, "loading device tree blob at {dtb_addr:x?}");
1005
1006        check_address_alignment(dtb_addr)?;
1007        let dtb_size_pages = align_up_to_page_size(device_tree_blob.len() as u64) / HV_PAGE_SIZE;
1008
1009        importer
1010            .import_pages(
1011                dtb_addr / HV_PAGE_SIZE,
1012                dtb_size_pages,
1013                "linux-device-tree",
1014                BootPageAcceptance::Exclusive,
1015                device_tree_blob,
1016            )
1017            .map_err(Error::Importer)?;
1018
1019        (
1020            dtb_addr + device_tree_blob.len() as u64,
1021            Some(dtb_addr..dtb_addr + device_tree_blob.len() as u64),
1022        )
1023    } else {
1024        (next_addr, None)
1025    };
1026
1027    let initrd_info = import_initrd(initrd, next_addr, importer)?;
1028
1029    Ok(LoadInfo {
1030        kernel: KernelInfo {
1031            gpa: kernel_minimum_start_address,
1032            size: kernel_size,
1033            entrypoint: kernel_load_offset as u64,
1034        },
1035        initrd: initrd_info,
1036        dtb,
1037        bzimage_setup_header: None,
1038    })
1039}
1040
1041/// Load the configuration info and registers for the Linux kernel based on the provided LoadInfo.
1042/// Parameters:
1043/// * `importer` - The importer to use.
1044/// * `load_info` - The kernel load info that contains information on where the kernel and initrd are.
1045/// * `vtl` - The target VTL.
1046pub fn set_direct_boot_registers_arm64(
1047    importer: &mut impl ImageLoad<Aarch64Register>,
1048    load_info: &LoadInfo,
1049) -> Result<(), Error> {
1050    let mut import_reg = |register| {
1051        importer
1052            .import_vp_register(register)
1053            .map_err(Error::Importer)
1054    };
1055
1056    import_reg(Aarch64Register::Pc(load_info.kernel.entrypoint))?;
1057    import_reg(Aarch64Register::Cpsr(
1058        Cpsr64::new()
1059            .with_sp(true)
1060            .with_el(1)
1061            .with_f(true)
1062            .with_i(true)
1063            .with_a(true)
1064            .with_d(true)
1065            .into(),
1066    ))?;
1067    import_reg(Aarch64Register::SctlrEl1(
1068        SctlrEl1::new()
1069            // MMU is disabled for EL1&0 stage 1 address translation.
1070            // The family of the `at` instructions and the `PAR_EL1` register are
1071            // useful for debugging MMU issues when it's on.
1072            .with_m(false)
1073            // Stage 1 Cacheability control, for data accesses.
1074            .with_c(true)
1075            // Stage 1 Cacheability control, for code.
1076            .with_i(true)
1077            // Reserved flags, must be set
1078            .with_eos(true)
1079            .with_tscxt(true)
1080            .with_eis(true)
1081            .with_span(true)
1082            .with_n_tlsmd(true)
1083            .with_lsmaoe(true)
1084            .into(),
1085    ))?;
1086    import_reg(Aarch64Register::TcrEl1(
1087        TranslationControlEl1::new()
1088            .with_t0sz(0x11)
1089            .with_irgn0(1)
1090            .with_orgn0(1)
1091            .with_sh0(3)
1092            .with_tg0(TranslationGranule0::TG_4KB)
1093            // Disable TTBR0_EL1 walks (i.e. the lower half).
1094            .with_epd0(1)
1095            // Disable TTBR1_EL1 walks (i.e. the upper half).
1096            .with_epd1(1)
1097            // Due to erratum #822227, need to set a valid TG1 regardless of EPD1.
1098            .with_tg1(TranslationGranule1::TG_4KB)
1099            .with_ips(IntermPhysAddrSize::IPA_48_BITS_256_TB)
1100            .into(),
1101    ))?;
1102    import_reg(Aarch64Register::Ttbr0El1(TranslationBaseEl1::new().into()))?;
1103    import_reg(Aarch64Register::Ttbr1El1(TranslationBaseEl1::new().into()))?;
1104    import_reg(Aarch64Register::VbarEl1(0))?;
1105
1106    if let Some(dtb) = &load_info.dtb {
1107        import_reg(Aarch64Register::X0(dtb.start))?;
1108    }
1109
1110    Ok(())
1111}
1112#[cfg(test)]
1113mod tests {
1114    use super::*;
1115    use crate::importer::IgvmParameterType;
1116    use crate::importer::IsolationConfig;
1117    use crate::importer::ParameterAreaIndex;
1118    use crate::importer::StartupMemoryType;
1119    use test_with_tracing::test;
1120    use zerocopy::FromBytes;
1121
1122    const MB: u64 = 0x100000;
1123    const GB: u64 = 0x4000_0000;
1124
1125    /// A guest memory layout with `ram_size` bytes of RAM and a 128 MB MMIO gap
1126    /// below 4 GiB (matching a typical x86_64 config). RAM up to the gap forms
1127    /// the first range `[0, min(ram_size, 4 GiB - 128 MB))`.
1128    fn make_layout(ram_size: u64) -> MemoryLayout {
1129        MemoryLayout::new(
1130            ram_size,
1131            &[MemoryRange::new(4 * GB - 128 * MB..4 * GB)],
1132            &[],
1133            &[],
1134            None,
1135        )
1136        .unwrap()
1137    }
1138
1139    /// Asserts that `map[..entries]` exactly covers `[0, first_ram_end)` with no
1140    /// gaps or overlaps and strictly ascending addresses, and that no entry is
1141    /// empty. The first entry must start at 0 and the last must end precisely at
1142    /// `first_ram_end`.
1143    fn assert_contiguous(p: &defs::boot_params, first_ram_end: u64) {
1144        let entries = p.e820_entries as usize;
1145        assert!(entries > 0);
1146        let mut expected_addr = 0u64;
1147        for i in 0..entries {
1148            let e = &p.e820_map[i];
1149            assert_ne!(u64::from(e.size), 0, "entry {i} is empty");
1150            assert_eq!(u64::from(e.addr), expected_addr, "gap/overlap at entry {i}");
1151            expected_addr = u64::from(e.addr) + u64::from(e.size);
1152        }
1153        assert_eq!(
1154            expected_addr, first_ram_end,
1155            "map ends at {expected_addr:#x}, expected {first_ram_end:#x}"
1156        );
1157    }
1158
1159    #[test]
1160    fn zero_page_layout_with_smbios() {
1161        let acpi_len = 0x1800; // aligns up to 0x2000
1162        let smbios_len = 0x100; // aligns up to 0x1000
1163        let p = build_zero_page(
1164            &make_layout(256 * MB),
1165            acpi_len,
1166            smbios_len,
1167            0,
1168            &CString::new("root=/dev/sda").unwrap(),
1169            0,
1170            0,
1171            None,
1172        )
1173        .unwrap()
1174        .boot_params;
1175
1176        let acpi_end = ACPI_TABLES_BASE + 0x2000;
1177        let smbios_end = acpi_end + 0x1000;
1178        let expected = [
1179            (0, ACPI_TABLES_BASE, defs::E820_RAM),
1180            (ACPI_TABLES_BASE, 0x2000, defs::E820_ACPI),
1181            (acpi_end, 0x1000, defs::E820_RESERVED),
1182            (smbios_end, RSDP_BASE - smbios_end, defs::E820_RAM),
1183            (RSDP_BASE, 0x100000 - RSDP_BASE, defs::E820_RESERVED),
1184            (0x100000, 256 * MB - 0x100000, defs::E820_RAM),
1185        ];
1186        assert_eq!(p.e820_entries as usize, expected.len());
1187        for (i, (addr, size, typ)) in expected.iter().enumerate() {
1188            let e = &p.e820_map[i];
1189            assert_eq!(u64::from(e.addr), *addr, "entry {i} addr");
1190            assert_eq!(u64::from(e.size), *size, "entry {i} size");
1191            assert_eq!(u32::from(e.typ), *typ, "entry {i} type");
1192        }
1193        assert_contiguous(&p, 256 * MB);
1194    }
1195
1196    #[test]
1197    fn zero_page_skips_empty_smbios_region() {
1198        // With no SMBIOS structure table, the reserved SMBIOS region collapses
1199        // to zero length and must not appear as an empty e820 entry.
1200        let p = build_zero_page(
1201            &make_layout(256 * MB),
1202            0x1800,
1203            0,
1204            0,
1205            &CString::new("").unwrap(),
1206            0,
1207            0,
1208            None,
1209        )
1210        .unwrap()
1211        .boot_params;
1212
1213        let acpi_end = ACPI_TABLES_BASE + 0x2000;
1214        let expected = [
1215            (0, ACPI_TABLES_BASE, defs::E820_RAM),
1216            (ACPI_TABLES_BASE, 0x2000, defs::E820_ACPI),
1217            (acpi_end, RSDP_BASE - acpi_end, defs::E820_RAM),
1218            (RSDP_BASE, 0x100000 - RSDP_BASE, defs::E820_RESERVED),
1219            (0x100000, 256 * MB - 0x100000, defs::E820_RAM),
1220        ];
1221        assert_eq!(p.e820_entries as usize, expected.len());
1222        for (i, (addr, size, typ)) in expected.iter().enumerate() {
1223            let e = &p.e820_map[i];
1224            assert_eq!(u64::from(e.addr), *addr, "entry {i} addr");
1225            assert_eq!(u64::from(e.size), *size, "entry {i} size");
1226            assert_eq!(u32::from(e.typ), *typ, "entry {i} type");
1227        }
1228        assert_contiguous(&p, 256 * MB);
1229    }
1230
1231    #[test]
1232    fn zero_page_multiple_ram_ranges() {
1233        // 8 GiB of RAM splits around the 4 GiB MMIO gap into two ranges; the
1234        // second appears after the six fixed low-memory entries.
1235        let p = build_zero_page(
1236            &make_layout(8 * GB),
1237            0x1000,
1238            0x1000,
1239            0,
1240            &CString::new("").unwrap(),
1241            0,
1242            0,
1243            None,
1244        )
1245        .unwrap()
1246        .boot_params;
1247        assert_eq!(p.e820_entries, 7);
1248        let last = &p.e820_map[6];
1249        assert_eq!(u64::from(last.addr), 4 * GB);
1250        assert_eq!(u32::from(last.typ), defs::E820_RAM);
1251        // The six fixed low-memory entries are contiguous from 0; the second
1252        // RAM range sits above the 4 GiB MMIO gap, so contiguity legitimately
1253        // breaks there.
1254        let below_gap = &p.e820_map[5];
1255        assert_eq!(
1256            u64::from(below_gap.addr) + u64::from(below_gap.size),
1257            4 * GB - 128 * MB
1258        );
1259    }
1260
1261    #[test]
1262    fn zero_page_tables_too_large() {
1263        // ACPI tables large enough to run past the RSDP reserved region.
1264        let result = build_zero_page(
1265            &make_layout(256 * MB),
1266            (RSDP_BASE - ACPI_TABLES_BASE) as usize + 0x1000,
1267            0,
1268            0,
1269            &CString::new("").unwrap(),
1270            0,
1271            0,
1272            None,
1273        );
1274        match result {
1275            Err(Error::LowTablesTooLarge(..)) => {}
1276            other => panic!("expected LowTablesTooLarge, got {:?}", other.err()),
1277        }
1278    }
1279
1280    /// An importer that records `import_pages` placements and accepts registers,
1281    /// panicking on any other entry point (none of which the Linux config path
1282    /// exercises).
1283    #[derive(Default)]
1284    struct RecordingImporter {
1285        /// `(debug_tag, page_base, page_count)` for each imported region.
1286        pages: Vec<(String, u64, u64)>,
1287        imports: Vec<ImportRecord>,
1288    }
1289
1290    #[derive(Debug)]
1291    struct ImportRecord {
1292        page_base: u64,
1293        page_count: u64,
1294        tag: String,
1295        acceptance: BootPageAcceptance,
1296        data: Vec<u8>,
1297    }
1298
1299    impl RecordingImporter {
1300        fn page_base(&self, tag: &str) -> Option<u64> {
1301            self.pages
1302                .iter()
1303                .find(|(t, ..)| t == tag)
1304                .map(|(_, base, _)| *base)
1305        }
1306    }
1307
1308    impl ImageLoad<X86Register> for RecordingImporter {
1309        fn isolation_config(&self) -> IsolationConfig {
1310            IsolationConfig {
1311                paravisor_present: false,
1312                isolation_type: crate::importer::IsolationType::None,
1313                shared_gpa_boundary_bits: None,
1314            }
1315        }
1316
1317        fn create_parameter_area(
1318            &mut self,
1319            _page_base: u64,
1320            _page_count: u32,
1321            _debug_tag: &str,
1322        ) -> anyhow::Result<ParameterAreaIndex> {
1323            unimplemented!()
1324        }
1325
1326        fn create_parameter_area_with_data(
1327            &mut self,
1328            _page_base: u64,
1329            _page_count: u32,
1330            _debug_tag: &str,
1331            _initial_data: &[u8],
1332        ) -> anyhow::Result<ParameterAreaIndex> {
1333            unimplemented!()
1334        }
1335
1336        fn import_parameter(
1337            &mut self,
1338            _parameter_area: ParameterAreaIndex,
1339            _byte_offset: u32,
1340            _parameter_type: IgvmParameterType,
1341        ) -> anyhow::Result<()> {
1342            unimplemented!()
1343        }
1344
1345        fn import_pages(
1346            &mut self,
1347            page_base: u64,
1348            page_count: u64,
1349            debug_tag: &'static str,
1350            acceptance: BootPageAcceptance,
1351            data: &[u8],
1352        ) -> anyhow::Result<()> {
1353            self.pages
1354                .push((debug_tag.to_string(), page_base, page_count));
1355            self.imports.push(ImportRecord {
1356                page_base,
1357                page_count,
1358                tag: debug_tag.to_string(),
1359                acceptance,
1360                data: data.to_vec(),
1361            });
1362            Ok(())
1363        }
1364
1365        fn import_vp_register(&mut self, _register: X86Register) -> anyhow::Result<()> {
1366            Ok(())
1367        }
1368
1369        fn verify_startup_memory_available(
1370            &mut self,
1371            _page_base: u64,
1372            _page_count: u64,
1373            _memory_type: StartupMemoryType,
1374        ) -> anyhow::Result<()> {
1375            Ok(())
1376        }
1377
1378        fn set_vp_context_page(&mut self, _page_base: u64) -> anyhow::Result<()> {
1379            unimplemented!()
1380        }
1381
1382        fn relocation_region(
1383            &mut self,
1384            _gpa: u64,
1385            _size_bytes: u64,
1386            _relocation_alignment: u64,
1387            _minimum_relocation_gpa: u64,
1388            _maximum_relocation_gpa: u64,
1389            _apply_rip_offset: bool,
1390            _apply_gdtr_offset: bool,
1391            _vp_index: u16,
1392        ) -> anyhow::Result<()> {
1393            unimplemented!()
1394        }
1395
1396        fn page_table_relocation(
1397            &mut self,
1398            _page_table_gpa: u64,
1399            _size_pages: u64,
1400            _used_pages: u64,
1401            _vp_index: u16,
1402        ) -> anyhow::Result<()> {
1403            unimplemented!()
1404        }
1405
1406        fn set_imported_regions_config_page(&mut self, _page_base: u64) {
1407            unimplemented!()
1408        }
1409    }
1410
1411    fn test_load_info() -> LoadInfo {
1412        LoadInfo {
1413            kernel: KernelInfo {
1414                gpa: KERNEL_BASE,
1415                size: 0x1000,
1416                entrypoint: KERNEL_BASE,
1417            },
1418            initrd: None,
1419            dtb: None,
1420            bzimage_setup_header: None,
1421        }
1422    }
1423
1424    #[test]
1425    fn import_config_places_tables_at_fixed_addresses() {
1426        let acpi = AcpiTables {
1427            rsdp: vec![0u8; 0x1000],
1428            tables: vec![0u8; 0x1800],
1429        };
1430        let smbios = crate::smbios::BuiltSmbios {
1431            entry_point: vec![0u8; crate::smbios::ENTRY_POINT_SIZE],
1432            structure_table: vec![0u8; 0x100],
1433        };
1434        let mut importer = RecordingImporter::default();
1435        import_config(
1436            &mut importer,
1437            &test_load_info(),
1438            &CString::new("console=ttyS0").unwrap(),
1439            &make_layout(256 * MB),
1440            &acpi,
1441            Some(&smbios),
1442            None,
1443        )
1444        .unwrap();
1445
1446        // The RSDP is re-homed to the fixed legacy-scan address, while the
1447        // tables it points to stay at the loader's chosen base.
1448        assert_eq!(
1449            importer.page_base("linux-rsdp"),
1450            Some(RSDP_BASE / HV_PAGE_SIZE)
1451        );
1452        assert_eq!(
1453            importer.page_base("linux-acpi-tables"),
1454            Some(ACPI_TABLES_BASE / HV_PAGE_SIZE)
1455        );
1456        // The SMBIOS anchor lands in the F-segment; the structure table sits
1457        // just above the ACPI tables.
1458        assert_eq!(
1459            importer.page_base("linux-smbios-anchor"),
1460            Some(SMBIOS_FSEGMENT_BASE / HV_PAGE_SIZE)
1461        );
1462        assert_eq!(
1463            importer.page_base("linux-smbios-tables"),
1464            Some(smbios_struct_table_base(acpi.tables.len()) / HV_PAGE_SIZE)
1465        );
1466        // Boot metadata at its fixed low-memory homes.
1467        assert_eq!(
1468            importer.page_base("linux-zeropage"),
1469            Some(ZERO_PAGE_BASE / HV_PAGE_SIZE)
1470        );
1471        assert_eq!(
1472            importer.page_base("linux-commandline"),
1473            Some(CMDLINE_BASE / HV_PAGE_SIZE)
1474        );
1475        assert_eq!(
1476            importer.page_base("linux-pagetables"),
1477            Some(CR3_BASE / HV_PAGE_SIZE)
1478        );
1479    }
1480
1481    #[test]
1482    fn import_config_rejects_oversized_command_line() {
1483        let acpi = AcpiTables {
1484            rsdp: vec![0u8; 0x1000],
1485            tables: vec![0u8; 0x1000],
1486        };
1487        // One byte too long once the NUL terminator is added.
1488        let cmdline = CString::new(vec![b'a'; (CR3_BASE - CMDLINE_BASE) as usize]).unwrap();
1489        let mut importer = RecordingImporter::default();
1490        let err = import_config(
1491            &mut importer,
1492            &test_load_info(),
1493            &cmdline,
1494            &make_layout(256 * MB),
1495            &acpi,
1496            None,
1497            None,
1498        )
1499        .unwrap_err();
1500        assert!(matches!(err, Error::CommandLineTooLong(..)), "got {err:?}");
1501        assert!(importer.pages.is_empty(), "importer used before the check");
1502    }
1503
1504    #[test]
1505    fn import_config_rejects_empty_acpi_tables() {
1506        // Empty tables would import zero pages; the loader must reject them
1507        // rather than feed page_count == 0 into the importer.
1508        let acpi = AcpiTables {
1509            rsdp: vec![0u8; 0x1000],
1510            tables: Vec::new(),
1511        };
1512        let mut importer = RecordingImporter::default();
1513        let err = import_config(
1514            &mut importer,
1515            &test_load_info(),
1516            &CString::new("").unwrap(),
1517            &make_layout(256 * MB),
1518            &acpi,
1519            None,
1520            None,
1521        )
1522        .unwrap_err();
1523        assert!(matches!(err, Error::EmptyAcpiTables), "got {err:?}");
1524    }
1525
1526    #[test]
1527    fn imports_snp_boot_pages_with_linux_cc_blob() {
1528        let acpi_len = 0x1800;
1529        let smbios_len = 0x100;
1530        let ZeroPageBuildResult {
1531            boot_params,
1532            additional_pages,
1533        } = build_zero_page(
1534            &make_layout(256 * MB),
1535            acpi_len,
1536            smbios_len,
1537            SNP_BOOT_PAGE_COUNT,
1538            &CString::new("").unwrap(),
1539            0,
1540            0,
1541            None,
1542        )
1543        .unwrap();
1544        let allocated_range = additional_pages.unwrap();
1545        let mut importer = RecordingImporter::default();
1546
1547        let cc_setup_data_address = import_snp_boot_pages(&mut importer, allocated_range).unwrap();
1548
1549        assert_eq!(importer.imports.len(), 4);
1550        assert_eq!(
1551            allocated_range.start(),
1552            ACPI_TABLES_BASE
1553                + align_up_to_page_size(acpi_len as u64)
1554                + align_up_to_page_size(smbios_len as u64)
1555        );
1556        assert_eq!(
1557            importer.imports[0].page_base,
1558            allocated_range.start() / HV_PAGE_SIZE
1559        );
1560        assert_eq!(importer.imports[0].page_count, 1);
1561        assert_eq!(importer.imports[0].tag, "linux-snp-secrets");
1562        assert_eq!(
1563            importer.imports[0].acceptance,
1564            BootPageAcceptance::SecretsPage
1565        );
1566        assert_eq!(
1567            importer.imports[1].page_base,
1568            allocated_range.start() / HV_PAGE_SIZE + 1
1569        );
1570        assert_eq!(
1571            importer.imports[1].acceptance,
1572            BootPageAcceptance::CpuidPage
1573        );
1574
1575        let cc_blob = defs::cc_blob_sev_info::read_from_bytes(&importer.imports[2].data).unwrap();
1576        assert_eq!(cc_blob.magic, defs::CC_BLOB_SEV_INFO_MAGIC);
1577        assert_eq!(cc_blob.version, 0);
1578        assert_eq!(cc_blob.secrets_phys, allocated_range.start());
1579        assert_eq!(cc_blob.secrets_len, HV_PAGE_SIZE as u32);
1580        assert_eq!(cc_blob.cpuid_phys, allocated_range.start() + HV_PAGE_SIZE);
1581        assert_eq!(cc_blob.cpuid_len, HV_PAGE_SIZE as u32);
1582
1583        let cc_setup_data =
1584            defs::cc_setup_data::read_from_bytes(&importer.imports[3].data).unwrap();
1585        assert_eq!(cc_setup_data.header.next, 0);
1586        assert_eq!(cc_setup_data.header.ty, defs::SETUP_CC_BLOB);
1587        assert_eq!(
1588            cc_setup_data.header.len,
1589            (size_of::<defs::cc_setup_data>() - size_of::<defs::setup_data>()) as u32
1590        );
1591        assert_eq!(
1592            cc_setup_data.cc_blob_address,
1593            u32::try_from(allocated_range.start() + 2 * HV_PAGE_SIZE).unwrap()
1594        );
1595        assert_eq!(allocated_range.end(), cc_setup_data_address + HV_PAGE_SIZE,);
1596        assert!(allocated_range.end() <= RSDP_BASE);
1597
1598        let smbios_reserved = &boot_params.e820_map[2];
1599        let acpi_end = ACPI_TABLES_BASE + align_up_to_page_size(acpi_len as u64);
1600        assert_eq!(u64::from(smbios_reserved.addr), acpi_end);
1601        assert_eq!(
1602            u64::from(smbios_reserved.size),
1603            align_up_to_page_size(smbios_len as u64)
1604        );
1605        let additional_reserved = &boot_params.e820_map[3];
1606        assert_eq!(u64::from(additional_reserved.addr), allocated_range.start());
1607        assert_eq!(u64::from(additional_reserved.size), allocated_range.len());
1608
1609        assert!(matches!(
1610            build_zero_page(
1611                &make_layout(256 * MB),
1612                (RSDP_BASE - ACPI_TABLES_BASE) as usize,
1613                0,
1614                SNP_BOOT_PAGE_COUNT,
1615                &CString::new("").unwrap(),
1616                0,
1617                0,
1618                None,
1619            ),
1620            Err(Error::LowTablesTooLarge(..))
1621        ));
1622    }
1623}