crosvm/hypervisor/tests/tsc_offsets.rs
Dennis Kempin 4fea399df9 Reformat imports
crosvm is switching the import style to use one import per line.
While more verbose, this will greatly reduce the occurence of merge
conflicts going forward.

Note: This is using a nightly feature of rustfmt. So it's a one-off
re-format only. We are considering adding a nightly toolchain to
enable the feature permanently.

BUG=b:239937122
TEST=CQ

Change-Id: Id2dd4dbdc0adfc4f8f3dd1d09da1daafa2a39992
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3784345
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Dennis Kempin <denniskempin@google.com>
Commit-Queue: Dennis Kempin <denniskempin@google.com>
2022-07-28 00:15:50 +00:00

230 lines
6.1 KiB
Rust

// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(b/237714823): Currently, only kvm is enabled for this test once LUCI can run windows.
#![cfg(unix)]
#![cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use std::arch::x86_64::_rdtsc;
use hypervisor::*;
use vm_memory::GuestAddress;
use vm_memory::GuestMemory;
macro_rules! assert_wrapping_close {
($value:expr, $expected: expr, $threshold: expr, $type: expr) => {
let e = $expected;
let v = $value;
let wrapping_diff = std::cmp::min(v.wrapping_sub(e), e.wrapping_sub(v));
assert!(
wrapping_diff < $threshold,
"{} value {} too far from {}",
$type,
$value,
$expected
);
};
}
#[test]
#[cfg(unix)]
fn test_kvm_tsc_offsets() {
use hypervisor::kvm::*;
test_tsc_offsets(|guest_mem| {
let kvm = Kvm::new().expect("failed to create kvm");
let vm =
KvmVm::new(&kvm, guest_mem, ProtectionType::Unprotected).expect("failed to create vm");
(kvm, vm)
});
}
#[test]
#[cfg(feature = "haxm")]
fn test_haxm_tsc_offsets() {
use hypervisor::haxm::*;
test_tsc_offsets(|guest_mem| {
let haxm = Haxm::new().expect("failed to create haxm");
let vm = HaxmVm::new(&haxm, guest_mem).expect("failed to create vm");
(haxm, vm)
});
}
#[test]
#[cfg(feature = "gvm")]
fn test_gvm_tsc_offsets() {
use hypervisor::gvm::*;
test_tsc_offsets(|guest_mem| {
let gvm = Gvm::new().expect("failed to create gvm");
let vm = GvmVm::new(&gvm, guest_mem).expect("failed to create vm");
(gvm, vm)
});
}
#[test]
#[cfg(feature = "whpx")]
fn test_whpx_tsc_offsets() {
use hypervisor::whpx::*;
if !Whpx::is_enabled() {
return;
}
test_tsc_offsets(|guest_mem| {
let whpx = Whpx::new().expect("failed to create whpx");
let vm =
WhpxVm::new(&whpx, 1, guest_mem, CpuId::new(0), false).expect("failed to create vm");
(whpx, vm)
});
}
fn test_tsc_offsets<CreateVm, HypervisorT, VmT>(create_vm: CreateVm)
where
CreateVm: FnOnce(GuestMemory) -> (HypervisorT, VmT),
HypervisorT: Hypervisor,
VmT: VmX86_64,
{
// We're in real mode, so we need to do two memory operations to get a 64 bit value into
// memory.
/*
0x0000000000000000: 0F 31 rdtsc
0x0000000000000002: 67 66 89 51 04 mov dword ptr [ecx + 4], edx
0x0000000000000007: 67 66 89 01 mov dword ptr [ecx], eax
0x000000000000000b: F4 hlt
*/
let code: [u8; 12] = [
0x0f, 0x31, 0x67, 0x66, 0x89, 0x51, 0x04, 0x67, 0x66, 0x89, 0x01, 0xf4,
];
let mem_size = 0x4000;
let load_addr = GuestAddress(0x1000);
let guest_mem =
GuestMemory::new(&[(GuestAddress(0), mem_size)]).expect("failed to create guest mem");
guest_mem
.write_at_addr(&code[..], load_addr)
.expect("failed to write to guest memory");
let mem_clone = guest_mem.clone();
let (_, vm) = create_vm(guest_mem);
let mut vcpu = vm.create_vcpu(0).expect("new vcpu failed");
let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed");
vcpu_sregs.cs.base = 0;
vcpu_sregs.cs.selector = 0;
vcpu.set_sregs(&vcpu_sregs).expect("set sregs failed");
let run_handle = vcpu.take_run_handle(None).unwrap();
// basic case, we set MSR to 0
let tsc_now = unsafe { _rdtsc() };
test_tsc_offset_run(
&run_handle,
&mut vcpu,
&mem_clone,
load_addr,
Some(0),
None,
u64::MAX - tsc_now + 1,
0,
);
// set offset to 0
let tsc_now = unsafe { _rdtsc() };
test_tsc_offset_run(
&run_handle,
&mut vcpu,
&mem_clone,
load_addr,
None,
Some(0),
0,
tsc_now,
);
// some moderately sized offset
let tsc_now = unsafe { _rdtsc() };
let ten_seconds = 2_500_000_000 * 10;
test_tsc_offset_run(
&run_handle,
&mut vcpu,
&mem_clone,
load_addr,
None,
Some(ten_seconds),
ten_seconds,
tsc_now + ten_seconds,
);
// set offset to u64::MAX - tsc_now + 1
let tsc_now = unsafe { _rdtsc() };
test_tsc_offset_run(
&run_handle,
&mut vcpu,
&mem_clone,
load_addr,
None,
Some(u64::MAX - tsc_now + 1),
u64::MAX - tsc_now + 1,
0,
);
}
fn test_tsc_offset_run(
run_handle: &VcpuRunHandle,
vcpu: &mut Box<dyn hypervisor::VcpuX86_64>,
mem_clone: &GuestMemory,
load_addr: GuestAddress,
set_msr: Option<u64>,
set_offset: Option<u64>,
expected_get_offset: u64,
expected_rdtsc: u64,
) {
// typical TSC frequency is like 2.5GHz so if we say the threshold is within 100ms then our
// threshold is 250_000_000
let threshold = 250_000_000;
let vcpu_regs = Regs {
rip: load_addr.offset() as u64,
rflags: 2,
rcx: 0x3000,
..Default::default()
};
vcpu.set_regs(&vcpu_regs).expect("set regs failed");
if let Some(value) = set_msr {
vcpu.set_msrs(&[Register {
id: 0x00000010,
value,
}])
.expect("set_msrs should not fail");
}
if let Some(offset) = set_offset {
vcpu.set_tsc_offset(offset)
.expect("set offset should not fail");
}
loop {
match vcpu.run(run_handle).expect("run failed") {
VcpuExit::Hlt => {
break;
}
// Continue on external interrupt or signal
VcpuExit::Intr => continue,
r => panic!("unexpected exit reason: {:?}", r),
}
}
assert_wrapping_close!(
mem_clone
.read_obj_from_addr::<u64>(GuestAddress(0x3000))
.expect("guest mem read should be ok"),
expected_rdtsc,
threshold,
"rdtsc written to memory"
);
assert_wrapping_close!(
vcpu.get_tsc_offset().expect("get offset should not fail"),
expected_get_offset,
threshold,
"tsc offset"
);
}