mirror of
https://git.yoctoproject.org/poky
synced 2026-05-08 17:19:20 +00:00
qemu: support virtual TPM
This enables the use of swtpm (from meta-security) as a virtual TPM in qemu. These patches extend the existing support in qemu for TPM passthrough so that a swtpm daemon can be accessed via CUSE (character device in user space). To use this: - add the meta-security layer including the swtpm enhancements for qemu - bitbake swtpm-native - create a TPM instance and initialize it with: $ mkdir -p my-machine/myvtpm0 $ tmp-glibc/sysroots/x86_64-linux/usr/bin/swtpm_setup_oe.sh --tpm-state my-machine/myvtpm0 --createek Starting vTPM manufacturing as root:root @ Fri 20 Jan 2017 08:56:18 AM CET TPM is listening on TCP port 52167. Successfully created EK. Successfully authored TPM state. Ending vTPM manufacturing @ Fri 20 Jan 2017 08:56:19 AM CET - run swtpm *before each runqemu invocation* (it shuts down after use) and do it as root (required to set up the /dev/vtpm0 CUSE device): $ sudo sh -c 'PATH=`pwd`/tmp-glibc/sysroots/x86_64-linux/usr/bin/:`pwd`/tmp-glibc/sysroots/x86_64-linux/usr/sbin/:$PATH; export TPM_PATH=`pwd`/my-machine/myvtpm0; swtpm_cuse -n vtpm0' && sudo chmod a+rw /dev/vtpm0 - run qemu: $ runqemu 'qemuparams=-tpmdev cuse-tpm,id=tpm0,path=/dev/vtpm0 -device tpm-tis,tpmdev=tpm0' ... The guest kernel has to have TPM support enabled, which can be done with: KERNEL_FEATURES_append = " features/tpm/tpm.scc" (From OE-Core rev: 1264d26fa251ac11a9069f3e602dec6be9d8b9ba) Signed-off-by: Patrick Ohly <patrick.ohly@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
committed by
Richard Purdie
parent
b16192c938
commit
81b7a9832f
@@ -0,0 +1,870 @@
|
||||
From 8737eef18f39ed087fd911d0a0886e8174d0468c Mon Sep 17 00:00:00 2001
|
||||
From: Stefan Berger <stefanb@linux.vnet.ibm.com>
|
||||
Date: Sat, 31 Dec 2016 11:23:32 -0500
|
||||
Subject: [PATCH 1/4] Provide support for the CUSE TPM
|
||||
|
||||
Rather than integrating TPM functionality into QEMU directly
|
||||
using the TPM emulation of libtpms, we now integrate an external
|
||||
emulated TPM device. This device is expected to implement a Linux
|
||||
CUSE interface (CUSE = character device in userspace).
|
||||
|
||||
QEMU talks to the CUSE TPM using much functionality of the
|
||||
passthrough driver. For example, the TPM commands and responses
|
||||
are sent to the CUSE TPM using the read()/write() interface.
|
||||
However, some out-of-band control needs to be done using the CUSE
|
||||
TPM's ioctls. The CUSE TPM currently defines and implements 15
|
||||
different ioctls for controlling certain life-cycle aspects of
|
||||
the emulated TPM. The ioctls can be regarded as a replacement for
|
||||
direct function calls to a TPM emulator if the TPM were to be
|
||||
directly integrated into QEMU.
|
||||
|
||||
One of the ioctls allows to get a bitmask of supported capabilities.
|
||||
Each returned bit indicates which capabilities have been implemented.
|
||||
An include file defining the various ioctls is added to QEMU.
|
||||
|
||||
The CUSE TPM and associated tools can be found here:
|
||||
|
||||
https://github.com/stefanberger/swtpm
|
||||
|
||||
(please use the latest version)
|
||||
|
||||
To use the external CUSE TPM, the CUSE TPM should be started as follows:
|
||||
|
||||
/usr/bin/swtpm_ioctl -s /dev/vtpm-test
|
||||
|
||||
/usr/bin/swtpm_cuse -n vtpm-test
|
||||
|
||||
QEMU can then be started using the following parameters:
|
||||
|
||||
qemu-system-x86_64 \
|
||||
[...] \
|
||||
-tpmdev cuse-tpm,id=tpm0,cancel-path=/dev/null,path=/dev/vtpm-test \
|
||||
-device tpm-tis,id=tpm0,tpmdev=tpm0 \
|
||||
[...]
|
||||
|
||||
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
|
||||
Cc: Eric Blake <eblake@redhat.com>
|
||||
|
||||
Conflicts:
|
||||
docs/qmp-commands.txt
|
||||
|
||||
Patch cherry-picked from https://github.com/stefanberger/qemu-tpm, branch v2.8.0+tpm,
|
||||
commit 27d6cd856d5a14061955df7a93ee490697a7a174. Applied cleanly except for
|
||||
docs/qmp-commands.txt which did not exist yet in qemu 2.7.
|
||||
|
||||
Upstream-Status: Pending [https://lists.nongnu.org/archive/html/qemu-devel/2016-06/msg00252.html]
|
||||
Signed-off-by: Patrick Ohly <patrick.ohly@intel.com>
|
||||
---
|
||||
hmp.c | 6 ++
|
||||
hw/tpm/tpm_int.h | 1 +
|
||||
hw/tpm/tpm_ioctl.h | 215 +++++++++++++++++++++++++++++++++++++
|
||||
hw/tpm/tpm_passthrough.c | 274 +++++++++++++++++++++++++++++++++++++++++++++--
|
||||
qapi-schema.json | 18 +++-
|
||||
qemu-options.hx | 21 +++-
|
||||
tpm.c | 11 +-
|
||||
7 files changed, 529 insertions(+), 17 deletions(-)
|
||||
create mode 100644 hw/tpm/tpm_ioctl.h
|
||||
|
||||
diff --git a/hmp.c b/hmp.c
|
||||
index cc2056e9e2..277b45ef5a 100644
|
||||
--- a/hmp.c
|
||||
+++ b/hmp.c
|
||||
@@ -883,6 +883,12 @@ void hmp_info_tpm(Monitor *mon, const QDict *qdict)
|
||||
tpo->has_cancel_path ? ",cancel-path=" : "",
|
||||
tpo->has_cancel_path ? tpo->cancel_path : "");
|
||||
break;
|
||||
+ case TPM_TYPE_OPTIONS_KIND_CUSE_TPM:
|
||||
+ tpo = ti->options->u.passthrough.data;
|
||||
+ monitor_printf(mon, "%s%s",
|
||||
+ tpo->has_path ? ",path=" : "",
|
||||
+ tpo->has_path ? tpo->path : "");
|
||||
+ break;
|
||||
case TPM_TYPE_OPTIONS_KIND__MAX:
|
||||
break;
|
||||
}
|
||||
diff --git a/hw/tpm/tpm_int.h b/hw/tpm/tpm_int.h
|
||||
index f2f285b3cc..6b2c9c953a 100644
|
||||
--- a/hw/tpm/tpm_int.h
|
||||
+++ b/hw/tpm/tpm_int.h
|
||||
@@ -61,6 +61,7 @@ struct tpm_resp_hdr {
|
||||
#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5
|
||||
#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6
|
||||
|
||||
+#define TPM_SUCCESS 0
|
||||
#define TPM_FAIL 9
|
||||
|
||||
#define TPM_ORD_ContinueSelfTest 0x53
|
||||
diff --git a/hw/tpm/tpm_ioctl.h b/hw/tpm/tpm_ioctl.h
|
||||
new file mode 100644
|
||||
index 0000000000..a341e15741
|
||||
--- /dev/null
|
||||
+++ b/hw/tpm/tpm_ioctl.h
|
||||
@@ -0,0 +1,215 @@
|
||||
+/*
|
||||
+ * tpm_ioctl.h
|
||||
+ *
|
||||
+ * (c) Copyright IBM Corporation 2014, 2015.
|
||||
+ *
|
||||
+ * This file is licensed under the terms of the 3-clause BSD license
|
||||
+ */
|
||||
+#ifndef _TPM_IOCTL_H_
|
||||
+#define _TPM_IOCTL_H_
|
||||
+
|
||||
+#include <stdint.h>
|
||||
+#include <sys/uio.h>
|
||||
+#include <sys/types.h>
|
||||
+#include <sys/ioctl.h>
|
||||
+
|
||||
+/*
|
||||
+ * Every response from a command involving a TPM command execution must hold
|
||||
+ * the ptm_res as the first element.
|
||||
+ * ptm_res corresponds to the error code of a command executed by the TPM.
|
||||
+ */
|
||||
+
|
||||
+typedef uint32_t ptm_res;
|
||||
+
|
||||
+/* PTM_GET_TPMESTABLISHED: get the establishment bit */
|
||||
+struct ptm_est {
|
||||
+ union {
|
||||
+ struct {
|
||||
+ ptm_res tpm_result;
|
||||
+ unsigned char bit; /* TPM established bit */
|
||||
+ } resp; /* response */
|
||||
+ } u;
|
||||
+};
|
||||
+
|
||||
+/* PTM_RESET_TPMESTABLISHED: reset establishment bit */
|
||||
+struct ptm_reset_est {
|
||||
+ union {
|
||||
+ struct {
|
||||
+ uint8_t loc; /* locality to use */
|
||||
+ } req; /* request */
|
||||
+ struct {
|
||||
+ ptm_res tpm_result;
|
||||
+ } resp; /* response */
|
||||
+ } u;
|
||||
+};
|
||||
+
|
||||
+/* PTM_INIT */
|
||||
+struct ptm_init {
|
||||
+ union {
|
||||
+ struct {
|
||||
+ uint32_t init_flags; /* see definitions below */
|
||||
+ } req; /* request */
|
||||
+ struct {
|
||||
+ ptm_res tpm_result;
|
||||
+ } resp; /* response */
|
||||
+ } u;
|
||||
+};
|
||||
+
|
||||
+/* above init_flags */
|
||||
+#define PTM_INIT_FLAG_DELETE_VOLATILE (1 << 0)
|
||||
+ /* delete volatile state file after reading it */
|
||||
+
|
||||
+/* PTM_SET_LOCALITY */
|
||||
+struct ptm_loc {
|
||||
+ union {
|
||||
+ struct {
|
||||
+ uint8_t loc; /* locality to set */
|
||||
+ } req; /* request */
|
||||
+ struct {
|
||||
+ ptm_res tpm_result;
|
||||
+ } resp; /* response */
|
||||
+ } u;
|
||||
+};
|
||||
+
|
||||
+/* PTM_HASH_DATA: hash given data */
|
||||
+struct ptm_hdata {
|
||||
+ union {
|
||||
+ struct {
|
||||
+ uint32_t length;
|
||||
+ uint8_t data[4096];
|
||||
+ } req; /* request */
|
||||
+ struct {
|
||||
+ ptm_res tpm_result;
|
||||
+ } resp; /* response */
|
||||
+ } u;
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * size of the TPM state blob to transfer; x86_64 can handle 8k,
|
||||
+ * ppc64le only ~7k; keep the response below a 4k page size
|
||||
+ */
|
||||
+#define PTM_STATE_BLOB_SIZE (3 * 1024)
|
||||
+
|
||||
+/*
|
||||
+ * The following is the data structure to get state blobs from the TPM.
|
||||
+ * If the size of the state blob exceeds the PTM_STATE_BLOB_SIZE, multiple reads
|
||||
+ * with this ioctl and with adjusted offset are necessary. All bytes
|
||||
+ * must be transferred and the transfer is done once the last byte has been
|
||||
+ * returned.
|
||||
+ * It is possible to use the read() interface for reading the data; however,
|
||||
+ * the first bytes of the state blob will be part of the response to the ioctl();
|
||||
+ * a subsequent read() is only necessary if the total length (totlength) exceeds
|
||||
+ * the number of received bytes. seek() is not supported.
|
||||
+ */
|
||||
+struct ptm_getstate {
|
||||
+ union {
|
||||
+ struct {
|
||||
+ uint32_t state_flags; /* may be: PTM_STATE_FLAG_DECRYPTED */
|
||||
+ uint32_t type; /* which blob to pull */
|
||||
+ uint32_t offset; /* offset from where to read */
|
||||
+ } req; /* request */
|
||||
+ struct {
|
||||
+ ptm_res tpm_result;
|
||||
+ uint32_t state_flags; /* may be: PTM_STATE_FLAG_ENCRYPTED */
|
||||
+ uint32_t totlength; /* total length that will be transferred */
|
||||
+ uint32_t length; /* number of bytes in following buffer */
|
||||
+ uint8_t data[PTM_STATE_BLOB_SIZE];
|
||||
+ } resp; /* response */
|
||||
+ } u;
|
||||
+};
|
||||
+
|
||||
+/* TPM state blob types */
|
||||
+#define PTM_BLOB_TYPE_PERMANENT 1
|
||||
+#define PTM_BLOB_TYPE_VOLATILE 2
|
||||
+#define PTM_BLOB_TYPE_SAVESTATE 3
|
||||
+
|
||||
+/* state_flags above : */
|
||||
+#define PTM_STATE_FLAG_DECRYPTED 1 /* on input: get decrypted state */
|
||||
+#define PTM_STATE_FLAG_ENCRYPTED 2 /* on output: state is encrypted */
|
||||
+
|
||||
+/*
|
||||
+ * The following is the data structure to set state blobs in the TPM.
|
||||
+ * If the size of the state blob exceeds the PTM_STATE_BLOB_SIZE, multiple
|
||||
+ * 'writes' using this ioctl are necessary. The last packet is indicated
|
||||
+ * by the length being smaller than the PTM_STATE_BLOB_SIZE.
|
||||
+ * The very first packet may have a length indicator of '0' enabling
|
||||
+ * a write() with all the bytes from a buffer. If the write() interface
|
||||
+ * is used, a final ioctl with a non-full buffer must be made to indicate
|
||||
+ * that all data were transferred (a write with 0 bytes would not work).
|
||||
+ */
|
||||
+struct ptm_setstate {
|
||||
+ union {
|
||||
+ struct {
|
||||
+ uint32_t state_flags; /* may be PTM_STATE_FLAG_ENCRYPTED */
|
||||
+ uint32_t type; /* which blob to set */
|
||||
+ uint32_t length; /* length of the data;
|
||||
+ use 0 on the first packet to
|
||||
+ transfer using write() */
|
||||
+ uint8_t data[PTM_STATE_BLOB_SIZE];
|
||||
+ } req; /* request */
|
||||
+ struct {
|
||||
+ ptm_res tpm_result;
|
||||
+ } resp; /* response */
|
||||
+ } u;
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * PTM_GET_CONFIG: Data structure to get runtime configuration information
|
||||
+ * such as which keys are applied.
|
||||
+ */
|
||||
+struct ptm_getconfig {
|
||||
+ union {
|
||||
+ struct {
|
||||
+ ptm_res tpm_result;
|
||||
+ uint32_t flags;
|
||||
+ } resp; /* response */
|
||||
+ } u;
|
||||
+};
|
||||
+
|
||||
+#define PTM_CONFIG_FLAG_FILE_KEY 0x1
|
||||
+#define PTM_CONFIG_FLAG_MIGRATION_KEY 0x2
|
||||
+
|
||||
+
|
||||
+typedef uint64_t ptm_cap;
|
||||
+typedef struct ptm_est ptm_est;
|
||||
+typedef struct ptm_reset_est ptm_reset_est;
|
||||
+typedef struct ptm_loc ptm_loc;
|
||||
+typedef struct ptm_hdata ptm_hdata;
|
||||
+typedef struct ptm_init ptm_init;
|
||||
+typedef struct ptm_getstate ptm_getstate;
|
||||
+typedef struct ptm_setstate ptm_setstate;
|
||||
+typedef struct ptm_getconfig ptm_getconfig;
|
||||
+
|
||||
+/* capability flags returned by PTM_GET_CAPABILITY */
|
||||
+#define PTM_CAP_INIT (1)
|
||||
+#define PTM_CAP_SHUTDOWN (1<<1)
|
||||
+#define PTM_CAP_GET_TPMESTABLISHED (1<<2)
|
||||
+#define PTM_CAP_SET_LOCALITY (1<<3)
|
||||
+#define PTM_CAP_HASHING (1<<4)
|
||||
+#define PTM_CAP_CANCEL_TPM_CMD (1<<5)
|
||||
+#define PTM_CAP_STORE_VOLATILE (1<<6)
|
||||
+#define PTM_CAP_RESET_TPMESTABLISHED (1<<7)
|
||||
+#define PTM_CAP_GET_STATEBLOB (1<<8)
|
||||
+#define PTM_CAP_SET_STATEBLOB (1<<9)
|
||||
+#define PTM_CAP_STOP (1<<10)
|
||||
+#define PTM_CAP_GET_CONFIG (1<<11)
|
||||
+
|
||||
+enum {
|
||||
+ PTM_GET_CAPABILITY = _IOR('P', 0, ptm_cap),
|
||||
+ PTM_INIT = _IOWR('P', 1, ptm_init),
|
||||
+ PTM_SHUTDOWN = _IOR('P', 2, ptm_res),
|
||||
+ PTM_GET_TPMESTABLISHED = _IOR('P', 3, ptm_est),
|
||||
+ PTM_SET_LOCALITY = _IOWR('P', 4, ptm_loc),
|
||||
+ PTM_HASH_START = _IOR('P', 5, ptm_res),
|
||||
+ PTM_HASH_DATA = _IOWR('P', 6, ptm_hdata),
|
||||
+ PTM_HASH_END = _IOR('P', 7, ptm_res),
|
||||
+ PTM_CANCEL_TPM_CMD = _IOR('P', 8, ptm_res),
|
||||
+ PTM_STORE_VOLATILE = _IOR('P', 9, ptm_res),
|
||||
+ PTM_RESET_TPMESTABLISHED = _IOWR('P', 10, ptm_reset_est),
|
||||
+ PTM_GET_STATEBLOB = _IOWR('P', 11, ptm_getstate),
|
||||
+ PTM_SET_STATEBLOB = _IOWR('P', 12, ptm_setstate),
|
||||
+ PTM_STOP = _IOR('P', 13, ptm_res),
|
||||
+ PTM_GET_CONFIG = _IOR('P', 14, ptm_getconfig),
|
||||
+};
|
||||
+
|
||||
+#endif /* _TPM_IOCTL_H */
|
||||
diff --git a/hw/tpm/tpm_passthrough.c b/hw/tpm/tpm_passthrough.c
|
||||
index e88c0d20bc..050f2ba850 100644
|
||||
--- a/hw/tpm/tpm_passthrough.c
|
||||
+++ b/hw/tpm/tpm_passthrough.c
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "sysemu/tpm_backend_int.h"
|
||||
#include "tpm_tis.h"
|
||||
#include "tpm_util.h"
|
||||
+#include "tpm_ioctl.h"
|
||||
|
||||
#define DEBUG_TPM 0
|
||||
|
||||
@@ -45,6 +46,7 @@
|
||||
#define TYPE_TPM_PASSTHROUGH "tpm-passthrough"
|
||||
#define TPM_PASSTHROUGH(obj) \
|
||||
OBJECT_CHECK(TPMPassthruState, (obj), TYPE_TPM_PASSTHROUGH)
|
||||
+#define TYPE_TPM_CUSE "tpm-cuse"
|
||||
|
||||
static const TPMDriverOps tpm_passthrough_driver;
|
||||
|
||||
@@ -71,12 +73,18 @@ struct TPMPassthruState {
|
||||
bool had_startup_error;
|
||||
|
||||
TPMVersion tpm_version;
|
||||
+ ptm_cap cuse_cap; /* capabilities of the CUSE TPM */
|
||||
+ uint8_t cur_locty_number; /* last set locality */
|
||||
};
|
||||
|
||||
typedef struct TPMPassthruState TPMPassthruState;
|
||||
|
||||
#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
|
||||
|
||||
+#define TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt) (tpm_pt->cuse_cap != 0)
|
||||
+
|
||||
+#define TPM_CUSE_IMPLEMENTS_ALL(S, cap) (((S)->cuse_cap & (cap)) == (cap))
|
||||
+
|
||||
/* functions */
|
||||
|
||||
static void tpm_passthrough_cancel_cmd(TPMBackend *tb);
|
||||
@@ -148,7 +156,28 @@ static bool tpm_passthrough_is_selftest(const uint8_t *in, uint32_t in_len)
|
||||
return false;
|
||||
}
|
||||
|
||||
+static int tpm_passthrough_set_locality(TPMPassthruState *tpm_pt,
|
||||
+ uint8_t locty_number)
|
||||
+{
|
||||
+ ptm_loc loc;
|
||||
+
|
||||
+ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
|
||||
+ if (tpm_pt->cur_locty_number != locty_number) {
|
||||
+ loc.u.req.loc = locty_number;
|
||||
+ if (ioctl(tpm_pt->tpm_fd, PTM_SET_LOCALITY, &loc) < 0) {
|
||||
+ error_report("tpm_cuse: could not set locality on "
|
||||
+ "CUSE TPM: %s",
|
||||
+ strerror(errno));
|
||||
+ return -1;
|
||||
+ }
|
||||
+ tpm_pt->cur_locty_number = locty_number;
|
||||
+ }
|
||||
+ }
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt,
|
||||
+ uint8_t locality_number,
|
||||
const uint8_t *in, uint32_t in_len,
|
||||
uint8_t *out, uint32_t out_len,
|
||||
bool *selftest_done)
|
||||
@@ -157,6 +186,11 @@ static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt,
|
||||
bool is_selftest;
|
||||
const struct tpm_resp_hdr *hdr;
|
||||
|
||||
+ ret = tpm_passthrough_set_locality(tpm_pt, locality_number);
|
||||
+ if (ret < 0) {
|
||||
+ goto err_exit;
|
||||
+ }
|
||||
+
|
||||
tpm_pt->tpm_op_canceled = false;
|
||||
tpm_pt->tpm_executing = true;
|
||||
*selftest_done = false;
|
||||
@@ -207,10 +241,12 @@ err_exit:
|
||||
}
|
||||
|
||||
static int tpm_passthrough_unix_transfer(TPMPassthruState *tpm_pt,
|
||||
+ uint8_t locality_number,
|
||||
const TPMLocality *locty_data,
|
||||
bool *selftest_done)
|
||||
{
|
||||
return tpm_passthrough_unix_tx_bufs(tpm_pt,
|
||||
+ locality_number,
|
||||
locty_data->w_buffer.buffer,
|
||||
locty_data->w_offset,
|
||||
locty_data->r_buffer.buffer,
|
||||
@@ -231,6 +267,7 @@ static void tpm_passthrough_worker_thread(gpointer data,
|
||||
switch (cmd) {
|
||||
case TPM_BACKEND_CMD_PROCESS_CMD:
|
||||
tpm_passthrough_unix_transfer(tpm_pt,
|
||||
+ thr_parms->tpm_state->locty_number,
|
||||
thr_parms->tpm_state->locty_data,
|
||||
&selftest_done);
|
||||
|
||||
@@ -247,6 +284,93 @@ static void tpm_passthrough_worker_thread(gpointer data,
|
||||
}
|
||||
|
||||
/*
|
||||
+ * Gracefully shut down the external CUSE TPM
|
||||
+ */
|
||||
+static void tpm_passthrough_shutdown(TPMPassthruState *tpm_pt)
|
||||
+{
|
||||
+ ptm_res res;
|
||||
+
|
||||
+ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
|
||||
+ if (ioctl(tpm_pt->tpm_fd, PTM_SHUTDOWN, &res) < 0) {
|
||||
+ error_report("tpm_cuse: Could not cleanly shut down "
|
||||
+ "the CUSE TPM: %s",
|
||||
+ strerror(errno));
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Probe for the CUSE TPM by sending an ioctl() requesting its
|
||||
+ * capability flags.
|
||||
+ */
|
||||
+static int tpm_passthrough_cuse_probe(TPMPassthruState *tpm_pt)
|
||||
+{
|
||||
+ int rc = 0;
|
||||
+
|
||||
+ if (ioctl(tpm_pt->tpm_fd, PTM_GET_CAPABILITY, &tpm_pt->cuse_cap) < 0) {
|
||||
+ error_report("Error: CUSE TPM was requested, but probing failed");
|
||||
+ rc = -1;
|
||||
+ }
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+static int tpm_passthrough_cuse_check_caps(TPMPassthruState *tpm_pt)
|
||||
+{
|
||||
+ int rc = 0;
|
||||
+ ptm_cap caps = 0;
|
||||
+ const char *tpm = NULL;
|
||||
+
|
||||
+ /* check for min. required capabilities */
|
||||
+ switch (tpm_pt->tpm_version) {
|
||||
+ case TPM_VERSION_1_2:
|
||||
+ caps = PTM_CAP_INIT | PTM_CAP_SHUTDOWN | PTM_CAP_GET_TPMESTABLISHED |
|
||||
+ PTM_CAP_SET_LOCALITY;
|
||||
+ tpm = "1.2";
|
||||
+ break;
|
||||
+ case TPM_VERSION_2_0:
|
||||
+ caps = PTM_CAP_INIT | PTM_CAP_SHUTDOWN | PTM_CAP_GET_TPMESTABLISHED |
|
||||
+ PTM_CAP_SET_LOCALITY | PTM_CAP_RESET_TPMESTABLISHED;
|
||||
+ tpm = "2";
|
||||
+ break;
|
||||
+ case TPM_VERSION_UNSPEC:
|
||||
+ error_report("tpm_cuse: %s: TPM version has not been set",
|
||||
+ __func__);
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ if (!TPM_CUSE_IMPLEMENTS_ALL(tpm_pt, caps)) {
|
||||
+ error_report("tpm_cuse: TPM does not implement minimum set of required "
|
||||
+ "capabilities for TPM %s (0x%x)", tpm, (int)caps);
|
||||
+ rc = -1;
|
||||
+ }
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Initialize the external CUSE TPM
|
||||
+ */
|
||||
+static int tpm_passthrough_cuse_init(TPMPassthruState *tpm_pt)
|
||||
+{
|
||||
+ int rc = 0;
|
||||
+ ptm_init init = {
|
||||
+ .u.req.init_flags = PTM_INIT_FLAG_DELETE_VOLATILE,
|
||||
+ };
|
||||
+
|
||||
+ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
|
||||
+ if (ioctl(tpm_pt->tpm_fd, PTM_INIT, &init) < 0) {
|
||||
+ error_report("tpm_cuse: Detected CUSE TPM but could not "
|
||||
+ "send INIT: %s",
|
||||
+ strerror(errno));
|
||||
+ rc = -1;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
* Start the TPM (thread). If it had been started before, then terminate
|
||||
* and start it again.
|
||||
*/
|
||||
@@ -261,6 +385,8 @@ static int tpm_passthrough_startup_tpm(TPMBackend *tb)
|
||||
tpm_passthrough_worker_thread,
|
||||
&tpm_pt->tpm_thread_params);
|
||||
|
||||
+ tpm_passthrough_cuse_init(tpm_pt);
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -291,14 +417,43 @@ static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
|
||||
|
||||
static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
|
||||
{
|
||||
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
+ ptm_est est;
|
||||
+
|
||||
+ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
|
||||
+ if (ioctl(tpm_pt->tpm_fd, PTM_GET_TPMESTABLISHED, &est) < 0) {
|
||||
+ error_report("tpm_cuse: Could not get the TPM established "
|
||||
+ "flag from the CUSE TPM: %s",
|
||||
+ strerror(errno));
|
||||
+ return false;
|
||||
+ }
|
||||
+ return (est.u.resp.bit != 0);
|
||||
+ }
|
||||
return false;
|
||||
}
|
||||
|
||||
static int tpm_passthrough_reset_tpm_established_flag(TPMBackend *tb,
|
||||
uint8_t locty)
|
||||
{
|
||||
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
+ int rc = 0;
|
||||
+ ptm_reset_est ptmreset_est;
|
||||
+
|
||||
/* only a TPM 2.0 will support this */
|
||||
- return 0;
|
||||
+ if (tpm_pt->tpm_version == TPM_VERSION_2_0) {
|
||||
+ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
|
||||
+ ptmreset_est.u.req.loc = tpm_pt->cur_locty_number;
|
||||
+
|
||||
+ if (ioctl(tpm_pt->tpm_fd, PTM_RESET_TPMESTABLISHED,
|
||||
+ &ptmreset_est) < 0) {
|
||||
+ error_report("tpm_cuse: Could not reset the establishment bit "
|
||||
+ "failed: %s",
|
||||
+ strerror(errno));
|
||||
+ rc = -1;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ return rc;
|
||||
}
|
||||
|
||||
static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
|
||||
@@ -329,7 +484,8 @@ static void tpm_passthrough_deliver_request(TPMBackend *tb)
|
||||
static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
|
||||
{
|
||||
TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
- int n;
|
||||
+ ptm_res res;
|
||||
+ static bool error_printed;
|
||||
|
||||
/*
|
||||
* As of Linux 3.7 the tpm_tis driver does not properly cancel
|
||||
@@ -338,17 +494,34 @@ static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
|
||||
* command, e.g., a command executed on the host.
|
||||
*/
|
||||
if (tpm_pt->tpm_executing) {
|
||||
- if (tpm_pt->cancel_fd >= 0) {
|
||||
- n = write(tpm_pt->cancel_fd, "-", 1);
|
||||
- if (n != 1) {
|
||||
- error_report("Canceling TPM command failed: %s",
|
||||
- strerror(errno));
|
||||
- } else {
|
||||
- tpm_pt->tpm_op_canceled = true;
|
||||
+ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
|
||||
+ if (TPM_CUSE_IMPLEMENTS_ALL(tpm_pt, PTM_CAP_CANCEL_TPM_CMD)) {
|
||||
+ if (ioctl(tpm_pt->tpm_fd, PTM_CANCEL_TPM_CMD, &res) < 0) {
|
||||
+ error_report("tpm_cuse: Could not cancel command on "
|
||||
+ "CUSE TPM: %s",
|
||||
+ strerror(errno));
|
||||
+ } else if (res != TPM_SUCCESS) {
|
||||
+ if (!error_printed) {
|
||||
+ error_report("TPM error code from command "
|
||||
+ "cancellation of CUSE TPM: 0x%x", res);
|
||||
+ error_printed = true;
|
||||
+ }
|
||||
+ } else {
|
||||
+ tpm_pt->tpm_op_canceled = true;
|
||||
+ }
|
||||
}
|
||||
} else {
|
||||
- error_report("Cannot cancel TPM command due to missing "
|
||||
- "TPM sysfs cancel entry");
|
||||
+ if (tpm_pt->cancel_fd >= 0) {
|
||||
+ if (write(tpm_pt->cancel_fd, "-", 1) != 1) {
|
||||
+ error_report("Canceling TPM command failed: %s",
|
||||
+ strerror(errno));
|
||||
+ } else {
|
||||
+ tpm_pt->tpm_op_canceled = true;
|
||||
+ }
|
||||
+ } else {
|
||||
+ error_report("Cannot cancel TPM command due to missing "
|
||||
+ "TPM sysfs cancel entry");
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,6 +551,11 @@ static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb)
|
||||
char *dev;
|
||||
char path[PATH_MAX];
|
||||
|
||||
+ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
|
||||
+ /* not needed, but so we have a fd */
|
||||
+ return qemu_open("/dev/null", O_WRONLY);
|
||||
+ }
|
||||
+
|
||||
if (tb->cancel_path) {
|
||||
fd = qemu_open(tb->cancel_path, O_WRONLY);
|
||||
if (fd < 0) {
|
||||
@@ -412,12 +590,22 @@ static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
|
||||
{
|
||||
TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
const char *value;
|
||||
+ bool have_cuse = false;
|
||||
+
|
||||
+ value = qemu_opt_get(opts, "type");
|
||||
+ if (value != NULL && !strcmp("cuse-tpm", value)) {
|
||||
+ have_cuse = true;
|
||||
+ }
|
||||
|
||||
value = qemu_opt_get(opts, "cancel-path");
|
||||
tb->cancel_path = g_strdup(value);
|
||||
|
||||
value = qemu_opt_get(opts, "path");
|
||||
if (!value) {
|
||||
+ if (have_cuse) {
|
||||
+ error_report("Missing path to access CUSE TPM");
|
||||
+ goto err_free_parameters;
|
||||
+ }
|
||||
value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
|
||||
}
|
||||
|
||||
@@ -432,15 +620,36 @@ static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
|
||||
goto err_free_parameters;
|
||||
}
|
||||
|
||||
+ tpm_pt->cur_locty_number = ~0;
|
||||
+
|
||||
+ if (have_cuse) {
|
||||
+ if (tpm_passthrough_cuse_probe(tpm_pt)) {
|
||||
+ goto err_close_tpmdev;
|
||||
+ }
|
||||
+ /* init TPM for probing */
|
||||
+ if (tpm_passthrough_cuse_init(tpm_pt)) {
|
||||
+ goto err_close_tpmdev;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
if (tpm_util_test_tpmdev(tpm_pt->tpm_fd, &tpm_pt->tpm_version)) {
|
||||
error_report("'%s' is not a TPM device.",
|
||||
tpm_pt->tpm_dev);
|
||||
goto err_close_tpmdev;
|
||||
}
|
||||
|
||||
+ if (have_cuse) {
|
||||
+ if (tpm_passthrough_cuse_check_caps(tpm_pt)) {
|
||||
+ goto err_close_tpmdev;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+
|
||||
return 0;
|
||||
|
||||
err_close_tpmdev:
|
||||
+ tpm_passthrough_shutdown(tpm_pt);
|
||||
+
|
||||
qemu_close(tpm_pt->tpm_fd);
|
||||
tpm_pt->tpm_fd = -1;
|
||||
|
||||
@@ -491,6 +700,8 @@ static void tpm_passthrough_destroy(TPMBackend *tb)
|
||||
|
||||
tpm_backend_thread_end(&tpm_pt->tbt);
|
||||
|
||||
+ tpm_passthrough_shutdown(tpm_pt);
|
||||
+
|
||||
qemu_close(tpm_pt->tpm_fd);
|
||||
qemu_close(tpm_pt->cancel_fd);
|
||||
|
||||
@@ -564,3 +775,44 @@ static void tpm_passthrough_register(void)
|
||||
}
|
||||
|
||||
type_init(tpm_passthrough_register)
|
||||
+
|
||||
+/* CUSE TPM */
|
||||
+static const char *tpm_passthrough_cuse_create_desc(void)
|
||||
+{
|
||||
+ return "CUSE TPM backend driver";
|
||||
+}
|
||||
+
|
||||
+static const TPMDriverOps tpm_cuse_driver = {
|
||||
+ .type = TPM_TYPE_CUSE_TPM,
|
||||
+ .opts = tpm_passthrough_cmdline_opts,
|
||||
+ .desc = tpm_passthrough_cuse_create_desc,
|
||||
+ .create = tpm_passthrough_create,
|
||||
+ .destroy = tpm_passthrough_destroy,
|
||||
+ .init = tpm_passthrough_init,
|
||||
+ .startup_tpm = tpm_passthrough_startup_tpm,
|
||||
+ .realloc_buffer = tpm_passthrough_realloc_buffer,
|
||||
+ .reset = tpm_passthrough_reset,
|
||||
+ .had_startup_error = tpm_passthrough_get_startup_error,
|
||||
+ .deliver_request = tpm_passthrough_deliver_request,
|
||||
+ .cancel_cmd = tpm_passthrough_cancel_cmd,
|
||||
+ .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
|
||||
+ .reset_tpm_established_flag = tpm_passthrough_reset_tpm_established_flag,
|
||||
+ .get_tpm_version = tpm_passthrough_get_tpm_version,
|
||||
+};
|
||||
+
|
||||
+static const TypeInfo tpm_cuse_info = {
|
||||
+ .name = TYPE_TPM_CUSE,
|
||||
+ .parent = TYPE_TPM_BACKEND,
|
||||
+ .instance_size = sizeof(TPMPassthruState),
|
||||
+ .class_init = tpm_passthrough_class_init,
|
||||
+ .instance_init = tpm_passthrough_inst_init,
|
||||
+ .instance_finalize = tpm_passthrough_inst_finalize,
|
||||
+};
|
||||
+
|
||||
+static void tpm_cuse_register(void)
|
||||
+{
|
||||
+ type_register_static(&tpm_cuse_info);
|
||||
+ tpm_register_driver(&tpm_cuse_driver);
|
||||
+}
|
||||
+
|
||||
+type_init(tpm_cuse_register)
|
||||
diff --git a/qapi-schema.json b/qapi-schema.json
|
||||
index 5658723b37..53120d0f63 100644
|
||||
--- a/qapi-schema.json
|
||||
+++ b/qapi-schema.json
|
||||
@@ -3522,10 +3522,12 @@
|
||||
# An enumeration of TPM types
|
||||
#
|
||||
# @passthrough: TPM passthrough type
|
||||
+# @cuse-tpm: CUSE TPM type
|
||||
+# Since: 2.6
|
||||
#
|
||||
# Since: 1.5
|
||||
##
|
||||
-{ 'enum': 'TpmType', 'data': [ 'passthrough' ] }
|
||||
+{ 'enum': 'TpmType', 'data': [ 'passthrough', 'cuse-tpm' ] }
|
||||
|
||||
##
|
||||
# @query-tpm-types:
|
||||
@@ -3554,6 +3556,17 @@
|
||||
'*cancel-path' : 'str'} }
|
||||
|
||||
##
|
||||
+# @TPMCuseOptions:
|
||||
+#
|
||||
+# Information about the CUSE TPM type
|
||||
+#
|
||||
+# @path: string describing the path used for accessing the TPM device
|
||||
+#
|
||||
+# Since: 2.6
|
||||
+##
|
||||
+{ 'struct': 'TPMCuseOptions', 'data': { 'path' : 'str'}}
|
||||
+
|
||||
+##
|
||||
# @TpmTypeOptions:
|
||||
#
|
||||
# A union referencing different TPM backend types' configuration options
|
||||
@@ -3563,7 +3576,8 @@
|
||||
# Since: 1.5
|
||||
##
|
||||
{ 'union': 'TpmTypeOptions',
|
||||
- 'data': { 'passthrough' : 'TPMPassthroughOptions' } }
|
||||
+ 'data': { 'passthrough' : 'TPMPassthroughOptions',
|
||||
+ 'cuse-tpm' : 'TPMCuseOptions' } }
|
||||
|
||||
##
|
||||
# @TpmInfo:
|
||||
diff --git a/qemu-options.hx b/qemu-options.hx
|
||||
index a71aaf8ea8..e0f1d8e676 100644
|
||||
--- a/qemu-options.hx
|
||||
+++ b/qemu-options.hx
|
||||
@@ -2763,7 +2763,10 @@ DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
|
||||
"-tpmdev passthrough,id=id[,path=path][,cancel-path=path]\n"
|
||||
" use path to provide path to a character device; default is /dev/tpm0\n"
|
||||
" use cancel-path to provide path to TPM's cancel sysfs entry; if\n"
|
||||
- " not provided it will be searched for in /sys/class/misc/tpm?/device\n",
|
||||
+ " not provided it will be searched for in /sys/class/misc/tpm?/device\n"
|
||||
+ "-tpmdev cuse-tpm,id=id,path=path\n"
|
||||
+ " use path to provide path to a character device to talk to the\n"
|
||||
+ " TPM emulator providing a CUSE interface\n",
|
||||
QEMU_ARCH_ALL)
|
||||
STEXI
|
||||
|
||||
@@ -2772,8 +2775,8 @@ The general form of a TPM device option is:
|
||||
|
||||
@item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
|
||||
@findex -tpmdev
|
||||
-Backend type must be:
|
||||
-@option{passthrough}.
|
||||
+Backend type must be either one of the following:
|
||||
+@option{passthrough}, @option{cuse-tpm}.
|
||||
|
||||
The specific backend type will determine the applicable options.
|
||||
The @code{-tpmdev} option creates the TPM backend and requires a
|
||||
@@ -2823,6 +2826,18 @@ To create a passthrough TPM use the following two options:
|
||||
Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by
|
||||
@code{tpmdev=tpm0} in the device option.
|
||||
|
||||
+@item -tpmdev cuse-tpm, id=@var{id}, path=@var{path}
|
||||
+
|
||||
+(Linux-host only) Enable access to a TPM emulator with a CUSE interface.
|
||||
+
|
||||
+@option{path} specifies the path to the CUSE TPM character device.
|
||||
+
|
||||
+To create a backend device accessing the CUSE TPM emulator using /dev/vtpm
|
||||
+use the following two options:
|
||||
+@example
|
||||
+-tpmdev cuse-tpm,id=tpm0,path=/dev/vtpm -device tpm-tis,tpmdev=tpm0
|
||||
+@end example
|
||||
+
|
||||
@end table
|
||||
|
||||
ETEXI
|
||||
diff --git a/tpm.c b/tpm.c
|
||||
index 9a7c7114d3..5ec2373286 100644
|
||||
--- a/tpm.c
|
||||
+++ b/tpm.c
|
||||
@@ -25,7 +25,7 @@ static QLIST_HEAD(, TPMBackend) tpm_backends =
|
||||
|
||||
|
||||
#define TPM_MAX_MODELS 1
|
||||
-#define TPM_MAX_DRIVERS 1
|
||||
+#define TPM_MAX_DRIVERS 2
|
||||
|
||||
static TPMDriverOps const *be_drivers[TPM_MAX_DRIVERS] = {
|
||||
NULL,
|
||||
@@ -272,6 +272,15 @@ static TPMInfo *qmp_query_tpm_inst(TPMBackend *drv)
|
||||
tpo->has_cancel_path = true;
|
||||
}
|
||||
break;
|
||||
+ case TPM_TYPE_CUSE_TPM:
|
||||
+ res->options->type = TPM_TYPE_OPTIONS_KIND_CUSE_TPM;
|
||||
+ tpo = g_new0(TPMPassthroughOptions, 1);
|
||||
+ res->options->u.passthrough.data = tpo;
|
||||
+ if (drv->path) {
|
||||
+ tpo->path = g_strdup(drv->path);
|
||||
+ tpo->has_path = true;
|
||||
+ }
|
||||
+ break;
|
||||
case TPM_TYPE__MAX:
|
||||
break;
|
||||
}
|
||||
--
|
||||
2.11.0
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
From b5ffd3aa4e9bd4edb09cc84c46f78da72697a946 Mon Sep 17 00:00:00 2001
|
||||
From: Stefan Berger <stefanb@linux.vnet.ibm.com>
|
||||
Date: Sat, 31 Dec 2016 11:23:32 -0500
|
||||
Subject: [PATCH 2/4] Introduce condition to notify waiters of completed
|
||||
command
|
||||
|
||||
Introduce a lock and a condition to notify anyone waiting for the completion
|
||||
of the execution of a TPM command by the backend (thread). The backend
|
||||
uses the condition to signal anyone waiting for command completion.
|
||||
We need to place the condition in two locations: one is invoked by the
|
||||
backend thread, the other by the bottom half thread.
|
||||
We will use the signalling to wait for command completion before VM
|
||||
suspend.
|
||||
|
||||
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
|
||||
|
||||
Upstream-Status: Pending [https://lists.nongnu.org/archive/html/qemu-devel/2016-06/msg00252.html]
|
||||
Signed-off-by: Patrick Ohly <patrick.ohly@intel.com>
|
||||
---
|
||||
hw/tpm/tpm_int.h | 3 +++
|
||||
hw/tpm/tpm_tis.c | 14 ++++++++++++++
|
||||
2 files changed, 17 insertions(+)
|
||||
|
||||
diff --git a/hw/tpm/tpm_int.h b/hw/tpm/tpm_int.h
|
||||
index 6b2c9c953a..70be1ad8d9 100644
|
||||
--- a/hw/tpm/tpm_int.h
|
||||
+++ b/hw/tpm/tpm_int.h
|
||||
@@ -30,6 +30,9 @@ struct TPMState {
|
||||
char *backend;
|
||||
TPMBackend *be_driver;
|
||||
TPMVersion be_tpm_version;
|
||||
+
|
||||
+ QemuMutex state_lock;
|
||||
+ QemuCond cmd_complete;
|
||||
};
|
||||
|
||||
#define TPM(obj) OBJECT_CHECK(TPMState, (obj), TYPE_TPM_TIS)
|
||||
diff --git a/hw/tpm/tpm_tis.c b/hw/tpm/tpm_tis.c
|
||||
index 381e7266ea..14d9e83ea2 100644
|
||||
--- a/hw/tpm/tpm_tis.c
|
||||
+++ b/hw/tpm/tpm_tis.c
|
||||
@@ -368,6 +368,8 @@ static void tpm_tis_receive_bh(void *opaque)
|
||||
TPMTISEmuState *tis = &s->s.tis;
|
||||
uint8_t locty = s->locty_number;
|
||||
|
||||
+ qemu_mutex_lock(&s->state_lock);
|
||||
+
|
||||
tpm_tis_sts_set(&tis->loc[locty],
|
||||
TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE);
|
||||
tis->loc[locty].state = TPM_TIS_STATE_COMPLETION;
|
||||
@@ -384,6 +386,10 @@ static void tpm_tis_receive_bh(void *opaque)
|
||||
tpm_tis_raise_irq(s, locty,
|
||||
TPM_TIS_INT_DATA_AVAILABLE | TPM_TIS_INT_STS_VALID);
|
||||
#endif
|
||||
+
|
||||
+ /* notify of completed command */
|
||||
+ qemu_cond_signal(&s->cmd_complete);
|
||||
+ qemu_mutex_unlock(&s->state_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -403,6 +409,11 @@ static void tpm_tis_receive_cb(TPMState *s, uint8_t locty,
|
||||
}
|
||||
}
|
||||
|
||||
+ qemu_mutex_lock(&s->state_lock);
|
||||
+ /* notify of completed command */
|
||||
+ qemu_cond_signal(&s->cmd_complete);
|
||||
+ qemu_mutex_unlock(&s->state_lock);
|
||||
+
|
||||
qemu_bh_schedule(tis->bh);
|
||||
}
|
||||
|
||||
@@ -1072,6 +1083,9 @@ static void tpm_tis_initfn(Object *obj)
|
||||
memory_region_init_io(&s->mmio, OBJECT(s), &tpm_tis_memory_ops,
|
||||
s, "tpm-tis-mmio",
|
||||
TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT);
|
||||
+
|
||||
+ qemu_mutex_init(&s->state_lock);
|
||||
+ qemu_cond_init(&s->cmd_complete);
|
||||
}
|
||||
|
||||
static void tpm_tis_class_init(ObjectClass *klass, void *data)
|
||||
--
|
||||
2.11.0
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
From 732a8e046948fd62b32cd1dd76a6798eb1caf4d6 Mon Sep 17 00:00:00 2001
|
||||
From: Stefan Berger <stefanb@linux.vnet.ibm.com>
|
||||
Date: Sat, 31 Dec 2016 11:23:32 -0500
|
||||
Subject: [PATCH 3/4] Introduce condition in TPM backend for notification
|
||||
|
||||
TPM backends will suspend independently of the frontends. Also
|
||||
here we need to be able to wait for the TPM command to have been
|
||||
completely processed.
|
||||
|
||||
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
|
||||
|
||||
Upstream-Status: Pending [https://lists.nongnu.org/archive/html/qemu-devel/2016-06/msg00252.html]
|
||||
Signed-off-by: Patrick Ohly <patrick.ohly@intel.com>
|
||||
---
|
||||
hw/tpm/tpm_passthrough.c | 20 ++++++++++++++++++++
|
||||
1 file changed, 20 insertions(+)
|
||||
|
||||
diff --git a/hw/tpm/tpm_passthrough.c b/hw/tpm/tpm_passthrough.c
|
||||
index 050f2ba850..44739ebad2 100644
|
||||
--- a/hw/tpm/tpm_passthrough.c
|
||||
+++ b/hw/tpm/tpm_passthrough.c
|
||||
@@ -75,6 +75,10 @@ struct TPMPassthruState {
|
||||
TPMVersion tpm_version;
|
||||
ptm_cap cuse_cap; /* capabilities of the CUSE TPM */
|
||||
uint8_t cur_locty_number; /* last set locality */
|
||||
+
|
||||
+ QemuMutex state_lock;
|
||||
+ QemuCond cmd_complete; /* singnaled once tpm_busy is false */
|
||||
+ bool tpm_busy;
|
||||
};
|
||||
|
||||
typedef struct TPMPassthruState TPMPassthruState;
|
||||
@@ -274,6 +278,11 @@ static void tpm_passthrough_worker_thread(gpointer data,
|
||||
thr_parms->recv_data_callback(thr_parms->tpm_state,
|
||||
thr_parms->tpm_state->locty_number,
|
||||
selftest_done);
|
||||
+ /* result delivered */
|
||||
+ qemu_mutex_lock(&tpm_pt->state_lock);
|
||||
+ tpm_pt->tpm_busy = false;
|
||||
+ qemu_cond_signal(&tpm_pt->cmd_complete);
|
||||
+ qemu_mutex_unlock(&tpm_pt->state_lock);
|
||||
break;
|
||||
case TPM_BACKEND_CMD_INIT:
|
||||
case TPM_BACKEND_CMD_END:
|
||||
@@ -401,6 +410,7 @@ static void tpm_passthrough_reset(TPMBackend *tb)
|
||||
tpm_backend_thread_end(&tpm_pt->tbt);
|
||||
|
||||
tpm_pt->had_startup_error = false;
|
||||
+ tpm_pt->tpm_busy = false;
|
||||
}
|
||||
|
||||
static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
|
||||
@@ -478,6 +488,11 @@ static void tpm_passthrough_deliver_request(TPMBackend *tb)
|
||||
{
|
||||
TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
|
||||
+ /* TPM considered busy once TPM Request scheduled for processing */
|
||||
+ qemu_mutex_lock(&tpm_pt->state_lock);
|
||||
+ tpm_pt->tpm_busy = true;
|
||||
+ qemu_mutex_unlock(&tpm_pt->state_lock);
|
||||
+
|
||||
tpm_backend_thread_deliver_request(&tpm_pt->tbt);
|
||||
}
|
||||
|
||||
@@ -746,6 +761,11 @@ static const TPMDriverOps tpm_passthrough_driver = {
|
||||
|
||||
static void tpm_passthrough_inst_init(Object *obj)
|
||||
{
|
||||
+ TPMBackend *tb = TPM_BACKEND(obj);
|
||||
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
+
|
||||
+ qemu_mutex_init(&tpm_pt->state_lock);
|
||||
+ qemu_cond_init(&tpm_pt->cmd_complete);
|
||||
}
|
||||
|
||||
static void tpm_passthrough_inst_finalize(Object *obj)
|
||||
--
|
||||
2.11.0
|
||||
|
||||
+719
@@ -0,0 +1,719 @@
|
||||
From 5e9dd9063f514447ea4f54046793f4f01c297ed4 Mon Sep 17 00:00:00 2001
|
||||
From: Stefan Berger <stefanb@linux.vnet.ibm.com>
|
||||
Date: Sat, 31 Dec 2016 11:23:32 -0500
|
||||
Subject: [PATCH 4/4] Add support for VM suspend/resume for TPM TIS
|
||||
|
||||
Extend the TPM TIS code to support suspend/resume. In case a command
|
||||
is being processed by the external TPM when suspending, wait for the command
|
||||
to complete to catch the result. In case the bottom half did not run,
|
||||
run the one function the bottom half is supposed to run. This then
|
||||
makes the resume operation work.
|
||||
|
||||
The passthrough backend does not support suspend/resume operation
|
||||
and is therefore blocked from suspend/resume and migration.
|
||||
|
||||
The CUSE TPM's supported capabilities are tested and if sufficient
|
||||
capabilities are implemented, suspend/resume, snapshotting and
|
||||
migration are supported by the CUSE TPM.
|
||||
|
||||
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
|
||||
|
||||
Upstream-Status: Pending [https://lists.nongnu.org/archive/html/qemu-devel/2016-06/msg00252.html]
|
||||
Signed-off-by: Patrick Ohly <patrick.ohly@intel.com>
|
||||
---
|
||||
hw/tpm/tpm_passthrough.c | 130 +++++++++++++++++++++++--
|
||||
hw/tpm/tpm_tis.c | 137 +++++++++++++++++++++++++-
|
||||
hw/tpm/tpm_tis.h | 2 +
|
||||
hw/tpm/tpm_util.c | 223 +++++++++++++++++++++++++++++++++++++++++++
|
||||
hw/tpm/tpm_util.h | 7 ++
|
||||
include/sysemu/tpm_backend.h | 12 +++
|
||||
6 files changed, 503 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/hw/tpm/tpm_passthrough.c b/hw/tpm/tpm_passthrough.c
|
||||
index 44739ebad2..bc8072d0bc 100644
|
||||
--- a/hw/tpm/tpm_passthrough.c
|
||||
+++ b/hw/tpm/tpm_passthrough.c
|
||||
@@ -34,6 +34,8 @@
|
||||
#include "tpm_tis.h"
|
||||
#include "tpm_util.h"
|
||||
#include "tpm_ioctl.h"
|
||||
+#include "migration/migration.h"
|
||||
+#include "qapi/error.h"
|
||||
|
||||
#define DEBUG_TPM 0
|
||||
|
||||
@@ -49,6 +51,7 @@
|
||||
#define TYPE_TPM_CUSE "tpm-cuse"
|
||||
|
||||
static const TPMDriverOps tpm_passthrough_driver;
|
||||
+static const VMStateDescription vmstate_tpm_cuse;
|
||||
|
||||
/* data structures */
|
||||
typedef struct TPMPassthruThreadParams {
|
||||
@@ -79,6 +82,10 @@ struct TPMPassthruState {
|
||||
QemuMutex state_lock;
|
||||
QemuCond cmd_complete; /* singnaled once tpm_busy is false */
|
||||
bool tpm_busy;
|
||||
+
|
||||
+ Error *migration_blocker;
|
||||
+
|
||||
+ TPMBlobBuffers tpm_blobs;
|
||||
};
|
||||
|
||||
typedef struct TPMPassthruState TPMPassthruState;
|
||||
@@ -306,6 +313,10 @@ static void tpm_passthrough_shutdown(TPMPassthruState *tpm_pt)
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
+ if (tpm_pt->migration_blocker) {
|
||||
+ migrate_del_blocker(tpm_pt->migration_blocker);
|
||||
+ error_free(tpm_pt->migration_blocker);
|
||||
+ }
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -360,12 +371,14 @@ static int tpm_passthrough_cuse_check_caps(TPMPassthruState *tpm_pt)
|
||||
/*
|
||||
* Initialize the external CUSE TPM
|
||||
*/
|
||||
-static int tpm_passthrough_cuse_init(TPMPassthruState *tpm_pt)
|
||||
+static int tpm_passthrough_cuse_init(TPMPassthruState *tpm_pt,
|
||||
+ bool is_resume)
|
||||
{
|
||||
int rc = 0;
|
||||
- ptm_init init = {
|
||||
- .u.req.init_flags = PTM_INIT_FLAG_DELETE_VOLATILE,
|
||||
- };
|
||||
+ ptm_init init;
|
||||
+ if (is_resume) {
|
||||
+ init.u.req.init_flags = PTM_INIT_FLAG_DELETE_VOLATILE;
|
||||
+ }
|
||||
|
||||
if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
|
||||
if (ioctl(tpm_pt->tpm_fd, PTM_INIT, &init) < 0) {
|
||||
@@ -394,7 +407,7 @@ static int tpm_passthrough_startup_tpm(TPMBackend *tb)
|
||||
tpm_passthrough_worker_thread,
|
||||
&tpm_pt->tpm_thread_params);
|
||||
|
||||
- tpm_passthrough_cuse_init(tpm_pt);
|
||||
+ tpm_passthrough_cuse_init(tpm_pt, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -466,6 +479,32 @@ static int tpm_passthrough_reset_tpm_established_flag(TPMBackend *tb,
|
||||
return rc;
|
||||
}
|
||||
|
||||
+static int tpm_cuse_get_state_blobs(TPMBackend *tb,
|
||||
+ bool decrypted_blobs,
|
||||
+ TPMBlobBuffers *tpm_blobs)
|
||||
+{
|
||||
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
+
|
||||
+ assert(TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt));
|
||||
+
|
||||
+ return tpm_util_cuse_get_state_blobs(tpm_pt->tpm_fd, decrypted_blobs,
|
||||
+ tpm_blobs);
|
||||
+}
|
||||
+
|
||||
+static int tpm_cuse_set_state_blobs(TPMBackend *tb,
|
||||
+ TPMBlobBuffers *tpm_blobs)
|
||||
+{
|
||||
+ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
+
|
||||
+ assert(TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt));
|
||||
+
|
||||
+ if (tpm_util_cuse_set_state_blobs(tpm_pt->tpm_fd, tpm_blobs)) {
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ return tpm_passthrough_cuse_init(tpm_pt, true);
|
||||
+}
|
||||
+
|
||||
static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
|
||||
{
|
||||
TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
@@ -488,7 +527,7 @@ static void tpm_passthrough_deliver_request(TPMBackend *tb)
|
||||
{
|
||||
TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
|
||||
- /* TPM considered busy once TPM Request scheduled for processing */
|
||||
+ /* TPM considered busy once TPM request scheduled for processing */
|
||||
qemu_mutex_lock(&tpm_pt->state_lock);
|
||||
tpm_pt->tpm_busy = true;
|
||||
qemu_mutex_unlock(&tpm_pt->state_lock);
|
||||
@@ -601,6 +640,25 @@ static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb)
|
||||
return fd;
|
||||
}
|
||||
|
||||
+static void tpm_passthrough_block_migration(TPMPassthruState *tpm_pt)
|
||||
+{
|
||||
+ ptm_cap caps;
|
||||
+
|
||||
+ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
|
||||
+ caps = PTM_CAP_GET_STATEBLOB | PTM_CAP_SET_STATEBLOB |
|
||||
+ PTM_CAP_STOP;
|
||||
+ if (!TPM_CUSE_IMPLEMENTS_ALL(tpm_pt, caps)) {
|
||||
+ error_setg(&tpm_pt->migration_blocker,
|
||||
+ "Migration disabled: CUSE TPM lacks necessary capabilities");
|
||||
+ migrate_add_blocker(tpm_pt->migration_blocker);
|
||||
+ }
|
||||
+ } else {
|
||||
+ error_setg(&tpm_pt->migration_blocker,
|
||||
+ "Migration disabled: Passthrough TPM does not support migration");
|
||||
+ migrate_add_blocker(tpm_pt->migration_blocker);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
|
||||
{
|
||||
TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
@@ -642,7 +700,7 @@ static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
|
||||
goto err_close_tpmdev;
|
||||
}
|
||||
/* init TPM for probing */
|
||||
- if (tpm_passthrough_cuse_init(tpm_pt)) {
|
||||
+ if (tpm_passthrough_cuse_init(tpm_pt, false)) {
|
||||
goto err_close_tpmdev;
|
||||
}
|
||||
}
|
||||
@@ -659,6 +717,7 @@ static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
|
||||
}
|
||||
}
|
||||
|
||||
+ tpm_passthrough_block_migration(tpm_pt);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -766,10 +825,13 @@ static void tpm_passthrough_inst_init(Object *obj)
|
||||
|
||||
qemu_mutex_init(&tpm_pt->state_lock);
|
||||
qemu_cond_init(&tpm_pt->cmd_complete);
|
||||
+
|
||||
+ vmstate_register(NULL, -1, &vmstate_tpm_cuse, obj);
|
||||
}
|
||||
|
||||
static void tpm_passthrough_inst_finalize(Object *obj)
|
||||
{
|
||||
+ vmstate_unregister(NULL, &vmstate_tpm_cuse, obj);
|
||||
}
|
||||
|
||||
static void tpm_passthrough_class_init(ObjectClass *klass, void *data)
|
||||
@@ -802,6 +864,60 @@ static const char *tpm_passthrough_cuse_create_desc(void)
|
||||
return "CUSE TPM backend driver";
|
||||
}
|
||||
|
||||
+static void tpm_cuse_pre_save(void *opaque)
|
||||
+{
|
||||
+ TPMPassthruState *tpm_pt = opaque;
|
||||
+ TPMBackend *tb = &tpm_pt->parent;
|
||||
+
|
||||
+ qemu_mutex_lock(&tpm_pt->state_lock);
|
||||
+ /* wait for TPM to finish processing */
|
||||
+ if (tpm_pt->tpm_busy) {
|
||||
+ qemu_cond_wait(&tpm_pt->cmd_complete, &tpm_pt->state_lock);
|
||||
+ }
|
||||
+ qemu_mutex_unlock(&tpm_pt->state_lock);
|
||||
+
|
||||
+ /* get the decrypted state blobs from the TPM */
|
||||
+ tpm_cuse_get_state_blobs(tb, TRUE, &tpm_pt->tpm_blobs);
|
||||
+}
|
||||
+
|
||||
+static int tpm_cuse_post_load(void *opaque,
|
||||
+ int version_id __attribute__((unused)))
|
||||
+{
|
||||
+ TPMPassthruState *tpm_pt = opaque;
|
||||
+ TPMBackend *tb = &tpm_pt->parent;
|
||||
+
|
||||
+ return tpm_cuse_set_state_blobs(tb, &tpm_pt->tpm_blobs);
|
||||
+}
|
||||
+
|
||||
+static const VMStateDescription vmstate_tpm_cuse = {
|
||||
+ .name = "cuse-tpm",
|
||||
+ .version_id = 1,
|
||||
+ .minimum_version_id = 0,
|
||||
+ .minimum_version_id_old = 0,
|
||||
+ .pre_save = tpm_cuse_pre_save,
|
||||
+ .post_load = tpm_cuse_post_load,
|
||||
+ .fields = (VMStateField[]) {
|
||||
+ VMSTATE_UINT32(tpm_blobs.permanent_flags, TPMPassthruState),
|
||||
+ VMSTATE_UINT32(tpm_blobs.permanent.size, TPMPassthruState),
|
||||
+ VMSTATE_VBUFFER_ALLOC_UINT32(tpm_blobs.permanent.buffer,
|
||||
+ TPMPassthruState, 1, NULL, 0,
|
||||
+ tpm_blobs.permanent.size),
|
||||
+
|
||||
+ VMSTATE_UINT32(tpm_blobs.volatil_flags, TPMPassthruState),
|
||||
+ VMSTATE_UINT32(tpm_blobs.volatil.size, TPMPassthruState),
|
||||
+ VMSTATE_VBUFFER_ALLOC_UINT32(tpm_blobs.volatil.buffer,
|
||||
+ TPMPassthruState, 1, NULL, 0,
|
||||
+ tpm_blobs.volatil.size),
|
||||
+
|
||||
+ VMSTATE_UINT32(tpm_blobs.savestate_flags, TPMPassthruState),
|
||||
+ VMSTATE_UINT32(tpm_blobs.savestate.size, TPMPassthruState),
|
||||
+ VMSTATE_VBUFFER_ALLOC_UINT32(tpm_blobs.savestate.buffer,
|
||||
+ TPMPassthruState, 1, NULL, 0,
|
||||
+ tpm_blobs.savestate.size),
|
||||
+ VMSTATE_END_OF_LIST()
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
static const TPMDriverOps tpm_cuse_driver = {
|
||||
.type = TPM_TYPE_CUSE_TPM,
|
||||
.opts = tpm_passthrough_cmdline_opts,
|
||||
diff --git a/hw/tpm/tpm_tis.c b/hw/tpm/tpm_tis.c
|
||||
index 14d9e83ea2..9b660cf737 100644
|
||||
--- a/hw/tpm/tpm_tis.c
|
||||
+++ b/hw/tpm/tpm_tis.c
|
||||
@@ -368,6 +368,8 @@ static void tpm_tis_receive_bh(void *opaque)
|
||||
TPMTISEmuState *tis = &s->s.tis;
|
||||
uint8_t locty = s->locty_number;
|
||||
|
||||
+ tis->bh_scheduled = false;
|
||||
+
|
||||
qemu_mutex_lock(&s->state_lock);
|
||||
|
||||
tpm_tis_sts_set(&tis->loc[locty],
|
||||
@@ -415,6 +417,8 @@ static void tpm_tis_receive_cb(TPMState *s, uint8_t locty,
|
||||
qemu_mutex_unlock(&s->state_lock);
|
||||
|
||||
qemu_bh_schedule(tis->bh);
|
||||
+
|
||||
+ tis->bh_scheduled = true;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1030,9 +1034,140 @@ static void tpm_tis_reset(DeviceState *dev)
|
||||
tpm_tis_do_startup_tpm(s);
|
||||
}
|
||||
|
||||
+
|
||||
+/* persistent state handling */
|
||||
+
|
||||
+static void tpm_tis_pre_save(void *opaque)
|
||||
+{
|
||||
+ TPMState *s = opaque;
|
||||
+ TPMTISEmuState *tis = &s->s.tis;
|
||||
+ uint8_t locty = tis->active_locty;
|
||||
+
|
||||
+ DPRINTF("tpm_tis: suspend: locty = %d : r_offset = %d, w_offset = %d\n",
|
||||
+ locty, tis->loc[0].r_offset, tis->loc[0].w_offset);
|
||||
+#ifdef DEBUG_TIS
|
||||
+ tpm_tis_dump_state(opaque, 0);
|
||||
+#endif
|
||||
+
|
||||
+ qemu_mutex_lock(&s->state_lock);
|
||||
+
|
||||
+ /* wait for outstanding request to complete */
|
||||
+ if (TPM_TIS_IS_VALID_LOCTY(locty) &&
|
||||
+ tis->loc[locty].state == TPM_TIS_STATE_EXECUTION) {
|
||||
+ /*
|
||||
+ * If we get here when the bh is scheduled but did not run,
|
||||
+ * we won't get notified...
|
||||
+ */
|
||||
+ if (!tis->bh_scheduled) {
|
||||
+ /* backend thread to notify us */
|
||||
+ qemu_cond_wait(&s->cmd_complete, &s->state_lock);
|
||||
+ }
|
||||
+ if (tis->loc[locty].state == TPM_TIS_STATE_EXECUTION) {
|
||||
+ /* bottom half did not run - run its function */
|
||||
+ qemu_mutex_unlock(&s->state_lock);
|
||||
+ tpm_tis_receive_bh(opaque);
|
||||
+ qemu_mutex_lock(&s->state_lock);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ qemu_mutex_unlock(&s->state_lock);
|
||||
+
|
||||
+ /* copy current active read or write buffer into the buffer
|
||||
+ written to disk */
|
||||
+ if (TPM_TIS_IS_VALID_LOCTY(locty)) {
|
||||
+ switch (tis->loc[locty].state) {
|
||||
+ case TPM_TIS_STATE_RECEPTION:
|
||||
+ memcpy(tis->buf,
|
||||
+ tis->loc[locty].w_buffer.buffer,
|
||||
+ MIN(sizeof(tis->buf),
|
||||
+ tis->loc[locty].w_buffer.size));
|
||||
+ tis->offset = tis->loc[locty].w_offset;
|
||||
+ break;
|
||||
+ case TPM_TIS_STATE_COMPLETION:
|
||||
+ memcpy(tis->buf,
|
||||
+ tis->loc[locty].r_buffer.buffer,
|
||||
+ MIN(sizeof(tis->buf),
|
||||
+ tis->loc[locty].r_buffer.size));
|
||||
+ tis->offset = tis->loc[locty].r_offset;
|
||||
+ break;
|
||||
+ default:
|
||||
+ /* leak nothing */
|
||||
+ memset(tis->buf, 0x0, sizeof(tis->buf));
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static int tpm_tis_post_load(void *opaque,
|
||||
+ int version_id __attribute__((unused)))
|
||||
+{
|
||||
+ TPMState *s = opaque;
|
||||
+ TPMTISEmuState *tis = &s->s.tis;
|
||||
+
|
||||
+ uint8_t locty = tis->active_locty;
|
||||
+
|
||||
+ if (TPM_TIS_IS_VALID_LOCTY(locty)) {
|
||||
+ switch (tis->loc[locty].state) {
|
||||
+ case TPM_TIS_STATE_RECEPTION:
|
||||
+ memcpy(tis->loc[locty].w_buffer.buffer,
|
||||
+ tis->buf,
|
||||
+ MIN(sizeof(tis->buf),
|
||||
+ tis->loc[locty].w_buffer.size));
|
||||
+ tis->loc[locty].w_offset = tis->offset;
|
||||
+ break;
|
||||
+ case TPM_TIS_STATE_COMPLETION:
|
||||
+ memcpy(tis->loc[locty].r_buffer.buffer,
|
||||
+ tis->buf,
|
||||
+ MIN(sizeof(tis->buf),
|
||||
+ tis->loc[locty].r_buffer.size));
|
||||
+ tis->loc[locty].r_offset = tis->offset;
|
||||
+ break;
|
||||
+ default:
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ DPRINTF("tpm_tis: resume : locty = %d : r_offset = %d, w_offset = %d\n",
|
||||
+ locty, tis->loc[0].r_offset, tis->loc[0].w_offset);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const VMStateDescription vmstate_locty = {
|
||||
+ .name = "loc",
|
||||
+ .version_id = 1,
|
||||
+ .minimum_version_id = 0,
|
||||
+ .minimum_version_id_old = 0,
|
||||
+ .fields = (VMStateField[]) {
|
||||
+ VMSTATE_UINT32(state, TPMLocality),
|
||||
+ VMSTATE_UINT32(inte, TPMLocality),
|
||||
+ VMSTATE_UINT32(ints, TPMLocality),
|
||||
+ VMSTATE_UINT8(access, TPMLocality),
|
||||
+ VMSTATE_UINT32(sts, TPMLocality),
|
||||
+ VMSTATE_UINT32(iface_id, TPMLocality),
|
||||
+ VMSTATE_END_OF_LIST(),
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
static const VMStateDescription vmstate_tpm_tis = {
|
||||
.name = "tpm",
|
||||
- .unmigratable = 1,
|
||||
+ .version_id = 1,
|
||||
+ .minimum_version_id = 0,
|
||||
+ .minimum_version_id_old = 0,
|
||||
+ .pre_save = tpm_tis_pre_save,
|
||||
+ .post_load = tpm_tis_post_load,
|
||||
+ .fields = (VMStateField[]) {
|
||||
+ VMSTATE_UINT32(s.tis.offset, TPMState),
|
||||
+ VMSTATE_BUFFER(s.tis.buf, TPMState),
|
||||
+ VMSTATE_UINT8(s.tis.active_locty, TPMState),
|
||||
+ VMSTATE_UINT8(s.tis.aborting_locty, TPMState),
|
||||
+ VMSTATE_UINT8(s.tis.next_locty, TPMState),
|
||||
+
|
||||
+ VMSTATE_STRUCT_ARRAY(s.tis.loc, TPMState, TPM_TIS_NUM_LOCALITIES, 1,
|
||||
+ vmstate_locty, TPMLocality),
|
||||
+
|
||||
+ VMSTATE_END_OF_LIST()
|
||||
+ }
|
||||
};
|
||||
|
||||
static Property tpm_tis_properties[] = {
|
||||
diff --git a/hw/tpm/tpm_tis.h b/hw/tpm/tpm_tis.h
|
||||
index a1df41fa21..b7fc0ea1a9 100644
|
||||
--- a/hw/tpm/tpm_tis.h
|
||||
+++ b/hw/tpm/tpm_tis.h
|
||||
@@ -54,6 +54,8 @@ typedef struct TPMLocality {
|
||||
|
||||
typedef struct TPMTISEmuState {
|
||||
QEMUBH *bh;
|
||||
+ bool bh_scheduled; /* bh scheduled but did not run yet */
|
||||
+
|
||||
uint32_t offset;
|
||||
uint8_t buf[TPM_TIS_BUFFER_MAX];
|
||||
|
||||
diff --git a/hw/tpm/tpm_util.c b/hw/tpm/tpm_util.c
|
||||
index 7b35429725..b6ff74d946 100644
|
||||
--- a/hw/tpm/tpm_util.c
|
||||
+++ b/hw/tpm/tpm_util.c
|
||||
@@ -22,6 +22,17 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "tpm_util.h"
|
||||
#include "tpm_int.h"
|
||||
+#include "tpm_ioctl.h"
|
||||
+#include "qemu/error-report.h"
|
||||
+
|
||||
+#define DEBUG_TPM 0
|
||||
+
|
||||
+#define DPRINTF(fmt, ...) do { \
|
||||
+ if (DEBUG_TPM) { \
|
||||
+ fprintf(stderr, fmt, ## __VA_ARGS__); \
|
||||
+ } \
|
||||
+} while (0)
|
||||
+
|
||||
|
||||
/*
|
||||
* A basic test of a TPM device. We expect a well formatted response header
|
||||
@@ -125,3 +136,215 @@ int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version)
|
||||
|
||||
return 1;
|
||||
}
|
||||
+
|
||||
+static void tpm_sized_buffer_reset(TPMSizedBuffer *tsb)
|
||||
+{
|
||||
+ g_free(tsb->buffer);
|
||||
+ tsb->buffer = NULL;
|
||||
+ tsb->size = 0;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Transfer a TPM state blob from the TPM into a provided buffer.
|
||||
+ *
|
||||
+ * @fd: file descriptor to talk to the CUSE TPM
|
||||
+ * @type: the type of blob to transfer
|
||||
+ * @decrypted_blob: whether we request to receive decrypted blobs
|
||||
+ * @tsb: the TPMSizeBuffer to fill with the blob
|
||||
+ * @flags: the flags to return to the caller
|
||||
+ */
|
||||
+static int tpm_util_cuse_get_state_blob(int fd,
|
||||
+ uint8_t type,
|
||||
+ bool decrypted_blob,
|
||||
+ TPMSizedBuffer *tsb,
|
||||
+ uint32_t *flags)
|
||||
+{
|
||||
+ ptm_getstate pgs;
|
||||
+ uint16_t offset = 0;
|
||||
+ ptm_res res;
|
||||
+ ssize_t n;
|
||||
+ size_t to_read;
|
||||
+
|
||||
+ tpm_sized_buffer_reset(tsb);
|
||||
+
|
||||
+ pgs.u.req.state_flags = (decrypted_blob) ? PTM_STATE_FLAG_DECRYPTED : 0;
|
||||
+ pgs.u.req.type = type;
|
||||
+ pgs.u.req.offset = offset;
|
||||
+
|
||||
+ if (ioctl(fd, PTM_GET_STATEBLOB, &pgs) < 0) {
|
||||
+ error_report("CUSE TPM PTM_GET_STATEBLOB ioctl failed: %s",
|
||||
+ strerror(errno));
|
||||
+ goto err_exit;
|
||||
+ }
|
||||
+ res = pgs.u.resp.tpm_result;
|
||||
+ if (res != 0 && (res & 0x800) == 0) {
|
||||
+ error_report("Getting the stateblob (type %d) failed with a TPM "
|
||||
+ "error 0x%x", type, res);
|
||||
+ goto err_exit;
|
||||
+ }
|
||||
+
|
||||
+ *flags = pgs.u.resp.state_flags;
|
||||
+
|
||||
+ tsb->buffer = g_malloc(pgs.u.resp.totlength);
|
||||
+ memcpy(tsb->buffer, pgs.u.resp.data, pgs.u.resp.length);
|
||||
+ tsb->size = pgs.u.resp.length;
|
||||
+
|
||||
+ /* if there are bytes left to get use read() interface */
|
||||
+ while (tsb->size < pgs.u.resp.totlength) {
|
||||
+ to_read = pgs.u.resp.totlength - tsb->size;
|
||||
+ if (unlikely(to_read > SSIZE_MAX)) {
|
||||
+ to_read = SSIZE_MAX;
|
||||
+ }
|
||||
+
|
||||
+ n = read(fd, &tsb->buffer[tsb->size], to_read);
|
||||
+ if (n != to_read) {
|
||||
+ error_report("Could not read stateblob (type %d) : %s",
|
||||
+ type, strerror(errno));
|
||||
+ goto err_exit;
|
||||
+ }
|
||||
+ tsb->size += to_read;
|
||||
+ }
|
||||
+
|
||||
+ DPRINTF("tpm_util: got state blob type %d, %d bytes, flags 0x%08x, "
|
||||
+ "decrypted=%d\n", type, tsb->size, *flags, decrypted_blob);
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+err_exit:
|
||||
+ return 1;
|
||||
+}
|
||||
+
|
||||
+int tpm_util_cuse_get_state_blobs(int tpm_fd,
|
||||
+ bool decrypted_blobs,
|
||||
+ TPMBlobBuffers *tpm_blobs)
|
||||
+{
|
||||
+ if (tpm_util_cuse_get_state_blob(tpm_fd, PTM_BLOB_TYPE_PERMANENT,
|
||||
+ decrypted_blobs,
|
||||
+ &tpm_blobs->permanent,
|
||||
+ &tpm_blobs->permanent_flags) ||
|
||||
+ tpm_util_cuse_get_state_blob(tpm_fd, PTM_BLOB_TYPE_VOLATILE,
|
||||
+ decrypted_blobs,
|
||||
+ &tpm_blobs->volatil,
|
||||
+ &tpm_blobs->volatil_flags) ||
|
||||
+ tpm_util_cuse_get_state_blob(tpm_fd, PTM_BLOB_TYPE_SAVESTATE,
|
||||
+ decrypted_blobs,
|
||||
+ &tpm_blobs->savestate,
|
||||
+ &tpm_blobs->savestate_flags)) {
|
||||
+ goto err_exit;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+ err_exit:
|
||||
+ tpm_sized_buffer_reset(&tpm_blobs->volatil);
|
||||
+ tpm_sized_buffer_reset(&tpm_blobs->permanent);
|
||||
+ tpm_sized_buffer_reset(&tpm_blobs->savestate);
|
||||
+
|
||||
+ return 1;
|
||||
+}
|
||||
+
|
||||
+static int tpm_util_cuse_do_set_stateblob_ioctl(int fd,
|
||||
+ uint32_t flags,
|
||||
+ uint32_t type,
|
||||
+ uint32_t length)
|
||||
+{
|
||||
+ ptm_setstate pss;
|
||||
+
|
||||
+ pss.u.req.state_flags = flags;
|
||||
+ pss.u.req.type = type;
|
||||
+ pss.u.req.length = length;
|
||||
+
|
||||
+ if (ioctl(fd, PTM_SET_STATEBLOB, &pss) < 0) {
|
||||
+ error_report("CUSE TPM PTM_SET_STATEBLOB ioctl failed: %s",
|
||||
+ strerror(errno));
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ if (pss.u.resp.tpm_result != 0) {
|
||||
+ error_report("Setting the stateblob (type %d) failed with a TPM "
|
||||
+ "error 0x%x", type, pss.u.resp.tpm_result);
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+
|
||||
+/*
|
||||
+ * Transfer a TPM state blob to the CUSE TPM.
|
||||
+ *
|
||||
+ * @fd: file descriptor to talk to the CUSE TPM
|
||||
+ * @type: the type of TPM state blob to transfer
|
||||
+ * @tsb: TPMSizeBuffer containing the TPM state blob
|
||||
+ * @flags: Flags describing the (encryption) state of the TPM state blob
|
||||
+ */
|
||||
+static int tpm_util_cuse_set_state_blob(int fd,
|
||||
+ uint32_t type,
|
||||
+ TPMSizedBuffer *tsb,
|
||||
+ uint32_t flags)
|
||||
+{
|
||||
+ uint32_t offset = 0;
|
||||
+ ssize_t n;
|
||||
+ size_t to_write;
|
||||
+
|
||||
+ /* initiate the transfer to the CUSE TPM */
|
||||
+ if (tpm_util_cuse_do_set_stateblob_ioctl(fd, flags, type, 0)) {
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ /* use the write() interface for transferring the state blob */
|
||||
+ while (offset < tsb->size) {
|
||||
+ to_write = tsb->size - offset;
|
||||
+ if (unlikely(to_write > SSIZE_MAX)) {
|
||||
+ to_write = SSIZE_MAX;
|
||||
+ }
|
||||
+
|
||||
+ n = write(fd, &tsb->buffer[offset], to_write);
|
||||
+ if (n != to_write) {
|
||||
+ error_report("Writing the stateblob (type %d) failed: %s",
|
||||
+ type, strerror(errno));
|
||||
+ goto err_exit;
|
||||
+ }
|
||||
+ offset += to_write;
|
||||
+ }
|
||||
+
|
||||
+ /* inidicate that the transfer is finished */
|
||||
+ if (tpm_util_cuse_do_set_stateblob_ioctl(fd, flags, type, 0)) {
|
||||
+ goto err_exit;
|
||||
+ }
|
||||
+
|
||||
+ DPRINTF("tpm_util: set the state blob type %d, %d bytes, flags 0x%08x\n",
|
||||
+ type, tsb->size, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+err_exit:
|
||||
+ return 1;
|
||||
+}
|
||||
+
|
||||
+int tpm_util_cuse_set_state_blobs(int tpm_fd,
|
||||
+ TPMBlobBuffers *tpm_blobs)
|
||||
+{
|
||||
+ ptm_res res;
|
||||
+
|
||||
+ if (ioctl(tpm_fd, PTM_STOP, &res) < 0) {
|
||||
+ error_report("tpm_passthrough: Could not stop "
|
||||
+ "the CUSE TPM: %s (%i)",
|
||||
+ strerror(errno), errno);
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ if (tpm_util_cuse_set_state_blob(tpm_fd, PTM_BLOB_TYPE_PERMANENT,
|
||||
+ &tpm_blobs->permanent,
|
||||
+ tpm_blobs->permanent_flags) ||
|
||||
+ tpm_util_cuse_set_state_blob(tpm_fd, PTM_BLOB_TYPE_VOLATILE,
|
||||
+ &tpm_blobs->volatil,
|
||||
+ tpm_blobs->volatil_flags) ||
|
||||
+ tpm_util_cuse_set_state_blob(tpm_fd, PTM_BLOB_TYPE_SAVESTATE,
|
||||
+ &tpm_blobs->savestate,
|
||||
+ tpm_blobs->savestate_flags)) {
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
diff --git a/hw/tpm/tpm_util.h b/hw/tpm/tpm_util.h
|
||||
index df76245e6e..c24071d812 100644
|
||||
--- a/hw/tpm/tpm_util.h
|
||||
+++ b/hw/tpm/tpm_util.h
|
||||
@@ -26,4 +26,11 @@
|
||||
|
||||
int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version);
|
||||
|
||||
+int tpm_util_cuse_get_state_blobs(int tpm_fd,
|
||||
+ bool decrypted_blobs,
|
||||
+ TPMBlobBuffers *tpm_blobs);
|
||||
+
|
||||
+int tpm_util_cuse_set_state_blobs(int tpm_fd,
|
||||
+ TPMBlobBuffers *tpm_blobs);
|
||||
+
|
||||
#endif /* TPM_TPM_UTIL_H */
|
||||
diff --git a/include/sysemu/tpm_backend.h b/include/sysemu/tpm_backend.h
|
||||
index b58f52d39f..3403821b9d 100644
|
||||
--- a/include/sysemu/tpm_backend.h
|
||||
+++ b/include/sysemu/tpm_backend.h
|
||||
@@ -62,6 +62,18 @@ typedef struct TPMSizedBuffer {
|
||||
uint8_t *buffer;
|
||||
} TPMSizedBuffer;
|
||||
|
||||
+/* blobs from the TPM; part of VM state when migrating */
|
||||
+typedef struct TPMBlobBuffers {
|
||||
+ uint32_t permanent_flags;
|
||||
+ TPMSizedBuffer permanent;
|
||||
+
|
||||
+ uint32_t volatil_flags;
|
||||
+ TPMSizedBuffer volatil;
|
||||
+
|
||||
+ uint32_t savestate_flags;
|
||||
+ TPMSizedBuffer savestate;
|
||||
+} TPMBlobBuffers;
|
||||
+
|
||||
struct TPMDriverOps {
|
||||
enum TpmType type;
|
||||
const QemuOptDesc *opts;
|
||||
--
|
||||
2.11.0
|
||||
|
||||
@@ -11,6 +11,13 @@ SRC_URI += "file://configure-fix-Darwin-target-detection.patch \
|
||||
file://target-ppc-fix-user-mode.patch \
|
||||
"
|
||||
|
||||
SRC_URI += " \
|
||||
file://0001-Provide-support-for-the-CUSE-TPM.patch \
|
||||
file://0002-Introduce-condition-to-notify-waiters-of-completed-c.patch \
|
||||
file://0003-Introduce-condition-in-TPM-backend-for-notification.patch \
|
||||
file://0004-Add-support-for-VM-suspend-resume-for-TPM-TIS.patch \
|
||||
"
|
||||
|
||||
SRC_URI =+ "http://wiki.qemu-project.org/download/${BP}.tar.bz2"
|
||||
|
||||
SRC_URI[md5sum] = "17940dce063b6ce450a12e719a6c9c43"
|
||||
|
||||
Reference in New Issue
Block a user