Skip to content

Commit

Permalink
pvh/arch-x86_64: Write start_info to guest memory
Browse files Browse the repository at this point in the history
Fill the hvm_start_info and related structures as specified
in the PVH boot protocol. Write the data structures to guest
memory at the GPA that will be stored in %rbx when the guest starts.

Signed-off-by: Colin Percival <[email protected]>
Co-authored-by: Alejandro Jimenez <[email protected]>
  • Loading branch information
cperciva and aljimenezb committed Dec 29, 2022
1 parent 6e8ceaf commit a46191f
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 7 deletions.
8 changes: 8 additions & 0 deletions src/arch/src/x86_64/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
231 changes: 224 additions & 7 deletions src/arch/src/x86_64/mod.rs
Original file line number Diff line number Diff line change
@@ -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
//
Expand All @@ -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)]
Expand All @@ -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.
Expand Down Expand Up @@ -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<InitrdConfig>,
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<InitrdConfig>,
) -> 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<hvm_modlist_entry> = 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<hvm_memmap_table_entry> = 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::<hvm_start_info>(&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::<hvm_memmap_table_entry>(&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::<hvm_modlist_entry>(&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<hvm_memmap_table_entry>,
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<InitrdConfig>,
) -> super::Result<()> {
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
const KERNEL_HDR_MAGIC: u32 = 0x5372_6448;
Expand All @@ -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;
Expand Down Expand Up @@ -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(),
Expand All @@ -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]
Expand Down Expand Up @@ -283,4 +473,31 @@ mod tests {
)
.is_err());
}

#[test]
fn test_add_memmap_entry() {
const MEMMAP_TYPE_RESERVED: u32 = 2;

let mut memmap: Vec<hvm_memmap_table_entry> = 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));
}
}
1 change: 1 addition & 0 deletions src/vmm/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,7 @@ pub fn configure_system_for_boot(
cmdline_size,
initrd,
vcpus.len() as u8,
entry_point.protocol,
)
.map_err(ConfigureSystem)?;
}
Expand Down

0 comments on commit a46191f

Please sign in to comment.