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