/*
* cpu_powerpc.c: CPU driver for 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_powerpc");
static const virArch archs[] = { VIR_ARCH_PPC64, VIR_ARCH_PPC64LE };
struct ppc_vendor {
char *name;
struct ppc_vendor *next;
};
struct ppc_model {
char *name;
const struct ppc_vendor *vendor;
struct cpuPPCData data;
struct ppc_model *next;
};
struct ppc_map {
struct ppc_vendor *vendors;
struct ppc_model *models;
};
static void
ppcModelFree(struct ppc_model *model)
{
if (model == NULL)
return;
VIR_FREE(model->name);
VIR_FREE(model);
}
static struct ppc_model *
ppcModelFind(const struct ppc_map *map,
const char *name)
{
struct ppc_model *model;
model = map->models;
while (model != NULL) {
if (STREQ(model->name, name))
return model;
model = model->next;
}
return NULL;
}
static struct ppc_model *
ppcModelFindPVR(const struct ppc_map *map,
uint32_t pvr)
{
struct ppc_model *model;
model = map->models;
while (model != NULL) {
if (model->data.pvr == pvr)
return model;
model = model->next;
}
/* PowerPC Processor Version Register is interpreted as follows :
* Higher order 16 bits : Power ISA generation.
* Lower order 16 bits : CPU chip version number.
* If the exact CPU isnt found, return the nearest matching CPU generation
*/
if (pvr & 0x0000FFFFul)
return ppcModelFindPVR(map, (pvr & 0xFFFF0000ul));
return NULL;
}
static struct ppc_model *
ppcModelCopy(const struct ppc_model *model)
{
struct ppc_model *copy;
if (VIR_ALLOC(copy) < 0 ||
VIR_STRDUP(copy->name, model->name) < 0) {
ppcModelFree(copy);
return NULL;
}
copy->data.pvr = model->data.pvr;
copy->vendor = model->vendor;
return copy;
}
static struct ppc_vendor *
ppcVendorFind(const struct ppc_map *map,
const char *name)
{
struct ppc_vendor *vendor;
vendor = map->vendors;
while (vendor) {
if (STREQ(vendor->name, name))
return vendor;
vendor = vendor->next;
}
return NULL;
}
static void
ppcVendorFree(struct ppc_vendor *vendor)
{
if (!vendor)
return;
VIR_FREE(vendor->name);
VIR_FREE(vendor);
}
static struct ppc_model *
ppcModelFromCPU(const virCPUDef *cpu,
const struct ppc_map *map)
{
struct ppc_model *model = NULL;
if ((model = ppcModelFind(map, cpu->model)) == NULL) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown CPU model %s"), cpu->model);
goto error;
}
if ((model = ppcModelCopy(model)) == NULL)
goto error;
return model;
error:
ppcModelFree(model);
return NULL;
}
static int
ppcVendorLoad(xmlXPathContextPtr ctxt,
struct ppc_map *map)
{
struct ppc_vendor *vendor = NULL;
if (VIR_ALLOC(vendor) < 0)
return -1;
vendor->name = virXPathString("string(@name)", ctxt);
if (!vendor->name) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("Missing CPU vendor name"));
goto ignore;
}
if (ppcVendorFind(map, vendor->name)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("CPU vendor %s already defined"), vendor->name);
goto ignore;
}
if (!map->vendors) {
map->vendors = vendor;
} else {
vendor->next = map->vendors;
map->vendors = vendor;
}
cleanup:
return 0;
ignore:
ppcVendorFree(vendor);
goto cleanup;
}
static int
ppcModelLoad(xmlXPathContextPtr ctxt,
struct ppc_map *map)
{
struct ppc_model *model;
char *vendor = NULL;
unsigned long pvr;
if (VIR_ALLOC(model) < 0)
return -1;
model->name = virXPathString("string(@name)", ctxt);
if (!model->name) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("Missing CPU model name"));
goto ignore;
}
if (ppcModelFind(map, model->name)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("CPU model %s already defined"), model->name);
goto ignore;
}
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 ignore;
}
if (!(model->vendor = ppcVendorFind(map, vendor))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown vendor %s referenced by CPU model %s"),
vendor, model->name);
goto ignore;
}
}
if (!virXPathBoolean("boolean(./pvr)", ctxt) ||
virXPathULongHex("string(./pvr/@value)", ctxt, &pvr) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Missing or invalid PVR value in CPU model %s"),
model->name);
goto ignore;
}
model->data.pvr = pvr;
if (map->models == NULL) {
map->models = model;
} else {
model->next = map->models;
map->models = model;
}
cleanup:
VIR_FREE(vendor);
return 0;
ignore:
ppcModelFree(model);
goto cleanup;
}
static int
ppcMapLoadCallback(cpuMapElement element,
xmlXPathContextPtr ctxt,
void *data)
{
struct ppc_map *map = data;
switch (element) {
case CPU_MAP_ELEMENT_VENDOR:
return ppcVendorLoad(ctxt, map);
case CPU_MAP_ELEMENT_MODEL:
return ppcModelLoad(ctxt, map);
case CPU_MAP_ELEMENT_FEATURE:
case CPU_MAP_ELEMENT_LAST:
break;
}
return 0;
}
static void
ppcMapFree(struct ppc_map *map)
{
if (map == NULL)
return;
while (map->models != NULL) {
struct ppc_model *model = map->models;
map->models = model->next;
ppcModelFree(model);
}
while (map->vendors != NULL) {
struct ppc_vendor *vendor = map->vendors;
map->vendors = vendor->next;
ppcVendorFree(vendor);
}
VIR_FREE(map);
}
static struct ppc_map *
ppcLoadMap(void)
{
struct ppc_map *map;
if (VIR_ALLOC(map) < 0)
return NULL;
if (cpuMapLoad("ppc64", ppcMapLoadCallback, map) < 0)
goto error;
return map;
error:
ppcMapFree(map);
return NULL;
}
static virCPUDataPtr
ppcMakeCPUData(virArch arch, struct cpuPPCData *data)
{
virCPUDataPtr cpuData;
if (VIR_ALLOC(cpuData) < 0)
return NULL;
cpuData->arch = arch;
cpuData->data.ppc = *data;
data = NULL;
return cpuData;
}
static virCPUCompareResult
ppcCompute(virCPUDefPtr host,
const virCPUDef *cpu,
virCPUDataPtr *guestData,
char **message)
{
struct ppc_map *map = NULL;
struct ppc_model *host_model = NULL;
struct ppc_model *guest_model = NULL;
virCPUCompareResult ret = VIR_CPU_COMPARE_ERROR;
virArch arch;
size_t i;
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 = ppcLoadMap()) ||
!(host_model = ppcModelFromCPU(host, map)) ||
!(guest_model = ppcModelFromCPU(cpu, map)))
goto cleanup;
if (guestData != NULL) {
if (cpu->type == VIR_CPU_TYPE_GUEST &&
cpu->match == VIR_CPU_MATCH_STRICT &&
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 = ppcMakeCPUData(arch, &guest_model->data)))
goto cleanup;
}
ret = VIR_CPU_COMPARE_IDENTICAL;
cleanup:
ppcMapFree(map);
ppcModelFree(host_model);
ppcModelFree(guest_model);
return ret;
}
static virCPUCompareResult
ppcCompare(virCPUDefPtr host,
virCPUDefPtr cpu,
bool failIncompatible)
{
if ((cpu->arch == VIR_ARCH_NONE || host->arch == cpu->arch) &&
STREQ(host->model, cpu->model))
return VIR_CPU_COMPARE_IDENTICAL;
if (failIncompatible) {
virReportError(VIR_ERR_CPU_INCOMPATIBLE, NULL);
return VIR_CPU_COMPARE_ERROR;
} else {
return VIR_CPU_COMPARE_INCOMPATIBLE;
}
}
static int
ppcDecode(virCPUDefPtr cpu,
const virCPUData *data,
const char **models,
unsigned int nmodels,
const char *preferred ATTRIBUTE_UNUSED,
unsigned int flags)
{
int ret = -1;
struct ppc_map *map;
const struct ppc_model *model;
virCheckFlags(VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES, -1);
if (data == NULL || (map = ppcLoadMap()) == NULL)
return -1;
if (!(model = ppcModelFindPVR(map, data->data.ppc.pvr))) {
virReportError(VIR_ERR_OPERATION_FAILED,
_("Cannot find CPU model with PVR 0x%08x"),
data->data.ppc.pvr);
goto cleanup;
}
if (!cpuModelIsAllowed(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:
ppcMapFree(map);
return ret;
}
static void
ppcDataFree(virCPUDataPtr data)
{
if (data == NULL)
return;
VIR_FREE(data);
}
static virCPUDataPtr
ppcNodeData(virArch arch)
{
virCPUDataPtr cpuData;
if (VIR_ALLOC(cpuData) < 0)
return NULL;
cpuData->arch = arch;
#if defined(__powerpc__) || defined(__powerpc64__)
asm("mfpvr %0"
: "=r" (cpuData->data.ppc.pvr));
#endif
return cpuData;
}
static virCPUCompareResult
ppcGuestData(virCPUDefPtr host,
virCPUDefPtr guest,
virCPUDataPtr *data,
char **message)
{
return ppcCompute(host, guest, data, message);
}
static int
ppcUpdate(virCPUDefPtr guest,
const virCPUDef *host)
{
switch ((virCPUMode) guest->mode) {
case VIR_CPU_MODE_HOST_MODEL:
case VIR_CPU_MODE_HOST_PASSTHROUGH:
guest->match = VIR_CPU_MATCH_EXACT;
virCPUDefFreeModel(guest);
return virCPUDefCopyModel(guest, host, true);
case VIR_CPU_MODE_CUSTOM:
return 0;
case VIR_CPU_MODE_LAST:
break;
}
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unexpected CPU mode: %d"), guest->mode);
return -1;
}
static virCPUDefPtr
ppcBaseline(virCPUDefPtr *cpus,
unsigned int ncpus,
const char **models ATTRIBUTE_UNUSED,
unsigned int nmodels ATTRIBUTE_UNUSED,
unsigned int flags)
{
struct ppc_map *map = NULL;
const struct ppc_model *model;
const struct ppc_vendor *vendor = NULL;
virCPUDefPtr cpu = NULL;
size_t i;
virCheckFlags(VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES, NULL);
if (!(map = ppcLoadMap()))
goto error;
if (!(model = ppcModelFind(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 ppc_vendor *vnd;
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 = ppcVendorFind(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;
cleanup:
ppcMapFree(map);
return cpu;
error:
virCPUDefFree(cpu);
cpu = NULL;
goto cleanup;
}
static int
ppcGetModels(char ***models)
{
struct ppc_map *map;
struct ppc_model *model;
char *name;
size_t nmodels = 0;
if (!(map = ppcLoadMap()))
goto error;
if (models && VIR_ALLOC_N(*models, 0) < 0)
goto error;
model = map->models;
while (model != NULL) {
if (models) {
if (VIR_STRDUP(name, model->name) < 0)
goto error;
if (VIR_APPEND_ELEMENT(*models, nmodels, name) < 0)
goto error;
} else {
nmodels++;
}
model = model->next;
}
cleanup:
ppcMapFree(map);
return nmodels;
error:
if (models) {
virStringFreeList(*models);
*models = NULL;
}
nmodels = -1;
goto cleanup;
}
struct cpuArchDriver cpuDriverPowerPC = {
.name = "ppc64",
.arch = archs,
.narch = ARRAY_CARDINALITY(archs),
.compare = ppcCompare,
.decode = ppcDecode,
.encode = NULL,
.free = ppcDataFree,
.nodeData = ppcNodeData,
.guestData = ppcGuestData,
.baseline = ppcBaseline,
.update = ppcUpdate,
.hasFeature = NULL,
.getModels = ppcGetModels,
};