Skip to content

Commit

Permalink
CPU frequency detection rework
Browse files Browse the repository at this point in the history
- Removed frequency detection functions
- use KVM capabilities on Linux for freq detection.
- Moved CPU frequency detection into VirtualCPU
  • Loading branch information
jounathaen committed Jan 28, 2025
1 parent 5e1408e commit 64ecc80
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 262 deletions.
176 changes: 0 additions & 176 deletions src/arch/x86_64/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
pub mod paging;
pub mod registers;

use core::arch::x86_64::_rdtsc as rdtsc;
use std::{
convert::TryInto,
time::{Duration, Instant},
};

use log::{debug, warn};
use raw_cpuid::{CpuId, CpuIdReaderNative};
use uhyve_interface::{GuestPhysAddr, GuestVirtAddr};
use x86_64::structures::paging::{
page_table::{FrameError, PageTableEntry},
Expand All @@ -18,93 +10,6 @@ use x86_64::structures::paging::{
use crate::{arch::paging::initialize_pagetables, mem::MmapMemory, paging::PagetableError};

pub const RAM_START: GuestPhysAddr = GuestPhysAddr::new(0x00);
const MHZ_TO_HZ: u64 = 1000000;
const KHZ_TO_HZ: u64 = 1000;

use crate::arch::FrequencyDetectionFailed;

pub fn detect_freq_from_cpuid(
cpuid: &CpuId<CpuIdReaderNative>,
) -> std::result::Result<u32, FrequencyDetectionFailed> {
debug!("Trying to detect CPU frequency by tsc info");

let has_invariant_tsc = cpuid
.get_advanced_power_mgmt_info()
.is_some_and(|apm_info| apm_info.has_invariant_tsc());
if !has_invariant_tsc {
warn!("TSC frequency varies with speed-stepping")
}

let tsc_frequency_hz = cpuid.get_tsc_info().map(|tinfo| {
if tinfo.tsc_frequency().is_some() {
tinfo.tsc_frequency()
} else {
// Skylake and Kabylake don't report the crystal clock, approximate with base frequency:
cpuid
.get_processor_frequency_info()
.map(|pinfo| pinfo.processor_base_frequency() as u64 * MHZ_TO_HZ)
.map(|cpu_base_freq_hz| {
let crystal_hz =
cpu_base_freq_hz * tinfo.denominator() as u64 / tinfo.numerator() as u64;
crystal_hz * tinfo.numerator() as u64 / tinfo.denominator() as u64
})
}
});

let hz = match tsc_frequency_hz {
Some(x) => x.unwrap_or(0),
None => {
return Err(FrequencyDetectionFailed);
}
};

if hz > 0 {
Ok((hz / MHZ_TO_HZ).try_into().unwrap())
} else {
Err(FrequencyDetectionFailed)
}
}

pub fn detect_freq_from_cpuid_hypervisor_info(
cpuid: &CpuId<CpuIdReaderNative>,
) -> std::result::Result<u32, FrequencyDetectionFailed> {
debug!("Trying to detect CPU frequency by hypervisor info");
let hypervisor_info = cpuid
.get_hypervisor_info()
.ok_or(FrequencyDetectionFailed)?;
debug!(
"cpuid detected hypervisor: {:?}",
hypervisor_info.identify()
);
let hz = hypervisor_info
.tsc_frequency()
.ok_or(FrequencyDetectionFailed)? as u64
* KHZ_TO_HZ;
let mhz: u32 = (hz / MHZ_TO_HZ).try_into().unwrap();
if mhz > 0 {
Ok(mhz)
} else {
Err(FrequencyDetectionFailed)
}
}

pub fn get_cpu_frequency_from_os() -> std::result::Result<u32, FrequencyDetectionFailed> {
// Determine TSC frequency by measuring it (loop for a second, record ticks)
let duration = Duration::from_millis(10);
let now = Instant::now();
let start = unsafe { crate::x86_64::rdtsc() };
if start > 0 {
loop {
if now.elapsed() >= duration {
break;
}
}
let end = unsafe { rdtsc() };
Ok((((end - start) * 100) / MHZ_TO_HZ).try_into().unwrap())
} else {
Err(FrequencyDetectionFailed)
}
}

/// Converts a virtual address in the guest to a physical address in the guest
pub fn virt_to_phys(
Expand Down Expand Up @@ -162,87 +67,6 @@ mod tests {
consts::{BOOT_PDE, BOOT_PDPTE, BOOT_PML4},
};

// test is derived from
// https://github.com/gz/rust-cpuid/blob/master/examples/tsc_frequency.rs
#[test]
fn test_detect_freq_from_cpuid() {
let cpuid = raw_cpuid::CpuId::new();
let has_tsc = cpuid
.get_feature_info()
.is_some_and(|finfo| finfo.has_tsc());

let has_invariant_tsc = cpuid
.get_advanced_power_mgmt_info()
.is_some_and(|apm_info| apm_info.has_invariant_tsc());

let tsc_frequency_hz = cpuid.get_tsc_info().map(|tinfo| {
if tinfo.tsc_frequency().is_some() {
tinfo.tsc_frequency()
} else {
// Skylake and Kabylake don't report the crystal clock, approximate with base frequency:
cpuid
.get_processor_frequency_info()
.map(|pinfo| pinfo.processor_base_frequency() as u64 * crate::x86_64::MHZ_TO_HZ)
.map(|cpu_base_freq_hz| {
let crystal_hz = cpu_base_freq_hz * tinfo.denominator() as u64
/ tinfo.numerator() as u64;
crystal_hz * tinfo.numerator() as u64 / tinfo.denominator() as u64
})
}
});

assert!(has_tsc, "System does not have a TSC.");

// Try to figure out TSC frequency with CPUID
println!(
"TSC Frequency is: {} ({})",
match tsc_frequency_hz {
Some(x) => format!("{} Hz", x.unwrap_or(0)),
None => String::from("unknown"),
},
if has_invariant_tsc {
"invariant"
} else {
"TSC frequency varies with speed-stepping"
}
);

// Check if we run in a VM and the hypervisor can give us the TSC frequency
cpuid.get_hypervisor_info().map(|hv| {
hv.tsc_frequency().map(|tsc_khz| {
let virtual_tsc_frequency_hz = tsc_khz as u64 * crate::x86_64::KHZ_TO_HZ;
println!("Hypervisor reports TSC Frequency at: {virtual_tsc_frequency_hz} Hz");
})
});

// Determine TSC frequency by measuring it (loop for a second, record ticks)
let one_second = crate::x86_64::Duration::from_secs(1);
let now = crate::x86_64::Instant::now();
let start = unsafe { crate::x86_64::rdtsc() };
assert!(start > 0, "Don't have rdtsc on stable!");
loop {
if now.elapsed() >= one_second {
break;
}
}
let end = unsafe { crate::x86_64::rdtsc() };
println!(
"Empirical measurement of TSC frequency was: {} Hz",
(end - start)
);
}

#[test]
fn test_get_cpu_frequency_from_os() {
let freq_res = crate::x86_64::get_cpu_frequency_from_os();
assert!(freq_res.is_ok());
let freq = freq_res.unwrap();
// The unit of the value for the first core must be in MHz.
// We presume that more than 10 GHz is incorrect.
assert!(freq > 0);
assert!(freq < 10000);
}

#[test]
fn test_virt_to_phys() {
let _ = env_logger::builder()
Expand Down
6 changes: 5 additions & 1 deletion src/linux/x86_64/kvm_cpu.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{num::NonZeroU32, sync::Arc};

use kvm_bindings::*;
use kvm_ioctls::{VcpuExit, VcpuFd, VmFd};
Expand Down Expand Up @@ -647,4 +647,8 @@ impl VirtualCPU for KvmCpu {
println!("apic_base: {:#18x}", sregs.apic_base);
println!("interrupt_bitmap: {:x?}", sregs.interrupt_bitmap);
}

fn get_cpu_frequency(&self) -> Option<NonZeroU32> {
self.vcpu.get_tsc_khz().map(|f| f.try_into().unwrap()).ok()
}
}
7 changes: 6 additions & 1 deletion src/macos/aarch64/vcpu.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(non_snake_case)]
#![allow(clippy::identity_op)]

use std::sync::Arc;
use std::{num::NonZeroU32, sync::Arc};

use log::debug;
use uhyve_interface::{GuestPhysAddr, Hypercall, HypercallAddress};
Expand Down Expand Up @@ -392,6 +392,11 @@ impl VirtualCPU for XhyveCpu {
x27 : {x27:016x} x28 : {x28:016x} x29 : {x29:016x}\n",
);
}

fn get_cpu_frequency(&self) -> Option<NonZeroU32> {
warn!("CPU base frequency detection not implemented!");
None
}
}

impl Drop for XhyveCpu {
Expand Down
6 changes: 6 additions & 0 deletions src/macos/x86_64/vcpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use std::{
arch::x86_64::__cpuid_count,
num::NonZeroU32,
sync::{Arc, LazyLock, Mutex},
};

Expand Down Expand Up @@ -1010,6 +1011,11 @@ impl VirtualCPU for XhyveCpu {
self.vcpu.read_vmcs(VMCS_GUEST_LINK_POINTER).unwrap()
);
}

fn get_cpu_frequency(&self) -> Option<NonZeroU32> {
warn!("CPU base frequency detection not implemented!");
None
}
}

impl Drop for XhyveCpu {
Expand Down
5 changes: 5 additions & 0 deletions src/vcpu.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::num::NonZeroU32;

use crate::stats::CpuStats;
/// The trait and fns that a virtual cpu requires
use crate::{os::DebugExitInfo, HypervisorResult};
Expand Down Expand Up @@ -26,4 +28,7 @@ pub trait VirtualCPU: Sized + Send {
/// Prints the VCPU's registers to stdout.
#[allow(dead_code)]
fn print_registers(&self);

/// Queries the CPUs base frequency in kHz
fn get_cpu_frequency(&self) -> Option<NonZeroU32>;
}
Loading

0 comments on commit 64ecc80

Please sign in to comment.