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 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 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