From aa1a9d8842e448538a5fdad7d8ebecf2bee603e0 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Mon, 18 May 2026 11:23:02 +0800 Subject: [PATCH 1/5] vmm/cpuid: Add support for Hygon processors Hygon processors are derived from AMD Zen microarchitecture. Their CPUID features and leaf layouts can be safely handled using the same logic as AMD CPUs. This patch adds the `HygonGenuine` vendor ID string and routes its CPUID parsing through the existing `AmdCpuid` wrapper. It also introduces the necessary unit tests to ensure `kvm_cpuid` to Firecracker `Cpuid` translations work correctly for Hygon processors. Signed-off-by: Wei Wang Tested-by: Yongwei Xu --- src/vmm/src/cpu_config/x86_64/cpuid/mod.rs | 67 +++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/vmm/src/cpu_config/x86_64/cpuid/mod.rs b/src/vmm/src/cpu_config/x86_64/cpuid/mod.rs index ce249cf1a76..337e96f79e3 100644 --- a/src/vmm/src/cpu_config/x86_64/cpuid/mod.rs +++ b/src/vmm/src/cpu_config/x86_64/cpuid/mod.rs @@ -57,6 +57,9 @@ pub const VENDOR_ID_INTEL: &[u8; 12] = b"GenuineIntel"; /// AMD brand string. pub const VENDOR_ID_AMD: &[u8; 12] = b"AuthenticAMD"; +/// Hygon brand string. +pub const VENDOR_ID_HYGON: &[u8; 12] = b"HygonGenuine"; + /// Intel brand string. #[allow(clippy::undocumented_unsafe_blocks)] pub const VENDOR_ID_INTEL_STR: &str = unsafe { std::str::from_utf8_unchecked(VENDOR_ID_INTEL) }; @@ -65,6 +68,10 @@ pub const VENDOR_ID_INTEL_STR: &str = unsafe { std::str::from_utf8_unchecked(VEN #[allow(clippy::undocumented_unsafe_blocks)] pub const VENDOR_ID_AMD_STR: &str = unsafe { std::str::from_utf8_unchecked(VENDOR_ID_AMD) }; +/// Hygon brand string. +#[allow(clippy::undocumented_unsafe_blocks)] +pub const VENDOR_ID_HYGON_STR: &str = unsafe { std::str::from_utf8_unchecked(VENDOR_ID_HYGON) }; + /// To store the brand string we have 3 leaves, each with 4 registers, each with 4 bytes. pub const BRAND_STRING_LENGTH: usize = 3 * 4 * 4; @@ -407,7 +414,9 @@ impl TryFrom for Cpuid { match std::str::from_utf8(&vendor_id) { Ok(VENDOR_ID_INTEL_STR) => Ok(Cpuid::Intel(IntelCpuid::from(kvm_cpuid))), - Ok(VENDOR_ID_AMD_STR) => Ok(Cpuid::Amd(AmdCpuid::from(kvm_cpuid))), + Ok(VENDOR_ID_AMD_STR | VENDOR_ID_HYGON_STR) => { + Ok(Cpuid::Amd(AmdCpuid::from(kvm_cpuid))) + } _ => Err(CpuidTryFromKvmCpuid::UnsupportedVendor(vendor_id)), } } @@ -653,6 +662,39 @@ mod tests { } } + fn build_hygon_leaf0_for_cpuid() -> (CpuidKey, CpuidEntry) { + ( + CpuidKey { + leaf: 0x0, + subleaf: 0x0, + }, + CpuidEntry { + flags: KvmCpuidFlags::EMPTY, + result: CpuidRegisters { + eax: 0x1, + // HygonGenuine + ebx: 0x6f677948, + ecx: 0x656e6975, + edx: 0x6e65476e, + }, + }, + ) + } + + fn build_hygon_leaf0_for_kvmcpuid() -> kvm_bindings::kvm_cpuid_entry2 { + kvm_bindings::kvm_cpuid_entry2 { + function: 0x0, + index: 0x0, + flags: 0x0, + eax: 0x1, + // HygonGenuine + ebx: 0x6f677948, + ecx: 0x656e6975, + edx: 0x6e65476e, + ..Default::default() + } + } + fn build_sample_leaf_for_cpuid() -> (CpuidKey, CpuidEntry) { ( CpuidKey { @@ -714,6 +756,21 @@ mod tests { .unwrap() } + fn build_sample_hygon_cpuid() -> Cpuid { + Cpuid::Amd(AmdCpuid(BTreeMap::from([ + build_hygon_leaf0_for_cpuid(), + build_sample_leaf_for_cpuid(), + ]))) + } + + fn build_sample_hygon_kvmcpuid() -> kvm_bindings::CpuId { + kvm_bindings::CpuId::from_entries(&[ + build_hygon_leaf0_for_kvmcpuid(), + build_sample_leaf_for_kvmcpuid(), + ]) + .unwrap() + } + #[test] fn get() { let cpuid = build_sample_intel_cpuid(); @@ -763,6 +820,10 @@ mod tests { let kvm_cpuid = build_sample_amd_kvmcpuid(); let cpuid = Cpuid::try_from(kvm_cpuid).unwrap(); assert_eq!(cpuid, build_sample_amd_cpuid()); + + let kvm_cpuid = build_sample_hygon_kvmcpuid(); + let cpuid = Cpuid::try_from(kvm_cpuid).unwrap(); + assert_eq!(cpuid, build_sample_hygon_cpuid()); } #[test] @@ -774,6 +835,10 @@ mod tests { let cpuid = build_sample_amd_cpuid(); let kvm_cpuid = kvm_bindings::CpuId::try_from(cpuid).unwrap(); assert_eq!(kvm_cpuid, build_sample_amd_kvmcpuid()); + + let cpuid = build_sample_hygon_cpuid(); + let kvm_cpuid = kvm_bindings::CpuId::try_from(cpuid).unwrap(); + assert_eq!(kvm_cpuid, build_sample_hygon_kvmcpuid()); } #[test] From 78bb6fb6360147b20db1c9f59566e9f0601ded8c Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Mon, 18 May 2026 11:34:07 +0800 Subject: [PATCH 2/5] vmm/cpuid: Allow cache topology passthrough for Hygon Hygon processors are derived from AMD Zen microarchitecture. Firecracker can safely pass through the host cache topology to the guest just as it does for AMD. Update passthrough_cache_topology to accept the Hygon vendor ID, preventing a BadVendorId error when booting on Hygon hosts. Signed-off-by: Wei Wang Tested-by: Yongwei Xu --- src/vmm/src/cpu_config/x86_64/cpuid/amd/normalize.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vmm/src/cpu_config/x86_64/cpuid/amd/normalize.rs b/src/vmm/src/cpu_config/x86_64/cpuid/amd/normalize.rs index f3a938e9a62..0eef6f4b314 100644 --- a/src/vmm/src/cpu_config/x86_64/cpuid/amd/normalize.rs +++ b/src/vmm/src/cpu_config/x86_64/cpuid/amd/normalize.rs @@ -7,7 +7,7 @@ use crate::cpu_config::x86_64::cpuid::normalize::{ }; use crate::cpu_config::x86_64::cpuid::{ BRAND_STRING_LENGTH, CpuidEntry, CpuidKey, CpuidRegisters, CpuidTrait, KvmCpuidFlags, - MissingBrandStringLeaves, VENDOR_ID_AMD, cpuid, cpuid_count, + MissingBrandStringLeaves, VENDOR_ID_AMD, VENDOR_ID_HYGON, cpuid, cpuid_count, }; /// Error type for [`super::AmdCpuid::normalize`]. @@ -121,11 +121,12 @@ impl super::AmdCpuid { /// /// This function passes through leaves from the host CPUID, if this does not match the AMD /// specification it is possible to enter an indefinite loop. To avoid this, this will return an - /// error when the host CPUID vendor id does not match the AMD CPUID vendor id. + /// error when the host CPUID vendor id does not match the AMD or Hygon CPUID vendor id. fn passthrough_cache_topology(&mut self) -> Result<(), PassthroughCacheTopologyError> { - if get_vendor_id_from_host().map_err(PassthroughCacheTopologyError::NoVendorId)? - != *VENDOR_ID_AMD - { + let vendor = + get_vendor_id_from_host().map_err(PassthroughCacheTopologyError::NoVendorId)?; + + if !matches!(&vendor, VENDOR_ID_AMD | VENDOR_ID_HYGON) { return Err(PassthroughCacheTopologyError::BadVendorId); } From 77663cd8083804797b29b7622493082cee629025 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Mon, 18 May 2026 11:35:44 +0800 Subject: [PATCH 3/5] vmm/vcpu: Treat Hygon like AMD in vCPU tests Hygon processors share the same architectural behavior as AMD processors. Update the vCPU topology and feature assertions in the test suite to apply the AMD-specific checks to the Hygon vendor ID as well. Signed-off-by: Wei Wang Tested-by: Yongwei Xu --- src/vmm/src/arch/x86_64/vcpu.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vmm/src/arch/x86_64/vcpu.rs b/src/vmm/src/arch/x86_64/vcpu.rs index 98563aab2e3..61db3f6e723 100644 --- a/src/vmm/src/arch/x86_64/vcpu.rs +++ b/src/vmm/src/arch/x86_64/vcpu.rs @@ -947,7 +947,7 @@ mod tests { ); assert!(!t2a_res); } - cpuid::VENDOR_ID_AMD => { + cpuid::VENDOR_ID_AMD | cpuid::VENDOR_ID_HYGON => { assert!(!t2_res); assert!(!c3_res); assert!(!t2s_res); From 204cc4afef670c98ad53be3ce903ed6988f0881b Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Mon, 18 May 2026 12:42:24 +0800 Subject: [PATCH 4/5] cpu-template-helper: Support dumping MSR templates on Hygon hosts msrs_to_modifier() in the x86_64 dumper applies an extra AMD-specific MSR exclusion list (should_exclude_msr_amd) on top of the common one, to drop MSRs that aren't reasonable to expose for user editing in a custom CPU template. The check was gated strictly on VENDOR_ID_AMD, so on a Hygon host the AMD-family MSRs (MSR_AMD64_*, the SEV/PSP block at 0xC001_00xx, etc.) leaked into the dumped template even though they share AMD's semantics and rationale for exclusion. Extend the host-vendor gate to accept Hygon as well, matching the AMD codepath the rest of the cpuid module now routes Hygon through, and update the unit test's expected-vector construction so it stays in lockstep with the production filter on every host vendor. Signed-off-by: Wei Wang Tested-by: Yongwei Xu --- src/cpu-template-helper/src/template/dump/x86_64.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/cpu-template-helper/src/template/dump/x86_64.rs b/src/cpu-template-helper/src/template/dump/x86_64.rs index eaa2553a658..02e06a0a69a 100644 --- a/src/cpu-template-helper/src/template/dump/x86_64.rs +++ b/src/cpu-template-helper/src/template/dump/x86_64.rs @@ -8,7 +8,7 @@ use vmm::arch::x86_64::generated::msr_index::*; use vmm::arch::x86_64::msr::MsrRange; use vmm::cpu_config::templates::{CpuConfiguration, CustomCpuTemplate, RegisterValueFilter}; use vmm::cpu_config::x86_64::cpuid::common::get_vendor_id_from_host; -use vmm::cpu_config::x86_64::cpuid::{Cpuid, VENDOR_ID_AMD}; +use vmm::cpu_config::x86_64::cpuid::{Cpuid, VENDOR_ID_AMD, VENDOR_ID_HYGON}; use vmm::cpu_config::x86_64::custom_cpu_template::{ CpuidLeafModifier, CpuidRegister, CpuidRegisterModifier, RegisterModifier, }; @@ -49,9 +49,11 @@ fn msrs_to_modifier(msrs: &BTreeMap) -> Vec { .iter() .map(|(index, value)| msr_modifier!(*index, *value)) .collect(); + let vendor = get_vendor_id_from_host().unwrap(); msrs.retain(|modifier| !should_exclude_msr(modifier.addr)); - if &get_vendor_id_from_host().unwrap() == VENDOR_ID_AMD { + + if matches!(&vendor, VENDOR_ID_AMD | VENDOR_ID_HYGON) { msrs.retain(|modifier| !should_exclude_msr_amd(modifier.addr)); } @@ -216,7 +218,9 @@ mod tests { msr_modifier!(0x3, 0x0000_0000_ffff_ffff), msr_modifier!(0x5, 0xffff_ffff_0000_0000), ]; - if &get_vendor_id_from_host().unwrap() != VENDOR_ID_AMD { + let vendor = get_vendor_id_from_host().unwrap(); + + if !matches!(&vendor, VENDOR_ID_AMD | VENDOR_ID_HYGON) { MSR_EXCLUSION_LIST_AMD.iter().for_each(|range| { (range.base..(range.base + range.nmsrs)).for_each(|id| { v.push(msr_modifier!(id, 0)); From 7b03929cbf1b5e72dc181e7a168a84c4a395920c Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Wed, 20 May 2026 08:24:52 +0800 Subject: [PATCH 5/5] tests: Fix CPU model name parsing for strings containing colons When parsing `/proc/cpuinfo` to retrieve the CPU model name, the test framework splits the matched line using the `:` separator and asserts that exactly two elements are returned. This assertion fails on Hygon CPUs, which include an Ordering Part Number (OPN) with an embedded colon in their model name string (e.g., `model name : Hygon C86-4G (OPN:7490)`). Splitting on all colons produces an array of length 3, causing the test suite to crash immediately upon initialization. Fix this by passing `maxsplit=1` to `split()`, ensuring the string is only divided at the first colon separating the key and the value. This cleanly supports Hygon processors while maintaining the same behavior for standard Intel and AMD strings. Signed-off-by: Wei Wang Tested-by: Yongwei Xu --- tests/framework/utils_cpuid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/framework/utils_cpuid.py b/tests/framework/utils_cpuid.py index 61f36086dbf..4820bfe9e4b 100644 --- a/tests/framework/utils_cpuid.py +++ b/tests/framework/utils_cpuid.py @@ -68,7 +68,7 @@ def get_cpu_model_name(): _, stdout, _ = check_output("cat /proc/cpuinfo | grep 'CPU part' | uniq") else: _, stdout, _ = check_output("cat /proc/cpuinfo | grep 'model name' | uniq") - info = stdout.strip().split(sep=":") + info = stdout.strip().split(sep=":", maxsplit=1) assert len(info) == 2 raw_cpu_model = info[1].strip() if platform.machine() == "x86_64":