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 "payload_common.h" 17 #include "payload_utils.h" 18 19 /* Arbitrary 2MB limit to keep heap away from stack */ 20 #define HEAP_LIMIT 0x200000 21 22 #define MSR_APICBASE 0x01b 23 #define IOP_ICU1 0x20 24 #define ADDR_IOAPIC_BASE 0xfec00000 25 26 typedef struct test_data { 27 uint32_t count; 28 uint64_t *data; 29 } test_data_t; 30 31 static void 32 zero_data(const test_data_t *td) 33 { 34 for (uint32_t i = 0; i < td->count; i++) { 35 td->data[i] = 0; 36 } 37 } 38 39 static void 40 output_data(const test_data_t *td, uint64_t tsc_start) 41 { 42 for (uint32_t i = td->count - 1; i > 0; i--) { 43 td->data[i] -= td->data[i - 1]; 44 } 45 td->data[0] -= tsc_start; 46 47 /* 48 * Output the low 32-bits of the data pointers, since that is adequate 49 * while the test resides wholly in lowmem. 50 */ 51 outl(IOP_TEST_VALUE, (uint32_t)(uintptr_t)td->data); 52 } 53 54 static uint32_t 55 mmio_read4(volatile uint32_t *ptr) 56 { 57 return (*ptr); 58 } 59 60 /* 61 * For a relatively cheap exit, rdmsr(APICBASE) should be suitable, since its 62 * emulation is dead simple and LAPIC-related MSR operations are handled within 63 * the tight confines of the SVM/VMX vmrun loop. 64 */ 65 static void 66 do_test_rdmsr(const test_data_t *td) 67 { 68 zero_data(td); 69 70 const uint64_t tsc_start = rdtsc(); 71 for (uint32_t i = 0; i < td->count; i++) { 72 (void) rdmsr(MSR_APICBASE); 73 td->data[i] = rdtsc(); 74 } 75 76 output_data(td, tsc_start); 77 } 78 79 /* 80 * For a moderately priced exit, an IO port read from the ATPIC should suffice. 81 * This will take us out of the SVM/VMX vmrun loop and into the instruction 82 * emulation, but the instruction fetch/decode should already be taken care of 83 * by the hardware, and no further memory (guest) accesses are required. 84 */ 85 static void 86 do_test_inb(const test_data_t *td) 87 { 88 zero_data(td); 89 90 const uint64_t tsc_start = rdtsc(); 91 for (uint32_t i = 0; i < td->count; i++) { 92 (void) inb(IOP_ICU1); 93 td->data[i] = rdtsc(); 94 } 95 96 output_data(td, tsc_start); 97 } 98 99 /* 100 * For a more expensive exit, read from the selector register in the IOAPIC. 101 * The device emulation is handled in-kernel, but the instruction will need to 102 * (potentially) fetched and decoded. 103 */ 104 static void 105 do_test_mmio_cheap(const test_data_t *td) 106 { 107 zero_data(td); 108 volatile uint32_t *ioapic_regsel = (void *)(uintptr_t)ADDR_IOAPIC_BASE; 109 110 const uint64_t tsc_start = rdtsc(); 111 for (uint32_t i = 0; i < td->count; i++) { 112 (void) mmio_read4(ioapic_regsel); 113 td->data[i] = rdtsc(); 114 } 115 116 output_data(td, tsc_start); 117 } 118 119 void 120 start(void) 121 { 122 123 /* Get the number of repetitions per test */ 124 const uint32_t count = inl(IOP_TEST_PARAM0); 125 126 if (count * sizeof (uint64_t) > HEAP_LIMIT) { 127 test_msg("excessive test count for memory sz"); 128 test_result_fail(); 129 return; 130 } 131 132 test_data_t td = { 133 .count = count, 134 .data = (uint64_t *)(uintptr_t)MEM_LOC_HEAP, 135 }; 136 137 do_test_rdmsr(&td); 138 do_test_inb(&td); 139 do_test_mmio_cheap(&td); 140 141 test_result_pass(); 142 } 143