From 8a78043e2f36caba214397bdba592cb877d90e07 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/12] 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 da5fae38141d179d29a7771fdbeff2c4142ca03f Mon Sep 17 00:00:00 2001 From: Wei Liu Date: Tue, 1 Jul 2025 04:59:19 +0000 Subject: [PATCH 02/12] 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 5c067fddaafe65e2a22ec68edc64f5b93de29aa7 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Tue, 29 Jul 2025 22:13:15 +0200 Subject: [PATCH 03/12] 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 bc311d799f0bb3c822ee71b562e5b2ca846cee7f Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 30 Jul 2025 12:49:58 +0200 Subject: [PATCH 04/12] 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 58502d01466df949ce94d46f7e0e1421eee47db0 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 30 Jul 2025 15:20:26 +0200 Subject: [PATCH 05/12] 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 3f5c32da7a419f9d107a00f7be0a1a0a6ea9cb05 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Wed, 30 Jul 2025 16:59:29 +0200 Subject: [PATCH 06/12] 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 241c41209eb1f2850fec53d0e0e8bd378c21e934 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Thu, 31 Jul 2025 09:47:37 +0200 Subject: [PATCH 07/12] 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 244a1eac33a0270ddd847018cf918c0fdd5ec26f Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Thu, 31 Jul 2025 20:12:49 +0200 Subject: [PATCH 08/12] 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 b2d940a66b..87ce0594b2 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -8,6 +8,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, @@ -542,6 +543,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") @@ -550,7 +552,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 @@ -586,6 +589,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. @@ -612,6 +619,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 2cf1cb6e95..ec5f4a9042 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)?; @@ -2333,6 +2341,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; @@ -2366,6 +2376,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 a2c5b996b4..135edfda0d 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 644a0a0a19a8efe62c669c16cf51039be3254c45 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Thu, 31 Jul 2025 20:56:21 +0200 Subject: [PATCH 09/12] 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 8815ae3b358119b902f1fd78e4c72e1346ad340e Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Thu, 31 Jul 2025 21:22:36 +0200 Subject: [PATCH 10/12] 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/12] 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 357ed77379a9482a1b159ca049987c8991397ce1 Mon Sep 17 00:00:00 2001 From: Oliver Anderson Date: Fri, 1 Aug 2025 08:28:55 +0200 Subject: [PATCH 12/12] Alternative implementation --- arch/src/x86_64/cpu_profile_feature_flags.rs | 130 ++--- arch/src/x86_64/mod.rs | 557 +++++++++++++------ 2 files changed, 460 insertions(+), 227 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..062417791e 100644 --- a/arch/src/x86_64/cpu_profile_feature_flags.rs +++ b/arch/src/x86_64/cpu_profile_feature_flags.rs @@ -14,74 +14,74 @@ pub(super) struct CascadeLakeServerV1CpuIdFeatures { } impl CascadeLakeServerV1CpuIdFeatures { - pub(super) fn new() -> Self { + pub(super) const 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, + // Placing this in a const block ensures compile time evaluation and we get isntant feedback + // (even from the LSP) if the lists contain arguments that are not defined for the given + // function, index, register triple via `crate::x86_64::impl_cpuid_feature_flags!` + const { + Self { + edx_1: FF::<1, 0, { CpuidReg::EDX as u8 }>::from_names(&[ + "vme", "sse2", "sse", "fxsr", "mmx", "clflush", "pse36", "pat", "cmov", "mca", + "pge", "mtrr", "sep", "apic", "cx8", "mce", "pae", "msr", "tsc", "pse", "de", + "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, + ecx_1: FF::<1, 0, { CpuidReg::ECX as u8 }>::from_names(&[ + "avx", + "xsave", + "aes", + "popcnt", + "x2apic", + "sse4.2", + "sse4.1", + "cx16", + "ssse3", + "pclmulqdq", + "pni", + "tsc-deadline", + "fma", + "movbe", + "pcid", + "f16c", + "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, + edx_8000_0001h: FF::<0x8000_0001, 0, { CpuidReg::EDX as u8 }>::from_names(&[ + "lm", "pdpe1gb", "rdtscp", "nx", "syscall", + ]), + ecx_8000_0001h: FF::<0x8000_0001, 0, { CpuidReg::ECX as u8 }>::from_names(&[ + "abm", + "lahf-lm", + "3dnowprefetch", + ]), + ebx_7_0: FF::<7, 0, { CpuidReg::EBX as u8 }>::from_names(&[ + "fsgsbase", + "bmi1", + "hle", + "avx2", + "smep", + "bmi2", + "erms", + "invpcid", + "rtm", + "rdseed", + "adx", + "smap", + "clwb", + "avx512f", + "avx512dq", + "avx512bw", + "avx512cd", + "avx512vl", + "clflushopt", + ]), + ecx_7_0: FF::<7, 0, { CpuidReg::ECX as u8 }>::from_names(&["pku", "avx512vnni"]), + edx_7_0: FF::<7, 0, { CpuidReg::EDX as u8 }>::from_names(&["spec-ctrl", "ssbd"]), + eax_0dh: FF::<0xd, 1, { CpuidReg::EAX as u8 }>::from_names(&[ + "xsaveopt", "xsavec", "xgetbv1", + ]), + } } } diff --git a/arch/src/x86_64/mod.rs b/arch/src/x86_64/mod.rs index 076dedc5a4..4098d9b17d 100644 --- a/arch/src/x86_64/mod.rs +++ b/arch/src/x86_64/mod.rs @@ -244,14 +244,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); -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 { @@ -324,179 +317,419 @@ impl CpuIdFeatureFlags { - impl $t { - #[allow(dead_code)] - const $name: Self = Self(1); - } +macro_rules! null_to_empty_else_identity { + (NULL) => { + "" }; - ($t:ty, "NULL", $($tail:tt)*) => { - impl $t { - cpuid_flag_constants!(1, $($tail)*); - } + ($name:literal) => { + $name }; - ($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",) => {}; +} - (,) => {}; +macro_rules! impl_cpuid_feature_flags { + (function = $func:literal, index = $idx:literal, register = $reg:path, $($name_or_null:tt),+$(,)*) => { + impl CpuIdFeatureFlags<$func, $idx, {$reg as u8}> { + #[allow(dead_code)] + const SHORT_NAMES_PER_BIT: [&str; 32] = [$(null_to_empty_else_identity!($name_or_null)),+]; + + /// Construct a parametrized [`CpuIdFeatureFlags`] from a set of feature names. + /// + /// # Warning + /// + /// This function should only be called in const contexts to avoid runtime panics (and checks) as const generics does not yet + /// permit arrays (of string slices) there is currently no nice way to enforce this constraint. When Rust will permit arrays of string slices + /// as const generic parameters then we can ensure that the function is only evaluated at compile time (hence no possibilities for runtime panics). + #[allow(dead_code)] + const fn from_names(feature_names: &[&'static str]) -> Self { + if feature_names.len() > 32 { + panic!("There can be at most 32 feature flags in a CPUID register") + } - // ============ 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)*); + let mut i = 0; + let mut set_flags = 0; + // Loop over the FEATURE_NAMES. We need to use while loops with indices due to + // const traits not yet being stable. + while i < feature_names.len() { + let feat_name = feature_names[i]; + // Loop until we find a matching feature in the predefined names + let mut j = 0; + // Unfortunately we need to check for equality byte for byte as this is a const function. + let feat_name_bytes= feat_name.as_bytes(); + while j < 32 { + let current_as_bytes = Self::SHORT_NAMES_PER_BIT[j].as_bytes(); + let length = current_as_bytes.len(); + if length != feat_name_bytes.len() { + // No point in checking for equality as the string slices have different lengths + j += 1; + // If j becomes 32 then the feature was not found. This indicates a bug in the program hence we panic. Unfortunately we + // cannot properly enforce this at compile time as of now (at least not without introducing loads of boiler plate). + if j == 32 { + panic!("the specified feature does not exists, or is not defined for the chosen parametrization of CpuIdFeatureFlags"); + } + continue; + } + let mut matches = true; + let mut k = 0; + while k < length { + matches &= (current_as_bytes[k] == feat_name_bytes[k]); + k += 1 + } + if matches { + set_flags |= (1u32 << j); + break; + } + j += 1; + if j == 32 { + panic!("the specified feature does not exists, or is not defined for the chosen parametrization of CpuIdFeatureFlags"); + } + } + i += 1; + } + Self(set_flags) + } + } }; - } -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, +impl_cpuid_feature_flags!( + function = 1, + index = 0, + register = CpuidReg::EDX, + "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 }>, - 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!( - 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, +impl_cpuid_feature_flags!( + function = 1, + index = 0, + register = CpuidReg::ECX, + "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::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_feature_flags!( + function = 0x8000_0001, + index = 0, + register = CpuidReg::EDX, + 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", + "3dnowext", + "3dnow", ); -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, + +impl_cpuid_feature_flags!( + function = 0x8000_0001, + index = 0, + register = CpuidReg::ECX, + "lahf-lm", + "cmp-legacy", + "svm", + "extapic", + "cr8legacy", + "abm", + "sse4a", + "misalignsse", + "3dnowprefetch", + "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::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_feature_flags!( + function = 7, + index = 0, + register = CpuidReg::EBX, + "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::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_feature_flags!( + function = 7, + index = 0, + register = CpuidReg::ECX, + 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<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_feature_flags!( + function = 7, + index = 0, + register = CpuidReg::EDX, + 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<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_feature_flags!( + function = 6, + index = 0, + register = CpuidReg::EAX, + 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_feature_flags!( + function = 0xd, + index = 1, + register = CpuidReg::EAX, + "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, ); pub struct CpuidPatch {