Parse the domain XML with TPM support.
Convert the strings from QEMU's QMP TPM commands into
capability flags.
Signed-off-by: Stefan Berger <stefanb(a)linux.vnet.ibm.com>
---
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 @@
<text/>
</element>
</define>
+
+ <define name="tpm">
+ <element name="tpm">
+ <optional>
+ <attribute name="model">
+ <choice>
+ <value>tpm-tis</value>
+ </choice>
+ </attribute>
+ </optional>
+ <ref name="tpm-backend"/>
+ <optional>
+ <ref name="alias"/>
+ </optional>
+ </element>
+ </define>
+
+ <define name="tpm-backend">
+ <element name="backend">
+ <choice>
+ <group>
+ <attribute name="type">
+ <value>passthrough</value>
+ </attribute>
+ <ref name="tpm-passthrough-device"/>
+ </group>
+ </choice>
+ </element>
+ </define>
+
+ <define name="tpm-passthrough-device">
+ <optional>
+ <element name="device">
+ <optional>
+ <attribute name="path">
+ <ref name="filePath"/>
+ </attribute>
+ </optional>
+ </element>
+ </optional>
+ </define>
+
<define name="input">
<element name="input">
<attribute name="type">
@@ -3111,6 +3153,7 @@
<ref name="redirdev"/>
<ref name="redirfilter"/>
<ref name="rng"/>
+ <ref name="tpm"/>
</choice>
</zeroOrMore>
<optional>
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:
+ *
+ * <tpm model='tpm-tis'>
+ * <backend type='passthrough'>
+ * <device path='/dev/tpm0'/>
+ * </backend>
+ * </tpm>
+ *
+ */
+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, " <tpm model='%s'>\n",
+ virDomainTPMModelTypeToString(def->model));
+
+ virBufferAsprintf(buf, " <backend type='%s'>\n",
+ virDomainTPMBackendTypeToString(def->type));
+
+ switch (def->type) {
+ case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
+ virBufferEscapeString(buf, " <device
path='%s'/>\n",
+ def->data.passthrough.path);
+ break;
+ case VIR_DOMAIN_TPM_TYPE_LAST:
+ break;
+ }
+
+ virBufferAddLit(buf, " </backend>\n");
+
+ if (virDomainDeviceInfoIsSet(&def->info, flags)) {
+ if (virDomainDeviceInfoFormat(buf, &def->info, flags) < 0)
+ return -1;
+ }
+
+ virBufferAddLit(buf, " </tpm>\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;