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 2022 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 23 #include <sys/types.h> 24 #include <sys/sysmacros.h> 25 #include <sys/debug.h> 26 #include <sys/vmm.h> 27 #include <sys/vmm_dev.h> 28 #include <vmmapi.h> 29 30 #include "in_guest.h" 31 #include "test_defs.h" 32 33 typedef struct reading { 34 hrtime_t when; 35 uint32_t value; 36 } reading_t; 37 38 static bool 39 check_reading(reading_t before, reading_t after, uint_t divisor, uint_t loops, 40 uint_t tick_margin, uint_t ppm_margin) 41 { 42 const hrtime_t time_delta = after.when - before.when; 43 44 45 /* 46 * The ticks margin should shrink proportionally to how coarsely the 47 * timer clock is being divided. 48 */ 49 tick_margin /= divisor; 50 51 /* 52 * The 'before' measurement includes the ticks which occurred between 53 * programming the timer and taking the first reading. The 'after' 54 * measurement includes the number of loops (each consisting of the 55 * target tick count) plus however many ticks had transpired since the 56 * most recent roll-over. 57 */ 58 const uint32_t tick_delta = 59 loops * LAPIC_TARGET_TICKS + before.value - after.value; 60 const uint32_t tick_target = loops * LAPIC_TARGET_TICKS; 61 62 /* is the number of ticks OK? */ 63 if (tick_delta < tick_target) { 64 if ((tick_target - tick_delta) > tick_margin) { 65 (void) printf("%u ticks outside margin %u\n", 66 tick_delta, tick_target - tick_margin); 67 } 68 } else if ((tick_delta - tick_target) > tick_margin) { 69 (void) printf("%u ticks outside margin %u\n", tick_delta, 70 tick_target + tick_margin); 71 return (false); 72 } 73 74 hrtime_t time_target = (tick_delta * NANOSEC * divisor) / LAPIC_FREQ; 75 76 hrtime_t offset; 77 if (time_delta < time_target) { 78 offset = time_target - time_delta; 79 } else { 80 offset = time_delta - time_target; 81 } 82 uint64_t ppm = (offset * 1000000) / time_target; 83 (void) printf("params: tick_margin=%u ppm_margin=%lu divisor=%u\n", 84 tick_margin, ppm_margin, divisor); 85 (void) printf("%u ticks in %lu ns (error %lu ppm)\n", 86 tick_delta, time_delta, ppm); 87 if (ppm > ppm_margin) { 88 (void) printf("UNACCEPTABLE!\n"); 89 return (false); 90 } 91 return (true); 92 } 93 94 95 static void 96 run_test(struct vmctx *ctx, uint_t divisor, uint_t loops, 97 struct vm_entry *ventry, struct vm_exit *vexit) 98 { 99 reading_t readings[2]; 100 uint_t nread = 0; 101 uint_t nrepeat = 0; 102 103 const uint_t margin_ticks = MAX(1, LAPIC_TARGET_TICKS / 5000); 104 const uint_t margin_ppm = 400; 105 106 do { 107 const enum vm_exit_kind kind = 108 test_run_vcpu(ctx, 0, ventry, vexit); 109 if (kind == VEK_REENTR) { 110 continue; 111 } else if (kind != VEK_UNHANDLED) { 112 test_fail_vmexit(vexit); 113 } 114 115 /* input the divisor (bits 0-15) and loop count (bits 16-31) */ 116 if (vexit_match_inout(vexit, true, IOP_TEST_PARAM0, 2, NULL)) { 117 ventry_fulfill_inout(vexit, ventry, divisor); 118 continue; 119 } 120 /* input the loop count */ 121 if (vexit_match_inout(vexit, true, IOP_TEST_PARAM1, 2, NULL)) { 122 ventry_fulfill_inout(vexit, ventry, loops); 123 continue; 124 } 125 126 uint32_t v; 127 if (vexit_match_inout(vexit, false, IOP_TEST_VALUE, 4, &v)) { 128 readings[nread].when = gethrtime(); 129 readings[nread].value = v; 130 ventry_fulfill_inout(vexit, ventry, 0); 131 132 nread++; 133 if (nread != 2) { 134 continue; 135 } 136 137 if (check_reading(readings[0], readings[1], divisor, 138 loops, margin_ticks, margin_ppm)) { 139 (void) printf("good result\n"); 140 return; 141 } else { 142 nrepeat++; 143 if (nrepeat < 3) { 144 nread = 0; 145 (void) printf("retry %u\n", nrepeat); 146 continue; 147 } 148 test_fail_msg("bad result after %u retries\n", 149 nrepeat); 150 } 151 } else { 152 test_fail_vmexit(vexit); 153 } 154 } while (true); 155 } 156 157 int 158 main(int argc, char *argv[]) 159 { 160 const char *test_suite_name = basename(argv[0]); 161 struct vmctx *ctx = NULL; 162 int err; 163 164 ctx = test_initialize(test_suite_name); 165 166 err = test_setup_vcpu(ctx, 0, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 167 if (err != 0) { 168 test_fail_errno(err, "Could not initialize vcpu0"); 169 } 170 171 struct vm_entry ventry = { 0 }; 172 struct vm_exit vexit = { 0 }; 173 174 run_test(ctx, 4, 3, &ventry, &vexit); 175 run_test(ctx, 2, 4, &ventry, &vexit); 176 test_pass(); 177 return (0); 178 } 179