vmm/arch/x86_64/
mod.rs

1// Copyright © 2020, Oracle and/or its affiliates.
2//
3// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4// SPDX-License-Identifier: Apache-2.0
5//
6// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
7// Use of this source code is governed by a BSD-style license that can be
8// found in the THIRD-PARTY file.
9
10/// Logic for handling x86_64 CPU models.
11pub mod cpu_model;
12mod gdt;
13/// Contains logic for setting up Advanced Programmable Interrupt Controller (local version).
14pub mod interrupts;
15/// Architecture specific KVM-related code
16pub mod kvm;
17/// Layout for the x86_64 system.
18pub mod layout;
19mod mptable;
20/// Logic for configuring x86_64 model specific registers (MSRs).
21pub mod msr;
22/// Logic for configuring x86_64 registers.
23pub mod regs;
24/// Architecture specific vCPU code
25pub mod vcpu;
26/// Architecture specific VM state code
27pub mod vm;
28/// Logic for configuring XSTATE features.
29pub mod xstate;
30
31#[allow(missing_docs)]
32pub mod generated;
33
34use std::cmp::max;
35use std::fs::File;
36
37use kvm::Kvm;
38use layout::{
39    CMDLINE_START, MMIO32_MEM_SIZE, MMIO32_MEM_START, MMIO64_MEM_SIZE, MMIO64_MEM_START,
40    PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START,
41};
42use linux_loader::configurator::linux::LinuxBootConfigurator;
43use linux_loader::configurator::pvh::PvhBootConfigurator;
44use linux_loader::configurator::{BootConfigurator, BootParams};
45use linux_loader::loader::bootparam::boot_params;
46use linux_loader::loader::elf::Elf as Loader;
47use linux_loader::loader::elf::start_info::{
48    hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
49};
50use linux_loader::loader::{Cmdline, KernelLoader, PvhBootCapability, load_cmdline};
51use log::debug;
52
53use super::EntryPoint;
54use crate::acpi::create_acpi_tables;
55use crate::arch::{BootProtocol, SYSTEM_MEM_SIZE, SYSTEM_MEM_START, arch_memory_regions_with_gap};
56use crate::cpu_config::templates::{CustomCpuTemplate, GuestConfigError};
57use crate::cpu_config::x86_64::CpuConfiguration;
58use crate::device_manager::DeviceManager;
59use crate::initrd::InitrdConfig;
60use crate::utils::{align_down, u64_to_usize, usize_to_u64};
61use crate::vmm_config::machine_config::MachineConfig;
62use crate::vstate::memory::{
63    Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion, GuestRegionType,
64};
65use crate::vstate::vcpu::KvmVcpuConfigureError;
66use crate::{Vcpu, VcpuConfig, Vm, logger};
67
68// Value taken from https://elixir.bootlin.com/linux/v5.10.68/source/arch/x86/include/uapi/asm/e820.h#L31
69// Usable normal RAM
70const E820_RAM: u32 = 1;
71
72// Reserved area that should be avoided during memory allocations
73const E820_RESERVED: u32 = 2;
74const MEMMAP_TYPE_RAM: u32 = 1;
75
76/// Errors thrown while configuring x86_64 system.
77#[derive(Debug, thiserror::Error, displaydoc::Display)]
78pub enum ConfigurationError {
79    /// Invalid e820 setup params.
80    E820Configuration,
81    /// Error writing MP table to memory: {0}
82    MpTableSetup(#[from] mptable::MptableError),
83    /// Error writing the zero page of guest memory.
84    ZeroPageSetup,
85    /// Error writing module entry to guest memory.
86    ModlistSetup,
87    /// Error writing memory map table to guest memory.
88    MemmapTableSetup,
89    /// Error writing hvm_start_info to guest memory.
90    StartInfoSetup,
91    /// Cannot copy kernel file fd
92    KernelFile,
93    /// Cannot load kernel due to invalid memory configuration or invalid kernel image: {0}
94    KernelLoader(linux_loader::loader::Error),
95    /// Cannot load command line string: {0}
96    LoadCommandline(linux_loader::loader::Error),
97    /// Failed to create guest config: {0}
98    CreateGuestConfig(#[from] GuestConfigError),
99    /// Error configuring the vcpu for boot: {0}
100    VcpuConfigure(#[from] KvmVcpuConfigureError),
101    /// Error configuring ACPI: {0}
102    Acpi(#[from] crate::acpi::AcpiError),
103}
104
105/// Returns a Vec of the valid memory addresses.
106/// These should be used to configure the GuestMemoryMmap structure for the platform.
107/// For x86_64 all addresses are valid from the start of the kernel except an 1GB
108/// carve out at the end of 32bit address space and a second 256GB one at the 256GB limit.
109pub fn arch_memory_regions(size: usize) -> Vec<(GuestAddress, usize)> {
110    // If we get here with size == 0 something has seriously gone wrong. Firecracker should never
111    // try to allocate guest memory of size 0
112    assert!(size > 0, "Attempt to allocate guest memory of length 0");
113
114    let dram_size = std::cmp::min(
115        usize::MAX - u64_to_usize(MMIO32_MEM_SIZE) - u64_to_usize(MMIO64_MEM_SIZE),
116        size,
117    );
118
119    if dram_size != size {
120        logger::warn!(
121            "Requested memory size {} exceeds architectural maximum (1022GiB). Size has been \
122             truncated to {}",
123            size,
124            dram_size
125        );
126    }
127
128    let mut regions = vec![];
129
130    if let Some((start_past_32bit_gap, remaining_past_32bit_gap)) = arch_memory_regions_with_gap(
131        &mut regions,
132        0,
133        dram_size,
134        u64_to_usize(MMIO32_MEM_START),
135        u64_to_usize(MMIO32_MEM_SIZE),
136    ) && let Some((start_past_64bit_gap, remaining_past_64bit_gap)) =
137        arch_memory_regions_with_gap(
138            &mut regions,
139            start_past_32bit_gap,
140            remaining_past_32bit_gap,
141            u64_to_usize(MMIO64_MEM_START),
142            u64_to_usize(MMIO64_MEM_SIZE),
143        )
144    {
145        regions.push((
146            GuestAddress(start_past_64bit_gap as u64),
147            remaining_past_64bit_gap,
148        ));
149    }
150
151    regions
152}
153
154/// Returns the memory address where the kernel could be loaded.
155pub fn get_kernel_start() -> u64 {
156    layout::HIMEM_START
157}
158
159/// Returns the memory address where the initrd could be loaded.
160pub fn initrd_load_addr(guest_mem: &GuestMemoryMmap, initrd_size: usize) -> Option<u64> {
161    let first_region = guest_mem.find_region(GuestAddress::new(0))?;
162    let lowmem_size = u64_to_usize(first_region.len());
163
164    if lowmem_size < initrd_size {
165        return None;
166    }
167
168    Some(align_down(
169        usize_to_u64(lowmem_size - initrd_size),
170        usize_to_u64(super::GUEST_PAGE_SIZE),
171    ))
172}
173
174/// Configures the system for booting Linux.
175#[allow(clippy::too_many_arguments)]
176pub fn configure_system_for_boot(
177    kvm: &Kvm,
178    vm: &Vm,
179    device_manager: &mut DeviceManager,
180    vcpus: &mut [Vcpu],
181    machine_config: &MachineConfig,
182    cpu_template: &CustomCpuTemplate,
183    entry_point: EntryPoint,
184    initrd: &Option<InitrdConfig>,
185    boot_cmdline: Cmdline,
186) -> Result<(), ConfigurationError> {
187    // Construct the base CpuConfiguration to apply CPU template onto.
188    let cpu_config = CpuConfiguration::new(kvm.supported_cpuid.clone(), cpu_template, &vcpus[0])?;
189    // Apply CPU template to the base CpuConfiguration.
190    let cpu_config = CpuConfiguration::apply_template(cpu_config, cpu_template)?;
191
192    let vcpu_config = VcpuConfig {
193        vcpu_count: machine_config.vcpu_count,
194        smt: machine_config.smt,
195        cpu_config,
196    };
197
198    // Configure vCPUs with normalizing and setting the generated CPU configuration.
199    for vcpu in vcpus.iter_mut() {
200        vcpu.kvm_vcpu
201            .configure(vm.guest_memory(), entry_point, &vcpu_config)?;
202    }
203
204    // Write the kernel command line to guest memory. This is x86_64 specific, since on
205    // aarch64 the command line will be specified through the FDT.
206    let cmdline_size = boot_cmdline
207        .as_cstring()
208        .map(|cmdline_cstring| cmdline_cstring.as_bytes_with_nul().len())
209        .expect("Cannot create cstring from cmdline string");
210
211    load_cmdline(
212        vm.guest_memory(),
213        GuestAddress(crate::arch::x86_64::layout::CMDLINE_START),
214        &boot_cmdline,
215    )
216    .map_err(ConfigurationError::LoadCommandline)?;
217
218    // Note that this puts the mptable at the last 1k of Linux's 640k base RAM
219    mptable::setup_mptable(
220        vm.guest_memory(),
221        &mut vm.resource_allocator(),
222        vcpu_config.vcpu_count,
223    )
224    .map_err(ConfigurationError::MpTableSetup)?;
225
226    match entry_point.protocol {
227        BootProtocol::PvhBoot => {
228            configure_pvh(vm.guest_memory(), GuestAddress(CMDLINE_START), initrd)?;
229        }
230        BootProtocol::LinuxBoot => {
231            configure_64bit_boot(
232                vm.guest_memory(),
233                GuestAddress(CMDLINE_START),
234                cmdline_size,
235                initrd,
236            )?;
237        }
238    }
239
240    // Create ACPI tables and write them in guest memory
241    // For the time being we only support ACPI in x86_64
242    create_acpi_tables(
243        vm.guest_memory(),
244        device_manager,
245        &mut vm.resource_allocator(),
246        vcpus,
247    )?;
248    Ok(())
249}
250
251fn configure_pvh(
252    guest_mem: &GuestMemoryMmap,
253    cmdline_addr: GuestAddress,
254    initrd: &Option<InitrdConfig>,
255) -> Result<(), ConfigurationError> {
256    const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336e_c578;
257    let himem_start = GuestAddress(layout::HIMEM_START);
258
259    // Vector to hold modules (currently either empty or holding initrd).
260    let mut modules: Vec<hvm_modlist_entry> = Vec::new();
261    if let Some(initrd_config) = initrd {
262        // The initrd has been written to guest memory already, here we just need to
263        // create the module structure that describes it.
264        modules.push(hvm_modlist_entry {
265            paddr: initrd_config.address.raw_value(),
266            size: initrd_config.size as u64,
267            ..Default::default()
268        });
269    }
270
271    // Vector to hold the memory maps which needs to be written to guest memory
272    // at MEMMAP_START after all of the mappings are recorded.
273    let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
274
275    // Create the memory map entries.
276    memmap.push(hvm_memmap_table_entry {
277        addr: 0,
278        size: SYSTEM_MEM_START,
279        type_: MEMMAP_TYPE_RAM,
280        ..Default::default()
281    });
282    memmap.push(hvm_memmap_table_entry {
283        addr: SYSTEM_MEM_START,
284        size: SYSTEM_MEM_SIZE,
285        type_: E820_RESERVED,
286        ..Default::default()
287    });
288    memmap.push(hvm_memmap_table_entry {
289        addr: PCI_MMCONFIG_START,
290        size: PCI_MMCONFIG_SIZE,
291        type_: E820_RESERVED,
292        ..Default::default()
293    });
294
295    for region in guest_mem
296        .iter()
297        .filter(|region| region.region_type == GuestRegionType::Dram)
298    {
299        // the first 1MB is reserved for the kernel
300        let addr = max(himem_start, region.start_addr());
301        memmap.push(hvm_memmap_table_entry {
302            addr: addr.raw_value(),
303            size: region.last_addr().unchecked_offset_from(addr) + 1,
304            type_: MEMMAP_TYPE_RAM,
305            ..Default::default()
306        });
307    }
308
309    // Construct the hvm_start_info structure and serialize it into
310    // boot_params.  This will be stored at PVH_INFO_START address, and %rbx
311    // will be initialized to contain PVH_INFO_START prior to starting the
312    // guest, as required by the PVH ABI.
313    #[allow(clippy::cast_possible_truncation)] // the vec lengths are single digit integers
314    let mut start_info = hvm_start_info {
315        magic: XEN_HVM_START_MAGIC_VALUE,
316        version: 1,
317        cmdline_paddr: cmdline_addr.raw_value(),
318        memmap_paddr: layout::MEMMAP_START,
319        memmap_entries: memmap.len() as u32,
320        nr_modules: modules.len() as u32,
321        ..Default::default()
322    };
323    if !modules.is_empty() {
324        start_info.modlist_paddr = layout::MODLIST_START;
325    }
326    let mut boot_params =
327        BootParams::new::<hvm_start_info>(&start_info, GuestAddress(layout::PVH_INFO_START));
328
329    // Copy the vector with the memmap table to the MEMMAP_START address
330    // which is already saved in the memmap_paddr field of hvm_start_info struct.
331    boot_params.set_sections::<hvm_memmap_table_entry>(&memmap, GuestAddress(layout::MEMMAP_START));
332
333    // Copy the vector with the modules list to the MODLIST_START address.
334    // Note that we only set the modlist_paddr address if there is a nonzero
335    // number of modules, but serializing an empty list is harmless.
336    boot_params.set_modules::<hvm_modlist_entry>(&modules, GuestAddress(layout::MODLIST_START));
337
338    // Write the hvm_start_info struct to guest memory.
339    PvhBootConfigurator::write_bootparams(&boot_params, guest_mem)
340        .map_err(|_| ConfigurationError::StartInfoSetup)
341}
342
343fn configure_64bit_boot(
344    guest_mem: &GuestMemoryMmap,
345    cmdline_addr: GuestAddress,
346    cmdline_size: usize,
347    initrd: &Option<InitrdConfig>,
348) -> Result<(), ConfigurationError> {
349    const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
350    const KERNEL_HDR_MAGIC: u32 = 0x5372_6448;
351    const KERNEL_LOADER_OTHER: u8 = 0xff;
352    const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x0100_0000; // Must be non-zero.
353
354    let himem_start = GuestAddress(layout::HIMEM_START);
355
356    // Set the location of RSDP in Boot Parameters to help the guest kernel find it faster.
357    let mut params = boot_params {
358        acpi_rsdp_addr: layout::RSDP_ADDR,
359        ..Default::default()
360    };
361
362    params.hdr.type_of_loader = KERNEL_LOADER_OTHER;
363    params.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
364    params.hdr.header = KERNEL_HDR_MAGIC;
365    params.hdr.cmd_line_ptr = u32::try_from(cmdline_addr.raw_value()).unwrap();
366    params.hdr.cmdline_size = u32::try_from(cmdline_size).unwrap();
367    params.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
368    if let Some(initrd_config) = initrd {
369        params.hdr.ramdisk_image = u32::try_from(initrd_config.address.raw_value()).unwrap();
370        params.hdr.ramdisk_size = u32::try_from(initrd_config.size).unwrap();
371    }
372
373    // We mark first [0x0, SYSTEM_MEM_START) region as usable RAM and the subsequent
374    // [SYSTEM_MEM_START, (SYSTEM_MEM_START + SYSTEM_MEM_SIZE)) as reserved (note
375    // SYSTEM_MEM_SIZE + SYSTEM_MEM_SIZE == HIMEM_START).
376    add_e820_entry(&mut params, 0, layout::SYSTEM_MEM_START, E820_RAM)?;
377    add_e820_entry(
378        &mut params,
379        layout::SYSTEM_MEM_START,
380        layout::SYSTEM_MEM_SIZE,
381        E820_RESERVED,
382    )?;
383    add_e820_entry(
384        &mut params,
385        PCI_MMCONFIG_START,
386        PCI_MMCONFIG_SIZE,
387        E820_RESERVED,
388    )?;
389
390    for region in guest_mem
391        .iter()
392        .filter(|region| region.region_type == GuestRegionType::Dram)
393    {
394        // the first 1MB is reserved for the kernel
395        let addr = max(himem_start, region.start_addr());
396        add_e820_entry(
397            &mut params,
398            addr.raw_value(),
399            region.last_addr().unchecked_offset_from(addr) + 1,
400            E820_RAM,
401        )?;
402    }
403
404    LinuxBootConfigurator::write_bootparams(
405        &BootParams::new(&params, GuestAddress(layout::ZERO_PAGE_START)),
406        guest_mem,
407    )
408    .map_err(|_| ConfigurationError::ZeroPageSetup)
409}
410
411/// Add an e820 region to the e820 map.
412/// Returns Ok(()) if successful, or an error if there is no space left in the map.
413fn add_e820_entry(
414    params: &mut boot_params,
415    addr: u64,
416    size: u64,
417    mem_type: u32,
418) -> Result<(), ConfigurationError> {
419    if params.e820_entries as usize >= params.e820_table.len() {
420        return Err(ConfigurationError::E820Configuration);
421    }
422
423    params.e820_table[params.e820_entries as usize].addr = addr;
424    params.e820_table[params.e820_entries as usize].size = size;
425    params.e820_table[params.e820_entries as usize].type_ = mem_type;
426    params.e820_entries += 1;
427
428    Ok(())
429}
430
431/// Load linux kernel into guest memory.
432pub fn load_kernel(
433    kernel: &File,
434    guest_memory: &GuestMemoryMmap,
435) -> Result<EntryPoint, ConfigurationError> {
436    // Need to clone the File because reading from it
437    // mutates it.
438    let mut kernel_file = kernel
439        .try_clone()
440        .map_err(|_| ConfigurationError::KernelFile)?;
441
442    let entry_addr = Loader::load(
443        guest_memory,
444        None,
445        &mut kernel_file,
446        Some(GuestAddress(get_kernel_start())),
447    )
448    .map_err(ConfigurationError::KernelLoader)?;
449
450    let mut entry_point_addr: GuestAddress = entry_addr.kernel_load;
451    let mut boot_prot: BootProtocol = BootProtocol::LinuxBoot;
452    if let PvhBootCapability::PvhEntryPresent(pvh_entry_addr) = entry_addr.pvh_boot_cap {
453        // Use the PVH kernel entry point to boot the guest
454        entry_point_addr = pvh_entry_addr;
455        boot_prot = BootProtocol::PvhBoot;
456    }
457
458    debug!("Kernel loaded using {boot_prot}");
459
460    Ok(EntryPoint {
461        entry_addr: entry_point_addr,
462        protocol: boot_prot,
463    })
464}
465
466#[cfg(kani)]
467mod verification {
468
469    use crate::arch::arch_memory_regions;
470    use crate::arch::x86_64::layout::{
471        FIRST_ADDR_PAST_32BITS, FIRST_ADDR_PAST_64BITS_MMIO, MMIO32_MEM_SIZE, MMIO32_MEM_START,
472        MMIO64_MEM_SIZE, MMIO64_MEM_START,
473    };
474    use crate::utils::u64_to_usize;
475
476    #[kani::proof]
477    #[kani::unwind(4)]
478    fn verify_arch_memory_regions() {
479        let len: u64 = kani::any::<u64>();
480
481        kani::assume(len > 0);
482
483        let regions = arch_memory_regions(len as usize);
484
485        // There are two MMIO gaps, so we can get either 1, 2 or 3 regions
486        assert!(regions.len() <= 3);
487        assert!(regions.len() >= 1);
488
489        // The first address is always 0
490        assert_eq!(regions[0].0.0, 0);
491
492        // The total length of all regions is what we requested
493        let actual_size = regions.iter().map(|&(_, len)| len).sum::<usize>();
494        assert!(actual_size <= len as usize);
495        if actual_size < u64_to_usize(len) {
496            assert_eq!(
497                actual_size,
498                usize::MAX - u64_to_usize(MMIO32_MEM_SIZE) - u64_to_usize(MMIO64_MEM_SIZE)
499            );
500        }
501
502        // No region overlaps the MMIO gap
503        assert!(
504            regions
505                .iter()
506                .all(|&(start, len)| (start.0 >= FIRST_ADDR_PAST_32BITS
507                    || start.0 + len as u64 <= MMIO32_MEM_START)
508                    && (start.0 >= FIRST_ADDR_PAST_64BITS_MMIO
509                        || start.0 + len as u64 <= MMIO64_MEM_START))
510        );
511
512        // All regions have non-zero length
513        assert!(regions.iter().all(|&(_, len)| len > 0));
514
515        // If there's at least two regions, they perfectly snuggle up to one of the two MMIO gaps
516        if regions.len() >= 2 {
517            kani::cover!();
518
519            assert_eq!(regions[0].0.0 + regions[0].1 as u64, MMIO32_MEM_START);
520            assert_eq!(regions[1].0.0, FIRST_ADDR_PAST_32BITS);
521        }
522
523        // If there are three regions, the last two perfectly snuggle up to the 64bit
524        // MMIO gap
525        if regions.len() == 3 {
526            kani::cover!();
527
528            assert_eq!(regions[1].0.0 + regions[1].1 as u64, MMIO64_MEM_START);
529            assert_eq!(regions[2].0.0, FIRST_ADDR_PAST_64BITS_MMIO);
530        }
531    }
532}
533
534#[cfg(test)]
535mod tests {
536    use linux_loader::loader::bootparam::boot_e820_entry;
537
538    use super::*;
539    use crate::arch::x86_64::layout::FIRST_ADDR_PAST_32BITS;
540    use crate::test_utils::{arch_mem, single_region_mem};
541    use crate::utils::mib_to_bytes;
542    use crate::vstate::resources::ResourceAllocator;
543
544    #[test]
545    fn regions_lt_4gb() {
546        let regions = arch_memory_regions(1usize << 29);
547        assert_eq!(1, regions.len());
548        assert_eq!(GuestAddress(0), regions[0].0);
549        assert_eq!(1usize << 29, regions[0].1);
550    }
551
552    #[test]
553    fn regions_gt_4gb() {
554        const MEMORY_SIZE: usize = (1 << 32) + 0x8000;
555
556        let regions = arch_memory_regions(MEMORY_SIZE);
557        assert_eq!(2, regions.len());
558        assert_eq!(GuestAddress(0), regions[0].0);
559        assert_eq!(GuestAddress(1u64 << 32), regions[1].0);
560
561        assert_eq!(
562            regions[1],
563            (
564                GuestAddress(FIRST_ADDR_PAST_32BITS),
565                MEMORY_SIZE - regions[0].1
566            )
567        )
568    }
569
570    #[test]
571    fn test_system_configuration() {
572        let no_vcpus = 4;
573        let gm = single_region_mem(0x10000);
574        let mut resource_allocator = ResourceAllocator::new();
575        let err = mptable::setup_mptable(&gm, &mut resource_allocator, 1);
576        assert!(matches!(
577            err.unwrap_err(),
578            mptable::MptableError::NotEnoughMemory
579        ));
580
581        // Now assigning some memory that falls before the 32bit memory hole.
582        let mem_size = mib_to_bytes(128);
583        let gm = arch_mem(mem_size);
584        let mut resource_allocator = ResourceAllocator::new();
585        mptable::setup_mptable(&gm, &mut resource_allocator, no_vcpus).unwrap();
586        configure_64bit_boot(&gm, GuestAddress(0), 0, &None).unwrap();
587        configure_pvh(&gm, GuestAddress(0), &None).unwrap();
588
589        // Now assigning some memory that is equal to the start of the 32bit memory hole.
590        let mem_size = mib_to_bytes(3328);
591        let gm = arch_mem(mem_size);
592        let mut resource_allocator = ResourceAllocator::new();
593        mptable::setup_mptable(&gm, &mut resource_allocator, no_vcpus).unwrap();
594        configure_64bit_boot(&gm, GuestAddress(0), 0, &None).unwrap();
595        configure_pvh(&gm, GuestAddress(0), &None).unwrap();
596
597        // Now assigning some memory that falls after the 32bit memory hole.
598        let mem_size = mib_to_bytes(3330);
599        let gm = arch_mem(mem_size);
600        let mut resource_allocator = ResourceAllocator::new();
601        mptable::setup_mptable(&gm, &mut resource_allocator, no_vcpus).unwrap();
602        configure_64bit_boot(&gm, GuestAddress(0), 0, &None).unwrap();
603        configure_pvh(&gm, GuestAddress(0), &None).unwrap();
604    }
605
606    #[test]
607    fn test_add_e820_entry() {
608        let e820_map = [(boot_e820_entry {
609            addr: 0x1,
610            size: 4,
611            type_: 1,
612        }); 128];
613
614        let expected_params = boot_params {
615            e820_table: e820_map,
616            e820_entries: 1,
617            ..Default::default()
618        };
619
620        let mut params: boot_params = Default::default();
621        add_e820_entry(
622            &mut params,
623            e820_map[0].addr,
624            e820_map[0].size,
625            e820_map[0].type_,
626        )
627        .unwrap();
628        assert_eq!(
629            format!("{:?}", params.e820_table[0]),
630            format!("{:?}", expected_params.e820_table[0])
631        );
632        assert_eq!(params.e820_entries, expected_params.e820_entries);
633
634        // Exercise the scenario where the field storing the length of the e820 entry table is
635        // is bigger than the allocated memory.
636        params.e820_entries = u8::try_from(params.e820_table.len()).unwrap() + 1;
637        assert!(
638            add_e820_entry(
639                &mut params,
640                e820_map[0].addr,
641                e820_map[0].size,
642                e820_map[0].type_
643            )
644            .is_err()
645        );
646    }
647}