[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH V14 5/7] Add a TPM Passthrough backend driver implem
From: |
Stefan Berger |
Subject: |
[Qemu-devel] [PATCH V14 5/7] Add a TPM Passthrough backend driver implementation |
Date: |
Wed, 14 Dec 2011 08:43:20 -0500 |
>From Andreas Niederl's original posting with adaptations where necessary:
This patch is based of off version 9 of Stefan Berger's patch series
"Qemu Trusted Platform Module (TPM) integration"
and adds a new backend driver for it.
This patch adds a passthrough backend driver for passing commands sent to the
emulated TPM device directly to a TPM device opened on the host machine.
Thus it is possible to use a hardware TPM device in a system running on QEMU,
providing the ability to access a TPM in a special state (e.g. after a Trusted
Boot).
This functionality is being used in the acTvSM Trusted Virtualization Platform
which is available on [1].
Usage example:
qemu-system-x86_64 -tpmdev passthrough,id=tpm0,path=/dev/tpm0 \
-device tpm-tis,tpmdev=tpm0 \
-cdrom test.iso -boot d
Some notes about the host TPM:
The TPM needs to be enabled and activated. If that's not the case one
has to go through the BIOS/UEFI and enable and activate that TPM for TPM
commands to work as expected.
It may be necessary to boot the kernel using tpm_tis.force=1 in the boot
command line or 'modprobe tpm_tis force=1' in case of using it as a module.
Regards,
Andreas Niederl, Stefan Berger
[1] http://trustedjava.sourceforge.net/
Signed-off-by: Andreas Niederl <address@hidden>
Signed-off-by: Stefan Berger <address@hidden>
---
Makefile.target | 1 +
configure | 3 +
hw/tpm_passthrough.c | 493 ++++++++++++++++++++++++++++++++++++++++++++++++++
qemu-options.hx | 33 ++++
tpm.c | 16 ++
tpm.h | 33 ++++
6 files changed, 579 insertions(+), 0 deletions(-)
create mode 100644 hw/tpm_passthrough.c
diff --git a/Makefile.target b/Makefile.target
index 0e1c032..2744a5c 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -207,6 +207,7 @@ obj-$(CONFIG_NO_KVM) += kvm-stub.o
obj-y += memory.o
obj-y += tpm.o
obj-$(CONFIG_TPM) += tpm_tis.o
+obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
LIBS+=-lz
QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
diff --git a/configure b/configure
index 385feb4..25995bc 100755
--- a/configure
+++ b/configure
@@ -3721,6 +3721,9 @@ fi
if test "$tpm" = "yes"; then
if test "$target_softmmu" = "yes" ; then
+ if test "$linux" = "yes" ; then
+ echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_target_mak
+ fi
echo "CONFIG_TPM=y" >> $config_host_mak
fi
fi
diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
new file mode 100644
index 0000000..76e4e35
--- /dev/null
+++ b/hw/tpm_passthrough.c
@@ -0,0 +1,493 @@
+/*
+ * passthrough TPM driver
+ *
+ * Copyright (c) 2010, 2011 IBM Corporation
+ * Authors:
+ * Stefan Berger <address@hidden>
+ *
+ * Copyright (C) 2011 IAIK, Graz University of Technology
+ * Author: Andreas Niederl
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "qemu-common.h"
+#include "qemu-error.h"
+#include "tpm.h"
+#include "hw/hw.h"
+#include "hw/tpm_tis.h"
+#include "hw/pc.h"
+
+/* #define DEBUG_TPM */
+
+#ifdef DEBUG_TPM
+#define dprintf(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define dprintf(fmt, ...) \
+ do { } while (0)
+#endif
+
+/* data structures */
+
+typedef struct TPMPassthruThreadParams {
+ TPMState *tpm_state;
+
+ TPMRecvDataCB *recv_data_callback;
+ TPMBackend *tb;
+} TPMPassthruThreadParams;
+
+struct TPMPassthruState {
+ QemuThread thread;
+ bool thread_terminate;
+ bool thread_running;
+
+ TPMPassthruThreadParams tpm_thread_params;
+
+ char tpm_dev[64];
+ int tpm_fd;
+ bool had_startup_error;
+};
+
+#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
+
+/* borrowed from qemu-char.c */
+static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
+{
+ int ret, len1;
+
+ len1 = len;
+ while (len1 > 0) {
+ ret = write(fd, buf, len1);
+ if (ret < 0) {
+ if (errno != EINTR && errno != EAGAIN) {
+ return -1;
+ }
+ } else if (ret == 0) {
+ break;
+ } else {
+ buf += ret;
+ len1 -= ret;
+ }
+ }
+ return len - len1;
+}
+
+static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
+{
+ int ret, len1;
+ uint8_t *buf1;
+
+ len1 = len;
+ buf1 = buf;
+ while ((len1 > 0) && (ret = read(fd, buf1, len1)) != 0) {
+ if (ret < 0) {
+ if (errno != EINTR && errno != EAGAIN) {
+ return -1;
+ }
+ } else {
+ buf1 += ret;
+ len1 -= ret;
+ }
+ }
+ return len - len1;
+}
+
+static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
+{
+ return be32_to_cpu(*(uint32_t *)&buf[2]);
+}
+
+static int tpm_passthrough_unix_tx_bufs(int tpm_fd,
+ const uint8_t *in, uint32_t in_len,
+ uint8_t *out, uint32_t out_len)
+{
+ int ret;
+
+ ret = tpm_passthrough_unix_write(tpm_fd, in, in_len);
+ if (ret < 0) {
+ error_report("tpm_passthrough: error while transmitting data "
+ "to TPM: %s (%i)\n",
+ strerror(errno), errno);
+ goto err_exit;
+ }
+
+ ret = tpm_passthrough_unix_read(tpm_fd, out, out_len);
+ if (ret < 0) {
+ error_report("tpm_passthrough: error while reading data from "
+ "TPM: %s (%i)\n",
+ strerror(errno), errno);
+ } else if (ret < sizeof(struct tpm_resp_hdr) ||
+ tpm_passthrough_get_size_from_buffer(out) != ret) {
+ ret = -1;
+ error_report("tpm_passthrough: received invalid response "
+ "packet from TPM\n");
+ }
+
+err_exit:
+ if (ret < 0) {
+ tpm_write_fatal_error_response(out, out_len);
+ }
+
+ return ret;
+}
+
+static int tpm_passthrough_unix_transfer(int tpm_fd,
+ const TPMLocality *cmd_locty)
+{
+ return tpm_passthrough_unix_tx_bufs(tpm_fd,
+ cmd_locty->w_buffer.buffer,
+ cmd_locty->w_offset,
+ cmd_locty->r_buffer.buffer,
+ cmd_locty->r_buffer.size);
+}
+
+static void *tpm_passthrough_thread(void *d)
+{
+ TPMPassthruThreadParams *thr_parms = d;
+ TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
+ uint32_t in_len;
+
+ dprintf("tpm_passthrough: THREAD IS STARTING\n");
+
+ /* start command processing */
+ while (!tpm_pt->thread_terminate) {
+ /* receive and handle commands */
+ in_len = 0;
+ do {
+ dprintf("tpm_passthrough: waiting for commands...\n");
+
+ if (tpm_pt->thread_terminate) {
+ break;
+ }
+
+ qemu_mutex_lock(&thr_parms->tpm_state->state_lock);
+
+ /* in case we were to slow and missed the signal, the
+ to_tpm_execute boolean tells us about a pending command */
+ if (!thr_parms->tpm_state->to_tpm_execute) {
+ qemu_cond_wait(&thr_parms->tpm_state->to_tpm_cond,
+ &thr_parms->tpm_state->state_lock);
+ }
+
+ thr_parms->tpm_state->to_tpm_execute = false;
+
+ qemu_mutex_unlock(&thr_parms->tpm_state->state_lock);
+
+ if (tpm_pt->thread_terminate) {
+ break;
+ }
+
+ in_len = thr_parms->tpm_state->cmd_locty->w_offset;
+
+ tpm_passthrough_unix_transfer(tpm_pt->tpm_fd,
+ thr_parms->tpm_state->cmd_locty);
+
+ thr_parms->recv_data_callback(thr_parms->tpm_state,
+ thr_parms->tpm_state->command_locty);
+ } while (in_len > 0);
+ }
+
+ dprintf("tpm_passthrough: THREAD IS ENDING\n");
+
+ tpm_pt->thread_running = false;
+
+ return NULL;
+}
+
+static void tpm_passthrough_terminate_tpm_thread(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ if (!tpm_pt->thread_running) {
+ return;
+ }
+
+ dprintf("tpm_passthrough: TERMINATING RUNNING TPM THREAD\n");
+
+ if (!tpm_pt->thread_terminate) {
+ tpm_pt->thread_terminate = true;
+
+ qemu_mutex_lock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);
+ qemu_cond_signal(&tpm_pt->tpm_thread_params.tpm_state->to_tpm_cond);
+ qemu_mutex_unlock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);
+
+ if (tpm_pt->thread_running) {
+ qemu_thread_join(&tpm_pt->thread);
+ }
+ memset(&tpm_pt->thread, 0, sizeof(tpm_pt->thread));
+ }
+}
+
+/**
+ * Start the TPM (thread). If it had been started before, then terminate
+ * and start it again.
+ */
+static int tpm_passthrough_do_startup_tpm(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ /* terminate a running TPM */
+ tpm_passthrough_terminate_tpm_thread(tb);
+
+ /* reset the flag so the thread keeps on running */
+ tpm_pt->thread_terminate = false;
+
+ qemu_thread_create(&tpm_pt->thread, tpm_passthrough_thread,
+ &tpm_pt->tpm_thread_params, 0);
+
+ tpm_pt->thread_running = true;
+
+ return 0;
+}
+
+static int tpm_passthrough_startup_tpm(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+ int rc;
+
+ rc = tpm_passthrough_do_startup_tpm(tb);
+ if (rc) {
+ tpm_pt->had_startup_error = true;
+ }
+ return rc;
+}
+
+static void tpm_passthrough_reset(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ dprintf("tpm_passthrough: CALL TO TPM_RESET!\n");
+
+ tpm_passthrough_terminate_tpm_thread(tb);
+
+ tpm_pt->had_startup_error = false;
+}
+
+static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
+ TPMRecvDataCB *recv_data_cb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ tpm_pt->tpm_thread_params.tpm_state = s;
+ tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb;
+ tpm_pt->tpm_thread_params.tb = tb;
+
+ tpm_pt->thread_running = false;
+
+ return 0;
+}
+
+static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
+{
+ return false;
+}
+
+static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ return tpm_pt->had_startup_error;
+}
+
+static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
+{
+ size_t wanted_size = 4096; /* Linux tpm.c buffer size */
+
+ if (sb->size != wanted_size) {
+ sb->buffer = g_realloc(sb->buffer, wanted_size);
+ if (sb->buffer != NULL) {
+ sb->size = wanted_size;
+ } else {
+ sb->size = 0;
+ }
+ }
+ return sb->size;
+}
+
+static const char *tpm_passthrough_create_desc(void)
+{
+ return "Passthrough TPM backend driver";
+}
+
+/* A basic test of a TPM device. We expect a well formatted response header
+ * (error response is fine) within one second.
+ */
+static int tpm_passthrough_test_tpmdev(int fd)
+{
+ struct tpm_req_hdr req = {
+ .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND),
+ .len = cpu_to_be32(sizeof(req)),
+ .ordinal = cpu_to_be32(TPM_ORD_GetTicks),
+ };
+ struct tpm_resp_hdr *resp;
+ fd_set readfds;
+ int n;
+ struct timeval tv = {
+ .tv_sec = 1,
+ .tv_usec = 0,
+ };
+ unsigned char buf[1024];
+
+ n = write(fd, &req, sizeof(req));
+ if (n < 0) {
+ return errno;
+ }
+ if (n != sizeof(req)) {
+ return EFAULT;
+ }
+
+ FD_ZERO(&readfds);
+ FD_SET(fd, &readfds);
+
+ /* wait for a second */
+ n = select(fd + 1, &readfds, NULL, NULL, &tv);
+ if (n != 1) {
+ return errno;
+ }
+
+ n = read(fd, &buf, sizeof(buf));
+ if (n < sizeof(struct tpm_resp_hdr)) {
+ return EFAULT;
+ }
+
+ resp = (struct tpm_resp_hdr *)buf;
+ /* check the header */
+ if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND ||
+ be32_to_cpu(resp->len) != n) {
+ return EBADMSG;
+ }
+
+ return 0;
+}
+
+static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
+{
+ const char *value;
+ char buf[64];
+ int n;
+
+ value = qemu_opt_get(opts, "path");
+ if (!value) {
+ value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
+ }
+
+ n = snprintf(tb->s.tpm_pt->tpm_dev, sizeof(tb->s.tpm_pt->tpm_dev),
+ "%s", value);
+
+ if (n >= sizeof(tb->s.tpm_pt->tpm_dev)) {
+ error_report("TPM device path is too long.\n");
+ goto err_exit;
+ }
+
+ snprintf(buf, sizeof(buf), "path=%s", tb->s.tpm_pt->tpm_dev);
+
+ tb->parameters = g_strdup(buf);
+
+ if (tb->parameters == NULL) {
+ return 1;
+ }
+
+ tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
+ if (tb->s.tpm_pt->tpm_fd < 0) {
+ error_report("Cannot access TPM device using '%s'.\n",
+ tb->s.tpm_pt->tpm_dev);
+ goto err_exit;
+ }
+
+ if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
+ error_report("'%s' is not a TPM device.\n",
+ tb->s.tpm_pt->tpm_dev);
+ goto err_close_tpmdev;
+ }
+
+ return 0;
+
+ err_close_tpmdev:
+ close(tb->s.tpm_pt->tpm_fd);
+ tb->s.tpm_pt->tpm_fd = -1;
+
+ err_exit:
+ g_free(tb->parameters);
+ tb->parameters = NULL;
+
+ return 1;
+}
+
+
+static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
+{
+ TPMBackend *tb;
+
+ tb = g_malloc0(sizeof(TPMBackend));
+ if (tb == NULL) {
+ error_report("tpm_passthrough: Could not allocate memory.\n");
+ return NULL;
+ }
+
+ tb->s.tpm_pt = g_malloc0(sizeof(TPMPassthruState));
+ if (tb->s.tpm_pt == NULL) {
+ error_report("tpm_passthrough: Could not allocate memory.\n");
+ g_free(tb);
+ return NULL;
+ }
+
+ tb->id = g_strdup(id);
+ if (tb->id == NULL) {
+ error_report("tpm_passthrough: Could not allocate memory.\n");
+ goto err_exit;
+ }
+
+ tb->ops = &tpm_passthrough_driver;
+
+ if (tpm_passthrough_handle_device_opts(opts, tb)) {
+ goto err_exit;
+ }
+
+ return tb;
+
+err_exit:
+ g_free(tb->id);
+ g_free(tb->s.tpm_pt);
+ g_free(tb);
+
+ return NULL;
+}
+
+static void tpm_passthrough_destroy(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ tpm_passthrough_terminate_tpm_thread(tb);
+
+ close(tpm_pt->tpm_fd);
+
+ g_free(tb->id);
+ g_free(tb->parameters);
+ g_free(tb->s.tpm_pt);
+ g_free(tb);
+}
+
+const TPMDriverOps tpm_passthrough_driver = {
+ .id = "passthrough",
+ .desc = tpm_passthrough_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,
+ .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
+};
diff --git a/qemu-options.hx b/qemu-options.hx
index 40a63b1..10ae499 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1916,6 +1916,7 @@ The general form of a TPM device option is:
@item -tpmdev @var{backend} ,address@hidden [,@var{options}]
@findex -tpmdev
Backend type must be:
address@hidden
The specific backend type will determine the applicable options.
The @code{-tpmdev} options requires a @code{-device} option.
@@ -1927,6 +1928,38 @@ Use ? to print all available TPM backend types.
qemu -tpmdev ?
@end example
address@hidden -tpmdev passthrough, address@hidden, address@hidden
+
+(Linux-host only) Enable access to the host's TPM using the passthrough
+driver.
+
address@hidden specifies the path to the host's TPM device, i.e., on
+a Linux host this would be @code{/dev/tpm0}.
address@hidden is optional and by default @code{/dev/tpm0} is used.
+
+Some notes about using the host's TPM with the passthrough driver:
+
+The TPM device accessed by the passthrough driver must not be
+used by any other application on the host.
+
+Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
+the VM's firmware (BIOS/UEFI) will not be able to initialize the
+TPM again and may therefore not show a TPM-specific menu that would
+otherwise allow the user to configure the TPM, e.g., allow the user to
+enable/disable or activate/deactivate the TPM.
+Further, if TPM ownership is released from within a VM then the host's TPM
+will get disabled and deactivated. To enable and activate the
+TPM again afterwards, the host has to be rebooted and the user is
+required to enter the firmware's menu to enable and activate the TPM.
+If the TPM is left disabled and/or deactivated most TPM commands will fail.
+
+To create a passthrough TPM use the following two options:
address@hidden
+-tpmdev passthrough,id=tpm0 -device tpm-tis,tpmdev=tpm0
address@hidden example
+Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by
address@hidden in the device option.
+
@end table
ETEXI
diff --git a/tpm.c b/tpm.c
index 408d319..ee38107 100644
--- a/tpm.c
+++ b/tpm.c
@@ -24,9 +24,25 @@ static QLIST_HEAD(, TPMBackend) tpm_backends =
#ifdef CONFIG_TPM
static const TPMDriverOps *bes[] = {
+#ifdef CONFIG_TPM_PASSTHROUGH
+ &tpm_passthrough_driver,
+#endif
NULL,
};
+/* Write an error message in the given output buffer.
+ */
+void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len)
+{
+ if (out_len >= sizeof(struct tpm_resp_hdr)) {
+ struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
+
+ resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND);
+ resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
+ resp->errcode = cpu_to_be32(TPM_FAIL);
+ }
+}
+
const TPMDriverOps *tpm_get_backend_driver(const char *id)
{
int i;
diff --git a/tpm.h b/tpm.h
index 075de3f..1b278cc 100644
--- a/tpm.h
+++ b/tpm.h
@@ -18,12 +18,18 @@
struct TPMDriverOps;
typedef struct TPMDriverOps TPMDriverOps;
+typedef struct TPMPassthruState TPMPassthruState;
+
typedef struct TPMBackend {
char *id;
const char *fe_model;
char *parameters;
const TPMDriverOps *ops;
+ union {
+ TPMPassthruState *tpm_pt;
+ } s;
+
QLIST_ENTRY(TPMBackend) list;
} TPMBackend;
@@ -76,11 +82,38 @@ struct TPMDriverOps {
#define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
+struct tpm_req_hdr {
+ uint16_t tag;
+ uint32_t len;
+ uint32_t ordinal;
+} __attribute__((packed));
+
+struct tpm_resp_hdr {
+ uint16_t tag;
+ uint32_t len;
+ uint32_t errcode;
+} __attribute__((packed));
+
+#define TPM_TAG_RQU_COMMAND 0xc1
+#define TPM_TAG_RQU_AUTH1_COMMAND 0xc2
+#define TPM_TAG_RQU_AUTH2_COMMAND 0xc3
+
+#define TPM_TAG_RSP_COMMAND 0xc4
+#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5
+#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6
+
+#define TPM_FAIL 9
+
+#define TPM_ORD_GetTicks 0xf1
+
int tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
int tpm_init(void);
void tpm_cleanup(void);
TPMBackend *qemu_find_tpm(const char *id);
void tpm_display_backend_drivers(void);
const TPMDriverOps *tpm_get_backend_driver(const char *id);
+void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len);
+
+extern const TPMDriverOps tpm_passthrough_driver;
#endif /* QEMU_TPM_H */
--
1.7.6.4
- [Qemu-devel] [PATCH V14 0/7] Qemu Trusted Platform Module (TPM) integration, Stefan Berger, 2011/12/14
- [Qemu-devel] [PATCH V14 6/7] Introduce --enable-tpm-passthrough configure option, Stefan Berger, 2011/12/14
- [Qemu-devel] [PATCH V14 7/7] Add fd parameter for TPM passthrough driver, Stefan Berger, 2011/12/14
- [Qemu-devel] [PATCH V14 1/7] Support for TPM command line options, Stefan Berger, 2011/12/14
- [Qemu-devel] [PATCH V14 3/7] Add a debug register, Stefan Berger, 2011/12/14
- [Qemu-devel] [PATCH V14 5/7] Add a TPM Passthrough backend driver implementation,
Stefan Berger <=
- [Qemu-devel] [PATCH V14 2/7] Add TPM (frontend) hardware interface (TPM TIS) to Qemu, Stefan Berger, 2011/12/14
- [Qemu-devel] [PATCH V14 4/7] Build the TPM frontend code, Stefan Berger, 2011/12/14