[RFC PATCH v2 10/15] cpu-model/s390: Add cpu class initialization routines

Michael Mueller
2015-02-17T14:29:04Z

This patch provides routines to dynamically update the previously defined
S390 cpu classes in the current host context. The main function performing
this process is s390_setup_cpu_classes(). It takes the current host context
as parameter to setup the classes accordingly. It basically performs the
following sub-tasks:

- Update of cpu classes with accelerator specific host and QEMU properties
- Mark adequate cpu class as default cpu class to be used for cpu model 'host'
- Invalidate cpu classes not supported by this hosting machine
- Define machine type aliases to latest GA number of a processor model
- Define aliases for common cpu model names
- Set cpu model alias 'host' to default cpu class

Forthermore the patch provides the following routines:

- cpu_desc_avail(), s390 specific stub indicating that list_cpus() can run
- s390_cpu_classes_initialized(), test if cpu classes have been initialized
- s390_test_facility(), facility bit probe function for QEMU land
- s390_probe_mode, test if running in probe mode

Signed-off-by: Michael Mueller <mimu@linux.vnet.ibm.com>
---
target-s390x/cpu-models.c | 464 ++++++++++++++++++++++++++++++++++++++++++++++
target-s390x/cpu-models.h | 27 +++
target-s390x/cpu.c | 17 +-
target-s390x/kvm.c | 4 +-
4 files changed, 510 insertions(+), 2 deletions(-)

diff --git a/target-s390x/cpu-models.c b/target-s390x/cpu-models.c
index 8d2c2e2..9f40998 100644
--- a/target-s390x/cpu-models.c
+++ b/target-s390x/cpu-models.c
@@ -12,6 +12,7 @@

#include "qemu-common.h"
#include "cpu-models.h"
+#include "qemu/error-report.h"

#define S390_FAC_NAME(n, _cpu_id) glue(glue(glue(FAC, n), _), _cpu_id)

@@ -90,8 +91,41 @@ S390_PROC_DEF("2827-ga1", CPU_S390_2827_GA1, "IBM zEnterprise EC12 GA1")
S390_PROC_DEF("2827-ga2", CPU_S390_2827_GA2, "IBM zEnterprise EC12 GA2")
S390_PROC_DEF("2828-ga1", CPU_S390_2828_GA1, "IBM zEnterprise BC12 GA1")

+/* some types for calls to g_list_foreach() with parameters */
+typedef struct ParmBoolShortShortAccel {
+ bool valid;
+ unsigned short type;
+ union {
+ unsigned short class;
+ unsigned short gen;
+ unsigned short ga;
+ };
+ AccelId accel;
+} ParmBoolShortShortAccel;
+
+typedef struct ParmAddrAddrAccel {
+ S390MachineProps *prop;
+ S390CPUClass *host_cc;
+ AccelId accel;
+} ParmAddrAddrAccel;
+
static GSList *s390_cpu_aliases;

+/* compare order of two cpu classes for ascending sort */
+gint s390_cpu_class_asc_order_compare(gconstpointer a, gconstpointer b)
+{
+ S390CPUClass *cc_a = S390_CPU_CLASS((ObjectClass *) a);
+ S390CPUClass *cc_b = S390_CPU_CLASS((ObjectClass *) b);
+
+ if (cc_a->mach->order < cc_b->mach->order) {
+ return -1;
+ }
+ if (cc_a->mach->order > cc_b->mach->order) {
+ return 1;
+ }
+ return 0;
+}
+
static gint s390_cpu_compare_class_name(gconstpointer a, gconstpointer b)
{
const char *aname = object_class_get_name((ObjectClass *) a);
@@ -167,3 +201,433 @@ int set_s390_cpu_alias(const char *name, const char *model)
return 0;
}

+/* return machine class for specific machine type */
+static void s390_machine_class_test_cpu_class(gpointer data, gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+ ParmBoolShortShortAccel *parm = user_data;
+
+ if (parm->valid || !cc->proc->type || parm->type != cc->proc->type) {
+ return;
+ }
+
+ parm->class = cc->mach->class;
+ parm->valid = true;
+}
+
+/* return machine class by machine type */
+static unsigned short machine_class(unsigned short type, void *user_data)
+{
+ GSList *list = object_class_get_list(TYPE_S390_CPU, false);
+ ParmBoolShortShortAccel parm_class, *parm = user_data;
+
+ if (parm->type != type) {
+ parm->class = 0;
+ }
+ if (!parm->class) {
+ parm_class.type = type;
+ parm_class.class = 0;
+ parm_class.valid = false;
+ g_slist_foreach(list, (GFunc) s390_machine_class_test_cpu_class,
+ &parm_class);
+ g_slist_free(list);
+ if (parm_class.valid) {
+ parm->class = parm_class.class;
+ }
+ }
+ parm->type = type;
+
+ return parm->class;
+}
+
+/* return CMOS generation for specific machine type */
+static void s390_machine_class_test_cpu_gen(gpointer data, gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+ ParmBoolShortShortAccel *parm = user_data;
+
+ if (parm->valid) {
+ return;
+ }
+
+ if (parm->type == cc->proc->type) {
+ parm->gen = cc->proc->gen;
+ parm->valid = true;
+ }
+}
+
+/* return CMOS generation by machine type */
+static uint16_t machine_gen(unsigned short type)
+{
+ GSList *list = object_class_get_list(TYPE_S390_CPU, false);
+ ParmBoolShortShortAccel parm;
+
+ parm.type = type;
+ parm.gen = 0;
+ parm.valid = false;
+ g_slist_foreach(list, (GFunc) s390_machine_class_test_cpu_gen, &parm);
+ g_slist_free(list);
+
+ return parm.gen;
+}
+
+/* mark cpu class, used in host cpu model case */
+static void s390_mark_host_cpu_class(gpointer data, gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+ ParmAddrAddrAccel *parm = user_data;
+ ParmBoolShortShortAccel parm_tc;
+
+ if (!cc->is_active[parm->accel]) {
+ return;
+ }
+
+ parm_tc.type = 0;
+ parm_tc.class = 0;
+ if (cc->mach->class != machine_class(cpuid_type(parm->prop->cpuid),
+ &parm_tc)) {
+ /* sort out machines that differ from host machine class */
+ return;
+ }
+ if (!parm->host_cc) {
+ /* use first matching machine type */
+ cc->is_host[parm->accel] = true;
+ parm->host_cc = cc;
+ return;
+ }
+ if (cc->proc->gen > machine_gen(cpuid_type(parm->prop->cpuid))) {
+ /* sort out CMOS generations later than hosts generation */
+ cc->is_active[parm->accel] = false;
+ return;
+ }
+ if (cc->mach->order > parm->host_cc->mach->order) {
+ /* select later machine as host */
+ parm->host_cc->is_host[parm->accel] = false;
+ cc->is_host[parm->accel] = true;
+ parm->host_cc = cc;
+ }
+}
+
+/* update a specific cpu model class with host retrieved configuration */
+static void s390_update_cpu_class(gpointer data, gpointer user_data)
+{
+ ObjectClass *oc = data;
+ ParmAddrAddrAccel *parm = user_data;
+ S390CPUClass *cc = S390_CPU_CLASS(oc);
+ unsigned int i;
+
+ if (!cc->proc->type) {
+ return;
+ }
+
+ /* Set processor identifier */
+ cc->proc->id = cpuid_id(parm->prop->cpuid);
+
+ /*
+ * Define model specific IBC value in current host context.
+ * IBC was introduced with CMOS version 10 i.e. type 2097.
+ * For older CPUs it is assumed to be 0x000. The BC system
+ * has always the same IBC version as the previous EC system.
+ * If the host supports IBC but not the requested type, it
+ * will be set to the oldest supported value.
+ */
+ if (has_ibc(parm->prop->ibc_range)) {
+ if (cc->proc->gen >= S390_CMOS_G10) {
+ cc->proc->ibc = ((cc->proc->gen - S390_CMOS_G10) << 4);
+ cc->proc->ibc += cc->mach->ga;
+ if (cc->mach->class == S390_BC) {
+ cc->proc->ibc++;
+ }
+ if (cc->proc->ibc < oldest_ibc(parm->prop->ibc_range)) {
+ cc->proc->ibc = oldest_ibc(parm->prop->ibc_range);
+ }
+ if (cc->proc->ibc > newest_ibc(parm->prop->ibc_range)) {
+ cc->proc->ibc = newest_ibc(parm->prop->ibc_range);
+ }
+ } else {
+ cc->proc->ibc = oldest_ibc(parm->prop->ibc_range);
+ }
+ }
+
+ /*
+ * Processor generation and GA level specific facility properties:
+ *
+ * - cc->fac_list (RFL):
+ * resulting facility list to be requested for guest cpus
+ * - cc->proc->fac_list (PFL):
+ * facility list defined per processor generation and GA level
+ *
+ * Machine specific facility properties reported by the host:
+ *
+ * - parm->prop->fac_list (MFL):
+ * host specifc facility list, might be reduced by some facilities
+ * in case the host is backed by z/VM and not a LPAR
+ * - parm->prop->fac_list_mask (MFM):
+ * host specific facility list mask containing facilities
+ *
+ * QEMU defined properties:
+ *
+ * - qemu_s390_fac_list_mask (QFM):
+ * locally defined facilities, they are added to the set of
+ * facilities requested for a guest vcpu. They are visible in
+ * the guest and require qemu side instruction handling
+ *
+ * The calculation for the vcpu specific facility list (RFL) from the
+ * above defined lists/masks works as follows:
+ *
+ * RFL = PFL & (QFM | MFM)
+ *
+ * Set resulting/desired facilities of given cpu class
+ */
+ for (i = 0; i < S390_FAC_LIST_SIZE_UINT64; i++) {
+ cc->fac_list[i] = cc->proc->fac_list[i] &
+ (qemu_s390_fac_list_mask[i] | parm->prop->fac_list_mask[i]);
+ }
+
+ /*
+ * Finally, mark the cpu class inactive if not all resulting/desired
+ * facilities are offered by the host.
+ * (RFL & MFL) != RFL
+ */
+ for (i = 0; i < S390_ARCH_FAC_LIST_SIZE_UINT64 &&
+ cc->is_active[parm->accel]; i++) {
+ if ((cc->fac_list[i] & parm->prop->fac_list[i]) != cc->fac_list[i]) {
+ cc->is_active[parm->accel] = false;
+ }
+ }
+}
+
+/* a cpu class that is newer then the current host */
+static void s390_set_not_supported_cpu_class(gpointer data, gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+ ParmAddrAddrAccel *parm = user_data;
+
+ if (!cc->is_active[parm->accel]) {
+ return;
+ }
+ if (cc->mach->order > parm->host_cc->mach->order) {
+ cc->is_active[parm->accel] = false;
+ }
+}
+
+/* set alias by type and ga */
+static int set_s390_cpu_alias_by_type_ga(unsigned short type, unsigned short ga)
+{
+ char name[8], model[16];
+
+ snprintf(name, sizeof(name), "%04x", type);
+ snprintf(model, sizeof(model), "%04x-ga%u", type, ga);
+
+ return set_s390_cpu_alias(name, model);
+}
+
+/* set alias if system has latest ga of a type */
+static void s390_set_ga_alias(gpointer data, gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+ ParmBoolShortShortAccel *parm = user_data;
+
+ if (!cc->is_active[parm->accel]) {
+ return;
+ }
+ if (!parm->type) {
+ parm->type = cc->proc->type;
+ }
+ if (cc->proc->type == parm->type) {
+ parm->ga = cc->mach->ga;
+ return;
+ }
+ set_s390_cpu_alias_by_type_ga(parm->type, parm->ga);
+ parm->type = cc->proc->type;
+ parm->ga = cc->mach->ga;
+}
+
+/* set host marked cpu class as alias to respective class */
+static void s390_set_host_alias_from_cpu_class(gpointer data,
+ gpointer user_data)
+{
+ S390CPUClass *cc = S390_CPU_CLASS((ObjectClass *) data);
+ ParmAddrAddrAccel *parm = user_data;
+
+ char model[16];
+
+ if (!cc->is_active[parm->accel] || !cc->is_host[parm->accel]) {
+ return;
+ }
+ snprintf(model, sizeof(model), "%04x-ga%u", cc->proc->type, cc->mach->ga);
+ set_s390_cpu_alias("host", model);
+}
+
+/**
+ * s390_setup_cpu_classes - initialize the cpu classes for a given accelerator
+ * type, mark the default cpu model and define aliases
+ * @accel: the accelerator id
+ * @prop: the addresd to a machine property structure initialized with
+ * machine related properties of the hosting machine
+ *
+ * Returns: 0 in case of success
+ * -EINVAL in case no valid default model was identified
+ */
+int s390_setup_cpu_classes(AccelId accel, S390MachineProps *prop)
+{
+ GSList *list;
+ ParmAddrAddrAccel parm;
+ ParmBoolShortShortAccel parm_alias;
+
+ list = object_class_get_list(TYPE_S390_CPU, false);
+ list = g_slist_sort(list, s390_cpu_class_asc_order_compare);
+
+ /* update cpu classes with accellerator properties */
+ parm.accel = accel;
+ parm.prop = prop;
+ g_slist_foreach(list, (GFunc) s390_update_cpu_class, (gpointer) &parm);
+
+ /* define cpu model "host" */
+ parm.host_cc = NULL;
+ g_slist_foreach(list, (GFunc) s390_mark_host_cpu_class, (gpointer) &parm);
+
+ if (!parm.host_cc) {
+ error_report("Failed to mark host cpu class");
+ return -EINVAL;
+ }
+
+ /* inactivate cpu classes not supported by this host */
+ g_slist_foreach(list, (GFunc) s390_set_not_supported_cpu_class, &parm);
+
+ /* set machine type aliases to latest ga of model (e.g. 2064 -> 2064-ga3) */
+ parm_alias.accel = accel;
+ parm_alias.type = 0;
+ parm_alias.ga = 0;
+ g_slist_foreach(list, (GFunc) s390_set_ga_alias, &parm_alias);
+ set_s390_cpu_alias_by_type_ga(parm_alias.type, parm_alias.ga);
+
+ /* set aliases for common model names to machine types */
+ set_s390_cpu_alias("z900", "2064");
+ set_s390_cpu_alias("z800", "2066");
+ set_s390_cpu_alias("z990", "2084");
+ set_s390_cpu_alias("z890", "2086");
+ set_s390_cpu_alias("z9-109", "2094-ga1");
+ set_s390_cpu_alias("z9", "2094");
+ set_s390_cpu_alias("z9-ec", "2094");
+ set_s390_cpu_alias("z9-bc", "2096");
+ set_s390_cpu_alias("z10", "2097");
+ set_s390_cpu_alias("z10-ec", "2097");
+ set_s390_cpu_alias("z10-bc", "2098");
+ set_s390_cpu_alias("z196", "2817");
+ set_s390_cpu_alias("z114", "2818");
+ set_s390_cpu_alias("zEC12", "2827");
+ set_s390_cpu_alias("zBC12", "2828");
+
+ /* set alias for cpu model "host" at end of list */
+ g_slist_foreach(list, (GFunc) s390_set_host_alias_from_cpu_class, &parm);
+
+ g_slist_free(list);
+ return 0;
+}
+
+/* list all supported cpu models and alias names */
+void s390_cpu_list_entry(gpointer data, gpointer user_data)
+{
+ ObjectClass *alias_oc, *oc = data;
+ CPUListState *s = user_data;
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ S390CPUClass *cc = S390_CPU_CLASS(oc);
+ const char *typename = object_class_get_name(oc);
+ S390CPUAlias *alias;
+ AccelId accel;
+ GSList *item;
+ char *name;
+
+ if (!kvm_enabled()) {
+ return;
+ }
+ accel = ACCEL_ID_KVM;
+ if (!cc->is_active[accel]) {
+ return;
+ }
+ name = g_strndup(typename, strlen(typename) - strlen("-" TYPE_S390_CPU));
+ (*s->cpu_fprintf)(s->file, "s390 %-10s %s\n", name, dc->desc);
+
+ for (item = s390_cpu_aliases; item != NULL; item = item->next) {
+ alias = (S390CPUAlias *) item->data;
+ alias_oc = s390_cpu_class_by_name(alias->model);
+ if (alias_oc != oc) {
+ continue;
+ }
+ (*s->cpu_fprintf)(s->file, "s390 %-10s (alias for %s)\n",
+ alias->name, name);
+ }
+
+ g_free(name);
+}
+
+/**
+ * s390_cpu_classes_initialized - indicates if the all cpu classes and
+ * their properties have been initialized
+ *
+ * Returns: true in case they are initialized
+ * false in case they are not iniialized
+ */
+bool s390_cpu_classes_initialized(void)
+{
+ if (kvm_enabled()) {
+ return kvm_s390_cpu_classes_initialized();
+ }
+ return false;
+}
+
+bool cpu_desc_avail(void)
+{
+ return s390_cpu_classes_initialized();
+}
+
+static inline uint64_t big_endian_bit(unsigned long nr)
+{
+ return 1ul << (BITS_PER_LONG - (nr % BITS_PER_LONG));
+};
+
+static inline int test_facility(unsigned long nr, uint64_t *fac_list)
+{
+ uint64_t *ptr;
+
+ if (nr >= S390_ARCH_FAC_LIST_SIZE_BYTE * BITS_PER_BYTE) {
+ return 0;
+ }
+ ptr = fac_list + (nr / BITS_PER_LONG);
+
+ return !!(*ptr & big_endian_bit(nr));
+}
+
+/**
+ * s390_test_facility - test if given facility bit is set facility list
+ * of given cpu class
+ * @class: address of cpu class to test
+ * @nr: bit number to test
+ *
+ * Returns: true in case it is set
+ * false in case it is not set
+ */
+bool s390_test_facility(S390CPUClass *cc, unsigned long nr)
+{
+ if (cc) {
+ return test_facility(nr, cc->fac_list) ? true : false;
+ }
+ return false;
+}
+
+/**
+ * s390_probe_mode - indicates that the current machine was initialized
+ * in probe mode
+ *
+ * Returns: true if in probe mode
+ * false if in standard mode
+ */
+bool s390_probe_mode(void)
+{
+ if (kvm_enabled() && kvm_s390_probe_mode()) {
+ return true;
+ }
+ return false;
+}
+
diff --git a/target-s390x/cpu-models.h b/target-s390x/cpu-models.h
index 76b3456..be94667 100644
--- a/target-s390x/cpu-models.h
+++ b/target-s390x/cpu-models.h
@@ -28,12 +28,32 @@
#define S390_DEF_ID 0xdecade
#define S390_DEF_TYPE 0x2064

+/* first s390 CMOS generation supporting IBC */
+#define S390_CMOS_G10 0xa
+
#define cpu_type(x) (((x) >> 0) & 0xffff)
#define cpu_order(x) (((x) >> 16) & 0xffff)
#define cpu_ga(x) (((x) >> 16) & 0xf)
#define cpu_class(x) (((x) >> 20) & 0x3)
#define cpu_generation(x) (((x) >> 24) & 0xff)

+#define cpuid_type(x) (((x) >> 16) & 0xffff)
+#define cpuid_id(x) (((x) >> 32) & 0xffffff)
+#define cpuid_ver(x) (((x) >> 56) & 0xff)
+
+#define type_cpuid(x) ((uint64_t)((x) & 0xffff) << 16)
+#define id_cpuid(x) ((uint64_t)((x) & 0xffffff) << 32)
+#define ver_cpuid(x) ((uint64_t)((x) & 0xff) << 56)
+
+#define oldest_ibc(x) (((uint32_t)(x) >> 16) & 0xfff)
+#define newest_ibc(x) ((uint32_t)(x) & 0xfff)
+#define has_ibc(x) (oldest_ibc(x) != 0)
+
+#define S390_DEF_CPUID \
+ (ver_cpuid(S390_DEF_VERSION) | \
+ id_cpuid(S390_DEF_ID) | \
+ type_cpuid(S390_DEF_TYPE))
+
ObjectClass *s390_cpu_class_by_name(const char *name);
int set_s390_cpu_alias(const char *name, const char *model);

@@ -84,6 +104,13 @@ static inline bool kvm_s390_probe_mode(void)
}
#endif

+int s390_setup_cpu_classes(AccelId accel, S390MachineProps *prop);
+gint s390_cpu_class_asc_order_compare(gconstpointer a, gconstpointer b);
+void s390_cpu_list_entry(gpointer data, gpointer user_data);
+bool s390_cpu_classes_initialized(void);
+bool s390_test_facility(S390CPUClass *cc, unsigned long nr);
+bool s390_probe_mode(void);
+
/*
* bits 0-7 : CMOS generation
* bits 8-9 : reserved
diff --git a/target-s390x/cpu.c b/target-s390x/cpu.c
index f30856e..d27832b 100644
--- a/target-s390x/cpu.c
+++ b/target-s390x/cpu.c
@@ -41,7 +41,22 @@
void s390_cpu_list(FILE *f, fprintf_function cpu_fprintf)
{
#ifdef CONFIG_KVM
- (*cpu_fprintf)(f, "s390 %16s\n", "host");
+ CPUListState s = {
+ .file = f,
+ .cpu_fprintf = cpu_fprintf,
+ };
+ GSList *list;
+
+ if (kvm_enabled() && s390_cpu_classes_initialized()) {
+ list = object_class_get_list(TYPE_S390_CPU, false);
+ list = g_slist_sort(list, s390_cpu_class_asc_order_compare);
+ g_slist_foreach(list, s390_cpu_list_entry, &s);
+ g_slist_free(list);
+ } else {
+#endif
+ (*cpu_fprintf)(f, "s390 host\n");
+#ifdef CONFIG_KVM
+ }
#endif
}

diff --git a/target-s390x/kvm.c b/target-s390x/kvm.c
index c518489..ce2392a 100644
--- a/target-s390x/kvm.c
+++ b/target-s390x/kvm.c
@@ -232,7 +232,9 @@ static void kvm_s390_setup_cpu_classes(KVMState *s)
S390MachineProps mach;

if (!kvm_s390_get_machine_props(s, &mach)) {
- cpu_classes_initialized = false;
+ if (!s390_setup_cpu_classes(ACCEL_ID_KVM, &mach)) {
+ cpu_classes_initialized = true;
+ }
}
}

--
1.8.3.1