use guestmem::GuestMemory;
use hvdef::HV_PAGE_SIZE;
use hvlite_defs::config::SerialInformation;
use hvlite_defs::config::Vtl2BaseAddressType;
use igvm::IgvmDirectiveHeader;
use igvm::IgvmFile;
use igvm::IgvmPlatformHeader;
use igvm::IgvmRelocatableRegion;
use igvm::page_table::CpuPagingState;
use igvm_defs::IGVM_VHS_MEMORY_MAP_ENTRY;
use igvm_defs::IGVM_VHS_MEMORY_RANGE;
use igvm_defs::IGVM_VHS_MMIO_RANGES;
use igvm_defs::IGVM_VHS_PARAMETER;
use igvm_defs::IGVM_VHS_PARAMETER_INSERT;
use igvm_defs::IgvmPageDataType;
use igvm_defs::IgvmPlatformType;
use loader::importer::Aarch64Register;
use loader::importer::BootPageAcceptance;
use loader::importer::GuestArch;
use loader::importer::ImageLoad;
use loader::importer::StartupMemoryType;
use loader::importer::TableRegister;
use loader::importer::X86Register;
use memory_range::MemoryRange;
use memory_range::subtract_ranges;
use range_map_vec::RangeMap;
use std::collections::HashMap;
use std::ffi::CString;
use std::io::Read;
use std::io::Seek;
use thiserror::Error;
use virt::PageVisibility;
use vm_loader::Loader;
use vm_topology::memory::MemoryLayout;
use vm_topology::memory::MemoryRangeWithNode;
use vm_topology::processor::ArchTopology;
use vm_topology::processor::ProcessorTopology;
use vm_topology::processor::aarch64::Aarch64Topology;
use vm_topology::processor::x86::X86Topology;
use zerocopy::IntoBytes;
#[derive(Debug, Error)]
pub enum Error {
#[error("command line is not a valid C string")]
InvalidCommandLine(#[source] std::ffi::NulError),
#[error("failed to read igvm file")]
Igvm(#[source] std::io::Error),
#[error("invalid igvm file")]
InvalidIgvmFile(#[source] igvm::Error),
#[error("loader error")]
Loader(#[source] anyhow::Error),
#[error("parameter too large for parameter area")]
ParameterTooLarge,
#[error("relocation not supported in igvm file")]
RelocationNotSupported,
#[error("multiple igvm relocation headers specified in the file")]
MultipleIgvmRelocationHeaders,
#[error("relocated base address is not supported by relocation header {file_relocation:?}")]
RelocationBaseInvalid {
file_relocation: IgvmRelocatableRegion,
},
#[error("page table relocation header not specified")]
NoPageTableRelocationHeader,
#[error("vp index does not describe the BSP in relocation headers")]
RelocationVpIndex,
#[error("vtl does not target vtl2 in relocation headers")]
RelocationVtl,
#[error("page table builder")]
PageTableBuilder(#[source] igvm::page_table::Error),
#[error("no vtl2 memory range in memory layout")]
NoVtl2MemoryRange,
#[error("no vtl2 memory source in igvm file")]
Vtl2MemorySource,
#[error("invalid memory config")]
MemoryConfig(#[source] vm_topology::memory::Error),
#[error("not enough physical address bits to allocate vtl2 range")]
NotEnoughPhysicalAddressBits,
#[error("building device tree for partition failed")]
DeviceTree(fdt::builder::Error),
#[error("supplied vtl2 memory {0} is not aligned to 2MB")]
Vtl2MemoryAligned(u64),
#[error("supplied vtl2 memory {0} is smaller than igvm file VTL2 range {1}")]
Vtl2MemoryTooSmall(u64, u64),
#[error("unsupported guest architecture")]
UnsupportedGuestArch,
#[error("igvm file does not support vbs")]
NoVbsSupport,
#[error("vp context for lower VTL not supported")]
LowerVtlContext,
#[error("missing required memory range {0}")]
MissingRequiredMemory(MemoryRange),
#[error("IGVM file requires at least two mmio ranges")]
UnsupportedMmio,
}
fn from_memory_range(range: &MemoryRange) -> IGVM_VHS_MEMORY_RANGE {
assert!(range.len() % HV_PAGE_SIZE == 0);
IGVM_VHS_MEMORY_RANGE {
starting_gpa_page_number: range.start() / HV_PAGE_SIZE,
number_of_pages: range.len() / HV_PAGE_SIZE,
}
}
fn memory_map_entry(range: &MemoryRange) -> IGVM_VHS_MEMORY_MAP_ENTRY {
assert!(range.len() % HV_PAGE_SIZE == 0);
IGVM_VHS_MEMORY_MAP_ENTRY {
starting_gpa_page_number: range.start() / HV_PAGE_SIZE,
number_of_pages: range.len() / HV_PAGE_SIZE,
entry_type: igvm_defs::MemoryMapEntryType::MEMORY,
flags: 0,
reserved: 0,
}
}
fn from_igvm_vtl(vtl: igvm::hv_defs::Vtl) -> hvdef::Vtl {
match vtl {
igvm::hv_defs::Vtl::Vtl0 => hvdef::Vtl::Vtl0,
igvm::hv_defs::Vtl::Vtl1 => hvdef::Vtl::Vtl1,
igvm::hv_defs::Vtl::Vtl2 => hvdef::Vtl::Vtl2,
}
}
pub fn read_igvm_file(mut file: &std::fs::File) -> Result<IgvmFile, Error> {
let mut file_contents = Vec::new();
file.rewind().map_err(Error::Igvm)?;
file.read_to_end(&mut file_contents).map_err(Error::Igvm)?;
let igvm_file = IgvmFile::new_from_binary(&file_contents, Some(igvm::IsolationType::Vbs))
.map_err(Error::InvalidIgvmFile)?;
Ok(igvm_file)
}
fn vbs_platform_header(igvm_file: &IgvmFile) -> Result<&IgvmPlatformHeader, Error> {
igvm_file
.platforms()
.iter()
.find(|header| {
let IgvmPlatformHeader::SupportedPlatform(info) = header;
info.platform_type == IgvmPlatformType::VSM_ISOLATION
})
.ok_or(Error::NoVbsSupport)
}
pub fn supports_relocations(igvm_file: &IgvmFile) -> bool {
let (mask, _max_vtl) = match vbs_platform_header(igvm_file).unwrap() {
IgvmPlatformHeader::SupportedPlatform(info) => {
debug_assert_eq!(info.platform_type, IgvmPlatformType::VSM_ISOLATION);
(info.compatibility_mask, info.highest_vtl)
}
};
igvm_file.relocations(mask).0.is_some()
}
pub fn vtl2_memory_info(igvm_file: &IgvmFile) -> Result<MemoryRange, Error> {
let (mask, _max_vtl) = match vbs_platform_header(igvm_file)? {
IgvmPlatformHeader::SupportedPlatform(info) => {
debug_assert_eq!(info.platform_type, IgvmPlatformType::VSM_ISOLATION);
(info.compatibility_mask, info.highest_vtl)
}
};
let mut required_memory = None;
for header in igvm_file.directives().iter().filter(|header| {
header
.compatibility_mask()
.map(|header_mask| header_mask & mask == mask)
.unwrap_or(true)
}) {
if let IgvmDirectiveHeader::RequiredMemory {
gpa,
compatibility_mask: _,
number_of_bytes,
vtl2_protectable: true,
} = *header
{
required_memory = Some(MemoryRange::new(gpa..gpa + number_of_bytes as u64));
break;
}
}
match required_memory {
Some(range) => Ok(range),
None => Err(Error::Vtl2MemorySource),
}
}
pub fn vtl2_memory_range(
physical_address_size: u8,
mem_size: u64,
mmio_gaps: &[MemoryRange],
igvm_file: &IgvmFile,
vtl2_size: Option<u64>,
) -> Result<MemoryRange, Error> {
let (mask, _max_vtl) = match vbs_platform_header(igvm_file)? {
IgvmPlatformHeader::SupportedPlatform(info) => {
debug_assert_eq!(info.platform_type, IgvmPlatformType::VSM_ISOLATION);
(info.compatibility_mask, info.highest_vtl)
}
};
let relocs = igvm_file.relocations(mask);
let igvm_size = vtl2_memory_info(igvm_file)?.len();
let reloc_region = relocs.0.ok_or(Error::RelocationNotSupported)?[0].clone();
let alignment = reloc_region.relocation_alignment;
let size = match vtl2_size {
Some(vtl2_size) => {
const TWO_MB: u64 = 2 * 1024 * 1024;
if vtl2_size % TWO_MB != 0 {
return Err(Error::Vtl2MemoryAligned(vtl2_size));
}
if vtl2_size < igvm_size {
return Err(Error::Vtl2MemoryTooSmall(vtl2_size, igvm_size));
}
vtl2_size
}
None => {
igvm_size
}
};
let align_base = |base| -> u64 { (base + alignment - 1) & !(alignment - 1) };
let physical_address_size = physical_address_size - 1;
let dummy_layout = MemoryLayout::new(mem_size, mmio_gaps, None).map_err(Error::MemoryConfig)?;
const MAX_ADDR_32TB: u64 = 32u64 << 40; let max_physical_address = 1 << physical_address_size;
let max_physical_address = max_physical_address.min(MAX_ADDR_32TB);
let (min_addr, max_addr) = (dummy_layout.end_of_ram_or_mmio(), max_physical_address);
let aligned_min_addr = align_base(min_addr);
let aligned_max_addr = (max_addr / alignment) * alignment;
assert!(aligned_min_addr >= reloc_region.minimum_relocation_gpa);
assert!(aligned_max_addr <= reloc_region.maximum_relocation_gpa);
if aligned_min_addr >= aligned_max_addr {
return Err(Error::NotEnoughPhysicalAddressBits);
}
tracing::trace!(min_addr, aligned_min_addr, max_addr, aligned_max_addr);
let possible_bases = (aligned_max_addr - aligned_min_addr) / alignment;
let mut num: u64 = 0;
getrandom::fill(num.as_mut_bytes()).expect("crng failure");
let selected_base = num % (possible_bases - 1);
let selected_addr = aligned_min_addr + (selected_base * alignment);
tracing::trace!(possible_bases, selected_base, selected_addr);
Ok(MemoryRange::new(selected_addr..(selected_addr + size)))
}
fn build_device_tree(
processor_topology: &ProcessorTopology<X86Topology>,
mem_layout: &MemoryLayout,
all_ram: &[MemoryRangeWithNode],
vtl2_protectable_ram: &[MemoryRange],
vtl2_base_address: Vtl2BaseAddressType,
command_line: &str,
with_vmbus_redirect: bool,
com_serial: Option<SerialInformation>,
entropy: Option<&[u8]>,
) -> Result<Vec<u8>, fdt::builder::Error> {
let mut buf = vec![0; HV_PAGE_SIZE as usize * 256];
let mut builder = fdt::builder::Builder::new(fdt::builder::BuilderConfig {
blob_buffer: buf.as_mut_slice(),
string_table_cap: 1024,
memory_reservations: &[],
})?;
let p_address_cells = builder.add_string("#address-cells")?;
let p_size_cells = builder.add_string("#size-cells")?;
let p_model = builder.add_string("model")?;
let p_reg = builder.add_string("reg")?;
let p_ranges = builder.add_string("ranges")?;
let p_device_type = builder.add_string("device_type")?;
let p_status = builder.add_string("status")?;
let p_igvm_type = builder.add_string(igvm_defs::dt::IGVM_DT_IGVM_TYPE_PROPERTY)?;
let p_compatible = builder.add_string("compatible")?;
let p_numa_node_id = builder.add_string("numa-node-id")?;
let p_vmbus_connection_id = builder.add_string("microsoft,message-connection-id")?;
let p_vtl = builder.add_string(igvm_defs::dt::IGVM_DT_VTL_PROPERTY)?;
let p_bootargs = builder.add_string("bootargs")?;
let p_clock_frequency = builder.add_string("clock-frequency")?;
let p_current_speed = builder.add_string("current-speed")?;
let p_interrupts = builder.add_string("interrupts")?;
let mut cpus = builder
.start_node("")?
.add_u32(p_address_cells, 2)? .add_u32(p_size_cells, 2)? .add_str(p_model, "microsoft,hyperv")?
.start_node("cpus")?
.add_u32(p_address_cells, 1)?
.add_u32(p_size_cells, 0)?;
for proc in processor_topology.vps_arch() {
let name = format!("cpu@{:x}", proc.base.vp_index.index() + 1);
cpus = cpus
.start_node(name.as_ref())?
.add_str(p_device_type, "cpu")?
.add_u32(p_reg, proc.apic_id)?
.add_u32(p_numa_node_id, proc.base.vnode)?
.add_str(p_status, "okay")?
.end_node()?;
}
let mut root = cpus.end_node()?;
let (memory_map, vnodes) = build_memory_map(all_ram, vtl2_protectable_ram);
for (entry, vnode) in memory_map.iter().zip(vnodes.iter()).rev() {
let start_address = entry.starting_gpa_page_number * HV_PAGE_SIZE;
let size = entry.number_of_pages * HV_PAGE_SIZE;
let name = format!("memory@{:x}", start_address);
let mut mem = root.start_node(&name)?;
mem = mem.add_str(p_device_type, "memory")?;
mem = mem.add_u64_array(p_reg, &[start_address, size])?;
mem = mem.add_u32(p_igvm_type, entry.entry_type.0 as u32)?;
mem = mem.add_u32(p_numa_node_id, *vnode)?;
root = mem.end_node()?;
}
let mut simple_bus = root
.start_node("bus")?
.add_str(p_compatible, "simple-bus")?
.add_u32(p_address_cells, 2)?
.add_u32(p_size_cells, 2)?
.add_prop_array(p_ranges, &[])?;
let mut mmio_chunks = mem_layout.mmio().chunks(2);
let extract_ranges = |mmio: Option<&[MemoryRange]>| -> Vec<u64> {
let mut ranges = Vec::new();
if let Some(mmio) = mmio {
for entry in mmio {
ranges.push(entry.start());
ranges.push(entry.start());
ranges.push(entry.len());
}
}
ranges
};
let ranges_vtl0 = extract_ranges(mmio_chunks.next());
let ranges_vtl2 = extract_ranges(mmio_chunks.next());
let vmbus_vtl0_name = if ranges_vtl0.is_empty() {
"vmbus-vtl0".into()
} else {
format!("vmbus-vtl0@{:x}", ranges_vtl0[0])
};
let vmbus_vtl0 = simple_bus.start_node(&vmbus_vtl0_name)?;
simple_bus = vmbus_vtl0
.add_u32(p_address_cells, 2)?
.add_u32(p_size_cells, 2)?
.add_str(p_compatible, "microsoft,vmbus")?
.add_u64_array(p_ranges, &ranges_vtl0)?
.add_u32(p_vtl, 0)?
.add_u32(p_vmbus_connection_id, 1)?
.end_node()?;
let vmbus_vtl2_name = if ranges_vtl2.is_empty() {
"vmbus-vtl2".into()
} else {
format!("vmbus-vtl2@{:x}", ranges_vtl2[0])
};
let vmbus_vtl2 = simple_bus.start_node(&vmbus_vtl2_name)?;
simple_bus = vmbus_vtl2
.add_u32(p_address_cells, 2)?
.add_u32(p_size_cells, 2)?
.add_str(p_compatible, "microsoft,vmbus")?
.add_u64_array(p_ranges, &ranges_vtl2)?
.add_u32(p_vtl, 2)?
.add_u32(
p_vmbus_connection_id,
if with_vmbus_redirect {
0x800074
} else {
4
},
)?
.end_node()?;
root = simple_bus.end_node()?;
if let Some(serial_cfg) = com_serial {
let mut io_port_bus = root
.start_node("pio-bus")?
.add_str(p_compatible, "x86-pio-bus")?
.add_u32(p_address_cells, 1)?
.add_u32(p_size_cells, 1)?
.add_prop_array(p_ranges, &[])?;
let serial_name = format!("serial@{:x}", serial_cfg.io_port);
io_port_bus = io_port_bus
.start_node(&serial_name)?
.add_str(p_compatible, "ns16550")?
.add_u32(p_clock_frequency, 0)?
.add_u32(p_current_speed, 115200)?
.add_u64_array(p_reg, &[serial_cfg.io_port.into(), 0x8])?
.add_u64_array(p_interrupts, &[serial_cfg.irq.into()])?
.end_node()?;
root = io_port_bus.end_node()?;
}
root = root
.start_node("chosen")?
.add_str(p_bootargs, command_line)?
.end_node()?;
let p_memory_allocation_mode = root.add_string("memory-allocation-mode")?;
let p_memory_size = root.add_string("memory-size")?;
let p_mmio_size = root.add_string("mmio-size")?;
let p_vf_keep_alive_devs = root.add_string("device-types")?;
let mut openhcl = root.start_node("openhcl")?;
let memory_allocation_mode = match vtl2_base_address {
Vtl2BaseAddressType::Vtl2Allocate { size } => {
if let Some(size) = size {
openhcl = openhcl.add_u64(p_memory_size, size)?;
}
openhcl = openhcl.add_u64(p_mmio_size, 128 * 1024 * 1024)?;
"vtl2"
}
_ => "host",
};
openhcl = openhcl.add_str(p_memory_allocation_mode, memory_allocation_mode)?;
if let Some(entropy) = entropy {
openhcl = openhcl
.start_node("entropy")?
.add_prop_array(p_reg, &[entropy])?
.end_node()?;
}
openhcl = openhcl
.start_node("keep-alive")?
.add_str(p_vf_keep_alive_devs, "nvme")?
.end_node()?;
root = openhcl.end_node()?;
let bytes_used = root
.end_node()?
.build(processor_topology.vp_arch(virt::VpIndex::BSP).apic_id)?;
buf.truncate(bytes_used);
Ok(buf)
}
#[derive(Clone, Copy)]
pub struct AcpiTables<'a> {
pub madt: &'a [u8],
pub srat: &'a [u8],
pub slit: Option<&'a [u8]>,
pub pptt: Option<&'a [u8]>,
}
pub struct LoadIgvmParams<'a, T: ArchTopology> {
pub igvm_file: &'a IgvmFile,
pub gm: &'a GuestMemory,
pub processor_topology: &'a ProcessorTopology<T>,
pub mem_layout: &'a MemoryLayout,
pub cmdline: &'a str,
pub acpi_tables: AcpiTables<'a>,
pub vtl2_base_address: Vtl2BaseAddressType,
pub vtl2_framebuffer_gpa_base: Option<u64>,
pub vtl2_only: bool,
pub with_vmbus_redirect: bool,
pub com_serial: Option<SerialInformation>,
pub entropy: Option<&'a [u8]>,
}
pub fn load_igvm(
params: LoadIgvmParams<'_, vm_topology::processor::TargetTopology>,
) -> Result<
(
Vec<loader::importer::Register>,
Vec<(MemoryRange, PageVisibility)>,
),
Error,
> {
#[cfg(guest_arch = "x86_64")]
{
load_igvm_x86(params)
}
#[cfg(guest_arch = "aarch64")]
{
load_igvm_aarch64(params)
}
}
#[cfg_attr(not(guest_arch = "x86_64"), expect(dead_code))]
fn load_igvm_x86(
params: LoadIgvmParams<'_, X86Topology>,
) -> Result<(Vec<X86Register>, Vec<(MemoryRange, PageVisibility)>), Error> {
let LoadIgvmParams {
igvm_file,
gm,
processor_topology,
mem_layout,
cmdline,
acpi_tables,
vtl2_base_address,
vtl2_framebuffer_gpa_base,
vtl2_only,
with_vmbus_redirect,
com_serial,
entropy,
} = params;
let relocations_enabled = match vtl2_base_address {
Vtl2BaseAddressType::File | Vtl2BaseAddressType::Vtl2Allocate { .. } => false,
Vtl2BaseAddressType::Absolute(_) | Vtl2BaseAddressType::MemoryLayout { .. } => true,
};
let cmdline = if let Some(vtl2_framebuffer_gpa_base) = vtl2_framebuffer_gpa_base {
format!(
"OPENHCL_FRAMEBUFFER_GPA_BASE={} {}",
vtl2_framebuffer_gpa_base, cmdline
)
} else {
cmdline.to_string()
};
let command_line = CString::new(cmdline).map_err(Error::InvalidCommandLine)?;
let (mask, max_vtl) = match vbs_platform_header(igvm_file)? {
IgvmPlatformHeader::SupportedPlatform(info) => {
debug_assert_eq!(info.platform_type, IgvmPlatformType::VSM_ISOLATION);
(info.compatibility_mask, info.highest_vtl)
}
};
let (relocation_regions, mut page_table_fixup) = igvm_file.relocations(mask);
let (relocation_region, relocation_offset) = if relocations_enabled {
match relocation_regions {
Some(regions) => {
if regions.len() != 1 {
return Err(Error::MultipleIgvmRelocationHeaders);
}
let region = regions[0].clone();
if !region.is_vtl2 {
return Err(Error::RelocationVtl);
}
if page_table_fixup.is_none() {
return Err(Error::NoPageTableRelocationHeader);
}
let page_table_fixup = page_table_fixup.as_ref().expect("is set");
let vtl2_base_address = match vtl2_base_address {
Vtl2BaseAddressType::Absolute(addr) => addr,
Vtl2BaseAddressType::MemoryLayout { .. } => {
let vtl2_range = mem_layout.vtl2_range().ok_or(Error::NoVtl2MemoryRange)?;
vtl2_range.start()
}
Vtl2BaseAddressType::File | Vtl2BaseAddressType::Vtl2Allocate { .. } => {
unreachable!()
}
};
if !region.relocation_base_valid(vtl2_base_address) {
return Err(Error::RelocationBaseInvalid {
file_relocation: region,
});
}
tracing::trace!(vtl2_base_address);
assert!(vtl2_base_address >= region.base_gpa);
let relocation_offset = Some(vtl2_base_address - region.base_gpa);
if region.vp_index != 0 || page_table_fixup.vp_index != 0 {
return Err(Error::RelocationVpIndex);
}
if region.vtl != igvm::hv_defs::Vtl::Vtl2
|| page_table_fixup.vtl != igvm::hv_defs::Vtl::Vtl2
{
return Err(Error::RelocationVtl);
}
tracing::trace!(relocation_offset);
(Some(region), relocation_offset)
}
None => {
return Err(Error::RelocationNotSupported);
}
}
} else {
(None, None)
};
let max_vtl = max_vtl
.try_into()
.expect("igvm file should be valid after new_from_binary");
let mut loader = Loader::new(gm.clone(), mem_layout, max_vtl);
#[derive(Debug)]
enum ParameterAreaState {
Allocated { data: Vec<u8>, max_size: u64 },
Inserted,
}
let mut parameter_areas: HashMap<u32, ParameterAreaState> = HashMap::new();
let import_parameter = |parameter_areas: &mut HashMap<u32, ParameterAreaState>,
info: &IGVM_VHS_PARAMETER,
parameter: &[u8]|
-> Result<(), Error> {
let (parameter_area, max_size) = match *parameter_areas
.get_mut(&info.parameter_area_index)
.expect("parameter area should be present")
{
ParameterAreaState::Allocated {
ref mut data,
max_size,
} => (data, max_size),
ParameterAreaState::Inserted => panic!("igvmfile is not valid"),
};
let offset = info.byte_offset as usize;
let end_of_parameter = offset + parameter.len();
if end_of_parameter > max_size as usize {
return Err(Error::ParameterTooLarge);
}
if parameter_area.len() < end_of_parameter {
parameter_area.resize(end_of_parameter, 0);
}
parameter_area[offset..end_of_parameter].copy_from_slice(parameter);
Ok(())
};
let relocate_gpa = |gpa: u64| -> u64 {
match (&relocation_offset, &relocation_region) {
(Some(offset), Some(region)) if region.contains(gpa) => gpa + offset,
_ => gpa,
}
};
let required_ram = igvm_file.directives().iter().filter_map(|header| {
if let IgvmDirectiveHeader::RequiredMemory {
gpa,
compatibility_mask: _,
number_of_bytes,
vtl2_protectable: _,
} = *header
{
let base = relocate_gpa(gpa);
Some(MemoryRange::new(base..base + number_of_bytes as u64))
} else {
None
}
});
let mut all_ram = mem_layout
.ram()
.iter()
.cloned()
.chain(
mem_layout
.vtl2_range()
.map(|r| MemoryRangeWithNode { range: r, vnode: 0 }),
)
.collect::<Vec<_>>();
all_ram.sort_by_key(|r| r.range.start());
if let Some(range) = subtract_ranges(required_ram, all_ram.iter().map(|r| r.range)).next() {
return Err(Error::MissingRequiredMemory(range));
}
let mut vtl2_protectable_ram = match vtl2_base_address {
Vtl2BaseAddressType::File
| Vtl2BaseAddressType::Absolute(_)
| Vtl2BaseAddressType::MemoryLayout { .. } => igvm_file
.directives()
.iter()
.filter_map(|header| {
if let IgvmDirectiveHeader::RequiredMemory {
gpa,
compatibility_mask: _,
number_of_bytes,
vtl2_protectable: true,
} = *header
{
let base = relocate_gpa(gpa);
Some(MemoryRange::new(base..base + number_of_bytes as u64))
} else {
None
}
})
.collect::<Vec<_>>(),
Vtl2BaseAddressType::Vtl2Allocate { .. } => Vec::new(),
};
if let Some(range) = mem_layout.vtl2_range() {
vtl2_protectable_ram.push(range);
}
vtl2_protectable_ram.sort_by_key(|r| r.start());
let mut page_table_cpu_state: Option<CpuPagingState> = None;
let pt_range = page_table_fixup.as_ref().map_or(MemoryRange::EMPTY, |x| {
MemoryRange::new(x.gpa..x.gpa + x.size)
});
let directives = igvm_file.directives().iter().filter(|&header| {
if !vtl2_only {
true
} else if let Some(reloc_region) = &relocation_region {
match *header {
IgvmDirectiveHeader::PageData { gpa, .. } => {
reloc_region.contains(gpa) || pt_range.contains_addr(gpa)
}
IgvmDirectiveHeader::X64VbsVpContext { vtl, .. } => vtl == igvm::hv_defs::Vtl::Vtl2,
IgvmDirectiveHeader::AArch64VbsVpContext { vtl, .. } => {
vtl == igvm::hv_defs::Vtl::Vtl2
}
IgvmDirectiveHeader::ParameterInsert(IGVM_VHS_PARAMETER_INSERT {
gpa,
compatibility_mask: _,
parameter_area_index: _,
}) => reloc_region.contains(gpa),
IgvmDirectiveHeader::ParameterArea { .. }
| IgvmDirectiveHeader::VpCount { .. }
| IgvmDirectiveHeader::Srat { .. }
| IgvmDirectiveHeader::Madt { .. }
| IgvmDirectiveHeader::Slit { .. }
| IgvmDirectiveHeader::Pptt { .. }
| IgvmDirectiveHeader::MmioRanges { .. }
| IgvmDirectiveHeader::MemoryMap { .. }
| IgvmDirectiveHeader::CommandLine { .. }
| IgvmDirectiveHeader::RequiredMemory { .. }
| IgvmDirectiveHeader::SnpVpContext { .. }
| IgvmDirectiveHeader::ErrorRange { .. }
| IgvmDirectiveHeader::SnpIdBlock { .. }
| IgvmDirectiveHeader::VbsMeasurement { .. }
| IgvmDirectiveHeader::DeviceTree { .. }
| IgvmDirectiveHeader::EnvironmentInfo { .. } => true,
IgvmDirectiveHeader::X64NativeVpContext { .. } => {
todo!("native igvm type not supported yet")
}
}
} else {
panic!("no relocation region, cannot filter to VTL2");
}
});
let mut page_data = PageDataBuffer::new();
for header in directives {
debug_assert!(header.compatibility_mask().unwrap_or(mask) & mask == mask);
match *header {
IgvmDirectiveHeader::PageData {
gpa,
compatibility_mask: _,
flags,
data_type,
ref data,
} => {
debug_assert!(data.len() as u64 % HV_PAGE_SIZE == 0);
assert!(data.len() as u64 == HV_PAGE_SIZE || data.is_empty());
if relocations_enabled && page_table_fixup.as_ref().expect("is some").contains(gpa)
{
page_table_fixup
.as_mut()
.expect("must have page table reloc")
.set_page_data(gpa, data)
.expect("gpa and len should be valid");
continue;
}
let acceptance = match data_type {
IgvmPageDataType::NORMAL => {
if flags.unmeasured() {
BootPageAcceptance::ExclusiveUnmeasured
} else if flags.shared() {
BootPageAcceptance::Shared
} else {
BootPageAcceptance::Exclusive
}
}
_ => todo!("unsupported IgvmPageDataType"),
};
if data.is_empty() {
page_data.zero(&mut loader, relocate_gpa(gpa), acceptance, HV_PAGE_SIZE)?;
} else {
page_data.append(&mut loader, relocate_gpa(gpa), acceptance, data)?;
}
}
IgvmDirectiveHeader::ParameterArea {
number_of_bytes,
parameter_area_index,
ref initial_data,
} => {
debug_assert!(number_of_bytes % HV_PAGE_SIZE == 0);
debug_assert!(
initial_data.is_empty() || initial_data.len() as u64 == number_of_bytes
);
if parameter_areas
.insert(
parameter_area_index,
ParameterAreaState::Allocated {
data: initial_data.clone(),
max_size: number_of_bytes,
},
)
.is_some()
{
panic!("IgvmFile is not valid, invalid invariant");
}
}
IgvmDirectiveHeader::VpCount(ref info) => {
let proc_count: u32 = processor_topology.vp_count();
import_parameter(&mut parameter_areas, info, proc_count.as_bytes())?;
}
IgvmDirectiveHeader::Srat(ref info) => {
import_parameter(&mut parameter_areas, info, acpi_tables.srat)?;
}
IgvmDirectiveHeader::Madt(ref info) => {
import_parameter(&mut parameter_areas, info, acpi_tables.madt)?;
}
IgvmDirectiveHeader::Slit(ref info) => {
if let Some(slit) = acpi_tables.slit {
import_parameter(&mut parameter_areas, info, slit)?;
} else {
tracing::warn!("igvm file requested a SLIT, but no SLIT was provided")
}
}
IgvmDirectiveHeader::Pptt(ref info) => {
if let Some(pptt) = acpi_tables.pptt {
import_parameter(&mut parameter_areas, info, pptt)?;
} else {
tracing::warn!("igvm file requested a PPTT, but no PPTT was provided")
}
}
IgvmDirectiveHeader::MmioRanges(ref info) => {
let mmio = mem_layout.mmio();
if mmio.len() < 2 {
return Err(Error::UnsupportedMmio);
}
let mmio_ranges = IGVM_VHS_MMIO_RANGES {
mmio_ranges: [from_memory_range(&mmio[0]), from_memory_range(&mmio[1])],
};
import_parameter(&mut parameter_areas, info, mmio_ranges.as_bytes())?;
}
IgvmDirectiveHeader::MemoryMap(ref info) => {
let (memory_map, _) = build_memory_map(&all_ram, &vtl2_protectable_ram);
import_parameter(&mut parameter_areas, info, memory_map.as_bytes())?;
}
IgvmDirectiveHeader::CommandLine(ref info) => {
import_parameter(&mut parameter_areas, info, command_line.as_bytes_with_nul())?;
}
IgvmDirectiveHeader::DeviceTree(ref info) => {
let dt = build_device_tree(
processor_topology,
mem_layout,
&all_ram,
&vtl2_protectable_ram,
vtl2_base_address,
&String::from_utf8_lossy(command_line.as_bytes()),
with_vmbus_redirect,
com_serial,
entropy,
)
.map_err(Error::DeviceTree)?;
import_parameter(&mut parameter_areas, info, &dt)?;
}
IgvmDirectiveHeader::RequiredMemory {
gpa,
compatibility_mask: _,
number_of_bytes,
vtl2_protectable,
} => {
let memory_type = if vtl2_protectable {
StartupMemoryType::Vtl2ProtectableRam
} else {
StartupMemoryType::Ram
};
let gpa = relocate_gpa(gpa);
loader
.verify_startup_memory_available(
gpa / HV_PAGE_SIZE,
number_of_bytes as u64 / HV_PAGE_SIZE,
memory_type,
)
.map_err(Error::Loader)?;
}
IgvmDirectiveHeader::EnvironmentInfo(ref info) => {
let environment_info =
igvm_defs::IgvmEnvironmentInfo::new().with_memory_is_shared(false);
import_parameter(&mut parameter_areas, info, environment_info.as_bytes())?;
}
IgvmDirectiveHeader::SnpVpContext { .. } => todo!("snp not supported"),
IgvmDirectiveHeader::SnpIdBlock { .. } => todo!("snp not supported"),
IgvmDirectiveHeader::VbsMeasurement { .. } => todo!("vbs not supported"),
IgvmDirectiveHeader::X64VbsVpContext {
vtl,
ref registers,
compatibility_mask: _,
} => {
if from_igvm_vtl(vtl) != max_vtl {
return Err(Error::LowerVtlContext);
}
let mut cr3: Option<u64> = None;
let mut cr4: Option<u64> = None;
for reg in registers.iter().map(|igvm_reg| {
let reg: X86Register = (*igvm_reg).into();
reg
}) {
let reloc_reg = match reg {
X86Register::Gdtr(value) => match relocation_region {
Some(ref region) if region.apply_gdtr_offset => {
X86Register::Gdtr(TableRegister {
base: relocate_gpa(value.base),
..value
})
}
_ => reg,
},
X86Register::Tr(_reg) => {
tracing::warn!("TR register load being skipped");
continue;
}
X86Register::Cr3(reg) => {
if let Some(offset) = relocation_offset {
cr3 = Some(reg);
let page_table_fixup =
page_table_fixup.as_ref().expect("should be some");
assert!(page_table_fixup.contains(reg));
let reloc_cr3 = reg + offset;
X86Register::Cr3(reloc_cr3)
} else {
X86Register::Cr3(reg)
}
}
X86Register::Cr4(val) => {
if relocations_enabled {
cr4 = Some(val);
}
reg
}
X86Register::Rip(rip) => match relocation_region {
Some(ref region) if region.apply_rip_offset => {
X86Register::Rip(relocate_gpa(rip))
}
_ => reg,
},
X86Register::Ds(_)
| X86Register::Es(_)
| X86Register::Fs(_)
| X86Register::Gs(_)
| X86Register::Ss(_)
| X86Register::Cs(_)
| X86Register::Cr0(_)
| X86Register::Efer(_)
| X86Register::Pat(_)
| X86Register::Rbp(_)
| X86Register::Rsi(_)
| X86Register::Rsp(_)
| X86Register::R8(_)
| X86Register::R9(_)
| X86Register::R10(_)
| X86Register::R11(_)
| X86Register::R12(_)
| X86Register::Rflags(_)
| X86Register::Idtr(_)
| X86Register::MtrrDefType(_)
| X86Register::MtrrFix64k00000(_)
| X86Register::MtrrFix16k80000(_)
| X86Register::MtrrPhysBase0(_)
| X86Register::MtrrPhysMask0(_)
| X86Register::MtrrPhysBase1(_)
| X86Register::MtrrPhysMask1(_)
| X86Register::MtrrPhysBase2(_)
| X86Register::MtrrPhysMask2(_)
| X86Register::MtrrPhysBase3(_)
| X86Register::MtrrPhysMask3(_)
| X86Register::MtrrPhysBase4(_)
| X86Register::MtrrPhysMask4(_)
| X86Register::MtrrFix4kE0000(_)
| X86Register::MtrrFix4kE8000(_)
| X86Register::MtrrFix4kF0000(_)
| X86Register::MtrrFix4kF8000(_) => reg,
};
loader
.import_vp_register(reloc_reg)
.map_err(Error::Loader)?;
}
if relocations_enabled {
match (cr3, cr4) {
(Some(cr3), Some(cr4)) => {
if vtl
== page_table_fixup
.as_ref()
.expect("relocations enabled must be set")
.vtl
{
page_table_cpu_state = Some(CpuPagingState { cr3, cr4 })
}
}
_ => panic!("invalid igvm file"),
}
}
}
IgvmDirectiveHeader::AArch64VbsVpContext { .. } => {
todo!("AArch64 VP context not supported")
}
IgvmDirectiveHeader::ParameterInsert(IGVM_VHS_PARAMETER_INSERT {
gpa,
compatibility_mask: _,
parameter_area_index,
}) => {
page_data.flush(&mut loader)?;
let gpa = relocate_gpa(gpa);
debug_assert!(gpa % HV_PAGE_SIZE == 0);
let area = parameter_areas
.get_mut(¶meter_area_index)
.expect("igvmfile should be valid");
match std::mem::replace(area, ParameterAreaState::Inserted) {
ParameterAreaState::Allocated { data, max_size } => loader
.import_pages(
gpa / HV_PAGE_SIZE,
max_size / HV_PAGE_SIZE,
"igvm-parameter",
BootPageAcceptance::ExclusiveUnmeasured,
&data,
)
.map_err(Error::Loader)?,
ParameterAreaState::Inserted => panic!("igvmfile is invalid, multiple insert"),
}
}
IgvmDirectiveHeader::ErrorRange { .. } => {
todo!("Error Range not supported")
}
IgvmDirectiveHeader::X64NativeVpContext { .. } => {
todo!("native vp context not supported")
}
}
}
page_data.flush(&mut loader)?;
if let Some(offset) = relocation_offset {
let page_table_cpu_state = page_table_cpu_state
.expect("igvm file should be valid and vp context should be present");
let page_table_fixup = page_table_fixup.take().expect("should be some");
let relocation_region = relocation_region.as_ref().expect("should be some");
let reloc_region_base_gpa = page_table_fixup.gpa + offset;
let mut reloc_regions = RangeMap::new();
reloc_regions.insert(
relocation_region.base_gpa..=relocation_region.base_gpa + relocation_region.size - 1,
offset as i64,
);
let page_table = page_table_fixup
.build(offset as i64, reloc_regions, page_table_cpu_state)
.map_err(Error::PageTableBuilder)?;
loader
.import_pages(
reloc_region_base_gpa / HV_PAGE_SIZE,
page_table.len() as u64 / HV_PAGE_SIZE,
"igvm-page-table",
BootPageAcceptance::Exclusive,
&page_table,
)
.map_err(Error::Loader)?;
}
Ok(loader.initial_regs_and_accepted_ranges())
}
fn build_memory_map(
all_ram: &[MemoryRangeWithNode],
vtl2_protectable_ram: &[MemoryRange],
) -> (Vec<IGVM_VHS_MEMORY_MAP_ENTRY>, Vec<u32>) {
let mut memory_map = Vec::new();
let mut vnodes = Vec::new();
for (range, r) in memory_range::walk_ranges(
all_ram.iter().map(|r| (r.range, r.vnode)),
memory_range::flatten_ranges(vtl2_protectable_ram.iter().copied()).map(|r| (r, ())),
) {
match r {
memory_range::RangeWalkResult::Neither => {}
memory_range::RangeWalkResult::Left(vnode) => {
memory_map.push(memory_map_entry(&range));
vnodes.push(vnode);
}
memory_range::RangeWalkResult::Right(()) => {
unreachable!("vtl2 protectable range not in all RAM")
}
memory_range::RangeWalkResult::Both(vnode, ()) => {
memory_map.push(IGVM_VHS_MEMORY_MAP_ENTRY {
starting_gpa_page_number: range.start_4k_gpn(),
number_of_pages: range.page_count_4k(),
entry_type: igvm_defs::MemoryMapEntryType::VTL2_PROTECTABLE,
flags: 0,
reserved: 0,
});
vnodes.push(vnode);
}
}
}
assert_eq!(memory_map.len(), vnodes.len());
(memory_map, vnodes)
}
#[cfg_attr(not(guest_arch = "aarch64"), expect(dead_code))]
fn load_igvm_aarch64(
_params: LoadIgvmParams<'_, Aarch64Topology>,
) -> Result<(Vec<Aarch64Register>, Vec<(MemoryRange, PageVisibility)>), Error> {
Err(Error::UnsupportedGuestArch)
}
struct PageDataBuffer {
gpa: u64,
acceptance: BootPageAcceptance,
len: u64,
data: Vec<u8>,
}
impl PageDataBuffer {
fn new() -> Self {
Self {
gpa: 0,
acceptance: BootPageAcceptance::Exclusive,
len: 0,
data: Vec::new(),
}
}
fn append<R: GuestArch>(
&mut self,
loader: &mut dyn ImageLoad<R>,
gpa: u64,
acceptance: BootPageAcceptance,
data: &[u8],
) -> Result<(), Error> {
assert_eq!(data.len() as u64, HV_PAGE_SIZE);
if self.len == 0
|| (self.data.len() as u64) < self.len
|| self.gpa + self.len != gpa
|| self.acceptance != acceptance
{
self.flush(loader)?;
self.gpa = gpa;
self.acceptance = acceptance;
}
self.data.extend_from_slice(data);
self.len += data.len() as u64;
Ok(())
}
fn zero<R: GuestArch>(
&mut self,
loader: &mut dyn ImageLoad<R>,
gpa: u64,
acceptance: BootPageAcceptance,
len: u64,
) -> Result<(), Error> {
assert_eq!(len, HV_PAGE_SIZE);
if self.len == 0 || self.gpa + self.len != gpa || self.acceptance != acceptance {
self.flush(loader)?;
self.gpa = gpa;
self.acceptance = acceptance;
}
self.len += len;
Ok(())
}
fn flush<R: GuestArch>(&mut self, loader: &mut dyn ImageLoad<R>) -> Result<(), Error> {
if self.len == 0 {
assert!(self.data.is_empty());
return Ok(());
}
loader
.import_pages(
self.gpa / HV_PAGE_SIZE,
self.len / HV_PAGE_SIZE,
"igvm-data",
self.acceptance,
&self.data,
)
.map_err(Error::Loader)?;
self.data.clear();
self.len = 0;
Ok(())
}
}