1717646f7SJordan Paige Hendricks /*
2717646f7SJordan Paige Hendricks  * This file and its contents are supplied under the terms of the
3717646f7SJordan Paige Hendricks  * Common Development and Distribution License ("CDDL"), version 1.0.
4717646f7SJordan Paige Hendricks  * You may only use this file in accordance with the terms of version
5717646f7SJordan Paige Hendricks  * 1.0 of the CDDL.
6717646f7SJordan Paige Hendricks  *
7717646f7SJordan Paige Hendricks  * A full copy of the text of the CDDL should have accompanied this
8717646f7SJordan Paige Hendricks  * source.  A copy of the CDDL is also available via the Internet at
9717646f7SJordan Paige Hendricks  * http://www.illumos.org/license/CDDL.
10717646f7SJordan Paige Hendricks  */
11717646f7SJordan Paige Hendricks 
12717646f7SJordan Paige Hendricks /*
13717646f7SJordan Paige Hendricks  * Copyright 2023 Oxide Computer Company
14717646f7SJordan Paige Hendricks  */
15717646f7SJordan Paige Hendricks 
16717646f7SJordan Paige Hendricks /*
17717646f7SJordan Paige Hendricks  * Test guest frequency control
18717646f7SJordan Paige Hendricks  *
19717646f7SJordan Paige Hendricks  * Note: requires `vmm_allow_state_writes` to be set, and only on AMD
20717646f7SJordan Paige Hendricks  */
21717646f7SJordan Paige Hendricks 
22717646f7SJordan Paige Hendricks #include <unistd.h>
23717646f7SJordan Paige Hendricks #include <stdlib.h>
24717646f7SJordan Paige Hendricks #include <libgen.h>
25717646f7SJordan Paige Hendricks #include <errno.h>
26717646f7SJordan Paige Hendricks #include <err.h>
27717646f7SJordan Paige Hendricks 
28717646f7SJordan Paige Hendricks #include <sys/time.h>
29717646f7SJordan Paige Hendricks #include <sys/vmm_data.h>
30717646f7SJordan Paige Hendricks #include <sys/vmm_dev.h>
31717646f7SJordan Paige Hendricks #include <vmmapi.h>
32717646f7SJordan Paige Hendricks 
33717646f7SJordan Paige Hendricks #include "common.h"
34717646f7SJordan Paige Hendricks #include "in_guest.h"
35717646f7SJordan Paige Hendricks 
36717646f7SJordan Paige Hendricks #define	PPM_MARGIN	200
37717646f7SJordan Paige Hendricks 
38717646f7SJordan Paige Hendricks typedef struct tsc_reading {
39717646f7SJordan Paige Hendricks 	hrtime_t when;
40717646f7SJordan Paige Hendricks 	uint64_t tsc;
41717646f7SJordan Paige Hendricks } tsc_reading_t;
42717646f7SJordan Paige Hendricks 
43717646f7SJordan Paige Hendricks 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)44717646f7SJordan Paige Hendricks check_reading(tsc_reading_t r1, tsc_reading_t r2, uint64_t guest_freq,
45717646f7SJordan Paige Hendricks     uint64_t min_ticks, uint64_t tick_margin, uint32_t ppm_margin)
46717646f7SJordan Paige Hendricks {
47717646f7SJordan Paige Hendricks 	hrtime_t time_delta = r2.when - r1.when;
48717646f7SJordan Paige Hendricks 	uint64_t tick_delta = r2.tsc - r1.tsc;
49717646f7SJordan Paige Hendricks 
50717646f7SJordan Paige Hendricks 	/* check that number of ticks seen is valid */
51717646f7SJordan Paige Hendricks 	if (tick_delta < min_ticks) {
52717646f7SJordan Paige Hendricks 		test_fail_msg("inadequate passage of guest TSC ticks %u < %u\n",
53717646f7SJordan Paige Hendricks 		    tick_delta, min_ticks);
54717646f7SJordan Paige Hendricks 	} else if ((tick_delta - min_ticks) > tick_margin) {
55717646f7SJordan Paige Hendricks 		(void) printf("%u ticks outside margin %u\n", tick_delta,
56717646f7SJordan Paige Hendricks 		    min_ticks + tick_margin);
57717646f7SJordan Paige Hendricks 		return (false);
58717646f7SJordan Paige Hendricks 	}
59717646f7SJordan Paige Hendricks 
60717646f7SJordan Paige Hendricks 	/* compute ppm error and validate */
61717646f7SJordan Paige Hendricks 	hrtime_t time_target = (tick_delta * NANOSEC) / guest_freq;
62717646f7SJordan Paige Hendricks 	hrtime_t offset;
63717646f7SJordan Paige Hendricks 	if (time_delta < time_target) {
64717646f7SJordan Paige Hendricks 		offset = time_target - time_delta;
65717646f7SJordan Paige Hendricks 	} else {
66717646f7SJordan Paige Hendricks 		offset = time_delta - time_target;
67717646f7SJordan Paige Hendricks 	}
68717646f7SJordan Paige Hendricks 	uint64_t ppm = (offset * 1000000) / time_target;
69717646f7SJordan Paige Hendricks 	(void) printf("%u ticks in %lu ns (error %lu ppm)\n",
70717646f7SJordan Paige Hendricks 	    tick_delta, time_delta, ppm);
71717646f7SJordan Paige Hendricks 	if (ppm > ppm_margin) {
72717646f7SJordan Paige Hendricks 		(void) printf("UNACCEPTABLE!\n");
73717646f7SJordan Paige Hendricks 		return (false);
74717646f7SJordan Paige Hendricks 	}
75717646f7SJordan Paige Hendricks 	return (true);
76717646f7SJordan Paige Hendricks }
77717646f7SJordan Paige Hendricks 
78717646f7SJordan Paige Hendricks 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)79717646f7SJordan Paige Hendricks do_freq_test(uint64_t guest_freq, uint8_t per_sec, uint8_t seconds,
80*32640292SAndy Fiddaman     const int vmfd, struct vcpu *vcpu, struct vdi_time_info_v1 *src)
81717646f7SJordan Paige Hendricks {
82717646f7SJordan Paige Hendricks 	/* configure the guest to have the desired frequency */
83717646f7SJordan Paige Hendricks 	struct vdi_time_info_v1 time_info = {
84717646f7SJordan Paige Hendricks 		.vt_guest_freq = guest_freq,
85717646f7SJordan Paige Hendricks 		.vt_guest_tsc = src->vt_guest_tsc,
86717646f7SJordan Paige Hendricks 		.vt_boot_hrtime = src->vt_boot_hrtime,
87717646f7SJordan Paige Hendricks 		.vt_hrtime = src->vt_hrtime,
88717646f7SJordan Paige Hendricks 		.vt_hres_sec = src->vt_hres_sec,
89717646f7SJordan Paige Hendricks 		.vt_hres_ns = src->vt_hres_ns,
90717646f7SJordan Paige Hendricks 	};
91717646f7SJordan Paige Hendricks 	struct vm_data_xfer xfer = {
92717646f7SJordan Paige Hendricks 		.vdx_class = VDC_VMM_TIME,
93717646f7SJordan Paige Hendricks 		.vdx_version = 1,
94717646f7SJordan Paige Hendricks 		.vdx_len = sizeof (struct vdi_time_info_v1),
95717646f7SJordan Paige Hendricks 		.vdx_data = &time_info,
96717646f7SJordan Paige Hendricks 	};
97717646f7SJordan Paige Hendricks 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
98717646f7SJordan Paige Hendricks 		int error;
99717646f7SJordan Paige Hendricks 		error = errno;
100717646f7SJordan Paige Hendricks 		if (error == EPERM) {
101717646f7SJordan Paige Hendricks 			warn("VMM_DATA_WRITE got EPERM: is "
102717646f7SJordan Paige Hendricks 			    "vmm_allow_state_writes set?");
103717646f7SJordan Paige Hendricks 		}
104717646f7SJordan Paige Hendricks 		errx(EXIT_FAILURE, "VMM_DATA_WRITE of time info failed");
105717646f7SJordan Paige Hendricks 	}
106717646f7SJordan Paige Hendricks 
107717646f7SJordan Paige Hendricks 
108717646f7SJordan Paige Hendricks 	/*
109717646f7SJordan Paige Hendricks 	 * Run the test:
110717646f7SJordan Paige Hendricks 	 * - ask the guest to report the TSC every 1/per_sec seconds, in terms
111717646f7SJordan Paige Hendricks 	 *   of guest ticks
112717646f7SJordan Paige Hendricks 	 * - collect readings for `seconds` seconds, along with host hrtime
113717646f7SJordan Paige Hendricks 	 * - to avoid additional latency in the readings, process readings at
114717646f7SJordan Paige Hendricks 	 *   the end: check for error in the number of ticks reported and the
115717646f7SJordan Paige Hendricks 	 *   ppm based on host hrtime
116717646f7SJordan Paige Hendricks 	 */
117717646f7SJordan Paige Hendricks 	uint64_t guest_ticks = guest_freq / per_sec;
118717646f7SJordan Paige Hendricks 	const uint32_t nreadings = per_sec * seconds + 1;
119717646f7SJordan Paige Hendricks 	tsc_reading_t tsc_readings[nreadings];
120717646f7SJordan Paige Hendricks 
121717646f7SJordan Paige Hendricks 	/* 5 percent margin */
122717646f7SJordan Paige Hendricks 	const uint32_t tick_margin = guest_ticks / 20;
123717646f7SJordan Paige Hendricks 
124717646f7SJordan Paige Hendricks 	bool half_read = false;
125717646f7SJordan Paige Hendricks 	uint64_t cur_tsc;
126717646f7SJordan Paige Hendricks 	uint32_t count = 0;
127717646f7SJordan Paige Hendricks 
128717646f7SJordan Paige Hendricks 	struct vm_entry ventry = { 0 };
129717646f7SJordan Paige Hendricks 	struct vm_exit vexit = { 0 };
130717646f7SJordan Paige Hendricks 
131717646f7SJordan Paige Hendricks 	do {
132717646f7SJordan Paige Hendricks 		if (count >= nreadings) {
133717646f7SJordan Paige Hendricks 			/* test completed: check results */
134717646f7SJordan Paige Hendricks 			for (int i = 1; i < nreadings; i++) {
135717646f7SJordan Paige Hendricks 				if (!check_reading(tsc_readings[i-1],
136717646f7SJordan Paige Hendricks 				    tsc_readings[i], guest_freq, guest_ticks,
137717646f7SJordan Paige Hendricks 				    tick_margin, PPM_MARGIN)) {
138717646f7SJordan Paige Hendricks 					test_fail_msg("freq test failed");
139717646f7SJordan Paige Hendricks 				}
140717646f7SJordan Paige Hendricks 			}
141717646f7SJordan Paige Hendricks 			break;
142717646f7SJordan Paige Hendricks 		}
143717646f7SJordan Paige Hendricks 
144717646f7SJordan Paige Hendricks 		const enum vm_exit_kind kind =
145*32640292SAndy Fiddaman 		    test_run_vcpu(vcpu, &ventry, &vexit);
146717646f7SJordan Paige Hendricks 
147717646f7SJordan Paige Hendricks 		if (kind == VEK_REENTR) {
148717646f7SJordan Paige Hendricks 			continue;
149717646f7SJordan Paige Hendricks 		} else if (kind != VEK_UNHANDLED) {
150717646f7SJordan Paige Hendricks 			test_fail_vmexit(&vexit);
151717646f7SJordan Paige Hendricks 		}
152717646f7SJordan Paige Hendricks 
153717646f7SJordan Paige Hendricks 		uint32_t val;
154717646f7SJordan Paige Hendricks 		if (vexit_match_inout(&vexit, true, IOP_TEST_VALUE, 4,
155717646f7SJordan Paige Hendricks 		    &val)) {
156717646f7SJordan Paige Hendricks 			/* test setup: tell guest how often to report its tsc */
157717646f7SJordan Paige Hendricks 			ventry_fulfill_inout(&vexit, &ventry, guest_ticks);
158717646f7SJordan Paige Hendricks 
159717646f7SJordan Paige Hendricks 		} else if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 4,
160717646f7SJordan Paige Hendricks 		    &val)) {
161717646f7SJordan Paige Hendricks 			/*
162717646f7SJordan Paige Hendricks 			 * Get reported guest TSC in two 32-bit parts, with the
163717646f7SJordan Paige Hendricks 			 * lower bits coming in first.
164717646f7SJordan Paige Hendricks 			 */
165717646f7SJordan Paige Hendricks 			if (!half_read) {
166717646f7SJordan Paige Hendricks 				/* lower bits */
167717646f7SJordan Paige Hendricks 				cur_tsc = val;
168717646f7SJordan Paige Hendricks 				half_read = true;
169717646f7SJordan Paige Hendricks 				ventry_fulfill_inout(&vexit, &ventry, 0);
170717646f7SJordan Paige Hendricks 			} else {
171717646f7SJordan Paige Hendricks 				/* upper bits */
172717646f7SJordan Paige Hendricks 				cur_tsc |= ((uint64_t)val << 32);
173717646f7SJordan Paige Hendricks 
174717646f7SJordan Paige Hendricks 				tsc_readings[count].when = gethrtime();
175717646f7SJordan Paige Hendricks 				tsc_readings[count].tsc = cur_tsc;
176717646f7SJordan Paige Hendricks 
177717646f7SJordan Paige Hendricks 				half_read = false;
178717646f7SJordan Paige Hendricks 				cur_tsc = 0;
179717646f7SJordan Paige Hendricks 				count++;
180717646f7SJordan Paige Hendricks 
181717646f7SJordan Paige Hendricks 				ventry_fulfill_inout(&vexit, &ventry, 0);
182717646f7SJordan Paige Hendricks 			}
183717646f7SJordan Paige Hendricks 		} else {
184717646f7SJordan Paige Hendricks 			test_fail_vmexit(&vexit);
185717646f7SJordan Paige Hendricks 		}
186717646f7SJordan Paige Hendricks 	} while (true);
187717646f7SJordan Paige Hendricks }
188717646f7SJordan Paige Hendricks 
189717646f7SJordan Paige Hendricks int
main(int argc,char * argv[])190717646f7SJordan Paige Hendricks main(int argc, char *argv[])
191717646f7SJordan Paige Hendricks {
192717646f7SJordan Paige Hendricks 	const char *test_suite_name = basename(argv[0]);
193717646f7SJordan Paige Hendricks 	struct vmctx *ctx = NULL;
194*32640292SAndy Fiddaman 	struct vcpu *vcpu;
195717646f7SJordan Paige Hendricks 	int err;
196717646f7SJordan Paige Hendricks 
197717646f7SJordan Paige Hendricks 	ctx = test_initialize(test_suite_name);
198717646f7SJordan Paige Hendricks 
199*32640292SAndy Fiddaman 	if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
200*32640292SAndy Fiddaman 		test_fail_errno(errno, "Could not open vcpu0");
201*32640292SAndy Fiddaman 	}
202*32640292SAndy Fiddaman 
203*32640292SAndy Fiddaman 	err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
204717646f7SJordan Paige Hendricks 	if (err != 0) {
205717646f7SJordan Paige Hendricks 		test_fail_errno(err, "Could not initialize vcpu0");
206717646f7SJordan Paige Hendricks 	}
207717646f7SJordan Paige Hendricks 
208717646f7SJordan Paige Hendricks 	const int vmfd = vm_get_device_fd(ctx);
209717646f7SJordan Paige Hendricks 	const bool is_svm = cpu_vendor_amd();
210717646f7SJordan Paige Hendricks 
211717646f7SJordan Paige Hendricks 	if (!is_svm) {
212717646f7SJordan Paige Hendricks 		test_fail_msg("intel not supported\n");
213717646f7SJordan Paige Hendricks 	}
214717646f7SJordan Paige Hendricks 
215717646f7SJordan Paige Hendricks 	/* Read time data to get baseline guest time values */
216717646f7SJordan Paige Hendricks 	struct vdi_time_info_v1 time_info;
217717646f7SJordan Paige Hendricks 	struct vm_data_xfer xfer = {
218717646f7SJordan Paige Hendricks 		.vdx_class = VDC_VMM_TIME,
219717646f7SJordan Paige Hendricks 		.vdx_version = 1,
220717646f7SJordan Paige Hendricks 		.vdx_len = sizeof (struct vdi_time_info_v1),
221717646f7SJordan Paige Hendricks 		.vdx_data = &time_info,
222717646f7SJordan Paige Hendricks 	};
223717646f7SJordan Paige Hendricks 	if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) {
224717646f7SJordan Paige Hendricks 		errx(EXIT_FAILURE, "VMM_DATA_READ of time info failed");
225717646f7SJordan Paige Hendricks 	}
226717646f7SJordan Paige Hendricks 	uint64_t host_freq = time_info.vt_guest_freq;
227717646f7SJordan Paige Hendricks 	uint64_t guest_freq = host_freq;
228717646f7SJordan Paige Hendricks 
229717646f7SJordan Paige Hendricks 	/* measure each test frequency 10x per sec, for 1 second */
230717646f7SJordan Paige Hendricks 	const uint8_t per_sec = 10;
231717646f7SJordan Paige Hendricks 	const uint8_t seconds = 1;
232717646f7SJordan Paige Hendricks 
233717646f7SJordan Paige Hendricks 	/* 2x host frequency */
234717646f7SJordan Paige Hendricks 	guest_freq = host_freq * 2;
235717646f7SJordan Paige Hendricks 	(void) printf("testing 2x host_freq: guest_freq=%lu, host_freq=%lu\n",
236717646f7SJordan Paige Hendricks 	    guest_freq, host_freq);
237*32640292SAndy Fiddaman 	do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info);
238717646f7SJordan Paige Hendricks 
239717646f7SJordan Paige Hendricks 	/* reset guest */
240*32640292SAndy Fiddaman 	vm_vcpu_close(vcpu);
241717646f7SJordan Paige Hendricks 	test_cleanup(false);
242717646f7SJordan Paige Hendricks 	ctx = test_initialize(test_suite_name);
243*32640292SAndy Fiddaman 	if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
244*32640292SAndy Fiddaman 		test_fail_errno(errno, "Could not open vcpu0");
245*32640292SAndy Fiddaman 	}
246*32640292SAndy Fiddaman 	err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
247717646f7SJordan Paige Hendricks 	if (err != 0) {
248717646f7SJordan Paige Hendricks 		test_fail_errno(err, "Could not initialize vcpu0");
249717646f7SJordan Paige Hendricks 	}
250717646f7SJordan Paige Hendricks 
251717646f7SJordan Paige Hendricks 
252717646f7SJordan Paige Hendricks 	/* 0.5x host frequency */
253717646f7SJordan Paige Hendricks 	guest_freq = host_freq / 2;
254717646f7SJordan Paige Hendricks 	(void) printf("testing 0.5x host_freq: guest_freq=%lu, host_freq=%lu\n",
255717646f7SJordan Paige Hendricks 	    guest_freq, host_freq);
256*32640292SAndy Fiddaman 	do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info);
257717646f7SJordan Paige Hendricks 
258717646f7SJordan Paige Hendricks 	/* reset guest */
259*32640292SAndy Fiddaman 	vm_vcpu_close(vcpu);
260717646f7SJordan Paige Hendricks 	test_cleanup(false);
261717646f7SJordan Paige Hendricks 	ctx = test_initialize(test_suite_name);
262*32640292SAndy Fiddaman 	if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
263*32640292SAndy Fiddaman 		test_fail_errno(errno, "Could not open vcpu0");
264*32640292SAndy Fiddaman 	}
265*32640292SAndy Fiddaman 	err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
266717646f7SJordan Paige Hendricks 	if (err != 0) {
267717646f7SJordan Paige Hendricks 		test_fail_errno(err, "Could not initialize vcpu0");
268717646f7SJordan Paige Hendricks 	}
269717646f7SJordan Paige Hendricks 
270717646f7SJordan Paige Hendricks 
271717646f7SJordan Paige Hendricks 	/* 1/3 host frequency */
272717646f7SJordan Paige Hendricks 	guest_freq = host_freq / 3;
273717646f7SJordan Paige Hendricks 	(void) printf("testing 1/3 host_freq: guest_freq=%lu, host_freq=%lu\n",
274717646f7SJordan Paige Hendricks 	    guest_freq, host_freq);
275*32640292SAndy Fiddaman 	do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info);
276717646f7SJordan Paige Hendricks 
277717646f7SJordan Paige Hendricks 	/* reset guest */
278*32640292SAndy Fiddaman 	vm_vcpu_close(vcpu);
279717646f7SJordan Paige Hendricks 	test_cleanup(false);
280717646f7SJordan Paige Hendricks 	ctx = test_initialize(test_suite_name);
281*32640292SAndy Fiddaman 	if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
282*32640292SAndy Fiddaman 		test_fail_errno(errno, "Could not open vcpu0");
283*32640292SAndy Fiddaman 	}
284*32640292SAndy Fiddaman 	err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
285717646f7SJordan Paige Hendricks 	if (err != 0) {
286717646f7SJordan Paige Hendricks 		test_fail_errno(err, "Could not initialize vcpu0");
287717646f7SJordan Paige Hendricks 	}
288717646f7SJordan Paige Hendricks 
289717646f7SJordan Paige Hendricks 
290717646f7SJordan Paige Hendricks 	/* 1x host frequency */
291717646f7SJordan Paige Hendricks 	guest_freq = host_freq;
292717646f7SJordan Paige Hendricks 	(void) printf("testing 1x host_freq: guest_freq=%lu, host_freq=%lu\n",
293717646f7SJordan Paige Hendricks 	    guest_freq, host_freq);
294*32640292SAndy Fiddaman 	do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info);
295717646f7SJordan Paige Hendricks 
296717646f7SJordan Paige Hendricks 	test_pass();
297717646f7SJordan Paige Hendricks }
298