Parse the domain XML with TPM support. Convert the strings from QEMU's QMP TPM commands into capability flags. Signed-off-by: Stefan Berger --- docs/schemas/domaincommon.rng | 43 ++++++ src/conf/domain_conf.c | 268 ++++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 34 +++++ src/libvirt_private.syms | 5 src/qemu/qemu_capabilities.c | 59 +++++++++ 5 files changed, 409 insertions(+) Index: libvirt/docs/schemas/domaincommon.rng =================================================================== --- libvirt.orig/docs/schemas/domaincommon.rng +++ libvirt/docs/schemas/domaincommon.rng @@ -2814,6 +2814,48 @@ + + + + + + + tpm-tis + + + + + + + + + + + + + + + + passthrough + + + + + + + + + + + + + + + + + + + @@ -3111,6 +3153,7 @@ + Index: libvirt/src/conf/domain_conf.c =================================================================== --- libvirt.orig/src/conf/domain_conf.c +++ libvirt/src/conf/domain_conf.c @@ -709,6 +709,13 @@ VIR_ENUM_IMPL(virDomainRNGBackend, "random", "egd"); +VIR_ENUM_IMPL(virDomainTPMModel, VIR_DOMAIN_TPM_MODEL_LAST, + "tpm-tis") + +VIR_ENUM_IMPL(virDomainTPMBackend, VIR_DOMAIN_TPM_TYPE_LAST, + "passthrough") + + #define VIR_DOMAIN_XML_WRITE_FLAGS VIR_DOMAIN_XML_SECURE #define VIR_DOMAIN_XML_READ_FLAGS VIR_DOMAIN_XML_INACTIVE @@ -1515,6 +1522,24 @@ void virDomainHostdevDefClear(virDomainH } } +void virDomainTPMDefFree(virDomainTPMDefPtr def) +{ + if (!def) + return; + + switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + VIR_FREE(def->data.passthrough.path); + VIR_FREE(def->data.passthrough.cancel_path); + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + + virDomainDeviceInfoClear(&def->info); + VIR_FREE(def); +} + void virDomainHostdevDefFree(virDomainHostdevDefPtr def) { if (!def) @@ -1776,6 +1801,8 @@ void virDomainDefFree(virDomainDefPtr de virDomainRNGDefFree(def->rng); + virDomainTPMDefFree(def->tpm); + VIR_FREE(def->os.type); VIR_FREE(def->os.machine); VIR_FREE(def->os.init); @@ -6312,6 +6339,192 @@ error: goto cleanup; } +/* + * Check whether the given base path, e.g., /sys/class/misc/tpm0/device, + * is the sysfs entry of a TPM. A TPM sysfs entry should be uniquely + * recognizable by the file entries 'pcrs' and 'cancel'. + * Upon success 'true' is returned and the basebath buffer has '/cancel' + * appended. + */ +static bool +virDomainTPMCheckSysfsCancel(char *basepath, size_t bufsz) +{ + char *path = NULL; + struct stat statbuf; + + if (virAsprintf(&path, "%s/pcrs", basepath) < 0) { + virReportOOMError(); + goto error; + } + if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) + goto error; + + VIR_FREE(path); + + if (virAsprintf(&path, "%s/cancel", basepath) < 0) { + virReportOOMError(); + goto error; + } + + if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) + goto error; + + if (!virStrncpy(basepath, path, strlen(path) + 1, bufsz)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Basepath buffer is too small")); + goto error; + } + + VIR_FREE(path); + + return true; + +error: + VIR_FREE(path); + return false; +} + + +static char * +virDomainTPMFindCancelPath(void) +{ + unsigned int idx; + int len; + DIR *pnp_dir; + char path[100], *p; + struct dirent entry, *result; + bool found = false; + + snprintf(path, sizeof(path), "/sys/class/misc"); + pnp_dir = opendir(path); + if (pnp_dir != NULL) { + while (readdir_r(pnp_dir, &entry, &result) == 0 && + result != NULL) { + if (sscanf(entry.d_name, "tpm%u%n", &idx, &len) < 1 || + len <= strlen("tpm") || + len != strlen(entry.d_name)) { + continue; + } + snprintf(path, sizeof(path), "/sys/class/misc/%s/device", + entry.d_name); + if (!virDomainTPMCheckSysfsCancel(path, sizeof(path))) { + continue; + } + + found = true; + break; + } + closedir(pnp_dir); + } + + if (found) { + if (!(p = strdup(path))) + virReportOOMError(); + return p; + } + + return NULL; +} + + +/* Parse the XML definition for a TPM device + * + * The XML looks like this: + * + * + * + * + * + * + * + */ +static virDomainTPMDefPtr +virDomainTPMDefParseXML(const xmlNodePtr node, + xmlXPathContextPtr ctxt, + unsigned int flags) +{ + char *type = NULL; + char *path = NULL; + char *model = NULL; + char *backend = NULL; + virDomainTPMDefPtr def; + xmlNodePtr save = ctxt->node; + xmlNodePtr *backends = NULL; + int nbackends; + + if (VIR_ALLOC(def) < 0) { + virReportOOMError(); + return NULL; + } + + model = virXMLPropString(node, "model"); + if (model != NULL && + (int)(def->model = virDomainTPMModelTypeFromString(model)) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("Unknown TPM frontend model '%s'"), model); + goto error; + } else { + def->model = VIR_DOMAIN_TPM_MODEL_TIS; + } + + ctxt->node = node; + + if ((nbackends = virXPathNodeSet("./backend", ctxt, &backends)) < 0) + goto error; + + if (nbackends > 1) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("only one TPM backend is supported")); + goto error; + } + + if (!(backend = virXMLPropString(backends[0], "type"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing TPM device backend type")); + goto error; + } + + if ((int)(def->type = virDomainTPMBackendTypeFromString(backend)) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Unknown TPM backend type '%s'"), + backend); + goto error; + } + + switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + path = virXPathString("string(./backend/device/@path)", ctxt); + if (!path && !(path = strdup(VIR_DOMAIN_TPM_DEFAULT_DEVICE))) { + virReportOOMError(); + goto error; + } + def->data.passthrough.path = path; + path = NULL; + /* cancel_path is read-only */ + def->data.passthrough.cancel_path = virDomainTPMFindCancelPath(); + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + goto error; + } + + if (virDomainDeviceInfoParseXML(node, NULL, &def->info, flags) < 0) + goto error; + +cleanup: + VIR_FREE(type); + VIR_FREE(path); + VIR_FREE(model); + VIR_FREE(backend); + VIR_FREE(backends); + ctxt->node = save; + return def; + +error: + virDomainTPMDefFree(def); + def = NULL; + goto cleanup; +} + /* Parse the XML definition for an input device */ static virDomainInputDefPtr virDomainInputDefParseXML(const char *ostype, @@ -10659,6 +10872,23 @@ virDomainDefParseXML(virCapsPtr caps, goto error; VIR_FREE(nodes); } + VIR_FREE(nodes); + + /* Parse the TPM devices */ + if ((n = virXPathNodeSet("./devices/tpm", ctxt, &nodes)) < 0) + goto error; + + if (n > 1) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("only a single TPM device is supported")); + goto error; + } + + if (n > 0) { + if (!(def->tpm = virDomainTPMDefParseXML(nodes[0], ctxt, flags))) + goto error; + } + VIR_FREE(nodes); /* analysis of the hub devices */ if ((n = virXPathNodeSet("./devices/hub", ctxt, &nodes)) < 0) { @@ -13589,6 +13819,39 @@ virDomainSoundCodecDefFormat(virBufferPt } static int +virDomainTPMDefFormat(virBufferPtr buf, + virDomainTPMDefPtr def, + unsigned int flags) +{ + virBufferAsprintf(buf, " \n", + virDomainTPMModelTypeToString(def->model)); + + virBufferAsprintf(buf, " \n", + virDomainTPMBackendTypeToString(def->type)); + + switch (def->type) { + case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: + virBufferEscapeString(buf, " \n", + def->data.passthrough.path); + break; + case VIR_DOMAIN_TPM_TYPE_LAST: + break; + } + + virBufferAddLit(buf, " \n"); + + if (virDomainDeviceInfoIsSet(&def->info, flags)) { + if (virDomainDeviceInfoFormat(buf, &def->info, flags) < 0) + return -1; + } + + virBufferAddLit(buf, " \n"); + + return 0; +} + + +static int virDomainSoundDefFormat(virBufferPtr buf, virDomainSoundDefPtr def, unsigned int flags) @@ -14905,6 +15168,11 @@ virDomainDefFormatInternal(virDomainDefP virDomainInputDefFormat(buf, def->inputs[n], flags) < 0) goto error; + if (def->tpm) { + if (virDomainTPMDefFormat(buf, def->tpm, flags) < 0) + goto error; + } + if (def->ngraphics > 0) { /* If graphics is enabled, add the implicit mouse */ virDomainInputDef autoInput = { Index: libvirt/src/conf/domain_conf.h =================================================================== --- libvirt.orig/src/conf/domain_conf.h +++ libvirt/src/conf/domain_conf.h @@ -1063,6 +1063,34 @@ struct _virDomainHubDef { virDomainDeviceInfo info; }; +enum virDomainTPMModel { + VIR_DOMAIN_TPM_MODEL_TIS, + + VIR_DOMAIN_TPM_MODEL_LAST +}; + +enum virDomainTPMBackendType { + VIR_DOMAIN_TPM_TYPE_PASSTHROUGH, + + VIR_DOMAIN_TPM_TYPE_LAST +}; + +# define VIR_DOMAIN_TPM_DEFAULT_DEVICE "/dev/tpm0" + +typedef struct _virDomainTPMDef virDomainTPMDef; +typedef virDomainTPMDef *virDomainTPMDefPtr; +struct _virDomainTPMDef { + enum virDomainTPMBackendType type; + virDomainDeviceInfo info; + enum virDomainTPMModel model; + union { + struct { + char *path; + char *cancel_path; + } passthrough; + } data; +}; + enum virDomainInputType { VIR_DOMAIN_INPUT_TYPE_MOUSE, VIR_DOMAIN_INPUT_TYPE_TABLET, @@ -1866,6 +1894,7 @@ struct _virDomainDef { /* Only 1 */ virDomainWatchdogDefPtr watchdog; virDomainMemballoonDefPtr memballoon; + virDomainTPMDefPtr tpm; virCPUDefPtr cpu; virSysinfoDefPtr sysinfo; virDomainRedirFilterDefPtr redirfilter; @@ -1988,6 +2017,9 @@ int virDomainDeviceInfoCopy(virDomainDev void virDomainDeviceInfoClear(virDomainDeviceInfoPtr info); void virDomainDefClearPCIAddresses(virDomainDefPtr def); void virDomainDefClearDeviceAliases(virDomainDefPtr def); +void virDomainTPMDefFree(virDomainTPMDefPtr def); +char *virDomainTPMGetPathCopy(virDomainTPMDefPtr def); +char *virDomainTPMGetCancelPath(virDomainTPMDefPtr def); typedef int (*virDomainDeviceInfoCallback)(virDomainDefPtr def, virDomainDeviceDefPtr dev, @@ -2346,6 +2378,8 @@ VIR_ENUM_DECL(virDomainNumatuneMemPlacem VIR_ENUM_DECL(virDomainHyperv) VIR_ENUM_DECL(virDomainRNGModel) VIR_ENUM_DECL(virDomainRNGBackend) +VIR_ENUM_DECL(virDomainTPMModel) +VIR_ENUM_DECL(virDomainTPMBackend) /* from libvirt.h */ VIR_ENUM_DECL(virDomainState) VIR_ENUM_DECL(virDomainNostateReason) Index: libvirt/src/libvirt_private.syms =================================================================== --- libvirt.orig/src/libvirt_private.syms +++ libvirt/src/libvirt_private.syms @@ -323,6 +323,11 @@ virDomainTimerTickpolicyTypeFromString; virDomainTimerTickpolicyTypeToString; virDomainTimerTrackTypeFromString; virDomainTimerTrackTypeToString; +virDomainTPMBackendTypeFromString; +virDomainTPMBackendTypeToString; +virDomainTPMDefFree; +virDomainTPMModelTypeFromString; +virDomainTPMModelTypeToString; virDomainVcpuPinAdd; virDomainVcpuPinDefArrayFree; virDomainVcpuPinDefCopy; Index: libvirt/src/qemu/qemu_capabilities.c =================================================================== --- libvirt.orig/src/qemu/qemu_capabilities.c +++ libvirt/src/qemu/qemu_capabilities.c @@ -2110,6 +2110,63 @@ virQEMUCapsProbeQMPCPUDefinitions(virQEM static int +virQEMUCapsProbeQMPTPM(virQEMUCapsPtr qemuCaps, + qemuMonitorPtr mon) +{ + int nentries, i; + char **entries = NULL; + struct typeToCaps { + int type; + enum virQEMUCapsFlags caps; + }; + const struct typeToCaps tpmTypesToCaps[] = { + { + .type = VIR_DOMAIN_TPM_TYPE_PASSTHROUGH, + .caps = QEMU_CAPS_DEVICE_TPM_PASSTHROUGH, + }, + }; + const struct typeToCaps tpmModelsToCaps[] = { + { + .type = VIR_DOMAIN_TPM_MODEL_TIS, + .caps = QEMU_CAPS_DEVICE_TPM_TIS, + }, + }; + + if ((nentries = qemuMonitorGetTPMModels(mon, &entries)) < 0) + return -1; + + if (nentries > 0) { + for (i = 0; i < ARRAY_CARDINALITY(tpmModelsToCaps); i++) { + const char *needle = virDomainTPMModelTypeToString( + tpmModelsToCaps[i].type); + if (virStrArrayHasString(entries, nentries, needle)) + virQEMUCapsSet(qemuCaps, tpmModelsToCaps[i].caps); + } + for (i = 0; i < nentries; i++) + VIR_FREE(entries[i]); + } + VIR_FREE(entries); + + if ((nentries = qemuMonitorGetTPMTypes(mon, &entries)) < 0) + return -1; + + if (nentries > 0) { + for (i = 0; i < ARRAY_CARDINALITY(tpmTypesToCaps); i++) { + const char *needle = virDomainTPMBackendTypeToString( + tpmTypesToCaps[i].type); + if (virStrArrayHasString(entries, nentries, needle)) + virQEMUCapsSet(qemuCaps, tpmTypesToCaps[i].caps); + } + for (i = 0; i < nentries; i++) + VIR_FREE(entries[i]); + } + VIR_FREE(entries); + + return 0; +} + + +static int virQEMUCapsProbeQMPKVMState(virQEMUCapsPtr qemuCaps, qemuMonitorPtr mon) { @@ -2458,6 +2515,8 @@ virQEMUCapsInitQMP(virQEMUCapsPtr qemuCa goto cleanup; if (virQEMUCapsProbeQMPKVMState(qemuCaps, mon) < 0) goto cleanup; + if (virQEMUCapsProbeQMPTPM(qemuCaps, mon) < 0) + goto cleanup; ret = 0;