]>
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 | ||
7dc847eb | 20 | cpu0 = qobject_to(QDict, qlist_peek(ret)); |
6efef58e | 21 | path = g_strdup(qdict_get_str(cpu0, "qom_path")); |
cb3e7f08 | 22 | qobject_unref(resp); |
6efef58e EH |
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"); | |
cb3e7f08 MAL |
33 | qobject_ref(ret); |
34 | qobject_unref(resp); | |
6efef58e EH |
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 | { | |
7dc847eb | 41 | QBool *value = qobject_to(QBool, qom_get(path, prop)); |
a2f9976e EH |
42 | bool b = qbool_get_bool(value); |
43 | ||
cb3e7f08 | 44 | qobject_unref(value); |
a2f9976e EH |
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(); | |
7dc847eb | 64 | value = qobject_to(QNum, qom_get(path, args->property)); |
01b2ffce MAL |
65 | g_assert(qnum_get_try_int(value, &val)); |
66 | g_assert_cmpint(val, ==, args->expected_value); | |
6efef58e EH |
67 | qtest_end(); |
68 | ||
cb3e7f08 | 69 | qobject_unref(value); |
6efef58e EH |
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)) { | |
7dc847eb | 108 | QDict *w = qobject_to(QDict, qlist_entry_obj(e)); |
17e8f541 EH |
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)) { | |
7dc847eb HR |
119 | g_assert(qnum_get_try_int(qobject_to(QNum, |
120 | qdict_get(w, "features")), | |
121 | &val)); | |
01b2ffce | 122 | return val; |
17e8f541 EH |
123 | } |
124 | } | |
125 | return 0; | |
126 | } | |
127 | ||
128 | static void test_feature_flag(const void *data) | |
129 | { | |
130 | const FeatureTestArgs *args = data; | |
131 | char *path; | |
132 | QList *present, *filtered; | |
133 | uint32_t value; | |
134 | ||
135 | qtest_start(args->cmdline); | |
136 | path = get_cpu0_qom_path(); | |
7dc847eb HR |
137 | present = qobject_to(QList, qom_get(path, "feature-words")); |
138 | filtered = qobject_to(QList, qom_get(path, "filtered-features")); | |
17e8f541 EH |
139 | value = get_feature_word(present, args->in_eax, args->in_ecx, args->reg); |
140 | value |= get_feature_word(filtered, args->in_eax, args->in_ecx, args->reg); | |
141 | qtest_end(); | |
142 | ||
143 | g_assert(!!(value & (1U << args->bitnr)) == args->expected_value); | |
144 | ||
cb3e7f08 MAL |
145 | qobject_unref(present); |
146 | qobject_unref(filtered); | |
17e8f541 EH |
147 | g_free(path); |
148 | } | |
149 | ||
150 | /* | |
151 | * Add test case to ensure that a given feature flag is set in | |
152 | * either "feature-words" or "filtered-features", when running QEMU | |
153 | * using cmdline | |
154 | */ | |
155 | static FeatureTestArgs *add_feature_test(const char *name, const char *cmdline, | |
156 | uint32_t eax, uint32_t ecx, | |
157 | const char *reg, int bitnr, | |
158 | bool expected_value) | |
159 | { | |
160 | FeatureTestArgs *args = g_new0(FeatureTestArgs, 1); | |
161 | args->cmdline = cmdline; | |
162 | args->in_eax = eax; | |
163 | args->in_ecx = ecx; | |
164 | args->reg = reg; | |
165 | args->bitnr = bitnr; | |
166 | args->expected_value = expected_value; | |
167 | qtest_add_data_func(name, args, test_feature_flag); | |
168 | return args; | |
169 | } | |
170 | ||
83a00f60 EH |
171 | #ifdef CONFIG_HAS_GLIB_SUBPROCESS_TESTS |
172 | static void test_plus_minus_subprocess(void) | |
a2f9976e EH |
173 | { |
174 | char *path; | |
175 | ||
176 | /* Rules: | |
177 | * 1)"-foo" overrides "+foo" | |
178 | * 2) "[+-]foo" overrides "foo=..." | |
179 | * 3) Old feature names with underscores (e.g. "sse4_2") | |
180 | * should keep working | |
181 | * | |
83a00f60 EH |
182 | * Note: rules 1 and 2 are planned to be removed soon, and |
183 | * should generate a warning. | |
a2f9976e EH |
184 | */ |
185 | qtest_start("-cpu pentium,-fpu,+fpu,-mce,mce=on,+cx8,cx8=off,+sse4_1,sse4_2=on"); | |
186 | path = get_cpu0_qom_path(); | |
187 | ||
188 | g_assert_false(qom_get_bool(path, "fpu")); | |
189 | g_assert_false(qom_get_bool(path, "mce")); | |
190 | g_assert_true(qom_get_bool(path, "cx8")); | |
191 | ||
192 | /* Test both the original and the alias feature names: */ | |
193 | g_assert_true(qom_get_bool(path, "sse4-1")); | |
194 | g_assert_true(qom_get_bool(path, "sse4.1")); | |
195 | ||
196 | g_assert_true(qom_get_bool(path, "sse4-2")); | |
197 | g_assert_true(qom_get_bool(path, "sse4.2")); | |
198 | ||
199 | qtest_end(); | |
200 | g_free(path); | |
201 | } | |
202 | ||
83a00f60 EH |
203 | static void test_plus_minus(void) |
204 | { | |
205 | g_test_trap_subprocess("/x86/cpuid/parsing-plus-minus/subprocess", 0, 0); | |
206 | g_test_trap_assert_passed(); | |
207 | g_test_trap_assert_stderr("*Ambiguous CPU model string. " | |
208 | "Don't mix both \"-mce\" and \"mce=on\"*"); | |
209 | g_test_trap_assert_stderr("*Ambiguous CPU model string. " | |
210 | "Don't mix both \"+cx8\" and \"cx8=off\"*"); | |
211 | g_test_trap_assert_stdout(""); | |
212 | } | |
213 | #endif | |
214 | ||
6efef58e EH |
215 | int main(int argc, char **argv) |
216 | { | |
217 | g_test_init(&argc, &argv, NULL); | |
218 | ||
83a00f60 EH |
219 | #ifdef CONFIG_HAS_GLIB_SUBPROCESS_TESTS |
220 | g_test_add_func("/x86/cpuid/parsing-plus-minus/subprocess", | |
221 | test_plus_minus_subprocess); | |
222 | g_test_add_func("/x86/cpuid/parsing-plus-minus", test_plus_minus); | |
223 | #endif | |
a2f9976e | 224 | |
6efef58e EH |
225 | /* Original level values for CPU models: */ |
226 | add_cpuid_test("x86/cpuid/phenom/level", | |
227 | "-cpu phenom", "level", 5); | |
228 | add_cpuid_test("x86/cpuid/Conroe/level", | |
229 | "-cpu Conroe", "level", 10); | |
230 | add_cpuid_test("x86/cpuid/SandyBridge/level", | |
231 | "-cpu SandyBridge", "level", 0xd); | |
232 | add_cpuid_test("x86/cpuid/486/xlevel", | |
233 | "-cpu 486", "xlevel", 0); | |
234 | add_cpuid_test("x86/cpuid/core2duo/xlevel", | |
235 | "-cpu core2duo", "xlevel", 0x80000008); | |
236 | add_cpuid_test("x86/cpuid/phenom/xlevel", | |
237 | "-cpu phenom", "xlevel", 0x8000001A); | |
0c3d7c00 EH |
238 | add_cpuid_test("x86/cpuid/athlon/xlevel", |
239 | "-cpu athlon", "xlevel", 0x80000008); | |
6efef58e EH |
240 | |
241 | /* If level is not large enough, it should increase automatically: */ | |
c39c0edf EH |
242 | /* CPUID[6].EAX: */ |
243 | add_cpuid_test("x86/cpuid/auto-level/phenom/arat", | |
244 | "-cpu 486,+arat", "level", 6); | |
6efef58e EH |
245 | /* CPUID[EAX=7,ECX=0].EBX: */ |
246 | add_cpuid_test("x86/cpuid/auto-level/phenom/fsgsbase", | |
247 | "-cpu phenom,+fsgsbase", "level", 7); | |
c39c0edf EH |
248 | /* CPUID[EAX=7,ECX=0].ECX: */ |
249 | add_cpuid_test("x86/cpuid/auto-level/phenom/avx512vbmi", | |
250 | "-cpu phenom,+avx512vbmi", "level", 7); | |
251 | /* CPUID[EAX=0xd,ECX=1].EAX: */ | |
252 | add_cpuid_test("x86/cpuid/auto-level/phenom/xsaveopt", | |
253 | "-cpu phenom,+xsaveopt", "level", 0xd); | |
254 | /* CPUID[8000_0001].EDX: */ | |
255 | add_cpuid_test("x86/cpuid/auto-xlevel/486/3dnow", | |
256 | "-cpu 486,+3dnow", "xlevel", 0x80000001); | |
257 | /* CPUID[8000_0001].ECX: */ | |
258 | add_cpuid_test("x86/cpuid/auto-xlevel/486/sse4a", | |
259 | "-cpu 486,+sse4a", "xlevel", 0x80000001); | |
260 | /* CPUID[8000_0007].EDX: */ | |
261 | add_cpuid_test("x86/cpuid/auto-xlevel/486/invtsc", | |
262 | "-cpu 486,+invtsc", "xlevel", 0x80000007); | |
263 | /* CPUID[8000_000A].EDX: */ | |
264 | add_cpuid_test("x86/cpuid/auto-xlevel/486/npt", | |
265 | "-cpu 486,+npt", "xlevel", 0x8000000A); | |
266 | /* CPUID[C000_0001].EDX: */ | |
267 | add_cpuid_test("x86/cpuid/auto-xlevel2/phenom/xstore", | |
268 | "-cpu phenom,+xstore", "xlevel2", 0xC0000001); | |
0c3d7c00 EH |
269 | /* SVM needs CPUID[0x8000000A] */ |
270 | add_cpuid_test("x86/cpuid/auto-xlevel/athlon/svm", | |
271 | "-cpu athlon,+svm", "xlevel", 0x8000000A); | |
c39c0edf | 272 | |
6efef58e EH |
273 | |
274 | /* If level is already large enough, it shouldn't change: */ | |
275 | add_cpuid_test("x86/cpuid/auto-level/SandyBridge/multiple", | |
276 | "-cpu SandyBridge,+arat,+fsgsbase,+avx512vbmi", | |
277 | "level", 0xd); | |
c39c0edf EH |
278 | /* If level is explicitly set, it shouldn't change: */ |
279 | add_cpuid_test("x86/cpuid/auto-level/486/fixed/0xF", | |
280 | "-cpu 486,level=0xF,+arat,+fsgsbase,+avx512vbmi,+xsaveopt", | |
281 | "level", 0xF); | |
282 | add_cpuid_test("x86/cpuid/auto-level/486/fixed/2", | |
283 | "-cpu 486,level=2,+arat,+fsgsbase,+avx512vbmi,+xsaveopt", | |
284 | "level", 2); | |
285 | add_cpuid_test("x86/cpuid/auto-level/486/fixed/0", | |
286 | "-cpu 486,level=0,+arat,+fsgsbase,+avx512vbmi,+xsaveopt", | |
287 | "level", 0); | |
6efef58e EH |
288 | |
289 | /* if xlevel is already large enough, it shouldn't change: */ | |
290 | add_cpuid_test("x86/cpuid/auto-xlevel/phenom/3dnow", | |
0c3d7c00 | 291 | "-cpu phenom,+3dnow,+sse4a,+invtsc,+npt,+svm", |
6efef58e | 292 | "xlevel", 0x8000001A); |
c39c0edf EH |
293 | /* If xlevel is explicitly set, it shouldn't change: */ |
294 | add_cpuid_test("x86/cpuid/auto-xlevel/486/fixed/80000002", | |
0c3d7c00 | 295 | "-cpu 486,xlevel=0x80000002,+3dnow,+sse4a,+invtsc,+npt,+svm", |
c39c0edf EH |
296 | "xlevel", 0x80000002); |
297 | add_cpuid_test("x86/cpuid/auto-xlevel/486/fixed/8000001A", | |
0c3d7c00 | 298 | "-cpu 486,xlevel=0x8000001A,+3dnow,+sse4a,+invtsc,+npt,+svm", |
c39c0edf EH |
299 | "xlevel", 0x8000001A); |
300 | add_cpuid_test("x86/cpuid/auto-xlevel/phenom/fixed/0", | |
0c3d7c00 | 301 | "-cpu 486,xlevel=0,+3dnow,+sse4a,+invtsc,+npt,+svm", |
c39c0edf | 302 | "xlevel", 0); |
6efef58e EH |
303 | |
304 | /* if xlevel2 is already large enough, it shouldn't change: */ | |
305 | add_cpuid_test("x86/cpuid/auto-xlevel2/486/fixed", | |
306 | "-cpu 486,xlevel2=0xC0000002,+xstore", | |
307 | "xlevel2", 0xC0000002); | |
308 | ||
df3e9af8 EH |
309 | /* Check compatibility of old machine-types that didn't |
310 | * auto-increase level/xlevel/xlevel2: */ | |
311 | ||
312 | add_cpuid_test("x86/cpuid/auto-level/pc-2.7", | |
313 | "-machine pc-i440fx-2.7 -cpu 486,+arat,+avx512vbmi,+xsaveopt", | |
314 | "level", 1); | |
315 | add_cpuid_test("x86/cpuid/auto-xlevel/pc-2.7", | |
0c3d7c00 | 316 | "-machine pc-i440fx-2.7 -cpu 486,+3dnow,+sse4a,+invtsc,+npt,+svm", |
df3e9af8 EH |
317 | "xlevel", 0); |
318 | add_cpuid_test("x86/cpuid/auto-xlevel2/pc-2.7", | |
319 | "-machine pc-i440fx-2.7 -cpu 486,+xstore", | |
320 | "xlevel2", 0); | |
1f435716 EH |
321 | /* |
322 | * QEMU 1.4.0 had auto-level enabled for CPUID[7], already, | |
323 | * and the compat code that sets default level shouldn't | |
324 | * disable the auto-level=7 code: | |
325 | */ | |
326 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-1.4/off", | |
327 | "-machine pc-i440fx-1.4 -cpu Nehalem", | |
328 | "level", 2); | |
329 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-1.5/on", | |
330 | "-machine pc-i440fx-1.4 -cpu Nehalem,+smap", | |
331 | "level", 7); | |
332 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.3/off", | |
333 | "-machine pc-i440fx-2.3 -cpu Penryn", | |
334 | "level", 4); | |
335 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.3/on", | |
336 | "-machine pc-i440fx-2.3 -cpu Penryn,+erms", | |
337 | "level", 7); | |
338 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.9/off", | |
339 | "-machine pc-i440fx-2.9 -cpu Conroe", | |
340 | "level", 10); | |
341 | add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.9/on", | |
342 | "-machine pc-i440fx-2.9 -cpu Conroe,+erms", | |
343 | "level", 10); | |
344 | ||
345 | /* | |
346 | * xlevel doesn't have any feature that triggers auto-level | |
347 | * code on old machine-types. Just check that the compat code | |
348 | * is working correctly: | |
349 | */ | |
350 | add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.3", | |
351 | "-machine pc-i440fx-2.3 -cpu SandyBridge", | |
352 | "xlevel", 0x8000000a); | |
353 | add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.4/npt-off", | |
354 | "-machine pc-i440fx-2.4 -cpu SandyBridge,", | |
355 | "xlevel", 0x80000008); | |
356 | add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.4/npt-on", | |
357 | "-machine pc-i440fx-2.4 -cpu SandyBridge,+npt", | |
358 | "xlevel", 0x80000008); | |
df3e9af8 | 359 | |
17e8f541 EH |
360 | /* Test feature parsing */ |
361 | add_feature_test("x86/cpuid/features/plus", | |
362 | "-cpu 486,+arat", | |
363 | 6, 0, "EAX", 2, true); | |
364 | add_feature_test("x86/cpuid/features/minus", | |
365 | "-cpu pentium,-mmx", | |
366 | 1, 0, "EDX", 23, false); | |
367 | add_feature_test("x86/cpuid/features/on", | |
368 | "-cpu 486,arat=on", | |
369 | 6, 0, "EAX", 2, true); | |
370 | add_feature_test("x86/cpuid/features/off", | |
371 | "-cpu pentium,mmx=off", | |
372 | 1, 0, "EDX", 23, false); | |
373 | add_feature_test("x86/cpuid/features/max-plus-invtsc", | |
374 | "-cpu max,+invtsc", | |
375 | 0x80000007, 0, "EDX", 8, true); | |
376 | add_feature_test("x86/cpuid/features/max-invtsc-on", | |
377 | "-cpu max,invtsc=on", | |
378 | 0x80000007, 0, "EDX", 8, true); | |
379 | add_feature_test("x86/cpuid/features/max-minus-mmx", | |
380 | "-cpu max,-mmx", | |
381 | 1, 0, "EDX", 23, false); | |
382 | add_feature_test("x86/cpuid/features/max-invtsc-on,mmx=off", | |
383 | "-cpu max,mmx=off", | |
384 | 1, 0, "EDX", 23, false); | |
385 | ||
6efef58e EH |
386 | return g_test_run(); |
387 | } |