From 77467441867456fdd4464e9eb7c388dd1d5d5d81 Mon Sep 17 00:00:00 2001 From: Adam Dunlap Date: Fri, 21 Feb 2025 14:55:18 -0800 Subject: [PATCH] ghcb: Use APIC ID List NAE event to get APIC IDs 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 --- kernel/src/acpi/tables.rs | 24 +++++++++---------- kernel/src/config.rs | 4 ++-- kernel/src/cpu/smp.rs | 9 ++++--- kernel/src/igvm_params.rs | 15 ++++-------- kernel/src/platform/mod.rs | 9 +++++++ kernel/src/platform/snp.rs | 8 +++++++ kernel/src/sev/ghcb.rs | 48 ++++++++++++++++++++++++++++++++++++-- kernel/src/svsm.rs | 20 +++++++++------- 8 files changed, 95 insertions(+), 42 deletions(-) diff --git a/kernel/src/acpi/tables.rs b/kernel/src/acpi/tables.rs index 44bd995b2..58108e74a 100644 --- a/kernel/src/acpi/tables.rs +++ b/kernel/src/acpi/tables.rs @@ -454,8 +454,8 @@ 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) => { @@ -463,13 +463,13 @@ pub struct ACPICPUInfo { /// } /// } /// ``` -pub fn load_acpi_cpu_info(fw_cfg: &FwCfg<'_>) -> Result, SvsmError> { +pub fn load_acpi_cpu_info(fw_cfg: &FwCfg<'_>) -> Result, 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 = Vec::new(); + let mut ids: Vec = Vec::new(); let mut offset = MADT_HEADER_SIZE; while offset < content.len() { @@ -483,19 +483,17 @@ pub fn load_acpi_cpu_info(fw_cfg: &FwCfg<'_>) -> Result, SvsmEr let lapic_ptr = apic_table .content_ptr::(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::() => { let x2apic_ptr = apic_table .content_ptr::(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!( @@ -512,5 +510,5 @@ pub fn load_acpi_cpu_info(fw_cfg: &FwCfg<'_>) -> Result, SvsmEr offset = offset.checked_add(entry_len).ok_or(SvsmError::Acpi)?; } - Ok(cpus) + Ok(ids) } diff --git a/kernel/src/config.rs b/kernel/src/config.rs index 85e1ceda0..cc005b049 100644 --- a/kernel/src/config.rs +++ b/kernel/src/config.rs @@ -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; @@ -95,7 +95,7 @@ impl SvsmConfig<'_> { SvsmConfig::IgvmConfig(igvm_params) => igvm_params.reserved_kernel_area_size(), } } - pub fn load_cpu_info(&self) -> Result, SvsmError> { + pub fn load_cpu_info(&self) -> Result, SvsmError> { match self { SvsmConfig::FirmwareConfig(fw_cfg) => load_acpi_cpu_info(fw_cfg), SvsmConfig::IgvmConfig(igvm_params) => igvm_params.load_cpu_info(), diff --git a/kernel/src/cpu/smp.rs b/kernel/src/cpu/smp.rs index 7e399bbbf..e157a3219 100644 --- a/kernel/src/cpu/smp.rs +++ b/kernel/src/cpu/smp.rs @@ -4,7 +4,6 @@ // // Author: Joerg Roedel -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}; @@ -37,10 +36,10 @@ 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. @@ -48,7 +47,7 @@ pub fn start_secondary_cpus(platform: &dyn SvsmPlatform, cpus: &[ACPICPUInfo]) { 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); diff --git a/kernel/src/igvm_params.rs b/kernel/src/igvm_params.rs index ad4e562d7..1c1c4576c 100644 --- a/kernel/src/igvm_params.rs +++ b/kernel/src/igvm_params.rs @@ -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; @@ -247,17 +246,11 @@ impl IgvmParams<'_> { Ok(()) } - pub fn load_cpu_info(&self) -> Result, SvsmError> { - let mut cpus: Vec = Vec::new(); + pub fn load_cpu_info(&self) -> Result, 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 { diff --git a/kernel/src/platform/mod.rs b/kernel/src/platform/mod.rs index dda3de163..1987d752a 100644 --- a/kernel/src/platform/mod.rs +++ b/kernel/src/platform/mod.rs @@ -4,6 +4,8 @@ // // Author: Jon Lange +extern crate alloc; + pub mod guest_cpu; pub mod native; pub mod snp; @@ -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; @@ -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, SvsmError> { + Err(SvsmError::NotSupported) + } } //FIXME - remove Copy trait diff --git a/kernel/src/platform/snp.rs b/kernel/src/platform/snp.rs index 6f91f9283..8e9a0c250 100644 --- a/kernel/src/platform/snp.rs +++ b/kernel/src/platform/snp.rs @@ -4,6 +4,10 @@ // // Author: Jon Lange +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, }; @@ -334,6 +338,10 @@ impl SvsmPlatform for SnpPlatform { fn start_svsm_request_loop(&self) -> bool { true } + + fn get_apic_ids(&self) -> Result, SvsmError> { + current_ghcb().get_apic_ids() + } } #[derive(Clone, Copy, Debug, Default)] diff --git a/kernel/src/sev/ghcb.rs b/kernel/src/sev/ghcb.rs index 3d159d1ac..8aa2ab0f1 100644 --- a/kernel/src/sev/ghcb.rs +++ b/kernel/src/sev/ghcb.rs @@ -4,15 +4,20 @@ // // Author: Joerg Roedel +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; @@ -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)] @@ -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, @@ -535,6 +541,44 @@ impl GHCB { Ok(()) } + pub fn get_apic_ids(&self) -> Result, 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 = SharedBox::try_new_zeroed()?; + assert_eq!(size_of::(), 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, diff --git a/kernel/src/svsm.rs b/kernel/src/svsm.rs index ee2b58b5f..67dee1b71 100755 --- a/kernel/src/svsm.rs +++ b/kernel/src/svsm.rs @@ -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; @@ -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);