xref: /openbmc/linux/tools/testing/selftests/kvm/x86_64/hyperv_clock.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
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