From 5dc01cbb5c6ecb464a86bf50c68bc15c993b9905 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 02:05:46 +0000 Subject: [PATCH 01/24] build: Bump crate-ci/typos from 1.33.1 to 1.34.0 Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.33.1 to 1.34.0. - [Release notes](https://github.com/crate-ci/typos/releases) - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md) - [Commits](https://github.com/crate-ci/typos/compare/v1.33.1...v1.34.0) --- updated-dependencies: - dependency-name: crate-ci/typos dependency-version: 1.34.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/quality.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b6852afa72c0c01fc9f6ee9e208dfe71f96ffdea Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 1 Jul 2025 04:59:19 +0000 Subject: [PATCH 02/24] docs: Fix the chown command in macvtap-bridge.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When invoking the script chown shows a warning. chown: warning: '.' should be ':': ‘1000.1000’ From `info coreutils 'chown invocation'`. Some older scripts may still use ‘.’ in place of the ‘:’ separator. POSIX 1003.1-2001 (*note Standards conformance::) does not require support for that, but for backward compatibility GNU ‘chown’ supports ‘.’ so long as no ambiguity results, although it issues a warning and support may be removed in future versions. New scripts should avoid the use of ‘.’ because it is not portable, and because it has undesirable results if the entire OWNER‘.’GROUP happens to identify a user whose name contains ‘.’. Signed-off-by: Wei Liu --- docs/macvtap-bridge.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 \ From 51d16abdabb06916ae5cb3dd61565ee13b5e41a1 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Tue, 29 Jul 2025 22:13:15 +0200 Subject: [PATCH 03/24] Start manual implementation of CPUID feature flag values --- arch/src/x86_64/mod.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index baa984c94b..f83b922c28 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -229,6 +229,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, @@ -237,6 +238,42 @@ pub enum CpuidReg { EDX, } +/* + CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX | + CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA | + CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 | + CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE | + CPUID_DE | CPUID_FP87, +*/ + +struct CpuIdFeatureFlags(u32); +impl CpuIdFeatureFlags<1, 0, { CpuidReg::EDX as u8 }> { + /// Onboard x87 FPU + const FPU: Self = Self(1); + /// Virtual 8086 mode extensions (such as VIF, VIP, PVI) + const VME: Self = Self(1 << 1); + /// Debugging extensions (CR4 bit 3) + const DE: Self = Self(1 << 2); + /// Page size extension (4 MB pages) + const PSE: Self = Self(1 << 3); + /// Time Stamp Counter and RDTSC instruction + const TSC: Self = Self(1 << 4); + /// Model-specific registers and RDMSR/WRMSR instructions + const MSR: Self = Self(1 << 5); + + const PAE: Self = Self(1 << 6); + /// CLFLUSH cache line flush instruction + const CL_FLUSH: Self = Self(1 << 19); + /// MMX instructions (64-bit SIMD) + const MMX: Self = Self(1 << 23); + /// FXSAVE, FXRSTOR instructions + const FXSR: Self = Self(1 << 24); + /// Streaming SIMD extensions instructions + const SSE: Self = Self(1 << 25); + /// SSE2 instructions + const SSE2: Self = Self(1 << 26); +} + pub struct CpuidPatch { pub function: u32, pub index: u32, From 3b9962faf96144dab31c70fd544905b62a2bc970 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 30 Jul 2025 12:49:58 +0200 Subject: [PATCH 04/24] Add Feature flags with macros --- arch/src/x86_64/mod.rs | 262 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 230 insertions(+), 32 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index f83b922c28..92f826d8ad 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -238,41 +238,239 @@ pub enum CpuidReg { EDX, } -/* - CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX | - CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA | - CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 | - CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE | - CPUID_DE | CPUID_FP87, -*/ +macro_rules! cpuid_flag_constants { + //============ Possible starting points ===========// + ($t:ty, $name:ident) => { + impl $t { + const $name: Self = Self(1); + } + }; + ($t:ty, "NULL", $($tail:tt)*) => { + impl $t { + cpuid_flag_constants!(1, $($tail)*); + } + }; + ($t:ty, $name:ident, $($tail:tt)*) => { + impl $t { + const $name: Self = Self(1); + cpuid_flag_constants!(1, $($tail)*); + } + }; + + //============ Possible most deeply nested macro invocation ===========// + ($i:expr, $name:ident) => { + const $name: Self = Self(1 << $i); + }; + + ($i:expr, "NULL") => {}; + + ("NULL") => {}; + + () => {}; + + // Also permit ending with a trailing , + ($i:expr, $name:ident,) => { + const $name: Self = Self(1 << $i); + }; + + ($i:expr, "NULL",) => {}; + + ("NULL",) => {}; + + (,) => {}; + + // ============ Possible continuations that continue the recursion ===== // + ($i:expr, "NULL", $($tail:tt)+) => { + cpuid_flag_constants!($i + 1, $($tail)*); + }; + ($i:expr, $name:ident, $($tail:tt)+) => { + const $name: Self = Self(1 << $i); + cpuid_flag_constants!($i + 1, $($tail)*); + }; +} struct CpuIdFeatureFlags(u32); -impl CpuIdFeatureFlags<1, 0, { CpuidReg::EDX as u8 }> { - /// Onboard x87 FPU - const FPU: Self = Self(1); - /// Virtual 8086 mode extensions (such as VIF, VIP, PVI) - const VME: Self = Self(1 << 1); - /// Debugging extensions (CR4 bit 3) - const DE: Self = Self(1 << 2); - /// Page size extension (4 MB pages) - const PSE: Self = Self(1 << 3); - /// Time Stamp Counter and RDTSC instruction - const TSC: Self = Self(1 << 4); - /// Model-specific registers and RDMSR/WRMSR instructions - const MSR: Self = Self(1 << 5); - - const PAE: Self = Self(1 << 6); - /// CLFLUSH cache line flush instruction - const CL_FLUSH: Self = Self(1 << 19); - /// MMX instructions (64-bit SIMD) - const MMX: Self = Self(1 << 23); - /// FXSAVE, FXRSTOR instructions - const FXSR: Self = Self(1 << 24); - /// Streaming SIMD extensions instructions - const SSE: Self = Self(1 << 25); - /// SSE2 instructions - const SSE2: Self = Self(1 << 26); +impl std::ops::BitOr + for CpuIdFeatureFlags +{ + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} +impl std::ops::Not + for CpuIdFeatureFlags +{ + type Output = Self; + fn not(self) -> Self::Output { + Self(!self.0) + } +} + +impl CpuIdFeatureFlags { + const fn into_entry(self) -> CpuIdEntry { + // Unfortunately we cannot use enums as const generic parameters as of now, hence we need + // this workaround + CpuIdEntry { + function: FUNCTION, + index: INDEX, + flags: CPUID_FLAG_VALID_INDEX, + eax: if REG == { CpuidReg::EAX as u8 } { + self.0 + } else { + 0 + }, + ebx: if REG == { CpuidReg::EBX as u8 } { + self.0 + } else { + 0 + }, + ecx: if REG == { CpuidReg::ECX as u8 } { + self.0 + } else { + 0 + }, + edx: if REG == { CpuidReg::EDX as u8 } { + self.0 + } else { + 0 + }, + } + } + const fn join( + self, + other: CpuIdFeatureFlags, + ) -> CpuIdEntry { + let mut entry_self = self.into_entry(); + let entry_other = other.into_entry(); + entry_self.eax |= entry_other.eax; + entry_self.ebx |= entry_other.ebx; + entry_self.ecx |= entry_other.ecx; + entry_self.edx |= entry_other.edx; + // Note that we don't meed tp worry about FUNCTION and INDEX matching for the two entries as that + // is already taken care of by the type system. + entry_self + } + const fn join_three( + self, + second: CpuIdFeatureFlags, + third: CpuIdFeatureFlags, + ) -> CpuIdEntry { + let mut join = self.join(second); + let third_as_entry = third.into_entry(); + join.eax |= third_as_entry.eax; + join.ebx |= third_as_entry.ebx; + join.ecx |= third_as_entry.ecx; + join.edx |= third_as_entry.edx; + // Note that we don't meed tp worry about FUNCTION and INDEX matching for the three entries as that + // is already taken care of by the type system. + join + } } +cpuid_flag_constants!( + CpuIdFeatureFlags<1, 0, { CpuidReg::EDX as u8 }>, + FPU, VME, DE, PSE, + TSC, MSR, PAE, MCE, + CX8, APIC, "NULL", SEP, + MTRR, PGE, MCA, CMOV, + PAT, PSE36, PN /* Intel psn */, CLFLUSH /* Intel clfsh */, + "NULL", DS /* INTEL DTS */, ACPI, MMX, + FXSR, SSE, SSE2, SS, + HT /* Intel htt */, TM, IA64, PBE, +); +cpuid_flag_constants!( + CpuIdFeatureFlags<1, 0, { CpuidReg::ECX as u8 }>, + PNI /* Intel,AMD sse3 */, PCLMULQDQ, DTES64, MONITOR, + DS_CPL, VMX, SMX, EST, + TM2, SSSE3, CID, "NULL", + FMA, CX16, XTPR, PDCM, + "NULL", PCID, DCA, SSE4_1, + SSE4_2, X2APIC, MOVBE, POPCNT, + TSC_DEADLINE, AES, XSAVE, "NULL" /* osxsave */, + AVX, F16C, RDRAND, HYPERVISOR, +); +cpuid_flag_constants!( + CpuIdFeatureFlags<0x8000_0001, 0, { CpuidReg::EDX as u8}>, + "NULL" /* fpu */, "NULL" /* vme */, "NULL" /* de */, "NULL" /* pse */, + "NULL" /* tsc */, "NULL" /* msr */, "NULL" /* pae */, "NULL" /* mce */, + "NULL" /* cx8 */, "NULL" /* apic */, "NULL", SYSCALL, + "NULL" /* mtrr */, "NULL" /* pge */, "NULL" /* mca */, "NULL" /* cmov */, + "NULL" /* pat */, "NULL" /* pse36 */, "NULL", "NULL" /* Linux mp */, + NX, "NULL", MMXEXT, "NULL" /* mmx */, + "NULL" /* fxsr */, FXSR_OPT, PDPE1GB, RDTSCP, + "NULL", LM, EXT_3DNOW, FIRST_3DNOW, + +); +cpuid_flag_constants!( + CpuIdFeatureFlags<0x8000_0001, 0, { CpuidReg::ECX as u8}>, + LAHF_LM, CMP_LEGACY, SVM, EXTAPIC, + CR8LEGACY, ABM, SSE4A, MISALIGNSSE, + PREFETCH_3DNOW, OSVW, IBS, XOP, + SKINIT, WDT, "NULL", LWP, + FMA4, TCE, "NULL", NODEID_MSR, + "NULL", TBM, TOPOEXT, PERFCTR_CORE, + PERFCTR_NB, "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", +); +cpuid_flag_constants!( + CpuIdFeatureFlags<7, 0, {CpuidReg::EBX as u8}>, + FSGSBASE, TSC_ADJUST, SGX, BMI1, + HLE, AVX2, FDP_EXCPTN_ONLY, SMEP, + BMI2, ERMS, INVPCID, RTM, + "NULL", ZERO_FCS_FDS, MPX, "NULL", + AVX512F, AVX512DQ, RDSEED, ADX, + SMAP, AVX512IFMA, PCOMMIT, CLFLUSHOPT, + CLWB, INTEL_PT, AVX512PF, AVX512ER, + AVX512CD, SHA_NI, AVX512BW, AVX512VL, +); +cpuid_flag_constants!( + CpuIdFeatureFlags<7, 0, {CpuidReg::ECX as u8}>, + "NULL", AVX512VBMI, UMIP, PKU, + "NULL" /* ospke */, WAITPKG, AVX512VBMI2, "NULL", + GFNI, VAES, VPCLMULQDQ, AVX512VNNI, + AVX512BITALG, "NULL", AVX512_VPOPCNTDQ, "NULL", + LA57, "NULL", "NULL", "NULL", + "NULL", "NULL", RDPID, "NULL", + BUS_LOCK_DETECT, CLDEMOTE, "NULL", MOVDIRI, + MOVDIR64B, "NULL", SGXLC, PKS, +); + +cpuid_flag_constants!( + CpuIdFeatureFlags<7, 0, {CpuidReg::EDX as u8}>, + "NULL", "NULL", AVX512_4VNNIW, AVX512_4FMAPS, + FSRM, "NULL", "NULL", "NULL", + AVX512_VP2INTERSECT, "NULL", MD_CLEAR, "NULL", + "NULL", "NULL", SERIALIZE, "NULL", + TSX_LDTRK, "NULL", "NULL" /* pconfig */, ARCH_LBR, + "NULL", "NULL", AMX_BF16, AVX512_FP16, + AMX_TILE, AMX_INT8, SPEC_CTRL, STIBP, + FLUSH_L1D, ARCH_CAPABILITIES, CORE_CAPABILITY, SSBD, +); + +cpuid_flag_constants!( + CpuIdFeatureFlags<0xd,1, {CpuidReg::EAX as u8}>, + XSAVEOPT, XSAVEC, XGETBV1, XSAVES, + XFD, "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", +); + +cpuid_flag_constants!( + CpuIdFeatureFlags<6, 0, {CpuidReg::EAX as u8}>, + "NULL", "NULL", ARAT, "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + +); pub struct CpuidPatch { pub function: u32, From 71bcaadf2858fc8c6eb0b469309531f86b81f7d8 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 30 Jul 2025 15:20:26 +0200 Subject: [PATCH 05/24] constructor --- arch/src/x86_64/cpu_profiles.rs | 106 ++++++++++++++++++++++++++++++++ arch/src/x86_64/mod.rs | 4 +- 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 arch/src/x86_64/cpu_profiles.rs diff --git a/arch/src/x86_64/cpu_profiles.rs b/arch/src/x86_64/cpu_profiles.rs new file mode 100644 index 0000000000..2d7c5f5d0b --- /dev/null +++ b/arch/src/x86_64/cpu_profiles.rs @@ -0,0 +1,106 @@ +use hypervisor::arch::x86::CpuIdEntry; + +use super::{CpuIdFeatureFlags, CpuidReg}; + +pub(super) struct CascadeLakeServerV1CpuIdFeatures { + edx_1: CpuIdFeatureFlags<1, 0, { CpuidReg::EDX as u8 }>, + ecx_1: CpuIdFeatureFlags<1, 0, { CpuidReg::ECX as u8 }>, + edx_8000_0001h: CpuIdFeatureFlags<0x8000_0001, 0, { CpuidReg::EDX as u8 }>, + ecx_8000_0001h: CpuIdFeatureFlags<0x8000_0001, 0, { CpuidReg::ECX as u8 }>, + ebx_7_0: CpuIdFeatureFlags<7, 0, { CpuidReg::EBX as u8 }>, + ecx_7_0: CpuIdFeatureFlags<7, 0, { CpuidReg::ECX as u8 }>, + edx_7_0: CpuIdFeatureFlags<7, 0, { CpuidReg::EDX as u8 }>, + eax_0dh: CpuIdFeatureFlags<0xd, 1, { CpuidReg::EAX as u8 }>, +} + +impl CascadeLakeServerV1CpuIdFeatures { + pub(super) fn new() -> Self { + use CpuIdFeatureFlags as FF; + Self { + edx_1: FF::VME + | FF::SSE2 + | FF::SSE + | FF::FXSR + | FF::MMX + | FF::CLFLUSH + | FF::PSE36 + | FF::PAT + | FF::CMOV + | FF::MCA + | FF::PGE + | FF::MTRR + | FF::SEP + | FF::APIC + | FF::CX8 + | FF::MCE + | FF::PAE + | FF::MSR + | FF::TSC + | FF::PSE + | FF::DE + | FF::FPU, + + ecx_1: FF::AVX + | FF::XSAVE + | FF::AES + | FF::POPCNT + | FF::X2APIC + | FF::SSE4_2 + | FF::SSE4_1 + | FF::CX16 + | FF::SSSE3 + | FF::PCLMULQDQ + | FF::SSE3 + | FF::TSC_DEADLINE + | FF::FMA + | FF::MOVBE + | FF::PCID + | FF::F16C + | FF::RDRAND, + + edx_8000_0001h: FF::LM | FF::PDPE1GB | FF::RDTSCP | FF::NX | FF::SYSCALL, + ecx_8000_0001h: FF::ABM | FF::LAHF_LM | FF::PREFETCH_3DNOW, + ebx_7_0: FF::FSGSBASE + | FF::BMI1 + | FF::HLE + | FF::AVX2 + | FF::SMEP + | FF::BMI2 + | FF::ERMS + | FF::INVPCID + | FF::RTM + | FF::RDSEED + | FF::ADX + | FF::SMAP + | FF::CLWB + | FF::AVX512F + | FF::AVX512DQ + | FF::AVX512BW + | FF::AVX512CD + | FF::AVX512VL + | FF::CLFLUSHOPT, + ecx_7_0: FF::PKU | FF::AVX512VNNI, + edx_7_0: FF::SPEC_CTRL | FF::SSBD, + eax_0dh: FF::XSAVEOPT | FF::XSAVEC | FF::XGETBV1, + } + } + + pub(super) const fn into_cpuid_entries(self) -> [CpuIdEntry; 4] { + let Self { + edx_1, + ecx_1, + edx_8000_0001h, + ecx_8000_0001h, + ebx_7_0, + ecx_7_0, + edx_7_0, + eax_0dh, + } = self; + [ + edx_1.join(ecx_1), + edx_8000_0001h.join(ecx_8000_0001h), + ebx_7_0.join_three(ecx_7_0, edx_7_0), + eax_0dh.into_entry(), + ] + } +} diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 92f826d8ad..cbd6ea90b7 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -7,10 +7,12 @@ // 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_profiles; pub mod interrupts; pub mod layout; mod mpspec; mod mptable; + pub mod regs; use std::collections::BTreeMap; use std::mem; @@ -380,7 +382,7 @@ cpuid_flag_constants!( ); cpuid_flag_constants!( CpuIdFeatureFlags<1, 0, { CpuidReg::ECX as u8 }>, - PNI /* Intel,AMD sse3 */, PCLMULQDQ, DTES64, MONITOR, + SSE3 /* Intel PNI,AMD sse3 */, PCLMULQDQ, DTES64, MONITOR, DS_CPL, VMX, SMX, EST, TM2, SSSE3, CID, "NULL", FMA, CX16, XTPR, PDCM, From 3dd61f6b987a03ad10a4251bbb0aa54523ec847f Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 30 Jul 2025 16:59:29 +0200 Subject: [PATCH 06/24] Intersect feature flags --- arch/src/x86_64/cpu_profiles.rs | 18 +++--- arch/src/x86_64/mod.rs | 107 ++++++++++++++++---------------- 2 files changed, 63 insertions(+), 62 deletions(-) diff --git a/arch/src/x86_64/cpu_profiles.rs b/arch/src/x86_64/cpu_profiles.rs index 2d7c5f5d0b..f754bb8e1f 100644 --- a/arch/src/x86_64/cpu_profiles.rs +++ b/arch/src/x86_64/cpu_profiles.rs @@ -85,7 +85,9 @@ impl CascadeLakeServerV1CpuIdFeatures { } } - pub(super) const fn into_cpuid_entries(self) -> [CpuIdEntry; 4] { + /// Restricts the given entries by performing bitwise intersections of registers + /// per set of matching parameters. + pub(super) fn restrict(self, cpuid: &mut [CpuIdEntry]) { let Self { edx_1, ecx_1, @@ -96,11 +98,13 @@ impl CascadeLakeServerV1CpuIdFeatures { edx_7_0, eax_0dh, } = self; - [ - edx_1.join(ecx_1), - edx_8000_0001h.join(ecx_8000_0001h), - ebx_7_0.join_three(ecx_7_0, edx_7_0), - eax_0dh.into_entry(), - ] + edx_1.intersect_matching(cpuid); + ecx_1.intersect_matching(cpuid); + edx_8000_0001h.intersect_matching(cpuid); + ecx_8000_0001h.intersect_matching(cpuid); + ebx_7_0.intersect_matching(cpuid); + ecx_7_0.intersect_matching(cpuid); + edx_7_0.intersect_matching(cpuid); + eax_0dh.intersect_matching(cpuid); } } diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index cbd6ea90b7..71419ab13f 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -310,65 +310,62 @@ impl std::ops::Not } impl CpuIdFeatureFlags { - const fn into_entry(self) -> CpuIdEntry { - // Unfortunately we cannot use enums as const generic parameters as of now, hence we need - // this workaround - CpuIdEntry { - function: FUNCTION, - index: INDEX, - flags: CPUID_FLAG_VALID_INDEX, - eax: if REG == { CpuidReg::EAX as u8 } { - self.0 + fn intersect_matching(&self, cpuid: &mut [CpuIdEntry]) { + if let Some(entry) = cpuid.iter_mut().find(|entry| { + entry.function == FUNCTION + && entry.index == INDEX + && entry.flags == CPUID_FLAG_VALID_INDEX + }) { + 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 { - 0 - }, - ebx: if REG == { CpuidReg::EBX as u8 } { - self.0 - } else { - 0 - }, - ecx: if REG == { CpuidReg::ECX as u8 } { - self.0 - } else { - 0 - }, - edx: if REG == { CpuidReg::EDX as u8 } { - self.0 - } else { - 0 - }, + // Unfortunately we cannot use enums as const generic parameters yet, hence we check for this + // here. + error!("BUG: CpuIdFeatureFlags constructed with invalid register value"); + } + 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; + } + // TODO: Use a proper register name rather than REG and consider returning this as an error instead of logging here + 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: {:?} in the register {:?} + even though the specified restriction permits it. + ", + entry.function, + entry.index, + entry.flags, + bit_positions, + REG + ); + } } } - const fn join( - self, - other: CpuIdFeatureFlags, - ) -> CpuIdEntry { - let mut entry_self = self.into_entry(); - let entry_other = other.into_entry(); - entry_self.eax |= entry_other.eax; - entry_self.ebx |= entry_other.ebx; - entry_self.ecx |= entry_other.ecx; - entry_self.edx |= entry_other.edx; - // Note that we don't meed tp worry about FUNCTION and INDEX matching for the two entries as that - // is already taken care of by the type system. - entry_self - } - const fn join_three( - self, - second: CpuIdFeatureFlags, - third: CpuIdFeatureFlags, - ) -> CpuIdEntry { - let mut join = self.join(second); - let third_as_entry = third.into_entry(); - join.eax |= third_as_entry.eax; - join.ebx |= third_as_entry.ebx; - join.ecx |= third_as_entry.ecx; - join.edx |= third_as_entry.edx; - // Note that we don't meed tp worry about FUNCTION and INDEX matching for the three entries as that - // is already taken care of by the type system. - join - } } + cpuid_flag_constants!( CpuIdFeatureFlags<1, 0, { CpuidReg::EDX as u8 }>, FPU, VME, DE, PSE, From 659b194af3461eb4ef051239fbe32d8b03bc9068 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Thu, 31 Jul 2025 09:47:37 +0200 Subject: [PATCH 07/24] Move CpuProfile into arch/src/lib.rs --- arch/src/lib.rs | 8 ++++++++ .../{cpu_profiles.rs => cpu_profile_feature_flags.rs} | 0 arch/src/x86_64/mod.rs | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) rename arch/src/x86_64/{cpu_profiles.rs => cpu_profile_feature_flags.rs} (100%) diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 333a65d9c4..4379936089 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -59,6 +59,14 @@ pub enum Error { /// Type for returning public functions outcome. pub type Result = result::Result; +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum CpuProfile { + #[default] + Host, + #[cfg(target_arch = "x86_64")] + CascadeLakeServerV1, +} + /// Type for memory region types. #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] pub enum RegionType { diff --git a/arch/src/x86_64/cpu_profiles.rs b/arch/src/x86_64/cpu_profile_feature_flags.rs similarity index 100% rename from arch/src/x86_64/cpu_profiles.rs rename to arch/src/x86_64/cpu_profile_feature_flags.rs diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 71419ab13f..7357827f82 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -7,7 +7,7 @@ // 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_profiles; +mod cpu_profile_feature_flags; pub mod interrupts; pub mod layout; mod mpspec; From bf5955c56b346677871c037c52c66f033c1f6be6 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Thu, 31 Jul 2025 20:12:49 +0200 Subject: [PATCH 08/24] Make it compile with warnings --- arch/src/lib.rs | 15 +++++++++++++++ arch/src/x86_64/mod.rs | 3 ++- vmm/src/config.rs | 10 +++++++++- vmm/src/cpu.rs | 4 ++++ vmm/src/lib.rs | 13 ++++++++++++- vmm/src/vm.rs | 10 +++++----- vmm/src/vm_config.rs | 4 ++++ 7 files changed, 51 insertions(+), 8 deletions(-) diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 4379936089..e1ecaccb2b 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -12,6 +12,7 @@ extern crate log; use std::collections::BTreeMap; +use std::str::FromStr; use std::sync::Arc; use std::{fmt, result}; @@ -60,6 +61,7 @@ pub enum Error { pub type Result = result::Result; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] pub enum CpuProfile { #[default] Host, @@ -67,6 +69,19 @@ pub enum CpuProfile { CascadeLakeServerV1, } +// TODO: Probably better to derive this +impl FromStr for CpuProfile { + // TODO: Use a proper error type + type Err = &'static str; + fn from_str(s: &str) -> result::Result { + match s { + "host" => Ok(Self::Host), + "cascadelake-server-v1" => Ok(Self::CascadeLakeServerV1), + _ => Err("invalid cpu profile"), + } + } +} + /// Type for memory region types. #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] pub enum RegionType { diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 7357827f82..6fd3a8dbea 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -29,7 +29,7 @@ use vm_memory::{ GuestMemoryRegion, GuestUsize, }; -use crate::{GuestMemoryMmap, InitramfsConfig, RegionType}; +use crate::{CpuProfile, GuestMemoryMmap, InitramfsConfig, RegionType}; mod smbios; use std::arch::x86_64; #[cfg(feature = "tdx")] @@ -129,6 +129,7 @@ pub struct CpuidConfig { #[cfg(feature = "tdx")] pub tdx: bool, pub amx: bool, + pub profile: CpuProfile, } #[derive(Debug, Error)] 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..7527888b45 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2773,11 +2773,10 @@ 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; arch::generate_common_cpuid( &self.hypervisor, &arch::CpuidConfig { @@ -2787,6 +2786,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(), } } } From f48b1e7ad1bf58901f9e0fa469ebad166aca537e Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Thu, 31 Jul 2025 20:56:21 +0200 Subject: [PATCH 09/24] Implement the restriction, improve documentation and some minor cleanup --- arch/src/x86_64/cpu_profile_feature_flags.rs | 2 + arch/src/x86_64/mod.rs | 138 ++++++++++++------- 2 files changed, 89 insertions(+), 51 deletions(-) diff --git a/arch/src/x86_64/cpu_profile_feature_flags.rs b/arch/src/x86_64/cpu_profile_feature_flags.rs index f754bb8e1f..842b37754d 100644 --- a/arch/src/x86_64/cpu_profile_feature_flags.rs +++ b/arch/src/x86_64/cpu_profile_feature_flags.rs @@ -88,6 +88,8 @@ impl CascadeLakeServerV1CpuIdFeatures { /// Restricts the given entries by performing bitwise intersections of registers /// per set of matching parameters. pub(super) fn restrict(self, cpuid: &mut [CpuIdEntry]) { + // NOTE: This might get a bit repetetive when we get more structs in this module. + // Might be worth introducing a proc macro for this at some point... let Self { edx_1, ecx_1, diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 6fd3a8dbea..030986d51e 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -241,57 +241,8 @@ pub enum CpuidReg { EDX, } -macro_rules! cpuid_flag_constants { - //============ Possible starting points ===========// - ($t:ty, $name:ident) => { - impl $t { - const $name: Self = Self(1); - } - }; - ($t:ty, "NULL", $($tail:tt)*) => { - impl $t { - cpuid_flag_constants!(1, $($tail)*); - } - }; - ($t:ty, $name:ident, $($tail:tt)*) => { - impl $t { - const $name: Self = Self(1); - cpuid_flag_constants!(1, $($tail)*); - } - }; - - //============ Possible most deeply nested macro invocation ===========// - ($i:expr, $name:ident) => { - const $name: Self = Self(1 << $i); - }; - - ($i:expr, "NULL") => {}; - - ("NULL") => {}; - - () => {}; - - // Also permit ending with a trailing , - ($i:expr, $name:ident,) => { - const $name: Self = Self(1 << $i); - }; - - ($i:expr, "NULL",) => {}; - - ("NULL",) => {}; - - (,) => {}; - - // ============ Possible continuations that continue the recursion ===== // - ($i:expr, "NULL", $($tail:tt)+) => { - cpuid_flag_constants!($i + 1, $($tail)*); - }; - ($i:expr, $name:ident, $($tail:tt)+) => { - const $name: Self = Self(1 << $i); - cpuid_flag_constants!($i + 1, $($tail)*); - }; - -} +/// A bitset of CPUID feature flags for a given leaf, sub-leaf and register triple +/// (or function, index, register in KVM terms). struct CpuIdFeatureFlags(u32); impl std::ops::BitOr for CpuIdFeatureFlags @@ -315,6 +266,8 @@ impl CpuIdFeatureFlags CpuIdFeatureFlags CpuIdFeatureFlags { + impl $t { + #[allow(dead_code)] + const $name: Self = Self(1); + } + }; + ($t:ty, "NULL", $($tail:tt)*) => { + impl $t { + cpuid_flag_constants!(1, $($tail)*); + } + }; + ($t:ty, $name:ident, $($tail:tt)*) => { + impl $t { + #[allow(dead_code)] + const $name: Self = Self(1); + cpuid_flag_constants!(1, $($tail)*); + } + }; + + //============ Possible most deeply nested macro invocation ===========// + ($i:expr, $name:ident) => { + #[allow(dead_code)] + const $name: Self = Self(1 << $i); + }; + + ($i:expr, "NULL") => {}; + + ("NULL") => {}; + + () => {}; + + // Also permit ending with a trailing , + ($i:expr, $name:ident,) => { + #[allow(dead_code)] + const $name: Self = Self(1 << $i); + }; + + ($i:expr, "NULL",) => {}; + + ("NULL",) => {}; + + (,) => {}; + + // ============ Possible continuations that continue the recursion ===== // + ($i:expr, "NULL", $($tail:tt)+) => { + cpuid_flag_constants!($i + 1, $($tail)*); + }; + ($i:expr, $name:ident, $($tail:tt)+) => { + #[allow(dead_code)] + const $name: Self = Self(1 << $i); + cpuid_flag_constants!($i + 1, $($tail)*); + }; + +} + cpuid_flag_constants!( CpuIdFeatureFlags<1, 0, { CpuidReg::EDX as u8 }>, FPU, VME, DE, PSE, @@ -895,6 +922,15 @@ pub fn generate_common_cpuid( .get_supported_cpuid() .map_err(Error::CpuidGetSupported)?; + // Restrict the supported CPUID to the features supported by the cpu profile + match config.profile { + CpuProfile::Host => { + // When this is set we do nothing + } + CpuProfile::CascadeLakeServerV1 => { + cpu_profile_feature_flags::CascadeLakeServerV1CpuIdFeatures::new().restrict(&mut cpuid); + } + } CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches); if let Some(sgx_epc_sections) = &config.sgx_epc_sections { From bce9f2912b347b4cc0a477fb79c631040ad19914 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Thu, 31 Jul 2025 21:22:36 +0200 Subject: [PATCH 10/24] Adjust documentation --- arch/src/x86_64/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 030986d51e..076dedc5a4 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -327,7 +327,7 @@ impl CpuIdFeatureFlags Date: Thu, 31 Jul 2025 21:30:28 +0200 Subject: [PATCH 11/24] Fix missing conditional compilation --- arch/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/src/lib.rs b/arch/src/lib.rs index e1ecaccb2b..16b393cc2f 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -76,6 +76,7 @@ impl FromStr for CpuProfile { fn from_str(s: &str) -> result::Result { match s { "host" => Ok(Self::Host), + #[cfg(target_arch = "x86_64")] "cascadelake-server-v1" => Ok(Self::CascadeLakeServerV1), _ => Err("invalid cpu profile"), } From cc2e355fd11598663dd0d718db1cdd8267e3c95c Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 1 Aug 2025 09:53:43 +0200 Subject: [PATCH 12/24] Fix name --- arch/src/lib.rs | 4 ++-- arch/src/x86_64/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 16b393cc2f..5190e120b1 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -66,7 +66,7 @@ pub enum CpuProfile { #[default] Host, #[cfg(target_arch = "x86_64")] - CascadeLakeServerV1, + CascadelakeServerV1, } // TODO: Probably better to derive this @@ -77,7 +77,7 @@ impl FromStr for CpuProfile { match s { "host" => Ok(Self::Host), #[cfg(target_arch = "x86_64")] - "cascadelake-server-v1" => Ok(Self::CascadeLakeServerV1), + "cascadelake-server-v1" => Ok(Self::CascadelakeServerV1), _ => Err("invalid cpu profile"), } } diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 076dedc5a4..cf83b18d0e 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -927,7 +927,7 @@ pub fn generate_common_cpuid( CpuProfile::Host => { // When this is set we do nothing } - CpuProfile::CascadeLakeServerV1 => { + CpuProfile::CascadelakeServerV1 => { cpu_profile_feature_flags::CascadeLakeServerV1CpuIdFeatures::new().restrict(&mut cpuid); } } From 4a23b61150882ce4093d1f33055ea6711ff7c7a9 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 1 Aug 2025 10:10:20 +0200 Subject: [PATCH 13/24] Include missing field --- src/main.rs | 1 + 1 file changed, 1 insertion(+) 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, From 0f75b3ebc18aca17fa022300de596ad3c1419506 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 1 Aug 2025 10:33:42 +0200 Subject: [PATCH 14/24] Shorten macro --- arch/src/x86_64/mod.rs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index cf83b18d0e..33f6dae3a0 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -359,28 +359,16 @@ macro_rules! cpuid_flag_constants { }; //============ Possible most deeply nested macro invocation ===========// - ($i:expr, $name:ident) => { + ($i:expr, $name:ident $(,)*) => { #[allow(dead_code)] const $name: Self = Self(1 << $i); }; - ($i:expr, "NULL") => {}; + ($i:expr, "NULL" $(,)*) => {}; - ("NULL") => {}; + ("NULL" $(,)*) => {}; - () => {}; - - // Also permit ending with a trailing , - ($i:expr, $name:ident,) => { - #[allow(dead_code)] - const $name: Self = Self(1 << $i); - }; - - ($i:expr, "NULL",) => {}; - - ("NULL",) => {}; - - (,) => {}; + ($(,)*) => {}; // ============ Possible continuations that continue the recursion ===== // ($i:expr, "NULL", $($tail:tt)+) => { From 618f16952b47773d0613162c1bf35f18a20a0a28 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 1 Aug 2025 10:40:13 +0200 Subject: [PATCH 15/24] Simplify macro further --- arch/src/x86_64/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 33f6dae3a0..44d78b5bd8 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -366,10 +366,6 @@ macro_rules! cpuid_flag_constants { ($i:expr, "NULL" $(,)*) => {}; - ("NULL" $(,)*) => {}; - - ($(,)*) => {}; - // ============ Possible continuations that continue the recursion ===== // ($i:expr, "NULL", $($tail:tt)+) => { cpuid_flag_constants!($i + 1, $($tail)*); From 8218816505120cba8f52c5d8924830a352db5c27 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 1 Aug 2025 13:02:44 +0200 Subject: [PATCH 16/24] Drop guard early --- vmm/src/vm.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 7527888b45..2e00794134 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -2777,6 +2777,7 @@ impl Snapshottable for Vm { 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 { From 96ffe84b4f83a1315b68212e3ee71d29d01987ea Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 6 Aug 2025 13:58:16 +0200 Subject: [PATCH 17/24] Make inner value public --- arch/src/x86_64/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 44d78b5bd8..1e9c2450d2 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -243,7 +243,7 @@ pub enum CpuidReg { /// A bitset of CPUID feature flags for a given leaf, sub-leaf and register triple /// (or function, index, register in KVM terms). -struct CpuIdFeatureFlags(u32); +struct CpuIdFeatureFlags(pub u32); impl std::ops::BitOr for CpuIdFeatureFlags { From 89a7ab34310f052c7830b8c9f803a4832fa74f38 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 6 Aug 2025 13:59:55 +0200 Subject: [PATCH 18/24] Rename CpuIdFeatureFlags --- arch/src/x86_64/cpu_profile_feature_flags.rs | 20 +++++++------- arch/src/x86_64/mod.rs | 28 +++++++++++--------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/arch/src/x86_64/cpu_profile_feature_flags.rs b/arch/src/x86_64/cpu_profile_feature_flags.rs index 842b37754d..1b4819ebeb 100644 --- a/arch/src/x86_64/cpu_profile_feature_flags.rs +++ b/arch/src/x86_64/cpu_profile_feature_flags.rs @@ -1,21 +1,21 @@ use hypervisor::arch::x86::CpuIdEntry; -use super::{CpuIdFeatureFlags, CpuidReg}; +use super::{CpuIdEntryRegister, CpuidReg}; pub(super) struct CascadeLakeServerV1CpuIdFeatures { - edx_1: CpuIdFeatureFlags<1, 0, { CpuidReg::EDX as u8 }>, - ecx_1: CpuIdFeatureFlags<1, 0, { CpuidReg::ECX as u8 }>, - edx_8000_0001h: CpuIdFeatureFlags<0x8000_0001, 0, { CpuidReg::EDX as u8 }>, - ecx_8000_0001h: CpuIdFeatureFlags<0x8000_0001, 0, { CpuidReg::ECX as u8 }>, - ebx_7_0: CpuIdFeatureFlags<7, 0, { CpuidReg::EBX as u8 }>, - ecx_7_0: CpuIdFeatureFlags<7, 0, { CpuidReg::ECX as u8 }>, - edx_7_0: CpuIdFeatureFlags<7, 0, { CpuidReg::EDX as u8 }>, - eax_0dh: CpuIdFeatureFlags<0xd, 1, { CpuidReg::EAX as u8 }>, + edx_1: CpuIdEntryRegister<1, 0, { CpuidReg::EDX as u8 }>, + ecx_1: CpuIdEntryRegister<1, 0, { CpuidReg::ECX as u8 }>, + edx_8000_0001h: CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::EDX as u8 }>, + ecx_8000_0001h: CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::ECX as u8 }>, + ebx_7_0: CpuIdEntryRegister<7, 0, { CpuidReg::EBX as u8 }>, + ecx_7_0: CpuIdEntryRegister<7, 0, { CpuidReg::ECX as u8 }>, + edx_7_0: CpuIdEntryRegister<7, 0, { CpuidReg::EDX as u8 }>, + eax_0dh: CpuIdEntryRegister<0xd, 1, { CpuidReg::EAX as u8 }>, } impl CascadeLakeServerV1CpuIdFeatures { pub(super) fn new() -> Self { - use CpuIdFeatureFlags as FF; + use CpuIdEntryRegister as FF; Self { edx_1: FF::VME | FF::SSE2 diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 1e9c2450d2..a063c6f8a4 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -243,9 +243,9 @@ pub enum CpuidReg { /// A bitset of CPUID feature flags for a given leaf, sub-leaf and register triple /// (or function, index, register in KVM terms). -struct CpuIdFeatureFlags(pub u32); +pub struct CpuIdEntryRegister(pub u32); impl std::ops::BitOr - for CpuIdFeatureFlags + for CpuIdEntryRegister { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { @@ -253,7 +253,7 @@ impl std::ops::BitOr } } impl std::ops::Not - for CpuIdFeatureFlags + for CpuIdEntryRegister { type Output = Self; fn not(self) -> Self::Output { @@ -261,7 +261,9 @@ impl std::ops::Not } } -impl CpuIdFeatureFlags { +impl + CpuIdEntryRegister +{ fn intersect_matching(&self, cpuid: &mut [CpuIdEntry]) { if let Some(entry) = cpuid.iter_mut().find(|entry| { entry.function == FUNCTION @@ -379,7 +381,7 @@ macro_rules! cpuid_flag_constants { } cpuid_flag_constants!( - CpuIdFeatureFlags<1, 0, { CpuidReg::EDX as u8 }>, + CpuIdEntryRegister<1, 0, { CpuidReg::EDX as u8 }>, FPU, VME, DE, PSE, TSC, MSR, PAE, MCE, CX8, APIC, "NULL", SEP, @@ -390,7 +392,7 @@ cpuid_flag_constants!( HT /* Intel htt */, TM, IA64, PBE, ); cpuid_flag_constants!( - CpuIdFeatureFlags<1, 0, { CpuidReg::ECX as u8 }>, + CpuIdEntryRegister<1, 0, { CpuidReg::ECX as u8 }>, SSE3 /* Intel PNI,AMD sse3 */, PCLMULQDQ, DTES64, MONITOR, DS_CPL, VMX, SMX, EST, TM2, SSSE3, CID, "NULL", @@ -401,7 +403,7 @@ cpuid_flag_constants!( AVX, F16C, RDRAND, HYPERVISOR, ); cpuid_flag_constants!( - CpuIdFeatureFlags<0x8000_0001, 0, { CpuidReg::EDX as u8}>, + CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::EDX as u8}>, "NULL" /* fpu */, "NULL" /* vme */, "NULL" /* de */, "NULL" /* pse */, "NULL" /* tsc */, "NULL" /* msr */, "NULL" /* pae */, "NULL" /* mce */, "NULL" /* cx8 */, "NULL" /* apic */, "NULL", SYSCALL, @@ -413,7 +415,7 @@ cpuid_flag_constants!( ); cpuid_flag_constants!( - CpuIdFeatureFlags<0x8000_0001, 0, { CpuidReg::ECX as u8}>, + CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::ECX as u8}>, LAHF_LM, CMP_LEGACY, SVM, EXTAPIC, CR8LEGACY, ABM, SSE4A, MISALIGNSSE, PREFETCH_3DNOW, OSVW, IBS, XOP, @@ -424,7 +426,7 @@ cpuid_flag_constants!( "NULL", "NULL", "NULL", "NULL", ); cpuid_flag_constants!( - CpuIdFeatureFlags<7, 0, {CpuidReg::EBX as u8}>, + CpuIdEntryRegister<7, 0, {CpuidReg::EBX as u8}>, FSGSBASE, TSC_ADJUST, SGX, BMI1, HLE, AVX2, FDP_EXCPTN_ONLY, SMEP, BMI2, ERMS, INVPCID, RTM, @@ -435,7 +437,7 @@ cpuid_flag_constants!( AVX512CD, SHA_NI, AVX512BW, AVX512VL, ); cpuid_flag_constants!( - CpuIdFeatureFlags<7, 0, {CpuidReg::ECX as u8}>, + CpuIdEntryRegister<7, 0, {CpuidReg::ECX as u8}>, "NULL", AVX512VBMI, UMIP, PKU, "NULL" /* ospke */, WAITPKG, AVX512VBMI2, "NULL", GFNI, VAES, VPCLMULQDQ, AVX512VNNI, @@ -447,7 +449,7 @@ cpuid_flag_constants!( ); cpuid_flag_constants!( - CpuIdFeatureFlags<7, 0, {CpuidReg::EDX as u8}>, + CpuIdEntryRegister<7, 0, {CpuidReg::EDX as u8}>, "NULL", "NULL", AVX512_4VNNIW, AVX512_4FMAPS, FSRM, "NULL", "NULL", "NULL", AVX512_VP2INTERSECT, "NULL", MD_CLEAR, "NULL", @@ -459,7 +461,7 @@ cpuid_flag_constants!( ); cpuid_flag_constants!( - CpuIdFeatureFlags<0xd,1, {CpuidReg::EAX as u8}>, + CpuIdEntryRegister<0xd,1, {CpuidReg::EAX as u8}>, XSAVEOPT, XSAVEC, XGETBV1, XSAVES, XFD, "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", @@ -471,7 +473,7 @@ cpuid_flag_constants!( ); cpuid_flag_constants!( - CpuIdFeatureFlags<6, 0, {CpuidReg::EAX as u8}>, + CpuIdEntryRegister<6, 0, {CpuidReg::EAX as u8}>, "NULL", "NULL", ARAT, "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", From 389f5f6498346f92acb67f8f2a8a2246cf567be5 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Thu, 7 Aug 2025 17:49:28 +0200 Subject: [PATCH 19/24] Refactor: Less structs. Use one complete struct with all feature registers as fields --- arch/src/lib.rs | 7 +- arch/src/x86_64/cpu_profile_feature_flags.rs | 179 ++++++++----------- arch/src/x86_64/mod.rs | 140 ++++++++++++--- 3 files changed, 201 insertions(+), 125 deletions(-) diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 5190e120b1..ffe26be1c0 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -60,13 +60,16 @@ pub enum Error { /// Type for returning public functions outcome. pub type Result = result::Result; +#[cfg(target_arch = "x86_64")] +pub use crate::x86_64::CpuProfile; +// Only concentrate on X86_64 for now, but keep it here to simplify imports in other crates, regardless of target. +// In the future we may add definitions for more target arches, and this can still be used as a fallback +#[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, - #[cfg(target_arch = "x86_64")] - CascadelakeServerV1, } // TODO: Probably better to derive this diff --git a/arch/src/x86_64/cpu_profile_feature_flags.rs b/arch/src/x86_64/cpu_profile_feature_flags.rs index 1b4819ebeb..3d05f91c12 100644 --- a/arch/src/x86_64/cpu_profile_feature_flags.rs +++ b/arch/src/x86_64/cpu_profile_feature_flags.rs @@ -1,112 +1,89 @@ -use hypervisor::arch::x86::CpuIdEntry; +use super::{CpuIdEntryRegister, CpuIdFeatureFlags}; -use super::{CpuIdEntryRegister, CpuidReg}; - -pub(super) struct CascadeLakeServerV1CpuIdFeatures { - edx_1: CpuIdEntryRegister<1, 0, { CpuidReg::EDX as u8 }>, - ecx_1: CpuIdEntryRegister<1, 0, { CpuidReg::ECX as u8 }>, - edx_8000_0001h: CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::EDX as u8 }>, - ecx_8000_0001h: CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::ECX as u8 }>, - ebx_7_0: CpuIdEntryRegister<7, 0, { CpuidReg::EBX as u8 }>, - ecx_7_0: CpuIdEntryRegister<7, 0, { CpuidReg::ECX as u8 }>, - edx_7_0: CpuIdEntryRegister<7, 0, { CpuidReg::EDX as u8 }>, - eax_0dh: CpuIdEntryRegister<0xd, 1, { CpuidReg::EAX as u8 }>, -} - -impl CascadeLakeServerV1CpuIdFeatures { - pub(super) fn new() -> Self { +impl CpuIdFeatureFlags { + pub const fn intel_cascadelake_v1() -> Self { use CpuIdEntryRegister as FF; + Self { edx_1: FF::VME - | FF::SSE2 - | FF::SSE - | FF::FXSR - | FF::MMX - | FF::CLFLUSH - | FF::PSE36 - | FF::PAT - | FF::CMOV - | FF::MCA - | FF::PGE - | FF::MTRR - | FF::SEP - | FF::APIC - | FF::CX8 - | FF::MCE - | FF::PAE - | FF::MSR - | FF::TSC - | FF::PSE - | FF::DE - | FF::FPU, + .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 - | FF::XSAVE - | FF::AES - | FF::POPCNT - | FF::X2APIC - | FF::SSE4_2 - | FF::SSE4_1 - | FF::CX16 - | FF::SSSE3 - | FF::PCLMULQDQ - | FF::SSE3 - | FF::TSC_DEADLINE - | FF::FMA - | FF::MOVBE - | FF::PCID - | FF::F16C - | FF::RDRAND, + .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 | FF::PDPE1GB | FF::RDTSCP | FF::NX | FF::SYSCALL, - ecx_8000_0001h: FF::ABM | FF::LAHF_LM | FF::PREFETCH_3DNOW, + 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 - | FF::BMI1 - | FF::HLE - | FF::AVX2 - | FF::SMEP - | FF::BMI2 - | FF::ERMS - | FF::INVPCID - | FF::RTM - | FF::RDSEED - | FF::ADX - | FF::SMAP - | FF::CLWB - | FF::AVX512F - | FF::AVX512DQ - | FF::AVX512BW - | FF::AVX512CD - | FF::AVX512VL - | FF::CLFLUSHOPT, - ecx_7_0: FF::PKU | FF::AVX512VNNI, - edx_7_0: FF::SPEC_CTRL | FF::SSBD, - eax_0dh: FF::XSAVEOPT | FF::XSAVEC | FF::XGETBV1, + .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, + edx_8000_0007h: FF::NULL, + ebx_8000_0008h: FF::NULL, + edx_8000_000ah: FF::NULL, + eax_8000_0021h: FF::NULL, } } - - /// Restricts the given entries by performing bitwise intersections of registers - /// per set of matching parameters. - pub(super) fn restrict(self, cpuid: &mut [CpuIdEntry]) { - // NOTE: This might get a bit repetetive when we get more structs in this module. - // Might be worth introducing a proc macro for this at some point... - let Self { - edx_1, - ecx_1, - edx_8000_0001h, - ecx_8000_0001h, - ebx_7_0, - ecx_7_0, - edx_7_0, - eax_0dh, - } = self; - edx_1.intersect_matching(cpuid); - ecx_1.intersect_matching(cpuid); - edx_8000_0001h.intersect_matching(cpuid); - ecx_8000_0001h.intersect_matching(cpuid); - ebx_7_0.intersect_matching(cpuid); - ecx_7_0.intersect_matching(cpuid); - edx_7_0.intersect_matching(cpuid); - eax_0dh.intersect_matching(cpuid); - } } diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index a063c6f8a4..adbae8a1f0 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -29,7 +29,7 @@ use vm_memory::{ GuestMemoryRegion, GuestUsize, }; -use crate::{CpuProfile, GuestMemoryMmap, InitramfsConfig, RegionType}; +use crate::{GuestMemoryMmap, InitramfsConfig, RegionType}; mod smbios; use std::arch::x86_64; #[cfg(feature = "tdx")] @@ -122,6 +122,15 @@ impl SgxEpcRegion { } } +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum CpuProfile { + #[default] + Host, + #[cfg(target_arch = "x86_64")] + CascadelakeServerV1, +} + pub struct CpuidConfig { pub sgx_epc_sections: Option>, pub phys_bits: u8, @@ -243,35 +252,33 @@ pub enum 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(pub u32); -impl std::ops::BitOr - for CpuIdEntryRegister -{ - type Output = Self; - fn bitor(self, rhs: Self) -> Self::Output { - Self(self.0 | rhs.0) - } -} -impl std::ops::Not - for CpuIdEntryRegister -{ - type Output = Self; - fn not(self) -> Self::Output { - Self(!self.0) - } -} +pub struct CpuIdEntryRegister(u32); impl CpuIdEntryRegister { + 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]) { - if let Some(entry) = cpuid.iter_mut().find(|entry| { + 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; @@ -320,7 +327,8 @@ impl REG ); } - } else { + } + if (!found_matching) && (self.0 != 0) { // TODO: Better log or return an error here log::warn!("no entry matched"); } @@ -485,6 +493,85 @@ cpuid_flag_constants!( ); +/// 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 handled separately for now) +/// - 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_feature_flags`](crate::x86_64::cpu_profile_feature_flags). +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 { + 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); + } +} pub struct CpuidPatch { pub function: u32, pub index: u32, @@ -908,15 +995,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 + // 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 => { - cpu_profile_feature_flags::CascadeLakeServerV1CpuIdFeatures::new().restrict(&mut cpuid); + CpuIdFeatureFlags::intel_cascadelake_v1().restrict(&mut cpuid); } } + CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches); if let Some(sgx_epc_sections) = &config.sgx_epc_sections { From 1964fbff9ecbc269452dd6576cc48016b22a7e22 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 8 Aug 2025 15:38:52 +0200 Subject: [PATCH 20/24] Refactoring --- arch/src/lib.rs | 23 +- .../cpuid_feature_flags_impl.rs} | 2 +- arch/src/x86_64/cpu_profile/mod.rs | 8 + arch/src/x86_64/cpuid_feature_flags.rs | 327 +++++++++++++++++ arch/src/x86_64/mod.rs | 336 +----------------- 5 files changed, 350 insertions(+), 346 deletions(-) rename arch/src/x86_64/{cpu_profile_feature_flags.rs => cpu_profile/cpuid_feature_flags_impl.rs} (97%) create mode 100644 arch/src/x86_64/cpu_profile/mod.rs create mode 100644 arch/src/x86_64/cpuid_feature_flags.rs diff --git a/arch/src/lib.rs b/arch/src/lib.rs index ffe26be1c0..2f3f2b0481 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -16,7 +16,7 @@ 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")] @@ -61,9 +61,7 @@ pub enum Error { pub type Result = result::Result; #[cfg(target_arch = "x86_64")] -pub use crate::x86_64::CpuProfile; -// Only concentrate on X86_64 for now, but keep it here to simplify imports in other crates, regardless of target. -// In the future we may add definitions for more target arches, and this can still be used as a fallback +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")] @@ -72,17 +70,16 @@ pub enum CpuProfile { Host, } -// TODO: Probably better to derive this impl FromStr for CpuProfile { - // TODO: Use a proper error type - type Err = &'static str; + type Err = serde::de::value::Error; fn from_str(s: &str) -> result::Result { - match s { - "host" => Ok(Self::Host), - #[cfg(target_arch = "x86_64")] - "cascadelake-server-v1" => Ok(Self::CascadelakeServerV1), - _ => Err("invalid cpu profile"), - } + // 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()) } } diff --git a/arch/src/x86_64/cpu_profile_feature_flags.rs b/arch/src/x86_64/cpu_profile/cpuid_feature_flags_impl.rs similarity index 97% rename from arch/src/x86_64/cpu_profile_feature_flags.rs rename to arch/src/x86_64/cpu_profile/cpuid_feature_flags_impl.rs index 3d05f91c12..4296cd93aa 100644 --- a/arch/src/x86_64/cpu_profile_feature_flags.rs +++ b/arch/src/x86_64/cpu_profile/cpuid_feature_flags_impl.rs @@ -1,4 +1,4 @@ -use super::{CpuIdEntryRegister, CpuIdFeatureFlags}; +use crate::x86_64::cpuid_feature_flags::{CpuIdEntryRegister, CpuIdFeatureFlags}; impl CpuIdFeatureFlags { pub const fn intel_cascadelake_v1() -> Self { 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..6e599b5032 --- /dev/null +++ b/arch/src/x86_64/cpuid_feature_flags.rs @@ -0,0 +1,327 @@ +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 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; + } + // TODO: Use a proper register name rather than REG and consider returning this as an error instead of logging here + 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: {:?} in the register {:?} + even though the specified restriction permits it. + ", + entry.function, + entry.index, + entry.flags, + bit_positions, + REG + ); + } + } + if (!found_matching) && (self.0 != 0) { + // TODO: Better log or return an error here + log::warn!("no entry matched"); + } + } +} + +/// Reduces boilerplate when implementing CpuIdFeatureFlag constants. +/// One passes in a parameterized CpuIdFeatureFlags together with +/// a name per bit in the 32bit bitset. +/// +/// For lower order bits that should not have any associated constant, appearing +/// before a higher order bit that needs a constant, you can simply place a "NULL" +/// in its place. +macro_rules! cpuid_flag_constants { + /* NOTE: We allow dead code within this macro as we may want to use certain + unused constants in the feature when introducing more cpu profiles. + The alternative would be to fill the macro invocations with more "NULL" + entries. + */ + //============ Possible starting points ===========// + ($t:ty, $name:ident) => { + impl $t { + #[allow(dead_code)] + pub const $name: Self = Self(1); + } + }; + ($t:ty, "NULL", $($tail:tt)*) => { + impl $t { + cpuid_flag_constants!(1, $($tail)*); + } + }; + ($t:ty, $name:ident, $($tail:tt)*) => { + impl $t { + #[allow(dead_code)] + pub const $name: Self = Self(1); + cpuid_flag_constants!(1, $($tail)*); + } + }; + + //============ Possible most deeply nested macro invocation ===========// + ($i:expr, $name:ident $(,)*) => { + #[allow(dead_code)] + pub const $name: Self = Self(1 << $i); + }; + + ($i:expr, "NULL" $(,)*) => {}; + + // ============ Possible continuations that continue the recursion ===== // + ($i:expr, "NULL", $($tail:tt)+) => { + cpuid_flag_constants!($i + 1, $($tail)*); + }; + ($i:expr, $name:ident, $($tail:tt)+) => { + #[allow(dead_code)] + pub const $name: Self = Self(1 << $i); + cpuid_flag_constants!($i + 1, $($tail)*); + }; + +} + +cpuid_flag_constants!( + CpuIdEntryRegister<1, 0, { CpuidReg::EDX as u8 }>, + FPU, VME, DE, PSE, + TSC, MSR, PAE, MCE, + CX8, APIC, "NULL", SEP, + MTRR, PGE, MCA, CMOV, + PAT, PSE36, PN /* Intel psn */, CLFLUSH /* Intel clfsh */, + "NULL", DS /* INTEL DTS */, ACPI, MMX, + FXSR, SSE, SSE2, SS, + HT /* Intel htt */, TM, IA64, PBE, +); +cpuid_flag_constants!( + CpuIdEntryRegister<1, 0, { CpuidReg::ECX as u8 }>, + SSE3 /* Intel PNI,AMD sse3 */, PCLMULQDQ, DTES64, MONITOR, + DS_CPL, VMX, SMX, EST, + TM2, SSSE3, CID, "NULL", + FMA, CX16, XTPR, PDCM, + "NULL", PCID, DCA, SSE4_1, + SSE4_2, X2APIC, MOVBE, POPCNT, + TSC_DEADLINE, AES, XSAVE, "NULL" /* osxsave */, + AVX, F16C, RDRAND, HYPERVISOR, +); +cpuid_flag_constants!( + CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::EDX as u8}>, + "NULL" /* fpu */, "NULL" /* vme */, "NULL" /* de */, "NULL" /* pse */, + "NULL" /* tsc */, "NULL" /* msr */, "NULL" /* pae */, "NULL" /* mce */, + "NULL" /* cx8 */, "NULL" /* apic */, "NULL", SYSCALL, + "NULL" /* mtrr */, "NULL" /* pge */, "NULL" /* mca */, "NULL" /* cmov */, + "NULL" /* pat */, "NULL" /* pse36 */, "NULL", "NULL" /* Linux mp */, + NX, "NULL", MMXEXT, "NULL" /* mmx */, + "NULL" /* fxsr */, FXSR_OPT, PDPE1GB, RDTSCP, + "NULL", LM, EXT_3DNOW, FIRST_3DNOW, + +); +cpuid_flag_constants!( + CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::ECX as u8}>, + LAHF_LM, CMP_LEGACY, SVM, EXTAPIC, + CR8LEGACY, ABM, SSE4A, MISALIGNSSE, + PREFETCH_3DNOW, OSVW, IBS, XOP, + SKINIT, WDT, "NULL", LWP, + FMA4, TCE, "NULL", NODEID_MSR, + "NULL", TBM, TOPOEXT, PERFCTR_CORE, + PERFCTR_NB, "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", +); +cpuid_flag_constants!( + CpuIdEntryRegister<7, 0, {CpuidReg::EBX as u8}>, + FSGSBASE, TSC_ADJUST, SGX, BMI1, + HLE, AVX2, FDP_EXCPTN_ONLY, SMEP, + BMI2, ERMS, INVPCID, RTM, + "NULL", ZERO_FCS_FDS, MPX, "NULL", + AVX512F, AVX512DQ, RDSEED, ADX, + SMAP, AVX512IFMA, PCOMMIT, CLFLUSHOPT, + CLWB, INTEL_PT, AVX512PF, AVX512ER, + AVX512CD, SHA_NI, AVX512BW, AVX512VL, +); +cpuid_flag_constants!( + CpuIdEntryRegister<7, 0, {CpuidReg::ECX as u8}>, + "NULL", AVX512VBMI, UMIP, PKU, + "NULL" /* ospke */, WAITPKG, AVX512VBMI2, "NULL", + GFNI, VAES, VPCLMULQDQ, AVX512VNNI, + AVX512BITALG, "NULL", AVX512_VPOPCNTDQ, "NULL", + LA57, "NULL", "NULL", "NULL", + "NULL", "NULL", RDPID, "NULL", + BUS_LOCK_DETECT, CLDEMOTE, "NULL", MOVDIRI, + MOVDIR64B, "NULL", SGXLC, PKS, +); + +cpuid_flag_constants!( + CpuIdEntryRegister<7, 0, {CpuidReg::EDX as u8}>, + "NULL", "NULL", AVX512_4VNNIW, AVX512_4FMAPS, + FSRM, "NULL", "NULL", "NULL", + AVX512_VP2INTERSECT, "NULL", MD_CLEAR, "NULL", + "NULL", "NULL", SERIALIZE, "NULL", + TSX_LDTRK, "NULL", "NULL" /* pconfig */, ARCH_LBR, + "NULL", "NULL", AMX_BF16, AVX512_FP16, + AMX_TILE, AMX_INT8, SPEC_CTRL, STIBP, + FLUSH_L1D, ARCH_CAPABILITIES, CORE_CAPABILITY, SSBD, +); + +cpuid_flag_constants!( + CpuIdEntryRegister<0xd,1, {CpuidReg::EAX as u8}>, + XSAVEOPT, XSAVEC, XGETBV1, XSAVES, + XFD, "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", +); + +cpuid_flag_constants!( + CpuIdEntryRegister<6, 0, {CpuidReg::EAX as u8}>, + "NULL", "NULL", ARAT, "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + +); + +/// 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 handled separately for now) +/// - 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 adbae8a1f0..fb1eef2f0c 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -7,7 +7,8 @@ // 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_feature_flags; +mod cpu_profile; +mod cpuid_feature_flags; pub mod interrupts; pub mod layout; mod mpspec; @@ -17,6 +18,8 @@ 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}; @@ -122,15 +125,6 @@ impl SgxEpcRegion { } } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum CpuProfile { - #[default] - Host, - #[cfg(target_arch = "x86_64")] - CascadelakeServerV1, -} - pub struct CpuidConfig { pub sgx_epc_sections: Option>, pub phys_bits: u8, @@ -250,328 +244,6 @@ pub enum CpuidReg { EDX, } -/// 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 -{ - 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; - } - // TODO: Use a proper register name rather than REG and consider returning this as an error instead of logging here - 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: {:?} in the register {:?} - even though the specified restriction permits it. - ", - entry.function, - entry.index, - entry.flags, - bit_positions, - REG - ); - } - } - if (!found_matching) && (self.0 != 0) { - // TODO: Better log or return an error here - log::warn!("no entry matched"); - } - } -} - -/// Reduces boilerplate when implementing CpuIdFeatureFlag constants. -/// One passes in a parameterized CpuIdFeatureFlags together with -/// a name per bit in the 32bit bitset. -/// -/// For lower order bits that should not have any associated constant, appearing -/// before a higher order bit that needs a constant, you can simply place a "NULL" -/// in its place. -macro_rules! cpuid_flag_constants { - /* NOTE: We allow dead code within this macro as we may want to use certain - unused constants in the feature when introducing more cpu profiles. - The alternative would be to fill the macro invocations with more "NULL" - entries. - */ - //============ Possible starting points ===========// - ($t:ty, $name:ident) => { - impl $t { - #[allow(dead_code)] - const $name: Self = Self(1); - } - }; - ($t:ty, "NULL", $($tail:tt)*) => { - impl $t { - cpuid_flag_constants!(1, $($tail)*); - } - }; - ($t:ty, $name:ident, $($tail:tt)*) => { - impl $t { - #[allow(dead_code)] - const $name: Self = Self(1); - cpuid_flag_constants!(1, $($tail)*); - } - }; - - //============ Possible most deeply nested macro invocation ===========// - ($i:expr, $name:ident $(,)*) => { - #[allow(dead_code)] - const $name: Self = Self(1 << $i); - }; - - ($i:expr, "NULL" $(,)*) => {}; - - // ============ Possible continuations that continue the recursion ===== // - ($i:expr, "NULL", $($tail:tt)+) => { - cpuid_flag_constants!($i + 1, $($tail)*); - }; - ($i:expr, $name:ident, $($tail:tt)+) => { - #[allow(dead_code)] - const $name: Self = Self(1 << $i); - cpuid_flag_constants!($i + 1, $($tail)*); - }; - -} - -cpuid_flag_constants!( - CpuIdEntryRegister<1, 0, { CpuidReg::EDX as u8 }>, - FPU, VME, DE, PSE, - TSC, MSR, PAE, MCE, - CX8, APIC, "NULL", SEP, - MTRR, PGE, MCA, CMOV, - PAT, PSE36, PN /* Intel psn */, CLFLUSH /* Intel clfsh */, - "NULL", DS /* INTEL DTS */, ACPI, MMX, - FXSR, SSE, SSE2, SS, - HT /* Intel htt */, TM, IA64, PBE, -); -cpuid_flag_constants!( - CpuIdEntryRegister<1, 0, { CpuidReg::ECX as u8 }>, - SSE3 /* Intel PNI,AMD sse3 */, PCLMULQDQ, DTES64, MONITOR, - DS_CPL, VMX, SMX, EST, - TM2, SSSE3, CID, "NULL", - FMA, CX16, XTPR, PDCM, - "NULL", PCID, DCA, SSE4_1, - SSE4_2, X2APIC, MOVBE, POPCNT, - TSC_DEADLINE, AES, XSAVE, "NULL" /* osxsave */, - AVX, F16C, RDRAND, HYPERVISOR, -); -cpuid_flag_constants!( - CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::EDX as u8}>, - "NULL" /* fpu */, "NULL" /* vme */, "NULL" /* de */, "NULL" /* pse */, - "NULL" /* tsc */, "NULL" /* msr */, "NULL" /* pae */, "NULL" /* mce */, - "NULL" /* cx8 */, "NULL" /* apic */, "NULL", SYSCALL, - "NULL" /* mtrr */, "NULL" /* pge */, "NULL" /* mca */, "NULL" /* cmov */, - "NULL" /* pat */, "NULL" /* pse36 */, "NULL", "NULL" /* Linux mp */, - NX, "NULL", MMXEXT, "NULL" /* mmx */, - "NULL" /* fxsr */, FXSR_OPT, PDPE1GB, RDTSCP, - "NULL", LM, EXT_3DNOW, FIRST_3DNOW, - -); -cpuid_flag_constants!( - CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::ECX as u8}>, - LAHF_LM, CMP_LEGACY, SVM, EXTAPIC, - CR8LEGACY, ABM, SSE4A, MISALIGNSSE, - PREFETCH_3DNOW, OSVW, IBS, XOP, - SKINIT, WDT, "NULL", LWP, - FMA4, TCE, "NULL", NODEID_MSR, - "NULL", TBM, TOPOEXT, PERFCTR_CORE, - PERFCTR_NB, "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", -); -cpuid_flag_constants!( - CpuIdEntryRegister<7, 0, {CpuidReg::EBX as u8}>, - FSGSBASE, TSC_ADJUST, SGX, BMI1, - HLE, AVX2, FDP_EXCPTN_ONLY, SMEP, - BMI2, ERMS, INVPCID, RTM, - "NULL", ZERO_FCS_FDS, MPX, "NULL", - AVX512F, AVX512DQ, RDSEED, ADX, - SMAP, AVX512IFMA, PCOMMIT, CLFLUSHOPT, - CLWB, INTEL_PT, AVX512PF, AVX512ER, - AVX512CD, SHA_NI, AVX512BW, AVX512VL, -); -cpuid_flag_constants!( - CpuIdEntryRegister<7, 0, {CpuidReg::ECX as u8}>, - "NULL", AVX512VBMI, UMIP, PKU, - "NULL" /* ospke */, WAITPKG, AVX512VBMI2, "NULL", - GFNI, VAES, VPCLMULQDQ, AVX512VNNI, - AVX512BITALG, "NULL", AVX512_VPOPCNTDQ, "NULL", - LA57, "NULL", "NULL", "NULL", - "NULL", "NULL", RDPID, "NULL", - BUS_LOCK_DETECT, CLDEMOTE, "NULL", MOVDIRI, - MOVDIR64B, "NULL", SGXLC, PKS, -); - -cpuid_flag_constants!( - CpuIdEntryRegister<7, 0, {CpuidReg::EDX as u8}>, - "NULL", "NULL", AVX512_4VNNIW, AVX512_4FMAPS, - FSRM, "NULL", "NULL", "NULL", - AVX512_VP2INTERSECT, "NULL", MD_CLEAR, "NULL", - "NULL", "NULL", SERIALIZE, "NULL", - TSX_LDTRK, "NULL", "NULL" /* pconfig */, ARCH_LBR, - "NULL", "NULL", AMX_BF16, AVX512_FP16, - AMX_TILE, AMX_INT8, SPEC_CTRL, STIBP, - FLUSH_L1D, ARCH_CAPABILITIES, CORE_CAPABILITY, SSBD, -); - -cpuid_flag_constants!( - CpuIdEntryRegister<0xd,1, {CpuidReg::EAX as u8}>, - XSAVEOPT, XSAVEC, XGETBV1, XSAVES, - XFD, "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", -); - -cpuid_flag_constants!( - CpuIdEntryRegister<6, 0, {CpuidReg::EAX as u8}>, - "NULL", "NULL", ARAT, "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - -); - -/// 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 handled separately for now) -/// - 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_feature_flags`](crate::x86_64::cpu_profile_feature_flags). -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 { - 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); - } -} pub struct CpuidPatch { pub function: u32, pub index: u32, From 2e50d2c931aaa7fa1b913811b95c4991f7846744 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 8 Aug 2025 16:02:43 +0200 Subject: [PATCH 21/24] Add TODO comment for AMD duplicates --- arch/src/x86_64/cpuid_feature_flags.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/arch/src/x86_64/cpuid_feature_flags.rs b/arch/src/x86_64/cpuid_feature_flags.rs index 6e599b5032..bc84748264 100644 --- a/arch/src/x86_64/cpuid_feature_flags.rs +++ b/arch/src/x86_64/cpuid_feature_flags.rs @@ -162,15 +162,19 @@ cpuid_flag_constants!( TSC_DEADLINE, AES, XSAVE, "NULL" /* osxsave */, AVX, F16C, RDRAND, HYPERVISOR, ); +/* +TODO: The duplicate values only set for AMD are currently ignored (set to "NULL"). We need to +change this when we add the first AMD cpu profile. +*/ cpuid_flag_constants!( CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::EDX as u8}>, - "NULL" /* fpu */, "NULL" /* vme */, "NULL" /* de */, "NULL" /* pse */, - "NULL" /* tsc */, "NULL" /* msr */, "NULL" /* pae */, "NULL" /* mce */, - "NULL" /* cx8 */, "NULL" /* apic */, "NULL", SYSCALL, - "NULL" /* mtrr */, "NULL" /* pge */, "NULL" /* mca */, "NULL" /* cmov */, - "NULL" /* pat */, "NULL" /* pse36 */, "NULL", "NULL" /* Linux mp */, - NX, "NULL", MMXEXT, "NULL" /* mmx */, - "NULL" /* fxsr */, FXSR_OPT, PDPE1GB, RDTSCP, + "NULL" /* AMD_FPU */, "NULL" /* AMD_VME */, "NULL" /* AMD_DE */, "NULL" /* AMD_PSE */, + "NULL" /* AMD_TSC */, "NULL" /* AMD_MSR */, "NULL" /* AMD_PAE */, "NULL" /* AMD_MCE */, + "NULL" /* AMD_CX8 */, "NULL" /* AMD_APIC */, "NULL", SYSCALL, + "NULL" /* AMD_MTRR */, "NULL" /* AMD_PGE */, "NULL" /* AMD_MCA */, "NULL" /* AMD_CMOV */, + "NULL" /* AMD_PAT */, "NULL" /* AMD_PSE36 */, "NULL", "NULL" /* AMD ECC */, + NX, "NULL", MMXEXT, "NULL" /* AMD_MMX */, + "NULL" /* AMD_FXSR */, FXSR_OPT, PDPE1GB, RDTSCP, "NULL", LM, EXT_3DNOW, FIRST_3DNOW, ); From f68a9d9c71519162225fff67a630cf8fb09e8616 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 8 Aug 2025 17:47:00 +0200 Subject: [PATCH 22/24] More associated constants --- arch/src/x86_64/cpuid_feature_flags.rs | 132 +++++++++++++++++++------ 1 file changed, 104 insertions(+), 28 deletions(-) diff --git a/arch/src/x86_64/cpuid_feature_flags.rs b/arch/src/x86_64/cpuid_feature_flags.rs index bc84748264..f653d95121 100644 --- a/arch/src/x86_64/cpuid_feature_flags.rs +++ b/arch/src/x86_64/cpuid_feature_flags.rs @@ -162,33 +162,21 @@ cpuid_flag_constants!( TSC_DEADLINE, AES, XSAVE, "NULL" /* osxsave */, AVX, F16C, RDRAND, HYPERVISOR, ); -/* -TODO: The duplicate values only set for AMD are currently ignored (set to "NULL"). We need to -change this when we add the first AMD cpu profile. -*/ -cpuid_flag_constants!( - CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::EDX as u8}>, - "NULL" /* AMD_FPU */, "NULL" /* AMD_VME */, "NULL" /* AMD_DE */, "NULL" /* AMD_PSE */, - "NULL" /* AMD_TSC */, "NULL" /* AMD_MSR */, "NULL" /* AMD_PAE */, "NULL" /* AMD_MCE */, - "NULL" /* AMD_CX8 */, "NULL" /* AMD_APIC */, "NULL", SYSCALL, - "NULL" /* AMD_MTRR */, "NULL" /* AMD_PGE */, "NULL" /* AMD_MCA */, "NULL" /* AMD_CMOV */, - "NULL" /* AMD_PAT */, "NULL" /* AMD_PSE36 */, "NULL", "NULL" /* AMD ECC */, - NX, "NULL", MMXEXT, "NULL" /* AMD_MMX */, - "NULL" /* AMD_FXSR */, FXSR_OPT, PDPE1GB, RDTSCP, - "NULL", LM, EXT_3DNOW, FIRST_3DNOW, -); +// https://en.wikipedia.org/wiki/CPUID#EAX=6:_Thermal_and_Power_Management cpuid_flag_constants!( - CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::ECX as u8}>, - LAHF_LM, CMP_LEGACY, SVM, EXTAPIC, - CR8LEGACY, ABM, SSE4A, MISALIGNSSE, - PREFETCH_3DNOW, OSVW, IBS, XOP, - SKINIT, WDT, "NULL", LWP, - FMA4, TCE, "NULL", NODEID_MSR, - "NULL", TBM, TOPOEXT, PERFCTR_CORE, - PERFCTR_NB, "NULL", "NULL", "NULL", + CpuIdEntryRegister<6, 0, {CpuidReg::EAX as u8}>, + "NULL", "NULL", ARAT, "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + ); + cpuid_flag_constants!( CpuIdEntryRegister<7, 0, {CpuidReg::EBX as u8}>, FSGSBASE, TSC_ADJUST, SGX, BMI1, @@ -224,10 +212,71 @@ cpuid_flag_constants!( FLUSH_L1D, ARCH_CAPABILITIES, CORE_CAPABILITY, SSBD, ); +// https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=1:_Extended_Features +cpuid_flag_constants!( + CpuIdEntryRegister<7, 1, {CpuidReg::ECX as u8}>, + "NULL", "NULL", "NULL", "NULL", + "NULL", MSR_IMM, "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + +); +cpuid_flag_constants!( + CpuIdEntryRegister<7, 1, {CpuidReg::EAX as u8}>, + SHA512, SM3, SM4, "NULL", + AVX_VNNI, AVX512_BF16, "NULL", CMPCCXADD, + "NULL", "NULL", FZRM, FSRS, + FSRC, "NULL", "NULL", "NULL", + "NULL", FRED, LKGS, WRMSRNS, + "NULL", AMX_FP16, "NULL", AVX_IFMA, + "NULL", "NULL", LAM, "NULL", + "NULL", "NULL", "NULL", "NULL", +); + +cpuid_flag_constants!( + CpuIdEntryRegister<7, 1, {CpuidReg::EDX as u8}>, + "NULL", "NULL", "NULL", "NULL", + AVX_VNNI_INT8, AVX_NE_CONVERT, "NULL", "NULL", + AMX_COMPLEX, "NULL", AVX_VNNI_INT16, "NULL", + "NULL", "NULL", PREFETCHITI, "NULL", + "NULL", "NULL", "NULL", AVX10, + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", +); + +cpuid_flag_constants!( + CpuIdEntryRegister<7, 2, {CpuidReg::EDX as u8}>, + INTEL_PSFD, IPRED_CTRL, RRSBA_CTRL, DDPD_U, + BHI_CTRL, MCDT_NO, "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", +); cpuid_flag_constants!( CpuIdEntryRegister<0xd,1, {CpuidReg::EAX as u8}>, - XSAVEOPT, XSAVEC, XGETBV1, XSAVES, - XFD, "NULL", "NULL", "NULL", + XSAVEOPT, XSAVEC, XGETBV1, XSAVES, + XFD, "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", +); + +// https://en.wikipedia.org/wiki/CPUID#EAX=24h,_ECX=1:_Discrete_AVX10_Features +cpuid_flag_constants!( + CpuIdEntryRegister<0x24, 1, {CpuidReg::ECX as u8}>, + VPMM, "NULL", AVX_10_VNNI_INT, "NULL", + "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", @@ -236,17 +285,44 @@ cpuid_flag_constants!( "NULL", "NULL", "NULL", "NULL", ); +/* +TODO: The duplicate values only set for AMD are currently ignored (set to "NULL"). We need to +change this when we add the first AMD cpu profile. +*/ cpuid_flag_constants!( - CpuIdEntryRegister<6, 0, {CpuidReg::EAX as u8}>, - "NULL", "NULL", ARAT, "NULL", + CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::EDX as u8}>, + "NULL" /* AMD_FPU */, "NULL" /* AMD_VME */, "NULL" /* AMD_DE */, "NULL" /* AMD_PSE */, + "NULL" /* AMD_TSC */, "NULL" /* AMD_MSR */, "NULL" /* AMD_PAE */, "NULL" /* AMD_MCE */, + "NULL" /* AMD_CX8 */, "NULL" /* AMD_APIC */, "NULL", SYSCALL, + "NULL" /* AMD_MTRR */, "NULL" /* AMD_PGE */, "NULL" /* AMD_MCA */, "NULL" /* AMD_CMOV */, + "NULL" /* AMD_PAT */, "NULL" /* AMD_PSE36 */, "NULL", "NULL" /* AMD ECC */, + NX, "NULL", MMXEXT, "NULL" /* AMD_MMX */, + "NULL" /* AMD_FXSR */, FXSR_OPT, PDPE1GB, RDTSCP, + "NULL", LM, EXT_3DNOW, FIRST_3DNOW, + +); +cpuid_flag_constants!( + CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::ECX as u8}>, + LAHF_LM, CMP_LEGACY, SVM, EXTAPIC, + CR8LEGACY, ABM, SSE4A, MISALIGNSSE, + PREFETCH_3DNOW, OSVW, IBS, XOP, + SKINIT, WDT, "NULL", LWP, + FMA4, TCE, "NULL", NODEID_MSR, + "NULL", TBM, TOPOEXT, PERFCTR_CORE, + PERFCTR_NB, "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", +); + +cpuid_flag_constants!( + CpuIdEntryRegister<0x8000_0007, 0, {CpuidReg::EDX as u8}>, "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", + INVTSC, "NULL", "NULL", "NULL", + "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", "NULL", - ); /// A set of CPUID feature flags consisting of the registers for the various leaves describing From 5c1ae50077666d4bb0fc3403b6ba4c534eeedbbb Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Mon, 11 Aug 2025 10:21:24 +0200 Subject: [PATCH 23/24] Change macro to be more doc friendly --- Cargo.lock | 1 + arch/Cargo.toml | 1 + arch/src/x86_64/cpuid_feature_flags.rs | 521 +++++++++++++++---------- 3 files changed, 314 insertions(+), 209 deletions(-) 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/x86_64/cpuid_feature_flags.rs b/arch/src/x86_64/cpuid_feature_flags.rs index f653d95121..58c66fa8e7 100644 --- a/arch/src/x86_64/cpuid_feature_flags.rs +++ b/arch/src/x86_64/cpuid_feature_flags.rs @@ -87,242 +87,345 @@ impl } } -/// Reduces boilerplate when implementing CpuIdFeatureFlag constants. -/// One passes in a parameterized CpuIdFeatureFlags together with -/// a name per bit in the 32bit bitset. +/// Generates multiple constants for [`CpuIdEntryRegister`] without having to repeat the function, index, register parameters every time. /// -/// For lower order bits that should not have any associated constant, appearing -/// before a higher order bit that needs a constant, you can simply place a "NULL" -/// in its place. -macro_rules! cpuid_flag_constants { - /* NOTE: We allow dead code within this macro as we may want to use certain - unused constants in the feature when introducing more cpu profiles. - The alternative would be to fill the macro invocations with more "NULL" - entries. - */ - //============ Possible starting points ===========// - ($t:ty, $name:ident) => { - impl $t { - #[allow(dead_code)] - pub const $name: Self = Self(1); - } - }; - ($t:ty, "NULL", $($tail:tt)*) => { - impl $t { - cpuid_flag_constants!(1, $($tail)*); - } - }; - ($t:ty, $name:ident, $($tail:tt)*) => { - impl $t { - #[allow(dead_code)] - pub const $name: Self = Self(1); - cpuid_flag_constants!(1, $($tail)*); +/// 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); + )+ + } } }; - - //============ Possible most deeply nested macro invocation ===========// - ($i:expr, $name:ident $(,)*) => { - #[allow(dead_code)] - pub const $name: Self = Self(1 << $i); - }; - - ($i:expr, "NULL" $(,)*) => {}; - - // ============ Possible continuations that continue the recursion ===== // - ($i:expr, "NULL", $($tail:tt)+) => { - cpuid_flag_constants!($i + 1, $($tail)*); - }; - ($i:expr, $name:ident, $($tail:tt)+) => { - #[allow(dead_code)] - pub const $name: Self = Self(1 << $i); - cpuid_flag_constants!($i + 1, $($tail)*); - }; - } -cpuid_flag_constants!( - CpuIdEntryRegister<1, 0, { CpuidReg::EDX as u8 }>, - FPU, VME, DE, PSE, - TSC, MSR, PAE, MCE, - CX8, APIC, "NULL", SEP, - MTRR, PGE, MCA, CMOV, - PAT, PSE36, PN /* Intel psn */, CLFLUSH /* Intel clfsh */, - "NULL", DS /* INTEL DTS */, ACPI, MMX, - FXSR, SSE, SSE2, SS, - HT /* Intel htt */, TM, IA64, PBE, +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), + ] ); -cpuid_flag_constants!( - CpuIdEntryRegister<1, 0, { CpuidReg::ECX as u8 }>, - SSE3 /* Intel PNI,AMD sse3 */, PCLMULQDQ, DTES64, MONITOR, - DS_CPL, VMX, SMX, EST, - TM2, SSSE3, CID, "NULL", - FMA, CX16, XTPR, PDCM, - "NULL", PCID, DCA, SSE4_1, - SSE4_2, X2APIC, MOVBE, POPCNT, - TSC_DEADLINE, AES, XSAVE, "NULL" /* osxsave */, - AVX, F16C, RDRAND, HYPERVISOR, -); - -// https://en.wikipedia.org/wiki/CPUID#EAX=6:_Thermal_and_Power_Management -cpuid_flag_constants!( - CpuIdEntryRegister<6, 0, {CpuidReg::EAX as u8}>, - "NULL", "NULL", ARAT, "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", +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), + ] ); -cpuid_flag_constants!( - CpuIdEntryRegister<7, 0, {CpuidReg::EBX as u8}>, - FSGSBASE, TSC_ADJUST, SGX, BMI1, - HLE, AVX2, FDP_EXCPTN_ONLY, SMEP, - BMI2, ERMS, INVPCID, RTM, - "NULL", ZERO_FCS_FDS, MPX, "NULL", - AVX512F, AVX512DQ, RDSEED, ADX, - SMAP, AVX512IFMA, PCOMMIT, CLFLUSHOPT, - CLWB, INTEL_PT, AVX512PF, AVX512ER, - AVX512CD, SHA_NI, AVX512BW, AVX512VL, +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)] ); -cpuid_flag_constants!( - CpuIdEntryRegister<7, 0, {CpuidReg::ECX as u8}>, - "NULL", AVX512VBMI, UMIP, PKU, - "NULL" /* ospke */, WAITPKG, AVX512VBMI2, "NULL", - GFNI, VAES, VPCLMULQDQ, AVX512VNNI, - AVX512BITALG, "NULL", AVX512_VPOPCNTDQ, "NULL", - LA57, "NULL", "NULL", "NULL", - "NULL", "NULL", RDPID, "NULL", - BUS_LOCK_DETECT, CLDEMOTE, "NULL", MOVDIRI, - MOVDIR64B, "NULL", SGXLC, PKS, + +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), + ] ); -cpuid_flag_constants!( - CpuIdEntryRegister<7, 0, {CpuidReg::EDX as u8}>, - "NULL", "NULL", AVX512_4VNNIW, AVX512_4FMAPS, - FSRM, "NULL", "NULL", "NULL", - AVX512_VP2INTERSECT, "NULL", MD_CLEAR, "NULL", - "NULL", "NULL", SERIALIZE, "NULL", - TSX_LDTRK, "NULL", "NULL" /* pconfig */, ARCH_LBR, - "NULL", "NULL", AMX_BF16, AVX512_FP16, - AMX_TILE, AMX_INT8, SPEC_CTRL, STIBP, - FLUSH_L1D, ARCH_CAPABILITIES, CORE_CAPABILITY, SSBD, +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), + ] ); -// https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=1:_Extended_Features -cpuid_flag_constants!( - CpuIdEntryRegister<7, 1, {CpuidReg::ECX as u8}>, - "NULL", "NULL", "NULL", "NULL", - "NULL", MSR_IMM, "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", +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)] ); -cpuid_flag_constants!( - CpuIdEntryRegister<7, 1, {CpuidReg::EAX as u8}>, - SHA512, SM3, SM4, "NULL", - AVX_VNNI, AVX512_BF16, "NULL", CMPCCXADD, - "NULL", "NULL", FZRM, FSRS, - FSRC, "NULL", "NULL", "NULL", - "NULL", FRED, LKGS, WRMSRNS, - "NULL", AMX_FP16, "NULL", AVX_IFMA, - "NULL", "NULL", LAM, "NULL", - "NULL", "NULL", "NULL", "NULL", +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), + ] ); -cpuid_flag_constants!( - CpuIdEntryRegister<7, 1, {CpuidReg::EDX as u8}>, - "NULL", "NULL", "NULL", "NULL", - AVX_VNNI_INT8, AVX_NE_CONVERT, "NULL", "NULL", - AMX_COMPLEX, "NULL", AVX_VNNI_INT16, "NULL", - "NULL", "NULL", PREFETCHITI, "NULL", - "NULL", "NULL", "NULL", AVX10, - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", +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), + ] ); -cpuid_flag_constants!( - CpuIdEntryRegister<7, 2, {CpuidReg::EDX as u8}>, - INTEL_PSFD, IPRED_CTRL, RRSBA_CTRL, DDPD_U, - BHI_CTRL, MCDT_NO, "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", +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), + ] ); -cpuid_flag_constants!( - CpuIdEntryRegister<0xd,1, {CpuidReg::EAX as u8}>, - XSAVEOPT, XSAVEC, XGETBV1, XSAVES, - XFD, "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", +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), + ] ); -// https://en.wikipedia.org/wiki/CPUID#EAX=24h,_ECX=1:_Discrete_AVX10_Features -cpuid_flag_constants!( - CpuIdEntryRegister<0x24, 1, {CpuidReg::ECX as u8}>, - VPMM, "NULL", AVX_10_VNNI_INT, "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", +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)] ); -/* -TODO: The duplicate values only set for AMD are currently ignored (set to "NULL"). We need to -change this when we add the first AMD cpu profile. -*/ -cpuid_flag_constants!( - CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::EDX as u8}>, - "NULL" /* AMD_FPU */, "NULL" /* AMD_VME */, "NULL" /* AMD_DE */, "NULL" /* AMD_PSE */, - "NULL" /* AMD_TSC */, "NULL" /* AMD_MSR */, "NULL" /* AMD_PAE */, "NULL" /* AMD_MCE */, - "NULL" /* AMD_CX8 */, "NULL" /* AMD_APIC */, "NULL", SYSCALL, - "NULL" /* AMD_MTRR */, "NULL" /* AMD_PGE */, "NULL" /* AMD_MCA */, "NULL" /* AMD_CMOV */, - "NULL" /* AMD_PAT */, "NULL" /* AMD_PSE36 */, "NULL", "NULL" /* AMD ECC */, - NX, "NULL", MMXEXT, "NULL" /* AMD_MMX */, - "NULL" /* AMD_FXSR */, FXSR_OPT, PDPE1GB, RDTSCP, - "NULL", LM, EXT_3DNOW, FIRST_3DNOW, - +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), + ] ); -cpuid_flag_constants!( - CpuIdEntryRegister<0x8000_0001, 0, { CpuidReg::ECX as u8}>, - LAHF_LM, CMP_LEGACY, SVM, EXTAPIC, - CR8LEGACY, ABM, SSE4A, MISALIGNSSE, - PREFETCH_3DNOW, OSVW, IBS, XOP, - SKINIT, WDT, "NULL", LWP, - FMA4, TCE, "NULL", NODEID_MSR, - "NULL", TBM, TOPOEXT, PERFCTR_CORE, - PERFCTR_NB, "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", +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), + ] ); - -cpuid_flag_constants!( - CpuIdEntryRegister<0x8000_0007, 0, {CpuidReg::EDX as u8}>, - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - INVTSC, "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", - "NULL", "NULL", "NULL", "NULL", +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 From b08bcc244267f8ebe9a387cdb5972045be95e970 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Tue, 12 Aug 2025 08:13:55 +0200 Subject: [PATCH 24/24] Improve logs and add more comments --- .../cpu_profile/cpuid_feature_flags_impl.rs | 4 +++ arch/src/x86_64/cpuid_feature_flags.rs | 36 ++++++++++++++----- 2 files changed, 32 insertions(+), 8 deletions(-) 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 index 4296cd93aa..92cb338c02 100644 --- a/arch/src/x86_64/cpu_profile/cpuid_feature_flags_impl.rs +++ b/arch/src/x86_64/cpu_profile/cpuid_feature_flags_impl.rs @@ -80,6 +80,10 @@ impl CpuIdFeatureFlags { 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, diff --git a/arch/src/x86_64/cpuid_feature_flags.rs b/arch/src/x86_64/cpuid_feature_flags.rs index 58c66fa8e7..e9a3c996cb 100644 --- a/arch/src/x86_64/cpuid_feature_flags.rs +++ b/arch/src/x86_64/cpuid_feature_flags.rs @@ -9,6 +9,22 @@ pub struct CpuIdEntryRegister 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 { @@ -63,26 +79,28 @@ impl let least_significant_bit = missing_bits & missing_bits.wrapping_neg(); missing_bits ^= least_significant_bit; } - // TODO: Use a proper register name rather than REG and consider returning this as an error instead of logging here 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: {:?} in the register {:?} + does not have the following bits set: {:?} for the {} register even though the specified restriction permits it. ", - entry.function, - entry.index, + FUNCTION, + INDEX, entry.flags, bit_positions, - REG + self.register_name() ); } } if (!found_matching) && (self.0 != 0) { - // TODO: Better log or return an error here - log::warn!("no entry matched"); + log::warn!( + "no entry matched the function = 0x{:8x}, index = 0x{:08x} parameters", + FUNCTION, + INDEX + ); } } } @@ -189,6 +207,7 @@ impl_cpuid_entry_register_constants!( ] ); +// 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, @@ -433,7 +452,8 @@ impl_cpuid_entry_register_constants!( /// /// # Feature related Registers that are ignored /// -/// - 0x12: SGX capabilities (this is handled separately for now) +/// - 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