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 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 79 do_freq_test(uint64_t guest_freq, uint8_t per_sec, uint8_t seconds, 80 const int vmfd, struct vmctx *ctx, 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(ctx, 0, &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 190 main(int argc, char *argv[]) 191 { 192 const char *test_suite_name = basename(argv[0]); 193 struct vmctx *ctx = NULL; 194 int err; 195 196 ctx = test_initialize(test_suite_name); 197 198 err = test_setup_vcpu(ctx, 0, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 199 if (err != 0) { 200 test_fail_errno(err, "Could not initialize vcpu0"); 201 } 202 203 const int vmfd = vm_get_device_fd(ctx); 204 const bool is_svm = cpu_vendor_amd(); 205 206 if (!is_svm) { 207 test_fail_msg("intel not supported\n"); 208 } 209 210 /* Read time data to get baseline guest time values */ 211 struct vdi_time_info_v1 time_info; 212 struct vm_data_xfer xfer = { 213 .vdx_class = VDC_VMM_TIME, 214 .vdx_version = 1, 215 .vdx_len = sizeof (struct vdi_time_info_v1), 216 .vdx_data = &time_info, 217 }; 218 if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) { 219 errx(EXIT_FAILURE, "VMM_DATA_READ of time info failed"); 220 } 221 uint64_t host_freq = time_info.vt_guest_freq; 222 uint64_t guest_freq = host_freq; 223 224 /* measure each test frequency 10x per sec, for 1 second */ 225 const uint8_t per_sec = 10; 226 const uint8_t seconds = 1; 227 228 /* 2x host frequency */ 229 guest_freq = host_freq * 2; 230 (void) printf("testing 2x host_freq: guest_freq=%lu, host_freq=%lu\n", 231 guest_freq, host_freq); 232 do_freq_test(guest_freq, per_sec, seconds, vmfd, ctx, &time_info); 233 234 /* reset guest */ 235 test_cleanup(false); 236 ctx = test_initialize(test_suite_name); 237 err = test_setup_vcpu(ctx, 0, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 238 if (err != 0) { 239 test_fail_errno(err, "Could not initialize vcpu0"); 240 } 241 242 243 /* 0.5x host frequency */ 244 guest_freq = host_freq / 2; 245 (void) printf("testing 0.5x host_freq: guest_freq=%lu, host_freq=%lu\n", 246 guest_freq, host_freq); 247 do_freq_test(guest_freq, per_sec, seconds, vmfd, ctx, &time_info); 248 249 /* reset guest */ 250 test_cleanup(false); 251 ctx = test_initialize(test_suite_name); 252 err = test_setup_vcpu(ctx, 0, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 253 if (err != 0) { 254 test_fail_errno(err, "Could not initialize vcpu0"); 255 } 256 257 258 /* 1/3 host frequency */ 259 guest_freq = host_freq / 3; 260 (void) printf("testing 1/3 host_freq: guest_freq=%lu, host_freq=%lu\n", 261 guest_freq, host_freq); 262 do_freq_test(guest_freq, per_sec, seconds, vmfd, ctx, &time_info); 263 264 /* reset guest */ 265 test_cleanup(false); 266 ctx = test_initialize(test_suite_name); 267 err = test_setup_vcpu(ctx, 0, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 268 if (err != 0) { 269 test_fail_errno(err, "Could not initialize vcpu0"); 270 } 271 272 273 /* 1x host frequency */ 274 guest_freq = host_freq; 275 (void) printf("testing 1x host_freq: guest_freq=%lu, host_freq=%lu\n", 276 guest_freq, host_freq); 277 do_freq_test(guest_freq, per_sec, seconds, vmfd, ctx, &time_info); 278 279 test_pass(); 280 } 281