diff mbox

[V9,5/5] Add a TPM Passthrough backend driver implementation

Message ID 20110926163731.347726806@linux.vnet.ibm.com
State New
Headers show

Commit Message

Stefan Berger Sept. 26, 2011, 4:35 p.m. UTC
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.


Changes for v9:
 - prefixing of all functions and variables with tpm_passthrough_
 - cleanup of all variables into a structure that is now accessed
   using TPMBackend (tb->s.tpm_pt)
 - build it on Linux machines
 - added function to test whether given device is a TPM and refuse
   startup if it is not

Regards,
Andreas Niederl, Stefan Berger

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

Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>

---
 Makefile.target      |    1 
 configure            |    3 
 hw/tpm_passthrough.c |  458 +++++++++++++++++++++++++++++++++++++++++++++++++++
 qemu-options.hx      |   24 ++
 tpm.c                |   21 ++
 tpm.h                |   34 +++
 6 files changed, 541 insertions(+)
 create mode 100644 hw/tpm_passthrough.c

Comments

Michael S. Tsirkin Sept. 26, 2011, 7:20 p.m. UTC | #1
On Mon, Sep 26, 2011 at 12:35:14PM -0400, Stefan Berger wrote:
> >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.
> 
> 
> Changes for v9:
>  - prefixing of all functions and variables with tpm_passthrough_
>  - cleanup of all variables into a structure that is now accessed
>    using TPMBackend (tb->s.tpm_pt)
>  - build it on Linux machines
>  - added function to test whether given device is a TPM and refuse
>    startup if it is not
> 
> Regards,
> Andreas Niederl, Stefan Berger
> 
> [1] http://trustedjava.sourceforge.net/
> 
> Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> 
> ---
>  Makefile.target      |    1 
>  configure            |    3 
>  hw/tpm_passthrough.c |  458 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  qemu-options.hx      |   24 ++
>  tpm.c                |   21 ++
>  tpm.h                |   34 +++
>  6 files changed, 541 insertions(+)
>  create mode 100644 hw/tpm_passthrough.c
> 
> Index: qemu-git.pt/Makefile.target
> ===================================================================
> --- qemu-git.pt.orig/Makefile.target
> +++ qemu-git.pt/Makefile.target
> @@ -195,6 +195,7 @@ obj-$(CONFIG_KVM) += kvm.o kvm-all.o
>  obj-$(CONFIG_NO_KVM) += kvm-stub.o
>  obj-y += memory.o
>  obj-$(CONFIG_TPM) += tpm.o tpm_tis.o
> +obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
>  LIBS+=-lz
>  
>  QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
> Index: qemu-git.pt/configure
> ===================================================================
> --- qemu-git.pt.orig/configure
> +++ qemu-git.pt/configure
> @@ -3565,6 +3565,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
> Index: qemu-git.pt/hw/tpm_passthrough.c
> ===================================================================
> --- /dev/null
> +++ qemu-git.pt/hw/tpm_passthrough.c
> @@ -0,0 +1,458 @@
> +/*
> + *  passthrough TPM driver
> + *
> + *  Copyright (c) 2010, 2011 IBM Corporation
> + *  Authors:
> + *    Stefan Berger <stefanb@us.ibm.com>
> + *
> + *  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 */
> +
> +/* data structures */
> +
> +typedef struct ThreadParams {
> +    TPMState *tpm_state;
> +
> +    TPMRecvDataCB *recv_data_callback;
> +    TPMBackend *tb;
> +} ThreadParams;

TPMPassthruThreadParams?

> +
> +struct TPMPassthruState {
> +    QemuThread thread;
> +    bool thread_terminate;
> +    bool thread_running;
> +
> +    ThreadParams tpm_thread_params;
> +
> +    char tpm_dev[64];
> +    int tpm_fd;
> +    bool had_startup_error;
> +};
> +
> +/* 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 void *tpm_passthrough_main_loop(void *d)
> +{
> +    ThreadParams *thr_parms = d;
> +    TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
> +    uint32_t in_len, out_len;
> +    uint8_t *in, *out;
> +    uint8_t locty;
> +    TPMLocality *cmd_locty;
> +    int ret;
> +
> +#ifdef DEBUG_TPM
> +    fprintf(stderr, "tpm_passthrough: THREAD IS STARTING\n");
> +#endif
> +
> +    /* start command processing */
> +    while (!tpm_pt->thread_terminate) {
> +        /* receive and handle commands */
> +        in_len = 0;
> +        do {
> +#ifdef DEBUG_TPM
> +            fprintf(stderr, "tpm_passthrough: waiting for commands...\n");
> +#endif
> +
> +            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;
> +            }
> +
> +            locty = thr_parms->tpm_state->command_locty;
> +
> +            cmd_locty = thr_parms->tpm_state->cmd_locty;
> +
> +            in      = cmd_locty->w_buffer.buffer;
> +            in_len  = cmd_locty->w_offset;
> +            out     = cmd_locty->r_buffer.buffer;
> +            out_len = cmd_locty->r_buffer.size;
> +
> +            ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len);
> +            if (ret < 0) {
> +                fprintf(stderr,
> +                        "tpm_passthrough: error while transmitting data "
> +                        "to host tpm: %s (%i)\n",
> +                        strerror(errno), errno);
> +                tpm_write_std_fatal_error_response(out, out_len, in, in_len);
> +                goto send_resp;
> +            }
> +
> +            ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len);
> +            if (ret < 0) {
> +                fprintf(stderr,
> +                        "tpm_passthrough: error while reading data from host "
> +                        "tpm : %s (%i)\n",
> +                        strerror(errno), errno);
> +                tpm_write_std_fatal_error_response(out, out_len, in, in_len);
> +            }
> +
> +send_resp:
> +            thr_parms->recv_data_callback(thr_parms->tpm_state, locty);
> +        } while (in_len > 0);
> +    }
> +
> +#ifdef DEBUG_TPM
> +    fprintf(stderr, "tpm_passthrough: THREAD IS ENDING\n");
> +#endif
> +
> +    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;
> +    }
> +
> +#if defined DEBUG_TPM
> +    fprintf(stderr, "tpm_passthrough: TERMINATING RUNNING TPM THREAD\n");
> +#endif
> +
> +    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);
> +
> +        while (tpm_pt->thread_running) {
> +            usleep(100000);
> +        }
> +        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_main_loop,
> +                       &tpm_pt->tpm_thread_params);
> +
> +    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;
> +
> +#if defined DEBUG_TPM
> +    fprintf(stderr, "tpm_passthrough: CALL TO TPM_RESET!\n");
> +#endif
> +
> +    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;
> +
> +    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 TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id,
> +                                          const char *model)
> +{
> +    TPMBackend *tb;
> +    const char *value;
> +    char buf[64];
> +    int n;
> +
> +    tb = g_malloc(sizeof(TPMBackend));
> +    if (tb == NULL) {
> +        fprintf(stderr, "tpm_passthrough: Could not allocate memory.\n");
> +        return NULL;
> +    }
> +
> +    tb->s.tpm_pt = g_malloc(sizeof(TPMPassthruState));
> +    if (tb->s.tpm_pt == NULL) {
> +        fprintf(stderr, "tpm_passthrough: Could not allocate memory.\n");
> +        g_free(tb);
> +        return NULL;
> +    }
> +
> +    tb->id = g_strdup(id);
> +    tb->model = NULL;
> +    if (model) {
> +        tb->model = g_strdup(model);
> +    }
> +    tb->ops = &tpm_passthrough_driver;
> +
> +    value = qemu_opt_get(opts, "path");
> +    if (value) {
> +        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)) {
> +            fprintf(stderr, "TPM device path is too long.\n");

error_report?

> +            goto err_exit;
> +        }
> +
> +        snprintf(buf, sizeof(buf), "path=%s", tb->s.tpm_pt->tpm_dev);
> +
> +        tb->parameters = g_strdup(buf);
> +
> +        if (tb->parameters == NULL) {
> +            goto err_exit;
> +        }
> +
> +        tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
> +        if (tb->s.tpm_pt->tpm_fd < 0) {
> +            fprintf(stderr,
> +                    "Cannot open device '%s' from TPM's path option.\n",
> +                    tb->s.tpm_pt->tpm_dev);
> +            goto err_exit;
> +        }
> +
> +    } else {
> +        fprintf(stderr, "-tpmdev is missing path= parameter\n");
> +        goto err_exit;
> +    }
> +

It's usually a good idea to allow passing the fd through
a unix file descriptor or command line. net has some code
for that, that can be generalized. Good for security
separation. It does not have to be part of this patch though, just
thinking aloud.


> +    if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
> +        fprintf(stderr,
> +                "'%s' is not a TPM device.\n",
> +                tb->s.tpm_pt->tpm_dev);
> +        goto err_close_tpmdev;

Is this a must? Is it common to have more than one
tpm device available on a computer? Maybe there's
a good default in case only one tpm exists there ...

> +    }
> +
> +    return tb;
> +
> +err_close_tpmdev:
> +    close(tb->s.tpm_pt->tpm_fd);
> +
> +err_exit:
> +    g_free(tb->id);
> +    g_free(tb->model);
> +    g_free(tb->parameters);
> +    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->model);
> +    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,
> +};
> Index: qemu-git.pt/qemu-options.hx
> ===================================================================
> --- qemu-git.pt.orig/qemu-options.hx
> +++ qemu-git.pt/qemu-options.hx
> @@ -1777,6 +1777,7 @@ The general form of a TPM device option 
>  @item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
>  @findex -tpmdev
>  Backend type must be:
> +@option{passthrough}.
>  
>  The specific backend type will determine the applicable options.
>  The @code{-tpmdev} options requires a @code{-device} option.
> @@ -1788,6 +1789,29 @@ Use ? to print all available TPM backend
>  qemu -tpmdev ?
>  @end example
>  
> +@item -tpmdev passthrough, id=@var{id}, path=@var{path}
> +
> +Enables access to the host's TPM using the passthrough driver.
> +
> +@option{path} specifies the path to the host's TPM device, i.e., on
> +a Linux host this would be @code{/dev/tpm0}.

So how about making this the default on linux?
Has passthough code any chance to work on non-linux btw?

> +
> +Note that the passthrough device must not be used by any application on
> +the host. Since the host's firmware has already initialized the TPM,
> +the firmware (BIOS) executed by QEMU will not be able to initialize the
> +TPM again and behave differently than if it could initialize the TPM.

For the benefit of users, could this text clarify what happens with
e.g. linux and windows guests in this case?


> +If TPM ownership is released from within a QEMU VM

When does this happen?

> then this requires

What requires?

> +rebooting of the host and entering the host's firmware

entering?

> to enable and activate
> +the TPM again.
> +@option{path} is required.
> +
> +To create a passthrough TPM use the following two options:
> +@example
> +-tpmdev pasthrough,id=tpm0,path=<path to TPM device> -device tpm-tis,tpmdev=tpm0
> +@end example
> +Not that the @code{-tpmdev} id is @code{tpm0} and is referenced by

Note?

> +@code{tpmdev=tpm0} in the device option.
> +
>  @end table
>  
>  ETEXI
Stefan Berger Sept. 26, 2011, 8:12 p.m. UTC | #2
On 09/26/2011 03:20 PM, Michael S. Tsirkin wrote:
> On Mon, Sep 26, 2011 at 12:35:14PM -0400, Stefan Berger wrote:
>> > 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.
>>
>>
>> Changes for v9:
>>   - prefixing of all functions and variables with tpm_passthrough_
>>   - cleanup of all variables into a structure that is now accessed
>>     using TPMBackend (tb->s.tpm_pt)
>>   - build it on Linux machines
>>   - added function to test whether given device is a TPM and refuse
>>     startup if it is not
>>
>> Regards,
>> Andreas Niederl, Stefan Berger
>>
>> [1] http://trustedjava.sourceforge.net/
>>
>> Signed-off-by: Andreas Niederl<andreas.niederl@iaik.tugraz.at>
>> Signed-off-by: Stefan Berger<stefanb@linux.vnet.ibm.com>
>>
>> ---
>>   Makefile.target      |    1
>>   configure            |    3
>>   hw/tpm_passthrough.c |  458 +++++++++++++++++++++++++++++++++++++++++++++++++++
>>   qemu-options.hx      |   24 ++
>>   tpm.c                |   21 ++
>>   tpm.h                |   34 +++
>>   6 files changed, 541 insertions(+)
>>   create mode 100644 hw/tpm_passthrough.c
>>
>> Index: qemu-git.pt/Makefile.target
>> ===================================================================
>> --- qemu-git.pt.orig/Makefile.target
>> +++ qemu-git.pt/Makefile.target
>> @@ -195,6 +195,7 @@ obj-$(CONFIG_KVM) += kvm.o kvm-all.o
>>   obj-$(CONFIG_NO_KVM) += kvm-stub.o
>>   obj-y += memory.o
>>   obj-$(CONFIG_TPM) += tpm.o tpm_tis.o
>> +obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
>>   LIBS+=-lz
>>
>>   QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
>> Index: qemu-git.pt/configure
>> ===================================================================
>> --- qemu-git.pt.orig/configure
>> +++ qemu-git.pt/configure
>> @@ -3565,6 +3565,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
>> Index: qemu-git.pt/hw/tpm_passthrough.c
>> ===================================================================
>> --- /dev/null
>> +++ qemu-git.pt/hw/tpm_passthrough.c
>> @@ -0,0 +1,458 @@
>> +/*
>> + *  passthrough TPM driver
>> + *
>> + *  Copyright (c) 2010, 2011 IBM Corporation
>> + *  Authors:
>> + *    Stefan Berger<stefanb@us.ibm.com>
>> + *
>> + *  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 */
>> +
>> +/* data structures */
>> +
>> +typedef struct ThreadParams {
>> +    TPMState *tpm_state;
>> +
>> +    TPMRecvDataCB *recv_data_callback;
>> +    TPMBackend *tb;
>> +} ThreadParams;
> TPMPassthruThreadParams?
Yes... Fixed.
>> +
>> +struct TPMPassthruState {
>> +    QemuThread thread;
>> +    bool thread_terminate;
>> +    bool thread_running;
>> +
>> +    ThreadParams tpm_thread_params;
>> +
>> +    char tpm_dev[64];
>> +    int tpm_fd;
>> +    bool had_startup_error;
>> +};
>> +
>> +/* 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 void *tpm_passthrough_main_loop(void *d)
>> +{
>> +    ThreadParams *thr_parms = d;
>> +    TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
>> +    uint32_t in_len, out_len;
>> +    uint8_t *in, *out;
>> +    uint8_t locty;
>> +    TPMLocality *cmd_locty;
>> +    int ret;
>> +
>> +#ifdef DEBUG_TPM
>> +    fprintf(stderr, "tpm_passthrough: THREAD IS STARTING\n");
>> +#endif
>> +
>> +    /* start command processing */
>> +    while (!tpm_pt->thread_terminate) {
>> +        /* receive and handle commands */
>> +        in_len = 0;
>> +        do {
>> +#ifdef DEBUG_TPM
>> +            fprintf(stderr, "tpm_passthrough: waiting for commands...\n");
>> +#endif
>> +
>> +            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;
>> +            }
>> +
>> +            locty = thr_parms->tpm_state->command_locty;
>> +
>> +            cmd_locty = thr_parms->tpm_state->cmd_locty;
>> +
>> +            in      = cmd_locty->w_buffer.buffer;
>> +            in_len  = cmd_locty->w_offset;
>> +            out     = cmd_locty->r_buffer.buffer;
>> +            out_len = cmd_locty->r_buffer.size;
>> +
>> +            ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len);
>> +            if (ret<  0) {
>> +                fprintf(stderr,
>> +                        "tpm_passthrough: error while transmitting data "
>> +                        "to host tpm: %s (%i)\n",
>> +                        strerror(errno), errno);
>> +                tpm_write_std_fatal_error_response(out, out_len, in, in_len);
>> +                goto send_resp;
>> +            }
>> +
>> +            ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len);
>> +            if (ret<  0) {
>> +                fprintf(stderr,
>> +                        "tpm_passthrough: error while reading data from host "
>> +                        "tpm : %s (%i)\n",
>> +                        strerror(errno), errno);
>> +                tpm_write_std_fatal_error_response(out, out_len, in, in_len);
>> +            }
>> +
>> +send_resp:
>> +            thr_parms->recv_data_callback(thr_parms->tpm_state, locty);
>> +        } while (in_len>  0);
>> +    }
>> +
>> +#ifdef DEBUG_TPM
>> +    fprintf(stderr, "tpm_passthrough: THREAD IS ENDING\n");
>> +#endif
>> +
>> +    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;
>> +    }
>> +
>> +#if defined DEBUG_TPM
>> +    fprintf(stderr, "tpm_passthrough: TERMINATING RUNNING TPM THREAD\n");
>> +#endif
>> +
>> +    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);
>> +
>> +        while (tpm_pt->thread_running) {
>> +            usleep(100000);
>> +        }
>> +        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_main_loop,
>> +&tpm_pt->tpm_thread_params);
>> +
>> +    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;
>> +
>> +#if defined DEBUG_TPM
>> +    fprintf(stderr, "tpm_passthrough: CALL TO TPM_RESET!\n");
>> +#endif
>> +
>> +    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;
>> +
>> +    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 TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id,
>> +                                          const char *model)
>> +{
>> +    TPMBackend *tb;
>> +    const char *value;
>> +    char buf[64];
>> +    int n;
>> +
>> +    tb = g_malloc(sizeof(TPMBackend));
>> +    if (tb == NULL) {
>> +        fprintf(stderr, "tpm_passthrough: Could not allocate memory.\n");
>> +        return NULL;
>> +    }
>> +
>> +    tb->s.tpm_pt = g_malloc(sizeof(TPMPassthruState));
>> +    if (tb->s.tpm_pt == NULL) {
>> +        fprintf(stderr, "tpm_passthrough: Could not allocate memory.\n");
>> +        g_free(tb);
>> +        return NULL;
>> +    }
>> +
>> +    tb->id = g_strdup(id);
>> +    tb->model = NULL;
>> +    if (model) {
>> +        tb->model = g_strdup(model);
>> +    }
>> +    tb->ops =&tpm_passthrough_driver;
>> +
>> +    value = qemu_opt_get(opts, "path");
>> +    if (value) {
>> +        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)) {
>> +            fprintf(stderr, "TPM device path is too long.\n");
> error_report?
Will use it.
>> +            goto err_exit;
>> +        }
>> +
>> +        snprintf(buf, sizeof(buf), "path=%s", tb->s.tpm_pt->tpm_dev);
>> +
>> +        tb->parameters = g_strdup(buf);
>> +
>> +        if (tb->parameters == NULL) {
>> +            goto err_exit;
>> +        }
>> +
>> +        tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
>> +        if (tb->s.tpm_pt->tpm_fd<  0) {
>> +            fprintf(stderr,
>> +                    "Cannot open device '%s' from TPM's path option.\n",
>> +                    tb->s.tpm_pt->tpm_dev);
>> +            goto err_exit;
>> +        }
>> +
>> +    } else {
>> +        fprintf(stderr, "-tpmdev is missing path= parameter\n");
>> +        goto err_exit;
>> +    }
>> +
> It's usually a good idea to allow passing the fd through
> a unix file descriptor or command line. net has some code
> for that, that can be generalized. Good for security
> separation. It does not have to be part of this patch though, just
> thinking aloud.
>
I also wanted to have that but would rather put that in another patch. 
Basically the following code from net.c needs to go into a separate 
function:

         char *endptr = NULL;

         fd = strtol(param, &endptr, 10);
         if (*endptr || (fd == 0 && param == endptr)) {
             return -1;
         }

My proposal would be to put it into

int qemu_parse_fd(const char *)

into qemu-char.c ?


>> +    if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
>> +        fprintf(stderr,
>> +                "'%s' is not a TPM device.\n",
>> +                tb->s.tpm_pt->tpm_dev);
>> +        goto err_close_tpmdev;
> Is this a must? Is it common to have more than one
> tpm device available on a computer? Maybe there's
> a good default in case only one tpm exists there ...
>
Well, passing /dev/tty48 in the place of /dev/tpm0 ends up in a 
disappointment. So I'd rather check what that device is and refuse to 
start if it is found not to be a TPM.

>> +    }
>> +
>> +    return tb;
>> +
>> +err_close_tpmdev:
>> +    close(tb->s.tpm_pt->tpm_fd);
>> +
>> +err_exit:
>> +    g_free(tb->id);
>> +    g_free(tb->model);
>> +    g_free(tb->parameters);
>> +    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->model);
>> +    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,
>> +};
>> Index: qemu-git.pt/qemu-options.hx
>> ===================================================================
>> --- qemu-git.pt.orig/qemu-options.hx
>> +++ qemu-git.pt/qemu-options.hx
>> @@ -1777,6 +1777,7 @@ The general form of a TPM device option
>>   @item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
>>   @findex -tpmdev
>>   Backend type must be:
>> +@option{passthrough}.
>>
>>   The specific backend type will determine the applicable options.
>>   The @code{-tpmdev} options requires a @code{-device} option.
>> @@ -1788,6 +1789,29 @@ Use ? to print all available TPM backend
>>   qemu -tpmdev ?
>>   @end example
>>
>> +@item -tpmdev passthrough, id=@var{id}, path=@var{path}
>> +
>> +Enables access to the host's TPM using the passthrough driver.
>> +
>> +@option{path} specifies the path to the host's TPM device, i.e., on
>> +a Linux host this would be @code{/dev/tpm0}.
> So how about making this the default on linux?
> Has passthough code any chance to work on non-linux btw?
>
For sure not on Win32. For other Unices I don't know.
>> +
>> +Note that the passthrough device must not be used by any application on
>> +the host. Since the host's firmware has already initialized the TPM,
>> +the firmware (BIOS) executed by QEMU will not be able to initialize the
>> +TPM again and behave differently than if it could initialize the TPM.
> For the benefit of users, could this text clarify what happens with
> e.g. linux and windows guests in this case?
>

I'll try to clarify.

FYI: The pending SeaBIOS patches would typically display a menu if they 
succeed in sending a (standard) initialization sequence to the TPM. But 
in this case the SeaBIOS patches aren't needed yet and if used will not 
succeed in sending that command sequence since the BIOS of the host 
already initialized the device.
>> +If TPM ownership is released from within a QEMU VM
> When does this happen?
>
tpm_clearown is a command line tool provided by the TrouSerS tss 
implementation in the tpm-tools package that allows you to release 
ownership of the device. If the VM user clears ownership (using the 
TPM's password), the device will become deactivated and disabled.
>> then this requires
> What requires?
>
>> +rebooting of the host and entering the host's firmware
> entering?
>
Go into the menu of the host's firmware (BIOS/UEFI).
>> to enable and activate
>> +the TPM again.
>> +@option{path} is required.
>> +
>> +To create a passthrough TPM use the following two options:
>> +@example
>> +-tpmdev pasthrough,id=tpm0,path=<path to TPM device>  -device tpm-tis,tpmdev=tpm0
>> +@end example
>> +Not that the @code{-tpmdev} id is @code{tpm0} and is referenced by
> Note?
>
:-/
>> +@code{tpmdev=tpm0} in the device option.
>> +
>>   @end table
>>
>>   ETEXI
Thanks for the review.

    Stefan
Michael S. Tsirkin Sept. 26, 2011, 8:24 p.m. UTC | #3
On Mon, Sep 26, 2011 at 04:12:19PM -0400, Stefan Berger wrote:
> >It's usually a good idea to allow passing the fd through
> >a unix file descriptor or command line. net has some code
> >for that, that can be generalized. Good for security
> >separation. It does not have to be part of this patch though, just
> >thinking aloud.
> >
> I also wanted to have that but would rather put that in another
> patch.

Yes, that's ok.

> Basically the following code from net.c needs to go into a
> separate function:
> 
>         char *endptr = NULL;
> 
>         fd = strtol(param, &endptr, 10);
>         if (*endptr || (fd == 0 && param == endptr)) {
>             return -1;
>         }
> 
> My proposal would be to put it into
> 
> int qemu_parse_fd(const char *)
> 
> into qemu-char.c ?

Sure.

> 
> >>+    if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
> >>+        fprintf(stderr,
> >>+                "'%s' is not a TPM device.\n",
> >>+                tb->s.tpm_pt->tpm_dev);
> >>+        goto err_close_tpmdev;
> >Is this a must? Is it common to have more than one
> >tpm device available on a computer? Maybe there's
> >a good default in case only one tpm exists there ...
> >
> Well, passing /dev/tty48 in the place of /dev/tpm0 ends up in a
> disappointment. So I'd rather check what that device is and refuse
> to start if it is found not to be a TPM.

Sorry, I mean can path= be made optional in case there's a single
/dev/tmpXXX? Is it even common to have any tpms except
/dev/tpm0?

> >>+    }
> >>+
> >>+    return tb;
> >>+
> >>+err_close_tpmdev:
> >>+    close(tb->s.tpm_pt->tpm_fd);
> >>+
> >>+err_exit:
> >>+    g_free(tb->id);
> >>+    g_free(tb->model);
> >>+    g_free(tb->parameters);
> >>+    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->model);
> >>+    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,
> >>+};
> >>Index: qemu-git.pt/qemu-options.hx
> >>===================================================================
> >>--- qemu-git.pt.orig/qemu-options.hx
> >>+++ qemu-git.pt/qemu-options.hx
> >>@@ -1777,6 +1777,7 @@ The general form of a TPM device option
> >>  @item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
> >>  @findex -tpmdev
> >>  Backend type must be:
> >>+@option{passthrough}.
> >>
> >>  The specific backend type will determine the applicable options.
> >>  The @code{-tpmdev} options requires a @code{-device} option.
> >>@@ -1788,6 +1789,29 @@ Use ? to print all available TPM backend
> >>  qemu -tpmdev ?
> >>  @end example
> >>
> >>+@item -tpmdev passthrough, id=@var{id}, path=@var{path}
> >>+
> >>+Enables access to the host's TPM using the passthrough driver.
> >>+
> >>+@option{path} specifies the path to the host's TPM device, i.e., on
> >>+a Linux host this would be @code{/dev/tpm0}.
> >So how about making this the default on linux?
> >Has passthough code any chance to work on non-linux btw?
> >
> For sure not on Win32. For other Unices I don't know.
> >>+
> >>+Note that the passthrough device must not be used by any application on
> >>+the host. Since the host's firmware has already initialized the TPM,
> >>+the firmware (BIOS) executed by QEMU will not be able to initialize the
> >>+TPM again and behave differently than if it could initialize the TPM.
> >For the benefit of users, could this text clarify what happens with
> >e.g. linux and windows guests in this case?
> >
> 
> I'll try to clarify.
> 
> FYI: The pending SeaBIOS patches would typically display a menu if
> they succeed in sending a (standard) initialization sequence to the
> TPM. But in this case the SeaBIOS patches aren't needed yet and if
> used will not succeed in sending that command sequence since the
> BIOS of the host already initialized the device.

OK. What happens then? It would be helpful
to describe the issue in terms of what the user sees.
I think it's ok to assume a patched seabios,
it will get merged by the time this all ships to users :)

> >>+If TPM ownership is released from within a QEMU VM
> >When does this happen?
> >
> tpm_clearown is a command line tool provided by the TrouSerS tss
> implementation in the tpm-tools package that allows you to release
> ownership of the device. If the VM user clears ownership (using the
> TPM's password), the device will become deactivated and disabled.
> >>then this requires
> >What requires?
> >
> >>+rebooting of the host and entering the host's firmware
> >entering?
> >
> Go into the menu of the host's firmware (BIOS/UEFI).

All good answers shall likely go into the documentation :)

> >>to enable and activate
> >>+the TPM again.
> >>+@option{path} is required.
> >>+
> >>+To create a passthrough TPM use the following two options:
> >>+@example
> >>+-tpmdev pasthrough,id=tpm0,path=<path to TPM device>  -device tpm-tis,tpmdev=tpm0
> >>+@end example
> >>+Not that the @code{-tpmdev} id is @code{tpm0} and is referenced by
> >Note?
> >
> :-/
> >>+@code{tpmdev=tpm0} in the device option.
> >>+
> >>  @end table
> >>
> >>  ETEXI
> Thanks for the review.
> 
>    Stefan
Stefan Berger Sept. 27, 2011, 2:20 a.m. UTC | #4
On 09/26/2011 04:24 PM, Michael S. Tsirkin wrote:
> On Mon, Sep 26, 2011 at 04:12:19PM -0400, Stefan Berger wrote:
>
>>>> +    if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
>>>> +        fprintf(stderr,
>>>> +                "'%s' is not a TPM device.\n",
>>>> +                tb->s.tpm_pt->tpm_dev);
>>>> +        goto err_close_tpmdev;
>>> Is this a must? Is it common to have more than one
>>> tpm device available on a computer? Maybe there's
>>> a good default in case only one tpm exists there ...
>>>
>> Well, passing /dev/tty48 in the place of /dev/tpm0 ends up in a
>> disappointment. So I'd rather check what that device is and refuse
>> to start if it is found not to be a TPM.
> Sorry, I mean can path= be made optional in case there's a single
> /dev/tmpXXX? Is it even common to have any tpms except
> /dev/tpm0?
No, typically there is only one TPM in the machine and it's accessible 
using /dev/tpm0. SR-IOV TPMs afaik don't exist. So, yes, I am adding 
'/dev/tpm0' as the default now.
>
> All good answers shall likely go into the documentation :)
I'll write about it... Will likely post v10 tomorrow.

    Stefan
diff mbox

Patch

Index: qemu-git.pt/Makefile.target
===================================================================
--- qemu-git.pt.orig/Makefile.target
+++ qemu-git.pt/Makefile.target
@@ -195,6 +195,7 @@  obj-$(CONFIG_KVM) += kvm.o kvm-all.o
 obj-$(CONFIG_NO_KVM) += kvm-stub.o
 obj-y += memory.o
 obj-$(CONFIG_TPM) += tpm.o tpm_tis.o
+obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
 LIBS+=-lz
 
 QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
Index: qemu-git.pt/configure
===================================================================
--- qemu-git.pt.orig/configure
+++ qemu-git.pt/configure
@@ -3565,6 +3565,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
Index: qemu-git.pt/hw/tpm_passthrough.c
===================================================================
--- /dev/null
+++ qemu-git.pt/hw/tpm_passthrough.c
@@ -0,0 +1,458 @@ 
+/*
+ *  passthrough TPM driver
+ *
+ *  Copyright (c) 2010, 2011 IBM Corporation
+ *  Authors:
+ *    Stefan Berger <stefanb@us.ibm.com>
+ *
+ *  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 */
+
+/* data structures */
+
+typedef struct ThreadParams {
+    TPMState *tpm_state;
+
+    TPMRecvDataCB *recv_data_callback;
+    TPMBackend *tb;
+} ThreadParams;
+
+struct TPMPassthruState {
+    QemuThread thread;
+    bool thread_terminate;
+    bool thread_running;
+
+    ThreadParams tpm_thread_params;
+
+    char tpm_dev[64];
+    int tpm_fd;
+    bool had_startup_error;
+};
+
+/* 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 void *tpm_passthrough_main_loop(void *d)
+{
+    ThreadParams *thr_parms = d;
+    TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
+    uint32_t in_len, out_len;
+    uint8_t *in, *out;
+    uint8_t locty;
+    TPMLocality *cmd_locty;
+    int ret;
+
+#ifdef DEBUG_TPM
+    fprintf(stderr, "tpm_passthrough: THREAD IS STARTING\n");
+#endif
+
+    /* start command processing */
+    while (!tpm_pt->thread_terminate) {
+        /* receive and handle commands */
+        in_len = 0;
+        do {
+#ifdef DEBUG_TPM
+            fprintf(stderr, "tpm_passthrough: waiting for commands...\n");
+#endif
+
+            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;
+            }
+
+            locty = thr_parms->tpm_state->command_locty;
+
+            cmd_locty = thr_parms->tpm_state->cmd_locty;
+
+            in      = cmd_locty->w_buffer.buffer;
+            in_len  = cmd_locty->w_offset;
+            out     = cmd_locty->r_buffer.buffer;
+            out_len = cmd_locty->r_buffer.size;
+
+            ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len);
+            if (ret < 0) {
+                fprintf(stderr,
+                        "tpm_passthrough: error while transmitting data "
+                        "to host tpm: %s (%i)\n",
+                        strerror(errno), errno);
+                tpm_write_std_fatal_error_response(out, out_len, in, in_len);
+                goto send_resp;
+            }
+
+            ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len);
+            if (ret < 0) {
+                fprintf(stderr,
+                        "tpm_passthrough: error while reading data from host "
+                        "tpm : %s (%i)\n",
+                        strerror(errno), errno);
+                tpm_write_std_fatal_error_response(out, out_len, in, in_len);
+            }
+
+send_resp:
+            thr_parms->recv_data_callback(thr_parms->tpm_state, locty);
+        } while (in_len > 0);
+    }
+
+#ifdef DEBUG_TPM
+    fprintf(stderr, "tpm_passthrough: THREAD IS ENDING\n");
+#endif
+
+    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;
+    }
+
+#if defined DEBUG_TPM
+    fprintf(stderr, "tpm_passthrough: TERMINATING RUNNING TPM THREAD\n");
+#endif
+
+    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);
+
+        while (tpm_pt->thread_running) {
+            usleep(100000);
+        }
+        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_main_loop,
+                       &tpm_pt->tpm_thread_params);
+
+    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;
+
+#if defined DEBUG_TPM
+    fprintf(stderr, "tpm_passthrough: CALL TO TPM_RESET!\n");
+#endif
+
+    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;
+
+    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 TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id,
+                                          const char *model)
+{
+    TPMBackend *tb;
+    const char *value;
+    char buf[64];
+    int n;
+
+    tb = g_malloc(sizeof(TPMBackend));
+    if (tb == NULL) {
+        fprintf(stderr, "tpm_passthrough: Could not allocate memory.\n");
+        return NULL;
+    }
+
+    tb->s.tpm_pt = g_malloc(sizeof(TPMPassthruState));
+    if (tb->s.tpm_pt == NULL) {
+        fprintf(stderr, "tpm_passthrough: Could not allocate memory.\n");
+        g_free(tb);
+        return NULL;
+    }
+
+    tb->id = g_strdup(id);
+    tb->model = NULL;
+    if (model) {
+        tb->model = g_strdup(model);
+    }
+    tb->ops = &tpm_passthrough_driver;
+
+    value = qemu_opt_get(opts, "path");
+    if (value) {
+        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)) {
+            fprintf(stderr, "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) {
+            goto err_exit;
+        }
+
+        tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
+        if (tb->s.tpm_pt->tpm_fd < 0) {
+            fprintf(stderr,
+                    "Cannot open device '%s' from TPM's path option.\n",
+                    tb->s.tpm_pt->tpm_dev);
+            goto err_exit;
+        }
+
+    } else {
+        fprintf(stderr, "-tpmdev is missing path= parameter\n");
+        goto err_exit;
+    }
+
+    if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
+        fprintf(stderr,
+                "'%s' is not a TPM device.\n",
+                tb->s.tpm_pt->tpm_dev);
+        goto err_close_tpmdev;
+    }
+
+    return tb;
+
+err_close_tpmdev:
+    close(tb->s.tpm_pt->tpm_fd);
+
+err_exit:
+    g_free(tb->id);
+    g_free(tb->model);
+    g_free(tb->parameters);
+    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->model);
+    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,
+};
Index: qemu-git.pt/qemu-options.hx
===================================================================
--- qemu-git.pt.orig/qemu-options.hx
+++ qemu-git.pt/qemu-options.hx
@@ -1777,6 +1777,7 @@  The general form of a TPM device option 
 @item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
 @findex -tpmdev
 Backend type must be:
+@option{passthrough}.
 
 The specific backend type will determine the applicable options.
 The @code{-tpmdev} options requires a @code{-device} option.
@@ -1788,6 +1789,29 @@  Use ? to print all available TPM backend
 qemu -tpmdev ?
 @end example
 
+@item -tpmdev passthrough, id=@var{id}, path=@var{path}
+
+Enables access to the host's TPM using the passthrough driver.
+
+@option{path} specifies the path to the host's TPM device, i.e., on
+a Linux host this would be @code{/dev/tpm0}.
+
+Note that the passthrough device must not be used by any application on
+the host. Since the host's firmware has already initialized the TPM,
+the firmware (BIOS) executed by QEMU will not be able to initialize the
+TPM again and behave differently than if it could initialize the TPM.
+If TPM ownership is released from within a QEMU VM then this requires
+rebooting of the host and entering the host's firmware to enable and activate
+the TPM again.
+@option{path} is required.
+
+To create a passthrough TPM use the following two options:
+@example
+-tpmdev pasthrough,id=tpm0,path=<path to TPM device> -device tpm-tis,tpmdev=tpm0
+@end example
+Not that the @code{-tpmdev} id is @code{tpm0} and is referenced by
+@code{tpmdev=tpm0} in the device option.
+
 @end table
 
 ETEXI
Index: qemu-git.pt/tpm.c
===================================================================
--- qemu-git.pt.orig/tpm.c
+++ qemu-git.pt/tpm.c
@@ -18,12 +18,33 @@ 
 #include "qerror.h"
 
 static const TPMDriverOps *bes[] = {
+    &tpm_passthrough_driver,
     NULL,
 };
 
 static QLIST_HEAD(, TPMBackend) tpm_backends =
     QLIST_HEAD_INITIALIZER(tpm_backends);
 
+void tpm_write_std_fatal_error_response(uint8_t *out, uint32_t out_len,
+                                        uint8_t *in, uint32_t in_len)
+{
+    if (out_len > sizeof(struct tpm_resp_hdr)) {
+        struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
+        struct tpm_req_hdr *req  = (struct tpm_req_hdr *)in;
+        uint16_t tag = be16_to_cpu(req->tag);
+
+        resp->tag = cpu_to_be16(
+                     (in_len > 2 &&
+                      tag >= TPM_TAG_RQU_COMMAND &&
+                      tag <= TPM_TAG_RQU_AUTH2_COMMAND)
+                     ? tag + 3
+                     : 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;
Index: qemu-git.pt/tpm.h
===================================================================
--- qemu-git.pt.orig/tpm.h
+++ qemu-git.pt/tpm.h
@@ -6,12 +6,18 @@ 
 struct TPMDriverOps;
 typedef struct TPMDriverOps TPMDriverOps;
 
+typedef struct TPMPassthruState TPMPassthruState;
+
 typedef struct TPMBackend {
     char *id;
     char *model;
     char *parameters;
     const TPMDriverOps *ops;
 
+    union {
+        TPMPassthruState *tpm_pt;
+    } s;
+
     QLIST_ENTRY(TPMBackend) list;
 } TPMBackend;
 
@@ -77,6 +83,30 @@  static inline void tpm_dump_buffer(FILE 
 
 #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
+
 void tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
 int tpm_init(void);
 void tpm_cleanup(void);
@@ -84,5 +114,9 @@  TPMBackend *qemu_find_tpm(const char *id
 void do_info_tpm(Monitor *mon);
 void tpm_display_backend_drivers(FILE *out);
 const TPMDriverOps *tpm_get_backend_driver(const char *id);
+void tpm_write_std_fatal_error_response(uint8_t *out, uint32_t out_len,
+                                        uint8_t *in, uint32_t in_len);
+
+extern const TPMDriverOps tpm_passthrough_driver;
 
 #endif /* _QEMU_TPM_H */