Patchwork Add a TPM Passthrough backend driver implementation

login
register
mail settings
Submitter Andreas Niederl
Date Aug. 12, 2011, 3:31 p.m.
Message ID <1313163072-15152-1-git-send-email-andreas.niederl@iaik.tugraz.at>
Download mbox | patch
Permalink /patch/109859/
State New
Headers show

Comments

Andreas Niederl - Aug. 12, 2011, 3:31 p.m.
This patch is based of off version 7 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

Regards,
Andreas Niederl

[1] http://trustedjava.sourceforge.net/

Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
---
 Makefile.target      |    2 +-
 hw/tpm_passthrough.c |  450 ++++++++++++++++++++++++++++++++++++++++++++++++++
 tpm.c                |    1 +
 tpm.h                |    1 +
 4 files changed, 453 insertions(+), 1 deletions(-)
 create mode 100644 hw/tpm_passthrough.c
Stefan Berger - Aug. 15, 2011, 5:34 p.m.
On 08/12/2011 11:31 AM, Andreas Niederl wrote:
> This patch is based of off version 7 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
>
The code looks good to me. Though hopefully nothing on the host is using 
the TPM in this case so that all PCRs are cleared once the VM starts -- 
this may otherwise create problems with attestation.
Also, this device may need to be marked as unmigratable -- I am not sure 
how to do this though. There is an API in savevm.c to register a device 
as unmigratable, but I don't think we can call it 
(register_device_unmigratable()). It may either require some rearranging 
of code in tpm_tis where the VMStateDescription structure also has a 
field to mark a device as unmigratable (used by usb emulators for 
example) or the introduction of a new API call that allows a device to 
be marked as unmigratable after it has been registered.

Regards,
    Stefan

> Regards,
> Andreas Niederl
>
> [1] http://trustedjava.sourceforge.net/
>
> Signed-off-by: Andreas Niederl<andreas.niederl@iaik.tugraz.at>
> ---
>   Makefile.target      |    2 +-
>   hw/tpm_passthrough.c |  450 ++++++++++++++++++++++++++++++++++++++++++++++++++
>   tpm.c                |    1 +
>   tpm.h                |    1 +
>   4 files changed, 453 insertions(+), 1 deletions(-)
>   create mode 100644 hw/tpm_passthrough.c
>
> diff --git a/Makefile.target b/Makefile.target
> index f4d42d4..7f19f28 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -233,7 +233,7 @@ obj-i386-y += debugcon.o multiboot.o
>   obj-i386-y += pc_piix.o
>   obj-i386-$(CONFIG_KVM) += kvmclock.o
>   obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o
> -obj-i386-$(CONFIG_TPM) += tpm_tis.o sha1.o tpm_null.o
> +obj-i386-$(CONFIG_TPM) += tpm_tis.o sha1.o tpm_null.o tpm_passthrough.o
>   obj-i386-$(CONFIG_TPM_BUILTIN) += tpm_builtin.o
>
>   ifdef CONFIG_TPM_BUILTIN
> diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
> new file mode 100644
> index 0000000..aabfea2
> --- /dev/null
> +++ b/hw/tpm_passthrough.c
> @@ -0,0 +1,450 @@
> +/*
> + *  passthrough TPM driver
> + *
> + *  Copyright (c) 2010, 2011 IBM Corporation
> + *  Copyright (c) 2010, 2011 Stefan Berger
> + *
> + *  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 "tpm.h"
> +#include "hw/hw.h"
> +#include "hw/tpm_tis.h"
> +#include "hw/pc.h"
> +
> +
> +//#define DEBUG_TPM
> +//#define DEBUG_TPM_SR /* suspend - resume */
> +
> +
> +/* data structures */
> +
> +typedef struct ThreadParams {
> +    TPMState *tpm_state;
> +
> +    TPMRecvDataCB *recv_data_callback;
> +
> +    int fd;
> +} ThreadParams;
> +
> +
> +/* local variables */
> +
> +static QemuThread thread;
> +
> +static bool thread_terminate = false;
> +static bool thread_running = false;
> +
> +static ThreadParams tpm_thread_params;
> +
> +static const unsigned char tpm_std_fatal_error_response[10] = {
> +    0x00, 0xc4, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09 /* TPM_FAIL */
> +};
> +
> +static char dev_description[80];
> +
> +static char tpm_dev[255];
> +static int tpmfd = -1;
> +
> +static bool had_startup_error = false;
> +
> +
> +/* borrowed from qemu-char.c */
> +static int 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 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 void tpm_write_std_fatal_error_response(uint8_t *out,
> +        uint8_t *in, uint32_t in_len)
> +{
> +    memcpy(out, tpm_std_fatal_error_response,
> +            sizeof(tpm_std_fatal_error_response));
> +    out[1] = (in_len>  2&&  in[1]>= 0xc1&&  in[1]<= 0xc3)
> +        ? in[1] + 3
> +        : 0xc4;
> +}
> +
> +static void *tpm_passthrough_main_loop(void *d)
> +{
> +    ThreadParams *thr_parms = d;
> +    uint32_t in_len, out_len;
> +    uint8_t *in, *out;
> +    uint8_t locty;
> +    int ret;
> +
> +#ifdef DEBUG_TPM
> +    fprintf(stderr, "tpm: THREAD IS STARTING\n");
> +#endif
> +
> +    /* start command processing */
> +    while (!thread_terminate) {
> +        /* receive and handle commands */
> +        in_len = 0;
> +        do {
> +#ifdef DEBUG_TPM
> +            fprintf(stderr, "tpm: waiting for commands...\n");
> +#endif
> +
> +            if (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 (thread_terminate) {
> +                break;
> +            }
> +
> +            locty = thr_parms->tpm_state->command_locty;
> +
> +            in      = thr_parms->tpm_state->loc[locty].w_buffer.buffer;
> +            in_len  = thr_parms->tpm_state->loc[locty].w_offset;
> +            out     = thr_parms->tpm_state->loc[locty].r_buffer.buffer;
> +            out_len = thr_parms->tpm_state->loc[locty].r_buffer.size;
> +
> +            ret = unix_write(tpmfd, in, in_len);
> +            if (ret<  0) {
> +                fprintf(stderr, "tpm: error while transmitting data to host tpm"
> +                        ": %s (%i)\n",
> +                        strerror(errno), errno);
> +                tpm_write_std_fatal_error_response(out, in, in_len);
> +                continue;
> +            }
> +
> +            ret = unix_read(tpmfd, out, out_len);
> +            if (ret<  0) {
> +                fprintf(stderr, "tpm: error while reading data from host tpm"
> +                        ": %s (%i)\n",
> +                        strerror(errno), errno);
> +                tpm_write_std_fatal_error_response(out, in, in_len);
> +                continue;
> +            }
> +
> +            thr_parms->recv_data_callback(thr_parms->tpm_state, locty);
> +        } while (in_len>  0);
> +    }
> +
> +#ifdef DEBUG_TPM
> +    fprintf(stderr, "tpm: THREAD IS ENDING\n");
> +#endif
> +
> +    thread_running = false;
> +
> +    return NULL;
> +}
> +
> +
> +static void tpm_passthrough_terminate_tpm_thread(void)
> +{
> +    if (!thread_running) {
> +        return;
> +    }
> +
> +#if defined DEBUG_TPM || defined DEBUG_TPM_SR
> +    fprintf(stderr, "tpm: TERMINATING RUNNING TPM THREAD\n");
> +#endif
> +
> +    if (!thread_terminate) {
> +        thread_terminate = true;
> +
> +        qemu_mutex_lock  (&tpm_thread_params.tpm_state->state_lock);
> +        qemu_cond_signal (&tpm_thread_params.tpm_state->to_tpm_cond);
> +        qemu_mutex_unlock(&tpm_thread_params.tpm_state->state_lock);
> +
> +        while (thread_running) {
> +            usleep(100000);
> +        }
> +        memset(&thread, 0, sizeof(thread));
> +    }
> +}
> +
> +
> +static void tpm_passthrough_tpm_atexit(void)
> +{
> +    tpm_passthrough_terminate_tpm_thread();
> +
> +    close(tpmfd);
> +    tpmfd = -1;
> +}
> +
> +
> +/**
> + * Start the TPM (thread). If it had been started before, then terminate
> + * and start it again.
> + */
> +static int tpm_passthrough_startup_tpm(void)
> +{
> +    /* terminate a running TPM */
> +    tpm_passthrough_terminate_tpm_thread();
> +
> +    /* reset the flag so the thread keeps on running */
> +    thread_terminate = false;
> +
> +    qemu_thread_create(&thread, tpm_passthrough_main_loop,&tpm_thread_params);
> +
> +    thread_running = true;
> +
> +    return 0;
> +}
> +
> +
> +static int tpm_passthrough_do_startup_tpm(void)
> +{
> +    int rc;
> +
> +    rc = tpm_passthrough_startup_tpm();
> +    if (rc) {
> +        had_startup_error = true;
> +    }
> +    return rc;
> +}
> +
> +
> +static int tpm_passthrough_early_startup_tpm(void)
> +{
> +    return tpm_passthrough_do_startup_tpm();
> +}
> +
> +
> +static int tpm_passthrough_late_startup_tpm(void)
> +{
> +    return tpm_passthrough_do_startup_tpm();
> +}
> +
> +
> +static void tpm_passthrough_reset(void)
> +{
> +#if defined DEBUG_TPM || defined DEBUG_TPM_SR
> +    fprintf(stderr, "tpm: CALL TO TPM_RESET!\n");
> +#endif
> +
> +    tpm_passthrough_terminate_tpm_thread();
> +
> +    had_startup_error = false;
> +}
> +
> +
> +/*
> + * Since the null driver does not have much persistent storage
> + * there is not much to do here...
> + */
> +static int tpm_passthrough_instantiate_with_volatile_data(TPMState *s)
> +{
> +    if (thread_running) {
> +#ifdef DEBUG_TPM_SR
> +        fprintf(stderr, "tpm: This is resume of a SNAPSHOT\n");
> +#endif
> +        tis_reset_for_snapshot_resume(s);
> +    }
> +
> +    return 0;
> +}
> +
> +
> +static int tpm_passthrough_init(TPMState *s, TPMRecvDataCB *recv_data_cb)
> +{
> +    tpm_thread_params.tpm_state = s;
> +    tpm_thread_params.recv_data_callback = recv_data_cb;
> +
> +    tpmfd = open(tpm_dev, O_RDWR);
> +    if (tpmfd<  0) {
> +        fprintf(stderr,
> +                "Cannot open device '%s' from tpm's path option.\n",
> +                tpm_dev);
> +        goto err_exit;
> +    }
> +
> +    atexit(tpm_passthrough_tpm_atexit);
> +
> +    return 0;
> +
> +err_exit:
> +    if (tpmfd>= 0) {
> +        close(tpmfd);
> +        tpmfd = -1;
> +    }
> +    return 1;
> +}
> +
> +
> +static bool tpm_passthrough_get_tpm_established_flag(void)
> +{
> +    return false;
> +}
> +
> +
> +static bool tpm_passthrough_get_startup_error(void)
> +{
> +    return had_startup_error;
> +}
> +
> +
> +/**
> + * This function is called by tpm_tis.c once the TPM has processed
> + * the last command and returned the response to the TIS.
> + */
> +static int tpm_passthrough_save_volatile_data(void)
> +{
> +    return 0;
> +}
> +
> +
> +static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
> +{
> +    size_t wanted_size = 4096;
> +
> +    if (sb->size != wanted_size) {
> +        sb->buffer = qemu_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)
> +{
> +    static int done;
> +
> +    if (!done) {
> +        snprintf(dev_description, sizeof(dev_description),
> +                "Passthrough TPM backend driver");
> +        done = 1;
> +    }
> +
> +    return dev_description;
> +}
> +
> +
> +static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id,
> +        const char *model)
> +{
> +    TPMBackend *driver;
> +    const char *value;
> +
> +    driver = qemu_malloc(sizeof(TPMBackend));
> +    if (!driver) {
> +        fprintf(stderr, "Could not allocate memory.\n");
> +        return NULL;
> +    }
> +    driver->id = qemu_strdup(id);
> +    driver->model = NULL;
> +    if (model)
> +        driver->model = qemu_strdup(model);
> +    driver->ops =&tpm_passthrough_driver;
> +
> +    value = qemu_opt_get(opts, "path");
> +    if (value) {
> +        if (access(value, R_OK|W_OK)) {
> +            fprintf(stderr,
> +                    "Cannot access device '%s' from tpm's path option.\n",
> +                    value);
> +            goto err_exit;
> +        }
> +        strncpy(tpm_dev, value, sizeof(tpm_dev));
> +    } else {
> +        fprintf(stderr, "-tpm is missing path= parameter\n");
> +        goto err_exit;
> +    }
> +
> +    return driver;
> +
> +err_exit:
> +    qemu_free(driver->id);
> +    if (driver->model)
> +        qemu_free(driver->model);
> +    qemu_free(driver);
> +    return NULL;
> +}
> +
> +
> +static void tpm_passthrough_destroy(TPMBackend *driver)
> +{
> +    qemu_free(driver->id);
> +    if (driver->model)
> +        qemu_free(driver->model);
> +    qemu_free(driver);
> +}
> +
> +
> +TPMDriverOps tpm_passthrough_driver = {
> +    .id                       = "passthrough",
> +    .desc                     = tpm_passthrough_create_desc,
> +    .job_for_main_thread      = NULL,
> +    .create                   = tpm_passthrough_create,
> +    .destroy                  = tpm_passthrough_destroy,
> +    .init                     = tpm_passthrough_init,
> +    .early_startup_tpm        = tpm_passthrough_early_startup_tpm,
> +    .late_startup_tpm         = tpm_passthrough_late_startup_tpm,
> +    .realloc_buffer           = tpm_passthrough_realloc_buffer,
> +    .reset                    = tpm_passthrough_reset,
> +    .had_startup_error        = tpm_passthrough_get_startup_error,
> +    .save_volatile_data       = tpm_passthrough_save_volatile_data,
> +    .load_volatile_data       = tpm_passthrough_instantiate_with_volatile_data,
> +    .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
> +};
> diff --git a/tpm.c b/tpm.c
> index b75fe5c..edcbfe7 100644
> --- a/tpm.c
> +++ b/tpm.c
> @@ -28,6 +28,7 @@ static const TPMDriverOps *bes[] = {
>   #ifdef CONFIG_TPM_BUILTIN
>       &tpm_builtin,
>   #endif
> +&tpm_passthrough_driver,
>       NULL,
>   };
>
> diff --git a/tpm.h b/tpm.h
> index d00e599..01cae9a 100644
> --- a/tpm.h
> +++ b/tpm.h
> @@ -143,5 +143,6 @@ void tpm_measure_buffer(const void *buffer, long length,
>
>   extern TPMDriverOps tpm_null_driver;
>   extern TPMDriverOps tpm_builtin;
> +extern TPMDriverOps tpm_passthrough_driver;
>
>   #endif /* _HW_TPM_CONFIG_H */

Patch

diff --git a/Makefile.target b/Makefile.target
index f4d42d4..7f19f28 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -233,7 +233,7 @@  obj-i386-y += debugcon.o multiboot.o
 obj-i386-y += pc_piix.o
 obj-i386-$(CONFIG_KVM) += kvmclock.o
 obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o
-obj-i386-$(CONFIG_TPM) += tpm_tis.o sha1.o tpm_null.o
+obj-i386-$(CONFIG_TPM) += tpm_tis.o sha1.o tpm_null.o tpm_passthrough.o
 obj-i386-$(CONFIG_TPM_BUILTIN) += tpm_builtin.o
 
 ifdef CONFIG_TPM_BUILTIN
diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
new file mode 100644
index 0000000..aabfea2
--- /dev/null
+++ b/hw/tpm_passthrough.c
@@ -0,0 +1,450 @@ 
+/*
+ *  passthrough TPM driver
+ *
+ *  Copyright (c) 2010, 2011 IBM Corporation
+ *  Copyright (c) 2010, 2011 Stefan Berger
+ *
+ *  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 "tpm.h"
+#include "hw/hw.h"
+#include "hw/tpm_tis.h"
+#include "hw/pc.h"
+
+
+//#define DEBUG_TPM
+//#define DEBUG_TPM_SR /* suspend - resume */
+
+
+/* data structures */
+
+typedef struct ThreadParams {
+    TPMState *tpm_state;
+
+    TPMRecvDataCB *recv_data_callback;
+
+    int fd;
+} ThreadParams;
+
+
+/* local variables */
+
+static QemuThread thread;
+
+static bool thread_terminate = false;
+static bool thread_running = false;
+
+static ThreadParams tpm_thread_params;
+
+static const unsigned char tpm_std_fatal_error_response[10] = {
+    0x00, 0xc4, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09 /* TPM_FAIL */
+};
+
+static char dev_description[80];
+
+static char tpm_dev[255];
+static int tpmfd = -1;
+
+static bool had_startup_error = false;
+
+
+/* borrowed from qemu-char.c */
+static int 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 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 void tpm_write_std_fatal_error_response(uint8_t *out,
+        uint8_t *in, uint32_t in_len)
+{
+    memcpy(out, tpm_std_fatal_error_response,
+            sizeof(tpm_std_fatal_error_response));
+    out[1] = (in_len > 2 && in[1] >= 0xc1 && in[1] <= 0xc3)
+        ? in[1] + 3
+        : 0xc4;
+}
+
+static void *tpm_passthrough_main_loop(void *d)
+{
+    ThreadParams *thr_parms = d;
+    uint32_t in_len, out_len;
+    uint8_t *in, *out;
+    uint8_t locty;
+    int ret;
+
+#ifdef DEBUG_TPM
+    fprintf(stderr, "tpm: THREAD IS STARTING\n");
+#endif
+
+    /* start command processing */
+    while (!thread_terminate) {
+        /* receive and handle commands */
+        in_len = 0;
+        do {
+#ifdef DEBUG_TPM
+            fprintf(stderr, "tpm: waiting for commands...\n");
+#endif
+
+            if (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 (thread_terminate) {
+                break;
+            }
+
+            locty = thr_parms->tpm_state->command_locty;
+
+            in      = thr_parms->tpm_state->loc[locty].w_buffer.buffer;
+            in_len  = thr_parms->tpm_state->loc[locty].w_offset;
+            out     = thr_parms->tpm_state->loc[locty].r_buffer.buffer;
+            out_len = thr_parms->tpm_state->loc[locty].r_buffer.size;
+
+            ret = unix_write(tpmfd, in, in_len);
+            if (ret < 0) {
+                fprintf(stderr, "tpm: error while transmitting data to host tpm"
+                        ": %s (%i)\n",
+                        strerror(errno), errno);
+                tpm_write_std_fatal_error_response(out, in, in_len);
+                continue;
+            }
+
+            ret = unix_read(tpmfd, out, out_len);
+            if (ret < 0) {
+                fprintf(stderr, "tpm: error while reading data from host tpm"
+                        ": %s (%i)\n",
+                        strerror(errno), errno);
+                tpm_write_std_fatal_error_response(out, in, in_len);
+                continue;
+            }
+
+            thr_parms->recv_data_callback(thr_parms->tpm_state, locty);
+        } while (in_len > 0);
+    }
+
+#ifdef DEBUG_TPM
+    fprintf(stderr, "tpm: THREAD IS ENDING\n");
+#endif
+
+    thread_running = false;
+
+    return NULL;
+}
+
+
+static void tpm_passthrough_terminate_tpm_thread(void)
+{
+    if (!thread_running) {
+        return;
+    }
+
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    fprintf(stderr, "tpm: TERMINATING RUNNING TPM THREAD\n");
+#endif
+
+    if (!thread_terminate) {
+        thread_terminate = true;
+
+        qemu_mutex_lock  (&tpm_thread_params.tpm_state->state_lock);
+        qemu_cond_signal (&tpm_thread_params.tpm_state->to_tpm_cond);
+        qemu_mutex_unlock(&tpm_thread_params.tpm_state->state_lock);
+
+        while (thread_running) {
+            usleep(100000);
+        }
+        memset(&thread, 0, sizeof(thread));
+    }
+}
+
+
+static void tpm_passthrough_tpm_atexit(void)
+{
+    tpm_passthrough_terminate_tpm_thread();
+
+    close(tpmfd);
+    tpmfd = -1;
+}
+
+
+/**
+ * Start the TPM (thread). If it had been started before, then terminate
+ * and start it again.
+ */
+static int tpm_passthrough_startup_tpm(void)
+{
+    /* terminate a running TPM */
+    tpm_passthrough_terminate_tpm_thread();
+
+    /* reset the flag so the thread keeps on running */
+    thread_terminate = false;
+
+    qemu_thread_create(&thread, tpm_passthrough_main_loop, &tpm_thread_params);
+
+    thread_running = true;
+
+    return 0;
+}
+
+
+static int tpm_passthrough_do_startup_tpm(void)
+{
+    int rc;
+
+    rc = tpm_passthrough_startup_tpm();
+    if (rc) {
+        had_startup_error = true;
+    }
+    return rc;
+}
+
+
+static int tpm_passthrough_early_startup_tpm(void)
+{
+    return tpm_passthrough_do_startup_tpm();
+}
+
+
+static int tpm_passthrough_late_startup_tpm(void)
+{
+    return tpm_passthrough_do_startup_tpm();
+}
+
+
+static void tpm_passthrough_reset(void)
+{
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    fprintf(stderr, "tpm: CALL TO TPM_RESET!\n");
+#endif
+
+    tpm_passthrough_terminate_tpm_thread();
+
+    had_startup_error = false;
+}
+
+
+/*
+ * Since the null driver does not have much persistent storage
+ * there is not much to do here...
+ */
+static int tpm_passthrough_instantiate_with_volatile_data(TPMState *s)
+{
+    if (thread_running) {
+#ifdef DEBUG_TPM_SR
+        fprintf(stderr, "tpm: This is resume of a SNAPSHOT\n");
+#endif
+        tis_reset_for_snapshot_resume(s);
+    }
+
+    return 0;
+}
+
+
+static int tpm_passthrough_init(TPMState *s, TPMRecvDataCB *recv_data_cb)
+{
+    tpm_thread_params.tpm_state = s;
+    tpm_thread_params.recv_data_callback = recv_data_cb;
+
+    tpmfd = open(tpm_dev, O_RDWR);
+    if (tpmfd < 0) {
+        fprintf(stderr,
+                "Cannot open device '%s' from tpm's path option.\n",
+                tpm_dev);
+        goto err_exit;
+    }
+
+    atexit(tpm_passthrough_tpm_atexit);
+
+    return 0;
+
+err_exit:
+    if (tpmfd >= 0) {
+        close(tpmfd);
+        tpmfd = -1;
+    }
+    return 1;
+}
+
+
+static bool tpm_passthrough_get_tpm_established_flag(void)
+{
+    return false;
+}
+
+
+static bool tpm_passthrough_get_startup_error(void)
+{
+    return had_startup_error;
+}
+
+
+/**
+ * This function is called by tpm_tis.c once the TPM has processed
+ * the last command and returned the response to the TIS.
+ */
+static int tpm_passthrough_save_volatile_data(void)
+{
+    return 0;
+}
+
+
+static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
+{
+    size_t wanted_size = 4096;
+
+    if (sb->size != wanted_size) {
+        sb->buffer = qemu_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)
+{
+    static int done;
+
+    if (!done) {
+        snprintf(dev_description, sizeof(dev_description),
+                "Passthrough TPM backend driver");
+        done = 1;
+    }
+
+    return dev_description;
+}
+
+
+static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id,
+        const char *model)
+{
+    TPMBackend *driver;
+    const char *value;
+
+    driver = qemu_malloc(sizeof(TPMBackend));
+    if (!driver) {
+        fprintf(stderr, "Could not allocate memory.\n");
+        return NULL;
+    }
+    driver->id = qemu_strdup(id);
+    driver->model = NULL;
+    if (model)
+        driver->model = qemu_strdup(model);
+    driver->ops = &tpm_passthrough_driver;
+
+    value = qemu_opt_get(opts, "path");
+    if (value) {
+        if (access(value, R_OK|W_OK)) {
+            fprintf(stderr,
+                    "Cannot access device '%s' from tpm's path option.\n",
+                    value);
+            goto err_exit;
+        }
+        strncpy(tpm_dev, value, sizeof(tpm_dev));
+    } else {
+        fprintf(stderr, "-tpm is missing path= parameter\n");
+        goto err_exit;
+    }
+
+    return driver;
+
+err_exit:
+    qemu_free(driver->id);
+    if (driver->model)
+        qemu_free(driver->model);
+    qemu_free(driver);
+    return NULL;
+}
+
+
+static void tpm_passthrough_destroy(TPMBackend *driver)
+{
+    qemu_free(driver->id);
+    if (driver->model)
+        qemu_free(driver->model);
+    qemu_free(driver);
+}
+
+
+TPMDriverOps tpm_passthrough_driver = {
+    .id                       = "passthrough",
+    .desc                     = tpm_passthrough_create_desc,
+    .job_for_main_thread      = NULL,
+    .create                   = tpm_passthrough_create,
+    .destroy                  = tpm_passthrough_destroy,
+    .init                     = tpm_passthrough_init,
+    .early_startup_tpm        = tpm_passthrough_early_startup_tpm,
+    .late_startup_tpm         = tpm_passthrough_late_startup_tpm,
+    .realloc_buffer           = tpm_passthrough_realloc_buffer,
+    .reset                    = tpm_passthrough_reset,
+    .had_startup_error        = tpm_passthrough_get_startup_error,
+    .save_volatile_data       = tpm_passthrough_save_volatile_data,
+    .load_volatile_data       = tpm_passthrough_instantiate_with_volatile_data,
+    .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
+};
diff --git a/tpm.c b/tpm.c
index b75fe5c..edcbfe7 100644
--- a/tpm.c
+++ b/tpm.c
@@ -28,6 +28,7 @@  static const TPMDriverOps *bes[] = {
 #ifdef CONFIG_TPM_BUILTIN
     &tpm_builtin,
 #endif
+    &tpm_passthrough_driver,
     NULL,
 };
 
diff --git a/tpm.h b/tpm.h
index d00e599..01cae9a 100644
--- a/tpm.h
+++ b/tpm.h
@@ -143,5 +143,6 @@  void tpm_measure_buffer(const void *buffer, long length,
 
 extern TPMDriverOps tpm_null_driver;
 extern TPMDriverOps tpm_builtin;
+extern TPMDriverOps tpm_passthrough_driver;
 
 #endif /* _HW_TPM_CONFIG_H */