#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use raw_cpuid::CpuId;
use serde::{Deserialize, Serialize};
use std::fmt;
const MAX_CACHE_LEVELS: usize = 4;
#[derive(Debug, thiserror::Error)]
pub enum CpuidError {
#[error("CPUID leaf {0} not supported")]
UnsupportedLeaf(u32),
#[error("CPUID leaf {0}, subleaf {1} not supported")]
UnsupportedSubleaf(u32, u32),
#[error("Cache level {0} information not available")]
CacheInfoNotAvailable(u8),
#[error("CPUID access failed: {0}")]
AccessError(String),
#[error("Unexpected CPUID result")]
UnexpectedResult,
#[error("Architecture not supported")]
UnsupportedArchitecture,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct CacheInfo {
pub level: u8,
pub cache_type: CacheType,
pub size_kb: u32,
pub line_size: u16,
pub associativity: u16,
pub sets: u32,
pub shared_by: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum CacheType {
Data,
Instruction,
Unified,
#[default]
Unknown,
}
impl fmt::Display for CacheType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CacheType::Data => write!(f, "Data"),
CacheType::Instruction => write!(f, "Instruction"),
CacheType::Unified => write!(f, "Unified"),
CacheType::Unknown => write!(f, "Unknown"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BasicInfo {
pub vendor_string: String,
pub brand_string: String,
pub family: u8,
pub model: u8,
pub stepping: u8,
pub extended_family: u8,
pub extended_model: u8,
pub processor_type: u8,
pub base_features: u64,
pub extended_features: u64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CacheTopology {
pub caches: [Option<CacheInfo>; MAX_CACHE_LEVELS],
}
#[derive(Debug)]
pub struct CpuidWrapper {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
cpuid: CpuId<raw_cpuid::CpuIdReaderNative>,
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
impl Default for CpuidWrapper {
fn default() -> Self {
Self::new()
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
impl Default for CpuidWrapper {
fn default() -> Self {
Self::new()
}
}
impl CpuidWrapper {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[must_use]
pub fn new() -> Self {
Self { cpuid: CpuId::new() }
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
#[must_use]
pub fn new() -> Self {
Self {}
}
pub fn get_basic_info(&self) -> Result<BasicInfo, CpuidError> {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let vendor = self
.cpuid
.get_vendor_info()
.ok_or_else(|| CpuidError::AccessError("Failed to get vendor information".into()))?;
let brand_string = self
.cpuid
.get_processor_brand_string()
.map_or_else(|| "Unknown".to_string(), |brand| brand.as_str().trim().to_string());
let feature_info = self.cpuid.get_feature_info().ok_or(CpuidError::UnsupportedLeaf(1))?;
let family_id = feature_info.family_id();
let model_id = feature_info.model_id();
let stepping_id = feature_info.stepping_id();
let extended_family_id = feature_info.extended_family_id();
let extended_model_id = feature_info.extended_model_id();
let processor_type = 0u8;
let base_features = 0u64;
let extended_features = 0u64;
Ok(BasicInfo {
vendor_string: vendor.as_str().to_string(),
brand_string,
family: family_id,
model: model_id,
stepping: stepping_id,
extended_family: extended_family_id,
extended_model: extended_model_id,
processor_type,
base_features,
extended_features,
})
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
Err(CpuidError::UnsupportedArchitecture)
}
}
pub fn get_cache_topology(&self) -> Result<CacheTopology, CpuidError> {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let mut topology = CacheTopology::default();
let mut cache_found = false;
if let Some(deterministic_cache) = self.cpuid.get_cache_parameters() {
let cache_iter = deterministic_cache;
let mut index = 0;
for cache in cache_iter {
if index >= MAX_CACHE_LEVELS {
break;
}
let cache_type = match cache.cache_type() {
raw_cpuid::CacheType::Data => CacheType::Data,
raw_cpuid::CacheType::Instruction => CacheType::Instruction,
raw_cpuid::CacheType::Unified => CacheType::Unified,
_ => CacheType::Unknown,
};
let size_kb = cache.associativity()
* cache.physical_line_partitions()
* cache.coherency_line_size()
* cache.sets()
/ 1024;
let target_index = match (cache.level(), cache_type) {
(1, CacheType::Instruction) => 0,
(1, CacheType::Data) => 1,
(2, _) => 2,
(3, _) => 3,
_ => {
if index < MAX_CACHE_LEVELS {
index
} else {
continue;
}
},
};
#[allow(clippy::cast_possible_truncation)]
let cache_entry = CacheInfo {
level: cache.level(),
cache_type,
size_kb: size_kb as u32,
line_size: cache.coherency_line_size() as u16,
associativity: cache.associativity() as u16,
sets: cache.sets() as u32,
shared_by: cache.max_cores_for_cache() as u16,
};
topology.caches[target_index] = Some(cache_entry);
cache_found = true;
index += 1;
}
if cache_found {
return Ok(topology);
}
}
if self.cpuid.get_cache_info().is_some() {
cache_found = true; }
if !cache_found {
if let Ok(info) = self.get_basic_info() {
if info.vendor_string == "GenuineIntel" {
topology.caches[0] = Some(CacheInfo {
level: 1,
cache_type: CacheType::Instruction,
size_kb: 32, line_size: 64, associativity: 8, sets: 0,
shared_by: 1,
});
topology.caches[1] = Some(CacheInfo {
level: 1,
cache_type: CacheType::Data,
size_kb: 32, line_size: 64, associativity: 8, sets: 0,
shared_by: 1,
});
} else if info.vendor_string == "AuthenticAMD" {
topology.caches[0] = Some(CacheInfo {
level: 1,
cache_type: CacheType::Instruction,
size_kb: 64, line_size: 64, associativity: 8, sets: 0,
shared_by: 1,
});
topology.caches[1] = Some(CacheInfo {
level: 1,
cache_type: CacheType::Data,
size_kb: 32, line_size: 64, associativity: 8, sets: 0,
shared_by: 1,
});
}
}
}
Ok(topology)
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
Err(CpuidError::UnsupportedArchitecture)
}
}
#[must_use]
pub fn detect_hypervisor(&self) -> Option<String> {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let feature_info = self.cpuid.get_feature_info()?;
if !feature_info.has_hypervisor() {
return None;
}
if let Some(hv_info) = self.cpuid.get_hypervisor_info() {
let name = match hv_info.identify() {
raw_cpuid::Hypervisor::Xen => "Xen",
raw_cpuid::Hypervisor::VMware => "VMware",
raw_cpuid::Hypervisor::HyperV => "Hyper-V",
raw_cpuid::Hypervisor::KVM => "KVM",
raw_cpuid::Hypervisor::Bhyve => "bhyve",
raw_cpuid::Hypervisor::QNX => "QNX",
raw_cpuid::Hypervisor::ACRN => "ACRN",
_ => "Unknown",
};
return Some(name.to_string());
}
Some("Unknown".to_string())
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
None
}
#[must_use]
pub fn has_feature(&self, _feature: u32, _register: CpuidRegister) -> bool {
false
}
#[must_use]
pub fn has_extended_feature(&self, _feature: u32, _register: CpuidRegister) -> bool {
false
}
}
#[derive(Debug, Clone, Copy)]
pub enum CpuidRegister {
EAX,
EBX,
ECX,
EDX,
}
#[cfg(test)]
mod tests {
#[cfg(any(
all(target_arch = "x86", not(target_env = "sgx"), target_feature = "sse"),
all(target_arch = "x86_64", not(target_env = "sgx"))
))]
use super::*;
#[test]
#[cfg(any(
all(target_arch = "x86", not(target_env = "sgx"), target_feature = "sse"),
all(target_arch = "x86_64", not(target_env = "sgx"))
))]
fn test_basic_info() {
let wrapper = CpuidWrapper::new();
let info = wrapper.get_basic_info().expect("Failed to get basic CPU info");
assert!(!info.vendor_string.is_empty());
assert!(!info.brand_string.is_empty());
assert!(info.family > 0, "Family ID should be non-zero on real hardware");
}
#[test]
#[cfg(any(
all(target_arch = "x86", not(target_env = "sgx"), target_feature = "sse"),
all(target_arch = "x86_64", not(target_env = "sgx"))
))]
fn test_cache_topology() {
let wrapper = CpuidWrapper::new();
let topology = wrapper.get_cache_topology().expect("Failed to get cache topology");
let has_at_least_one_cache = topology.caches.iter().any(Option::is_some);
assert!(has_at_least_one_cache, "No caches detected on this CPU");
}
}