e08f6e0b9f
It is better to check if some older cpu models like 486, athlon, pentium, penryn, phenom, core2duo etc are available before running their corresponding tests. Some downstream distributions may no longer support these older cpu models. Signature of add_feature_test() has been modified to return void as FeatureTestArgs* was not used by the caller. One minor correction. Replaced 'phenom' with '486' in the test 'x86/cpuid/auto-level/phenom/arat' matching the cpu used. Signed-off-by: Ani Sinha <anisinha@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Message-ID: <20240610155303.7933-4-anisinha@redhat.com> Signed-off-by: Thomas Huth <thuth@redhat.com>
430 lines
15 KiB
C
430 lines
15 KiB
C
#include "qemu/osdep.h"
|
|
#include "qapi/qmp/qdict.h"
|
|
#include "qapi/qmp/qlist.h"
|
|
#include "qapi/qmp/qnum.h"
|
|
#include "qapi/qmp/qbool.h"
|
|
#include "libqtest-single.h"
|
|
|
|
static char *get_cpu0_qom_path(void)
|
|
{
|
|
QDict *resp;
|
|
QList *ret;
|
|
QDict *cpu0;
|
|
char *path;
|
|
|
|
resp = qmp("{'execute': 'query-cpus-fast', 'arguments': {}}");
|
|
g_assert(qdict_haskey(resp, "return"));
|
|
ret = qdict_get_qlist(resp, "return");
|
|
|
|
cpu0 = qobject_to(QDict, qlist_peek(ret));
|
|
path = g_strdup(qdict_get_str(cpu0, "qom-path"));
|
|
qobject_unref(resp);
|
|
return path;
|
|
}
|
|
|
|
static QObject *qom_get(const char *path, const char *prop)
|
|
{
|
|
QDict *resp = qmp("{ 'execute': 'qom-get',"
|
|
" 'arguments': { 'path': %s,"
|
|
" 'property': %s } }",
|
|
path, prop);
|
|
QObject *ret = qdict_get(resp, "return");
|
|
qobject_ref(ret);
|
|
qobject_unref(resp);
|
|
return ret;
|
|
}
|
|
|
|
static bool qom_get_bool(const char *path, const char *prop)
|
|
{
|
|
QBool *value = qobject_to(QBool, qom_get(path, prop));
|
|
bool b = qbool_get_bool(value);
|
|
|
|
qobject_unref(value);
|
|
return b;
|
|
}
|
|
|
|
typedef struct CpuidTestArgs {
|
|
const char *cmdline;
|
|
const char *property;
|
|
int64_t expected_value;
|
|
} CpuidTestArgs;
|
|
|
|
static void test_cpuid_prop(const void *data)
|
|
{
|
|
const CpuidTestArgs *args = data;
|
|
char *path;
|
|
QNum *value;
|
|
int64_t val;
|
|
|
|
qtest_start(args->cmdline);
|
|
path = get_cpu0_qom_path();
|
|
value = qobject_to(QNum, qom_get(path, args->property));
|
|
g_assert(qnum_get_try_int(value, &val));
|
|
g_assert_cmpint(val, ==, args->expected_value);
|
|
qtest_end();
|
|
|
|
qobject_unref(value);
|
|
g_free(path);
|
|
}
|
|
|
|
static void add_cpuid_test(const char *name, const char *cpu,
|
|
const char *cpufeat, const char *machine,
|
|
const char *property, int64_t expected_value)
|
|
{
|
|
CpuidTestArgs *args = g_new0(CpuidTestArgs, 1);
|
|
char *cmdline;
|
|
char *save;
|
|
|
|
if (!qtest_has_cpu_model(cpu)) {
|
|
return;
|
|
}
|
|
cmdline = g_strdup_printf("-cpu %s", cpu);
|
|
|
|
if (cpufeat) {
|
|
save = cmdline;
|
|
cmdline = g_strdup_printf("%s,%s", cmdline, cpufeat);
|
|
g_free(save);
|
|
}
|
|
if (machine) {
|
|
save = cmdline;
|
|
cmdline = g_strdup_printf("-machine %s %s", machine, cmdline);
|
|
g_free(save);
|
|
}
|
|
args->cmdline = cmdline;
|
|
args->property = property;
|
|
args->expected_value = expected_value;
|
|
qtest_add_data_func(name, args, test_cpuid_prop);
|
|
}
|
|
|
|
|
|
/* Parameters to a add_feature_test() test case */
|
|
typedef struct FeatureTestArgs {
|
|
/* cmdline to start QEMU */
|
|
const char *cmdline;
|
|
/*
|
|
* cpuid-input-eax and cpuid-input-ecx values to look for,
|
|
* in "feature-words" and "filtered-features" properties.
|
|
*/
|
|
uint32_t in_eax, in_ecx;
|
|
/* The register name to look for, in the X86CPUFeatureWordInfo array */
|
|
const char *reg;
|
|
/* The bit to check in X86CPUFeatureWordInfo.features */
|
|
int bitnr;
|
|
/* The expected value for the bit in (X86CPUFeatureWordInfo.features) */
|
|
bool expected_value;
|
|
} FeatureTestArgs;
|
|
|
|
/* Get the value for a feature word in a X86CPUFeatureWordInfo list */
|
|
static uint32_t get_feature_word(QList *features, uint32_t eax, uint32_t ecx,
|
|
const char *reg)
|
|
{
|
|
const QListEntry *e;
|
|
|
|
for (e = qlist_first(features); e; e = qlist_next(e)) {
|
|
QDict *w = qobject_to(QDict, qlist_entry_obj(e));
|
|
const char *rreg = qdict_get_str(w, "cpuid-register");
|
|
uint32_t reax = qdict_get_int(w, "cpuid-input-eax");
|
|
bool has_ecx = qdict_haskey(w, "cpuid-input-ecx");
|
|
uint32_t recx = 0;
|
|
int64_t val;
|
|
|
|
if (has_ecx) {
|
|
recx = qdict_get_int(w, "cpuid-input-ecx");
|
|
}
|
|
if (eax == reax && (!has_ecx || ecx == recx) && !strcmp(rreg, reg)) {
|
|
g_assert(qnum_get_try_int(qobject_to(QNum,
|
|
qdict_get(w, "features")),
|
|
&val));
|
|
return val;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void test_feature_flag(const void *data)
|
|
{
|
|
const FeatureTestArgs *args = data;
|
|
char *path;
|
|
QList *present, *filtered;
|
|
uint32_t value;
|
|
|
|
qtest_start(args->cmdline);
|
|
path = get_cpu0_qom_path();
|
|
present = qobject_to(QList, qom_get(path, "feature-words"));
|
|
filtered = qobject_to(QList, qom_get(path, "filtered-features"));
|
|
value = get_feature_word(present, args->in_eax, args->in_ecx, args->reg);
|
|
value |= get_feature_word(filtered, args->in_eax, args->in_ecx, args->reg);
|
|
qtest_end();
|
|
|
|
g_assert(!!(value & (1U << args->bitnr)) == args->expected_value);
|
|
|
|
qobject_unref(present);
|
|
qobject_unref(filtered);
|
|
g_free(path);
|
|
}
|
|
|
|
/*
|
|
* Add test case to ensure that a given feature flag is set in
|
|
* either "feature-words" or "filtered-features", when running QEMU
|
|
* using cmdline
|
|
*/
|
|
static void add_feature_test(const char *name, const char *cpu,
|
|
const char *cpufeat, uint32_t eax,
|
|
uint32_t ecx, const char *reg,
|
|
int bitnr, bool expected_value)
|
|
{
|
|
FeatureTestArgs *args = g_new0(FeatureTestArgs, 1);
|
|
char *cmdline;
|
|
|
|
if (!qtest_has_cpu_model(cpu)) {
|
|
return;
|
|
}
|
|
|
|
if (cpufeat) {
|
|
cmdline = g_strdup_printf("-cpu %s,%s", cpu, cpufeat);
|
|
} else {
|
|
cmdline = g_strdup_printf("-cpu %s", cpu);
|
|
}
|
|
|
|
args->cmdline = cmdline;
|
|
args->in_eax = eax;
|
|
args->in_ecx = ecx;
|
|
args->reg = reg;
|
|
args->bitnr = bitnr;
|
|
args->expected_value = expected_value;
|
|
qtest_add_data_func(name, args, test_feature_flag);
|
|
return;
|
|
}
|
|
|
|
static void test_plus_minus_subprocess(void)
|
|
{
|
|
char *path;
|
|
|
|
if (!qtest_has_cpu_model("pentium")) {
|
|
return;
|
|
}
|
|
|
|
/* Rules:
|
|
* 1)"-foo" overrides "+foo"
|
|
* 2) "[+-]foo" overrides "foo=..."
|
|
* 3) Old feature names with underscores (e.g. "sse4_2")
|
|
* should keep working
|
|
*
|
|
* Note: rules 1 and 2 are planned to be removed soon, and
|
|
* should generate a warning.
|
|
*/
|
|
qtest_start("-cpu pentium,-fpu,+fpu,-mce,mce=on,+cx8,cx8=off,+sse4_1,sse4_2=on");
|
|
path = get_cpu0_qom_path();
|
|
|
|
g_assert_false(qom_get_bool(path, "fpu"));
|
|
g_assert_false(qom_get_bool(path, "mce"));
|
|
g_assert_true(qom_get_bool(path, "cx8"));
|
|
|
|
/* Test both the original and the alias feature names: */
|
|
g_assert_true(qom_get_bool(path, "sse4-1"));
|
|
g_assert_true(qom_get_bool(path, "sse4.1"));
|
|
|
|
g_assert_true(qom_get_bool(path, "sse4-2"));
|
|
g_assert_true(qom_get_bool(path, "sse4.2"));
|
|
|
|
qtest_end();
|
|
g_free(path);
|
|
}
|
|
|
|
static void test_plus_minus(void)
|
|
{
|
|
if (!qtest_has_cpu_model("pentium")) {
|
|
return;
|
|
}
|
|
|
|
g_test_trap_subprocess("/x86/cpuid/parsing-plus-minus/subprocess", 0, 0);
|
|
g_test_trap_assert_passed();
|
|
g_test_trap_assert_stderr("*Ambiguous CPU model string. "
|
|
"Don't mix both \"-mce\" and \"mce=on\"*");
|
|
g_test_trap_assert_stderr("*Ambiguous CPU model string. "
|
|
"Don't mix both \"+cx8\" and \"cx8=off\"*");
|
|
g_test_trap_assert_stdout("");
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
g_test_init(&argc, &argv, NULL);
|
|
|
|
g_test_add_func("/x86/cpuid/parsing-plus-minus/subprocess",
|
|
test_plus_minus_subprocess);
|
|
g_test_add_func("/x86/cpuid/parsing-plus-minus", test_plus_minus);
|
|
|
|
/* Original level values for CPU models: */
|
|
add_cpuid_test("x86/cpuid/phenom/level",
|
|
"phenom", NULL, NULL, "level", 5);
|
|
add_cpuid_test("x86/cpuid/Conroe/level",
|
|
"Conroe", NULL, NULL, "level", 10);
|
|
add_cpuid_test("x86/cpuid/SandyBridge/level",
|
|
"SandyBridge", NULL, NULL, "level", 0xd);
|
|
add_cpuid_test("x86/cpuid/486/xlevel",
|
|
"486", NULL, NULL, "xlevel", 0);
|
|
add_cpuid_test("x86/cpuid/core2duo/xlevel",
|
|
"core2duo", NULL, NULL, "xlevel", 0x80000008);
|
|
add_cpuid_test("x86/cpuid/phenom/xlevel",
|
|
"phenom", NULL, NULL, "xlevel", 0x8000001A);
|
|
add_cpuid_test("x86/cpuid/athlon/xlevel",
|
|
"athlon", NULL, NULL, "xlevel", 0x80000008);
|
|
|
|
/* If level is not large enough, it should increase automatically: */
|
|
/* CPUID[6].EAX: */
|
|
add_cpuid_test("x86/cpuid/auto-level/486/arat",
|
|
"486", "arat=on", NULL, "level", 6);
|
|
/* CPUID[EAX=7,ECX=0].EBX: */
|
|
add_cpuid_test("x86/cpuid/auto-level/phenom/fsgsbase",
|
|
"phenom", "fsgsbase=on", NULL, "level", 7);
|
|
/* CPUID[EAX=7,ECX=0].ECX: */
|
|
add_cpuid_test("x86/cpuid/auto-level/phenom/avx512vbmi",
|
|
"phenom", "avx512vbmi=on", NULL, "level", 7);
|
|
/* CPUID[EAX=0xd,ECX=1].EAX: */
|
|
add_cpuid_test("x86/cpuid/auto-level/phenom/xsaveopt",
|
|
"phenom", "xsaveopt=on", NULL, "level", 0xd);
|
|
/* CPUID[8000_0001].EDX: */
|
|
add_cpuid_test("x86/cpuid/auto-xlevel/486/3dnow",
|
|
"486", "3dnow=on", NULL, "xlevel", 0x80000001);
|
|
/* CPUID[8000_0001].ECX: */
|
|
add_cpuid_test("x86/cpuid/auto-xlevel/486/sse4a",
|
|
"486", "sse4a=on", NULL, "xlevel", 0x80000001);
|
|
/* CPUID[8000_0007].EDX: */
|
|
add_cpuid_test("x86/cpuid/auto-xlevel/486/invtsc",
|
|
"486", "invtsc=on", NULL, "xlevel", 0x80000007);
|
|
/* CPUID[8000_000A].EDX: */
|
|
add_cpuid_test("x86/cpuid/auto-xlevel/486/npt",
|
|
"486", "svm=on,npt=on", NULL, "xlevel", 0x8000000A);
|
|
/* CPUID[C000_0001].EDX: */
|
|
add_cpuid_test("x86/cpuid/auto-xlevel2/phenom/xstore",
|
|
"phenom", "xstore=on", NULL, "xlevel2", 0xC0000001);
|
|
/* SVM needs CPUID[0x8000000A] */
|
|
add_cpuid_test("x86/cpuid/auto-xlevel/athlon/svm",
|
|
"athlon", "svm=on", NULL, "xlevel", 0x8000000A);
|
|
|
|
|
|
/* If level is already large enough, it shouldn't change: */
|
|
add_cpuid_test("x86/cpuid/auto-level/SandyBridge/multiple",
|
|
"SandyBridge", "arat=on,fsgsbase=on,avx512vbmi=on",
|
|
NULL, "level", 0xd);
|
|
/* If level is explicitly set, it shouldn't change: */
|
|
add_cpuid_test("x86/cpuid/auto-level/486/fixed/0xF",
|
|
"486",
|
|
"level=0xF,arat=on,fsgsbase=on,avx512vbmi=on,xsaveopt=on",
|
|
NULL, "level", 0xF);
|
|
add_cpuid_test("x86/cpuid/auto-level/486/fixed/2",
|
|
"486",
|
|
"level=2,arat=on,fsgsbase=on,avx512vbmi=on,xsaveopt=on",
|
|
NULL, "level", 2);
|
|
add_cpuid_test("x86/cpuid/auto-level/486/fixed/0",
|
|
"486",
|
|
"level=0,arat=on,fsgsbase=on,avx512vbmi=on,xsaveopt=on",
|
|
NULL, "level", 0);
|
|
|
|
/* if xlevel is already large enough, it shouldn't change: */
|
|
add_cpuid_test("x86/cpuid/auto-xlevel/phenom/3dnow",
|
|
"phenom", "3dnow=on,sse4a=on,invtsc=on,npt=on,svm=on",
|
|
NULL, "xlevel", 0x8000001A);
|
|
/* If xlevel is explicitly set, it shouldn't change: */
|
|
add_cpuid_test("x86/cpuid/auto-xlevel/486/fixed/80000002",
|
|
"486",
|
|
"xlevel=0x80000002,3dnow=on,sse4a=on,invtsc=on,npt=on,svm=on",
|
|
NULL, "xlevel", 0x80000002);
|
|
add_cpuid_test("x86/cpuid/auto-xlevel/486/fixed/8000001A",
|
|
"486",
|
|
"xlevel=0x8000001A,3dnow=on,sse4a=on,invtsc=on,npt=on,svm=on",
|
|
NULL, "xlevel", 0x8000001A);
|
|
add_cpuid_test("x86/cpuid/auto-xlevel/phenom/fixed/0",
|
|
"486",
|
|
"xlevel=0,3dnow=on,sse4a=on,invtsc=on,npt=on,svm=on",
|
|
NULL, "xlevel", 0);
|
|
|
|
/* if xlevel2 is already large enough, it shouldn't change: */
|
|
add_cpuid_test("x86/cpuid/auto-xlevel2/486/fixed",
|
|
"486", "xlevel2=0xC0000002,xstore=on",
|
|
NULL, "xlevel2", 0xC0000002);
|
|
|
|
/* Check compatibility of old machine-types that didn't
|
|
* auto-increase level/xlevel/xlevel2: */
|
|
if (qtest_has_machine("pc-i440fx-2.7")) {
|
|
add_cpuid_test("x86/cpuid/auto-level/pc-2.7",
|
|
"486", "arat=on,avx512vbmi=on,xsaveopt=on",
|
|
"pc-i440fx-2.7", "level", 1);
|
|
add_cpuid_test("x86/cpuid/auto-xlevel/pc-2.7",
|
|
"486", "3dnow=on,sse4a=on,invtsc=on,npt=on,svm=on",
|
|
"pc-i440fx-2.7", "xlevel", 0);
|
|
add_cpuid_test("x86/cpuid/auto-xlevel2/pc-2.7",
|
|
"486", "xstore=on", "pc-i440fx-2.7",
|
|
"xlevel2", 0);
|
|
}
|
|
/*
|
|
* QEMU 2.3.0 had auto-level enabled for CPUID[7], already,
|
|
* and the compat code that sets default level shouldn't
|
|
* disable the auto-level=7 code:
|
|
*/
|
|
if (qtest_has_machine("pc-i440fx-2.3")) {
|
|
add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.3/off",
|
|
"Penryn", NULL, "pc-i440fx-2.3",
|
|
"level", 4);
|
|
add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.3/on",
|
|
"Penryn", "erms=on", "pc-i440fx-2.3",
|
|
"level", 7);
|
|
}
|
|
if (qtest_has_machine("pc-i440fx-2.9")) {
|
|
add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.9/off",
|
|
"Conroe", NULL, "pc-i440fx-2.9",
|
|
"level", 10);
|
|
add_cpuid_test("x86/cpuid/auto-level7/pc-i440fx-2.9/on",
|
|
"Conroe", "erms=on", "pc-i440fx-2.9",
|
|
"level", 10);
|
|
}
|
|
|
|
/*
|
|
* xlevel doesn't have any feature that triggers auto-level
|
|
* code on old machine-types. Just check that the compat code
|
|
* is working correctly:
|
|
*/
|
|
if (qtest_has_machine("pc-i440fx-2.3")) {
|
|
add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.3",
|
|
"SandyBridge", NULL, "pc-i440fx-2.3",
|
|
"xlevel", 0x8000000a);
|
|
}
|
|
if (qtest_has_machine("pc-i440fx-2.4")) {
|
|
add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.4/npt-off",
|
|
"SandyBridge", NULL, "pc-i440fx-2.4",
|
|
"xlevel", 0x80000008);
|
|
add_cpuid_test("x86/cpuid/xlevel-compat/pc-i440fx-2.4/npt-on",
|
|
"SandyBridge", "svm=on,npt=on", "pc-i440fx-2.4",
|
|
"xlevel", 0x80000008);
|
|
}
|
|
|
|
/* Test feature parsing */
|
|
add_feature_test("x86/cpuid/features/plus",
|
|
"486", "+arat",
|
|
6, 0, "EAX", 2, true);
|
|
add_feature_test("x86/cpuid/features/minus",
|
|
"pentium", "-mmx",
|
|
1, 0, "EDX", 23, false);
|
|
add_feature_test("x86/cpuid/features/on",
|
|
"486", "arat=on",
|
|
6, 0, "EAX", 2, true);
|
|
add_feature_test("x86/cpuid/features/off",
|
|
"pentium", "mmx=off",
|
|
1, 0, "EDX", 23, false);
|
|
|
|
add_feature_test("x86/cpuid/features/max-plus-invtsc",
|
|
"max" , "+invtsc",
|
|
0x80000007, 0, "EDX", 8, true);
|
|
add_feature_test("x86/cpuid/features/max-invtsc-on",
|
|
"max", "invtsc=on",
|
|
0x80000007, 0, "EDX", 8, true);
|
|
add_feature_test("x86/cpuid/features/max-minus-mmx",
|
|
"max", "-mmx",
|
|
1, 0, "EDX", 23, false);
|
|
add_feature_test("x86/cpuid/features/max-invtsc-on,mmx=off",
|
|
"max", "mmx=off",
|
|
1, 0, "EDX", 23, false);
|
|
|
|
return g_test_run();
|
|
}
|