]>
Commit | Line | Data |
---|---|---|
6efef58e EH |
1 | #include "qemu/osdep.h" |
2 | #include "qemu-common.h" | |
6efef58e | 3 | #include "qapi/qmp/qdict.h" |
47e6b297 | 4 | #include "qapi/qmp/qlist.h" |
01b2ffce | 5 | #include "qapi/qmp/qnum.h" |
a2f9976e | 6 | #include "qapi/qmp/qbool.h" |
6efef58e EH |
7 | #include "libqtest.h" |
8 | ||
9 | static char *get_cpu0_qom_path(void) | |
10 | { | |
11 | QDict *resp; | |
12 | QList *ret; | |
13 | QDict *cpu0; | |
14 | char *path; | |
15 | ||
16 | resp = qmp("{'execute': 'query-cpus', 'arguments': {}}"); | |
17 | g_assert(qdict_haskey(resp, "return")); | |
18 | ret = qdict_get_qlist(resp, "return"); | |
19 | ||
20 | cpu0 = qobject_to_qdict(qlist_peek(ret)); | |
21 | path = g_strdup(qdict_get_str(cpu0, "qom_path")); | |
22 | QDECREF(resp); | |
23 | return path; | |
24 | } | |
25 | ||
26 | static QObject *qom_get(const char *path, const char *prop) | |
27 | { | |
28 | QDict *resp = qmp("{ 'execute': 'qom-get'," | |
29 | " 'arguments': { 'path': %s," | |
30 | " 'property': %s } }", | |
31 | path, prop); | |
32 | QObject *ret = qdict_get(resp, "return"); | |
33 | qobject_incref(ret); | |
34 | QDECREF(resp); | |
35 | return ret; | |
36 | } | |
37 | ||
83a00f60 | 38 | #ifdef CONFIG_HAS_GLIB_SUBPROCESS_TESTS |
a2f9976e EH |
39 | static bool qom_get_bool(const char *path, const char *prop) |
40 | { | |
41 | QBool *value = qobject_to_qbool(qom_get(path, prop)); | |
42 | bool b = qbool_get_bool(value); | |
43 | ||
44 | QDECREF(value); | |
45 | return b; | |
46 | } | |
83a00f60 | 47 | #endif |
a2f9976e | 48 | |
6efef58e EH |
49 | typedef struct CpuidTestArgs { |
50 | const char *cmdline; | |
51 | const char *property; | |
52 | int64_t expected_value; | |
53 | } CpuidTestArgs; | |
54 | ||
55 | static void test_cpuid_prop(const void *data) | |
56 | { | |
57 | const CpuidTestArgs *args = data; | |
58 | char *path; | |
01b2ffce MAL |
59 | QNum *value; |
60 | int64_t val; | |
6efef58e EH |
61 | |
62 | qtest_start(args->cmdline); | |
63 | path = get_cpu0_qom_path(); | |
01b2ffce MAL |
64 | value = qobject_to_qnum(qom_get(path, args->property)); |
65 | g_assert(qnum_get_try_int(value, &val)); | |
66 | g_assert_cmpint(val, ==, args->expected_value); | |
6efef58e EH |
67 | qtest_end(); |
68 | ||
69 | QDECREF(value); | |
70 | g_free(path); | |
71 | } | |
72 | ||
73 | static void add_cpuid_test(const char *name, const char *cmdline, | |
74 | const char *property, int64_t expected_value) | |
75 | { | |
76 | CpuidTestArgs *args = g_new0(CpuidTestArgs, 1); | |
77 | args->cmdline = cmdline; | |
78 | args->property = property; | |
79 | args->expected_value = expected_value; | |
80 | qtest_add_data_func(name, args, test_cpuid_prop); | |
81 | } | |
82 | ||
17e8f541 EH |
83 | |
84 | /* Parameters to a add_feature_test() test case */ | |
85 | typedef struct FeatureTestArgs { | |
86 | /* cmdline to start QEMU */ | |
87 | const char *cmdline; | |
88 | /* | |
89 | * cpuid-input-eax and cpuid-input-ecx values to look for, | |
90 | * in "feature-words" and "filtered-features" properties. | |
91 | */ | |
92 | uint32_t in_eax, in_ecx; | |
93 | /* The register name to look for, in the X86CPUFeatureWordInfo array */ | |
94 | const char *reg; | |
95 | /* The bit to check in X86CPUFeatureWordInfo.features */ | |
96 | int bitnr; | |
97 | /* The expected value for the bit in (X86CPUFeatureWordInfo.features) */ | |
98 | bool expected_value; | |
99 | } FeatureTestArgs; | |
100 | ||
101 | /* Get the value for a feature word in a X86CPUFeatureWordInfo list */ | |
102 | static uint32_t get_feature_word(QList *features, uint32_t eax, uint32_t ecx, | |
103 | const char *reg) | |
104 | { | |
105 | const QListEntry *e; | |
106 | ||
107 | for (e = qlist_first(features); e; e = qlist_next(e)) { | |
108 | QDict *w = qobject_to_qdict(qlist_entry_obj(e)); | |
109 | const char *rreg = qdict_get_str(w, "cpuid-register"); | |
110 | uint32_t reax = qdict_get_int(w, "cpuid-input-eax"); | |
111 | bool has_ecx = qdict_haskey(w, "cpuid-input-ecx"); | |
112 | uint32_t recx = 0; | |
01b2ffce | 113 | int64_t val; |
17e8f541 EH |
114 | |
115 | if (has_ecx) { | |
116 | recx = qdict_get_int(w, "cpuid-input-ecx"); | |
117 | } | |
118 | if (eax == reax && (!has_ecx || ecx == recx) && !strcmp(rreg, reg)) { | |
01b2ffce MAL |
119 | g_assert(qnum_get_try_int(qobject_to_qnum(qdict_get(w, "features")), |
120 | &val)); | |
121 | return val; | |
17e8f541 EH |
122 | } |
123 | } | |
124 | return 0; | |
125 | } | |
126 | ||
127 | static void test_feature_flag(const void *data) | |
128 | { | |
129 | const FeatureTestArgs *args = data; | |
130 | char *path; | |
131 | QList *present, *filtered; | |
132 | uint32_t value; | |
133 | ||
134 | qtest_start(args->cmdline); | |
135 | path = get_cpu0_qom_path(); | |
136 | present = qobject_to_qlist(qom_get(path, "feature-words")); | |
137 | filtered = qobject_to_qlist(qom_get(path, "filtered-features")); | |
138 | value = get_feature_word(present, args->in_eax, args->in_ecx, args->reg); | |
139 | value |= get_feature_word(filtered, args->in_eax, args->in_ecx, args->reg); | |
140 | qtest_end(); | |
141 | ||
142 | g_assert(!!(value & (1U << args->bitnr)) == args->expected_value); | |
143 | ||
144 | QDECREF(present); | |
145 | QDECREF(filtered); | |
146 | g_free(path); | |
147 | } | |
148 | ||
149 | /* | |
150 | * Add test case to ensure that a given feature flag is set in | |
151 | * either "feature-words" or "filtered-features", when running QEMU | |
152 | * using cmdline | |
153 | */ | |
154 | static FeatureTestArgs *add_feature_test(const char *name, const char *cmdline, | |
155 | uint32_t eax, uint32_t ecx, | |
156 | const char *reg, int bitnr, | |
157 | bool expected_value) | |
158 | { | |
159 | FeatureTestArgs *args = g_new0(FeatureTestArgs, 1); | |
160 | args->cmdline = cmdline; | |
161 | args->in_eax = eax; | |
162 | args->in_ecx = ecx; | |
163 | args->reg = reg; | |
164 | args->bitnr = bitnr; | |
165 | args->expected_value = expected_value; | |
166 | qtest_add_data_func(name, args, test_feature_flag); | |
167 | return args; | |
168 | } | |
169 | ||
83a00f60 EH |
170 | #ifdef CONFIG_HAS_GLIB_SUBPROCESS_TESTS |
171 | static void test_plus_minus_subprocess(void) | |
a2f9976e EH |
172 | { |
173 | char *path; | |
174 | ||
175 | /* Rules: | |
176 | * 1)"-foo" overrides "+foo" | |
177 | * 2) "[+-]foo" overrides "foo=..." | |
178 | * 3) Old feature names with underscores (e.g. "sse4_2") | |
179 | * should keep working | |
180 | * | |
83a00f60 EH |
181 | * Note: rules 1 and 2 are planned to be removed soon, and |
182 | * should generate a warning. | |
a2f9976e EH |
183 | */ |
184 | qtest_start("-cpu pentium,-fpu,+fpu,-mce,mce=on,+cx8,cx8=off,+sse4_1,sse4_2=on"); | |
185 | path = get_cpu0_qom_path(); | |
186 | ||
187 | g_assert_false(qom_get_bool(path, "fpu")); | |
188 | g_assert_false(qom_get_bool(path, "mce")); | |
189 | g_assert_true(qom_get_bool(path, "cx8")); | |
190 | ||
191 | /* Test both the original and the alias feature names: */ | |
192 | g_assert_true(qom_get_bool(path, "sse4-1")); | |
193 | g_assert_true(qom_get_bool(path, "sse4.1")); | |
194 | ||
195 | g_assert_true(qom_get_bool(path, "sse4-2")); | |
196 | g_assert_true(qom_get_bool(path, "sse4.2")); | |
197 | ||
198 | qtest_end(); | |
199 | g_free(path); | |
200 | } | |
201 | ||
83a00f60 EH |
202 | static void test_plus_minus(void) |
203 | { | |
204 | g_test_trap_subprocess("/x86/cpuid/parsing-plus-minus/subprocess", 0, 0); | |
205 | g_test_trap_assert_passed(); | |
206 | g_test_trap_assert_stderr("*Ambiguous CPU model string. " | |
207 | "Don't mix both \"-mce\" and \"mce=on\"*"); | |
208 | g_test_trap_assert_stderr("*Ambiguous CPU model string. " | |
209 | "Don't mix both \"+cx8\" and \"cx8=off\"*"); | |
210 | g_test_trap_assert_stdout(""); | |
211 | } | |
212 | #endif | |
213 | ||
6efef58e EH |
214 | int main(int argc, char **argv) |
215 | { | |
216 | g_test_init(&argc, &argv, NULL); | |
217 | ||
83a00f60 EH |
218 | #ifdef CONFIG_HAS_GLIB_SUBPROCESS_TESTS |
219 | g_test_add_func("/x86/cpuid/parsing-plus-minus/subprocess", | |
220 | test_plus_minus_subprocess); | |
221 | g_test_add_func("/x86/cpuid/parsing-plus-minus", test_plus_minus); | |
222 | #endif | |
a2f9976e | 223 | |
6efef58e EH |
224 | /* Original level values for CPU models: */ |
225 | add_cpuid_test("x86/cpuid/phenom/level", | |
226 | "-cpu phenom", "level", 5); | |
227 | add_cpuid_test("x86/cpuid/Conroe/level", | |
228 | "-cpu Conroe", "level", 10); | |
229 | add_cpuid_test("x86/cpuid/SandyBridge/level", | |
230 | "-cpu SandyBridge", "level", 0xd); | |
231 | add_cpuid_test("x86/cpuid/486/xlevel", | |
232 | "-cpu 486", "xlevel", 0); | |
233 | add_cpuid_test("x86/cpuid/core2duo/xlevel", | |
234 | "-cpu core2duo", "xlevel", 0x80000008); | |
235 | add_cpuid_test("x86/cpuid/phenom/xlevel", | |
236 | "-cpu phenom", "xlevel", 0x8000001A); | |
0c3d7c00 EH |
237 | add_cpuid_test("x86/cpuid/athlon/xlevel", |
238 | "-cpu athlon", "xlevel", 0x80000008); | |
6efef58e EH |
239 | |
240 | /* If level is not large enough, it should increase automatically: */ | |
c39c0edf EH |
241 | /* CPUID[6].EAX: */ |
242 | add_cpuid_test("x86/cpuid/auto-level/phenom/arat", | |
243 | "-cpu 486,+arat", "level", 6); | |
6efef58e EH |
244 | /* CPUID[EAX=7,ECX=0].EBX: */ |
245 | add_cpuid_test("x86/cpuid/auto-level/phenom/fsgsbase", | |
246 | "-cpu phenom,+fsgsbase", "level", 7); | |
c39c0edf EH |
247 | /* CPUID[EAX=7,ECX=0].ECX: */ |
248 | add_cpuid_test("x86/cpuid/auto-level/phenom/avx512vbmi", | |
249 | "-cpu phenom,+avx512vbmi", "level", 7); | |
250 | /* CPUID[EAX=0xd,ECX=1].EAX: */ | |
251 | add_cpuid_test("x86/cpuid/auto-level/phenom/xsaveopt", | |
252 | "-cpu phenom,+xsaveopt", "level", 0xd); | |
253 | /* CPUID[8000_0001].EDX: */ | |
254 | add_cpuid_test("x86/cpuid/auto-xlevel/486/3dnow", | |
255 | "-cpu 486,+3dnow", "xlevel", 0x80000001); | |
256 | /* CPUID[8000_0001].ECX: */ | |
257 | add_cpuid_test("x86/cpuid/auto-xlevel/486/sse4a", | |
258 | "-cpu 486,+sse4a", "xlevel", 0x80000001); | |
259 | /* CPUID[8000_0007].EDX: */ | |
260 | add_cpuid_test("x86/cpuid/auto-xlevel/486/invtsc", | |
261 | "-cpu 486,+invtsc", "xlevel", 0x80000007); | |
262 | /* CPUID[8000_000A].EDX: */ | |
263 | add_cpuid_test("x86/cpuid/auto-xlevel/486/npt", | |
264 | "-cpu 486,+npt", "xlevel", 0x8000000A); | |
265 | /* CPUID[C000_0001].EDX: */ | |
266 | add_cpuid_test("x86/cpuid/auto-xlevel2/phenom/xstore", | |
267 | "-cpu phenom,+xstore", "xlevel2", 0xC0000001); | |
0c3d7c00 EH |
268 | /* SVM needs CPUID[0x8000000A] */ |
269 | add_cpuid_test("x86/cpuid/auto-xlevel/athlon/svm", | |
270 | "-cpu athlon,+svm", "xlevel", 0x8000000A); | |
c39c0edf | 271 | |
6efef58e EH |
272 | |
273 | /* If level is already large enough, it shouldn't change: */ | |
274 | add_cpuid_test("x86/cpuid/auto-level/SandyBridge/multiple", | |
275 | "-cpu SandyBridge,+arat,+fsgsbase,+avx512vbmi", | |
276 | "level", 0xd); | |
c39c0edf EH |
277 | /* If level is explicitly set, it shouldn't change: */ |
278 | add_cpuid_test("x86/cpuid/auto-level/486/fixed/0xF", | |
279 | "-cpu 486,level=0xF,+arat,+fsgsbase,+avx512vbmi,+xsaveopt", | |
280 | "level", 0xF); | |
281 | add_cpuid_test("x86/cpuid/auto-level/486/fixed/2", | |
282 | "-cpu 486,level=2,+arat,+fsgsbase,+avx512vbmi,+xsaveopt", | |
283 | "level", 2); | |
284 | add_cpuid_test("x86/cpuid/auto-level/486/fixed/0", | |
285 | "-cpu 486,level=0,+arat,+fsgsbase,+avx512vbmi,+xsaveopt", | |
286 | "level", 0); | |
6efef58e EH |
287 | |
288 | /* if xlevel is already large enough, it shouldn't change: */ | |
289 | add_cpuid_test("x86/cpuid/auto-xlevel/phenom/3dnow", | |
0c3d7c00 | 290 | "-cpu phenom,+3dnow,+sse4a,+invtsc,+npt,+svm", |
6efef58e | 291 | "xlevel", 0x8000001A); |
c39c0edf EH |
292 | /* If xlevel is explicitly set, it shouldn't change: */ |
293 | add_cpuid_test("x86/cpuid/auto-xlevel/486/fixed/80000002", | |
0c3d7c00 | 294 | "-cpu 486,xlevel=0x80000002,+3dnow,+sse4a,+invtsc,+npt,+svm", |
c39c0edf EH |
295 | "xlevel", 0x80000002); |
296 | add_cpuid_test("x86/cpuid/auto-xlevel/486/fixed/8000001A", | |
0c3d7c00 | 297 | "-cpu 486,xlevel=0x8000001A,+3dnow,+sse4a,+invtsc,+npt,+svm", |
c39c0edf EH |
298 | "xlevel", 0x8000001A); |
299 | add_cpuid_test("x86/cpuid/auto-xlevel/phenom/fixed/0", | |
0c3d7c00 | 300 | "-cpu 486,xlevel=0,+3dnow,+sse4a,+invtsc,+npt,+svm", |
c39c0edf | 301 | "xlevel", 0); |
6efef58e EH |
302 | |
303 | /* if xlevel2 is already large enough, it shouldn't change: */ | |
304 | add_cpuid_test("x86/cpuid/auto-xlevel2/486/fixed", | |
305 | "-cpu 486,xlevel2=0xC0000002,+xstore", | |
306 | "xlevel2", 0xC0000002); | |
307 | ||
df3e9af8 EH |
308 | /* Check compatibility of old machine-types that didn't |
309 | * auto-increase level/xlevel/xlevel2: */ | |
310 | ||
311 | add_cpuid_test("x86/cpuid/auto-level/pc-2.7", | |
312 | "-machine pc-i440fx-2.7 -cpu 486,+arat,+avx512vbmi,+xsaveopt", | |
313 | "level", 1); | |
314 | add_cpuid_test("x86/cpuid/auto-xlevel/pc-2.7", | |
0c3d7c00 | 315 | "-machine pc-i440fx-2.7 -cpu 486,+3dnow,+sse4a,+invtsc,+npt,+svm", |
df3e9af8 EH |
316 | "xlevel", 0); |
317 | add_cpuid_test("x86/cpuid/auto-xlevel2/pc-2.7", | |
318 | "-machine pc-i440fx-2.7 -cpu 486,+xstore", | |
319 | "xlevel2", 0); | |
1f435716 EH |
320 | /* |
321 | * QEMU 1.4.0 had auto-level enabled for CPUID[7], already, | |
322 | * and the compat code that sets default level shouldn't | |
323 | * disable the auto-level=7 code: | |
324 | */ | |
325 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-1.4/off", | |
326 | "-machine pc-i440fx-1.4 -cpu Nehalem", | |
327 | "level", 2); | |
328 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-1.5/on", | |
329 | "-machine pc-i440fx-1.4 -cpu Nehalem,+smap", | |
330 | "level", 7); | |
331 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.3/off", | |
332 | "-machine pc-i440fx-2.3 -cpu Penryn", | |
333 | "level", 4); | |
334 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.3/on", | |
335 | "-machine pc-i440fx-2.3 -cpu Penryn,+erms", | |
336 | "level", 7); | |
337 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.9/off", | |
338 | "-machine pc-i440fx-2.9 -cpu Conroe", | |
339 | "level", 10); | |
340 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.9/on", | |
341 | "-machine pc-i440fx-2.9 -cpu Conroe,+erms", | |
342 | "level", 10); | |
343 | ||
344 | /* | |
345 | * xlevel doesn't have any feature that triggers auto-level | |
346 | * code on old machine-types. Just check that the compat code | |
347 | * is working correctly: | |
348 | */ | |
349 | add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.3", | |
350 | "-machine pc-i440fx-2.3 -cpu SandyBridge", | |
351 | "xlevel", 0x8000000a); | |
352 | add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.4/npt-off", | |
353 | "-machine pc-i440fx-2.4 -cpu SandyBridge,", | |
354 | "xlevel", 0x80000008); | |
355 | add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.4/npt-on", | |
356 | "-machine pc-i440fx-2.4 -cpu SandyBridge,+npt", | |
357 | "xlevel", 0x80000008); | |
df3e9af8 | 358 | |
17e8f541 EH |
359 | /* Test feature parsing */ |
360 | add_feature_test("x86/cpuid/features/plus", | |
361 | "-cpu 486,+arat", | |
362 | 6, 0, "EAX", 2, true); | |
363 | add_feature_test("x86/cpuid/features/minus", | |
364 | "-cpu pentium,-mmx", | |
365 | 1, 0, "EDX", 23, false); | |
366 | add_feature_test("x86/cpuid/features/on", | |
367 | "-cpu 486,arat=on", | |
368 | 6, 0, "EAX", 2, true); | |
369 | add_feature_test("x86/cpuid/features/off", | |
370 | "-cpu pentium,mmx=off", | |
371 | 1, 0, "EDX", 23, false); | |
372 | add_feature_test("x86/cpuid/features/max-plus-invtsc", | |
373 | "-cpu max,+invtsc", | |
374 | 0x80000007, 0, "EDX", 8, true); | |
375 | add_feature_test("x86/cpuid/features/max-invtsc-on", | |
376 | "-cpu max,invtsc=on", | |
377 | 0x80000007, 0, "EDX", 8, true); | |
378 | add_feature_test("x86/cpuid/features/max-minus-mmx", | |
379 | "-cpu max,-mmx", | |
380 | 1, 0, "EDX", 23, false); | |
381 | add_feature_test("x86/cpuid/features/max-invtsc-on,mmx=off", | |
382 | "-cpu max,mmx=off", | |
383 | 1, 0, "EDX", 23, false); | |
384 | ||
6efef58e EH |
385 | return g_test_run(); |
386 | } |