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 2025 Oxide Computer Company
14 */
15
16 #include "payload_common.h"
17 #include "payload_utils.h"
18 #include "cpuid_guest_state.h"
19
20 #define CPUID_APIC 0x00000200
21 #define CPUID2_XSAVE 0x04000000
22 #define CPUID2_OSXSAVE 0x08000000
23 #define APICBASE_ENABLED 0x00000800
24 #define CR4_OSXSAVE 0x00040000
25
26 #define XCR0_X87_SSE 0x00000003
27 #define XCR0_AVX 0x00000004
28
29 int
leaf_cmp(const uint32_t * a,const uint32_t * b)30 leaf_cmp(const uint32_t *a, const uint32_t *b)
31 {
32 return (a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]);
33 }
34
35 const uint32_t expected_base[] = {
36 TEST_CPUID_0_EAX,
37 TEST_CPUID_0_EBX,
38 TEST_CPUID_0_ECX,
39 TEST_CPUID_0_EDX
40 };
41
42 /* Verifies that leaf 1 returns the test suite's explicitly-set values. */
43 void
test_leaf_1_explicit()44 test_leaf_1_explicit()
45 {
46 uint32_t regs[4];
47
48 cpuid(1, 0, regs);
49 if (regs[0] != TEST_CPUID_1_EAX || regs[1] != TEST_CPUID_1_EBX ||
50 regs[3] != TEST_CPUID_1_EDX) {
51 test_result_fail();
52 }
53 }
54
55 /* Verifies that leaf 1 ecx's value varies as cr4's OSXSAVE bit changes. */
56 void
test_leaf_1_osxsave()57 test_leaf_1_osxsave()
58 {
59 uint32_t regs[4];
60 uint32_t orig_cr4 = getcr4();
61
62 setcr4(getcr4() & ~CR4_OSXSAVE);
63 cpuid(1, 0, regs);
64 if (regs[2] != (TEST_CPUID_1_ECX & ~CPUID2_OSXSAVE)) {
65 test_result_fail();
66 }
67
68 /* Turn OSXSAVE back on and check that it reappears in CPUID. */
69 setcr4(getcr4() | CR4_OSXSAVE);
70 cpuid(1, 0, regs);
71 if (regs[2] != (TEST_CPUID_1_ECX | CPUID2_OSXSAVE)) {
72 test_result_fail();
73 }
74 setcr4(orig_cr4);
75 }
76
77 /* Verifies that leaf 1 edx's value varies as the APIC enable flag changes. */
78 void
test_leaf_1_apic()79 test_leaf_1_apic()
80 {
81 uint32_t regs[4];
82
83 wrmsr(0x1B, rdmsr(0x1B) & ~APICBASE_ENABLED);
84 cpuid(1, 0, regs);
85 if (regs[3] != (TEST_CPUID_1_EDX & ~CPUID_APIC)) {
86 test_result_fail();
87 }
88 wrmsr(0x1B, rdmsr(0x1B) | APICBASE_ENABLED);
89 cpuid(1, 0, regs);
90 if (regs[3] != (TEST_CPUID_1_EDX | CPUID_APIC)) {
91 test_result_fail();
92 }
93 }
94
95 /* Verifies that leaf D subleaf 0 ebx's value changes as XCR0 changes. */
96 void
test_leaf_d_index_0(bool using_explicit)97 test_leaf_d_index_0(bool using_explicit)
98 {
99 uint32_t regs[4];
100 uint64_t orig_cr4 = getcr4();
101 uint64_t orig_xcr0;
102
103 setcr4(getcr4() | CR4_OSXSAVE);
104 orig_xcr0 = getxcr(0);
105 cpuid(0xD, 0, regs);
106 if (using_explicit) {
107 if (regs[0] != TEST_CPUID_D_0_EAX ||
108 regs[2] != XSAVE_AREA_SIZE_MAX || regs[3] != 0) {
109 test_result_fail();
110 }
111 }
112
113 setxcr(0, XCR0_X87_SSE);
114 cpuid(0xD, 0, regs);
115 if (regs[1] != XSAVE_AREA_SIZE_BASE) {
116 test_result_fail();
117 }
118
119 setxcr(0, XCR0_X87_SSE | XCR0_AVX);
120 cpuid(0xD, 0, regs);
121 if (regs[1] != XSAVE_AREA_SIZE_BASE + XSAVE_AREA_SIZE_AVX) {
122 test_result_fail();
123 }
124
125 setxcr(0, orig_xcr0);
126 setcr4(orig_cr4);
127 }
128
129 /* Verifies that leaf D subleaf 1 ebx's value changes as XCR0 changes. */
130 void
test_leaf_d_index_1(bool using_explicit,bool via_fallback)131 test_leaf_d_index_1(bool using_explicit, bool via_fallback)
132 {
133 uint32_t leaf;
134 uint32_t regs[4];
135 uint64_t orig_cr4 = getcr4();
136 uint64_t orig_xcr0;
137
138 if (via_fallback) {
139 leaf = 0xE;
140 } else {
141 leaf = 0xD;
142 }
143
144 setcr4(getcr4() | CR4_OSXSAVE);
145 orig_xcr0 = getxcr(0);
146 cpuid(leaf, 1, regs);
147 if (using_explicit) {
148 if (regs[0] != TEST_CPUID_D_1_EAX || regs[2] != 0 ||
149 regs[3] != 0) {
150 test_result_fail();
151 }
152 }
153
154 setxcr(0, XCR0_X87_SSE);
155 cpuid(leaf, 1, regs);
156 if (regs[1] != XSAVE_AREA_SIZE_BASE) {
157 test_result_fail();
158 }
159
160 setxcr(0, XCR0_X87_SSE | XCR0_AVX);
161 cpuid(leaf, 1, regs);
162 if (regs[1] != XSAVE_AREA_SIZE_BASE + XSAVE_AREA_SIZE_AVX) {
163 test_result_fail();
164 }
165
166 setxcr(0, orig_xcr0);
167 setcr4(orig_cr4);
168 }
169
170 /*
171 * Tests that CPUID returns the expected values for leaves that are either
172 * expected to be present on the host or that are explicitly supplied by the
173 * test harness.
174 */
175 void
do_basic_test(bool using_explicit)176 do_basic_test(bool using_explicit)
177 {
178 uint32_t regs[4];
179
180 /*
181 * The specific values returned by leaves 0 and 1 are host-dependent,
182 * so only check them when explicitly overriding host CPUID.
183 */
184 if (using_explicit) {
185 cpuid(0, 0, regs);
186 if (!leaf_cmp(regs, expected_base)) {
187 test_result_fail();
188 }
189
190 test_leaf_1_explicit();
191 }
192
193 test_leaf_1_osxsave();
194 test_leaf_1_apic();
195 test_leaf_d_index_0(using_explicit);
196 test_leaf_d_index_1(using_explicit, false);
197 }
198
199 /*
200 * Tests that CPUID leaves are specialized properly when reached by the
201 * "fallback" behavior described in the Intel SDM. The expected behavior is:
202 *
203 * - Querying a leaf that is not present but is less than the maximum
204 * supported standard leaf should return all zeroes.
205 * - Querying a leaf that is greater than the maximum supported standard leaf
206 * should return the values from the maximum supported leaf.
207 */
208 void
do_fallback_test()209 do_fallback_test()
210 {
211 uint32_t regs[4];
212 uint32_t zero_cpuid[4] = {0, 0, 0, 0};
213
214 /*
215 * The fallback entries contain only leaf 0 and leaf D. Leaf 1 should
216 * return zeroes even if the OSXSAVE bit is set in cr4.
217 */
218 setcr4(getcr4() | CR4_OSXSAVE);
219 cpuid(1, 0, regs);
220 if (!leaf_cmp(regs, zero_cpuid)) {
221 test_result_fail();
222 }
223
224 /*
225 * bhyve's fallback implementation currently falls back to the highest
226 * subleaf for a given leaf and not the guest's requested subleaf. This
227 * means that fallback can't be used to test leaf D subleaf 0 (since
228 * it resolves to subleaf 1 instead).
229 */
230 test_leaf_d_index_1(true, true);
231 }
232
233 void
start(void)234 start(void)
235 {
236 do_basic_test(false);
237 outl(IOP_TEST_VALUE, 0);
238 do_basic_test(true);
239 outl(IOP_TEST_VALUE, 1);
240 do_basic_test(true);
241 outl(IOP_TEST_VALUE, 2);
242 do_fallback_test();
243 test_result_pass();
244 }
245