Signed-off-by: Jiri Denemark <jdenemar(a)redhat.com>
---
src/qemu/qemu_conf.c | 401 +++++++++++++++++++++++++++++++++++++++++++++---
src/qemu/qemu_conf.h | 7 +
src/qemu/qemu_driver.c | 43 ++++--
3 files changed, 421 insertions(+), 30 deletions(-)
diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c
index 2ee2042..303a1bf 100644
--- a/src/qemu/qemu_conf.c
+++ b/src/qemu/qemu_conf.c
@@ -52,6 +52,7 @@
#include "nodeinfo.h"
#include "logging.h"
#include "network.h"
+#include "cpu/cpu.h"
#define VIR_FROM_THIS VIR_FROM_QEMU
@@ -608,6 +609,151 @@ qemudGetOldMachines(const char *ostype,
return 0;
}
+
+typedef int
+(*qemudParseCPUModels)(const char *output,
+ unsigned int *retcount,
+ const char ***retcpus);
+
+/* Format:
+ * <arch> <model>
+ */
+static int
+qemudParseX86Models(const char *output,
+ unsigned int *retcount,
+ const char ***retcpus)
+{
+ const char *p = output;
+ const char *next;
+ unsigned int count = 0;
+ const char **cpus = NULL;
+ int i;
+
+ do {
+ const char *t;
+
+ if ((next = strchr(p, '\n')))
+ next++;
+
+ if (!(t = strchr(p, ' ')) || (next && t >= next))
+ continue;
+
+ if (!STRPREFIX(p, "x86"))
+ continue;
+
+ p = t;
+ while (*p == ' ')
+ p++;
+
+ if (*p == '\0' || *p == '\n')
+ continue;
+
+ if (retcpus) {
+ if (VIR_REALLOC_N(cpus, count + 1) < 0)
+ goto error;
+
+ if (next)
+ cpus[count] = strndup(p, next - p - 1);
+ else
+ cpus[count] = strdup(p);
+
+ if (!cpus[count])
+ goto error;
+ }
+ count++;
+ } while ((p = next));
+
+ if (retcount)
+ *retcount = count;
+ if (retcpus)
+ *retcpus = cpus;
+
+ return 0;
+
+error:
+ if (cpus) {
+ for (i = 0; i < count; i++)
+ VIR_FREE(cpus[i]);
+ }
+ VIR_FREE(cpus);
+
+ return -1;
+}
+
+
+int
+qemudProbeCPUModels(const char *qemu,
+ const char *arch,
+ unsigned int *count,
+ const char ***cpus)
+{
+ const char *const qemuarg[] = { qemu, "-cpu", "?", NULL };
+ const char *const qemuenv[] = { "LC_ALL=C", NULL };
+ enum { MAX_MACHINES_OUTPUT_SIZE = 1024*4 };
+ char *output = NULL;
+ int newstdout = -1;
+ int ret = -1;
+ pid_t child;
+ int status;
+ int len;
+ qemudParseCPUModels parse;
+
+ if (count)
+ *count = 0;
+ if (cpus)
+ *cpus = NULL;
+
+ if (STREQ(arch, "i686") || STREQ(arch, "x86_64"))
+ parse = qemudParseX86Models;
+ else {
+ VIR_DEBUG(_("don't know how to parse %s CPU models"), arch);
+ return 0;
+ }
+
+ if (virExec(NULL, qemuarg, qemuenv, NULL,
+ &child, -1, &newstdout, NULL, VIR_EXEC_CLEAR_CAPS) < 0)
+ return -1;
+
+ len = virFileReadLimFD(newstdout, MAX_MACHINES_OUTPUT_SIZE, &output);
+ if (len < 0) {
+ virReportSystemError(NULL, errno, "%s",
+ _("Unable to read QEMU supported CPU models"));
+ goto cleanup;
+ }
+
+ if (parse(output, count, cpus) < 0) {
+ virReportOOMError(NULL);
+ goto cleanup;
+ }
+
+ ret = 0;
+
+cleanup:
+ VIR_FREE(output);
+ if (close(newstdout) < 0)
+ ret = -1;
+
+rewait:
+ if (waitpid(child, &status, 0) != child) {
+ if (errno == EINTR)
+ goto rewait;
+
+ VIR_ERROR(_("Unexpected exit status from qemu %d pid %lu"),
+ WEXITSTATUS(status), (unsigned long)child);
+ ret = -1;
+ }
+ /* Check & log unexpected exit status, but don't fail,
+ * as there's really no need to throw an error if we did
+ * actually read a valid version number above */
+ if (WEXITSTATUS(status) != 0) {
+ VIR_WARN(_("Unexpected exit status '%d', qemu probably
failed"),
+ WEXITSTATUS(status));
+ }
+
+ return ret;
+}
+
+
static int
qemudCapsInitGuest(virCapsPtr caps,
virCapsPtr old_caps,
@@ -624,6 +770,7 @@ qemudCapsInitGuest(virCapsPtr caps,
virCapsGuestMachinePtr *machines = NULL;
int nmachines = 0;
struct stat st;
+ unsigned int ncpus;
/* Check for existance of base emulator, or alternate base
* which can be used with magic cpu choice
@@ -725,6 +872,11 @@ qemudCapsInitGuest(virCapsPtr caps,
guest->arch.defaultInfo.emulator_mtime = binary_mtime;
+ if (qemudProbeCPUModels(binary, info->arch, &ncpus, NULL) == 0
+ && ncpus > 0
+ && !virCapabilitiesAddGuestFeature(guest, "cpuselection", 1,
0))
+ return -1;
+
if (hvm) {
if (virCapabilitiesAddGuestDomain(guest,
"qemu",
@@ -808,6 +960,49 @@ qemudCapsInitGuest(virCapsPtr caps,
return 0;
}
+
+static int
+qemudCapsInitCPU(virCapsPtr caps,
+ const char *arch)
+{
+ virCPUDefPtr cpu = NULL;
+ union cpuData *data = NULL;
+ virNodeInfo nodeinfo;
+ int ret = -1;
+
+ if (VIR_ALLOC(cpu) < 0
+ || !(cpu->arch = strdup(arch))) {
+ virReportOOMError(NULL);
+ goto error;
+ }
+
+ if (nodeGetInfo(NULL, &nodeinfo))
+ goto error;
+
+ cpu->type = VIR_CPU_TYPE_HOST;
+ cpu->sockets = nodeinfo.sockets * nodeinfo.nodes;
+ cpu->cores = nodeinfo.cores;
+ cpu->threads = nodeinfo.threads;
+
+ if (!(data = cpuNodeData(NULL, arch))
+ || cpuDecode(NULL, cpu, data, 0, NULL) < 0)
+ goto error;
+
+ caps->host.cpu = cpu;
+
+ ret = 0;
+
+cleanup:
+ cpuDataFree(NULL, arch, data);
+
+ return ret;
+
+error:
+ virCPUDefFree(cpu);
+ goto cleanup;
+}
+
+
virCapsPtr qemudCapsInit(virCapsPtr old_caps) {
struct utsname utsname;
virCapsPtr caps;
@@ -832,8 +1027,17 @@ virCapsPtr qemudCapsInit(virCapsPtr old_caps) {
VIR_WARN0("Failed to query host NUMA topology, disabling NUMA
capabilities");
}
+ if (old_caps == NULL || old_caps->host.cpu == NULL) {
+ if (qemudCapsInitCPU(caps, utsname.machine) < 0)
+ VIR_WARN0("Failed to get host CPU");
+ }
+ else {
+ caps->host.cpu = old_caps->host.cpu;
+ old_caps->host.cpu = NULL;
+ }
+
virCapabilitiesAddHostMigrateTransport(caps,
- "tcp");
+ "Tcp");
/* First the pure HVM guests */
for (i = 0 ; i < ARRAY_CARDINALITY(arch_info_hvm) ; i++)
@@ -1563,6 +1767,99 @@ static void qemudBuildCommandLineChrDevStr(virDomainChrDefPtr dev,
}
}
+
+static int
+qemudBuildCommandLineCPU(virConnectPtr conn,
+ const struct qemud_driver *driver,
+ const virDomainDefPtr def,
+ const char *emulator,
+ const struct utsname *ut,
+ char **opt)
+{
+ const virCPUDefPtr host = driver->caps->host.cpu;
+ virCPUDefPtr guest = NULL;
+ unsigned int ncpus;
+ const char **cpus = NULL;
+ union cpuData *data = NULL;
+ int ret = -1;
+ virBuffer buf = VIR_BUFFER_INITIALIZER;
+ int i;
+
+ if (qemudProbeCPUModels(emulator, ut->machine, &ncpus, &cpus) < 0)
+ goto cleanup;
+
+ if (ncpus > 0 && host && def->cpu &&
def->cpu->model) {
+ virCPUCompareResult cmp;
+
+ cmp = cpuGuestData(conn, host, def->cpu, &data);
+ switch (cmp) {
+ case VIR_CPU_COMPARE_INCOMPATIBLE:
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ "%s", _("guest CPU is not compatible with host
CPU"));
+ /* fall through */
+ case VIR_CPU_COMPARE_ERROR:
+ goto cleanup;
+
+ default:
+ break;
+ }
+
+ if (VIR_ALLOC(guest) < 0 || !(guest->arch = strdup(ut->machine)))
+ goto no_memory;
+
+ if (qemudProbeCPUModels(emulator, ut->machine, &ncpus, &cpus) < 0
+ || cpuDecode(conn, guest, data, ncpus, cpus) < 0)
+ goto cleanup;
+
+ virBufferVSprintf(&buf, "%s", guest->model);
+ for (i = 0; i < guest->nfeatures; i++)
+ virBufferVSprintf(&buf, ",+%s", guest->features[i].name);
+ }
+ else {
+ /*
+ * Need to force a 32-bit guest CPU type if
+ *
+ * 1. guest OS is i686
+ * 2. host OS is x86_64
+ * 3. emulator is qemu-kvm or kvm
+ *
+ * Or
+ *
+ * 1. guest OS is i686
+ * 2. emulator is qemu-system-x86_64
+ */
+ if (STREQ(def->os.arch, "i686") &&
+ ((STREQ(ut->machine, "x86_64") &&
+ strstr(emulator, "kvm")) ||
+ strstr(emulator, "x86_64")))
+ virBufferAddLit(&buf, "qemu32");
+ }
+
+ if (virBufferError(&buf))
+ goto no_memory;
+
+ *opt = virBufferContentAndReset(&buf);
+
+ ret = 0;
+
+cleanup:
+ virCPUDefFree(guest);
+ cpuDataFree(conn, ut->machine, data);
+
+ if (cpus) {
+ for (i = 0; i < ncpus; i++)
+ VIR_FREE(cpus[i]);
+ VIR_FREE(cpus);
+ }
+
+ return ret;
+
+no_memory:
+ virReportOOMError(conn);
+ goto cleanup;
+}
+
+
#define QEMU_SERIAL_PARAM_ACCEPTED_CHARS \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
@@ -1610,7 +1907,7 @@ int qemudBuildCommandLine(virConnectPtr conn,
const char *emulator;
char uuid[VIR_UUID_STRING_BUFLEN];
char domid[50];
- const char *cpu = NULL;
+ char *cpu;
uname_normalize(&ut);
@@ -1670,24 +1967,6 @@ int qemudBuildCommandLine(virConnectPtr conn,
def->virtType == VIR_DOMAIN_VIRT_KVM)
enableKVM = 1;
- /*
- * Need to force a 32-bit guest CPU type if
- *
- * 1. guest OS is i686
- * 2. host OS is x86_64
- * 3. emulator is qemu-kvm or kvm
- *
- * Or
- *
- * 1. guest OS is i686
- * 2. emulator is qemu-system-x86_64
- */
- if (STREQ(def->os.arch, "i686") &&
- ((STREQ(ut.machine, "x86_64") &&
- strstr(emulator, "kvm")) ||
- strstr(emulator, "x86_64")))
- cpu = "qemu32";
-
#define ADD_ARG_SPACE \
do { \
if (qargc == qarga) { \
@@ -1788,9 +2067,14 @@ int qemudBuildCommandLine(virConnectPtr conn,
ADD_ARG_LIT("-M");
ADD_ARG_LIT(def->os.machine);
}
+
+ if (qemudBuildCommandLineCPU(conn, driver, def, emulator, &ut, &cpu) < 0)
+ goto error;
+
if (cpu) {
ADD_ARG_LIT("-cpu");
ADD_ARG_LIT(cpu);
+ VIR_FREE(cpu);
}
if (disableKQEMU)
@@ -3429,6 +3713,79 @@ error:
return NULL;
}
+
+static int
+qemuParseCommandLineCPU(virConnectPtr conn,
+ virDomainDefPtr dom,
+ const char *val)
+{
+ virCPUDefPtr cpu;
+ const char *p = val;
+ const char *next;
+
+ if (VIR_ALLOC(cpu) < 0)
+ goto no_memory;
+
+ cpu->type = VIR_CPU_TYPE_GUEST;
+
+ do {
+ if (*p == '\0' || *p == ',')
+ goto syntax;
+
+ if ((next = strchr(p, ',')))
+ next++;
+
+ if (!cpu->model) {
+ if (next)
+ cpu->model = strndup(p, next - p - 1);
+ else
+ cpu->model = strdup(p);
+
+ if (!cpu->model)
+ goto no_memory;
+ }
+ else if (*p == '+' || *p == '-') {
+ char *feature;
+ int policy;
+ int ret;
+
+ if (*p == '+')
+ policy = VIR_CPU_FEATURE_REQUIRE;
+ else
+ policy = VIR_CPU_FEATURE_DISABLE;
+
+ p++;
+ if (*p == '\0' || *p == ',')
+ goto syntax;
+
+ if (next)
+ feature = strndup(p, next - p - 1);
+ else
+ feature = strdup(p);
+
+ ret = virCPUDefAddFeature(conn, cpu, feature, policy);
+ VIR_FREE(feature);
+ if (ret < 0)
+ goto error;
+ }
+ } while ((p = next));
+
+ dom->cpu = cpu;
+ return 0;
+
+syntax:
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("unknown CPU syntax '%s'"), val);
+ goto error;
+
+no_memory:
+ virReportOOMError(conn);
+error:
+ virCPUDefFree(cpu);
+ return -1;
+}
+
+
/*
* Analyse the env and argv settings and reconstruct a
* virDomainDefPtr representing these settings as closely
@@ -3856,6 +4213,10 @@ virDomainDefPtr qemuParseCommandLine(virConnectPtr conn,
_("unknown video adapter type '%s'"),
val);
goto error;
}
+ } else if (STREQ(arg, "-cpu")) {
+ WANT_VALUE();
+ if (qemuParseCommandLineCPU(conn, def, val) < 0)
+ goto error;
} else if (STREQ(arg, "-domid")) {
WANT_VALUE();
/* ignore, generted on the fly */
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h
index 248677a..e958850 100644
--- a/src/qemu/qemu_conf.h
+++ b/src/qemu/qemu_conf.h
@@ -37,6 +37,8 @@
#include "security/security_driver.h"
#include "cgroup.h"
#include "pci.h"
+#include "cpu_conf.h"
+#include "driver.h"
#define qemudDebug(fmt, ...) do {} while(0)
@@ -203,6 +205,11 @@ int qemudProbeMachineTypes (const char *binary,
virCapsGuestMachinePtr **machines,
int *nmachines);
+int qemudProbeCPUModels (const char *qemu,
+ const char *arch,
+ unsigned int *count,
+ const char ***cpus);
+
int qemudCanonicalizeMachine (struct qemud_driver *driver,
virDomainDefPtr def);
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 9ef6c35..ceabff2 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -73,6 +73,7 @@
#include "cgroup.h"
#include "libvirt_internal.h"
#include "xml.h"
+#include "cpu/cpu.h"
#define VIR_FROM_THIS VIR_FROM_QEMU
@@ -2844,24 +2845,19 @@ static int qemudGetMaxVCPUs(virConnectPtr conn, const char *type)
{
static char *qemudGetCapabilities(virConnectPtr conn) {
struct qemud_driver *driver = conn->privateData;
- virCapsPtr caps;
+ virCapsPtr caps = NULL;
char *xml = NULL;
qemuDriverLock(driver);
- if ((caps = qemudCapsInit(qemu_driver->caps)) == NULL) {
- virReportOOMError(conn);
- goto cleanup;
- }
+ if ((caps = qemudCapsInit(qemu_driver->caps)) == NULL)
+ goto no_memory;
caps->privateDataAllocFunc = qemuDomainObjPrivateAlloc;
caps->privateDataFreeFunc = qemuDomainObjPrivateFree;
if (qemu_driver->securityDriver &&
- qemudSecurityCapsInit(qemu_driver->securityDriver, caps) < 0) {
- virCapabilitiesFree(caps);
- virReportOOMError(conn);
- goto cleanup;
- }
+ qemudSecurityCapsInit(qemu_driver->securityDriver, caps) < 0)
+ goto no_memory;
virCapabilitiesFree(qemu_driver->caps);
qemu_driver->caps = caps;
@@ -2873,6 +2869,11 @@ cleanup:
qemuDriverUnlock(driver);
return xml;
+
+no_memory:
+ virCapabilitiesFree(caps);
+ virReportOOMError(conn);
+ goto cleanup;
}
@@ -7849,6 +7850,27 @@ out:
return ret;
}
+static int
+qemuCPUCompare(virConnectPtr conn,
+ const char *xmlDesc)
+{
+ struct qemud_driver *driver = conn->privateData;
+ int ret = VIR_CPU_COMPARE_ERROR;
+
+ qemuDriverLock(driver);
+
+ if (!driver->caps || !driver->caps->host.cpu) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT,
+ "%s", _("cannot get host CPU
capabilities"));
+ }
+ else
+ ret = cpuCompareXML(conn, driver->caps->host.cpu, xmlDesc);
+
+ qemuDriverUnlock(driver);
+
+ return ret;
+}
+
static virDriver qemuDriver = {
VIR_DRV_QEMU,
"QEMU",
@@ -7923,6 +7945,7 @@ static virDriver qemuDriver = {
qemuIsSecure,
qemuDomainIsActive,
qemuDomainIsPersistent,
+ qemuCPUCompare, /* cpuCompare */
};
--
1.6.5.6