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