diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index ce2a5ea7..59b5072b 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -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}, @@ -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, -) -> std::result::Result { - 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, -) -> std::result::Result { - 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 { - // 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( @@ -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() diff --git a/src/linux/x86_64/kvm_cpu.rs b/src/linux/x86_64/kvm_cpu.rs index a2ea0dd8..84f5bd1f 100644 --- a/src/linux/x86_64/kvm_cpu.rs +++ b/src/linux/x86_64/kvm_cpu.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{num::NonZeroU32, sync::Arc}; use kvm_bindings::*; use kvm_ioctls::{VcpuExit, VcpuFd, VmFd}; @@ -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 { + self.vcpu.get_tsc_khz().map(|f| f.try_into().unwrap()).ok() + } } diff --git a/src/macos/aarch64/vcpu.rs b/src/macos/aarch64/vcpu.rs index 73b60673..0e3c16b8 100644 --- a/src/macos/aarch64/vcpu.rs +++ b/src/macos/aarch64/vcpu.rs @@ -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}; @@ -392,6 +392,11 @@ impl VirtualCPU for XhyveCpu { x27 : {x27:016x} x28 : {x28:016x} x29 : {x29:016x}\n", ); } + + fn get_cpu_frequency(&self) -> Option { + warn!("CPU base frequency detection not implemented!"); + None + } } impl Drop for XhyveCpu { diff --git a/src/macos/x86_64/vcpu.rs b/src/macos/x86_64/vcpu.rs index d8a8d783..0d2282ca 100644 --- a/src/macos/x86_64/vcpu.rs +++ b/src/macos/x86_64/vcpu.rs @@ -2,6 +2,7 @@ use std::{ arch::x86_64::__cpuid_count, + num::NonZeroU32, sync::{Arc, LazyLock, Mutex}, }; @@ -1010,6 +1011,11 @@ impl VirtualCPU for XhyveCpu { self.vcpu.read_vmcs(VMCS_GUEST_LINK_POINTER).unwrap() ); } + + fn get_cpu_frequency(&self) -> Option { + warn!("CPU base frequency detection not implemented!"); + None + } } impl Drop for XhyveCpu { diff --git a/src/vcpu.rs b/src/vcpu.rs index 29fa8e1a..13a2cb6a 100644 --- a/src/vcpu.rs +++ b/src/vcpu.rs @@ -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}; @@ -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; } diff --git a/src/vm.rs b/src/vm.rs index ae09b4aa..86e6e7cf 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -15,17 +15,12 @@ use hermit_entry::{ elf::{KernelObject, LoadedKernel, ParseKernelError}, }; use internal::VirtualizationBackendInternal; -use log::{error, warn}; -use sysinfo::System; +use log::error; use thiserror::Error; use uhyve_interface::GuestPhysAddr; -#[cfg(target_arch = "x86_64")] -use crate::arch::x86_64::{ - detect_freq_from_cpuid, detect_freq_from_cpuid_hypervisor_info, get_cpu_frequency_from_os, -}; use crate::{ - arch::{self, FrequencyDetectionFailed}, + arch, consts::*, fdt::Fdt, isolation::filemap::UhyveFileMap, @@ -53,58 +48,6 @@ pub enum LoadKernelError { pub type LoadKernelResult = Result; -pub fn detect_freq_from_sysinfo() -> std::result::Result { - debug!("Trying to detect CPU frequency using sysinfo"); - - let mut system = System::new(); - system.refresh_cpu_frequency(); - - let frequency = system.cpus().first().unwrap().frequency(); - println!("frequencies: {frequency:?}"); - - if !system.cpus().iter().all(|cpu| cpu.frequency() == frequency) { - // Even if the CPU frequencies are not all equal, the - // frequency of the "first" CPU is treated as "authoritative". - eprintln!("CPU frequencies are not all equal"); - } - - if frequency > 0 { - Ok(frequency.try_into().unwrap()) - } else { - Err(FrequencyDetectionFailed) - } -} - -// TODO: move to architecture specific section -fn detect_cpu_freq() -> u32 { - #[cfg(target_arch = "aarch64")] - let mhz: u32 = detect_freq_from_sysinfo().unwrap_or_else(|_| { - debug!("Failed to detect using sysinfo"); - 0 - }); - #[cfg(target_arch = "x86_64")] - let mhz = { - let mhz: u32 = detect_freq_from_sysinfo().unwrap_or_else(|_| { - debug!("Failed to detect using sysinfo"); - let cpuid = raw_cpuid::CpuId::new(); - detect_freq_from_cpuid(&cpuid).unwrap_or_else(|_| { - debug!("Failed to detect from cpuid"); - detect_freq_from_cpuid_hypervisor_info(&cpuid).unwrap_or_else(|_| { - debug!("Failed to detect from hypervisor_info"); - get_cpu_frequency_from_os().unwrap_or(0) - }) - }) - }); - debug!("detected a cpu frequency of {} Mhz", mhz); - - mhz - }; - if mhz == 0 { - warn!("Unable to determine processor frequency"); - } - mhz -} - #[cfg(target_os = "linux")] pub type DefaultBackend = crate::linux::x86_64::kvm_cpu::KvmVm; #[cfg(target_os = "macos")] @@ -263,7 +206,13 @@ impl UhyveVm { // TODO: Get frequency write_fdt_into_mem(&peripherals.mem, &kernel_info.params); - write_boot_info_to_mem(&peripherals.mem, load_info, cpu_count as u64); + write_boot_info_to_mem( + &peripherals.mem, + load_info, + cpu_count as u64, + vcpus[0].get_cpu_frequency(), + ); + init_guest_mem( unsafe { peripherals.mem.as_slice_mut() }, // slice only lives during this fn call ); @@ -422,7 +371,12 @@ fn write_fdt_into_mem(mem: &MmapMemory, params: &Params) { } } -fn write_boot_info_to_mem(mem: &MmapMemory, load_info: LoadInfo, num_cpus: u64) { +fn write_boot_info_to_mem( + mem: &MmapMemory, + load_info: LoadInfo, + num_cpus: u64, + cpu_freq: Option, +) { debug!("Writing BootInfo to memory"); let boot_info = BootInfo { hardware_info: HardwareInfo { @@ -437,7 +391,7 @@ fn write_boot_info_to_mem(mem: &MmapMemory, load_info: LoadInfo, num_cpus: u64) platform_info: PlatformInfo::Uhyve { has_pci: cfg!(target_os = "linux"), num_cpus: num_cpus.try_into().unwrap(), - cpu_freq: NonZeroU32::new(detect_cpu_freq() * 1000), + cpu_freq, boot_time: SystemTime::now().into(), }, }; @@ -473,25 +427,3 @@ fn load_kernel_to_mem( kernel_address, )) } - -#[cfg(test)] -mod tests { - #[test] - // derived from test_get_cpu_frequency_from_os() in src/arch/x86_64/mod.rs - fn test_detect_freq_from_sysinfo() { - let freq_res = crate::vm::detect_freq_from_sysinfo(); - - #[cfg(target_os = "macos")] - // The CI always returns 0 as freq and thus a None in the MacOS CI - if option_env!("CI").is_some() { - return; - } - - 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); - } -}