xref: /kvm-unit-tests/x86/hyperv_clock.c (revision ddbcb8f4ca8aedc9835851bd9acf2cd5221d7476)
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  */
scale_delta(u64 delta,u64 mul_frac)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 
hvclock_tsc_to_ticks(struct hv_reference_tsc_page * shadow,uint64_t tsc)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  */
hvclock_get_time_values(struct hv_reference_tsc_page * shadow,struct hv_reference_tsc_page * page)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 
hv_clock_read(void)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 
hv_clock_test(void * data)64 static void hv_clock_test(void *data)
65 {
66 	int i = (long)data;
67 	uint64_t t_msr_prev = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
68 	uint64_t t_page_prev = hv_clock_read();
69 	uint64_t end = t_page_prev + TICKS_PER_SEC;
70 	bool got_drift = false;
71 	bool got_warp_msr = false;
72 	bool got_warp_page = false;
73 
74 	ok[i] = true;
75 	do {
76 		uint64_t t_page_1, t_page_2, t_msr;
77 
78 		t_page_1 = hv_clock_read();
79 		barrier();
80 		t_msr = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
81 		barrier();
82 		t_page_2 = hv_clock_read();
83 
84 		if (!got_drift && (t_msr < t_page_1 || t_msr > t_page_2)) {
85 			printf("drift on CPU %d, MSR value = %ld, acceptable [%ld, %ld]\n", i,
86 			       t_msr, t_page_1, t_page_2);
87 			ok[i] = false;
88 			got_drift = true;
89 		}
90 
91 		if (!got_warp_msr && t_msr < t_msr_prev) {
92 			printf("warp on CPU %d, MSR value = %ld prev MSR value = %ld!\n", i,
93 			       t_msr, t_msr_prev);
94 			ok[i] = false;
95 			got_warp_msr = true;
96 			break;
97 		}
98 
99 		if (!got_warp_page && t_page_1 < t_page_prev) {
100 			printf("warp on CPU %d, TSC page value = %ld prev TSC page value = %ld!\n", i,
101 			       t_page_1, t_page_prev);
102 			ok[i] = false;
103 			got_warp_page = true;
104 			break;
105 		}
106 
107 		t_page_prev = t_page_1;
108 		t_msr_prev = t_msr;
109 
110 	} while(t_page_prev < end);
111 
112 	barrier();
113 }
114 
check_test(int ncpus)115 static void check_test(int ncpus)
116 {
117 	int i;
118 	bool pass;
119 
120 	for (i = ncpus - 1; i >= 0; i--)
121 		on_cpu_async(i, hv_clock_test, (void *)(long)i);
122 
123 	while (cpus_active() > 1)
124 		pause();
125 
126 	pass = true;
127 	for (i = ncpus - 1; i >= 0; i--)
128 		pass &= ok[i];
129 
130 	report(pass, "TSC reference precision test");
131 }
132 
hv_perf_test(void * data)133 static void hv_perf_test(void *data)
134 {
135 	int i = (long)data;
136 	uint64_t t = hv_clock_read();
137 	uint64_t end = t + 1000000000 / 100;
138 	uint64_t local_loops = 0;
139 
140 	do {
141 		t = hv_clock_read();
142 		local_loops++;
143 	} while(t < end);
144 
145 	loops[i] = local_loops;
146 }
147 
perf_test(int ncpus)148 static void perf_test(int ncpus)
149 {
150 	int i;
151 	uint64_t total_loops;
152 
153 	for (i = ncpus - 1; i >= 0; i--)
154 		on_cpu_async(i, hv_perf_test, (void *)(long)i);
155 
156 	while (cpus_active() > 1)
157 		pause();
158 
159 	total_loops = 0;
160 	for (i = ncpus - 1; i >= 0; i--)
161 		total_loops += loops[i];
162 	printf("iterations/sec:  %" PRId64"\n", total_loops / ncpus);
163 }
164 
main(int ac,char ** av)165 int main(int ac, char **av)
166 {
167 	int ncpus;
168 	struct hv_reference_tsc_page shadow;
169 	uint64_t tsc1, t1, tsc2, t2;
170 	uint64_t ref1, ref2;
171 
172 	if (!hv_time_ref_counter_supported()) {
173 		report_skip("time reference counter is unsupported");
174 		goto done;
175 	}
176 
177 	setup_vm();
178 
179 	ncpus = cpu_count();
180 	if (ncpus > MAX_CPU)
181 		report_abort("number cpus exceeds %d", MAX_CPU);
182 
183 	hv_clock = alloc_page();
184 	wrmsr(HV_X64_MSR_REFERENCE_TSC, (u64)(uintptr_t)hv_clock | 1);
185 	report(rdmsr(HV_X64_MSR_REFERENCE_TSC) == ((u64)(uintptr_t)hv_clock | 1),
186 	       "MSR value after enabling");
187 
188 	hvclock_get_time_values(&shadow, hv_clock);
189 	if (shadow.tsc_sequence == 0 || shadow.tsc_sequence == 0xFFFFFFFF)
190 		report_abort("Reference TSC page not available\n");
191 
192 	printf("sequence: %u. scale: %" PRIx64" offset: %" PRId64"\n",
193 	       shadow.tsc_sequence, shadow.tsc_scale, shadow.tsc_offset);
194 	ref1 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
195 	tsc1 = rdtsc();
196 	t1 = hvclock_tsc_to_ticks(&shadow, tsc1);
197 	printf("refcnt %" PRId64", TSC %" PRIx64", TSC reference %" PRId64"\n",
198 	       ref1, tsc1, t1);
199 
200 	do
201 		ref2 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
202 	while (ref2 < ref1 + 2 * TICKS_PER_SEC);
203 
204 	tsc2 = rdtsc();
205 	t2 = hvclock_tsc_to_ticks(&shadow, tsc2);
206 	printf("refcnt %" PRId64" (delta %" PRId64"), TSC %" PRIx64", "
207 	       "TSC reference %" PRId64" (delta %" PRId64")\n",
208 	       ref2, ref2 - ref1, tsc2, t2, t2 - t1);
209 
210 	check_test(ncpus);
211 	perf_test(ncpus);
212 
213 	wrmsr(HV_X64_MSR_REFERENCE_TSC, 0LL);
214 	report(rdmsr(HV_X64_MSR_REFERENCE_TSC) == 0,
215 	       "MSR value after disabling");
216 
217 done:
218 	return report_summary();
219 }
220