| 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 | } |