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