Commit dcafd1ef authored by Eduardo Habkost's avatar Eduardo Habkost
Browse files

i386: Register versioned CPU models



Add support for registration of multiple versions of CPU models.

The existing CPU models will be registered with a "-v1" suffix.

The -noTSX, -IBRS, and -IBPB CPU model variants will become
versions of the original models in a separate patch, so
make sure we register no versions for them.

Signed-off-by: default avatarEduardo Habkost <ehabkost@redhat.com>
Message-Id: <20190628002844.24894-5-ehabkost@redhat.com>
Reviewed-by: default avatarDaniel P. Berrangé <berrange@redhat.com>
Signed-off-by: default avatarEduardo Habkost <ehabkost@redhat.com>
parent 164e779c
Loading
Loading
Loading
Loading
+2 −8
Original line number Diff line number Diff line
@@ -36,13 +36,7 @@
#define X86_CPU_GET_CLASS(obj) \
    OBJECT_GET_CLASS(X86CPUClass, (obj), TYPE_X86_CPU)

/**
 * X86CPUDefinition:
 *
 * CPU model definition data that was not converted to QOM per-subclass
 * property defaults yet.
 */
typedef struct X86CPUDefinition X86CPUDefinition;
typedef struct X86CPUModel X86CPUModel;

/**
 * X86CPUClass:
@@ -64,7 +58,7 @@ typedef struct X86CPUClass {
    /* CPU definition, automatically loaded by instance_init if not NULL.
     * Should be eventually replaced by subclass-specific property defaults.
     */
    X86CPUDefinition *cpu_def;
    X86CPUModel *model;

    bool host_cpuid_required;
    int ordering;
+201 −22
Original line number Diff line number Diff line
@@ -1433,7 +1433,17 @@ static char *x86_cpu_class_get_model_name(X86CPUClass *cc)
                     strlen(class_name) - strlen(X86_CPU_TYPE_SUFFIX));
}

struct X86CPUDefinition {
typedef struct PropValue {
    const char *prop, *value;
} PropValue;

typedef struct X86CPUVersionDefinition {
    X86CPUVersion version;
    PropValue *props;
} X86CPUVersionDefinition;

/* Base definition for a CPU model */
typedef struct X86CPUDefinition {
    const char *name;
    uint32_t level;
    uint32_t xlevel;
@@ -1445,8 +1455,41 @@ struct X86CPUDefinition {
    FeatureWordArray features;
    const char *model_id;
    CPUCaches *cache_info;
    /*
     * Definitions for alternative versions of CPU model.
     * List is terminated by item with version == 0.
     * If NULL, version 1 will be registered automatically.
     */
    const X86CPUVersionDefinition *versions;
} X86CPUDefinition;

/* Reference to a specific CPU model version */
struct X86CPUModel {
    /* Base CPU definition */
    X86CPUDefinition *cpudef;
    /* CPU model version */
    X86CPUVersion version;
};

/* Get full model name for CPU version */
static char *x86_cpu_versioned_model_name(X86CPUDefinition *cpudef,
                                          X86CPUVersion version)
{
    assert(version > 0);
    return g_strdup_printf("%s-v%d", cpudef->name, (int)version);
}

static const X86CPUVersionDefinition *x86_cpu_def_get_versions(X86CPUDefinition *def)
{
    /* When X86CPUDefinition::versions is NULL, we register only v1 */
    static const X86CPUVersionDefinition default_version_list[] = {
        { 1 },
        { /* end of list */ }
    };

    return def->versions ?: default_version_list;
}

static CPUCaches epyc_cache_info = {
    .l1d_cache = &(CPUCacheInfo) {
        .type = DATA_CACHE,
@@ -1833,6 +1876,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_EXT3_LAHF_LM,
        .xlevel = 0x80000008,
        .model_id = "Intel Core i7 9xx (Nehalem Core i7, IBRS update)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "Westmere",
@@ -1887,6 +1935,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_6_EAX_ARAT,
        .xlevel = 0x80000008,
        .model_id = "Westmere E56xx/L56xx/X56xx (IBRS update)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "SandyBridge",
@@ -1951,6 +2004,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_6_EAX_ARAT,
        .xlevel = 0x80000008,
        .model_id = "Intel Xeon E312xx (Sandy Bridge, IBRS update)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "IvyBridge",
@@ -2021,6 +2079,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_6_EAX_ARAT,
        .xlevel = 0x80000008,
        .model_id = "Intel Xeon E3-12xx v2 (Ivy Bridge, IBRS)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "Haswell-noTSX",
@@ -2057,6 +2120,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_6_EAX_ARAT,
        .xlevel = 0x80000008,
        .model_id = "Intel Core Processor (Haswell, no TSX)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "Haswell-noTSX-IBRS",
@@ -2095,6 +2163,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_6_EAX_ARAT,
        .xlevel = 0x80000008,
        .model_id = "Intel Core Processor (Haswell, no TSX, IBRS)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "Haswell",
@@ -2171,6 +2244,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_6_EAX_ARAT,
        .xlevel = 0x80000008,
        .model_id = "Intel Core Processor (Haswell, IBRS)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "Broadwell-noTSX",
@@ -2209,6 +2287,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_6_EAX_ARAT,
        .xlevel = 0x80000008,
        .model_id = "Intel Core Processor (Broadwell, no TSX)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "Broadwell-noTSX-IBRS",
@@ -2249,6 +2332,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_6_EAX_ARAT,
        .xlevel = 0x80000008,
        .model_id = "Intel Core Processor (Broadwell, no TSX, IBRS)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "Broadwell",
@@ -2327,6 +2415,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_6_EAX_ARAT,
        .xlevel = 0x80000008,
        .model_id = "Intel Core Processor (Broadwell, IBRS)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "Skylake-Client",
@@ -2419,6 +2512,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_6_EAX_ARAT,
        .xlevel = 0x80000008,
        .model_id = "Intel Core Processor (Skylake, IBRS)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "Skylake-Server",
@@ -2521,6 +2619,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
            CPUID_6_EAX_ARAT,
        .xlevel = 0x80000008,
        .model_id = "Intel Xeon Processor (Skylake, IBRS)",
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "Cascadelake-Server",
@@ -3029,6 +3132,11 @@ static X86CPUDefinition builtin_x86_defs[] = {
        .xlevel = 0x8000001E,
        .model_id = "AMD EPYC Processor (with IBPB)",
        .cache_info = &epyc_cache_info,
        /*
         * This CPU model will eventually be replaced by an alias,
         * don't register any versions for it.
         */
        .versions = (X86CPUVersionDefinition[]) { { /* end of list */ } },
    },
    {
        .name = "Dhyana",
@@ -3082,10 +3190,6 @@ static X86CPUDefinition builtin_x86_defs[] = {
    },
};

typedef struct PropValue {
    const char *prop, *value;
} PropValue;

/* KVM-specific features that are automatically added/removed
 * from all CPU models when KVM is enabled.
 */
@@ -3111,6 +3215,28 @@ static PropValue tcg_default_props[] = {
};


static X86CPUVersion x86_cpu_model_last_version(const X86CPUModel *model)
{
    int v = 0;
    const X86CPUVersionDefinition *vdef =
        x86_cpu_def_get_versions(model->cpudef);
    while (vdef->version) {
        v = vdef->version;
        vdef++;
    }
    return v;
}

/* Return the actual version being used for a specific CPU model */
static X86CPUVersion x86_cpu_model_resolve_version(const X86CPUModel *model)
{
    X86CPUVersion v = model->version;
    if (v == CPU_VERSION_LATEST) {
        return x86_cpu_model_last_version(model);
    }
    return v;
}

void x86_cpu_change_kvm_default(const char *prop, const char *value)
{
    PropValue *pv;
@@ -3188,8 +3314,6 @@ static void max_x86_cpu_class_init(ObjectClass *oc, void *data)
    dc->props = max_x86_cpu_properties;
}

static void x86_cpu_load_def(X86CPU *cpu, X86CPUDefinition *def, Error **errp);

static void max_x86_cpu_initfn(Object *obj)
{
    X86CPU *cpu = X86_CPU(obj);
@@ -3980,10 +4104,40 @@ static void x86_cpu_apply_props(X86CPU *cpu, PropValue *props)
    }
}

/* Apply properties for the CPU model version specified in model */
static void x86_cpu_apply_version_props(X86CPU *cpu, X86CPUModel *model)
{
    const X86CPUVersionDefinition *vdef;
    X86CPUVersion version = x86_cpu_model_resolve_version(model);

    if (version == CPU_VERSION_LEGACY) {
        return;
    }

    for (vdef = x86_cpu_def_get_versions(model->cpudef); vdef->version; vdef++) {
        PropValue *p;

        for (p = vdef->props; p && p->prop; p++) {
            object_property_parse(OBJECT(cpu), p->value, p->prop,
                                  &error_abort);
        }

        if (vdef->version == version) {
            break;
        }
    }

    /*
     * If we reached the end of the list, version number was invalid
     */
    assert(vdef->version == version);
}

/* Load data from X86CPUDefinition into a X86CPU object
 */
static void x86_cpu_load_def(X86CPU *cpu, X86CPUDefinition *def, Error **errp)
static void x86_cpu_load_model(X86CPU *cpu, X86CPUModel *model, Error **errp)
{
    X86CPUDefinition *def = model->cpudef;
    CPUX86State *env = &cpu->env;
    const char *vendor;
    char host_vendor[CPUID_VENDOR_SZ + 1];
@@ -4040,11 +4194,12 @@ static void x86_cpu_load_def(X86CPU *cpu, X86CPUDefinition *def, Error **errp)

    object_property_set_str(OBJECT(cpu), vendor, "vendor", errp);

    x86_cpu_apply_version_props(cpu, model);
}

#ifndef CONFIG_USER_ONLY
/* Return a QDict containing keys for all properties that can be included
 * in static expansion of CPU models. All properties set by x86_cpu_load_def()
 * in static expansion of CPU models. All properties set by x86_cpu_load_model()
 * must be included in the dictionary.
 */
static QDict *x86_cpu_static_props(void)
@@ -4258,23 +4413,33 @@ static gchar *x86_gdb_arch_name(CPUState *cs)

static void x86_cpu_cpudef_class_init(ObjectClass *oc, void *data)
{
    X86CPUDefinition *cpudef = data;
    X86CPUModel *model = data;
    X86CPUClass *xcc = X86_CPU_CLASS(oc);

    xcc->cpu_def = cpudef;
    xcc->model = model;
    xcc->migration_safe = true;
}

static void x86_register_cpudef_type(X86CPUDefinition *def)
static void x86_register_cpu_model_type(const char *name, X86CPUModel *model)
{
    char *typename = x86_cpu_type_name(def->name);
    char *typename = x86_cpu_type_name(name);
    TypeInfo ti = {
        .name = typename,
        .parent = TYPE_X86_CPU,
        .class_init = x86_cpu_cpudef_class_init,
        .class_data = def,
        .class_data = model,
    };

    type_register(&ti);
    g_free(typename);
}

static void x86_register_cpudef_types(X86CPUDefinition *def)
{
    X86CPUModel *m;
    const X86CPUVersionDefinition *vdef;
    char *name;

    /* AMD aliases are handled at runtime based on CPUID vendor, so
     * they shouldn't be set on the CPU model table.
     */
@@ -4282,9 +4447,23 @@ static void x86_register_cpudef_type(X86CPUDefinition *def)
    /* catch mistakes instead of silently truncating model_id when too long */
    assert(def->model_id && strlen(def->model_id) <= 48);

    /* Unversioned model: */
    m = g_new0(X86CPUModel, 1);
    m->cpudef = def;
    m->version = CPU_VERSION_LEGACY;
    x86_register_cpu_model_type(def->name, m);

    /* Versioned models: */

    for (vdef = x86_cpu_def_get_versions(def); vdef->version; vdef++) {
        X86CPUModel *m = g_new0(X86CPUModel, 1);
        m->cpudef = def;
        m->version = vdef->version;
        name = x86_cpu_versioned_model_name(def, vdef->version);
        x86_register_cpu_model_type(name, m);
        g_free(name);
    }

    type_register(&ti);
    g_free(typename);
}

#if !defined(CONFIG_USER_ONLY)
@@ -5110,7 +5289,7 @@ static void x86_cpu_enable_xsave_components(X86CPU *cpu)
 * involved in setting up CPUID data are:
 *
 * 1) Loading CPU model definition (X86CPUDefinition). This is
 *    implemented by x86_cpu_load_def() and should be completely
 *    implemented by x86_cpu_load_model() and should be completely
 *    transparent, as it is done automatically by instance_init.
 *    No code should need to look at X86CPUDefinition structs
 *    outside instance_init.
@@ -5432,7 +5611,7 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp)

    /* Cache information initialization */
    if (!cpu->legacy_cache) {
        if (!xcc->cpu_def || !xcc->cpu_def->cache_info) {
        if (!xcc->model || !xcc->model->cpudef->cache_info) {
            char *name = x86_cpu_class_get_model_name(xcc);
            error_setg(errp,
                       "CPU model '%s' doesn't support legacy-cache=off", name);
@@ -5440,7 +5619,7 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp)
            return;
        }
        env->cache_info_cpuid2 = env->cache_info_cpuid4 = env->cache_info_amd =
            *xcc->cpu_def->cache_info;
            *xcc->model->cpudef->cache_info;
    } else {
        /* Build legacy cache information */
        env->cache_info_cpuid2.l1d_cache = &legacy_l1d_cache;
@@ -5799,8 +5978,8 @@ static void x86_cpu_initfn(Object *obj)
    object_property_add_alias(obj, "sse4_1", obj, "sse4.1", &error_abort);
    object_property_add_alias(obj, "sse4_2", obj, "sse4.2", &error_abort);

    if (xcc->cpu_def) {
        x86_cpu_load_def(cpu, xcc->cpu_def, &error_abort);
    if (xcc->model) {
        x86_cpu_load_model(cpu, xcc->model, &error_abort);
    }
}

@@ -6139,7 +6318,7 @@ static void x86_cpu_register_types(void)

    type_register_static(&x86_cpu_type_info);
    for (i = 0; i < ARRAY_SIZE(builtin_x86_defs); i++) {
        x86_register_cpudef_type(&builtin_x86_defs[i]);
        x86_register_cpudef_types(&builtin_x86_defs[i]);
    }
    type_register_static(&max_x86_cpu_type_info);
    type_register_static(&x86_base_cpu_type_info);
+10 −0
Original line number Diff line number Diff line
@@ -1929,6 +1929,16 @@ void apic_handle_tpr_access_report(DeviceState *d, target_ulong ip,
 */
void x86_cpu_change_kvm_default(const char *prop, const char *value);

/* Special values for X86CPUVersion: */

/* Resolve to latest CPU version */
#define CPU_VERSION_LATEST -1

/* Don't resolve to any versioned CPU models, like old QEMU versions */
#define CPU_VERSION_LEGACY  0

typedef int X86CPUVersion;

/* Return name of 32-bit register, from a R_* constant */
const char *get_register_name_32(unsigned int reg);

+105 −0
Original line number Diff line number Diff line
#!/usr/bin/env python
#
# Basic validation of x86 versioned CPU models and CPU model aliases
#
#  Copyright (c) 2019 Red Hat Inc
#
# Author:
#  Eduardo Habkost <ehabkost@redhat.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, see <http://www.gnu.org/licenses/>.
#


import avocado_qemu
import re

class X86CPUModelAliases(avocado_qemu.Test):
    """
    Validation of PC CPU model versions and CPU model aliases

    :avocado: tags=arch:x86_64
    """
    def validate_aliases(self, cpus):
        for c in cpus.values():
            if 'alias-of' in c:
                # all aliases must point to a valid CPU model name:
                self.assertIn(c['alias-of'], cpus,
                              '%s.alias-of (%s) is not a valid CPU model name' % (c['name'], c['alias-of']))
                # aliases must not point to aliases
                self.assertNotIn('alias-of', cpus[c['alias-of']],
                                 '%s.alias-of (%s) points to another alias' % (c['name'], c['alias-of']))

                # aliases must not be static
                self.assertFalse(c['static'])

    def validate_variant_aliases(self, cpus):
        # -noTSX, -IBRS and -IBPB variants of CPU models are special:
        # they shouldn't have their own versions:
        self.assertNotIn("Haswell-noTSX-v1", cpus,
                         "Haswell-noTSX shouldn't be versioned")
        self.assertNotIn("Broadwell-noTSX-v1", cpus,
                         "Broadwell-noTSX shouldn't be versioned")
        self.assertNotIn("Nehalem-IBRS-v1", cpus,
                         "Nehalem-IBRS shouldn't be versioned")
        self.assertNotIn("Westmere-IBRS-v1", cpus,
                         "Westmere-IBRS shouldn't be versioned")
        self.assertNotIn("SandyBridge-IBRS-v1", cpus,
                         "SandyBridge-IBRS shouldn't be versioned")
        self.assertNotIn("IvyBridge-IBRS-v1", cpus,
                         "IvyBridge-IBRS shouldn't be versioned")
        self.assertNotIn("Haswell-noTSX-IBRS-v1", cpus,
                         "Haswell-noTSX-IBRS shouldn't be versioned")
        self.assertNotIn("Haswell-IBRS-v1", cpus,
                         "Haswell-IBRS shouldn't be versioned")
        self.assertNotIn("Broadwell-noTSX-IBRS-v1", cpus,
                         "Broadwell-noTSX-IBRS shouldn't be versioned")
        self.assertNotIn("Broadwell-IBRS-v1", cpus,
                         "Broadwell-IBRS shouldn't be versioned")
        self.assertNotIn("Skylake-Client-IBRS-v1", cpus,
                         "Skylake-Client-IBRS shouldn't be versioned")
        self.assertNotIn("Skylake-Server-IBRS-v1", cpus,
                         "Skylake-Server-IBRS shouldn't be versioned")
        self.assertNotIn("EPYC-IBPB-v1", cpus,
                         "EPYC-IBPB shouldn't be versioned")

    def test_4_0_alias_compatibility(self):
        """Check if pc-*-4.0 unversioned CPU model won't be reported as aliases"""
        # pc-*-4.0 won't expose non-versioned CPU models as aliases
        # We do this to help management software to keep compatibility
        # with older QEMU versions that didn't have the versioned CPU model
        self.vm.add_args('-S')
        self.vm.set_machine('pc-i440fx-4.0')
        self.vm.launch()
        cpus = dict((m['name'], m) for m in self.vm.command('query-cpu-definitions'))

        self.assertFalse(cpus['Cascadelake-Server']['static'],
                         'unversioned Cascadelake-Server CPU model must not be static')
        self.assertNotIn('alias-of', cpus['Cascadelake-Server'],
                         'Cascadelake-Server must not be an alias')
        self.assertNotIn('alias-of', cpus['Cascadelake-Server-v1'],
                         'Cascadelake-Server-v1 must not be an alias')

        self.assertFalse(cpus['qemu64']['static'],
                         'unversioned qemu64 CPU model must not be static')
        self.assertNotIn('alias-of', cpus['qemu64'],
                         'qemu64 must not be an alias')
        self.assertNotIn('alias-of', cpus['qemu64-v1'],
                         'qemu64-v1 must not be an alias')

        self.validate_variant_aliases(cpus)

        # On pc-*-4.0, no CPU model should be reported as an alias:
        for name,c in cpus.items():
            self.assertNotIn('alias-of', c, "%s shouldn't be an alias" % (name))