diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index c1889a491c..19a4981a4d 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -143,4 +143,4 @@ jobs: steps: - uses: actions/checkout@v4 # Executes "typos ." - - uses: crate-ci/typos@v1.33.1 + - uses: crate-ci/typos@v1.34.0 diff --git a/Cargo.lock b/Cargo.lock index afa7538716..3ed871f60e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,6 +114,7 @@ dependencies = [ "libc", "linux-loader", "log", + "paste", "serde", "thiserror 2.0.12", "uuid", diff --git a/arch/Cargo.toml b/arch/Cargo.toml index 4c068d131f..b67f398bfa 100644 --- a/arch/Cargo.toml +++ b/arch/Cargo.toml @@ -17,6 +17,7 @@ hypervisor = { path = "../hypervisor" } libc = "0.2.167" linux-loader = { workspace = true, features = ["bzimage", "elf", "pe"] } log = "0.4.22" +paste = { version = "1"} serde = { version = "1.0.208", features = ["derive", "rc"] } thiserror = { workspace = true } uuid = { workspace = true } diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 333a65d9c4..2f3f2b0481 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -12,10 +12,11 @@ extern crate log; use std::collections::BTreeMap; +use std::str::FromStr; use std::sync::Arc; use std::{fmt, result}; -use serde::{Deserialize, Serialize}; +use serde::{de::IntoDeserializer, Deserialize, Serialize}; use thiserror::Error; #[cfg(target_arch = "x86_64")] @@ -59,6 +60,29 @@ pub enum Error { /// Type for returning public functions outcome. pub type Result = result::Result; +#[cfg(target_arch = "x86_64")] +pub use crate::x86_64::{CpuIdEntryRegister, CpuIdFeatureFlags, CpuProfile}; +#[cfg(not(target_arch = "x86_64"))] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum CpuProfile { + #[default] + Host, +} + +impl FromStr for CpuProfile { + type Err = serde::de::value::Error; + fn from_str(s: &str) -> result::Result { + // Should accept both plain strings, and strings surrounded by `"`. + let normalized = s + .strip_prefix('"') + .unwrap_or(s) + .strip_suffix('"') + .unwrap_or(s); + Self::deserialize(normalized.into_deserializer()) + } +} + /// Type for memory region types. #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] pub enum RegionType { diff --git a/arch/src/x86_64/cpu_profile/cpuid_feature_flags_impl.rs b/arch/src/x86_64/cpu_profile/cpuid_feature_flags_impl.rs new file mode 100644 index 0000000000..92cb338c02 --- /dev/null +++ b/arch/src/x86_64/cpu_profile/cpuid_feature_flags_impl.rs @@ -0,0 +1,93 @@ +use crate::x86_64::cpuid_feature_flags::{CpuIdEntryRegister, CpuIdFeatureFlags}; + +impl CpuIdFeatureFlags { + pub const fn intel_cascadelake_v1() -> Self { + use CpuIdEntryRegister as FF; + + Self { + edx_1: FF::VME + .or(FF::SSE2) + .or(FF::SSE) + .or(FF::FXSR) + .or(FF::MMX) + .or(FF::CLFLUSH) + .or(FF::PSE36) + .or(FF::PAT) + .or(FF::CMOV) + .or(FF::MCA) + .or(FF::PGE) + .or(FF::MTRR) + .or(FF::SEP) + .or(FF::APIC) + .or(FF::CX8) + .or(FF::MCE) + .or(FF::PAE) + .or(FF::MSR) + .or(FF::TSC) + .or(FF::PSE) + .or(FF::DE) + .or(FF::FPU), + + ecx_1: FF::AVX + .or(FF::XSAVE) + .or(FF::AES) + .or(FF::POPCNT) + .or(FF::X2APIC) + .or(FF::SSE4_2) + .or(FF::SSE4_1) + .or(FF::CX16) + .or(FF::SSSE3) + .or(FF::PCLMULQDQ) + .or(FF::SSE3) + .or(FF::TSC_DEADLINE) + .or(FF::FMA) + .or(FF::MOVBE) + .or(FF::PCID) + .or(FF::F16C) + .or(FF::RDRAND), + + edx_8000_0001h: FF::LM + .or(FF::PDPE1GB) + .or(FF::RDTSCP) + .or(FF::NX) + .or(FF::SYSCALL), + ecx_8000_0001h: FF::ABM.or(FF::LAHF_LM).or(FF::PREFETCH_3DNOW), + ebx_7_0: FF::FSGSBASE + .or(FF::BMI1) + .or(FF::HLE) + .or(FF::AVX2) + .or(FF::SMEP) + .or(FF::BMI2) + .or(FF::ERMS) + .or(FF::INVPCID) + .or(FF::RTM) + .or(FF::RDSEED) + .or(FF::ADX) + .or(FF::SMAP) + .or(FF::CLWB) + .or(FF::AVX512F) + .or(FF::AVX512DQ) + .or(FF::AVX512BW) + .or(FF::AVX512CD) + .or(FF::AVX512VL) + .or(FF::CLFLUSHOPT), + ecx_7_0: FF::PKU.or(FF::AVX512VNNI), + edx_7_0: FF::SPEC_CTRL.or(FF::SSBD), + eax_0dh_1: FF::XSAVEOPT.or(FF::XSAVEC).or(FF::XGETBV1), + eax_7_1: FF::NULL, + ecx_7_1: FF::NULL, + edx_7_1: FF::NULL, + edx_7_2: FF::NULL, + ecx_14h: FF::NULL, + ecx_24h_1: FF::NULL, + /* + Caskadelake Server only supports TscInvariant, but this feature cannot be relied upon in the context of migration hence we also leave that turned off. + TODO: Isnt't this feature very useful when the VM is running normally though? (i.e. outside of migrations). + */ + edx_8000_0007h: FF::NULL, + ebx_8000_0008h: FF::NULL, + edx_8000_000ah: FF::NULL, + eax_8000_0021h: FF::NULL, + } + } +} diff --git a/arch/src/x86_64/cpu_profile/mod.rs b/arch/src/x86_64/cpu_profile/mod.rs new file mode 100644 index 0000000000..27e1f2007c --- /dev/null +++ b/arch/src/x86_64/cpu_profile/mod.rs @@ -0,0 +1,8 @@ +mod cpuid_feature_flags_impl; +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum CpuProfile { + #[default] + Host, + CascadelakeServerV1, +} diff --git a/arch/src/x86_64/cpuid_feature_flags.rs b/arch/src/x86_64/cpuid_feature_flags.rs new file mode 100644 index 0000000000..e9a3c996cb --- /dev/null +++ b/arch/src/x86_64/cpuid_feature_flags.rs @@ -0,0 +1,530 @@ +use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX}; + +use super::CpuidReg; + +/// A bitset of CPUID feature flags for a given leaf, sub-leaf and register triple +/// (or function, index, register in KVM terms). +pub struct CpuIdEntryRegister(u32); + +impl + CpuIdEntryRegister +{ + pub const fn register_name(&self) -> &'static str { + const { + if CpuidReg::EAX as u8 == REG { + "eax" + } else if CpuidReg::EBX as u8 == REG { + "ebx" + } else if CpuidReg::ECX as u8 == REG { + "ecx" + } else if CpuidReg::EDX as u8 == REG { + "edx" + } else { + // Note that this block is evaluated at compile time and cannot lead to runtime panics + panic!("invalid register value"); + } + } + } + pub const NULL: Self = Self(0); + // Workaround until we can use BitOr in const contexts + pub const fn or(self, other: Self) -> Self { + Self(self.0 | other.0) + } + /// Represents this [`CpuIdEntryRegister`] as a bitset in terms of a [`u32`]. + /// + /// The returned representation does not track the metadata consisting of + /// the function/leaf, index/subleaf and register name. + pub const fn into_raw(&self) -> u32 { + self.0 + } + fn intersect_matching(&self, cpuid: &mut [CpuIdEntry]) { + let mut found_matching = false; + for entry in cpuid.iter_mut().filter(|entry| { + entry.function == FUNCTION + && entry.index == INDEX + // We only care about the cpuid entries with a valid index flag in KVM terms. + // TODO: Is this indeed the case? + && entry.flags == CPUID_FLAG_VALID_INDEX + }) { + found_matching = true; + let mut updated = 0; + if REG == { CpuidReg::EAX as u8 } { + entry.eax &= self.0; + updated = entry.eax; + } else if REG == { CpuidReg::EBX as u8 } { + entry.ebx &= self.0; + updated = entry.ebx; + } else if REG == { CpuidReg::ECX as u8 } { + entry.ecx &= self.0; + updated = entry.ecx; + } else if REG == { CpuidReg::EDX as u8 } { + entry.edx &= self.0; + updated = entry.edx; + } else { + // Unfortunately we cannot use enums as const generic parameters yet, hence we check for this + // here. + error!("BUG: CpuIdFeatureFlags constructed with invalid register value"); + } + // The job is done, but also check if the updated register contains all set feature flags, + // if not report them. + let mut missing_bits = updated ^ self.0; + if missing_bits != 0 { + // iterate over the missing bits and log a warning + let mut bit_positions = Vec::new(); + while missing_bits != 0 { + let idx = missing_bits.trailing_zeros() as usize; + bit_positions.push(u8::try_from(idx).expect( + "idx is at most 32 hence it can be represented as a u8 without problems", + )); + let least_significant_bit = missing_bits & missing_bits.wrapping_neg(); + missing_bits ^= least_significant_bit; + } + log::warn!( + "the given cpuid entry identified by: \n + function = 0x{:08x} \ + index = 0x{:08x} \ + flags = 0x{:08x} \ + does not have the following bits set: {:?} for the {} register + even though the specified restriction permits it. + ", + FUNCTION, + INDEX, + entry.flags, + bit_positions, + self.register_name() + ); + } + } + if (!found_matching) && (self.0 != 0) { + log::warn!( + "no entry matched the function = 0x{:8x}, index = 0x{:08x} parameters", + FUNCTION, + INDEX + ); + } + } +} + +/// Generates multiple constants for [`CpuIdEntryRegister`] without having to repeat the function, index, register parameters every time. +/// +/// Also produces decent documentation for each constant including the parameters, the bit position and the wikipedia entry where the meaning of each feature flag for +/// the current (function, index, register) triple can be found. +/// +/// Invocations have the following named arguments in the following order: +/// - `wiki`: A string with the link to the wikipedia entry describing the various values in this leaf, subleaf, register triple (or function, index, register in KVM terms). +/// - `function`: The leaf for the CPUID entry (called function in KVM terminology). +/// - `index`: The subleaf for the CPUID entry (called index in KVM terminology). +/// - `register`: The register where the feature bits are located (valid inputs are `eax`, `ebx`, `ecx` and `edx`. These are idents and not string literals). +/// - A list of tuples where the first is the name of the constant and the second is its corresponding bit position. +macro_rules! impl_cpuid_entry_register_constants { + (wiki = $wiki:literal, function = $function:literal, index = $index:literal, register = $register:ident, [$(($name:ident, $position:literal)),+$(,)*]) => { + paste::paste! { + impl CpuIdEntryRegister<$function, $index, {CpuidReg::[<$register:upper>] as u8}> { + $( + #[doc = "Bit `" $position "` in register `" $register "` of CPUID function = `" $function "`, index = `" $index "`."] + #[doc = "\n\nSee [this section of the CPUID article in wikipedia]( " $wiki " ) for more information"] + pub const $name: Self = Self(1u32 << $position); + )+ + } + } + }; +} + +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=1:_Processor_Info_and_Feature_Bits", + function = 1, + index = 0, + register = edx, + [ + (FPU, 0), + (VME, 1), + (DE, 2), + (PSE, 3), + (TSC, 4), + (MSR, 5), + (PAE, 6), + (MCE, 7), + (CX8, 8), + (APIC, 9), + (SEP, 11), + (MTRR, 12), + (PGE, 13), + (MCA, 14), + (CMOV, 15), + (PAT, 16), + (PSE36, 17), + (PN /* Intel psn */, 18), + (CLFLUSH /* Intel clfsh */, 19), + (DS /* INTEL DTS */, 21), + (ACPI, 22), + (MMX, 23), + (FXSR, 24), + (SSE, 25), + (SSE2, 26), + (SS, 27), + (HT /* Intel htt */, 28), + (TM, 29), + (IA64, 30), + (PBE, 31), + ] +); + +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=1:_Processor_Info_and_Feature_Bits", + function = 1, + index = 0, + register = ecx, + [ + (SSE3 /* Intel PNI and AMD sse3 */, 0), + (PCLMULQDQ, 1), + (DTES64, 2), + (MONITOR, 3), + (DS_CPL, 4), + (VMX, 5), + (SMX, 6), + (EST, 7), + (TM2, 8), + (SSSE3, 9), + (CID, 10), + (FMA, 12), + (CX16, 13), + (XTPR, 14), + (PDCM, 15), + (PCID, 17), + (DCA, 18), + (SSE4_1, 19), + (SSE4_2, 20), + (X2APIC, 21), + (MOVBE, 22), + (POPCNT, 23), + (TSC_DEADLINE, 24), + (AES, 25), + (XSAVE, 26), + (AVX, 28), + (F16C, 29), + (RDRAND, 30), + (HYPERVISOR, 31), + ] +); + +// We only make the ARAT (always running APIC timer) capability from this leaf ergonomic for now. +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=6:_Thermal_and_Power_Management", + function = 6, + index = 0, + register = eax, + [(ARAT, 2)] +); + +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=0:_Extended_Features", + function = 7, + index = 0, + register = ebx, + [ + (FSGSBASE, 0), + (TSC_ADJUST, 1), + (SGX, 2), + (BMI1, 3), + (HLE, 4), + (AVX2, 5), + (FDP_EXCPTN_ONLY, 6), + (SMEP, 7), + (BMI2, 8), + (ERMS, 9), + (INVPCID, 10), + (RTM, 11), + (ZERO_FCS_FDS, 13), + (MPX, 14), + (AVX512F, 16), + (AVX512DQ, 17), + (RDSEED, 18), + (ADX, 19), + (SMAP, 20), + (AVX512IFMA, 21), + (PCOMMIT, 22), + (CLFLUSHOPT, 23), + (CLWB, 24), + (INTEL_PT, 25), + (AVX512PF, 26), + (AVX512ER, 27), + (AVX512CD, 28), + (SHA_NI, 29), + (AVX512BW, 30), + (AVX512VL, 31), + ] +); + +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=0:_Extended_Features", + function = 7, + index = 0, + register = ecx, + [ + (AVX512VBMI, 1), + (UMIP, 2), + (PKU, 3), + (WAITPKG, 5), + (AVX512VBMI2, 6), + (GFNI, 8), + (VAES, 9), + (VPCLMULQDQ, 10), + (AVX512VNNI, 11), + (AVX512BITALG, 12), + (AVX512_VPOPCNTDQ, 14), + (LA57, 16), + (RDPID, 22), + (BUS_LOCK_DETECT, 24), + (CLDEMOTE, 25), + (MOVDIRI, 27), + (MOVDIR64B, 28), + (SGXLC, 30), + (PKS, 31), + ] +); + +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=0:_Extended_Features", + function = 7, + index = 0, + register = edx, + [ + (AVX512_4VNNIW, 2), + (AVX512_4FMAPS, 3), + (FSRM, 4), + (AVX512_VP2INTERSECT, 8), + (MD_CLEAR, 10), + (SERIALIZE, 14), + (TSX_LDTRK, 16), + (ARCH_LBR, 19), + (AMX_BF16, 22), + (AVX512_FP16, 23), + (AMX_TILE, 24), + (AMX_INT8, 25), + (SPEC_CTRL, 26), + (STIBP, 27), + (FLUSH_L1D, 28), + (ARCH_CAPABILITIES, 29), + (CORE_CAPABILITY, 30), + (SSBD, 31), + ] +); + +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=1:_Extended_Features", + function = 7, + index = 1, + register = ecx, + [(MSR_IMM, 5)] +); +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=1:_Extended_Features", + function = 7, + index = 1, + register = eax, + [ + (SHA512, 0), + (SM3, 1), + (SM4, 2), + (AVX_VNNI, 4), + (AVX512_BF16, 5), + (CMPCCXADD, 7), + (FZRM, 10), + (FSRS, 11), + (FSRC, 12), + (FRED, 17), + (LKGS, 18), + (WRMSRNS, 19), + (AMX_FP16, 21), + (AVX_IFMA, 23), + (LAM, 26), + ] +); + +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=1:_Extended_Features", + function = 7, + index = 1, + register = edx, + [ + (AVX_VNNI_INT8, 4), + (AVX_NE_CONVERT, 5), + (AMX_COMPLEX, 8), + (AVX_VNNI_INT16, 10), + (PREFETCHITI, 14), + (AVX10, 19), + ] +); + +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=2:_Extended_Features", + function = 7, + index = 2, + register = edx, + [ + (INTEL_PSFD, 0), + (IPRED_CTRL, 1), + (RRSBA_CTRL, 2), + (DDPD_U, 3), + (BHI_CTRL, 4), + (MCDT_NO, 5), + ] +); +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=0Dh:_XSAVE_Features_and_State_Components", + function = 0xd, + index = 1, + register = eax, + [ + (XSAVEOPT, 0), + (XSAVEC, 1), + (XGETBV1, 2), + (XSAVES, 3), + (XFD, 4), + ] +); + +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=24h,_ECX=1:_Discrete_AVX10_Features", + function = 0x24, + index = 1, + register = ecx, + [(VPMM, 0), (AVX_10_VNNI_INT, 2)] +); + +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=8000'0001h:_Extended_Processor_Info_and_Feature_Bits", + function = 0x8000_0001, + index = 0, + register = edx, + [ + (SYSCALL, 11), + (NX, 20), + (MMXEXT, 22), + (FXSR_OPT, 25), + (PDPE1GB, 26), + (RDTSCP, 27), + (LM, 29), + (EXT_3DNOW, 30), + (FIRST_3DNOW, 31), + ] +); +impl_cpuid_entry_register_constants!( + wiki = "https://en.wikipedia.org/wiki/CPUID#EAX=8000'0001h:_Extended_Processor_Info_and_Feature_Bits", + function = 0x8000_0001, + index = 0, + register = ecx, + [ + (LAHF_LM,0), + (CMP_LEGACY,1), + (SVM,2), + (EXTAPIC,3), + (CR8LEGACY,4), + (ABM,5), + (SSE4A,6), + (MISALIGNSSE,7), + (PREFETCH_3DNOW,8), + (OSVW,9), + (IBS,10), + (XOP,11), + (SKINIT,12), + (WDT,13), + (LWP,15), + (FMA4,16), + (TCE,17), + (NODEID_MSR,19), + (TBM,21), + (TOPOEXT,22), + (PERFCTR_CORE,23), + (PERFCTR_NB,24), + ] +); +impl_cpuid_entry_register_constants!( + wiki="https://en.wikipedia.org/wiki/CPUID#EAX=8000'0007h:_Processor_Power_Management_Information_and_RAS_Capabilities", + function = 0x8000_0007, + index = 0, + register = edx, + [(INVTSC, 8)] +); + +/// A set of CPUID feature flags consisting of the registers for the various leaves describing +/// CPU feature flags. +/// +/// # Feature related Registers that are ignored +/// +/// - 0x12: SGX capabilities (this is ignored as CHV has deprecated its support). TODO: Should we include this and set it to `Self::NULL` ? +/// +/// - 0x19: Intel key locker features (ignored, but why?) +/// - 0x1E, EXC=1: TMUL information (this is handled separately for now) +/// - TODO: 0x8000_0001F: Encrypted Memory Capabilities +/// - 0xC000_0000: Highest Centaur Extended Function (out of scope for now) +/// - 0xC000_0001: Centaur Feature Information (out of scope for now) +/// # Constructors +/// +/// This struct has constructors for each of the currently supported CPU profiles. +/// +/// For DEVELOPERS: The CPU profile constructors are located in [`crate::x86_64::cpu_profile::cpuid_feature_flags_impl`](crate::x86_64::cpu_profile::cpuid_feature_flags_impl). +pub struct CpuIdFeatureFlags { + pub edx_1: CpuIdEntryRegister<1, 0, { CpuidReg::EDX as u8 }>, + pub ecx_1: CpuIdEntryRegister<1, 0, { CpuidReg::ECX as u8 }>, + pub ebx_7_0: CpuIdEntryRegister<7, 0, { CpuidReg::EBX as u8 }>, + pub ecx_7_0: CpuIdEntryRegister<7, 0, { CpuidReg::ECX as u8 }>, + pub edx_7_0: CpuIdEntryRegister<7, 0, { CpuidReg::EDX as u8 }>, + pub eax_7_1: CpuIdEntryRegister<7, 1, { CpuidReg::EAX as u8 }>, + pub ecx_7_1: CpuIdEntryRegister<7, 1, { CpuidReg::ECX as u8 }>, + pub edx_7_1: CpuIdEntryRegister<7, 1, { CpuidReg::EDX as u8 }>, + pub edx_7_2: CpuIdEntryRegister<7, 2, { CpuidReg::EDX as u8 }>, + pub eax_0dh_1: CpuIdEntryRegister<0xd, 1, { CpuidReg::EAX as u8 }>, + pub ecx_14h: CpuIdEntryRegister<0x14, 0, { CpuidReg::ECX as u8 }>, + pub ecx_24h_1: CpuIdEntryRegister<0x24, 1, { CpuidReg::ECX as u8 }>, + pub edx_8000_0001h: CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::EDX as u8 }>, + pub ecx_8000_0001h: CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::ECX as u8 }>, + pub edx_8000_0007h: CpuIdEntryRegister<0x8000_0007, 0, { CpuidReg::EDX as u8 }>, + pub ebx_8000_0008h: CpuIdEntryRegister<0x8000_0008, 0, { CpuidReg::EBX as u8 }>, + /// FEAT_SVM in QEMU + pub edx_8000_000ah: CpuIdEntryRegister<0x8000_000A, 0, { CpuidReg::EDX as u8 }>, + pub eax_8000_0021h: CpuIdEntryRegister<0x8000_0021, 0, { CpuidReg::EAX as u8 }>, +} + +impl CpuIdFeatureFlags { + pub(in crate::x86_64) fn restrict(&self, cpuid: &mut [CpuIdEntry]) { + let Self { + edx_1, + ecx_1, + ebx_7_0, + ecx_7_0, + edx_7_0, + eax_7_1, + ecx_7_1, + edx_7_1, + edx_7_2, + eax_0dh_1, + ecx_14h, + ecx_24h_1, + edx_8000_0001h, + ecx_8000_0001h, + edx_8000_0007h, + ebx_8000_0008h, + edx_8000_000ah, + eax_8000_0021h, + } = self; + edx_1.intersect_matching(cpuid); + ecx_1.intersect_matching(cpuid); + ebx_7_0.intersect_matching(cpuid); + ecx_7_0.intersect_matching(cpuid); + edx_7_0.intersect_matching(cpuid); + eax_7_1.intersect_matching(cpuid); + ecx_7_1.intersect_matching(cpuid); + edx_7_1.intersect_matching(cpuid); + edx_7_2.intersect_matching(cpuid); + eax_0dh_1.intersect_matching(cpuid); + ecx_14h.intersect_matching(cpuid); + ecx_24h_1.intersect_matching(cpuid); + edx_8000_0001h.intersect_matching(cpuid); + ecx_8000_0001h.intersect_matching(cpuid); + edx_8000_0007h.intersect_matching(cpuid); + ebx_8000_0008h.intersect_matching(cpuid); + edx_8000_000ah.intersect_matching(cpuid); + eax_8000_0021h.intersect_matching(cpuid); + } +} diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index baa984c94b..fb1eef2f0c 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -7,14 +7,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE-BSD-3-Clause file. use std::sync::Arc; +mod cpu_profile; +mod cpuid_feature_flags; pub mod interrupts; pub mod layout; mod mpspec; mod mptable; + pub mod regs; use std::collections::BTreeMap; use std::mem; +pub use cpu_profile::CpuProfile; +pub use cpuid_feature_flags::{CpuIdEntryRegister, CpuIdFeatureFlags}; use hypervisor::arch::x86::{CpuIdEntry, CPUID_FLAG_VALID_INDEX}; use hypervisor::{CpuVendor, HypervisorCpuError, HypervisorError}; use linux_loader::loader::bootparam::{boot_params, setup_header}; @@ -127,6 +132,7 @@ pub struct CpuidConfig { #[cfg(feature = "tdx")] pub tdx: bool, pub amx: bool, + pub profile: CpuProfile, } #[derive(Debug, Error)] @@ -229,6 +235,7 @@ pub fn get_x2apic_id(cpu_id: u32, topology: Option<(u8, u8, u8)>) -> u32 { cpu_id } +#[repr(u8)] #[derive(Copy, Clone, Debug)] pub enum CpuidReg { EAX, @@ -660,6 +667,24 @@ pub fn generate_common_cpuid( .get_supported_cpuid() .map_err(Error::CpuidGetSupported)?; + // Restrict the supported CPUID to the features supported by the cpu profile. + /* NOTE: + The compiler will probably inline all function calls in the following match. This + should be fine for a few dozen cpu profiles, but since `CpuIdFeatureFlags` is a fairly large + struct it can lead to potential stack overflow if/when we implement really many cpu profiles. + + If that becomes the case we recommend changing the match arms to return a function pointer to the relevant + constructor instead. + */ + match config.profile { + CpuProfile::Host => { + // When this is set we do nothing + } + CpuProfile::CascadelakeServerV1 => { + CpuIdFeatureFlags::intel_cascadelake_v1().restrict(&mut cpuid); + } + } + CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches); if let Some(sgx_epc_sections) = &config.sgx_epc_sections { diff --git a/docs/macvtap-bridge.md b/docs/macvtap-bridge.md index 75173ee1ca..5161eb7bb6 100644 --- a/docs/macvtap-bridge.md +++ b/docs/macvtap-bridge.md @@ -18,7 +18,7 @@ tapindex=$(< /sys/class/net/macvtap0/ifindex) tapdevice="/dev/tap$tapindex" # Ensure that we can access this device -sudo chown "$UID.$UID" "$tapdevice" +sudo chown "$UID:$UID" "$tapdevice" # Use --net fd=3 to point to fd 3 which the shell has opened to point to the /dev/tapN device target/debug/cloud-hypervisor \ diff --git a/src/main.rs b/src/main.rs index 5d3de31ae3..c2e67e9d59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -955,6 +955,7 @@ mod unit_tests { max_phys_bits: 46, affinity: None, features: CpuFeatures::default(), + profile: Default::default(), }, memory: MemoryConfig { size: 536_870_912, diff --git a/vmm/src/config.rs b/vmm/src/config.rs index eefacd2b6a..cfe2054da6 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -9,6 +9,7 @@ use std::path::PathBuf; use std::result; use std::str::FromStr; +use arch::CpuProfile; use clap::ArgMatches; use option_parser::{ ByteSized, IntegerList, OptionParser, OptionParserError, StringList, Toggle, Tuple, @@ -543,6 +544,7 @@ impl FromStr for CpuTopology { impl CpusConfig { pub fn parse(cpus: &str) -> Result { + // TODO: Verify that this works with the new profile option let mut parser = OptionParser::new(); parser .add("boot") @@ -551,7 +553,8 @@ impl CpusConfig { .add("kvm_hyperv") .add("max_phys_bits") .add("affinity") - .add("features"); + .add("features") + .add("profile"); parser.parse(cpus).map_err(Error::ParseCpus)?; let boot_vcpus: u8 = parser @@ -587,6 +590,10 @@ impl CpusConfig { .convert::("features") .map_err(Error::ParseCpus)? .unwrap_or_default(); + let profile: CpuProfile = parser + .convert("profile") + .map_err(Error::ParseCpus)? + .unwrap_or_default(); // Some ugliness here as the features being checked might be disabled // at compile time causing the below allow and the need to specify the // ref type in the match. @@ -613,6 +620,7 @@ impl CpusConfig { max_phys_bits, affinity, features, + profile, }) } } diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index e26946538e..1263e0f446 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -729,6 +729,9 @@ impl CpuManager { } } } + // TODO: Are there more CPU features that need explicit kernel support to work? + // If so we propose adding each of them to `CpuFeatures` and also dealing with + // here. let proximity_domain_per_cpu: BTreeMap = { let mut cpu_list = Vec::new(); @@ -809,6 +812,7 @@ impl CpuManager { #[cfg(feature = "tdx")] tdx, amx: self.config.features.amx, + profile: self.config.profile, }, ) .map_err(Error::CommonCpuId)? diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 03c8ee773e..9f9885afd4 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1125,7 +1125,12 @@ impl Vmm { ))); }; - let amx = vm_config.lock().unwrap().cpus.features.amx; + let (amx, cpu_profile) = { + let guard = vm_config.lock().unwrap(); + let amx = guard.cpus.features.amx; + let profile = guard.cpus.profile; + (amx, profile) + }; let phys_bits = vm::physical_bits(&hypervisor, vm_config.lock().unwrap().cpus.max_phys_bits); arch::generate_common_cpuid( @@ -1137,6 +1142,7 @@ impl Vmm { #[cfg(feature = "tdx")] tdx: false, amx, + profile: cpu_profile, }, ) .map_err(|e| { @@ -1276,6 +1282,7 @@ impl Vmm { #[cfg(feature = "tdx")] tdx: false, amx: vm_config.cpus.features.amx, + profile: vm_config.cpus.profile, }, ) .map_err(|e| { @@ -1296,6 +1303,7 @@ impl Vmm { vm_config: Arc>, prefault: bool, ) -> std::result::Result<(), VmError> { + // TODO: Do we need to check for compatibility with the cpu profile here as well? let snapshot = recv_vm_state(source_url).map_err(VmError::Restore)?; #[cfg(all(feature = "kvm", target_arch = "x86_64"))] let vm_snapshot = get_vm_snapshot(&snapshot).map_err(VmError::Restore)?; @@ -2358,6 +2366,8 @@ const DEVICE_MANAGER_SNAPSHOT_ID: &str = "device-manager"; #[cfg(test)] mod unit_tests { + use arch::CpuProfile; + use super::*; #[cfg(target_arch = "x86_64")] use crate::vm_config::DebugConsoleConfig; @@ -2391,6 +2401,7 @@ mod unit_tests { max_phys_bits: 46, affinity: None, features: CpuFeatures::default(), + profile: CpuProfile::default(), }, memory: MemoryConfig { size: 536_870_912, diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index d7bba25cc0..2e00794134 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2773,11 +2773,11 @@ impl Snapshottable for Vm { #[cfg(all(feature = "kvm", target_arch = "x86_64"))] let common_cpuid = { - let amx = self.config.lock().unwrap().cpus.features.amx; - let phys_bits = physical_bits( - &self.hypervisor, - self.config.lock().unwrap().cpus.max_phys_bits, - ); + let guard = self.config.lock().unwrap(); + let amx = guard.cpus.features.amx; + let phys_bits = physical_bits(&self.hypervisor, guard.cpus.max_phys_bits); + let profile = guard.cpus.profile; + core::mem::drop(guard); arch::generate_common_cpuid( &self.hypervisor, &arch::CpuidConfig { @@ -2787,6 +2787,7 @@ impl Snapshottable for Vm { #[cfg(feature = "tdx")] tdx: false, amx, + profile, }, ) .map_err(|e| { diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index d926bf14cb..44b7ac2b0d 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -6,6 +6,7 @@ use std::net::{IpAddr, Ipv4Addr}; use std::path::PathBuf; use std::{fs, result}; +use arch::CpuProfile; use net_util::MacAddr; use serde::{Deserialize, Serialize}; use virtio_devices::RateLimiterConfig; @@ -65,6 +66,8 @@ pub struct CpusConfig { pub affinity: Option>, #[serde(default)] pub features: CpuFeatures, + #[serde(default)] + pub profile: CpuProfile, } pub const DEFAULT_VCPUS: u8 = 1; @@ -79,6 +82,7 @@ impl Default for CpusConfig { max_phys_bits: DEFAULT_MAX_PHYS_BITS, affinity: None, features: CpuFeatures::default(), + profile: CpuProfile::default(), } } }