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 uint16_t value; 36 } reading_t; 37 38 static bool 39 check_reading(reading_t before, reading_t after, uint_t tick_margin, 40 uint_t ppm_margin) 41 { 42 hrtime_t time_delta = after.when - before.when; 43 uint16_t tick_delta; 44 45 tick_delta = before.value - after.value; 46 47 /* is the number of ticks OK? */ 48 if (tick_delta < ATPIT_TARGET_TICKS) { 49 test_fail_msg("inadequate passage of ticks %u < %u\n", 50 tick_delta, ATPIT_TARGET_TICKS); 51 } else if ((tick_delta - ATPIT_TARGET_TICKS) > tick_margin) { 52 (void) printf("%u ticks outside margin %u\n", tick_delta, 53 ATPIT_TARGET_TICKS + tick_margin); 54 return (false); 55 } 56 57 hrtime_t time_target = (tick_delta * NANOSEC) / ATPIT_FREQ; 58 59 hrtime_t offset; 60 if (time_delta < time_target) { 61 offset = time_target - time_delta; 62 } else { 63 offset = time_delta - time_target; 64 } 65 uint64_t ppm = (offset * 1000000) / time_target; 66 (void) printf("margin limits: ticks=%u ppm=%lu\n", 67 tick_margin, ppm_margin); 68 (void) printf("%u ticks in %lu ns (error %lu ppm)\n", 69 tick_delta, time_delta, ppm); 70 if (ppm > ppm_margin) { 71 (void) printf("UNACCEPTABLE!\n"); 72 return (false); 73 } 74 return (true); 75 } 76 77 int 78 main(int argc, char *argv[]) 79 { 80 const char *test_suite_name = basename(argv[0]); 81 struct vmctx *ctx = NULL; 82 int err; 83 84 ctx = test_initialize(test_suite_name); 85 86 err = test_setup_vcpu(ctx, 0, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 87 if (err != 0) { 88 test_fail_errno(err, "Could not initialize vcpu0"); 89 } 90 91 struct vm_entry ventry = { 0 }; 92 struct vm_exit vexit = { 0 }; 93 reading_t readings[2]; 94 uint_t nread = 0; 95 uint_t nrepeat = 0; 96 97 /* 98 * Since the PIT is slower to read back (requiring 3 emulated reads), 99 * operate with a more loose ticks margin. 100 */ 101 const uint_t margin_ticks = MAX(1, ATPIT_TARGET_TICKS / 2500); 102 const uint_t margin_ppm = 400; 103 104 do { 105 const enum vm_exit_kind kind = 106 test_run_vcpu(ctx, 0, &ventry, &vexit); 107 if (kind == VEK_REENTR) { 108 continue; 109 } else if (kind != VEK_UNHANDLED) { 110 test_fail_vmexit(&vexit); 111 } 112 113 uint32_t v; 114 if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 2, &v)) { 115 readings[nread].when = gethrtime(); 116 readings[nread].value = v; 117 118 ventry_fulfill_inout(&vexit, &ventry, 0); 119 120 nread++; 121 if (nread != 2) { 122 continue; 123 } 124 125 if (check_reading(readings[0], readings[1], 126 margin_ticks, margin_ppm)) { 127 test_pass(); 128 } else { 129 nrepeat++; 130 if (nrepeat < 3) { 131 nread = 0; 132 (void) printf("retry %u\n", nrepeat); 133 continue; 134 } 135 test_fail_msg("bad result after %u retries\n", 136 nrepeat); 137 } 138 } else { 139 test_fail_vmexit(&vexit); 140 } 141 142 } while (true); 143 144 return (0); 145 } 146