2011-10-03 05:56:20 -07:00
|
|
|
/*
|
2012-10-15 17:07:51 +08:00
|
|
|
* cpu_powerpc.c: CPU driver for PowerPC CPUs
|
2011-10-03 05:56:20 -07:00
|
|
|
*
|
maint: avoid 'const fooPtr' in cpu files
'const fooPtr' is the same as 'foo * const' (the pointer won't
change, but it's contents can). But in general, if an interface
is trying to be const-correct, it should be using 'const foo *'
(the pointer is to data that can't be changed).
Fix up offenders in src/cpu.
* src/cpu/cpu.h (cpuArchDecode, cpuArchEncode, cpuArchUpdate)
(cpuArchHasFeature, cpuDecode, cpuEncode, cpuUpdate)
(cpuHasFeature): Use intended type.
* src/conf/cpu_conf.h (virCPUDefCopyModel, virCPUDefCopy):
Likewise.
(virCPUDefParseXML): Drop const.
* src/cpu/cpu.c (cpuDecode, cpuEncode, cpuUpdate, cpuHasFeature):
Fix fallout.
* src/cpu/cpu_x86.c (x86ModelFromCPU, x86ModelSubtractCPU)
(x86DecodeCPUData, x86EncodePolicy, x86Encode, x86UpdateCustom)
(x86UpdateHostModel, x86Update, x86HasFeature): Likewise.
* src/cpu/cpu_s390.c (s390Decode): Likewise.
* src/cpu/cpu_arm.c (ArmDecode): Likewise.
* src/cpu/cpu_powerpc.c (ppcModelFromCPU, ppcCompute, ppcDecode)
(ppcUpdate): Likewise.
* src/conf/cpu_conf.c (virCPUDefCopyModel, virCPUDefCopy)
(virCPUDefParseXML): Likewise.
Signed-off-by: Eric Blake <eblake@redhat.com>
2013-10-05 14:01:02 -06:00
|
|
|
* Copyright (C) 2013 Red Hat, Inc.
|
2012-10-15 17:07:51 +08:00
|
|
|
* Copyright (C) IBM Corporation, 2010
|
2011-10-03 05:56:20 -07:00
|
|
|
*
|
|
|
|
* 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
|
2012-09-20 16:30:55 -06:00
|
|
|
* License along with this library. If not, see
|
2012-07-21 18:06:23 +08:00
|
|
|
* <http://www.gnu.org/licenses/>.
|
2011-10-03 05:56:20 -07:00
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Anton Blanchard <anton@au.ibm.com>
|
|
|
|
* Prerna Saxena <prerna@linux.vnet.ibm.com>
|
2012-10-15 17:07:50 +08:00
|
|
|
* Li Zhang <zhlcindy@linux.vnet.ibm.com>
|
2011-10-03 05:56:20 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
2012-10-15 17:07:50 +08:00
|
|
|
#include <stdint.h>
|
2011-10-03 05:56:20 -07:00
|
|
|
|
2012-12-12 17:59:27 +00:00
|
|
|
#include "virlog.h"
|
2012-12-12 18:06:53 +00:00
|
|
|
#include "viralloc.h"
|
2011-10-03 05:56:20 -07:00
|
|
|
#include "cpu.h"
|
2013-05-03 14:41:23 +02:00
|
|
|
#include "virstring.h"
|
2012-10-15 17:07:50 +08:00
|
|
|
#include "cpu_map.h"
|
2012-12-04 12:04:07 +00:00
|
|
|
#include "virbuffer.h"
|
2011-10-03 05:56:20 -07:00
|
|
|
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_CPU
|
|
|
|
|
2012-12-11 12:58:54 +00:00
|
|
|
static const virArch archs[] = { VIR_ARCH_PPC64 };
|
2011-10-03 05:56:20 -07:00
|
|
|
|
2012-10-15 17:07:50 +08:00
|
|
|
struct ppc_vendor {
|
|
|
|
char *name;
|
|
|
|
struct ppc_vendor *next;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ppc_model {
|
|
|
|
char *name;
|
|
|
|
const struct ppc_vendor *vendor;
|
2012-12-19 01:35:51 +01:00
|
|
|
struct cpuPPCData data;
|
2012-10-15 17:07:50 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-12-19 01:35:51 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-09-03 14:28:24 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-10-15 17:07:50 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2013-09-03 14:28:24 +08:00
|
|
|
static struct ppc_model *
|
maint: avoid 'const fooPtr' in cpu files
'const fooPtr' is the same as 'foo * const' (the pointer won't
change, but it's contents can). But in general, if an interface
is trying to be const-correct, it should be using 'const foo *'
(the pointer is to data that can't be changed).
Fix up offenders in src/cpu.
* src/cpu/cpu.h (cpuArchDecode, cpuArchEncode, cpuArchUpdate)
(cpuArchHasFeature, cpuDecode, cpuEncode, cpuUpdate)
(cpuHasFeature): Use intended type.
* src/conf/cpu_conf.h (virCPUDefCopyModel, virCPUDefCopy):
Likewise.
(virCPUDefParseXML): Drop const.
* src/cpu/cpu.c (cpuDecode, cpuEncode, cpuUpdate, cpuHasFeature):
Fix fallout.
* src/cpu/cpu_x86.c (x86ModelFromCPU, x86ModelSubtractCPU)
(x86DecodeCPUData, x86EncodePolicy, x86Encode, x86UpdateCustom)
(x86UpdateHostModel, x86Update, x86HasFeature): Likewise.
* src/cpu/cpu_s390.c (s390Decode): Likewise.
* src/cpu/cpu_arm.c (ArmDecode): Likewise.
* src/cpu/cpu_powerpc.c (ppcModelFromCPU, ppcCompute, ppcDecode)
(ppcUpdate): Likewise.
* src/conf/cpu_conf.c (virCPUDefCopyModel, virCPUDefCopy)
(virCPUDefParseXML): Likewise.
Signed-off-by: Eric Blake <eblake@redhat.com>
2013-10-05 14:01:02 -06:00
|
|
|
ppcModelFromCPU(const virCPUDef *cpu,
|
2013-09-03 14:28:24 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-10-15 17:07:50 +08:00
|
|
|
static int
|
|
|
|
ppcVendorLoad(xmlXPathContextPtr ctxt,
|
|
|
|
struct ppc_map *map)
|
|
|
|
{
|
|
|
|
struct ppc_vendor *vendor = NULL;
|
|
|
|
|
2013-07-04 12:03:29 +02:00
|
|
|
if (VIR_ALLOC(vendor) < 0)
|
2012-12-19 00:06:45 +01:00
|
|
|
return -1;
|
2012-10-15 17:07:50 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-12-19 00:06:45 +01:00
|
|
|
if (!map->vendors) {
|
2012-10-15 17:07:50 +08:00
|
|
|
map->vendors = vendor;
|
2012-12-19 00:06:45 +01:00
|
|
|
} else {
|
2012-10-15 17:07:50 +08:00
|
|
|
vendor->next = map->vendors;
|
|
|
|
map->vendors = vendor;
|
|
|
|
}
|
|
|
|
|
2012-12-19 00:06:45 +01:00
|
|
|
cleanup:
|
|
|
|
return 0;
|
2012-10-15 17:07:50 +08:00
|
|
|
|
|
|
|
ignore:
|
|
|
|
ppcVendorFree(vendor);
|
2012-12-19 00:06:45 +01:00
|
|
|
goto cleanup;
|
2012-10-15 17:07:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
ppcModelLoad(xmlXPathContextPtr ctxt,
|
|
|
|
struct ppc_map *map)
|
|
|
|
{
|
|
|
|
struct ppc_model *model;
|
|
|
|
char *vendor = NULL;
|
2012-12-19 01:35:51 +01:00
|
|
|
unsigned long pvr;
|
2012-10-15 17:07:50 +08:00
|
|
|
|
2013-07-04 12:03:29 +02:00
|
|
|
if (VIR_ALLOC(model) < 0)
|
2012-12-19 01:35:51 +01:00
|
|
|
return -1;
|
2012-10-15 17:07:50 +08:00
|
|
|
|
|
|
|
model->name = virXPathString("string(@name)", ctxt);
|
2012-12-19 01:35:51 +01:00
|
|
|
if (!model->name) {
|
2012-10-15 17:07:50 +08:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
"%s", _("Missing CPU model name"));
|
|
|
|
goto ignore;
|
|
|
|
}
|
|
|
|
|
2012-12-19 01:35:51 +01:00
|
|
|
if (ppcModelFind(map, model->name)) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("CPU model %s already defined"), model->name);
|
|
|
|
goto ignore;
|
|
|
|
}
|
2012-10-15 17:07:50 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-19 01:35:51 +01:00
|
|
|
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) {
|
2012-10-15 17:07:50 +08:00
|
|
|
map->models = model;
|
2012-12-19 01:35:51 +01:00
|
|
|
} else {
|
2012-10-15 17:07:50 +08:00
|
|
|
model->next = map->models;
|
|
|
|
map->models = model;
|
|
|
|
}
|
|
|
|
|
2012-12-19 01:35:51 +01:00
|
|
|
cleanup:
|
2012-10-15 17:07:50 +08:00
|
|
|
VIR_FREE(vendor);
|
2012-12-19 01:35:51 +01:00
|
|
|
return 0;
|
2012-10-15 17:07:50 +08:00
|
|
|
|
|
|
|
ignore:
|
|
|
|
ppcModelFree(model);
|
2012-12-19 01:35:51 +01:00
|
|
|
goto cleanup;
|
2012-10-15 17:07:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
ppcMapLoadCallback(enum 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);
|
2012-12-19 01:35:51 +01:00
|
|
|
case CPU_MAP_ELEMENT_FEATURE:
|
|
|
|
case CPU_MAP_ELEMENT_LAST:
|
2012-10-15 17:07:50 +08:00
|
|
|
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;
|
|
|
|
|
2013-07-04 12:03:29 +02:00
|
|
|
if (VIR_ALLOC(map) < 0)
|
2012-10-15 17:07:50 +08:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (cpuMapLoad("ppc64", ppcMapLoadCallback, map) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
return map;
|
|
|
|
|
|
|
|
error:
|
|
|
|
ppcMapFree(map);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-09-03 14:28:24 +08:00
|
|
|
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,
|
maint: avoid 'const fooPtr' in cpu files
'const fooPtr' is the same as 'foo * const' (the pointer won't
change, but it's contents can). But in general, if an interface
is trying to be const-correct, it should be using 'const foo *'
(the pointer is to data that can't be changed).
Fix up offenders in src/cpu.
* src/cpu/cpu.h (cpuArchDecode, cpuArchEncode, cpuArchUpdate)
(cpuArchHasFeature, cpuDecode, cpuEncode, cpuUpdate)
(cpuHasFeature): Use intended type.
* src/conf/cpu_conf.h (virCPUDefCopyModel, virCPUDefCopy):
Likewise.
(virCPUDefParseXML): Drop const.
* src/cpu/cpu.c (cpuDecode, cpuEncode, cpuUpdate, cpuHasFeature):
Fix fallout.
* src/cpu/cpu_x86.c (x86ModelFromCPU, x86ModelSubtractCPU)
(x86DecodeCPUData, x86EncodePolicy, x86Encode, x86UpdateCustom)
(x86UpdateHostModel, x86Update, x86HasFeature): Likewise.
* src/cpu/cpu_s390.c (s390Decode): Likewise.
* src/cpu/cpu_arm.c (ArmDecode): Likewise.
* src/cpu/cpu_powerpc.c (ppcModelFromCPU, ppcCompute, ppcDecode)
(ppcUpdate): Likewise.
* src/conf/cpu_conf.c (virCPUDefCopyModel, virCPUDefCopy)
(virCPUDefParseXML): Likewise.
Signed-off-by: Eric Blake <eblake@redhat.com>
2013-10-05 14:01:02 -06:00
|
|
|
const virCPUDef *cpu,
|
2013-09-09 13:56:33 +08:00
|
|
|
virCPUDataPtr *guestData,
|
|
|
|
char **message)
|
2013-09-03 14:28:24 +08:00
|
|
|
|
|
|
|
{
|
|
|
|
struct ppc_map *map = NULL;
|
|
|
|
struct ppc_model *host_model = NULL;
|
|
|
|
struct ppc_model *guest_model = NULL;
|
|
|
|
|
2013-09-09 13:56:33 +08:00
|
|
|
virCPUCompareResult ret = VIR_CPU_COMPARE_ERROR;
|
2013-09-03 14:28:24 +08:00
|
|
|
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)
|
2013-09-09 13:56:33 +08:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
ret = VIR_CPU_COMPARE_INCOMPATIBLE;
|
|
|
|
goto cleanup;
|
2013-09-03 14:28:24 +08:00
|
|
|
}
|
|
|
|
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)
|
2013-09-09 13:56:33 +08:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
ret = VIR_CPU_COMPARE_INCOMPATIBLE;
|
|
|
|
goto cleanup;
|
2013-09-03 14:28:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(map = ppcLoadMap()) ||
|
|
|
|
!(host_model = ppcModelFromCPU(host, map)) ||
|
|
|
|
!(guest_model = ppcModelFromCPU(cpu, map)))
|
2013-09-09 13:56:33 +08:00
|
|
|
goto cleanup;
|
2013-09-03 14:28:24 +08:00
|
|
|
|
|
|
|
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)
|
2013-09-09 13:56:33 +08:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
ret = VIR_CPU_COMPARE_INCOMPATIBLE;
|
|
|
|
goto cleanup;
|
2013-09-03 14:28:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(*guestData = ppcMakeCPUData(arch, &guest_model->data)))
|
2013-09-09 13:56:33 +08:00
|
|
|
goto cleanup;
|
2013-09-03 14:28:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = VIR_CPU_COMPARE_IDENTICAL;
|
|
|
|
|
2013-09-09 13:56:33 +08:00
|
|
|
cleanup:
|
2013-09-03 14:28:24 +08:00
|
|
|
ppcMapFree(map);
|
|
|
|
ppcModelFree(host_model);
|
|
|
|
ppcModelFree(guest_model);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-10-15 17:07:50 +08:00
|
|
|
static virCPUCompareResult
|
2012-12-18 23:32:01 +01:00
|
|
|
ppcCompare(virCPUDefPtr host,
|
2012-10-15 17:07:50 +08:00
|
|
|
virCPUDefPtr cpu)
|
|
|
|
{
|
2012-12-18 23:32:01 +01:00
|
|
|
if ((cpu->arch == VIR_ARCH_NONE || host->arch == cpu->arch) &&
|
|
|
|
STREQ(host->model, cpu->model))
|
|
|
|
return VIR_CPU_COMPARE_IDENTICAL;
|
2012-10-15 17:07:50 +08:00
|
|
|
|
2012-12-18 23:32:01 +01:00
|
|
|
return VIR_CPU_COMPARE_INCOMPATIBLE;
|
2012-10-15 17:07:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2012-12-19 01:53:25 +01:00
|
|
|
ppcDecode(virCPUDefPtr cpu,
|
maint: avoid 'const fooPtr' in cpu files
'const fooPtr' is the same as 'foo * const' (the pointer won't
change, but it's contents can). But in general, if an interface
is trying to be const-correct, it should be using 'const foo *'
(the pointer is to data that can't be changed).
Fix up offenders in src/cpu.
* src/cpu/cpu.h (cpuArchDecode, cpuArchEncode, cpuArchUpdate)
(cpuArchHasFeature, cpuDecode, cpuEncode, cpuUpdate)
(cpuHasFeature): Use intended type.
* src/conf/cpu_conf.h (virCPUDefCopyModel, virCPUDefCopy):
Likewise.
(virCPUDefParseXML): Drop const.
* src/cpu/cpu.c (cpuDecode, cpuEncode, cpuUpdate, cpuHasFeature):
Fix fallout.
* src/cpu/cpu_x86.c (x86ModelFromCPU, x86ModelSubtractCPU)
(x86DecodeCPUData, x86EncodePolicy, x86Encode, x86UpdateCustom)
(x86UpdateHostModel, x86Update, x86HasFeature): Likewise.
* src/cpu/cpu_s390.c (s390Decode): Likewise.
* src/cpu/cpu_arm.c (ArmDecode): Likewise.
* src/cpu/cpu_powerpc.c (ppcModelFromCPU, ppcCompute, ppcDecode)
(ppcUpdate): Likewise.
* src/conf/cpu_conf.c (virCPUDefCopyModel, virCPUDefCopy)
(virCPUDefParseXML): Likewise.
Signed-off-by: Eric Blake <eblake@redhat.com>
2013-10-05 14:01:02 -06:00
|
|
|
const virCPUData *data,
|
2012-10-15 17:07:50 +08:00
|
|
|
const char **models,
|
|
|
|
unsigned int nmodels,
|
2013-08-02 13:08:19 -06:00
|
|
|
const char *preferred ATTRIBUTE_UNUSED,
|
|
|
|
unsigned int flags)
|
2012-10-15 17:07:50 +08:00
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct ppc_map *map;
|
2012-12-19 01:35:51 +01:00
|
|
|
const struct ppc_model *model;
|
2012-10-15 17:07:50 +08:00
|
|
|
|
2013-08-02 13:08:19 -06:00
|
|
|
virCheckFlags(VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES, -1);
|
|
|
|
|
2012-10-15 17:07:50 +08:00
|
|
|
if (data == NULL || (map = ppcLoadMap()) == NULL)
|
|
|
|
return -1;
|
|
|
|
|
2013-07-16 14:39:40 +02:00
|
|
|
if (!(model = ppcModelFindPVR(map, data->data.ppc.pvr))) {
|
2012-12-19 01:35:51 +01:00
|
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
|
|
_("Cannot find CPU model with PVR 0x%08x"),
|
2013-07-16 14:39:40 +02:00
|
|
|
data->data.ppc.pvr);
|
2012-12-19 01:53:25 +01:00
|
|
|
goto cleanup;
|
2012-12-19 01:35:51 +01:00
|
|
|
}
|
2012-10-15 17:07:50 +08:00
|
|
|
|
2012-12-19 01:35:51 +01:00
|
|
|
if (!cpuModelIsAllowed(model->name, models, nmodels)) {
|
2012-12-19 01:53:25 +01:00
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("CPU model %s is not supported by hypervisor"),
|
2012-12-19 01:35:51 +01:00
|
|
|
model->name);
|
2012-12-19 01:53:25 +01:00
|
|
|
goto cleanup;
|
2012-10-15 17:07:50 +08:00
|
|
|
}
|
|
|
|
|
2013-05-03 14:41:23 +02:00
|
|
|
if (VIR_STRDUP(cpu->model, model->name) < 0 ||
|
|
|
|
(model->vendor && VIR_STRDUP(cpu->vendor, model->vendor->name) < 0)) {
|
2012-12-19 01:35:51 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
2012-10-15 17:07:50 +08:00
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
2012-12-19 01:53:25 +01:00
|
|
|
cleanup:
|
2012-10-15 17:07:50 +08:00
|
|
|
ppcMapFree(map);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2012-12-18 19:44:23 +01:00
|
|
|
ppcDataFree(virCPUDataPtr data)
|
2012-10-15 17:07:50 +08:00
|
|
|
{
|
|
|
|
if (data == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
VIR_FREE(data);
|
|
|
|
}
|
|
|
|
|
2012-12-18 19:44:23 +01:00
|
|
|
static virCPUDataPtr
|
Ensure 'arch' is always set in cpuArchNodeData
The s390, ppc and arm CPU drivers never set the 'arch' field
in their impl of cpuArchNodeData. This leads to error messages
being reported from cpuDataFree later, due to trying to use
VIR_ARCH_NONE.
#0 virRaiseErrorFull (filename=filename@entry=0x76f94434 "cpu/cpu.c", funcname=funcname@entry=0x76f942dc <__FUNCTION__.18096> "cpuGetSubDriver", linenr=linenr@entry=58,
domain=domain@entry=31, code=code@entry=1, level=level@entry=VIR_ERR_ERROR, str1=0x76f70e18 "internal error: %s",
str2=str2@entry=0x7155f2ec "undefined hardware architecture", str3=str3@entry=0x0, int1=int1@entry=-1, int2=int2@entry=-1, fmt=0x76f70e18 "internal error: %s")
at util/virerror.c:646
#1 0x76e682ea in virReportErrorHelper (domcode=domcode@entry=31, errorcode=errorcode@entry=1, filename=0x76f94434 "cpu/cpu.c",
funcname=0x76f942dc <__FUNCTION__.18096> "cpuGetSubDriver", linenr=linenr@entry=58, fmt=0x76f7e7e4 "%s") at util/virerror.c:1292
#2 0x76ed82d4 in cpuGetSubDriver (arch=<optimized out>) at cpu/cpu.c:57
#3 cpuGetSubDriver (arch=VIR_ARCH_NONE) at cpu/cpu.c:51
#4 0x76ed8818 in cpuDataFree (data=data@entry=0x70c22d78) at cpu/cpu.c:216
#5 0x716aaec0 in virQEMUCapsInitCPU (arch=VIR_ARCH_ARMV7L, caps=0x70c29a08) at qemu/qemu_capabilities.c:867
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-09-04 17:41:55 +01:00
|
|
|
ppcNodeData(virArch arch)
|
2011-10-03 05:56:20 -07:00
|
|
|
{
|
2013-07-29 15:41:19 +08:00
|
|
|
virCPUDataPtr cpuData;
|
2011-10-03 05:56:20 -07:00
|
|
|
|
2013-07-29 15:41:19 +08:00
|
|
|
if (VIR_ALLOC(cpuData) < 0)
|
2011-10-03 05:56:20 -07:00
|
|
|
return NULL;
|
|
|
|
|
Ensure 'arch' is always set in cpuArchNodeData
The s390, ppc and arm CPU drivers never set the 'arch' field
in their impl of cpuArchNodeData. This leads to error messages
being reported from cpuDataFree later, due to trying to use
VIR_ARCH_NONE.
#0 virRaiseErrorFull (filename=filename@entry=0x76f94434 "cpu/cpu.c", funcname=funcname@entry=0x76f942dc <__FUNCTION__.18096> "cpuGetSubDriver", linenr=linenr@entry=58,
domain=domain@entry=31, code=code@entry=1, level=level@entry=VIR_ERR_ERROR, str1=0x76f70e18 "internal error: %s",
str2=str2@entry=0x7155f2ec "undefined hardware architecture", str3=str3@entry=0x0, int1=int1@entry=-1, int2=int2@entry=-1, fmt=0x76f70e18 "internal error: %s")
at util/virerror.c:646
#1 0x76e682ea in virReportErrorHelper (domcode=domcode@entry=31, errorcode=errorcode@entry=1, filename=0x76f94434 "cpu/cpu.c",
funcname=0x76f942dc <__FUNCTION__.18096> "cpuGetSubDriver", linenr=linenr@entry=58, fmt=0x76f7e7e4 "%s") at util/virerror.c:1292
#2 0x76ed82d4 in cpuGetSubDriver (arch=<optimized out>) at cpu/cpu.c:57
#3 cpuGetSubDriver (arch=VIR_ARCH_NONE) at cpu/cpu.c:51
#4 0x76ed8818 in cpuDataFree (data=data@entry=0x70c22d78) at cpu/cpu.c:216
#5 0x716aaec0 in virQEMUCapsInitCPU (arch=VIR_ARCH_ARMV7L, caps=0x70c29a08) at qemu/qemu_capabilities.c:867
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-09-04 17:41:55 +01:00
|
|
|
cpuData->arch = arch;
|
|
|
|
|
|
|
|
#if defined(__powerpc__) || defined(__powerpc64__)
|
2012-12-18 23:42:34 +01:00
|
|
|
asm("mfpvr %0"
|
2013-07-29 15:41:19 +08:00
|
|
|
: "=r" (cpuData->data.ppc.pvr));
|
Ensure 'arch' is always set in cpuArchNodeData
The s390, ppc and arm CPU drivers never set the 'arch' field
in their impl of cpuArchNodeData. This leads to error messages
being reported from cpuDataFree later, due to trying to use
VIR_ARCH_NONE.
#0 virRaiseErrorFull (filename=filename@entry=0x76f94434 "cpu/cpu.c", funcname=funcname@entry=0x76f942dc <__FUNCTION__.18096> "cpuGetSubDriver", linenr=linenr@entry=58,
domain=domain@entry=31, code=code@entry=1, level=level@entry=VIR_ERR_ERROR, str1=0x76f70e18 "internal error: %s",
str2=str2@entry=0x7155f2ec "undefined hardware architecture", str3=str3@entry=0x0, int1=int1@entry=-1, int2=int2@entry=-1, fmt=0x76f70e18 "internal error: %s")
at util/virerror.c:646
#1 0x76e682ea in virReportErrorHelper (domcode=domcode@entry=31, errorcode=errorcode@entry=1, filename=0x76f94434 "cpu/cpu.c",
funcname=0x76f942dc <__FUNCTION__.18096> "cpuGetSubDriver", linenr=linenr@entry=58, fmt=0x76f7e7e4 "%s") at util/virerror.c:1292
#2 0x76ed82d4 in cpuGetSubDriver (arch=<optimized out>) at cpu/cpu.c:57
#3 cpuGetSubDriver (arch=VIR_ARCH_NONE) at cpu/cpu.c:51
#4 0x76ed8818 in cpuDataFree (data=data@entry=0x70c22d78) at cpu/cpu.c:216
#5 0x716aaec0 in virQEMUCapsInitCPU (arch=VIR_ARCH_ARMV7L, caps=0x70c29a08) at qemu/qemu_capabilities.c:867
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-09-04 17:41:55 +01:00
|
|
|
#endif
|
2012-10-15 17:07:50 +08:00
|
|
|
|
2013-07-29 15:41:19 +08:00
|
|
|
return cpuData;
|
2011-10-03 05:56:20 -07:00
|
|
|
}
|
|
|
|
|
2013-09-03 14:28:24 +08:00
|
|
|
static virCPUCompareResult
|
|
|
|
ppcGuestData(virCPUDefPtr host,
|
|
|
|
virCPUDefPtr guest,
|
|
|
|
virCPUDataPtr *data,
|
|
|
|
char **message)
|
|
|
|
{
|
|
|
|
return ppcCompute(host, guest, data, message);
|
|
|
|
}
|
|
|
|
|
2011-10-03 05:56:20 -07:00
|
|
|
static int
|
2013-09-03 14:28:24 +08:00
|
|
|
ppcUpdate(virCPUDefPtr guest,
|
maint: avoid 'const fooPtr' in cpu files
'const fooPtr' is the same as 'foo * const' (the pointer won't
change, but it's contents can). But in general, if an interface
is trying to be const-correct, it should be using 'const foo *'
(the pointer is to data that can't be changed).
Fix up offenders in src/cpu.
* src/cpu/cpu.h (cpuArchDecode, cpuArchEncode, cpuArchUpdate)
(cpuArchHasFeature, cpuDecode, cpuEncode, cpuUpdate)
(cpuHasFeature): Use intended type.
* src/conf/cpu_conf.h (virCPUDefCopyModel, virCPUDefCopy):
Likewise.
(virCPUDefParseXML): Drop const.
* src/cpu/cpu.c (cpuDecode, cpuEncode, cpuUpdate, cpuHasFeature):
Fix fallout.
* src/cpu/cpu_x86.c (x86ModelFromCPU, x86ModelSubtractCPU)
(x86DecodeCPUData, x86EncodePolicy, x86Encode, x86UpdateCustom)
(x86UpdateHostModel, x86Update, x86HasFeature): Likewise.
* src/cpu/cpu_s390.c (s390Decode): Likewise.
* src/cpu/cpu_arm.c (ArmDecode): Likewise.
* src/cpu/cpu_powerpc.c (ppcModelFromCPU, ppcCompute, ppcDecode)
(ppcUpdate): Likewise.
* src/conf/cpu_conf.c (virCPUDefCopyModel, virCPUDefCopy)
(virCPUDefParseXML): Likewise.
Signed-off-by: Eric Blake <eblake@redhat.com>
2013-10-05 14:01:02 -06:00
|
|
|
const virCPUDef *host)
|
2011-10-03 05:56:20 -07:00
|
|
|
{
|
2013-09-03 14:28:24 +08:00
|
|
|
switch ((enum 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;
|
2011-10-03 05:56:20 -07:00
|
|
|
}
|
2012-12-20 12:38:25 +01:00
|
|
|
|
2012-10-15 17:07:50 +08:00
|
|
|
static virCPUDefPtr
|
2012-12-19 01:33:01 +01:00
|
|
|
ppcBaseline(virCPUDefPtr *cpus,
|
|
|
|
unsigned int ncpus,
|
|
|
|
const char **models,
|
2013-08-02 13:08:19 -06:00
|
|
|
unsigned int nmodels,
|
|
|
|
unsigned int flags)
|
2011-10-03 05:56:20 -07:00
|
|
|
{
|
2012-10-15 17:07:50 +08:00
|
|
|
struct ppc_map *map = NULL;
|
2012-12-19 01:33:01 +01:00
|
|
|
const struct ppc_model *model;
|
|
|
|
const struct ppc_vendor *vendor = NULL;
|
2012-10-15 17:07:50 +08:00
|
|
|
virCPUDefPtr cpu = NULL;
|
Convert 'int i' to 'size_t i' in src/cpu/ files
Convert the type of loop iterators named 'i', 'j', k',
'ii', 'jj', 'kk', to be 'size_t' instead of 'int' or
'unsigned int', also santizing 'ii', 'jj', 'kk' to use
the normal 'i', 'j', 'k' naming
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-07-08 15:09:33 +01:00
|
|
|
size_t i;
|
2012-12-19 01:33:01 +01:00
|
|
|
|
2013-08-02 13:08:19 -06:00
|
|
|
virCheckFlags(VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES, NULL);
|
|
|
|
|
2012-12-19 01:33:01 +01:00
|
|
|
if (!(map = ppcLoadMap()))
|
|
|
|
goto error;
|
2012-10-15 17:07:50 +08:00
|
|
|
|
2012-12-19 01:33:01 +01:00
|
|
|
if (!(model = ppcModelFind(map, cpus[0]->model))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("Unknown CPU model %s"), cpus[0]->model);
|
2012-10-15 17:07:50 +08:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2012-12-19 01:33:01 +01:00
|
|
|
if (!cpuModelIsAllowed(model->name, models, nmodels)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("CPU model %s is not supported by hypervisor"),
|
|
|
|
model->name);
|
2012-10-15 17:07:50 +08:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2012-12-19 01:33:01 +01:00
|
|
|
for (i = 0; i < ncpus; i++) {
|
|
|
|
const struct ppc_vendor *vnd;
|
2012-12-11 12:58:54 +00:00
|
|
|
|
2012-12-19 01:33:01 +01:00
|
|
|
if (STRNEQ(cpus[i]->model, model->name)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
|
|
_("CPUs are incompatible"));
|
|
|
|
goto error;
|
|
|
|
}
|
2012-10-15 17:07:50 +08:00
|
|
|
|
2012-12-19 01:33:01 +01:00
|
|
|
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;
|
|
|
|
}
|
2012-10-15 17:07:50 +08:00
|
|
|
}
|
|
|
|
|
2012-12-19 01:33:01 +01:00
|
|
|
if (VIR_ALLOC(cpu) < 0 ||
|
2013-05-03 14:41:23 +02:00
|
|
|
VIR_STRDUP(cpu->model, model->name) < 0)
|
2013-07-04 12:03:29 +02:00
|
|
|
goto error;
|
2012-10-15 17:07:50 +08:00
|
|
|
|
2013-05-03 14:41:23 +02:00
|
|
|
if (vendor && VIR_STRDUP(cpu->vendor, vendor->name) < 0)
|
|
|
|
goto error;
|
2012-12-19 01:33:01 +01:00
|
|
|
|
|
|
|
cpu->type = VIR_CPU_TYPE_GUEST;
|
|
|
|
cpu->match = VIR_CPU_MATCH_EXACT;
|
2012-10-15 17:07:50 +08:00
|
|
|
|
|
|
|
cleanup:
|
|
|
|
ppcMapFree(map);
|
2011-10-03 05:56:20 -07:00
|
|
|
|
2012-10-15 17:07:50 +08:00
|
|
|
return cpu;
|
2012-12-19 01:33:01 +01:00
|
|
|
|
2012-10-15 17:07:50 +08:00
|
|
|
error:
|
|
|
|
virCPUDefFree(cpu);
|
|
|
|
cpu = NULL;
|
|
|
|
goto cleanup;
|
2011-10-03 05:56:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
struct cpuArchDriver cpuDriverPowerPC = {
|
|
|
|
.name = "ppc64",
|
|
|
|
.arch = archs,
|
|
|
|
.narch = ARRAY_CARDINALITY(archs),
|
2012-12-18 23:32:01 +01:00
|
|
|
.compare = ppcCompare,
|
2012-12-19 01:53:25 +01:00
|
|
|
.decode = ppcDecode,
|
2011-10-03 05:56:20 -07:00
|
|
|
.encode = NULL,
|
2012-12-20 12:38:25 +01:00
|
|
|
.free = ppcDataFree,
|
2012-12-18 23:42:34 +01:00
|
|
|
.nodeData = ppcNodeData,
|
2013-09-03 14:28:24 +08:00
|
|
|
.guestData = ppcGuestData,
|
2012-12-19 01:33:01 +01:00
|
|
|
.baseline = ppcBaseline,
|
2012-12-20 12:38:25 +01:00
|
|
|
.update = ppcUpdate,
|
2011-10-03 05:56:20 -07:00
|
|
|
.hasFeature = NULL,
|
|
|
|
};
|