12c7f76b4SVitaly Kuznetsov // SPDX-License-Identifier: GPL-2.0-only
22c7f76b4SVitaly Kuznetsov /*
32c7f76b4SVitaly Kuznetsov * Copyright (C) 2021, Red Hat, Inc.
42c7f76b4SVitaly Kuznetsov *
52c7f76b4SVitaly Kuznetsov * Tests for Hyper-V clocksources
62c7f76b4SVitaly Kuznetsov */
72c7f76b4SVitaly Kuznetsov #include "test_util.h"
82c7f76b4SVitaly Kuznetsov #include "kvm_util.h"
92c7f76b4SVitaly Kuznetsov #include "processor.h"
1075a3f428SVitaly Kuznetsov #include "hyperv.h"
112c7f76b4SVitaly Kuznetsov
122c7f76b4SVitaly Kuznetsov struct ms_hyperv_tsc_page {
132c7f76b4SVitaly Kuznetsov volatile u32 tsc_sequence;
142c7f76b4SVitaly Kuznetsov u32 reserved1;
152c7f76b4SVitaly Kuznetsov volatile u64 tsc_scale;
162c7f76b4SVitaly Kuznetsov volatile s64 tsc_offset;
172c7f76b4SVitaly Kuznetsov } __packed;
182c7f76b4SVitaly Kuznetsov
192c7f76b4SVitaly Kuznetsov /* Simplified mul_u64_u64_shr() */
mul_u64_u64_shr64(u64 a,u64 b)202c7f76b4SVitaly Kuznetsov static inline u64 mul_u64_u64_shr64(u64 a, u64 b)
212c7f76b4SVitaly Kuznetsov {
222c7f76b4SVitaly Kuznetsov union {
232c7f76b4SVitaly Kuznetsov u64 ll;
242c7f76b4SVitaly Kuznetsov struct {
252c7f76b4SVitaly Kuznetsov u32 low, high;
262c7f76b4SVitaly Kuznetsov } l;
272c7f76b4SVitaly Kuznetsov } rm, rn, rh, a0, b0;
282c7f76b4SVitaly Kuznetsov u64 c;
292c7f76b4SVitaly Kuznetsov
302c7f76b4SVitaly Kuznetsov a0.ll = a;
312c7f76b4SVitaly Kuznetsov b0.ll = b;
322c7f76b4SVitaly Kuznetsov
332c7f76b4SVitaly Kuznetsov rm.ll = (u64)a0.l.low * b0.l.high;
342c7f76b4SVitaly Kuznetsov rn.ll = (u64)a0.l.high * b0.l.low;
352c7f76b4SVitaly Kuznetsov rh.ll = (u64)a0.l.high * b0.l.high;
362c7f76b4SVitaly Kuznetsov
372c7f76b4SVitaly Kuznetsov rh.l.low = c = rm.l.high + rn.l.high + rh.l.low;
382c7f76b4SVitaly Kuznetsov rh.l.high = (c >> 32) + rh.l.high;
392c7f76b4SVitaly Kuznetsov
402c7f76b4SVitaly Kuznetsov return rh.ll;
412c7f76b4SVitaly Kuznetsov }
422c7f76b4SVitaly Kuznetsov
nop_loop(void)432c7f76b4SVitaly Kuznetsov static inline void nop_loop(void)
442c7f76b4SVitaly Kuznetsov {
452c7f76b4SVitaly Kuznetsov int i;
462c7f76b4SVitaly Kuznetsov
47eae260beSVitaly Kuznetsov for (i = 0; i < 100000000; i++)
482c7f76b4SVitaly Kuznetsov asm volatile("nop");
492c7f76b4SVitaly Kuznetsov }
502c7f76b4SVitaly Kuznetsov
check_tsc_msr_rdtsc(void)512c7f76b4SVitaly Kuznetsov static inline void check_tsc_msr_rdtsc(void)
522c7f76b4SVitaly Kuznetsov {
532c7f76b4SVitaly Kuznetsov u64 tsc_freq, r1, r2, t1, t2;
542c7f76b4SVitaly Kuznetsov s64 delta_ns;
552c7f76b4SVitaly Kuznetsov
562c7f76b4SVitaly Kuznetsov tsc_freq = rdmsr(HV_X64_MSR_TSC_FREQUENCY);
572c7f76b4SVitaly Kuznetsov GUEST_ASSERT(tsc_freq > 0);
582c7f76b4SVitaly Kuznetsov
59eae260beSVitaly Kuznetsov /* For increased accuracy, take mean rdtsc() before and afrer rdmsr() */
602c7f76b4SVitaly Kuznetsov r1 = rdtsc();
612c7f76b4SVitaly Kuznetsov t1 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
62eae260beSVitaly Kuznetsov r1 = (r1 + rdtsc()) / 2;
632c7f76b4SVitaly Kuznetsov nop_loop();
642c7f76b4SVitaly Kuznetsov r2 = rdtsc();
652c7f76b4SVitaly Kuznetsov t2 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
66eae260beSVitaly Kuznetsov r2 = (r2 + rdtsc()) / 2;
672c7f76b4SVitaly Kuznetsov
682c7f76b4SVitaly Kuznetsov GUEST_ASSERT(r2 > r1 && t2 > t1);
692c7f76b4SVitaly Kuznetsov
702c7f76b4SVitaly Kuznetsov /* HV_X64_MSR_TIME_REF_COUNT is in 100ns */
712c7f76b4SVitaly Kuznetsov delta_ns = ((t2 - t1) * 100) - ((r2 - r1) * 1000000000 / tsc_freq);
722c7f76b4SVitaly Kuznetsov if (delta_ns < 0)
732c7f76b4SVitaly Kuznetsov delta_ns = -delta_ns;
742c7f76b4SVitaly Kuznetsov
752c7f76b4SVitaly Kuznetsov /* 1% tolerance */
762c7f76b4SVitaly Kuznetsov GUEST_ASSERT(delta_ns * 100 < (t2 - t1) * 100);
772c7f76b4SVitaly Kuznetsov }
782c7f76b4SVitaly Kuznetsov
get_tscpage_ts(struct ms_hyperv_tsc_page * tsc_page)7955626ca9SVitaly Kuznetsov static inline u64 get_tscpage_ts(struct ms_hyperv_tsc_page *tsc_page)
8055626ca9SVitaly Kuznetsov {
8155626ca9SVitaly Kuznetsov return mul_u64_u64_shr64(rdtsc(), tsc_page->tsc_scale) + tsc_page->tsc_offset;
8255626ca9SVitaly Kuznetsov }
8355626ca9SVitaly Kuznetsov
check_tsc_msr_tsc_page(struct ms_hyperv_tsc_page * tsc_page)842c7f76b4SVitaly Kuznetsov static inline void check_tsc_msr_tsc_page(struct ms_hyperv_tsc_page *tsc_page)
852c7f76b4SVitaly Kuznetsov {
862c7f76b4SVitaly Kuznetsov u64 r1, r2, t1, t2;
872c7f76b4SVitaly Kuznetsov
882c7f76b4SVitaly Kuznetsov /* Compare TSC page clocksource with HV_X64_MSR_TIME_REF_COUNT */
8955626ca9SVitaly Kuznetsov t1 = get_tscpage_ts(tsc_page);
902c7f76b4SVitaly Kuznetsov r1 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
912c7f76b4SVitaly Kuznetsov
922c7f76b4SVitaly Kuznetsov /* 10 ms tolerance */
932c7f76b4SVitaly Kuznetsov GUEST_ASSERT(r1 >= t1 && r1 - t1 < 100000);
942c7f76b4SVitaly Kuznetsov nop_loop();
952c7f76b4SVitaly Kuznetsov
9655626ca9SVitaly Kuznetsov t2 = get_tscpage_ts(tsc_page);
972c7f76b4SVitaly Kuznetsov r2 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
982c7f76b4SVitaly Kuznetsov GUEST_ASSERT(r2 >= t1 && r2 - t2 < 100000);
992c7f76b4SVitaly Kuznetsov }
1002c7f76b4SVitaly Kuznetsov
guest_main(struct ms_hyperv_tsc_page * tsc_page,vm_paddr_t tsc_page_gpa)1012c7f76b4SVitaly Kuznetsov static void guest_main(struct ms_hyperv_tsc_page *tsc_page, vm_paddr_t tsc_page_gpa)
1022c7f76b4SVitaly Kuznetsov {
1032c7f76b4SVitaly Kuznetsov u64 tsc_scale, tsc_offset;
1042c7f76b4SVitaly Kuznetsov
1052c7f76b4SVitaly Kuznetsov /* Set Guest OS id to enable Hyper-V emulation */
1062c7f76b4SVitaly Kuznetsov GUEST_SYNC(1);
107f6509201SVipin Sharma wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID);
1082c7f76b4SVitaly Kuznetsov GUEST_SYNC(2);
1092c7f76b4SVitaly Kuznetsov
1102c7f76b4SVitaly Kuznetsov check_tsc_msr_rdtsc();
1112c7f76b4SVitaly Kuznetsov
1122c7f76b4SVitaly Kuznetsov GUEST_SYNC(3);
1132c7f76b4SVitaly Kuznetsov
1142c7f76b4SVitaly Kuznetsov /* Set up TSC page is disabled state, check that it's clean */
1152c7f76b4SVitaly Kuznetsov wrmsr(HV_X64_MSR_REFERENCE_TSC, tsc_page_gpa);
1162c7f76b4SVitaly Kuznetsov GUEST_ASSERT(tsc_page->tsc_sequence == 0);
1172c7f76b4SVitaly Kuznetsov GUEST_ASSERT(tsc_page->tsc_scale == 0);
1182c7f76b4SVitaly Kuznetsov GUEST_ASSERT(tsc_page->tsc_offset == 0);
1192c7f76b4SVitaly Kuznetsov
1202c7f76b4SVitaly Kuznetsov GUEST_SYNC(4);
1212c7f76b4SVitaly Kuznetsov
1222c7f76b4SVitaly Kuznetsov /* Set up TSC page is enabled state */
1232c7f76b4SVitaly Kuznetsov wrmsr(HV_X64_MSR_REFERENCE_TSC, tsc_page_gpa | 0x1);
1242c7f76b4SVitaly Kuznetsov GUEST_ASSERT(tsc_page->tsc_sequence != 0);
1252c7f76b4SVitaly Kuznetsov
1262c7f76b4SVitaly Kuznetsov GUEST_SYNC(5);
1272c7f76b4SVitaly Kuznetsov
1282c7f76b4SVitaly Kuznetsov check_tsc_msr_tsc_page(tsc_page);
1292c7f76b4SVitaly Kuznetsov
1302c7f76b4SVitaly Kuznetsov GUEST_SYNC(6);
1312c7f76b4SVitaly Kuznetsov
1322c7f76b4SVitaly Kuznetsov tsc_offset = tsc_page->tsc_offset;
1332c7f76b4SVitaly Kuznetsov /* Call KVM_SET_CLOCK from userspace, check that TSC page was updated */
13455626ca9SVitaly Kuznetsov
1352c7f76b4SVitaly Kuznetsov GUEST_SYNC(7);
13655626ca9SVitaly Kuznetsov /* Sanity check TSC page timestamp, it should be close to 0 */
13755626ca9SVitaly Kuznetsov GUEST_ASSERT(get_tscpage_ts(tsc_page) < 100000);
13855626ca9SVitaly Kuznetsov
1392c7f76b4SVitaly Kuznetsov GUEST_ASSERT(tsc_page->tsc_offset != tsc_offset);
1402c7f76b4SVitaly Kuznetsov
1412c7f76b4SVitaly Kuznetsov nop_loop();
1422c7f76b4SVitaly Kuznetsov
1432c7f76b4SVitaly Kuznetsov /*
1442c7f76b4SVitaly Kuznetsov * Enable Re-enlightenment and check that TSC page stays constant across
1452c7f76b4SVitaly Kuznetsov * KVM_SET_CLOCK.
1462c7f76b4SVitaly Kuznetsov */
1472c7f76b4SVitaly Kuznetsov wrmsr(HV_X64_MSR_REENLIGHTENMENT_CONTROL, 0x1 << 16 | 0xff);
1482c7f76b4SVitaly Kuznetsov wrmsr(HV_X64_MSR_TSC_EMULATION_CONTROL, 0x1);
1492c7f76b4SVitaly Kuznetsov tsc_offset = tsc_page->tsc_offset;
1502c7f76b4SVitaly Kuznetsov tsc_scale = tsc_page->tsc_scale;
1512c7f76b4SVitaly Kuznetsov GUEST_SYNC(8);
1522c7f76b4SVitaly Kuznetsov GUEST_ASSERT(tsc_page->tsc_offset == tsc_offset);
1532c7f76b4SVitaly Kuznetsov GUEST_ASSERT(tsc_page->tsc_scale == tsc_scale);
1542c7f76b4SVitaly Kuznetsov
1552c7f76b4SVitaly Kuznetsov GUEST_SYNC(9);
1562c7f76b4SVitaly Kuznetsov
1572c7f76b4SVitaly Kuznetsov check_tsc_msr_tsc_page(tsc_page);
1582c7f76b4SVitaly Kuznetsov
1592c7f76b4SVitaly Kuznetsov /*
1602c7f76b4SVitaly Kuznetsov * Disable re-enlightenment and TSC page, check that KVM doesn't update
1612c7f76b4SVitaly Kuznetsov * it anymore.
1622c7f76b4SVitaly Kuznetsov */
1632c7f76b4SVitaly Kuznetsov wrmsr(HV_X64_MSR_REENLIGHTENMENT_CONTROL, 0);
1642c7f76b4SVitaly Kuznetsov wrmsr(HV_X64_MSR_TSC_EMULATION_CONTROL, 0);
1652c7f76b4SVitaly Kuznetsov wrmsr(HV_X64_MSR_REFERENCE_TSC, 0);
1662c7f76b4SVitaly Kuznetsov memset(tsc_page, 0, sizeof(*tsc_page));
1672c7f76b4SVitaly Kuznetsov
1682c7f76b4SVitaly Kuznetsov GUEST_SYNC(10);
1692c7f76b4SVitaly Kuznetsov GUEST_ASSERT(tsc_page->tsc_sequence == 0);
1702c7f76b4SVitaly Kuznetsov GUEST_ASSERT(tsc_page->tsc_offset == 0);
1712c7f76b4SVitaly Kuznetsov GUEST_ASSERT(tsc_page->tsc_scale == 0);
1722c7f76b4SVitaly Kuznetsov
1732c7f76b4SVitaly Kuznetsov GUEST_DONE();
1742c7f76b4SVitaly Kuznetsov }
1752c7f76b4SVitaly Kuznetsov
host_check_tsc_msr_rdtsc(struct kvm_vcpu * vcpu)176a8581637SSean Christopherson static void host_check_tsc_msr_rdtsc(struct kvm_vcpu *vcpu)
1772c7f76b4SVitaly Kuznetsov {
1782c7f76b4SVitaly Kuznetsov u64 tsc_freq, r1, r2, t1, t2;
1792c7f76b4SVitaly Kuznetsov s64 delta_ns;
1802c7f76b4SVitaly Kuznetsov
181768e9a61SSean Christopherson tsc_freq = vcpu_get_msr(vcpu, HV_X64_MSR_TSC_FREQUENCY);
1822c7f76b4SVitaly Kuznetsov TEST_ASSERT(tsc_freq > 0, "TSC frequency must be nonzero");
1832c7f76b4SVitaly Kuznetsov
184eae260beSVitaly Kuznetsov /* For increased accuracy, take mean rdtsc() before and afrer ioctl */
1852c7f76b4SVitaly Kuznetsov r1 = rdtsc();
186768e9a61SSean Christopherson t1 = vcpu_get_msr(vcpu, HV_X64_MSR_TIME_REF_COUNT);
187eae260beSVitaly Kuznetsov r1 = (r1 + rdtsc()) / 2;
1882c7f76b4SVitaly Kuznetsov nop_loop();
1892c7f76b4SVitaly Kuznetsov r2 = rdtsc();
190768e9a61SSean Christopherson t2 = vcpu_get_msr(vcpu, HV_X64_MSR_TIME_REF_COUNT);
191eae260beSVitaly Kuznetsov r2 = (r2 + rdtsc()) / 2;
1922c7f76b4SVitaly Kuznetsov
1932c7f76b4SVitaly Kuznetsov TEST_ASSERT(t2 > t1, "Time reference MSR is not monotonic (%ld <= %ld)", t1, t2);
1942c7f76b4SVitaly Kuznetsov
1952c7f76b4SVitaly Kuznetsov /* HV_X64_MSR_TIME_REF_COUNT is in 100ns */
1962c7f76b4SVitaly Kuznetsov delta_ns = ((t2 - t1) * 100) - ((r2 - r1) * 1000000000 / tsc_freq);
1972c7f76b4SVitaly Kuznetsov if (delta_ns < 0)
1982c7f76b4SVitaly Kuznetsov delta_ns = -delta_ns;
1992c7f76b4SVitaly Kuznetsov
2002c7f76b4SVitaly Kuznetsov /* 1% tolerance */
2012c7f76b4SVitaly Kuznetsov TEST_ASSERT(delta_ns * 100 < (t2 - t1) * 100,
2022c7f76b4SVitaly Kuznetsov "Elapsed time does not match (MSR=%ld, TSC=%ld)",
2032c7f76b4SVitaly Kuznetsov (t2 - t1) * 100, (r2 - r1) * 1000000000 / tsc_freq);
2042c7f76b4SVitaly Kuznetsov }
2052c7f76b4SVitaly Kuznetsov
main(void)2062c7f76b4SVitaly Kuznetsov int main(void)
2072c7f76b4SVitaly Kuznetsov {
208a8581637SSean Christopherson struct kvm_vcpu *vcpu;
2092c7f76b4SVitaly Kuznetsov struct kvm_vm *vm;
2102c7f76b4SVitaly Kuznetsov struct ucall uc;
2112c7f76b4SVitaly Kuznetsov vm_vaddr_t tsc_page_gva;
2122c7f76b4SVitaly Kuznetsov int stage;
2132c7f76b4SVitaly Kuznetsov
214a8581637SSean Christopherson vm = vm_create_with_one_vcpu(&vcpu, guest_main);
2152c7f76b4SVitaly Kuznetsov
216768e9a61SSean Christopherson vcpu_set_hv_cpuid(vcpu);
2172c7f76b4SVitaly Kuznetsov
218a9db9609SSean Christopherson tsc_page_gva = vm_vaddr_alloc_page(vm);
21913c2c3cfSMaxim Levitsky memset(addr_gva2hva(vm, tsc_page_gva), 0x0, getpagesize());
2202c7f76b4SVitaly Kuznetsov TEST_ASSERT((addr_gva2gpa(vm, tsc_page_gva) & (getpagesize() - 1)) == 0,
2212c7f76b4SVitaly Kuznetsov "TSC page has to be page aligned\n");
222768e9a61SSean Christopherson vcpu_args_set(vcpu, 2, tsc_page_gva, addr_gva2gpa(vm, tsc_page_gva));
2232c7f76b4SVitaly Kuznetsov
224a8581637SSean Christopherson host_check_tsc_msr_rdtsc(vcpu);
2252c7f76b4SVitaly Kuznetsov
2262c7f76b4SVitaly Kuznetsov for (stage = 1;; stage++) {
227768e9a61SSean Christopherson vcpu_run(vcpu);
228*c96f57b0SVipin Sharma TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
2292c7f76b4SVitaly Kuznetsov
230768e9a61SSean Christopherson switch (get_ucall(vcpu, &uc)) {
2312c7f76b4SVitaly Kuznetsov case UCALL_ABORT:
232594a1c27SColton Lewis REPORT_GUEST_ASSERT(uc);
2332c7f76b4SVitaly Kuznetsov /* NOT REACHED */
2342c7f76b4SVitaly Kuznetsov case UCALL_SYNC:
2352c7f76b4SVitaly Kuznetsov break;
2362c7f76b4SVitaly Kuznetsov case UCALL_DONE:
2372c7f76b4SVitaly Kuznetsov /* Keep in sync with guest_main() */
2382c7f76b4SVitaly Kuznetsov TEST_ASSERT(stage == 11, "Testing ended prematurely, stage %d\n",
2392c7f76b4SVitaly Kuznetsov stage);
2402c7f76b4SVitaly Kuznetsov goto out;
2412c7f76b4SVitaly Kuznetsov default:
2422c7f76b4SVitaly Kuznetsov TEST_FAIL("Unknown ucall %lu", uc.cmd);
2432c7f76b4SVitaly Kuznetsov }
2442c7f76b4SVitaly Kuznetsov
2452c7f76b4SVitaly Kuznetsov TEST_ASSERT(!strcmp((const char *)uc.args[0], "hello") &&
2462c7f76b4SVitaly Kuznetsov uc.args[1] == stage,
2472c7f76b4SVitaly Kuznetsov "Stage %d: Unexpected register values vmexit, got %lx",
2482c7f76b4SVitaly Kuznetsov stage, (ulong)uc.args[1]);
2492c7f76b4SVitaly Kuznetsov
2502c7f76b4SVitaly Kuznetsov /* Reset kvmclock triggering TSC page update */
2512c7f76b4SVitaly Kuznetsov if (stage == 7 || stage == 8 || stage == 10) {
2522c7f76b4SVitaly Kuznetsov struct kvm_clock_data clock = {0};
2532c7f76b4SVitaly Kuznetsov
2542c7f76b4SVitaly Kuznetsov vm_ioctl(vm, KVM_SET_CLOCK, &clock);
2552c7f76b4SVitaly Kuznetsov }
2562c7f76b4SVitaly Kuznetsov }
2572c7f76b4SVitaly Kuznetsov
2582c7f76b4SVitaly Kuznetsov out:
2592c7f76b4SVitaly Kuznetsov kvm_vm_free(vm);
2602c7f76b4SVitaly Kuznetsov }
261