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 #include <errno.h> 23 24 #include <sys/types.h> 25 #include <sys/sysmacros.h> 26 #include <sys/debug.h> 27 #include <sys/vmm.h> 28 #include <sys/vmm_dev.h> 29 #include <vmmapi.h> 30 31 #include "in_guest.h" 32 #include "test_defs.h" 33 34 typedef struct reading { 35 hrtime_t when; 36 uint32_t value; 37 } reading_t; 38 39 static bool 40 check_reading(reading_t before, reading_t after, uint_t tick_margin, 41 uint_t ppm_margin) 42 { 43 hrtime_t time_delta = after.when - before.when; 44 uint32_t tick_delta; 45 46 if (after.value < before.value) { 47 /* handle rollover */ 48 tick_delta = (UINT32_MAX - before.value) + after.value; 49 } else { 50 tick_delta = after.value - before.value; 51 } 52 53 /* is the number of ticks OK? */ 54 if (tick_delta < PMTMR_TARGET_TICKS) { 55 test_fail_msg("inadequate passage of ticks %u < %u\n", 56 tick_delta, PMTMR_TARGET_TICKS); 57 } else if ((tick_delta - PMTMR_TARGET_TICKS) > tick_margin) { 58 (void) printf("%u ticks outside margin %u\n", tick_delta, 59 PMTMR_TARGET_TICKS + tick_margin); 60 return (false); 61 } 62 63 hrtime_t time_target = (tick_delta * NANOSEC) / PMTMR_FREQ; 64 65 hrtime_t offset; 66 if (time_delta < time_target) { 67 offset = time_target - time_delta; 68 } else { 69 offset = time_delta - time_target; 70 } 71 uint64_t ppm = (offset * 1000000) / time_target; 72 (void) printf("margin limits: ticks=%u ppm=%lu\n", 73 tick_margin, ppm_margin); 74 (void) printf("%u ticks in %lu ns (error %lu ppm)\n", 75 tick_delta, time_delta, ppm); 76 if (ppm > ppm_margin) { 77 (void) printf("UNACCEPTABLE!\n"); 78 return (false); 79 } 80 return (true); 81 } 82 83 int 84 main(int argc, char *argv[]) 85 { 86 const char *test_suite_name = basename(argv[0]); 87 struct vmctx *ctx = NULL; 88 struct vcpu *vcpu; 89 int err; 90 91 ctx = test_initialize(test_suite_name); 92 93 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { 94 test_fail_errno(errno, "Could not open vcpu0"); 95 } 96 97 err = vm_pmtmr_set_location(ctx, IOP_PMTMR); 98 if (err != 0) { 99 test_fail_errno(err, "Could not place pmtmr"); 100 } 101 102 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 103 if (err != 0) { 104 test_fail_errno(err, "Could not initialize vcpu0"); 105 } 106 107 struct vm_entry ventry = { 0 }; 108 struct vm_exit vexit = { 0 }; 109 reading_t readings[2]; 110 uint_t nread = 0; 111 uint_t nrepeat = 0; 112 113 const uint_t margin_ticks = MAX(1, PMTMR_TARGET_TICKS / 10000); 114 const uint_t margin_ppm = 400; 115 116 do { 117 const enum vm_exit_kind kind = 118 test_run_vcpu(vcpu, &ventry, &vexit); 119 if (kind == VEK_REENTR) { 120 continue; 121 } else if (kind != VEK_UNHANDLED) { 122 test_fail_vmexit(&vexit); 123 } 124 125 uint32_t v; 126 if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 4, &v)) { 127 readings[nread].when = gethrtime(); 128 readings[nread].value = vexit.u.inout.eax; 129 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], 138 margin_ticks, margin_ppm)) { 139 test_pass(); 140 } else { 141 nrepeat++; 142 if (nrepeat < 3) { 143 nread = 0; 144 (void) printf("retry %u\n", nrepeat); 145 continue; 146 } 147 test_fail_msg("bad result after %u retries\n", 148 nrepeat); 149 } 150 } else { 151 test_fail_vmexit(&vexit); 152 } 153 154 } while (true); 155 156 return (0); 157 } 158