xref: /illumos-gate/usr/src/test/bhyve-tests/tests/inst_emul/payload_cpuid_guest_state.c (revision 8ed63bc6926cb5db26dff2588e5d1622eda438ab)
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