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