diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 2ee20420d9bdf9572424e519cb8bc2cdd5782bd5..36bf9a273f51d114a6533fdb98b33b5d17dc9494 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: + * + */ +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; + 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,6 +1027,15 @@ 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"); @@ -1563,6 +1767,98 @@ 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 (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 +1906,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 +1966,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 +2066,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 +3712,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 +4212,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 248677a8cdeddb9ef53e5e8b01e95a9b19c20349..e958850b300ef7c3e8709f36c290ad254b1c580a 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 80ffce6d5c26711074dcff84ba88f7f931dc78be..242045670baa54f3b7eb30d22d4f7237aafb0ce2 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,28 @@ out: return ret; } +static int +qemuCPUCompare(virConnectPtr conn, + const char *xmlDesc, + unsigned int flags ATTRIBUTE_UNUSED) +{ + 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,7 +7946,7 @@ static virDriver qemuDriver = { qemuIsSecure, qemuDomainIsActive, qemuDomainIsPersistent, - NULL, /* cpuCompare */ + qemuCPUCompare, /* cpuCompare */ };