1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2023 Oxide Computer Company
14 */
15
16 /*
17 * Test guest frequency control
18 *
19 * Note: requires `vmm_allow_state_writes` to be set, and only on AMD
20 */
21
22 #include <unistd.h>
23 #include <stdlib.h>
24 #include <libgen.h>
25 #include <errno.h>
26 #include <err.h>
27
28 #include <sys/time.h>
29 #include <sys/vmm_data.h>
30 #include <sys/vmm_dev.h>
31 #include <vmmapi.h>
32
33 #include "common.h"
34 #include "in_guest.h"
35
36 #define PPM_MARGIN 200
37
38 typedef struct tsc_reading {
39 hrtime_t when;
40 uint64_t tsc;
41 } tsc_reading_t;
42
43 static bool
check_reading(tsc_reading_t r1,tsc_reading_t r2,uint64_t guest_freq,uint64_t min_ticks,uint64_t tick_margin,uint32_t ppm_margin)44 check_reading(tsc_reading_t r1, tsc_reading_t r2, uint64_t guest_freq,
45 uint64_t min_ticks, uint64_t tick_margin, uint32_t ppm_margin)
46 {
47 hrtime_t time_delta = r2.when - r1.when;
48 uint64_t tick_delta = r2.tsc - r1.tsc;
49
50 /* check that number of ticks seen is valid */
51 if (tick_delta < min_ticks) {
52 test_fail_msg("inadequate passage of guest TSC ticks %u < %u\n",
53 tick_delta, min_ticks);
54 } else if ((tick_delta - min_ticks) > tick_margin) {
55 (void) printf("%u ticks outside margin %u\n", tick_delta,
56 min_ticks + tick_margin);
57 return (false);
58 }
59
60 /* compute ppm error and validate */
61 hrtime_t time_target = (tick_delta * NANOSEC) / guest_freq;
62 hrtime_t offset;
63 if (time_delta < time_target) {
64 offset = time_target - time_delta;
65 } else {
66 offset = time_delta - time_target;
67 }
68 uint64_t ppm = (offset * 1000000) / time_target;
69 (void) printf("%u ticks in %lu ns (error %lu ppm)\n",
70 tick_delta, time_delta, ppm);
71 if (ppm > ppm_margin) {
72 (void) printf("UNACCEPTABLE!\n");
73 return (false);
74 }
75 return (true);
76 }
77
78 void
do_freq_test(uint64_t guest_freq,uint8_t per_sec,uint8_t seconds,const int vmfd,struct vcpu * vcpu,struct vdi_time_info_v1 * src)79 do_freq_test(uint64_t guest_freq, uint8_t per_sec, uint8_t seconds,
80 const int vmfd, struct vcpu *vcpu, struct vdi_time_info_v1 *src)
81 {
82 /* configure the guest to have the desired frequency */
83 struct vdi_time_info_v1 time_info = {
84 .vt_guest_freq = guest_freq,
85 .vt_guest_tsc = src->vt_guest_tsc,
86 .vt_boot_hrtime = src->vt_boot_hrtime,
87 .vt_hrtime = src->vt_hrtime,
88 .vt_hres_sec = src->vt_hres_sec,
89 .vt_hres_ns = src->vt_hres_ns,
90 };
91 struct vm_data_xfer xfer = {
92 .vdx_class = VDC_VMM_TIME,
93 .vdx_version = 1,
94 .vdx_len = sizeof (struct vdi_time_info_v1),
95 .vdx_data = &time_info,
96 };
97 if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
98 int error;
99 error = errno;
100 if (error == EPERM) {
101 warn("VMM_DATA_WRITE got EPERM: is "
102 "vmm_allow_state_writes set?");
103 }
104 errx(EXIT_FAILURE, "VMM_DATA_WRITE of time info failed");
105 }
106
107
108 /*
109 * Run the test:
110 * - ask the guest to report the TSC every 1/per_sec seconds, in terms
111 * of guest ticks
112 * - collect readings for `seconds` seconds, along with host hrtime
113 * - to avoid additional latency in the readings, process readings at
114 * the end: check for error in the number of ticks reported and the
115 * ppm based on host hrtime
116 */
117 uint64_t guest_ticks = guest_freq / per_sec;
118 const uint32_t nreadings = per_sec * seconds + 1;
119 tsc_reading_t tsc_readings[nreadings];
120
121 /* 5 percent margin */
122 const uint32_t tick_margin = guest_ticks / 20;
123
124 bool half_read = false;
125 uint64_t cur_tsc;
126 uint32_t count = 0;
127
128 struct vm_entry ventry = { 0 };
129 struct vm_exit vexit = { 0 };
130
131 do {
132 if (count >= nreadings) {
133 /* test completed: check results */
134 for (int i = 1; i < nreadings; i++) {
135 if (!check_reading(tsc_readings[i-1],
136 tsc_readings[i], guest_freq, guest_ticks,
137 tick_margin, PPM_MARGIN)) {
138 test_fail_msg("freq test failed");
139 }
140 }
141 break;
142 }
143
144 const enum vm_exit_kind kind =
145 test_run_vcpu(vcpu, &ventry, &vexit);
146
147 if (kind == VEK_REENTR) {
148 continue;
149 } else if (kind != VEK_UNHANDLED) {
150 test_fail_vmexit(&vexit);
151 }
152
153 uint32_t val;
154 if (vexit_match_inout(&vexit, true, IOP_TEST_VALUE, 4,
155 &val)) {
156 /* test setup: tell guest how often to report its tsc */
157 ventry_fulfill_inout(&vexit, &ventry, guest_ticks);
158
159 } else if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 4,
160 &val)) {
161 /*
162 * Get reported guest TSC in two 32-bit parts, with the
163 * lower bits coming in first.
164 */
165 if (!half_read) {
166 /* lower bits */
167 cur_tsc = val;
168 half_read = true;
169 ventry_fulfill_inout(&vexit, &ventry, 0);
170 } else {
171 /* upper bits */
172 cur_tsc |= ((uint64_t)val << 32);
173
174 tsc_readings[count].when = gethrtime();
175 tsc_readings[count].tsc = cur_tsc;
176
177 half_read = false;
178 cur_tsc = 0;
179 count++;
180
181 ventry_fulfill_inout(&vexit, &ventry, 0);
182 }
183 } else {
184 test_fail_vmexit(&vexit);
185 }
186 } while (true);
187 }
188
189 int
main(int argc,char * argv[])190 main(int argc, char *argv[])
191 {
192 const char *test_suite_name = basename(argv[0]);
193 struct vmctx *ctx = NULL;
194 struct vcpu *vcpu;
195 int err;
196
197 ctx = test_initialize(test_suite_name);
198
199 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
200 test_fail_errno(errno, "Could not open vcpu0");
201 }
202
203 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
204 if (err != 0) {
205 test_fail_errno(err, "Could not initialize vcpu0");
206 }
207
208 const int vmfd = vm_get_device_fd(ctx);
209 const bool is_svm = cpu_vendor_amd();
210
211 if (!is_svm) {
212 test_fail_msg("intel not supported\n");
213 }
214
215 /* Read time data to get baseline guest time values */
216 struct vdi_time_info_v1 time_info;
217 struct vm_data_xfer xfer = {
218 .vdx_class = VDC_VMM_TIME,
219 .vdx_version = 1,
220 .vdx_len = sizeof (struct vdi_time_info_v1),
221 .vdx_data = &time_info,
222 };
223 if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) {
224 errx(EXIT_FAILURE, "VMM_DATA_READ of time info failed");
225 }
226 uint64_t host_freq = time_info.vt_guest_freq;
227 uint64_t guest_freq = host_freq;
228
229 /* measure each test frequency 10x per sec, for 1 second */
230 const uint8_t per_sec = 10;
231 const uint8_t seconds = 1;
232
233 /* 2x host frequency */
234 guest_freq = host_freq * 2;
235 (void) printf("testing 2x host_freq: guest_freq=%lu, host_freq=%lu\n",
236 guest_freq, host_freq);
237 do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info);
238
239 /* reset guest */
240 vm_vcpu_close(vcpu);
241 test_cleanup(false);
242 ctx = test_initialize(test_suite_name);
243 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
244 test_fail_errno(errno, "Could not open vcpu0");
245 }
246 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
247 if (err != 0) {
248 test_fail_errno(err, "Could not initialize vcpu0");
249 }
250
251
252 /* 0.5x host frequency */
253 guest_freq = host_freq / 2;
254 (void) printf("testing 0.5x host_freq: guest_freq=%lu, host_freq=%lu\n",
255 guest_freq, host_freq);
256 do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info);
257
258 /* reset guest */
259 vm_vcpu_close(vcpu);
260 test_cleanup(false);
261 ctx = test_initialize(test_suite_name);
262 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
263 test_fail_errno(errno, "Could not open vcpu0");
264 }
265 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
266 if (err != 0) {
267 test_fail_errno(err, "Could not initialize vcpu0");
268 }
269
270
271 /* 1/3 host frequency */
272 guest_freq = host_freq / 3;
273 (void) printf("testing 1/3 host_freq: guest_freq=%lu, host_freq=%lu\n",
274 guest_freq, host_freq);
275 do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info);
276
277 /* reset guest */
278 vm_vcpu_close(vcpu);
279 test_cleanup(false);
280 ctx = test_initialize(test_suite_name);
281 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
282 test_fail_errno(errno, "Could not open vcpu0");
283 }
284 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
285 if (err != 0) {
286 test_fail_errno(err, "Could not initialize vcpu0");
287 }
288
289
290 /* 1x host frequency */
291 guest_freq = host_freq;
292 (void) printf("testing 1x host_freq: guest_freq=%lu, host_freq=%lu\n",
293 guest_freq, host_freq);
294 do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info);
295
296 test_pass();
297 }
298