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, 40 uint_t tick_margin, uint_t ppm_margin) 41 { 42 hrtime_t time_delta = after.when - before.when; 43 uint32_t tick_delta; 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 /* timer is counting down, so act appropriately */ 52 if (after.value > before.value) { 53 /* handle rollover */ 54 tick_delta = (UINT32_MAX - after.value) + before.value; 55 } else { 56 tick_delta = before.value - after.value; 57 } 58 59 /* is the number of ticks OK? */ 60 if (tick_delta < LAPIC_TARGET_TICKS) { 61 test_fail_msg("inadequate passage of ticks %u < %u\n", 62 tick_delta, LAPIC_TARGET_TICKS); 63 } else if ((tick_delta - LAPIC_TARGET_TICKS) > tick_margin) { 64 (void) printf("%u ticks outside margin %u\n", tick_delta, 65 LAPIC_TARGET_TICKS + tick_margin); 66 return (false); 67 } 68 69 hrtime_t time_target = (tick_delta * NANOSEC * divisor) / LAPIC_FREQ; 70 71 hrtime_t offset; 72 if (time_delta < time_target) { 73 offset = time_target - time_delta; 74 } else { 75 offset = time_delta - time_target; 76 } 77 uint64_t ppm = (offset * 1000000) / time_target; 78 (void) printf("params: tick_margin=%u ppm_margin=%lu divisor=%u\n", 79 tick_margin, ppm_margin, divisor); 80 (void) printf("%u ticks in %lu ns (error %lu ppm)\n", 81 tick_delta, time_delta, ppm); 82 if (ppm > ppm_margin) { 83 (void) printf("UNACCEPTABLE!\n"); 84 return (false); 85 } 86 return (true); 87 } 88 89 90 static void 91 test_for_divisor(struct vmctx *ctx, uint_t divisor, struct vm_entry *ventry, 92 struct vm_exit *vexit) 93 { 94 reading_t readings[2]; 95 uint_t nread = 0; 96 uint_t nrepeat = 0; 97 98 const uint_t margin_ticks = MAX(1, LAPIC_TARGET_TICKS / 5000); 99 const uint_t margin_ppm = 400; 100 101 do { 102 const enum vm_exit_kind kind = 103 test_run_vcpu(ctx, 0, ventry, vexit); 104 if (kind == VEK_REENTR) { 105 continue; 106 } else if (kind != VEK_UNHANDLED) { 107 test_fail_vmexit(vexit); 108 } 109 110 /* input the divisor */ 111 if (vexit_match_inout(vexit, true, IOP_TEST_PARAM, 4, NULL)) { 112 ventry_fulfill_inout(vexit, ventry, divisor); 113 continue; 114 } 115 116 uint32_t v; 117 if (vexit_match_inout(vexit, false, IOP_TEST_VALUE, 4, &v)) { 118 readings[nread].when = gethrtime(); 119 readings[nread].value = v; 120 ventry_fulfill_inout(vexit, ventry, 0); 121 122 nread++; 123 if (nread != 2) { 124 continue; 125 } 126 127 if (check_reading(readings[0], readings[1], divisor, 128 margin_ticks, margin_ppm)) { 129 (void) printf("good result\n"); 130 return; 131 } else { 132 nrepeat++; 133 if (nrepeat < 3) { 134 nread = 0; 135 (void) printf("retry %u\n", nrepeat); 136 continue; 137 } 138 test_fail_msg("bad result after %u retries\n", 139 nrepeat); 140 } 141 } else { 142 test_fail_vmexit(vexit); 143 } 144 } while (true); 145 } 146 147 int 148 main(int argc, char *argv[]) 149 { 150 const char *test_suite_name = basename(argv[0]); 151 struct vmctx *ctx = NULL; 152 int err; 153 154 ctx = test_initialize(test_suite_name); 155 156 err = test_setup_vcpu(ctx, 0, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 157 if (err != 0) { 158 test_fail_errno(err, "Could not initialize vcpu0"); 159 } 160 161 struct vm_entry ventry = { 0 }; 162 struct vm_exit vexit = { 0 }; 163 164 test_for_divisor(ctx, 2, &ventry, &vexit); 165 test_for_divisor(ctx, 4, &ventry, &vexit); 166 test_for_divisor(ctx, 16, &ventry, &vexit); 167 test_pass(); 168 return (0); 169 } 170