Skip to content

Commit

Permalink
igvm: use MADT for determining APIC IDs
Browse files Browse the repository at this point in the history
The IGVM format specifies a mechanism for the host to inject an MADT
into the guest address space.  This MADT should be used to determine the
set of APIC IDs associated with the set of CPUs that will be started by
the guest.

Signed-off-by: Jon Lange <jlange@microsoft.com>
  • Loading branch information
msft-jlange committed Feb 24, 2025
1 parent bef01a0 commit 55af869
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 28 deletions.
7 changes: 7 additions & 0 deletions bootlib/src/igvm_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ pub struct IgvmParamBlock {
/// of the parameter page.
pub param_page_offset: u32,

/// The offset, in bytes, from the base of the parameter block to the base
/// of the host-supplied MADT.
pub madt_offset: u32,

/// The size, in bytes, of the MADT area.
pub madt_size: u32,

/// The offset, in bytes, from the base of the parameter block to the base
/// of the memory map (which is in IGVM format).
pub memory_map_offset: u32,
Expand Down
4 changes: 2 additions & 2 deletions fuzz/fuzz_targets/acpi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use core::num::NonZeroUsize;
use core::sync::atomic::{AtomicUsize, Ordering};
use libfuzzer_sys::{fuzz_target, Corpus};
use std::hint::black_box;
use svsm::acpi::tables::load_acpi_cpu_info;
use svsm::acpi::tables::load_fw_cpu_info;
use svsm::fw_cfg::FwCfg;
use svsm::io::IOPort;

Expand Down Expand Up @@ -57,7 +57,7 @@ fuzz_target!(|data: &[u8]| -> Corpus {
};
let fwcfg = FwCfg::new(&io);

if let Ok(info) = load_acpi_cpu_info(&fwcfg) {
if let Ok(info) = load_fw_cpu_info(&fwcfg) {
let _ = black_box(info);
}

Expand Down
5 changes: 4 additions & 1 deletion igvmbuilder/src/gpa_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub struct GpaMap {
pub igvm_param_block: GpaRange,
pub general_params: GpaRange,
pub memory_map: GpaRange,
pub madt: GpaRange,
pub guest_context: GpaRange,
// The kernel region represents the maximum allowable size. The hypervisor may request that it
// be smaller to save memory on smaller machine shapes. However, the entire region should not
Expand Down Expand Up @@ -139,7 +140,8 @@ impl GpaMap {

let igvm_param_block = GpaRange::new_page(kernel_fs.get_end())?;
let general_params = GpaRange::new_page(igvm_param_block.get_end())?;
let memory_map = GpaRange::new_page(general_params.get_end())?;
let madt = GpaRange::new_page(general_params.get_end())?;
let memory_map = GpaRange::new_page(madt.get_end())?;
let guest_context = if let Some(firmware) = firmware {
if firmware.get_guest_context().is_some() {
// Locate the guest context after the memory map parameter page
Expand Down Expand Up @@ -171,6 +173,7 @@ impl GpaMap {
igvm_param_block,
general_params,
memory_map,
madt,
guest_context,
kernel,
vmsa,
Expand Down
27 changes: 24 additions & 3 deletions igvmbuilder/src/igvm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ pub const ANY_NATIVE_COMPATIBILITY_MASK: u32 = NATIVE_COMPATIBILITY_MASK | VSM_C
// Parameter area indices
const IGVM_GENERAL_PARAMS_PA: u32 = 0;
const IGVM_MEMORY_MAP_PA: u32 = 1;
const IGVM_PARAMETER_COUNT: u32 = 2;
const IGVM_MADT_PA: u32 = 2;
const IGVM_PARAMETER_COUNT: u32 = 3;

const _: () = assert!(size_of::<IgvmParamBlock>() as u64 <= PAGE_SIZE_4K);
const _: () = assert!(size_of::<IgvmGuestContext>() as u64 <= PAGE_SIZE_4K);
Expand Down Expand Up @@ -192,7 +193,8 @@ impl IgvmBuilder {

fn create_param_block(&self) -> Result<IgvmParamBlock, Box<dyn Error>> {
let param_page_offset = PAGE_SIZE_4K as u32;
let memory_map_offset = param_page_offset + PAGE_SIZE_4K as u32;
let madt_offset = param_page_offset + PAGE_SIZE_4K as u32;
let memory_map_offset = madt_offset + PAGE_SIZE_4K as u32;
let kernel_min_size = 0x1000000; // 16 MiB
let (guest_context_offset, param_area_size) = if self.gpa_map.guest_context.get_size() == 0
{
Expand Down Expand Up @@ -233,6 +235,8 @@ impl IgvmBuilder {
param_area_size,
param_page_offset,
memory_map_offset,
madt_offset,
madt_size: PAGE_SIZE_4K as u32,
guest_context_offset,
debug_serial_port: self.options.get_port_address(),
firmware: fw_info,
Expand Down Expand Up @@ -345,12 +349,17 @@ impl IgvmBuilder {
});
}

// Create the two parameter areas for memory map and general parameters.
// Create the parameter areas for all host-supplied parameters.
self.directives.push(IgvmDirectiveHeader::ParameterArea {
number_of_bytes: PAGE_SIZE_4K,
parameter_area_index: IGVM_MEMORY_MAP_PA,
initial_data: vec![],
});
self.directives.push(IgvmDirectiveHeader::ParameterArea {
number_of_bytes: PAGE_SIZE_4K,
parameter_area_index: IGVM_MADT_PA,
initial_data: vec![],
});
self.directives.push(IgvmDirectiveHeader::ParameterArea {
number_of_bytes: PAGE_SIZE_4K,
parameter_area_index: IGVM_GENERAL_PARAMS_PA,
Expand All @@ -366,6 +375,11 @@ impl IgvmBuilder {
parameter_area_index: IGVM_GENERAL_PARAMS_PA,
byte_offset: 4,
}));
self.directives
.push(IgvmDirectiveHeader::Madt(IGVM_VHS_PARAMETER {
parameter_area_index: IGVM_MADT_PA,
byte_offset: 0,
}));
self.directives
.push(IgvmDirectiveHeader::MemoryMap(IGVM_VHS_PARAMETER {
parameter_area_index: IGVM_MEMORY_MAP_PA,
Expand All @@ -378,6 +392,13 @@ impl IgvmBuilder {
parameter_area_index: IGVM_MEMORY_MAP_PA,
},
));
self.directives.push(IgvmDirectiveHeader::ParameterInsert(
IGVM_VHS_PARAMETER_INSERT {
gpa: self.gpa_map.madt.get_start(),
compatibility_mask: COMPATIBILITY_MASK.get(),
parameter_area_index: IGVM_MADT_PA,
},
));
self.directives.push(IgvmDirectiveHeader::ParameterInsert(
IGVM_VHS_PARAMETER_INSERT {
gpa: self.gpa_map.general_params.get_start(),
Expand Down
35 changes: 26 additions & 9 deletions kernel/src/acpi/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ impl ACPITableHeader {

#[derive(Debug)]
/// ACPI table, both header and contents
struct ACPITable {
pub struct ACPITable {
header: ACPITableHeader,
/// Raw binary content of ACPI table
buf: Vec<u8>,
Expand All @@ -164,7 +164,7 @@ impl ACPITable {
/// # Returns
///
/// A new [`ACPITable`] instance on success, or an [`SvsmError`] if parsing fails.
fn new(ptr: &[u8]) -> Result<Self, SvsmError> {
pub fn new(ptr: &[u8]) -> Result<Self, SvsmError> {
let (raw_header, _) =
RawACPITableHeader::read_from_prefix(ptr).map_err(|_| SvsmError::Acpi)?;
let size = raw_header.len as usize;
Expand Down Expand Up @@ -412,9 +412,7 @@ pub struct ACPICPUInfo {
pub enabled: bool,
}

/// Loads ACPI CPU information by parsing the ACPI tables.
///
/// This function retrieves CPU information from the ACPI tables provided by the firmware.
/// Loads ACPI CPU information by parsing the ACPI tables provided by the firmware
/// It processes the Multiple APIC Description Table (MADT) to extract information about each CPU's
/// APIC ID and enabled status.
///
Expand All @@ -435,7 +433,7 @@ pub struct ACPICPUInfo {
/// # Example
///
/// ```
/// use svsm::acpi::tables::load_acpi_cpu_info;
/// use svsm::acpi::tables::load_fw_cpu_info;
/// use svsm::fw_cfg::FwCfg;
/// use svsm::io::IOPort;
///
Expand All @@ -452,7 +450,7 @@ pub struct ACPICPUInfo {
///
/// let io = MyIo;
/// let fw_cfg = FwCfg::new(&io);
/// match load_acpi_cpu_info(&fw_cfg) {
/// match load_fw_cpu_info(&fw_cfg) {
/// Ok(cpu_info) => {
/// for info in cpu_info {
/// // You can print id (info.apic_id) and whether it is enabled (info.enabled)
Expand All @@ -463,12 +461,31 @@ pub struct ACPICPUInfo {
/// }
/// }
/// ```
pub fn load_acpi_cpu_info(fw_cfg: &FwCfg<'_>) -> Result<Vec<ACPICPUInfo>, SvsmError> {
pub fn load_fw_cpu_info(fw_cfg: &FwCfg<'_>) -> Result<Vec<ACPICPUInfo>, SvsmError> {
let buffer = ACPITableBuffer::from_fwcfg(fw_cfg)?;

let apic_table = buffer.acp_table_by_sig("APIC").ok_or(SvsmError::Acpi)?;
let content = apic_table.content().ok_or(SvsmError::Acpi)?;
load_acpi_cpu_info(&apic_table)
}

/// Loads ACPI CPU information by parsing the ACPI tables.
/// It processes the Multiple APIC Description Table (MADT) to extract information about each CPU's
/// APIC ID and enabled status.
///
/// # Arguments
///
/// * 'apic_table': A reference to the MADT that was located from the ACPI tables.
///
/// # Returns
///
/// A [`Result`] containing a vector of [`ACPICPUInfo`] structs representing CPU information.
/// If successful, the vector contains information about each detected CPU; otherwise, an error is returned.
///
/// # Errors
///
/// This function returns an error if there are issues with reading or parsing ACPI tables.
pub fn load_acpi_cpu_info(apic_table: &ACPITable) -> Result<Vec<ACPICPUInfo>, SvsmError> {
let content = apic_table.content().ok_or(SvsmError::Acpi)?;
let mut cpus: Vec<ACPICPUInfo> = Vec::new();

let mut offset = MADT_HEADER_SIZE;
Expand Down
4 changes: 2 additions & 2 deletions kernel/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ extern crate alloc;

use core::slice;

use crate::acpi::tables::{load_acpi_cpu_info, ACPICPUInfo};
use crate::acpi::tables::{load_fw_cpu_info, ACPICPUInfo};
use crate::address::PhysAddr;
use crate::error::SvsmError;
use crate::fw_cfg::FwCfg;
Expand Down Expand Up @@ -97,7 +97,7 @@ impl SvsmConfig<'_> {
}
pub fn load_cpu_info(&self) -> Result<Vec<ACPICPUInfo>, SvsmError> {
match self {
SvsmConfig::FirmwareConfig(fw_cfg) => load_acpi_cpu_info(fw_cfg),
SvsmConfig::FirmwareConfig(fw_cfg) => load_fw_cpu_info(fw_cfg),
SvsmConfig::IgvmConfig(igvm_params) => igvm_params.load_cpu_info(),
}
}
Expand Down
23 changes: 12 additions & 11 deletions kernel/src/igvm_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

extern crate alloc;

use crate::acpi::tables::ACPICPUInfo;
use crate::acpi::tables::{load_acpi_cpu_info, ACPICPUInfo, ACPITable};
use crate::address::{Address, PhysAddr, VirtAddr};
use crate::cpu::efer::EFERFlags;
use crate::error::SvsmError;
Expand All @@ -20,6 +20,7 @@ use cpuarch::vmsa::VMSA;
use bootlib::igvm_params::{IgvmGuestContext, IgvmParamBlock, IgvmParamPage};
use bootlib::kernel_launch::LOWMEM_END;
use core::mem::size_of;
use core::slice;
use igvm_defs::{IgvmEnvironmentInfo, MemoryMapEntryType, IGVM_VHS_MEMORY_MAP_ENTRY};

const IGVM_MEMORY_ENTRIES_PER_PAGE: usize = PAGE_SIZE / size_of::<IGVM_VHS_MEMORY_MAP_ENTRY>();
Expand All @@ -35,6 +36,7 @@ pub struct IgvmParams<'a> {
igvm_param_block: &'a IgvmParamBlock,
igvm_param_page: &'a IgvmParamPage,
igvm_memory_map: &'a IgvmMemoryMap,
igvm_madt: &'a [u8],
igvm_guest_context: Option<&'a IgvmGuestContext>,
}

Expand All @@ -45,6 +47,12 @@ impl IgvmParams<'_> {
let param_page = Self::try_aligned_ref::<IgvmParamPage>(param_page_address)?;
let memory_map_address = addr + param_block.memory_map_offset as usize;
let memory_map = Self::try_aligned_ref::<IgvmMemoryMap>(memory_map_address)?;
let madt_address = addr + param_block.madt_offset as usize;
// SAFETY: the parameter block correctly describes the bounds of the
// MADT.
let madt = unsafe {
slice::from_raw_parts(madt_address.as_ptr::<u8>(), param_block.madt_size as usize)
};
let guest_context = if param_block.guest_context_offset != 0 {
let offset = usize::try_from(param_block.guest_context_offset).unwrap();
Some(Self::try_aligned_ref::<IgvmGuestContext>(addr + offset)?)
Expand All @@ -56,6 +64,7 @@ impl IgvmParams<'_> {
igvm_param_block: param_block,
igvm_param_page: param_page,
igvm_memory_map: memory_map,
igvm_madt: madt,
igvm_guest_context: guest_context,
})
}
Expand Down Expand Up @@ -248,16 +257,8 @@ impl IgvmParams<'_> {
}

pub fn load_cpu_info(&self) -> Result<Vec<ACPICPUInfo>, SvsmError> {
let mut cpus: Vec<ACPICPUInfo> = Vec::new();
log::info!("CPU count is {}", { self.igvm_param_page.cpu_count });
for i in 0..self.igvm_param_page.cpu_count {
let cpu = ACPICPUInfo {
apic_id: i,
enabled: true,
};
cpus.push(cpu);
}
Ok(cpus)
let madt = ACPITable::new(self.igvm_madt)?;
load_acpi_cpu_info(&madt)
}

pub fn should_launch_fw(&self) -> bool {
Expand Down

0 comments on commit 55af869

Please sign in to comment.