mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-10-07 23:05:48 +00:00
36d8e7d8d7
* global: patch created by running: for f in $(git ls-files '*.[ch]') ; do cppi $f > $f.t && mv $f.t $f done
1296 lines
30 KiB
C
1296 lines
30 KiB
C
/*
|
|
* cpu_x86.c: CPU driver for CPUs with x86 compatible CPUID instruction
|
|
*
|
|
* Copyright (C) 2009-2010 Red Hat, Inc.
|
|
*
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* Authors:
|
|
* Jiri Denemark <jdenemar@redhat.com>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "logging.h"
|
|
#include "memory.h"
|
|
#include "cpu.h"
|
|
#include "cpu_map.h"
|
|
#include "cpu_x86.h"
|
|
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_CPU
|
|
|
|
static const char *archs[] = { "i686", "x86_64" };
|
|
|
|
struct x86_feature {
|
|
char *name;
|
|
unsigned int ncpuid;
|
|
struct cpuX86cpuid *cpuid;
|
|
|
|
struct x86_feature *next;
|
|
};
|
|
|
|
struct x86_model {
|
|
char *name;
|
|
unsigned int ncpuid;
|
|
struct cpuX86cpuid *cpuid;
|
|
|
|
struct x86_model *next;
|
|
};
|
|
|
|
struct x86_map {
|
|
struct x86_feature *features;
|
|
struct x86_model *models;
|
|
};
|
|
|
|
|
|
enum compare_result {
|
|
SUBSET,
|
|
EQUAL,
|
|
SUPERSET,
|
|
UNRELATED
|
|
};
|
|
|
|
|
|
static struct cpuX86cpuid *
|
|
x86cpuidFind(struct cpuX86cpuid *cpuids,
|
|
unsigned int ncpuids,
|
|
uint32_t function)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ncpuids; i++) {
|
|
if (cpuids[i].function == function)
|
|
return cpuids + i;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static inline int
|
|
x86cpuidMatch(const struct cpuX86cpuid *cpuid1,
|
|
const struct cpuX86cpuid *cpuid2)
|
|
{
|
|
return (cpuid1->eax == cpuid2->eax &&
|
|
cpuid1->ebx == cpuid2->ebx &&
|
|
cpuid1->ecx == cpuid2->ecx &&
|
|
cpuid1->edx == cpuid2->edx);
|
|
}
|
|
|
|
|
|
static inline int
|
|
x86cpuidMatchMasked(const struct cpuX86cpuid *cpuid,
|
|
const struct cpuX86cpuid *mask)
|
|
{
|
|
return ((cpuid->eax & mask->eax) == mask->eax &&
|
|
(cpuid->ebx & mask->ebx) == mask->ebx &&
|
|
(cpuid->ecx & mask->ecx) == mask->ecx &&
|
|
(cpuid->edx & mask->edx) == mask->edx);
|
|
}
|
|
|
|
|
|
static inline int
|
|
x86cpuidMatchAny(const struct cpuX86cpuid *cpuid,
|
|
const struct cpuX86cpuid *mask)
|
|
{
|
|
return ((cpuid->eax & mask->eax) ||
|
|
(cpuid->ebx & mask->ebx) ||
|
|
(cpuid->ecx & mask->ecx) ||
|
|
(cpuid->edx & mask->edx));
|
|
}
|
|
|
|
|
|
static inline void
|
|
x86cpuidSetBits(struct cpuX86cpuid *cpuid,
|
|
const struct cpuX86cpuid *mask)
|
|
{
|
|
cpuid->eax |= mask->eax;
|
|
cpuid->ebx |= mask->ebx;
|
|
cpuid->ecx |= mask->ecx;
|
|
cpuid->edx |= mask->edx;
|
|
}
|
|
|
|
|
|
static inline void
|
|
x86cpuidClearBits(struct cpuX86cpuid *cpuid,
|
|
const struct cpuX86cpuid *mask)
|
|
{
|
|
cpuid->eax &= ~mask->eax;
|
|
cpuid->ebx &= ~mask->ebx;
|
|
cpuid->ecx &= ~mask->ecx;
|
|
cpuid->edx &= ~mask->edx;
|
|
}
|
|
|
|
|
|
static inline void
|
|
x86cpuidAndBits(struct cpuX86cpuid *cpuid,
|
|
const struct cpuX86cpuid *mask)
|
|
{
|
|
cpuid->eax &= mask->eax;
|
|
cpuid->ebx &= mask->ebx;
|
|
cpuid->ecx &= mask->ecx;
|
|
cpuid->edx &= mask->edx;
|
|
}
|
|
|
|
|
|
static struct cpuX86cpuid *
|
|
x86DataCpuid(const union cpuData *data,
|
|
uint32_t function)
|
|
{
|
|
struct cpuX86cpuid *cpuids;
|
|
int len;
|
|
unsigned int i;
|
|
|
|
if (function < CPUX86_EXTENDED) {
|
|
cpuids = data->x86.basic;
|
|
len = data->x86.basic_len;
|
|
i = function;
|
|
}
|
|
else {
|
|
cpuids = data->x86.extended;
|
|
len = data->x86.extended_len;
|
|
i = function - CPUX86_EXTENDED;
|
|
}
|
|
|
|
if (i < len)
|
|
return cpuids + i;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
x86DataFree(union cpuData *data)
|
|
{
|
|
if (data == NULL)
|
|
return;
|
|
|
|
VIR_FREE(data->x86.basic);
|
|
VIR_FREE(data->x86.extended);
|
|
VIR_FREE(data);
|
|
}
|
|
|
|
|
|
static union cpuData *
|
|
x86DataCopy(const union cpuData *data)
|
|
{
|
|
union cpuData *copy = NULL;
|
|
int i;
|
|
|
|
if (VIR_ALLOC(copy) < 0
|
|
|| VIR_ALLOC_N(copy->x86.basic, data->x86.basic_len) < 0
|
|
|| VIR_ALLOC_N(copy->x86.extended, data->x86.extended_len) < 0) {
|
|
x86DataFree(copy);
|
|
return NULL;
|
|
}
|
|
|
|
copy->x86.basic_len = data->x86.basic_len;
|
|
for (i = 0; i < data->x86.basic_len; i++)
|
|
copy->x86.basic[i] = data->x86.basic[i];
|
|
|
|
copy->x86.extended_len = data->x86.extended_len;
|
|
for (i = 0; i < data->x86.extended_len; i++)
|
|
copy->x86.extended[i] = data->x86.extended[i];
|
|
|
|
return copy;
|
|
}
|
|
|
|
|
|
static union cpuData *
|
|
x86DataFromModel(const struct x86_model *model)
|
|
{
|
|
union cpuData *data = NULL;
|
|
uint32_t basic_len = 0;
|
|
uint32_t extended_len = 0;
|
|
struct cpuX86cpuid *cpuid;
|
|
int i;
|
|
|
|
for (i = 0; i < model->ncpuid; i++) {
|
|
cpuid = model->cpuid + i;
|
|
if (cpuid->function < CPUX86_EXTENDED) {
|
|
if (cpuid->function >= basic_len)
|
|
basic_len = cpuid->function + 1;
|
|
}
|
|
else if (cpuid->function - CPUX86_EXTENDED >= extended_len)
|
|
extended_len = cpuid->function - CPUX86_EXTENDED + 1;
|
|
}
|
|
|
|
if (VIR_ALLOC(data) < 0
|
|
|| VIR_ALLOC_N(data->x86.basic, basic_len) < 0
|
|
|| VIR_ALLOC_N(data->x86.extended, extended_len) < 0) {
|
|
x86DataFree(data);
|
|
return NULL;
|
|
}
|
|
|
|
data->x86.basic_len = basic_len;
|
|
data->x86.extended_len = extended_len;
|
|
|
|
for (i = 0; i < model->ncpuid; i++) {
|
|
cpuid = x86DataCpuid(data, model->cpuid[i].function);
|
|
*cpuid = model->cpuid[i];
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
|
|
static virCPUDefPtr
|
|
x86DataToCPU(const union cpuData *data,
|
|
const struct x86_model *model,
|
|
const struct x86_map *map)
|
|
{
|
|
virCPUDefPtr cpu;
|
|
union cpuData *tmp = NULL;
|
|
struct cpuX86cpuid *cpuid;
|
|
const struct x86_feature *feature;
|
|
int i;
|
|
|
|
if (VIR_ALLOC(cpu) < 0 ||
|
|
(cpu->model = strdup(model->name)) == NULL ||
|
|
(tmp = x86DataCopy(data)) == NULL)
|
|
goto no_memory;
|
|
|
|
for (i = 0; i < model->ncpuid; i++) {
|
|
x86cpuidClearBits(x86DataCpuid(tmp, model->cpuid[i].function),
|
|
model->cpuid + i);
|
|
}
|
|
|
|
feature = map->features;
|
|
while (feature != NULL) {
|
|
for (i = 0; i < feature->ncpuid; i++) {
|
|
if ((cpuid = x86DataCpuid(tmp, feature->cpuid[i].function))
|
|
&& x86cpuidMatchMasked(cpuid, feature->cpuid + i)) {
|
|
x86cpuidClearBits(cpuid, feature->cpuid + i);
|
|
if (virCPUDefAddFeature(cpu, feature->name,
|
|
VIR_CPU_FEATURE_REQUIRE) < 0)
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
feature = feature->next;
|
|
}
|
|
|
|
cleanup:
|
|
x86DataFree(tmp);
|
|
return cpu;
|
|
|
|
no_memory:
|
|
virReportOOMError();
|
|
error:
|
|
virCPUDefFree(cpu);
|
|
cpu = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
static void
|
|
x86FeatureFree(struct x86_feature *feature)
|
|
{
|
|
if (feature == NULL)
|
|
return;
|
|
|
|
VIR_FREE(feature->name);
|
|
VIR_FREE(feature->cpuid);
|
|
VIR_FREE(feature);
|
|
}
|
|
|
|
|
|
static struct x86_feature *
|
|
x86FeatureFind(const struct x86_map *map,
|
|
const char *name)
|
|
{
|
|
struct x86_feature *feature;
|
|
|
|
feature = map->features;
|
|
while (feature != NULL) {
|
|
if (STREQ(feature->name, name))
|
|
return feature;
|
|
|
|
feature = feature->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
x86FeatureLoad(xmlXPathContextPtr ctxt,
|
|
void *data)
|
|
{
|
|
struct x86_map *map = data;
|
|
xmlNodePtr *nodes = NULL;
|
|
xmlNodePtr ctxt_node = ctxt->node;
|
|
struct x86_feature *feature = NULL;
|
|
int ret = 0;
|
|
int i;
|
|
int n;
|
|
|
|
if (VIR_ALLOC(feature) < 0)
|
|
goto no_memory;
|
|
|
|
feature->name = virXPathString("string(@name)", ctxt);
|
|
if (feature->name == NULL) {
|
|
virCPUReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("Missing CPU feature name"));
|
|
goto ignore;
|
|
}
|
|
|
|
if (x86FeatureFind(map, feature->name)) {
|
|
virCPUReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("CPU feature %s already defined"), feature->name);
|
|
goto ignore;
|
|
}
|
|
|
|
n = virXPathNodeSet("./cpuid", ctxt, &nodes);
|
|
if (n < 0)
|
|
goto ignore;
|
|
|
|
if (n > 0) {
|
|
if (VIR_ALLOC_N(feature->cpuid, n) < 0)
|
|
goto no_memory;
|
|
feature->ncpuid = n;
|
|
}
|
|
|
|
for (i = 0; i < n; i++) {
|
|
struct cpuX86cpuid *cpuid = feature->cpuid + i;
|
|
unsigned long fun, eax, ebx, ecx, edx;
|
|
int ret_fun, ret_eax, ret_ebx, ret_ecx, ret_edx;
|
|
|
|
ctxt->node = nodes[i];
|
|
fun = eax = ebx = ecx = edx = 0;
|
|
ret_fun = virXPathULongHex("string(@function)", ctxt, &fun);
|
|
ret_eax = virXPathULongHex("string(@eax)", ctxt, &eax);
|
|
ret_ebx = virXPathULongHex("string(@ebx)", ctxt, &ebx);
|
|
ret_ecx = virXPathULongHex("string(@ecx)", ctxt, &ecx);
|
|
ret_edx = virXPathULongHex("string(@edx)", ctxt, &edx);
|
|
|
|
if (ret_fun < 0 || ret_eax == -2 || ret_ebx == -2
|
|
|| ret_ecx == -2 || ret_edx == -2) {
|
|
virCPUReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Invalid cpuid[%d] in %s feature"), i, feature->name);
|
|
goto ignore;
|
|
}
|
|
|
|
cpuid->function = fun;
|
|
cpuid->eax = eax;
|
|
cpuid->ebx = ebx;
|
|
cpuid->ecx = ecx;
|
|
cpuid->edx = edx;
|
|
}
|
|
|
|
if (map->features == NULL)
|
|
map->features = feature;
|
|
else {
|
|
feature->next = map->features;
|
|
map->features = feature;
|
|
}
|
|
|
|
out:
|
|
ctxt->node = ctxt_node;
|
|
VIR_FREE(nodes);
|
|
|
|
return ret;
|
|
|
|
no_memory:
|
|
virReportOOMError();
|
|
ret = -1;
|
|
|
|
ignore:
|
|
x86FeatureFree(feature);
|
|
goto out;
|
|
}
|
|
|
|
|
|
static void
|
|
x86ModelFree(struct x86_model *model)
|
|
{
|
|
if (model == NULL)
|
|
return;
|
|
|
|
VIR_FREE(model->name);
|
|
VIR_FREE(model->cpuid);
|
|
VIR_FREE(model);
|
|
}
|
|
|
|
|
|
static struct x86_model *
|
|
x86ModelCopy(const struct x86_model *model)
|
|
{
|
|
struct x86_model *copy;
|
|
int i;
|
|
|
|
if (VIR_ALLOC(copy) < 0
|
|
|| (copy->name = strdup(model->name)) == NULL
|
|
|| VIR_ALLOC_N(copy->cpuid, model->ncpuid) < 0) {
|
|
x86ModelFree(copy);
|
|
return NULL;
|
|
}
|
|
|
|
copy->ncpuid = model->ncpuid;
|
|
for (i = 0; i < model->ncpuid; i++)
|
|
copy->cpuid[i] = model->cpuid[i];
|
|
|
|
return copy;
|
|
}
|
|
|
|
|
|
static int
|
|
x86ModelAddCpuid(struct x86_model *model,
|
|
const struct cpuX86cpuid *cpuid)
|
|
{
|
|
struct cpuX86cpuid *model_cpuid;
|
|
|
|
model_cpuid = x86cpuidFind(model->cpuid, model->ncpuid, cpuid->function);
|
|
|
|
if (model_cpuid != NULL)
|
|
x86cpuidSetBits(model_cpuid, cpuid);
|
|
else {
|
|
if (VIR_REALLOC_N(model->cpuid, model->ncpuid + 1) < 0)
|
|
return -1;
|
|
|
|
model->cpuid[model->ncpuid] = *cpuid;
|
|
model->ncpuid++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
x86ModelSubtract(struct x86_model *model1,
|
|
const struct x86_model *model2)
|
|
{
|
|
int i;
|
|
struct cpuX86cpuid *cpuid;
|
|
|
|
for (i = 0; i < model2->ncpuid; i++) {
|
|
cpuid = x86cpuidFind(model1->cpuid,
|
|
model1->ncpuid,
|
|
model2->cpuid[i].function);
|
|
if (cpuid != NULL)
|
|
x86cpuidClearBits(cpuid, model2->cpuid + i);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
x86ModelIntersect(struct x86_model *model1,
|
|
const struct x86_model *model2)
|
|
{
|
|
int i;
|
|
struct cpuX86cpuid *cpuid;
|
|
|
|
for (i = 0; i < model1->ncpuid; i++) {
|
|
struct cpuX86cpuid *intersection = model1->cpuid + i;
|
|
|
|
cpuid = x86cpuidFind(model2->cpuid,
|
|
model2->ncpuid,
|
|
intersection->function);
|
|
if (cpuid != NULL)
|
|
x86cpuidAndBits(intersection, cpuid);
|
|
else
|
|
x86cpuidClearBits(intersection, intersection);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
x86ModelAdd(struct x86_model *model1,
|
|
const struct x86_model *model2)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < model2->ncpuid; i++) {
|
|
if (x86ModelAddCpuid(model1, model2->cpuid + i))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct x86_model *
|
|
x86ModelFind(const struct x86_map *map,
|
|
const char *name)
|
|
{
|
|
struct x86_model *model;
|
|
|
|
model = map->models;
|
|
while (model != NULL) {
|
|
if (STREQ(model->name, name))
|
|
return model;
|
|
|
|
model = model->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
x86ModelMergeFeature(struct x86_model *model,
|
|
const struct x86_feature *feature)
|
|
{
|
|
int i;
|
|
|
|
if (feature == NULL)
|
|
return 0;
|
|
|
|
for (i = 0; i < feature->ncpuid; i++) {
|
|
if (x86ModelAddCpuid(model, feature->cpuid + i))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct x86_model *
|
|
x86ModelFromCPU(const virCPUDefPtr cpu,
|
|
const struct x86_map *map,
|
|
int policy)
|
|
{
|
|
struct x86_model *model = NULL;
|
|
int i;
|
|
|
|
if (cpu->type == VIR_CPU_TYPE_HOST
|
|
|| policy == VIR_CPU_FEATURE_REQUIRE) {
|
|
if ((model = x86ModelFind(map, cpu->model)) == NULL) {
|
|
virCPUReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown CPU model %s"), cpu->model);
|
|
goto error;
|
|
}
|
|
|
|
if ((model = x86ModelCopy(model)) == NULL)
|
|
goto no_memory;
|
|
}
|
|
else if (VIR_ALLOC(model) < 0)
|
|
goto no_memory;
|
|
|
|
for (i = 0; i < cpu->nfeatures; i++) {
|
|
const struct x86_feature *feature;
|
|
|
|
if (cpu->type == VIR_CPU_TYPE_GUEST
|
|
&& cpu->features[i].policy != policy)
|
|
continue;
|
|
|
|
if ((feature = x86FeatureFind(map, cpu->features[i].name)) == NULL) {
|
|
virCPUReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown CPU feature %s"), cpu->features[i].name);
|
|
goto error;
|
|
}
|
|
|
|
if (x86ModelMergeFeature(model, feature))
|
|
goto no_memory;
|
|
}
|
|
|
|
return model;
|
|
|
|
no_memory:
|
|
virReportOOMError();
|
|
|
|
error:
|
|
x86ModelFree(model);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static enum compare_result
|
|
x86ModelCompare(const struct x86_model *model1,
|
|
const struct x86_model *model2)
|
|
{
|
|
enum compare_result result = EQUAL;
|
|
struct cpuX86cpuid *cpuid1;
|
|
struct cpuX86cpuid *cpuid2;
|
|
int i;
|
|
|
|
for (i = 0; i < model1->ncpuid; i++) {
|
|
enum compare_result match = SUPERSET;
|
|
|
|
cpuid1 = model1->cpuid + i;
|
|
cpuid2 = x86cpuidFind(model2->cpuid,
|
|
model2->ncpuid,
|
|
cpuid1->function);
|
|
if (cpuid2 != NULL) {
|
|
if (x86cpuidMatch(cpuid1, cpuid2))
|
|
continue;
|
|
else if (!x86cpuidMatchMasked(cpuid1, cpuid2))
|
|
match = SUBSET;
|
|
}
|
|
|
|
if (result == EQUAL)
|
|
result = match;
|
|
else if (result != match)
|
|
return UNRELATED;
|
|
}
|
|
|
|
for (i = 0; i < model2->ncpuid; i++) {
|
|
enum compare_result match = SUBSET;
|
|
|
|
cpuid2 = model2->cpuid + i;
|
|
cpuid1 = x86cpuidFind(model1->cpuid,
|
|
model1->ncpuid,
|
|
cpuid2->function);
|
|
if (cpuid1 != NULL) {
|
|
if (x86cpuidMatch(cpuid2, cpuid1))
|
|
continue;
|
|
else if (!x86cpuidMatchMasked(cpuid2, cpuid1))
|
|
match = SUPERSET;
|
|
}
|
|
|
|
if (result == EQUAL)
|
|
result = match;
|
|
else if (result != match)
|
|
return UNRELATED;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
static int
|
|
x86ModelLoad(xmlXPathContextPtr ctxt,
|
|
void *data)
|
|
{
|
|
struct x86_map *map = data;
|
|
xmlNodePtr *nodes = NULL;
|
|
struct x86_model *model = NULL;
|
|
int ret = 0;
|
|
int i;
|
|
int n;
|
|
|
|
if (VIR_ALLOC(model) < 0)
|
|
goto no_memory;
|
|
|
|
model->name = virXPathString("string(@name)", ctxt);
|
|
if (model->name == NULL) {
|
|
virCPUReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("Missing CPU model name"));
|
|
goto ignore;
|
|
}
|
|
|
|
if (virXPathNode("./model", ctxt) != NULL) {
|
|
const struct x86_model *ancestor;
|
|
char *name;
|
|
|
|
name = virXPathString("string(./model/@name)", ctxt);
|
|
if (name == NULL) {
|
|
virCPUReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Missing ancestor's name in CPU model %s"),
|
|
model->name);
|
|
goto ignore;
|
|
}
|
|
|
|
if ((ancestor = x86ModelFind(map, name)) == NULL) {
|
|
virCPUReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Ancestor model %s not found for CPU model %s"),
|
|
name, model->name);
|
|
VIR_FREE(name);
|
|
goto ignore;
|
|
}
|
|
|
|
VIR_FREE(name);
|
|
|
|
if (VIR_ALLOC_N(model->cpuid, ancestor->ncpuid) < 0)
|
|
goto no_memory;
|
|
|
|
model->ncpuid = ancestor->ncpuid;
|
|
memcpy(model->cpuid, ancestor->cpuid,
|
|
sizeof(*model->cpuid) * model->ncpuid);
|
|
}
|
|
|
|
n = virXPathNodeSet("./feature", ctxt, &nodes);
|
|
if (n < 0)
|
|
goto ignore;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
const struct x86_feature *feature;
|
|
char *name;
|
|
|
|
if ((name = virXMLPropString(nodes[i], "name")) == NULL) {
|
|
virCPUReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Missing feature name for CPU model %s"), model->name);
|
|
goto ignore;
|
|
}
|
|
|
|
if ((feature = x86FeatureFind(map, name)) == NULL) {
|
|
virCPUReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Feature %s required by CPU model %s not found"),
|
|
name, model->name);
|
|
VIR_FREE(name);
|
|
goto ignore;
|
|
}
|
|
VIR_FREE(name);
|
|
|
|
if (x86ModelMergeFeature(model, feature))
|
|
goto no_memory;
|
|
}
|
|
|
|
if (map->models == NULL)
|
|
map->models = model;
|
|
else {
|
|
model->next = map->models;
|
|
map->models = model;
|
|
}
|
|
|
|
out:
|
|
VIR_FREE(nodes);
|
|
return ret;
|
|
|
|
no_memory:
|
|
virReportOOMError();
|
|
ret = -1;
|
|
|
|
ignore:
|
|
x86ModelFree(model);
|
|
goto out;
|
|
}
|
|
|
|
|
|
static void
|
|
x86MapFree(struct x86_map *map)
|
|
{
|
|
if (map == NULL)
|
|
return;
|
|
|
|
while (map->features != NULL) {
|
|
struct x86_feature *feature = map->features;
|
|
map->features = feature->next;
|
|
x86FeatureFree(feature);
|
|
}
|
|
|
|
while (map->models != NULL) {
|
|
struct x86_model *model = map->models;
|
|
map->models = model->next;
|
|
x86ModelFree(model);
|
|
}
|
|
|
|
VIR_FREE(map);
|
|
}
|
|
|
|
|
|
static struct x86_map *
|
|
x86LoadMap(void)
|
|
{
|
|
struct x86_map *map;
|
|
|
|
if (VIR_ALLOC(map) < 0) {
|
|
virReportOOMError();
|
|
return NULL;
|
|
}
|
|
|
|
if (cpuMapLoad("x86",
|
|
x86FeatureLoad, map,
|
|
x86ModelLoad, map) < 0)
|
|
goto error;
|
|
|
|
return map;
|
|
|
|
error:
|
|
x86MapFree(map);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static virCPUCompareResult
|
|
x86Compute(virCPUDefPtr host,
|
|
virCPUDefPtr cpu,
|
|
union cpuData **guest)
|
|
{
|
|
struct cpuX86cpuid cpuid_zero = { 0, 0, 0, 0, 0 };
|
|
struct x86_map *map = NULL;
|
|
struct x86_model *host_model = NULL;
|
|
struct x86_model *cpu_force = NULL;
|
|
struct x86_model *cpu_require = NULL;
|
|
struct x86_model *cpu_optional = NULL;
|
|
struct x86_model *cpu_disable = NULL;
|
|
struct x86_model *cpu_forbid = NULL;
|
|
struct x86_model *diff = NULL;
|
|
struct x86_model *guest_model = NULL;
|
|
virCPUCompareResult ret;
|
|
enum compare_result result;
|
|
int i;
|
|
|
|
if (cpu->arch != NULL) {
|
|
bool found = false;
|
|
|
|
for (i = 0; i < ARRAY_CARDINALITY(archs); i++) {
|
|
if (STREQ(archs[i], cpu->arch)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
VIR_DEBUG("CPU arch %s does not match host arch", cpu->arch);
|
|
return VIR_CPU_COMPARE_INCOMPATIBLE;
|
|
}
|
|
}
|
|
|
|
if ((map = x86LoadMap()) == NULL)
|
|
goto error;
|
|
|
|
if (!(host_model = x86ModelFromCPU(host, map, 0)))
|
|
goto error;
|
|
|
|
if (!(cpu_force = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_FORCE)))
|
|
goto error;
|
|
|
|
if (!(cpu_require = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_REQUIRE)))
|
|
goto error;
|
|
|
|
if (!(cpu_optional = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_OPTIONAL)))
|
|
goto error;
|
|
|
|
if (!(cpu_disable = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_DISABLE)))
|
|
goto error;
|
|
|
|
if (!(cpu_forbid = x86ModelFromCPU(cpu, map, VIR_CPU_FEATURE_FORBID)))
|
|
goto error;
|
|
|
|
x86ModelSubtract(cpu_require, cpu_disable);
|
|
|
|
if ((diff = x86ModelCopy(host_model)) == NULL)
|
|
goto no_memory;
|
|
|
|
x86ModelSubtract(diff, cpu_require);
|
|
x86ModelSubtract(diff, cpu_optional);
|
|
x86ModelSubtract(diff, cpu_force);
|
|
|
|
for (i = 0; i < cpu_forbid->ncpuid; i++) {
|
|
const struct cpuX86cpuid *cpuid1;
|
|
const struct cpuX86cpuid *cpuid2;
|
|
|
|
cpuid1 = cpu_forbid->cpuid + i;
|
|
cpuid2 = x86cpuidFind(host_model->cpuid,
|
|
host_model->ncpuid,
|
|
cpuid1->function);
|
|
|
|
if (cpuid2 != NULL && x86cpuidMatchAny(cpuid2, cpuid1)) {
|
|
VIR_DEBUG("Host CPU provides forbidden features in CPUID function 0x%x",
|
|
cpuid1->function);
|
|
ret = VIR_CPU_COMPARE_INCOMPATIBLE;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
result = x86ModelCompare(host_model, cpu_require);
|
|
if (result == SUBSET || result == UNRELATED) {
|
|
VIR_DEBUG0("Host CPU does not provide all required features");
|
|
ret = VIR_CPU_COMPARE_INCOMPATIBLE;
|
|
goto out;
|
|
}
|
|
|
|
ret = VIR_CPU_COMPARE_IDENTICAL;
|
|
|
|
for (i = 0; i < host_model->ncpuid; i++) {
|
|
if (!x86cpuidMatch(host_model->cpuid + i, &cpuid_zero)) {
|
|
ret = VIR_CPU_COMPARE_SUPERSET;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret == VIR_CPU_COMPARE_SUPERSET
|
|
&& cpu->type == VIR_CPU_TYPE_GUEST
|
|
&& cpu->match == VIR_CPU_MATCH_STRICT) {
|
|
VIR_DEBUG0("Host CPU does not strictly match guest CPU");
|
|
ret = VIR_CPU_COMPARE_INCOMPATIBLE;
|
|
goto out;
|
|
}
|
|
|
|
if (guest != NULL) {
|
|
if ((guest_model = x86ModelCopy(host_model)) == NULL)
|
|
goto no_memory;
|
|
|
|
if (cpu->type == VIR_CPU_TYPE_GUEST
|
|
&& cpu->match == VIR_CPU_MATCH_EXACT)
|
|
x86ModelSubtract(guest_model, diff);
|
|
|
|
if (x86ModelAdd(guest_model, cpu_force))
|
|
goto no_memory;
|
|
|
|
x86ModelSubtract(guest_model, cpu_disable);
|
|
|
|
if ((*guest = x86DataFromModel(guest_model)) == NULL)
|
|
goto no_memory;
|
|
}
|
|
|
|
out:
|
|
x86MapFree(map);
|
|
x86ModelFree(host_model);
|
|
x86ModelFree(diff);
|
|
x86ModelFree(cpu_force);
|
|
x86ModelFree(cpu_require);
|
|
x86ModelFree(cpu_optional);
|
|
x86ModelFree(cpu_disable);
|
|
x86ModelFree(cpu_forbid);
|
|
x86ModelFree(guest_model);
|
|
|
|
return ret;
|
|
|
|
no_memory:
|
|
virReportOOMError();
|
|
|
|
error:
|
|
ret = VIR_CPU_COMPARE_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
|
|
static virCPUCompareResult
|
|
x86Compare(virCPUDefPtr host,
|
|
virCPUDefPtr cpu)
|
|
{
|
|
return x86Compute(host, cpu, NULL);
|
|
}
|
|
|
|
|
|
static virCPUCompareResult
|
|
x86GuestData(virCPUDefPtr host,
|
|
virCPUDefPtr guest,
|
|
union cpuData **data)
|
|
{
|
|
return x86Compute(host, guest, data);
|
|
}
|
|
|
|
|
|
static int
|
|
x86Decode(virCPUDefPtr cpu,
|
|
const union cpuData *data,
|
|
const char **models,
|
|
unsigned int nmodels)
|
|
{
|
|
int ret = -1;
|
|
struct x86_map *map;
|
|
const struct x86_model *candidate;
|
|
virCPUDefPtr cpuCandidate;
|
|
virCPUDefPtr cpuModel = NULL;
|
|
struct cpuX86cpuid *cpuid;
|
|
int i;
|
|
|
|
if (data == NULL || (map = x86LoadMap()) == NULL)
|
|
return -1;
|
|
|
|
candidate = map->models;
|
|
while (candidate != NULL) {
|
|
bool allowed = (models == NULL);
|
|
|
|
for (i = 0; i < candidate->ncpuid; i++) {
|
|
cpuid = x86DataCpuid(data, candidate->cpuid[i].function);
|
|
if (cpuid == NULL
|
|
|| !x86cpuidMatchMasked(cpuid, candidate->cpuid + i))
|
|
goto next;
|
|
}
|
|
|
|
for (i = 0; i < nmodels; i++) {
|
|
if (models && models[i] && STREQ(models[i], candidate->name)) {
|
|
allowed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!allowed) {
|
|
VIR_DEBUG("CPU model %s not allowed by hypervisor; ignoring",
|
|
candidate->name);
|
|
goto next;
|
|
}
|
|
|
|
if (!(cpuCandidate = x86DataToCPU(data, candidate, map)))
|
|
goto out;
|
|
|
|
if (cpuModel == NULL
|
|
|| cpuModel->nfeatures > cpuCandidate->nfeatures) {
|
|
virCPUDefFree(cpuModel);
|
|
cpuModel = cpuCandidate;
|
|
} else
|
|
virCPUDefFree(cpuCandidate);
|
|
|
|
next:
|
|
candidate = candidate->next;
|
|
}
|
|
|
|
if (cpuModel == NULL) {
|
|
virCPUReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("Cannot find suitable CPU model for given data"));
|
|
goto out;
|
|
}
|
|
|
|
cpu->model = cpuModel->model;
|
|
cpu->nfeatures = cpuModel->nfeatures;
|
|
cpu->features = cpuModel->features;
|
|
VIR_FREE(cpuModel);
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
x86MapFree(map);
|
|
virCPUDefFree(cpuModel);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static union cpuData *
|
|
x86EncodePolicy(const virCPUDefPtr cpu,
|
|
const struct x86_map *map,
|
|
enum virCPUFeaturePolicy policy)
|
|
{
|
|
struct x86_model *model;
|
|
union cpuData *data = NULL;
|
|
|
|
if (!(model = x86ModelFromCPU(cpu, map, policy)))
|
|
return NULL;
|
|
|
|
if (!(data = x86DataFromModel(model)))
|
|
virReportOOMError();
|
|
|
|
x86ModelFree(model);
|
|
|
|
return data;
|
|
}
|
|
|
|
|
|
static int
|
|
x86Encode(const virCPUDefPtr cpu,
|
|
union cpuData **forced,
|
|
union cpuData **required,
|
|
union cpuData **optional,
|
|
union cpuData **disabled,
|
|
union cpuData **forbidden)
|
|
{
|
|
struct x86_map *map = NULL;
|
|
union cpuData *data_forced = NULL;
|
|
union cpuData *data_required = NULL;
|
|
union cpuData *data_optional = NULL;
|
|
union cpuData *data_disabled = NULL;
|
|
union cpuData *data_forbidden = NULL;
|
|
int ret = -1;
|
|
|
|
if ((map = x86LoadMap()) == NULL)
|
|
goto error;
|
|
|
|
if (forced) {
|
|
data_forced = x86EncodePolicy(cpu, map, VIR_CPU_FEATURE_FORCE);
|
|
if (!data_forced)
|
|
goto error;
|
|
}
|
|
|
|
if (required) {
|
|
data_required = x86EncodePolicy(cpu, map, VIR_CPU_FEATURE_REQUIRE);
|
|
if (!data_required)
|
|
goto error;
|
|
}
|
|
|
|
if (optional) {
|
|
data_optional = x86EncodePolicy(cpu, map, VIR_CPU_FEATURE_OPTIONAL);
|
|
if (!data_optional)
|
|
goto error;
|
|
}
|
|
|
|
if (disabled) {
|
|
data_disabled = x86EncodePolicy(cpu, map, VIR_CPU_FEATURE_DISABLE);
|
|
if (!data_disabled)
|
|
goto error;
|
|
}
|
|
|
|
if (forbidden) {
|
|
data_forbidden = x86EncodePolicy(cpu, map, VIR_CPU_FEATURE_FORBID);
|
|
if (!data_forbidden)
|
|
goto error;
|
|
}
|
|
|
|
if (forced)
|
|
*forced = data_forced;
|
|
if (required)
|
|
*required = data_required;
|
|
if (optional)
|
|
*optional = data_optional;
|
|
if (disabled)
|
|
*disabled = data_disabled;
|
|
if (forbidden)
|
|
*forbidden = data_forbidden;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
x86MapFree(map);
|
|
|
|
return ret;
|
|
|
|
error:
|
|
x86DataFree(data_forced);
|
|
x86DataFree(data_required);
|
|
x86DataFree(data_optional);
|
|
x86DataFree(data_disabled);
|
|
x86DataFree(data_forbidden);
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
#if HAVE_CPUID
|
|
static inline void
|
|
cpuidCall(struct cpuX86cpuid *cpuid)
|
|
{
|
|
# if __x86_64__
|
|
asm("cpuid"
|
|
: "=a" (cpuid->eax),
|
|
"=b" (cpuid->ebx),
|
|
"=c" (cpuid->ecx),
|
|
"=d" (cpuid->edx)
|
|
: "a" (cpuid->function));
|
|
# else
|
|
/* we need to avoid direct use of ebx for CPUID output as it is used
|
|
* for global offset table on i386 with -fPIC
|
|
*/
|
|
asm("push %%ebx;"
|
|
"cpuid;"
|
|
"mov %%ebx, %1;"
|
|
"pop %%ebx;"
|
|
: "=a" (cpuid->eax),
|
|
"=r" (cpuid->ebx),
|
|
"=c" (cpuid->ecx),
|
|
"=d" (cpuid->edx)
|
|
: "a" (cpuid->function)
|
|
: "cc");
|
|
# endif
|
|
}
|
|
|
|
|
|
static int
|
|
cpuidSet(uint32_t base, struct cpuX86cpuid **set)
|
|
{
|
|
uint32_t max;
|
|
uint32_t i;
|
|
struct cpuX86cpuid cpuid = { base, 0, 0, 0, 0 };
|
|
|
|
cpuidCall(&cpuid);
|
|
max = cpuid.eax - base;
|
|
|
|
if (VIR_ALLOC_N(*set, max + 1) < 0) {
|
|
virReportOOMError();
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i <= max; i++) {
|
|
cpuid.function = base | i;
|
|
cpuidCall(&cpuid);
|
|
(*set)[i] = cpuid;
|
|
}
|
|
|
|
return max + 1;
|
|
}
|
|
|
|
|
|
static union cpuData *
|
|
x86NodeData(void)
|
|
{
|
|
union cpuData *data;
|
|
|
|
if (VIR_ALLOC(data) < 0) {
|
|
virReportOOMError();
|
|
return NULL;
|
|
}
|
|
|
|
data->x86.basic_len = cpuidSet(CPUX86_BASIC, &data->x86.basic);
|
|
if (data->x86.basic_len < 0)
|
|
goto error;
|
|
|
|
data->x86.extended_len = cpuidSet(CPUX86_EXTENDED, &data->x86.extended);
|
|
if (data->x86.extended_len < 0)
|
|
goto error;
|
|
|
|
return data;
|
|
|
|
error:
|
|
x86DataFree(data);
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
static virCPUDefPtr
|
|
x86Baseline(virCPUDefPtr *cpus,
|
|
unsigned int ncpus,
|
|
const char **models,
|
|
unsigned int nmodels)
|
|
{
|
|
struct x86_map *map = NULL;
|
|
struct x86_model *base_model = NULL;
|
|
union cpuData *data = NULL;
|
|
virCPUDefPtr cpu = NULL;
|
|
unsigned int i;
|
|
|
|
if (!(map = x86LoadMap()))
|
|
goto error;
|
|
|
|
if (!(base_model = x86ModelFromCPU(cpus[0], map, 0)))
|
|
goto error;
|
|
|
|
if (VIR_ALLOC(cpu) < 0 ||
|
|
!(cpu->arch = strdup(cpus[0]->arch)))
|
|
goto no_memory;
|
|
|
|
for (i = 1; i < ncpus; i++) {
|
|
struct x86_model *model;
|
|
if (!(model = x86ModelFromCPU(cpus[i], map, 0)))
|
|
goto error;
|
|
|
|
x86ModelIntersect(base_model, model);
|
|
x86ModelFree(model);
|
|
}
|
|
|
|
if (!(data = x86DataFromModel(base_model)))
|
|
goto no_memory;
|
|
|
|
if (x86Decode(cpu, data, models, nmodels) < 0)
|
|
goto error;
|
|
|
|
cleanup:
|
|
x86DataFree(data);
|
|
x86ModelFree(base_model);
|
|
x86MapFree(map);
|
|
|
|
return cpu;
|
|
|
|
no_memory:
|
|
virReportOOMError();
|
|
error:
|
|
virCPUDefFree(cpu);
|
|
cpu = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
struct cpuArchDriver cpuDriverX86 = {
|
|
.name = "x86",
|
|
.arch = archs,
|
|
.narch = ARRAY_CARDINALITY(archs),
|
|
.compare = x86Compare,
|
|
.decode = x86Decode,
|
|
.encode = x86Encode,
|
|
.free = x86DataFree,
|
|
#if HAVE_CPUID
|
|
.nodeData = x86NodeData,
|
|
#else
|
|
.nodeData = NULL,
|
|
#endif
|
|
.guestData = x86GuestData,
|
|
.baseline = x86Baseline,
|
|
};
|