xref: /kvm-unit-tests/x86/hyperv_clock.c (revision 2c96b77ec9d3b1fcec7525174e23a6240ee05949)
1 #include "libcflat.h"
2 #include "smp.h"
3 #include "atomic.h"
4 #include "processor.h"
5 #include "hyperv.h"
6 #include "vm.h"
7 #include "alloc_page.h"
8 
9 #define MAX_CPU 4
10 #define TICKS_PER_SEC (1000000000 / 100)
11 
12 struct hv_reference_tsc_page *hv_clock;
13 
14 /*
15  * Scale a 64-bit delta by scaling and multiplying by a 32-bit fraction,
16  * yielding a 64-bit result.
17  */
18 static inline u64 scale_delta(u64 delta, u64 mul_frac)
19 {
20 	u64 product, unused;
21 
22 	__asm__ (
23 		"mulq %3"
24 		: "=d" (product), "=a" (unused) : "1" (delta), "rm" ((u64)mul_frac) );
25 
26 	return product;
27 }
28 
29 static u64 hvclock_tsc_to_ticks(struct hv_reference_tsc_page *shadow, uint64_t tsc)
30 {
31 	u64 delta = tsc;
32 	return scale_delta(delta, shadow->tsc_scale) + shadow->tsc_offset;
33 }
34 
35 /*
36  * Reads a consistent set of time-base values from hypervisor,
37  * into a shadow data area.
38  */
39 static void hvclock_get_time_values(struct hv_reference_tsc_page *shadow,
40 				    struct hv_reference_tsc_page *page)
41 {
42 	int seq;
43 	do {
44 		seq = page->tsc_sequence;
45 		rmb();		/* fetch version before data */
46 		*shadow = *page;
47 		rmb();		/* test version after fetching data */
48 	} while (shadow->tsc_sequence != seq);
49 }
50 
51 static uint64_t hv_clock_read(void)
52 {
53 	struct hv_reference_tsc_page shadow;
54 
55 	hvclock_get_time_values(&shadow, hv_clock);
56 	return hvclock_tsc_to_ticks(&shadow, rdtsc());
57 }
58 
59 bool ok[MAX_CPU];
60 uint64_t loops[MAX_CPU];
61 
62 #define iabs(x)   ((x) < 0 ? -(x) : (x))
63 
64 static void hv_clock_test(void *data)
65 {
66 	int i = smp_id();
67 	uint64_t t = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
68 	uint64_t end = t + 3 * TICKS_PER_SEC;
69 	uint64_t msr_sample = t + TICKS_PER_SEC;
70 	int min_delta = 123456, max_delta = -123456;
71 	bool got_drift = false;
72 	bool got_warp = false;
73 
74 	ok[i] = true;
75 	do {
76 		uint64_t now = hv_clock_read();
77 		int delta = rdmsr(HV_X64_MSR_TIME_REF_COUNT) - now;
78 
79 		min_delta = delta < min_delta ? delta : min_delta;
80 		if (t < msr_sample) {
81 			max_delta = delta > max_delta ? delta: max_delta;
82 		} else if (delta < 0 || delta > max_delta * 3 / 2) {
83 			printf("suspecting drift on CPU %d? delta = %d, acceptable [0, %d)\n", smp_id(),
84 			       delta, max_delta);
85 			ok[i] = false;
86 			got_drift = true;
87 			max_delta *= 2;
88 		}
89 
90 		if (now < t && !got_warp) {
91 			printf("warp on CPU %d!\n", smp_id());
92 			ok[i] = false;
93 			got_warp = true;
94 			break;
95 		}
96 		t = now;
97 	} while(t < end);
98 
99 	if (!got_drift)
100 		printf("delta on CPU %d was %d...%d\n", smp_id(), min_delta, max_delta);
101 	barrier();
102 }
103 
104 static void check_test(int ncpus)
105 {
106 	int i;
107 	bool pass;
108 
109 	on_cpus(hv_clock_test, NULL);
110 
111 	pass = true;
112 	for (i = ncpus - 1; i >= 0; i--)
113 		pass &= ok[i];
114 
115 	report(pass, "TSC reference precision test");
116 }
117 
118 static void hv_perf_test(void *data)
119 {
120 	uint64_t t = hv_clock_read();
121 	uint64_t end = t + 1000000000 / 100;
122 	uint64_t local_loops = 0;
123 
124 	do {
125 		t = hv_clock_read();
126 		local_loops++;
127 	} while(t < end);
128 
129 	loops[smp_id()] = local_loops;
130 }
131 
132 static void perf_test(int ncpus)
133 {
134 	int i;
135 	uint64_t total_loops;
136 
137 	on_cpus(hv_perf_test, NULL);
138 
139 	total_loops = 0;
140 	for (i = ncpus - 1; i >= 0; i--)
141 		total_loops += loops[i];
142 	printf("iterations/sec:  %" PRId64"\n", total_loops / ncpus);
143 }
144 
145 int main(int ac, char **av)
146 {
147 	int nerr = 0;
148 	int ncpus;
149 	struct hv_reference_tsc_page shadow;
150 	uint64_t tsc1, t1, tsc2, t2;
151 	uint64_t ref1, ref2;
152 
153 	if (!hv_time_ref_counter_supported()) {
154 		report_skip("time reference counter is unsupported");
155 		return report_summary();
156 	}
157 
158 	setup_vm();
159 
160 	ncpus = cpu_count();
161 	if (ncpus > MAX_CPU)
162 		report_abort("number cpus exceeds %d", MAX_CPU);
163 
164 	hv_clock = alloc_page();
165 	wrmsr(HV_X64_MSR_REFERENCE_TSC, (u64)(uintptr_t)hv_clock | 1);
166 	report(rdmsr(HV_X64_MSR_REFERENCE_TSC) == ((u64)(uintptr_t)hv_clock | 1),
167 	       "MSR value after enabling");
168 
169 	hvclock_get_time_values(&shadow, hv_clock);
170 	if (shadow.tsc_sequence == 0 || shadow.tsc_sequence == 0xFFFFFFFF) {
171 		printf("Reference TSC page not available\n");
172 		exit(1);
173 	}
174 
175 	printf("scale: %" PRIx64" offset: %" PRId64"\n", shadow.tsc_scale, shadow.tsc_offset);
176 	ref1 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
177 	tsc1 = rdtsc();
178 	t1 = hvclock_tsc_to_ticks(&shadow, tsc1);
179 	printf("refcnt %" PRId64", TSC %" PRIx64", TSC reference %" PRId64"\n",
180 	       ref1, tsc1, t1);
181 
182 	do
183 		ref2 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
184 	while (ref2 < ref1 + 2 * TICKS_PER_SEC);
185 
186 	tsc2 = rdtsc();
187 	t2 = hvclock_tsc_to_ticks(&shadow, tsc2);
188 	printf("refcnt %" PRId64" (delta %" PRId64"), TSC %" PRIx64", "
189 	       "TSC reference %" PRId64" (delta %" PRId64")\n",
190 	       ref2, ref2 - ref1, tsc2, t2, t2 - t1);
191 
192 	check_test(ncpus);
193 	perf_test(ncpus);
194 
195 	wrmsr(HV_X64_MSR_REFERENCE_TSC, 0LL);
196 	report(rdmsr(HV_X64_MSR_REFERENCE_TSC) == 0,
197 	       "MSR value after disabling");
198 
199 	return nerr > 0 ? 1 : 0;
200 }
201