/* * cpu_ppc64.c: CPU driver for 64-bit PowerPC CPUs * * Copyright (C) 2013 Red Hat, Inc. * Copyright (C) IBM Corporation, 2010 * * 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.1 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 * . * * Authors: * Anton Blanchard * Prerna Saxena * Li Zhang */ #include #include #include "virlog.h" #include "viralloc.h" #include "cpu.h" #include "virstring.h" #include "cpu_map.h" #include "virbuffer.h" #define VIR_FROM_THIS VIR_FROM_CPU VIR_LOG_INIT("cpu.cpu_ppc64"); static const virArch archs[] = { VIR_ARCH_PPC64, VIR_ARCH_PPC64LE }; struct ppc64_vendor { char *name; }; struct ppc64_model { char *name; const struct ppc64_vendor *vendor; virCPUppc64Data data; }; struct ppc64_map { size_t nvendors; struct ppc64_vendor **vendors; size_t nmodels; struct ppc64_model **models; }; /* Convert a legacy CPU definition by transforming * model names to generation names: * POWER7_v2.1 => POWER7 * POWER7_v2.3 => POWER7 * POWER7+_v2.1 => POWER7 * POWER8_v1.0 => POWER8 */ static int virCPUppc64ConvertLegacy(virCPUDefPtr cpu) { if (cpu->model && (STREQ(cpu->model, "POWER7_v2.1") || STREQ(cpu->model, "POWER7_v2.3") || STREQ(cpu->model, "POWER7+_v2.1") || STREQ(cpu->model, "POWER8_v1.0"))) { cpu->model[strlen("POWERx")] = 0; } return 0; } /* Some hosts can run guests in compatibility mode, but not all * host CPUs support this and not all combinations are valid. * This function performs the necessary checks */ static virCPUCompareResult ppc64CheckCompatibilityMode(const char *host_model, const char *compat_mode) { int host; int compat; char *tmp; virCPUCompareResult ret = VIR_CPU_COMPARE_ERROR; if (!compat_mode) return VIR_CPU_COMPARE_IDENTICAL; /* Valid host CPUs: POWER6, POWER7, POWER8 */ if (!STRPREFIX(host_model, "POWER") || !(tmp = (char *) host_model + strlen("POWER")) || virStrToLong_i(tmp, NULL, 10, &host) < 0 || host < 6 || host > 8) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Host CPU does not support compatibility modes")); goto out; } /* Valid compatibility modes: power6, power7, power8 */ if (!STRPREFIX(compat_mode, "power") || !(tmp = (char *) compat_mode + strlen("power")) || virStrToLong_i(tmp, NULL, 10, &compat) < 0 || compat < 6 || compat > 8) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown compatibility mode %s"), compat_mode); goto out; } /* Version check */ if (compat > host) ret = VIR_CPU_COMPARE_INCOMPATIBLE; else ret = VIR_CPU_COMPARE_IDENTICAL; out: return ret; } static void ppc64DataClear(virCPUppc64Data *data) { if (!data) return; VIR_FREE(data->pvr); } static int ppc64DataCopy(virCPUppc64Data *dst, const virCPUppc64Data *src) { size_t i; if (VIR_ALLOC_N(dst->pvr, src->len) < 0) return -1; dst->len = src->len; for (i = 0; i < src->len; i++) { dst->pvr[i].value = src->pvr[i].value; dst->pvr[i].mask = src->pvr[i].mask; } return 0; } static void ppc64VendorFree(struct ppc64_vendor *vendor) { if (!vendor) return; VIR_FREE(vendor->name); VIR_FREE(vendor); } static struct ppc64_vendor * ppc64VendorFind(const struct ppc64_map *map, const char *name) { size_t i; for (i = 0; i < map->nvendors; i++) { if (STREQ(map->vendors[i]->name, name)) return map->vendors[i]; } return NULL; } static void ppc64ModelFree(struct ppc64_model *model) { if (!model) return; ppc64DataClear(&model->data); VIR_FREE(model->name); VIR_FREE(model); } static struct ppc64_model * ppc64ModelCopy(const struct ppc64_model *model) { struct ppc64_model *copy; if (VIR_ALLOC(copy) < 0) goto error; if (VIR_STRDUP(copy->name, model->name) < 0) goto error; if (ppc64DataCopy(©->data, &model->data) < 0) goto error; copy->vendor = model->vendor; return copy; error: ppc64ModelFree(copy); return NULL; } static struct ppc64_model * ppc64ModelFind(const struct ppc64_map *map, const char *name) { size_t i; for (i = 0; i < map->nmodels; i++) { if (STREQ(map->models[i]->name, name)) return map->models[i]; } return NULL; } static struct ppc64_model * ppc64ModelFindPVR(const struct ppc64_map *map, uint32_t pvr) { size_t i; size_t j; for (i = 0; i < map->nmodels; i++) { struct ppc64_model *model = map->models[i]; for (j = 0; j < model->data.len; j++) { if ((pvr & model->data.pvr[j].mask) == model->data.pvr[j].value) return model; } } return NULL; } static struct ppc64_model * ppc64ModelFromCPU(const virCPUDef *cpu, const struct ppc64_map *map) { struct ppc64_model *model; if (!(model = ppc64ModelFind(map, cpu->model))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown CPU model %s"), cpu->model); return NULL; } return ppc64ModelCopy(model); } static void ppc64MapFree(struct ppc64_map *map) { size_t i; if (!map) return; for (i = 0; i < map->nmodels; i++) ppc64ModelFree(map->models[i]); VIR_FREE(map->models); for (i = 0; i < map->nvendors; i++) ppc64VendorFree(map->vendors[i]); VIR_FREE(map->vendors); VIR_FREE(map); } static struct ppc64_vendor * ppc64VendorParse(xmlXPathContextPtr ctxt, struct ppc64_map *map) { struct ppc64_vendor *vendor; if (VIR_ALLOC(vendor) < 0) return NULL; vendor->name = virXPathString("string(@name)", ctxt); if (!vendor->name) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing CPU vendor name")); goto error; } if (ppc64VendorFind(map, vendor->name)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("CPU vendor %s already defined"), vendor->name); goto error; } return vendor; error: ppc64VendorFree(vendor); return NULL; } static int ppc64VendorsLoad(struct ppc64_map *map, xmlXPathContextPtr ctxt, xmlNodePtr *nodes, int n) { struct ppc64_vendor *vendor; size_t i; if (VIR_ALLOC_N(map->vendors, n) < 0) return -1; for (i = 0; i < n; i++) { ctxt->node = nodes[i]; if (!(vendor = ppc64VendorParse(ctxt, map))) return -1; map->vendors[map->nvendors++] = vendor; } return 0; } static struct ppc64_model * ppc64ModelParse(xmlXPathContextPtr ctxt, struct ppc64_map *map) { struct ppc64_model *model; xmlNodePtr *nodes = NULL; char *vendor = NULL; unsigned long pvr; size_t i; int n; if (VIR_ALLOC(model) < 0) goto error; model->name = virXPathString("string(@name)", ctxt); if (!model->name) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Missing CPU model name")); goto error; } if (ppc64ModelFind(map, model->name)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("CPU model %s already defined"), model->name); goto error; } if (virXPathBoolean("boolean(./vendor)", ctxt)) { vendor = virXPathString("string(./vendor/@name)", ctxt); if (!vendor) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid vendor element in CPU model %s"), model->name); goto error; } if (!(model->vendor = ppc64VendorFind(map, vendor))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown vendor %s referenced by CPU model %s"), vendor, model->name); goto error; } } if ((n = virXPathNodeSet("./pvr", ctxt, &nodes)) <= 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Missing PVR information for CPU model %s"), model->name); goto error; } if (VIR_ALLOC_N(model->data.pvr, n) < 0) goto error; model->data.len = n; for (i = 0; i < n; i++) { ctxt->node = nodes[i]; if (virXPathULongHex("string(./@value)", ctxt, &pvr) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Missing or invalid PVR value in CPU model %s"), model->name); goto error; } model->data.pvr[i].value = pvr; if (virXPathULongHex("string(./@mask)", ctxt, &pvr) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Missing or invalid PVR mask in CPU model %s"), model->name); goto error; } model->data.pvr[i].mask = pvr; } cleanup: VIR_FREE(vendor); VIR_FREE(nodes); return model; error: ppc64ModelFree(model); model = NULL; goto cleanup; } static int ppc64ModelsLoad(struct ppc64_map *map, xmlXPathContextPtr ctxt, xmlNodePtr *nodes, int n) { struct ppc64_model *model; size_t i; if (VIR_ALLOC_N(map->models, n) < 0) return -1; for (i = 0; i < n; i++) { ctxt->node = nodes[i]; if (!(model = ppc64ModelParse(ctxt, map))) return -1; map->models[map->nmodels++] = model; } return 0; } static int ppc64MapLoadCallback(cpuMapElement element, xmlXPathContextPtr ctxt, xmlNodePtr *nodes, int n, void *data) { struct ppc64_map *map = data; switch (element) { case CPU_MAP_ELEMENT_VENDOR: return ppc64VendorsLoad(map, ctxt, nodes, n); case CPU_MAP_ELEMENT_MODEL: return ppc64ModelsLoad(map, ctxt, nodes, n); case CPU_MAP_ELEMENT_FEATURE: case CPU_MAP_ELEMENT_LAST: break; } return 0; } static struct ppc64_map * ppc64LoadMap(void) { struct ppc64_map *map; if (VIR_ALLOC(map) < 0) goto error; if (cpuMapLoad("ppc64", ppc64MapLoadCallback, map) < 0) goto error; return map; error: ppc64MapFree(map); return NULL; } static virCPUDataPtr ppc64MakeCPUData(virArch arch, virCPUppc64Data *data) { virCPUDataPtr cpuData; if (VIR_ALLOC(cpuData) < 0) return NULL; cpuData->arch = arch; if (ppc64DataCopy(&cpuData->data.ppc64, data) < 0) VIR_FREE(cpuData); return cpuData; } static virCPUCompareResult ppc64Compute(virCPUDefPtr host, const virCPUDef *other, virCPUDataPtr *guestData, char **message) { struct ppc64_map *map = NULL; struct ppc64_model *host_model = NULL; struct ppc64_model *guest_model = NULL; virCPUDefPtr cpu = NULL; virCPUCompareResult ret = VIR_CPU_COMPARE_ERROR; virArch arch; size_t i; /* Ensure existing configurations are handled correctly */ if (!(cpu = virCPUDefCopy(other)) || virCPUppc64ConvertLegacy(cpu) < 0) goto cleanup; if (cpu->arch != VIR_ARCH_NONE) { bool found = false; for (i = 0; i < ARRAY_CARDINALITY(archs); i++) { if (archs[i] == cpu->arch) { found = true; break; } } if (!found) { VIR_DEBUG("CPU arch %s does not match host arch", virArchToString(cpu->arch)); if (message && virAsprintf(message, _("CPU arch %s does not match host arch"), virArchToString(cpu->arch)) < 0) goto cleanup; ret = VIR_CPU_COMPARE_INCOMPATIBLE; goto cleanup; } arch = cpu->arch; } else { arch = host->arch; } if (cpu->vendor && (!host->vendor || STRNEQ(cpu->vendor, host->vendor))) { VIR_DEBUG("host CPU vendor does not match required CPU vendor %s", cpu->vendor); if (message && virAsprintf(message, _("host CPU vendor does not match required " "CPU vendor %s"), cpu->vendor) < 0) goto cleanup; ret = VIR_CPU_COMPARE_INCOMPATIBLE; goto cleanup; } if (!(map = ppc64LoadMap())) goto cleanup; /* Host CPU information */ if (!(host_model = ppc64ModelFromCPU(host, map))) goto cleanup; if (cpu->type == VIR_CPU_TYPE_GUEST) { /* Guest CPU information */ virCPUCompareResult tmp; switch (cpu->mode) { case VIR_CPU_MODE_HOST_MODEL: /* host-model only: * we need to take compatibility modes into account */ tmp = ppc64CheckCompatibilityMode(host->model, cpu->model); if (tmp != VIR_CPU_COMPARE_IDENTICAL) { ret = tmp; goto cleanup; } ATTRIBUTE_FALLTHROUGH; case VIR_CPU_MODE_HOST_PASSTHROUGH: /* host-model and host-passthrough: * the guest CPU is the same as the host */ guest_model = ppc64ModelCopy(host_model); break; case VIR_CPU_MODE_CUSTOM: /* custom: * look up guest CPU information */ guest_model = ppc64ModelFromCPU(cpu, map); break; } } else { /* Other host CPU information */ guest_model = ppc64ModelFromCPU(cpu, map); } if (!guest_model) goto cleanup; if (STRNEQ(guest_model->name, host_model->name)) { VIR_DEBUG("host CPU model does not match required CPU model %s", guest_model->name); if (message && virAsprintf(message, _("host CPU model does not match required " "CPU model %s"), guest_model->name) < 0) goto cleanup; ret = VIR_CPU_COMPARE_INCOMPATIBLE; goto cleanup; } if (guestData) if (!(*guestData = ppc64MakeCPUData(arch, &guest_model->data))) goto cleanup; ret = VIR_CPU_COMPARE_IDENTICAL; cleanup: virCPUDefFree(cpu); ppc64MapFree(map); ppc64ModelFree(host_model); ppc64ModelFree(guest_model); return ret; } static virCPUCompareResult virCPUppc64Compare(virCPUDefPtr host, virCPUDefPtr cpu, bool failIncompatible) { virCPUCompareResult ret; char *message = NULL; if (!host || !host->model) { if (failIncompatible) { virReportError(VIR_ERR_CPU_INCOMPATIBLE, "%s", _("unknown host CPU")); } else { VIR_WARN("unknown host CPU"); ret = VIR_CPU_COMPARE_INCOMPATIBLE; } return -1; } ret = ppc64Compute(host, cpu, NULL, &message); if (failIncompatible && ret == VIR_CPU_COMPARE_INCOMPATIBLE) { ret = VIR_CPU_COMPARE_ERROR; if (message) { virReportError(VIR_ERR_CPU_INCOMPATIBLE, "%s", message); } else { virReportError(VIR_ERR_CPU_INCOMPATIBLE, NULL); } } VIR_FREE(message); return ret; } static int ppc64DriverDecode(virCPUDefPtr cpu, const virCPUData *data, const char **models, unsigned int nmodels, const char *preferred ATTRIBUTE_UNUSED) { int ret = -1; struct ppc64_map *map; const struct ppc64_model *model; if (!data || !(map = ppc64LoadMap())) return -1; if (!(model = ppc64ModelFindPVR(map, data->data.ppc64.pvr[0].value))) { virReportError(VIR_ERR_OPERATION_FAILED, _("Cannot find CPU model with PVR 0x%08x"), data->data.ppc64.pvr[0].value); goto cleanup; } if (!virCPUModelIsAllowed(model->name, models, nmodels)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("CPU model %s is not supported by hypervisor"), model->name); goto cleanup; } if (VIR_STRDUP(cpu->model, model->name) < 0 || (model->vendor && VIR_STRDUP(cpu->vendor, model->vendor->name) < 0)) { goto cleanup; } ret = 0; cleanup: ppc64MapFree(map); return ret; } static void virCPUppc64DataFree(virCPUDataPtr data) { if (!data) return; ppc64DataClear(&data->data.ppc64); VIR_FREE(data); } static int virCPUppc64GetHost(virCPUDefPtr cpu, const char **models, unsigned int nmodels) { virCPUDataPtr cpuData = NULL; virCPUppc64Data *data; int ret = -1; if (!(cpuData = virCPUDataNew(archs[0]))) goto cleanup; data = &cpuData->data.ppc64; if (VIR_ALLOC_N(data->pvr, 1) < 0) goto cleanup; data->len = 1; #if defined(__powerpc__) || defined(__powerpc64__) asm("mfpvr %0" : "=r" (data->pvr[0].value)); #endif data->pvr[0].mask = 0xfffffffful; ret = ppc64DriverDecode(cpu, cpuData, models, nmodels, NULL); cleanup: virCPUppc64DataFree(cpuData); return ret; } static int virCPUppc64Update(virCPUDefPtr guest, const virCPUDef *host ATTRIBUTE_UNUSED) { /* * - host-passthrough doesn't even get here * - host-model is used for host CPU running in a compatibility mode and * it needs to remain unchanged * - custom doesn't support any optional features, there's nothing to * update */ if (guest->mode == VIR_CPU_MODE_CUSTOM) guest->match = VIR_CPU_MATCH_EXACT; return 0; } static virCPUDefPtr ppc64DriverBaseline(virCPUDefPtr *cpus, unsigned int ncpus, const char **models ATTRIBUTE_UNUSED, unsigned int nmodels ATTRIBUTE_UNUSED, bool migratable ATTRIBUTE_UNUSED) { struct ppc64_map *map; const struct ppc64_model *model; const struct ppc64_vendor *vendor = NULL; virCPUDefPtr cpu = NULL; size_t i; if (!(map = ppc64LoadMap())) goto error; if (!(model = ppc64ModelFind(map, cpus[0]->model))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown CPU model %s"), cpus[0]->model); goto error; } for (i = 0; i < ncpus; i++) { const struct ppc64_vendor *vnd; /* Hosts running old (<= 1.2.18) versions of libvirt will report * strings like 'power7+' or 'power8e' instead of proper CPU model * names in the capabilities XML; moreover, they lack information * about some proper CPU models like 'POWER8'. * This implies two things: * 1) baseline among such hosts never worked * 2) while a few models, eg. 'POWER8_v1.0', could work on both * old and new versions of libvirt, the information we have * here is not enough to pick such a model * Hence we just compare models by name to decide whether or not * two hosts are compatible */ if (STRNEQ(cpus[i]->model, model->name)) { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("CPUs are incompatible")); goto error; } if (!cpus[i]->vendor) continue; if (!(vnd = ppc64VendorFind(map, cpus[i]->vendor))) { virReportError(VIR_ERR_OPERATION_FAILED, _("Unknown CPU vendor %s"), cpus[i]->vendor); goto error; } if (model->vendor) { if (model->vendor != vnd) { virReportError(VIR_ERR_OPERATION_FAILED, _("CPU vendor %s of model %s differs from " "vendor %s"), model->vendor->name, model->name, vnd->name); goto error; } } else if (vendor) { if (vendor != vnd) { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("CPU vendors do not match")); goto error; } } else { vendor = vnd; } } if (VIR_ALLOC(cpu) < 0 || VIR_STRDUP(cpu->model, model->name) < 0) goto error; if (vendor && VIR_STRDUP(cpu->vendor, vendor->name) < 0) goto error; cpu->type = VIR_CPU_TYPE_GUEST; cpu->match = VIR_CPU_MATCH_EXACT; cpu->fallback = VIR_CPU_FALLBACK_FORBID; cleanup: ppc64MapFree(map); return cpu; error: virCPUDefFree(cpu); cpu = NULL; goto cleanup; } static int virCPUppc64DriverGetModels(char ***models) { struct ppc64_map *map; size_t i; int ret = -1; if (!(map = ppc64LoadMap())) goto error; if (models) { if (VIR_ALLOC_N(*models, map->nmodels + 1) < 0) goto error; for (i = 0; i < map->nmodels; i++) { if (VIR_STRDUP((*models)[i], map->models[i]->name) < 0) goto error; } } ret = map->nmodels; cleanup: ppc64MapFree(map); return ret; error: if (models) { virStringListFree(*models); *models = NULL; } goto cleanup; } struct cpuArchDriver cpuDriverPPC64 = { .name = "ppc64", .arch = archs, .narch = ARRAY_CARDINALITY(archs), .compare = virCPUppc64Compare, .decode = ppc64DriverDecode, .encode = NULL, .dataFree = virCPUppc64DataFree, .getHost = virCPUppc64GetHost, .baseline = ppc64DriverBaseline, .update = virCPUppc64Update, .getModels = virCPUppc64DriverGetModels, .convertLegacy = virCPUppc64ConvertLegacy, };