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 #include <stdio.h> 17 #include <unistd.h> 18 #include <stdlib.h> 19 #include <strings.h> 20 #include <libgen.h> 21 #include <assert.h> 22 #include <errno.h> 23 #include <err.h> 24 25 #include <sys/types.h> 26 #include <sys/sysmacros.h> 27 #include <sys/debug.h> 28 #include <sys/vmm.h> 29 #include <sys/vmm_dev.h> 30 #include <vmmapi.h> 31 32 #include "in_guest.h" 33 34 static uint32_t opt_repeat_count = 1000; 35 static bool opt_summarize = false; 36 37 /* Names of the test phases running in guest context */ 38 static const char *test_metric_idents[] = { 39 "MSR", 40 "PIO", 41 "MMIO", 42 }; 43 44 /* Track which test phase the guest is executing */ 45 static uint_t current_test = 0; 46 /* Cache the queried CPU frequency */ 47 static uint64_t cpu_freq = 0; 48 49 static uint64_t 50 query_cpu_freq(struct vmctx *ctx) 51 { 52 const int vmfd = vm_get_device_fd(ctx); 53 struct vdi_time_info_v1 time_info; 54 struct vm_data_xfer xfer = { 55 .vdx_class = VDC_VMM_TIME, 56 .vdx_version = 1, 57 .vdx_len = sizeof (struct vdi_time_info_v1), 58 .vdx_data = &time_info, 59 }; 60 61 if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) { 62 errx(EXIT_FAILURE, "VMM_DATA_READ of time info failed"); 63 } 64 return (time_info.vt_guest_freq); 65 } 66 67 static double 68 cycles_to_ns(uint64_t cycles) 69 { 70 return ((cycles * 1000000000.0) / cpu_freq); 71 } 72 73 static void 74 print_result(struct vmctx *ctx, uintptr_t gpa, uint_t test_idx) 75 { 76 if (test_idx >= ARRAY_SIZE(test_metric_idents)) { 77 test_fail_msg("unexpected test iteration"); 78 return; 79 } 80 81 const uint64_t *data = 82 vm_map_gpa(ctx, gpa, opt_repeat_count * sizeof (uint64_t)); 83 assert(data != NULL); 84 85 printf("%s", test_metric_idents[test_idx]); 86 if (opt_summarize) { 87 double sum = 0.0; 88 for (uint32_t i = 0; i < opt_repeat_count; i++) { 89 sum += cycles_to_ns(data[i]); 90 } 91 printf(",%0.2f", sum / opt_repeat_count); 92 } else { 93 for (uint32_t i = 0; i < opt_repeat_count; i++) { 94 printf(",%0.2f", cycles_to_ns(data[i])); 95 } 96 } 97 printf("\n"); 98 } 99 100 static void 101 handle_exit(struct vmctx *ctx, const struct vm_exit *vexit, 102 struct vm_entry *ventry) 103 { 104 uint32_t outval; 105 106 if (vexit_match_inout(vexit, true, IOP_TEST_PARAM0, 4, NULL)) { 107 ventry_fulfill_inout(vexit, ventry, opt_repeat_count); 108 return; 109 } 110 if (vexit_match_inout(vexit, false, IOP_TEST_VALUE, 4, &outval)) { 111 ventry_fulfill_inout(vexit, ventry, 0); 112 print_result(ctx, (uintptr_t)outval, current_test); 113 /* proceed to next test */ 114 current_test++; 115 return; 116 } 117 118 test_fail_vmexit(vexit); 119 } 120 121 static void 122 usage(const char *progname, int status) 123 { 124 char *base = strdup(progname); 125 126 (void) printf("usage: %s [args]\n" 127 "\t-n <count>\tNumber of repetitions (default: 1000)\n" 128 "\t-s\t\tSummarize (average) results\n" 129 "\t-h\t\tPrint this help\n", basename(base)); 130 exit(status); 131 } 132 133 static void 134 parse_args(int argc, char *argv[]) 135 { 136 int c; 137 unsigned long num_parsed; 138 139 while ((c = getopt(argc, argv, ":hsn:")) != -1) { 140 switch (c) { 141 case 'h': 142 usage(argv[0], EXIT_SUCCESS); 143 break; 144 case 's': 145 opt_summarize = true; 146 break; 147 case 'n': 148 errno = 0; 149 num_parsed = strtoul(optarg, NULL, 10); 150 if (num_parsed == 0 && errno != 0) { 151 perror("Invalid repeat count"); 152 usage(argv[0], EXIT_FAILURE); 153 } 154 if (num_parsed <= 0 || num_parsed > UINT32_MAX) { 155 (void) printf( 156 "Repeat count must be between 1 - %lu\n", 157 UINT32_MAX); 158 usage(argv[0], EXIT_FAILURE); 159 } 160 opt_repeat_count = num_parsed; 161 break; 162 case ':': 163 (void) printf("Missing argument for option '%c'\n", 164 optopt); 165 usage(argv[0], EXIT_FAILURE); 166 break; 167 case '?': 168 (void) printf("Unrecognized option '%c'\n", optopt); 169 usage(argv[0], EXIT_FAILURE); 170 break; 171 } 172 } 173 } 174 175 int 176 main(int argc, char *argv[]) 177 { 178 const char *test_suite_name = basename(argv[0]); 179 struct vmctx *ctx = NULL; 180 struct vcpu *vcpu; 181 int err; 182 183 parse_args(argc, argv); 184 185 ctx = test_initialize(test_suite_name); 186 187 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { 188 test_fail_errno(errno, "Could not open vcpu0"); 189 } 190 191 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 192 if (err != 0) { 193 test_fail_errno(err, "Could not initialize vcpu0"); 194 } 195 cpu_freq = query_cpu_freq(ctx); 196 197 struct vm_entry ventry = { 0 }; 198 struct vm_exit vexit = { 0 }; 199 200 do { 201 const enum vm_exit_kind kind = 202 test_run_vcpu(vcpu, &ventry, &vexit); 203 switch (kind) { 204 case VEK_REENTR: 205 break; 206 case VEK_UNHANDLED: 207 handle_exit(ctx, &vexit, &ventry); 208 break; 209 210 case VEK_TEST_PASS: 211 /* 212 * Skip the normal "PASS" message, since the consumer is 213 * interested in the data itself. 214 */ 215 exit(EXIT_SUCCESS); 216 break; 217 case VEK_TEST_MSG: 218 test_msg_print(ctx); 219 break; 220 case VEK_TEST_FAIL: 221 default: 222 test_fail_vmexit(&vexit); 223 break; 224 } 225 } while (true); 226 } 227