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