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
zero_data(const test_data_t * td)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
output_data(const test_data_t * td,uint64_t tsc_start)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
mmio_read4(volatile uint32_t * ptr)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
do_test_rdmsr(const test_data_t * td)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
do_test_inb(const test_data_t * td)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
do_test_mmio_cheap(const test_data_t * td)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
start(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