diff --git a/src/arch/src/x86_64/layout.rs b/src/arch/src/x86_64/layout.rs index 936458d54d4c..84be91489679 100644 --- a/src/arch/src/x86_64/layout.rs +++ b/src/arch/src/x86_64/layout.rs @@ -30,5 +30,13 @@ pub const KVM_TSS_ADDRESS: u64 = 0xfffb_d000; /// Address of the hvm_start_info struct used in PVH boot pub const PVH_INFO_START: u64 = 0x6000; +/// Starting address of array of modules of hvm_modlist_entry type. +/// Used to enable initrd support using the PVH boot ABI. +pub const MODLIST_START: u64 = 0x6040; + +/// Address of memory map table used in PVH boot. Can overlap +/// with the zero page address since they are mutually exclusive. +pub const MEMMAP_START: u64 = 0x7000; + /// The 'zero page', a.k.a linux kernel bootparams. pub const ZERO_PAGE_START: u64 = 0x7000; diff --git a/src/arch/src/x86_64/mod.rs b/src/arch/src/x86_64/mod.rs index 3462da6786bf..d118f925632d 100644 --- a/src/arch/src/x86_64/mod.rs +++ b/src/arch/src/x86_64/mod.rs @@ -1,3 +1,5 @@ +// Copyright © 2020, Oracle and/or its affiliates. +// // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // @@ -17,14 +19,20 @@ pub mod msr; pub mod regs; use linux_loader::configurator::linux::LinuxBootConfigurator; +use linux_loader::configurator::pvh::PvhBootConfigurator; use linux_loader::configurator::{BootConfigurator, BootParams}; use linux_loader::loader::bootparam::boot_params; +use linux_loader::loader::elf::start_info::{ + hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info, +}; use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion}; +use super::BootProtocol; use crate::InitrdConfig; // Value taken from https://elixir.bootlin.com/linux/v5.10.68/source/arch/x86/include/uapi/asm/e820.h#L31 const E820_RAM: u32 = 1; +const MEMMAP_TYPE_RAM: u32 = 1; /// Errors thrown while configuring x86_64 system. #[derive(Debug, PartialEq, Eq, derive_more::From)] @@ -37,6 +45,12 @@ pub enum Error { ZeroPageSetup, /// Failed to compute initrd address. InitrdAddress, + /// Error writing module entry to guest memory. + ModlistSetup, + /// Error writing memory map table to guest memory. + MemmapTableSetup, + /// Error writing hvm_start_info to guest memory. + StartInfoSetup, } // Where BIOS/VGA magic would live on a real PC. @@ -97,12 +111,139 @@ pub fn initrd_load_addr(guest_mem: &GuestMemoryMmap, initrd_size: usize) -> supe /// * `cmdline_size` - Size of the kernel command line in bytes including the null terminator. /// * `initrd` - Information about where the ramdisk image was loaded in the `guest_mem`. /// * `num_cpus` - Number of virtual CPUs the guest will have. +/// * `boot_prot` - Boot protocol that will be used to boot the guest. pub fn configure_system( guest_mem: &GuestMemoryMmap, cmdline_addr: GuestAddress, cmdline_size: usize, initrd: &Option, num_cpus: u8, + boot_prot: BootProtocol, +) -> super::Result<()> { + // Note that this puts the mptable at the last 1k of Linux's 640k base RAM + mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?; + + match boot_prot { + BootProtocol::PvhBoot => { + configure_pvh(guest_mem, cmdline_addr, initrd)?; + } + BootProtocol::LinuxBoot => { + configure_64bit_boot(guest_mem, cmdline_addr, cmdline_size, initrd)?; + } + } + + Ok(()) +} + +fn configure_pvh( + guest_mem: &GuestMemoryMmap, + cmdline_addr: GuestAddress, + initrd: &Option, +) -> super::Result<()> { + const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336e_c578; + let first_addr_past_32bits = GuestAddress(FIRST_ADDR_PAST_32BITS); + let end_32bit_gap_start = GuestAddress(MMIO_MEM_START); + let himem_start = GuestAddress(layout::HIMEM_START); + + // Vector to hold modules (currently either empty or holding initrd). + let mut modules: Vec = Vec::new(); + if let Some(initrd_config) = initrd { + // The initrd has been written to guest memory already, here we just need to + // create the module structure that describes it. + modules.push(hvm_modlist_entry { + paddr: initrd_config.address.raw_value(), + size: initrd_config.size as u64, + ..Default::default() + }); + } + + // Vector to hold the memory maps which needs to be written to guest memory + // at MEMMAP_START after all of the mappings are recorded. + let mut memmap: Vec = Vec::new(); + + // Create the memory map entries. + add_memmap_entry(&mut memmap, 0, EBDA_START, MEMMAP_TYPE_RAM)?; + let last_addr = guest_mem.last_addr(); + if last_addr < end_32bit_gap_start { + add_memmap_entry( + &mut memmap, + himem_start.raw_value() as u64, + last_addr.unchecked_offset_from(himem_start) as u64 + 1, + MEMMAP_TYPE_RAM, + )?; + } else { + add_memmap_entry( + &mut memmap, + himem_start.raw_value(), + end_32bit_gap_start.unchecked_offset_from(himem_start), + MEMMAP_TYPE_RAM, + )?; + + if last_addr > first_addr_past_32bits { + add_memmap_entry( + &mut memmap, + first_addr_past_32bits.raw_value(), + last_addr.unchecked_offset_from(first_addr_past_32bits) + 1, + MEMMAP_TYPE_RAM, + )?; + } + } + + // Construct the hvm_start_info structure and serialize it into + // boot_params. This will be stored at PVH_INFO_START address, and %rbx + // will be initialized to contain PVH_INFO_START prior to starting the + // guest, as required by the PVH ABI. + let mut start_info = hvm_start_info { + magic: XEN_HVM_START_MAGIC_VALUE, + version: 1, + cmdline_paddr: cmdline_addr.raw_value(), + memmap_paddr: layout::MEMMAP_START, + memmap_entries: memmap.len() as u32, + nr_modules: modules.len() as u32, + ..Default::default() + }; + if !modules.is_empty() { + start_info.modlist_paddr = layout::MODLIST_START; + } + let mut boot_params = + BootParams::new::(&start_info, GuestAddress(layout::PVH_INFO_START)); + + // Copy the vector with the memmap table to the MEMMAP_START address + // which is already saved in the memmap_paddr field of hvm_start_info struct. + boot_params.set_sections::(&memmap, GuestAddress(layout::MEMMAP_START)); + + // Copy the vector with the modules list to the MODLIST_START address. + // Note that we only set the modlist_paddr address if there is a nonzero + // number of modules, but serializing an empty list is harmless. + boot_params.set_modules::(&modules, GuestAddress(layout::MODLIST_START)); + + // Write the hvm_start_info struct to guest memory. + PvhBootConfigurator::write_bootparams(&boot_params, guest_mem) + .map_err(|_| Error::StartInfoSetup) +} + +fn add_memmap_entry( + memmap: &mut Vec, + addr: u64, + size: u64, + mem_type: u32, +) -> super::Result<()> { + // Add the table entry to the vector + memmap.push(hvm_memmap_table_entry { + addr, + size, + type_: mem_type, + reserved: 0, + }); + + Ok(()) +} + +fn configure_64bit_boot( + guest_mem: &GuestMemoryMmap, + cmdline_addr: GuestAddress, + cmdline_size: usize, + initrd: &Option, ) -> super::Result<()> { const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55; const KERNEL_HDR_MAGIC: u32 = 0x5372_6448; @@ -113,9 +254,6 @@ pub fn configure_system( let himem_start = GuestAddress(layout::HIMEM_START); - // Note that this puts the mptable at the last 1k of Linux's 640k base RAM - mptable::setup_mptable(guest_mem, num_cpus)?; - let mut params = boot_params::default(); params.hdr.type_of_loader = KERNEL_LOADER_OTHER; @@ -218,7 +356,8 @@ mod tests { let gm = vm_memory::test_utils::create_anon_guest_memory(&[(GuestAddress(0), 0x10000)], false) .unwrap(); - let config_err = configure_system(&gm, GuestAddress(0), 0, &None, 1); + let config_err = + configure_system(&gm, GuestAddress(0), 0, &None, 1, BootProtocol::LinuxBoot); assert!(config_err.is_err()); assert_eq!( config_err.unwrap_err(), @@ -229,19 +368,70 @@ mod tests { let mem_size = 128 << 20; let arch_mem_regions = arch_memory_regions(mem_size); let gm = vm_memory::test_utils::create_anon_guest_memory(&arch_mem_regions, false).unwrap(); - configure_system(&gm, GuestAddress(0), 0, &None, no_vcpus).unwrap(); + configure_system( + &gm, + GuestAddress(0), + 0, + &None, + no_vcpus, + BootProtocol::LinuxBoot, + ) + .unwrap(); + configure_system( + &gm, + GuestAddress(0), + 0, + &None, + no_vcpus, + BootProtocol::PvhBoot, + ) + .unwrap(); // Now assigning some memory that is equal to the start of the 32bit memory hole. let mem_size = 3328 << 20; let arch_mem_regions = arch_memory_regions(mem_size); let gm = vm_memory::test_utils::create_anon_guest_memory(&arch_mem_regions, false).unwrap(); - configure_system(&gm, GuestAddress(0), 0, &None, no_vcpus).unwrap(); + configure_system( + &gm, + GuestAddress(0), + 0, + &None, + no_vcpus, + BootProtocol::LinuxBoot, + ) + .unwrap(); + configure_system( + &gm, + GuestAddress(0), + 0, + &None, + no_vcpus, + BootProtocol::PvhBoot, + ) + .unwrap(); // Now assigning some memory that falls after the 32bit memory hole. let mem_size = 3330 << 20; let arch_mem_regions = arch_memory_regions(mem_size); let gm = vm_memory::test_utils::create_anon_guest_memory(&arch_mem_regions, false).unwrap(); - configure_system(&gm, GuestAddress(0), 0, &None, no_vcpus).unwrap(); + configure_system( + &gm, + GuestAddress(0), + 0, + &None, + no_vcpus, + BootProtocol::LinuxBoot, + ) + .unwrap(); + configure_system( + &gm, + GuestAddress(0), + 0, + &None, + no_vcpus, + BootProtocol::PvhBoot, + ) + .unwrap(); } #[test] @@ -283,4 +473,31 @@ mod tests { ) .is_err()); } + + #[test] + fn test_add_memmap_entry() { + const MEMMAP_TYPE_RESERVED: u32 = 2; + + let mut memmap: Vec = Vec::new(); + + let expected_memmap = vec![ + hvm_memmap_table_entry { + addr: 0x0, + size: 0x1000, + type_: MEMMAP_TYPE_RAM, + ..Default::default() + }, + hvm_memmap_table_entry { + addr: 0x10000, + size: 0xa000, + type_: MEMMAP_TYPE_RESERVED, + ..Default::default() + }, + ]; + + add_memmap_entry(&mut memmap, 0, 0x1000, MEMMAP_TYPE_RAM).unwrap(); + add_memmap_entry(&mut memmap, 0x10000, 0xa000, MEMMAP_TYPE_RESERVED).unwrap(); + + assert_eq!(format!("{:?}", memmap), format!("{:?}", expected_memmap)); + } } diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index 2b8a592cb67b..d1c70b88a1db 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -886,6 +886,7 @@ pub fn configure_system_for_boot( cmdline_size, initrd, vcpus.len() as u8, + entry_point.protocol, ) .map_err(ConfigureSystem)?; }