Skip to content

Commit

Permalink
ghcb: Use APIC ID List NAE event to get APIC IDs
Browse files Browse the repository at this point in the history
The GHCB protocol includes an NAE to get the list of APIC IDs for the
guest, so use it on SEV-SNP. Previously, the list of APIC IDs was either
gotten via ACPI table or using IGVM to get the number of vCPUs and
assuming the APIC IDs were sequential. This is still done if the
platform is not SNP since other platforms do not provide a way to get
this list.

Signed-off-by: Adam Dunlap <acdunlap@google.com>
  • Loading branch information
AdamCDunlap committed Feb 22, 2025
1 parent 035bb52 commit 7746744
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 42 deletions.
24 changes: 11 additions & 13 deletions kernel/src/acpi/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,22 +454,22 @@ pub struct ACPICPUInfo {
/// let fw_cfg = FwCfg::new(&io);
/// match load_acpi_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)
/// for apic_id in cpu_info {
/// // You can print apic id
/// }
/// }
/// Err(err) => {
/// // Print error
/// }
/// }
/// ```
pub fn load_acpi_cpu_info(fw_cfg: &FwCfg<'_>) -> Result<Vec<ACPICPUInfo>, SvsmError> {
pub fn load_acpi_cpu_info(fw_cfg: &FwCfg<'_>) -> Result<Vec<u32>, 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)?;

let mut cpus: Vec<ACPICPUInfo> = Vec::new();
let mut ids: Vec<u32> = Vec::new();

let mut offset = MADT_HEADER_SIZE;
while offset < content.len() {
Expand All @@ -483,19 +483,17 @@ pub fn load_acpi_cpu_info(fw_cfg: &FwCfg<'_>) -> Result<Vec<ACPICPUInfo>, SvsmEr
let lapic_ptr = apic_table
.content_ptr::<RawMADTEntryLocalApic>(offset)
.ok_or(SvsmError::Acpi)?;
cpus.push(ACPICPUInfo {
apic_id: lapic_ptr.apic_id as u32,
enabled: (lapic_ptr.flags & 1) == 1,
});
if (lapic_ptr.flags & 1) == 1 {
ids.push(lapic_ptr.apic_id as u32);
}
}
9 if entry_len == mem::size_of::<RawMADTEntryLocalX2Apic>() => {
let x2apic_ptr = apic_table
.content_ptr::<RawMADTEntryLocalX2Apic>(offset)
.ok_or(SvsmError::Acpi)?;
cpus.push(ACPICPUInfo {
apic_id: x2apic_ptr.apic_id,
enabled: (x2apic_ptr.flags & 1) == 1,
});
if (x2apic_ptr.flags & 1) == 1 {
ids.push(x2apic_ptr.apic_id);
}
}
madt_type if entry_len == 0 => {
log::warn!(
Expand All @@ -512,5 +510,5 @@ pub fn load_acpi_cpu_info(fw_cfg: &FwCfg<'_>) -> Result<Vec<ACPICPUInfo>, SvsmEr
offset = offset.checked_add(entry_len).ok_or(SvsmError::Acpi)?;
}

Ok(cpus)
Ok(ids)
}
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_acpi_cpu_info;
use crate::address::PhysAddr;
use crate::error::SvsmError;
use crate::fw_cfg::FwCfg;
Expand Down Expand Up @@ -95,7 +95,7 @@ impl SvsmConfig<'_> {
SvsmConfig::IgvmConfig(igvm_params) => igvm_params.reserved_kernel_area_size(),
}
}
pub fn load_cpu_info(&self) -> Result<Vec<ACPICPUInfo>, SvsmError> {
pub fn load_cpu_info(&self) -> Result<Vec<u32>, SvsmError> {
match self {
SvsmConfig::FirmwareConfig(fw_cfg) => load_acpi_cpu_info(fw_cfg),
SvsmConfig::IgvmConfig(igvm_params) => igvm_params.load_cpu_info(),
Expand Down
9 changes: 4 additions & 5 deletions kernel/src/cpu/smp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
//
// Author: Joerg Roedel <jroedel@suse.de>

use crate::acpi::tables::ACPICPUInfo;
use crate::address::{Address, VirtAddr};
use crate::cpu::idt::idt;
use crate::cpu::percpu::{cpu_idle_loop, this_cpu, this_cpu_shared, PerCpu};
Expand Down Expand Up @@ -37,18 +36,18 @@ fn start_cpu(platform: &dyn SvsmPlatform, apic_id: u32) -> Result<(), SvsmError>
Ok(())
}

pub fn start_secondary_cpus(platform: &dyn SvsmPlatform, cpus: &[ACPICPUInfo]) {
pub fn start_secondary_cpus(platform: &dyn SvsmPlatform, apic_ids: &[u32]) {
let mut count: usize = 0;
for c in cpus.iter().filter(|c| c.apic_id != 0 && c.enabled) {
log::info!("Launching AP with APIC-ID {}", c.apic_id);
for id in apic_ids.iter().cloned().filter(|&id| id != 0) {
log::info!("Launching AP with APIC-ID {}", id);

// If this is the first AP being started, then advise the TLB package
// that future TLB flushes will have to be done with SMP scope.
if count == 0 {
set_tlb_flush_smp();
}

start_cpu(platform, c.apic_id).expect("Failed to bring CPU online");
start_cpu(platform, id).expect("Failed to bring CPU online");
count += 1;
}
log::info!("Brought {} AP(s) online", count);
Expand Down
15 changes: 4 additions & 11 deletions kernel/src/igvm_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

extern crate alloc;

use crate::acpi::tables::ACPICPUInfo;
use crate::address::{Address, PhysAddr, VirtAddr};
use crate::cpu::efer::EFERFlags;
use crate::error::SvsmError;
Expand Down Expand Up @@ -247,17 +246,11 @@ impl IgvmParams<'_> {
Ok(())
}

pub fn load_cpu_info(&self) -> Result<Vec<ACPICPUInfo>, SvsmError> {
let mut cpus: Vec<ACPICPUInfo> = Vec::new();
pub fn load_cpu_info(&self) -> Result<Vec<u32>, SvsmError> {
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)
// Assume the APIC IDs are sequential. This may not be true, so other means
// should be used to determine APIC IDs if possible.
Ok((0..self.igvm_param_page.cpu_count).collect())
}

pub fn should_launch_fw(&self) -> bool {
Expand Down
9 changes: 9 additions & 0 deletions kernel/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
//
// Author: Jon Lange <jlange@microsoft.com>

extern crate alloc;

pub mod guest_cpu;
pub mod native;
pub mod snp;
Expand All @@ -12,6 +14,8 @@ pub mod tdp;
mod snp_fw;
pub use snp_fw::{parse_fw_meta_data, SevFWMetaData};

use alloc::vec::Vec;

use native::NativePlatform;
use snp::SnpPlatform;
use tdp::TdpPlatform;
Expand Down Expand Up @@ -183,6 +187,11 @@ pub trait SvsmPlatform {
fn start_svsm_request_loop(&self) -> bool {
false
}

/// Gets a list of all the APIC IDs available. Returns NotSupported if the platform does not support this operation.
fn get_apic_ids(&self) -> Result<Vec<u32>, SvsmError> {
Err(SvsmError::NotSupported)
}
}

//FIXME - remove Copy trait
Expand Down
8 changes: 8 additions & 0 deletions kernel/src/platform/snp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
//
// Author: Jon Lange <jlange@microsoft.com>

extern crate alloc;

use alloc::vec::Vec;

use super::snp_fw::{
copy_tables_to_fw, launch_fw, prepare_fw_launch, print_fw_meta, validate_fw, validate_fw_memory,
};
Expand Down Expand Up @@ -334,6 +338,10 @@ impl SvsmPlatform for SnpPlatform {
fn start_svsm_request_loop(&self) -> bool {
true
}

fn get_apic_ids(&self) -> Result<Vec<u32>, SvsmError> {
current_ghcb().get_apic_ids()
}
}

#[derive(Clone, Copy, Debug, Default)]
Expand Down
48 changes: 46 additions & 2 deletions kernel/src/sev/ghcb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@
//
// Author: Joerg Roedel <jroedel@suse.de>

extern crate alloc;

use alloc::vec::Vec;

use crate::address::{Address, PhysAddr, VirtAddr};
use crate::cpu::msr::{write_msr, SEV_GHCB};
use crate::cpu::percpu::this_cpu;
use crate::cpu::{flush_tlb_global_sync, IrqGuard, X86GeneralRegs};
use crate::error::SvsmError;
use crate::mm::page_visibility::SharedBox;
use crate::mm::validate::{
valid_bitmap_clear_valid_4k, valid_bitmap_set_valid_4k, valid_bitmap_valid_addr,
};
use crate::mm::virt_to_phys;
use crate::mm::{virt_to_phys, PAGE_SIZE};
use crate::platform::PageStateChangeOp;
use crate::sev::hv_doorbell::HVDoorbell;
use crate::sev::utils::raw_vmgexit;
Expand All @@ -29,7 +34,7 @@ use core::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, AtomicU8, Ordering};
use super::msr_protocol::{invalidate_page_msr, register_ghcb_gpa_msr, validate_page_msr};
use super::{pvalidate, PvalidateOp};

use zerocopy::{FromZeros, Immutable, IntoBytes};
use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes};

#[repr(C, packed)]
#[derive(Debug, Default, Clone, Copy, IntoBytes, Immutable)]
Expand Down Expand Up @@ -103,6 +108,7 @@ enum GHCBExitCode {
AP_CREATE = 0x80000013,
HV_DOORBELL = 0x8000_0014,
HV_IPI = 0x8000_0015,
GET_APIC_IDS = 0x8000_0017,
CONFIGURE_INT_INJ = 0x8000_0019,
DISABLE_ALT_INJ = 0x8000_001A,
SPECIFIC_EOI = 0x8000_001B,
Expand Down Expand Up @@ -535,6 +541,44 @@ impl GHCB {
Ok(())
}

pub fn get_apic_ids(&self) -> Result<Vec<u32>, SvsmError> {
self.clear();

// RAX holds the number of pages given to the hypervisor.
// exit_info_1 stores the GPA of the buffer
// If RAX was not big enough, RAX is changed to hold the required size.

#[repr(C)]
#[derive(FromBytes, Copy, Clone)]
struct SevApicIdDesc {
num_entries: u32,
apic_ids: [u32; 1023],
}

let shared_page: SharedBox<SevApicIdDesc> = SharedBox::try_new_zeroed()?;
assert_eq!(size_of::<SevApicIdDesc>(), PAGE_SIZE);
self.set_rax_valid(1);
self.vmgexit(
GHCBExitCode::GET_APIC_IDS,
virt_to_phys(shared_page.addr()).into(),
0,
)?;
if self.get_rax_valid()? != 1 {
// Hypervisor reports the list of APIC IDs doesn't fit in a page.
return Err(SvsmError::PlatformInit);
}

let mut ids = SevApicIdDesc::new_zeroed();
shared_page.read_into(&mut ids);

Ok(ids
.apic_ids
.iter()
.take(ids.num_entries as usize)
.cloned()
.collect())
}

pub fn ap_create(
&self,
vmsa_gpa: PhysAddr,
Expand Down
20 changes: 11 additions & 9 deletions kernel/src/svsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use svsm::cpu::sse::sse_init;
use svsm::debug::gdbstub::svsm_gdbstub::{debug_break, gdbstub_start};
use svsm::debug::stacktrace::print_stack;
use svsm::enable_shadow_stacks;
use svsm::error::SvsmError;
use svsm::fs::{initialize_fs, opendir, populate_ram_fs};
use svsm::fw_cfg::FwCfg;
use svsm::igvm_params::IgvmParams;
Expand Down Expand Up @@ -312,16 +313,17 @@ pub extern "C" fn svsm_main() {
populate_ram_fs(LAUNCH_INFO.kernel_fs_start, LAUNCH_INFO.kernel_fs_end)
.expect("Failed to unpack FS archive");

let cpus = config.load_cpu_info().expect("Failed to load ACPI tables");
let mut nr_cpus = 0;

for cpu in cpus.iter() {
if cpu.enabled {
nr_cpus += 1;
}
}
// Get the list of APIC IDs from the platform if possible. If the platform does not
// support the operation, get them from the config instead.
let cpus = match SVSM_PLATFORM.get_apic_ids() {
Ok(cpus) => cpus,
Err(SvsmError::NotSupported) => config
.load_cpu_info()
.expect("Failed to get APIC ID list from config"),
Err(e) => panic!("Failed to get APIC ID list from platform: {e:?}"),
};

log::info!("{} CPU(s) present", nr_cpus);
log::info!("{} CPU(s) present", cpus.len());

start_secondary_cpus(&**SVSM_PLATFORM, &cpus);

Expand Down

0 comments on commit 7746744

Please sign in to comment.