Path: blob/master/tools/testing/selftests/kvm/x86/hyperv_clock.c
38237 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Copyright (C) 2021, Red Hat, Inc.3*4* Tests for Hyper-V clocksources5*/6#include "test_util.h"7#include "kvm_util.h"8#include "processor.h"9#include "hyperv.h"1011struct ms_hyperv_tsc_page {12volatile u32 tsc_sequence;13u32 reserved1;14volatile u64 tsc_scale;15volatile s64 tsc_offset;16} __packed;1718/* Simplified mul_u64_u64_shr() */19static inline u64 mul_u64_u64_shr64(u64 a, u64 b)20{21union {22u64 ll;23struct {24u32 low, high;25} l;26} rm, rn, rh, a0, b0;27u64 c;2829a0.ll = a;30b0.ll = b;3132rm.ll = (u64)a0.l.low * b0.l.high;33rn.ll = (u64)a0.l.high * b0.l.low;34rh.ll = (u64)a0.l.high * b0.l.high;3536rh.l.low = c = rm.l.high + rn.l.high + rh.l.low;37rh.l.high = (c >> 32) + rh.l.high;3839return rh.ll;40}4142static inline void nop_loop(void)43{44int i;4546for (i = 0; i < 100000000; i++)47asm volatile("nop");48}4950static inline void check_tsc_msr_rdtsc(void)51{52u64 tsc_freq, r1, r2, t1, t2;53s64 delta_ns;5455tsc_freq = rdmsr(HV_X64_MSR_TSC_FREQUENCY);56GUEST_ASSERT(tsc_freq > 0);5758/* For increased accuracy, take mean rdtsc() before and afrer rdmsr() */59r1 = rdtsc();60t1 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);61r1 = (r1 + rdtsc()) / 2;62nop_loop();63r2 = rdtsc();64t2 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);65r2 = (r2 + rdtsc()) / 2;6667GUEST_ASSERT(r2 > r1 && t2 > t1);6869/* HV_X64_MSR_TIME_REF_COUNT is in 100ns */70delta_ns = ((t2 - t1) * 100) - ((r2 - r1) * 1000000000 / tsc_freq);71if (delta_ns < 0)72delta_ns = -delta_ns;7374/* 1% tolerance */75GUEST_ASSERT(delta_ns * 100 < (t2 - t1) * 100);76}7778static inline u64 get_tscpage_ts(struct ms_hyperv_tsc_page *tsc_page)79{80return mul_u64_u64_shr64(rdtsc(), tsc_page->tsc_scale) + tsc_page->tsc_offset;81}8283static inline void check_tsc_msr_tsc_page(struct ms_hyperv_tsc_page *tsc_page)84{85u64 r1, r2, t1, t2;8687/* Compare TSC page clocksource with HV_X64_MSR_TIME_REF_COUNT */88t1 = get_tscpage_ts(tsc_page);89r1 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);9091/* 10 ms tolerance */92GUEST_ASSERT(r1 >= t1 && r1 - t1 < 100000);93nop_loop();9495t2 = get_tscpage_ts(tsc_page);96r2 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);97GUEST_ASSERT(r2 >= t1 && r2 - t2 < 100000);98}99100static void guest_main(struct ms_hyperv_tsc_page *tsc_page, vm_paddr_t tsc_page_gpa)101{102u64 tsc_scale, tsc_offset;103104/* Set Guest OS id to enable Hyper-V emulation */105GUEST_SYNC(1);106wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID);107GUEST_SYNC(2);108109check_tsc_msr_rdtsc();110111GUEST_SYNC(3);112113/* Set up TSC page is disabled state, check that it's clean */114wrmsr(HV_X64_MSR_REFERENCE_TSC, tsc_page_gpa);115GUEST_ASSERT(tsc_page->tsc_sequence == 0);116GUEST_ASSERT(tsc_page->tsc_scale == 0);117GUEST_ASSERT(tsc_page->tsc_offset == 0);118119GUEST_SYNC(4);120121/* Set up TSC page is enabled state */122wrmsr(HV_X64_MSR_REFERENCE_TSC, tsc_page_gpa | 0x1);123GUEST_ASSERT(tsc_page->tsc_sequence != 0);124125GUEST_SYNC(5);126127check_tsc_msr_tsc_page(tsc_page);128129GUEST_SYNC(6);130131tsc_offset = tsc_page->tsc_offset;132/* Call KVM_SET_CLOCK from userspace, check that TSC page was updated */133134GUEST_SYNC(7);135/* Sanity check TSC page timestamp, it should be close to 0 */136GUEST_ASSERT(get_tscpage_ts(tsc_page) < 100000);137138GUEST_ASSERT(tsc_page->tsc_offset != tsc_offset);139140nop_loop();141142/*143* Enable Re-enlightenment and check that TSC page stays constant across144* KVM_SET_CLOCK.145*/146wrmsr(HV_X64_MSR_REENLIGHTENMENT_CONTROL, 0x1 << 16 | 0xff);147wrmsr(HV_X64_MSR_TSC_EMULATION_CONTROL, 0x1);148tsc_offset = tsc_page->tsc_offset;149tsc_scale = tsc_page->tsc_scale;150GUEST_SYNC(8);151GUEST_ASSERT(tsc_page->tsc_offset == tsc_offset);152GUEST_ASSERT(tsc_page->tsc_scale == tsc_scale);153154GUEST_SYNC(9);155156check_tsc_msr_tsc_page(tsc_page);157158/*159* Disable re-enlightenment and TSC page, check that KVM doesn't update160* it anymore.161*/162wrmsr(HV_X64_MSR_REENLIGHTENMENT_CONTROL, 0);163wrmsr(HV_X64_MSR_TSC_EMULATION_CONTROL, 0);164wrmsr(HV_X64_MSR_REFERENCE_TSC, 0);165memset(tsc_page, 0, sizeof(*tsc_page));166167GUEST_SYNC(10);168GUEST_ASSERT(tsc_page->tsc_sequence == 0);169GUEST_ASSERT(tsc_page->tsc_offset == 0);170GUEST_ASSERT(tsc_page->tsc_scale == 0);171172GUEST_DONE();173}174175static void host_check_tsc_msr_rdtsc(struct kvm_vcpu *vcpu)176{177u64 tsc_freq, r1, r2, t1, t2;178s64 delta_ns;179180tsc_freq = vcpu_get_msr(vcpu, HV_X64_MSR_TSC_FREQUENCY);181TEST_ASSERT(tsc_freq > 0, "TSC frequency must be nonzero");182183/* For increased accuracy, take mean rdtsc() before and afrer ioctl */184r1 = rdtsc();185t1 = vcpu_get_msr(vcpu, HV_X64_MSR_TIME_REF_COUNT);186r1 = (r1 + rdtsc()) / 2;187nop_loop();188r2 = rdtsc();189t2 = vcpu_get_msr(vcpu, HV_X64_MSR_TIME_REF_COUNT);190r2 = (r2 + rdtsc()) / 2;191192TEST_ASSERT(t2 > t1, "Time reference MSR is not monotonic (%ld <= %ld)", t1, t2);193194/* HV_X64_MSR_TIME_REF_COUNT is in 100ns */195delta_ns = ((t2 - t1) * 100) - ((r2 - r1) * 1000000000 / tsc_freq);196if (delta_ns < 0)197delta_ns = -delta_ns;198199/* 1% tolerance */200TEST_ASSERT(delta_ns * 100 < (t2 - t1) * 100,201"Elapsed time does not match (MSR=%ld, TSC=%ld)",202(t2 - t1) * 100, (r2 - r1) * 1000000000 / tsc_freq);203}204205int main(void)206{207struct kvm_vcpu *vcpu;208struct kvm_vm *vm;209struct ucall uc;210vm_vaddr_t tsc_page_gva;211int stage;212213TEST_REQUIRE(kvm_has_cap(KVM_CAP_HYPERV_TIME));214TEST_REQUIRE(sys_clocksource_is_based_on_tsc());215216vm = vm_create_with_one_vcpu(&vcpu, guest_main);217218vcpu_set_hv_cpuid(vcpu);219220tsc_page_gva = vm_vaddr_alloc_page(vm);221memset(addr_gva2hva(vm, tsc_page_gva), 0x0, getpagesize());222TEST_ASSERT((addr_gva2gpa(vm, tsc_page_gva) & (getpagesize() - 1)) == 0,223"TSC page has to be page aligned");224vcpu_args_set(vcpu, 2, tsc_page_gva, addr_gva2gpa(vm, tsc_page_gva));225226host_check_tsc_msr_rdtsc(vcpu);227228for (stage = 1;; stage++) {229vcpu_run(vcpu);230TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);231232switch (get_ucall(vcpu, &uc)) {233case UCALL_ABORT:234REPORT_GUEST_ASSERT(uc);235/* NOT REACHED */236case UCALL_SYNC:237break;238case UCALL_DONE:239/* Keep in sync with guest_main() */240TEST_ASSERT(stage == 11, "Testing ended prematurely, stage %d",241stage);242goto out;243default:244TEST_FAIL("Unknown ucall %lu", uc.cmd);245}246247TEST_ASSERT(!strcmp((const char *)uc.args[0], "hello") &&248uc.args[1] == stage,249"Stage %d: Unexpected register values vmexit, got %lx",250stage, (ulong)uc.args[1]);251252/* Reset kvmclock triggering TSC page update */253if (stage == 7 || stage == 8 || stage == 10) {254struct kvm_clock_data clock = {0};255256vm_ioctl(vm, KVM_SET_CLOCK, &clock);257}258}259260out:261kvm_vm_free(vm);262}263264265