diff mbox

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

Message ID 1323870202-25742-6-git-send-email-stefanb@linux.vnet.ibm.com
State New
Headers show

Commit Message

Stefan Berger Dec. 14, 2011, 1:43 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.

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 |  493 ++++++++++++++++++++++++++++++++++++++++++++++++++
 qemu-options.hx      |   33 ++++
 tpm.c                |   16 ++
 tpm.h                |   33 ++++
 6 files changed, 579 insertions(+), 0 deletions(-)
 create mode 100644 hw/tpm_passthrough.c

Comments

Michael S. Tsirkin Feb. 20, 2012, 7:51 p.m. UTC | #1
On Wed, Dec 14, 2011 at 08:43:20AM -0500, 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.
> 
> 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>

So this was mentioned by Blue Swirl and Anthony and
I have to agree: it's not clear why this wants its own
thread.
I would go further and claim that forcing a threaded build for a
periferal just looks wrong. What's going on, select+unblocking access
does not work for tpm?
If this behaves like  a normal device
we won't need locking ...


> ---
>  Makefile.target      |    1 +
>  configure            |    3 +
>  hw/tpm_passthrough.c |  493 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  qemu-options.hx      |   33 ++++
>  tpm.c                |   16 ++
>  tpm.h                |   33 ++++
>  6 files changed, 579 insertions(+), 0 deletions(-)
>  create mode 100644 hw/tpm_passthrough.c
> 
> diff --git a/Makefile.target b/Makefile.target
> index 0e1c032..2744a5c 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -207,6 +207,7 @@ obj-$(CONFIG_NO_KVM) += kvm-stub.o
>  obj-y += memory.o
>  obj-y += tpm.o
>  obj-$(CONFIG_TPM) += tpm_tis.o
> +obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
>  LIBS+=-lz
>  
>  QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
> diff --git a/configure b/configure
> index 385feb4..25995bc 100755
> --- a/configure
> +++ b/configure
> @@ -3721,6 +3721,9 @@ fi
>  
>  if test "$tpm" = "yes"; then
>    if test "$target_softmmu" = "yes" ; then
> +    if test "$linux" = "yes" ; then
> +      echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_target_mak
> +    fi
>      echo "CONFIG_TPM=y" >> $config_host_mak
>    fi
>  fi
> diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
> new file mode 100644
> index 0000000..76e4e35
> --- /dev/null
> +++ b/hw/tpm_passthrough.c
> @@ -0,0 +1,493 @@
> +/*
> + *  passthrough TPM driver
> + *
> + *  Copyright (c) 2010, 2011 IBM Corporation
> + *  Authors:
> + *    Stefan Berger <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 "qemu-error.h"
> +#include "tpm.h"
> +#include "hw/hw.h"
> +#include "hw/tpm_tis.h"
> +#include "hw/pc.h"
> +
> +/* #define DEBUG_TPM */
> +
> +#ifdef DEBUG_TPM
> +#define dprintf(fmt, ...) \
> +    do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
> +#else
> +#define dprintf(fmt, ...) \
> +    do { } while (0)
> +#endif
> +
> +/* data structures */
> +
> +typedef struct TPMPassthruThreadParams {
> +    TPMState *tpm_state;
> +
> +    TPMRecvDataCB *recv_data_callback;
> +    TPMBackend *tb;
> +} TPMPassthruThreadParams;
> +
> +struct TPMPassthruState {
> +    QemuThread thread;
> +    bool thread_terminate;
> +    bool thread_running;
> +
> +    TPMPassthruThreadParams tpm_thread_params;
> +
> +    char tpm_dev[64];
> +    int tpm_fd;
> +    bool had_startup_error;
> +};
> +
> +#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
> +
> +/* borrowed from qemu-char.c */
> +static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
> +{
> +    int ret, len1;
> +
> +    len1 = len;
> +    while (len1 > 0) {
> +        ret = write(fd, buf, len1);
> +        if (ret < 0) {
> +            if (errno != EINTR && errno != EAGAIN) {
> +                return -1;
> +            }
> +        } else if (ret == 0) {
> +            break;
> +        } else {
> +            buf  += ret;
> +            len1 -= ret;
> +        }
> +    }
> +    return len - len1;
> +}
> +
> +static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
> +{
> +    int ret, len1;
> +    uint8_t *buf1;
> +
> +    len1 = len;
> +    buf1 = buf;
> +    while ((len1 > 0) && (ret = read(fd, buf1, len1)) != 0) {
> +        if (ret < 0) {
> +            if (errno != EINTR && errno != EAGAIN) {
> +                return -1;
> +            }
> +        } else {
> +            buf1 += ret;
> +            len1 -= ret;
> +        }
> +    }
> +    return len - len1;
> +}
> +
> +static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
> +{
> +    return be32_to_cpu(*(uint32_t *)&buf[2]);
> +}
> +
> +static int tpm_passthrough_unix_tx_bufs(int tpm_fd,
> +                                        const uint8_t *in, uint32_t in_len,
> +                                        uint8_t *out, uint32_t out_len)
> +{
> +    int ret;
> +
> +    ret = tpm_passthrough_unix_write(tpm_fd, in, in_len);
> +    if (ret < 0) {
> +        error_report("tpm_passthrough: error while transmitting data "
> +                     "to TPM: %s (%i)\n",
> +                     strerror(errno), errno);
> +        goto err_exit;
> +    }
> +
> +    ret = tpm_passthrough_unix_read(tpm_fd, out, out_len);
> +    if (ret < 0) {
> +        error_report("tpm_passthrough: error while reading data from "
> +                     "TPM: %s (%i)\n",
> +                     strerror(errno), errno);
> +    } else if (ret < sizeof(struct tpm_resp_hdr) ||
> +               tpm_passthrough_get_size_from_buffer(out) != ret) {
> +        ret = -1;
> +        error_report("tpm_passthrough: received invalid response "
> +                     "packet from TPM\n");
> +    }
> +
> +err_exit:
> +    if (ret < 0) {
> +        tpm_write_fatal_error_response(out, out_len);
> +    }
> +
> +    return ret;
> +}
> +
> +static int tpm_passthrough_unix_transfer(int tpm_fd,
> +                                         const TPMLocality *cmd_locty)
> +{
> +    return tpm_passthrough_unix_tx_bufs(tpm_fd,
> +                                        cmd_locty->w_buffer.buffer,
> +                                        cmd_locty->w_offset,
> +                                        cmd_locty->r_buffer.buffer,
> +                                        cmd_locty->r_buffer.size);
> +}
> +
> +static void *tpm_passthrough_thread(void *d)
> +{
> +    TPMPassthruThreadParams *thr_parms = d;
> +    TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
> +    uint32_t in_len;
> +
> +    dprintf("tpm_passthrough: THREAD IS STARTING\n");
> +
> +    /* start command processing */
> +    while (!tpm_pt->thread_terminate) {
> +        /* receive and handle commands */
> +        in_len = 0;
> +        do {
> +            dprintf("tpm_passthrough: waiting for commands...\n");
> +
> +            if (tpm_pt->thread_terminate) {
> +                break;
> +            }
> +
> +            qemu_mutex_lock(&thr_parms->tpm_state->state_lock);
> +
> +            /* in case we were to slow and missed the signal, the
> +               to_tpm_execute boolean tells us about a pending command */
> +            if (!thr_parms->tpm_state->to_tpm_execute) {
> +                qemu_cond_wait(&thr_parms->tpm_state->to_tpm_cond,
> +                               &thr_parms->tpm_state->state_lock);
> +            }
> +
> +            thr_parms->tpm_state->to_tpm_execute = false;
> +
> +            qemu_mutex_unlock(&thr_parms->tpm_state->state_lock);
> +
> +            if (tpm_pt->thread_terminate) {
> +                break;
> +            }
> +
> +            in_len = thr_parms->tpm_state->cmd_locty->w_offset;
> +
> +            tpm_passthrough_unix_transfer(tpm_pt->tpm_fd,
> +                                          thr_parms->tpm_state->cmd_locty);
> +
> +            thr_parms->recv_data_callback(thr_parms->tpm_state,
> +                                          thr_parms->tpm_state->command_locty);
> +        } while (in_len > 0);
> +    }
> +
> +    dprintf("tpm_passthrough: THREAD IS ENDING\n");
> +
> +    tpm_pt->thread_running = false;
> +
> +    return NULL;
> +}
> +
> +static void tpm_passthrough_terminate_tpm_thread(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    if (!tpm_pt->thread_running) {
> +        return;
> +    }
> +
> +    dprintf("tpm_passthrough: TERMINATING RUNNING TPM THREAD\n");
> +
> +    if (!tpm_pt->thread_terminate) {
> +        tpm_pt->thread_terminate = true;
> +
> +        qemu_mutex_lock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);
> +        qemu_cond_signal(&tpm_pt->tpm_thread_params.tpm_state->to_tpm_cond);
> +        qemu_mutex_unlock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);
> +
> +        if (tpm_pt->thread_running) {
> +            qemu_thread_join(&tpm_pt->thread);
> +        }
> +        memset(&tpm_pt->thread, 0, sizeof(tpm_pt->thread));
> +    }
> +}
> +
> +/**
> + * Start the TPM (thread). If it had been started before, then terminate
> + * and start it again.
> + */
> +static int tpm_passthrough_do_startup_tpm(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    /* terminate a running TPM */
> +    tpm_passthrough_terminate_tpm_thread(tb);
> +
> +    /* reset the flag so the thread keeps on running */
> +    tpm_pt->thread_terminate = false;
> +
> +    qemu_thread_create(&tpm_pt->thread, tpm_passthrough_thread,
> +                       &tpm_pt->tpm_thread_params, 0);
> +
> +    tpm_pt->thread_running = true;
> +
> +    return 0;
> +}
> +
> +static int tpm_passthrough_startup_tpm(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +    int rc;
> +
> +    rc = tpm_passthrough_do_startup_tpm(tb);
> +    if (rc) {
> +        tpm_pt->had_startup_error = true;
> +    }
> +    return rc;
> +}
> +
> +static void tpm_passthrough_reset(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    dprintf("tpm_passthrough: CALL TO TPM_RESET!\n");
> +
> +    tpm_passthrough_terminate_tpm_thread(tb);
> +
> +    tpm_pt->had_startup_error = false;
> +}
> +
> +static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
> +                                TPMRecvDataCB *recv_data_cb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    tpm_pt->tpm_thread_params.tpm_state = s;
> +    tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb;
> +    tpm_pt->tpm_thread_params.tb = tb;
> +
> +    tpm_pt->thread_running = false;
> +
> +    return 0;
> +}
> +
> +static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
> +{
> +    return false;
> +}
> +
> +static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    return tpm_pt->had_startup_error;
> +}
> +
> +static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
> +{
> +    size_t wanted_size = 4096; /* Linux tpm.c buffer size */
> +
> +    if (sb->size != wanted_size) {
> +        sb->buffer = g_realloc(sb->buffer, wanted_size);
> +        if (sb->buffer != NULL) {
> +            sb->size = wanted_size;
> +        } else {
> +            sb->size = 0;
> +        }
> +    }
> +    return sb->size;
> +}
> +
> +static const char *tpm_passthrough_create_desc(void)
> +{
> +    return "Passthrough TPM backend driver";
> +}
> +
> +/* A basic test of a TPM device. We expect a well formatted response header
> + * (error response is fine) within one second.
> + */
> +static int tpm_passthrough_test_tpmdev(int fd)
> +{
> +    struct tpm_req_hdr req = {
> +        .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND),
> +        .len = cpu_to_be32(sizeof(req)),
> +        .ordinal = cpu_to_be32(TPM_ORD_GetTicks),
> +    };
> +    struct tpm_resp_hdr *resp;
> +    fd_set readfds;
> +    int n;
> +    struct timeval tv = {
> +        .tv_sec = 1,
> +        .tv_usec = 0,
> +    };
> +    unsigned char buf[1024];
> +
> +    n = write(fd, &req, sizeof(req));
> +    if (n < 0) {
> +        return errno;
> +    }
> +    if (n != sizeof(req)) {
> +        return EFAULT;
> +    }
> +
> +    FD_ZERO(&readfds);
> +    FD_SET(fd, &readfds);
> +
> +    /* wait for a second */
> +    n = select(fd + 1, &readfds, NULL, NULL, &tv);
> +    if (n != 1) {
> +        return errno;
> +    }
> +
> +    n = read(fd, &buf, sizeof(buf));
> +    if (n < sizeof(struct tpm_resp_hdr)) {
> +        return EFAULT;
> +    }
> +
> +    resp = (struct tpm_resp_hdr *)buf;
> +    /* check the header */
> +    if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND ||
> +        be32_to_cpu(resp->len) != n) {
> +        return EBADMSG;
> +    }
> +
> +    return 0;
> +}
> +
> +static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
> +{
> +    const char *value;
> +    char buf[64];
> +    int n;
> +
> +    value = qemu_opt_get(opts, "path");
> +    if (!value) {
> +        value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
> +    }
> +
> +    n = snprintf(tb->s.tpm_pt->tpm_dev, sizeof(tb->s.tpm_pt->tpm_dev),
> +                 "%s", value);
> +
> +    if (n >= sizeof(tb->s.tpm_pt->tpm_dev)) {
> +        error_report("TPM device path is too long.\n");
> +        goto err_exit;
> +    }
> +
> +    snprintf(buf, sizeof(buf), "path=%s", tb->s.tpm_pt->tpm_dev);
> +
> +    tb->parameters = g_strdup(buf);
> +
> +    if (tb->parameters == NULL) {
> +        return 1;
> +    }
> +
> +    tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
> +    if (tb->s.tpm_pt->tpm_fd < 0) {
> +        error_report("Cannot access TPM device using '%s'.\n",
> +                     tb->s.tpm_pt->tpm_dev);
> +        goto err_exit;
> +    }
> +
> +    if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
> +        error_report("'%s' is not a TPM device.\n",
> +                     tb->s.tpm_pt->tpm_dev);
> +        goto err_close_tpmdev;
> +    }
> +
> +    return 0;
> +
> + err_close_tpmdev:
> +    close(tb->s.tpm_pt->tpm_fd);
> +    tb->s.tpm_pt->tpm_fd = -1;
> +
> + err_exit:
> +    g_free(tb->parameters);
> +    tb->parameters = NULL;
> +
> +    return 1;
> +}
> +
> +
> +static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
> +{
> +    TPMBackend *tb;
> +
> +    tb = g_malloc0(sizeof(TPMBackend));
> +    if (tb == NULL) {
> +        error_report("tpm_passthrough: Could not allocate memory.\n");
> +        return NULL;
> +    }
> +
> +    tb->s.tpm_pt = g_malloc0(sizeof(TPMPassthruState));
> +    if (tb->s.tpm_pt == NULL) {
> +        error_report("tpm_passthrough: Could not allocate memory.\n");
> +        g_free(tb);
> +        return NULL;
> +    }
> +
> +    tb->id = g_strdup(id);
> +    if (tb->id == NULL) {
> +        error_report("tpm_passthrough: Could not allocate memory.\n");
> +        goto err_exit;
> +    }
> +
> +    tb->ops = &tpm_passthrough_driver;
> +
> +    if (tpm_passthrough_handle_device_opts(opts, tb)) {
> +        goto err_exit;
> +    }
> +
> +    return tb;
> +
> +err_exit:
> +    g_free(tb->id);
> +    g_free(tb->s.tpm_pt);
> +    g_free(tb);
> +
> +    return NULL;
> +}
> +
> +static void tpm_passthrough_destroy(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    tpm_passthrough_terminate_tpm_thread(tb);
> +
> +    close(tpm_pt->tpm_fd);
> +
> +    g_free(tb->id);
> +    g_free(tb->parameters);
> +    g_free(tb->s.tpm_pt);
> +    g_free(tb);
> +}
> +
> +const TPMDriverOps tpm_passthrough_driver = {
> +    .id                       = "passthrough",
> +    .desc                     = tpm_passthrough_create_desc,
> +    .create                   = tpm_passthrough_create,
> +    .destroy                  = tpm_passthrough_destroy,
> +    .init                     = tpm_passthrough_init,
> +    .startup_tpm              = tpm_passthrough_startup_tpm,
> +    .realloc_buffer           = tpm_passthrough_realloc_buffer,
> +    .reset                    = tpm_passthrough_reset,
> +    .had_startup_error        = tpm_passthrough_get_startup_error,
> +    .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
> +};
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 40a63b1..10ae499 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -1916,6 +1916,7 @@ The general form of a TPM device option is:
>  @item -tpmdev @var{backend} ,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.
> @@ -1927,6 +1928,38 @@ Use ? to print all available TPM backend types.
>  qemu -tpmdev ?
>  @end example
>  
> +@item -tpmdev passthrough, id=@var{id}, path=@var{path}
> +
> +(Linux-host only) Enable 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}.
> +@option{path} is optional and by default @code{/dev/tpm0} is used.
> +
> +Some notes about using the host's TPM with the passthrough driver:
> +
> +The TPM device accessed by the passthrough driver must not be
> +used by any other application on the host.
> +
> +Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
> +the VM's firmware (BIOS/UEFI) will not be able to initialize the
> +TPM again and may therefore not show a TPM-specific menu that would
> +otherwise allow the user to configure the TPM, e.g., allow the user to
> +enable/disable or activate/deactivate the TPM.
> +Further, if TPM ownership is released from within a VM then the host's TPM
> +will get disabled and deactivated. To enable and activate the
> +TPM again afterwards, the host has to be rebooted and the user is
> +required to enter the firmware's menu to enable and activate the TPM.
> +If the TPM is left disabled and/or deactivated most TPM commands will fail.
> +
> +To create a passthrough TPM use the following two options:
> +@example
> +-tpmdev passthrough,id=tpm0 -device tpm-tis,tpmdev=tpm0
> +@end example
> +Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by
> +@code{tpmdev=tpm0} in the device option.
> +
>  @end table
>  
>  ETEXI
> diff --git a/tpm.c b/tpm.c
> index 408d319..ee38107 100644
> --- a/tpm.c
> +++ b/tpm.c
> @@ -24,9 +24,25 @@ static QLIST_HEAD(, TPMBackend) tpm_backends =
>  #ifdef CONFIG_TPM
>  
>  static const TPMDriverOps *bes[] = {
> +#ifdef CONFIG_TPM_PASSTHROUGH
> +    &tpm_passthrough_driver,
> +#endif
>      NULL,
>  };
>  
> +/* Write an error message in the given output buffer.
> + */
> +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len)
> +{
> +    if (out_len >= sizeof(struct tpm_resp_hdr)) {
> +        struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
> +
> +        resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND);
> +        resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
> +        resp->errcode = cpu_to_be32(TPM_FAIL);
> +    }
> +}
> +
>  const TPMDriverOps *tpm_get_backend_driver(const char *id)
>  {
>      int i;
> diff --git a/tpm.h b/tpm.h
> index 075de3f..1b278cc 100644
> --- a/tpm.h
> +++ b/tpm.h
> @@ -18,12 +18,18 @@
>  struct TPMDriverOps;
>  typedef struct TPMDriverOps TPMDriverOps;
>  
> +typedef struct TPMPassthruState TPMPassthruState;
> +
>  typedef struct TPMBackend {
>      char *id;
>      const char *fe_model;
>      char *parameters;
>      const TPMDriverOps *ops;
>  
> +    union {
> +        TPMPassthruState *tpm_pt;
> +    } s;
> +
>      QLIST_ENTRY(TPMBackend) list;
>  } TPMBackend;
>  
> @@ -76,11 +82,38 @@ struct TPMDriverOps {
>  
>  #define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
>  
> +struct tpm_req_hdr {
> +    uint16_t tag;
> +    uint32_t len;
> +    uint32_t ordinal;
> +} __attribute__((packed));
> +
> +struct tpm_resp_hdr {
> +    uint16_t tag;
> +    uint32_t len;
> +    uint32_t errcode;
> +} __attribute__((packed));
> +
> +#define TPM_TAG_RQU_COMMAND       0xc1
> +#define TPM_TAG_RQU_AUTH1_COMMAND 0xc2
> +#define TPM_TAG_RQU_AUTH2_COMMAND 0xc3
> +
> +#define TPM_TAG_RSP_COMMAND       0xc4
> +#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5
> +#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6
> +
> +#define TPM_FAIL                  9
> +
> +#define TPM_ORD_GetTicks          0xf1
> +
>  int tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
>  int tpm_init(void);
>  void tpm_cleanup(void);
>  TPMBackend *qemu_find_tpm(const char *id);
>  void tpm_display_backend_drivers(void);
>  const TPMDriverOps *tpm_get_backend_driver(const char *id);
> +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len);
> +
> +extern const TPMDriverOps tpm_passthrough_driver;
>  
>  #endif /* QEMU_TPM_H */
> -- 
> 1.7.6.4
>
Michael S. Tsirkin Feb. 20, 2012, 8:01 p.m. UTC | #2
On Wed, Dec 14, 2011 at 08:43:20AM -0500, 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.
> 
> 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 |  493 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  qemu-options.hx      |   33 ++++
>  tpm.c                |   16 ++
>  tpm.h                |   33 ++++
>  6 files changed, 579 insertions(+), 0 deletions(-)
>  create mode 100644 hw/tpm_passthrough.c
> 
> diff --git a/Makefile.target b/Makefile.target
> index 0e1c032..2744a5c 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -207,6 +207,7 @@ obj-$(CONFIG_NO_KVM) += kvm-stub.o
>  obj-y += memory.o
>  obj-y += tpm.o
>  obj-$(CONFIG_TPM) += tpm_tis.o
> +obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
>  LIBS+=-lz
>  
>  QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
> diff --git a/configure b/configure
> index 385feb4..25995bc 100755
> --- a/configure
> +++ b/configure
> @@ -3721,6 +3721,9 @@ fi
>  
>  if test "$tpm" = "yes"; then
>    if test "$target_softmmu" = "yes" ; then
> +    if test "$linux" = "yes" ; then
> +      echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_target_mak
> +    fi
>      echo "CONFIG_TPM=y" >> $config_host_mak
>    fi
>  fi
> diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
> new file mode 100644
> index 0000000..76e4e35
> --- /dev/null
> +++ b/hw/tpm_passthrough.c
> @@ -0,0 +1,493 @@
> +/*
> + *  passthrough TPM driver
> + *
> + *  Copyright (c) 2010, 2011 IBM Corporation
> + *  Authors:
> + *    Stefan Berger <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 "qemu-error.h"
> +#include "tpm.h"
> +#include "hw/hw.h"
> +#include "hw/tpm_tis.h"
> +#include "hw/pc.h"
> +
> +/* #define DEBUG_TPM */
> +
> +#ifdef DEBUG_TPM
> +#define dprintf(fmt, ...) \
> +    do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
> +#else
> +#define dprintf(fmt, ...) \
> +    do { } while (0)
> +#endif
> +
> +/* data structures */
> +
> +typedef struct TPMPassthruThreadParams {
> +    TPMState *tpm_state;
> +
> +    TPMRecvDataCB *recv_data_callback;
> +    TPMBackend *tb;
> +} TPMPassthruThreadParams;
> +
> +struct TPMPassthruState {
> +    QemuThread thread;
> +    bool thread_terminate;
> +    bool thread_running;
> +
> +    TPMPassthruThreadParams tpm_thread_params;
> +
> +    char tpm_dev[64];

why not strdup and avoid length limits?


> +    int tpm_fd;
> +    bool had_startup_error;
> +};
> +
> +#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
> +
> +/* borrowed from qemu-char.c */
> +static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
> +{
> +    int ret, len1;
> +
> +    len1 = len;
> +    while (len1 > 0) {
> +        ret = write(fd, buf, len1);
> +        if (ret < 0) {
> +            if (errno != EINTR && errno != EAGAIN) {
> +                return -1;
> +            }
> +        } else if (ret == 0) {
> +            break;
> +        } else {
> +            buf  += ret;
> +            len1 -= ret;
> +        }
> +    }
> +    return len - len1;
> +}
> +
> +static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
> +{
> +    int ret, len1;
> +    uint8_t *buf1;
> +
> +    len1 = len;
> +    buf1 = buf;
> +    while ((len1 > 0) && (ret = read(fd, buf1, len1)) != 0) {
> +        if (ret < 0) {
> +            if (errno != EINTR && errno != EAGAIN) {
> +                return -1;
> +            }
> +        } else {
> +            buf1 += ret;
> +            len1 -= ret;
> +        }
> +    }
> +    return len - len1;
> +}
> +
> +static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
> +{
> +    return be32_to_cpu(*(uint32_t *)&buf[2]);
> +}
> +
> +static int tpm_passthrough_unix_tx_bufs(int tpm_fd,
> +                                        const uint8_t *in, uint32_t in_len,
> +                                        uint8_t *out, uint32_t out_len)
> +{
> +    int ret;
> +
> +    ret = tpm_passthrough_unix_write(tpm_fd, in, in_len);
> +    if (ret < 0) {
> +        error_report("tpm_passthrough: error while transmitting data "
> +                     "to TPM: %s (%i)\n",
> +                     strerror(errno), errno);
> +        goto err_exit;
> +    }
> +
> +    ret = tpm_passthrough_unix_read(tpm_fd, out, out_len);
> +    if (ret < 0) {
> +        error_report("tpm_passthrough: error while reading data from "
> +                     "TPM: %s (%i)\n",
> +                     strerror(errno), errno);
> +    } else if (ret < sizeof(struct tpm_resp_hdr) ||
> +               tpm_passthrough_get_size_from_buffer(out) != ret) {
> +        ret = -1;
> +        error_report("tpm_passthrough: received invalid response "
> +                     "packet from TPM\n");
> +    }
> +
> +err_exit:
> +    if (ret < 0) {
> +        tpm_write_fatal_error_response(out, out_len);
> +    }
> +
> +    return ret;
> +}
> +
> +static int tpm_passthrough_unix_transfer(int tpm_fd,
> +                                         const TPMLocality *cmd_locty)
> +{
> +    return tpm_passthrough_unix_tx_bufs(tpm_fd,
> +                                        cmd_locty->w_buffer.buffer,
> +                                        cmd_locty->w_offset,
> +                                        cmd_locty->r_buffer.buffer,
> +                                        cmd_locty->r_buffer.size);
> +}
> +
> +static void *tpm_passthrough_thread(void *d)
> +{
> +    TPMPassthruThreadParams *thr_parms = d;
> +    TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
> +    uint32_t in_len;
> +
> +    dprintf("tpm_passthrough: THREAD IS STARTING\n");
> +
> +    /* start command processing */
> +    while (!tpm_pt->thread_terminate) {
> +        /* receive and handle commands */
> +        in_len = 0;
> +        do {
> +            dprintf("tpm_passthrough: waiting for commands...\n");
> +
> +            if (tpm_pt->thread_terminate) {
> +                break;
> +            }
> +
> +            qemu_mutex_lock(&thr_parms->tpm_state->state_lock);
> +
> +            /* in case we were to slow and missed the signal, the



too slow


> +               to_tpm_execute boolean tells us about a pending command */
> +            if (!thr_parms->tpm_state->to_tpm_execute) {
> +                qemu_cond_wait(&thr_parms->tpm_state->to_tpm_cond,
> +                               &thr_parms->tpm_state->state_lock);
> +            }
> +
> +            thr_parms->tpm_state->to_tpm_execute = false;
> +
> +            qemu_mutex_unlock(&thr_parms->tpm_state->state_lock);
> +
> +            if (tpm_pt->thread_terminate) {
> +                break;
> +            }
> +
> +            in_len = thr_parms->tpm_state->cmd_locty->w_offset;
> +
> +            tpm_passthrough_unix_transfer(tpm_pt->tpm_fd,
> +                                          thr_parms->tpm_state->cmd_locty);
> +
> +            thr_parms->recv_data_callback(thr_parms->tpm_state,
> +                                          thr_parms->tpm_state->command_locty);
> +        } while (in_len > 0);
> +    }
> +
> +    dprintf("tpm_passthrough: THREAD IS ENDING\n");
> +
> +    tpm_pt->thread_running = false;
> +
> +    return NULL;
> +}
> +
> +static void tpm_passthrough_terminate_tpm_thread(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    if (!tpm_pt->thread_running) {
> +        return;
> +    }
> +
> +    dprintf("tpm_passthrough: TERMINATING RUNNING TPM THREAD\n");
> +
> +    if (!tpm_pt->thread_terminate) {
> +        tpm_pt->thread_terminate = true;
> +
> +        qemu_mutex_lock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);
> +        qemu_cond_signal(&tpm_pt->tpm_thread_params.tpm_state->to_tpm_cond);
> +        qemu_mutex_unlock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);

why lock?

> +
> +        if (tpm_pt->thread_running) {
> +            qemu_thread_join(&tpm_pt->thread);
> +        }
> +        memset(&tpm_pt->thread, 0, sizeof(tpm_pt->thread));
> +    }
> +}
> +
> +/**
> + * Start the TPM (thread). If it had been started before, then terminate
> + * and start it again.
> + */
> +static int tpm_passthrough_do_startup_tpm(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    /* terminate a running TPM */
> +    tpm_passthrough_terminate_tpm_thread(tb);
> +
> +    /* reset the flag so the thread keeps on running */
> +    tpm_pt->thread_terminate = false;
> +
> +    qemu_thread_create(&tpm_pt->thread, tpm_passthrough_thread,
> +                       &tpm_pt->tpm_thread_params, 0);
> +
> +    tpm_pt->thread_running = true;
> +
> +    return 0;
> +}
> +
> +static int tpm_passthrough_startup_tpm(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +    int rc;
> +
> +    rc = tpm_passthrough_do_startup_tpm(tb);
> +    if (rc) {
> +        tpm_pt->had_startup_error = true;
> +    }
> +    return rc;
> +}
> +
> +static void tpm_passthrough_reset(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    dprintf("tpm_passthrough: CALL TO TPM_RESET!\n");
> +
> +    tpm_passthrough_terminate_tpm_thread(tb);
> +
> +    tpm_pt->had_startup_error = false;
> +}
> +
> +static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
> +                                TPMRecvDataCB *recv_data_cb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    tpm_pt->tpm_thread_params.tpm_state = s;
> +    tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb;
> +    tpm_pt->tpm_thread_params.tb = tb;
> +
> +    tpm_pt->thread_running = false;
> +
> +    return 0;
> +}
> +
> +static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
> +{
> +    return false;
> +}
> +
> +static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    return tpm_pt->had_startup_error;
> +}
> +
> +static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
> +{
> +    size_t wanted_size = 4096; /* Linux tpm.c buffer size */

what does the comment mean?


> +
> +    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));

so why read the whole buf? hoiw do we know 1024 is enough?
read the heade then discard the rest
until empty.

> +    if (n < sizeof(struct tpm_resp_hdr)) {
> +        return EFAULT;
> +    }
> +
> +    resp = (struct tpm_resp_hdr *)buf;
> +    /* check the header */
> +    if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND ||
> +        be32_to_cpu(resp->len) != n) {
> +        return EBADMSG;
> +    }
> +
> +    return 0;
> +}
> +
> +static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
> +{
> +    const char *value;
> +    char buf[64];
> +    int n;
> +
> +    value = qemu_opt_get(opts, "path");
> +    if (!value) {
> +        value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
> +    }
> +
> +    n = snprintf(tb->s.tpm_pt->tpm_dev, sizeof(tb->s.tpm_pt->tpm_dev),
> +                 "%s", value);
> +
> +    if (n >= sizeof(tb->s.tpm_pt->tpm_dev)) {
> +        error_report("TPM device path is too long.\n");
> +        goto err_exit;
> +    }
> +
> +    snprintf(buf, sizeof(buf), "path=%s", tb->s.tpm_pt->tpm_dev);
> +
> +    tb->parameters = g_strdup(buf);
> +
> +    if (tb->parameters == NULL) {
> +        return 1;
> +    }
> +
> +    tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
> +    if (tb->s.tpm_pt->tpm_fd < 0) {
> +        error_report("Cannot access TPM device using '%s'.\n",
> +                     tb->s.tpm_pt->tpm_dev);
> +        goto err_exit;
> +    }
> +
> +    if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
> +        error_report("'%s' is not a TPM device.\n",
> +                     tb->s.tpm_pt->tpm_dev);
> +        goto err_close_tpmdev;
> +    }
> +
> +    return 0;
> +
> + err_close_tpmdev:
> +    close(tb->s.tpm_pt->tpm_fd);
> +    tb->s.tpm_pt->tpm_fd = -1;
> +
> + err_exit:
> +    g_free(tb->parameters);
> +    tb->parameters = NULL;
> +
> +    return 1;
> +}
> +
> +
> +static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
> +{
> +    TPMBackend *tb;
> +
> +    tb = g_malloc0(sizeof(TPMBackend));
> +    if (tb == NULL) {
> +        error_report("tpm_passthrough: Could not allocate memory.\n");
> +        return NULL;
> +    }
> +
> +    tb->s.tpm_pt = g_malloc0(sizeof(TPMPassthruState));
> +    if (tb->s.tpm_pt == NULL) {
> +        error_report("tpm_passthrough: Could not allocate memory.\n");
> +        g_free(tb);
> +        return NULL;
> +    }
> +
> +    tb->id = g_strdup(id);
> +    if (tb->id == NULL) {
> +        error_report("tpm_passthrough: Could not allocate memory.\n");
> +        goto err_exit;
> +    }
> +
> +    tb->ops = &tpm_passthrough_driver;
> +
> +    if (tpm_passthrough_handle_device_opts(opts, tb)) {
> +        goto err_exit;
> +    }
> +
> +    return tb;
> +
> +err_exit:
> +    g_free(tb->id);
> +    g_free(tb->s.tpm_pt);
> +    g_free(tb);
> +
> +    return NULL;
> +}
> +
> +static void tpm_passthrough_destroy(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    tpm_passthrough_terminate_tpm_thread(tb);
> +
> +    close(tpm_pt->tpm_fd);
> +
> +    g_free(tb->id);
> +    g_free(tb->parameters);
> +    g_free(tb->s.tpm_pt);
> +    g_free(tb);
> +}
> +
> +const TPMDriverOps tpm_passthrough_driver = {
> +    .id                       = "passthrough",
> +    .desc                     = tpm_passthrough_create_desc,
> +    .create                   = tpm_passthrough_create,
> +    .destroy                  = tpm_passthrough_destroy,
> +    .init                     = tpm_passthrough_init,
> +    .startup_tpm              = tpm_passthrough_startup_tpm,
> +    .realloc_buffer           = tpm_passthrough_realloc_buffer,
> +    .reset                    = tpm_passthrough_reset,
> +    .had_startup_error        = tpm_passthrough_get_startup_error,
> +    .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
> +};
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 40a63b1..10ae499 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -1916,6 +1916,7 @@ The general form of a TPM device option is:
>  @item -tpmdev @var{backend} ,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.
> @@ -1927,6 +1928,38 @@ Use ? to print all available TPM backend types.
>  qemu -tpmdev ?
>  @end example
>  
> +@item -tpmdev passthrough, id=@var{id}, path=@var{path}
> +
> +(Linux-host only) Enable 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}.
> +@option{path} is optional and by default @code{/dev/tpm0} is used.
> +
> +Some notes about using the host's TPM with the passthrough driver:
> +
> +The TPM device accessed by the passthrough driver must not be
> +used by any other application on the host.
> +
> +Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
> +the VM's firmware (BIOS/UEFI) will not be able to initialize the
> +TPM again and may therefore not show a TPM-specific menu that would
> +otherwise allow the user to configure the TPM, e.g., allow the user to
> +enable/disable or activate/deactivate the TPM.
> +Further, if TPM ownership is released from within a VM then the host's TPM
> +will get disabled and deactivated. To enable and activate the
> +TPM again afterwards, the host has to be rebooted and the user is
> +required to enter the firmware's menu to enable and activate the TPM.
> +If the TPM is left disabled and/or deactivated most TPM commands will fail.
> +
> +To create a passthrough TPM use the following two options:
> +@example
> +-tpmdev passthrough,id=tpm0 -device tpm-tis,tpmdev=tpm0
> +@end example
> +Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by
> +@code{tpmdev=tpm0} in the device option.
> +
>  @end table
>  
>  ETEXI
> diff --git a/tpm.c b/tpm.c
> index 408d319..ee38107 100644
> --- a/tpm.c
> +++ b/tpm.c
> @@ -24,9 +24,25 @@ static QLIST_HEAD(, TPMBackend) tpm_backends =
>  #ifdef CONFIG_TPM
>  
>  static const TPMDriverOps *bes[] = {
> +#ifdef CONFIG_TPM_PASSTHROUGH
> +    &tpm_passthrough_driver,
> +#endif
>      NULL,
>  };
>  
> +/* Write an error message in the given output buffer.
> + */
> +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len)
> +{
> +    if (out_len >= sizeof(struct tpm_resp_hdr)) {
> +        struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
> +
> +        resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND);
> +        resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
> +        resp->errcode = cpu_to_be32(TPM_FAIL);
> +    }
> +}
> +
>  const TPMDriverOps *tpm_get_backend_driver(const char *id)
>  {
>      int i;
> diff --git a/tpm.h b/tpm.h
> index 075de3f..1b278cc 100644
> --- a/tpm.h
> +++ b/tpm.h
> @@ -18,12 +18,18 @@
>  struct TPMDriverOps;
>  typedef struct TPMDriverOps TPMDriverOps;
>  
> +typedef struct TPMPassthruState TPMPassthruState;
> +
>  typedef struct TPMBackend {
>      char *id;
>      const char *fe_model;
>      char *parameters;
>      const TPMDriverOps *ops;
>  
> +    union {
> +        TPMPassthruState *tpm_pt;
> +    } s;
> +
>      QLIST_ENTRY(TPMBackend) list;
>  } TPMBackend;
>  
> @@ -76,11 +82,38 @@ struct TPMDriverOps {
>  
>  #define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
>  
> +struct tpm_req_hdr {
> +    uint16_t tag;
> +    uint32_t len;
> +    uint32_t ordinal;
> +} __attribute__((packed));
> +
> +struct tpm_resp_hdr {
> +    uint16_t tag;
> +    uint32_t len;
> +    uint32_t errcode;
> +} __attribute__((packed));
> +
> +#define TPM_TAG_RQU_COMMAND       0xc1
> +#define TPM_TAG_RQU_AUTH1_COMMAND 0xc2
> +#define TPM_TAG_RQU_AUTH2_COMMAND 0xc3
> +
> +#define TPM_TAG_RSP_COMMAND       0xc4
> +#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5
> +#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6
> +
> +#define TPM_FAIL                  9
> +
> +#define TPM_ORD_GetTicks          0xf1
> +
>  int tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
>  int tpm_init(void);
>  void tpm_cleanup(void);
>  TPMBackend *qemu_find_tpm(const char *id);
>  void tpm_display_backend_drivers(void);
>  const TPMDriverOps *tpm_get_backend_driver(const char *id);
> +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len);
> +
> +extern const TPMDriverOps tpm_passthrough_driver;
>  
>  #endif /* QEMU_TPM_H */
> -- 
> 1.7.6.4
>
Stefan Berger Feb. 20, 2012, 8:25 p.m. UTC | #3
On 02/20/2012 02:51 PM, Michael S. Tsirkin wrote:
> On Wed, Dec 14, 2011 at 08:43:20AM -0500, 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.
>>
>> 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>
> So this was mentioned by Blue Swirl and Anthony and
> I have to agree: it's not clear why this wants its own
> thread.

This is not only due to how the Linux TPM driver works but also, as 
previously mentioned, due to how I would like the libtpms driver to 
work. The former does not support select() (yes, this is probably a TPM 
driver problem but it has been around for ages and didn't matter so far 
because only the TSS (TrouSerS) was meant to access the TPM). The latter 
will certainly support creation of 2048 bit RSA keys and disappear into 
e crypto function that also isn't select() able, unless you end up 
introducing another thread here. And you most probably don't want the 
main thread to be busy for several seconds (depending on availability of 
CPU cycles) to have that key created. So in effect having this thread 
allows us to have a common architecture for both passthrough and libtpms 
backends.


> I would go further and claim that forcing a threaded build for a
> periferal just looks wrong. What's going on, select+unblocking access
> does not work for tpm?

Yep.

> If this behaves like  a normal device
> we won't need locking ...
>

Stefan
Stefan Berger Feb. 20, 2012, 9:12 p.m. UTC | #4
On 02/20/2012 03:01 PM, Michael S. Tsirkin wrote:
> On Wed, Dec 14, 2011 at 08:43:20AM -0500, Stefan Berger wrote:
>> +struct TPMPassthruState {
>> +    QemuThread thread;
>> +    bool thread_terminate;
>> +    bool thread_running;
>> +
>> +    TPMPassthruThreadParams tpm_thread_params;
>> +
>> +    char tpm_dev[64];
> why not strdup and avoid length limits?
>
>

Fixed.


>> +
>> +            /* in case we were to slow and missed the signal, the
>
>
> too slow

Fixed.

> +
>> +        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);
> why lock?
>

Before the other thread calls the function to wait for the condition it 
has to grab the lock. Since the signal is not sticky, it could happen 
that it had grabbed the lock but wasn't waiting for the condition, yet, 
thus missing the signal yet still waiting for it soon after and then it 
may end up waiting forever. The lock prevents this. Check example here:

https://computing.llnl.gov/tutorials/pthreads/#ConVarSignal

>> +static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
>> +{
>> +    size_t wanted_size = 4096; /* Linux tpm.c buffer size */
> what does the comment mean?
>

It basically means that it allocates the same size of buffer as the 
Linux driver does. If the TPM was to send a response (there are no 
requests of even close that size) then the Linux driver can accommodate 
the size of the response, we can also accomodate it.


>> + * (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));
> so why read the whole buf? hoiw do we know 1024 is enough?

The size of the response for this particular command can easily be read 
into 1024 bytes, even less than 50 would be enough. I could try to come 
up with the exact number, but that then probably causes more 
questions... 1024 bytes is ample space.

> read the heade then discard the rest
> until empty.
>

I don't follow... ?

FYI: The TPM only lets you read the buffer in one go. If you don't bring 
a buffer with enough size when doing the read from /dev/tpm0, the rest 
of the result is gone. No 2nd read on the same response buffer.

Even though /dev/tpm0 blocks in the write(), the above select() is there 
to prepare for usage with sockets. It doesn't hurt at the moment, but it 
also doesn't do the expected wait for 1 second...


    Stefan
Michael S. Tsirkin Feb. 20, 2012, 9:15 p.m. UTC | #5
On Mon, Feb 20, 2012 at 03:25:37PM -0500, Stefan Berger wrote:
> On 02/20/2012 02:51 PM, Michael S. Tsirkin wrote:
> >On Wed, Dec 14, 2011 at 08:43:20AM -0500, 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.
> >>
> >>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>
> >So this was mentioned by Blue Swirl and Anthony and
> >I have to agree: it's not clear why this wants its own
> >thread.
> 
> This is not only due to how the Linux TPM driver works but also, as
> previously mentioned, due to how I would like the libtpms driver to
> work. The former does not support select() (yes, this is probably a
> TPM driver problem but it has been around for ages and didn't matter
> so far because only the TSS (TrouSerS) was meant to access the TPM).
> The latter will certainly support creation of 2048 bit RSA keys and
> disappear into e crypto function that also isn't select() able,
> unless you end up introducing another thread here.
> And you most
> probably don't want the main thread to be busy for several seconds
> (depending on availability of CPU cycles) to have that key created.
> So in effect having this thread allows us to have a common
> architecture for both passthrough and libtpms backends.
> 

OK, so let's say this is a work around linux tpm
backend limitations. But it is unfortunate that this
spills into frontend code.
How about taking the global qemu lock instead?

Also, you need to be more careful with
running your thread, e.g. it must be stopped on
vmstop, etc. It is probably a good idea to use
a genetic thread pool with all that logic.


> >I would go further and claim that forcing a threaded build for a
> >periferal just looks wrong. What's going on, select+unblocking access
> >does not work for tpm?
> 
> Yep.

Then maybe it's better to make tpm *depend* on threads,
not force them.

> >If this behaves like  a normal device
> >we won't need locking ...
> >
> 
> Stefan
Michael S. Tsirkin Feb. 20, 2012, 9:30 p.m. UTC | #6
On Wed, Dec 14, 2011 at 08:43:20AM -0500, 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.
> 
> 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 |  493 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  qemu-options.hx      |   33 ++++
>  tpm.c                |   16 ++
>  tpm.h                |   33 ++++
>  6 files changed, 579 insertions(+), 0 deletions(-)
>  create mode 100644 hw/tpm_passthrough.c
> 
> diff --git a/Makefile.target b/Makefile.target
> index 0e1c032..2744a5c 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -207,6 +207,7 @@ obj-$(CONFIG_NO_KVM) += kvm-stub.o
>  obj-y += memory.o
>  obj-y += tpm.o
>  obj-$(CONFIG_TPM) += tpm_tis.o
> +obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
>  LIBS+=-lz
>  
>  QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
> diff --git a/configure b/configure
> index 385feb4..25995bc 100755
> --- a/configure
> +++ b/configure
> @@ -3721,6 +3721,9 @@ fi
>  
>  if test "$tpm" = "yes"; then
>    if test "$target_softmmu" = "yes" ; then
> +    if test "$linux" = "yes" ; then
> +      echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_target_mak
> +    fi
>      echo "CONFIG_TPM=y" >> $config_host_mak
>    fi
>  fi
> diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
> new file mode 100644
> index 0000000..76e4e35
> --- /dev/null
> +++ b/hw/tpm_passthrough.c
> @@ -0,0 +1,493 @@
> +/*
> + *  passthrough TPM driver
> + *
> + *  Copyright (c) 2010, 2011 IBM Corporation
> + *  Authors:
> + *    Stefan Berger <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 "qemu-error.h"
> +#include "tpm.h"
> +#include "hw/hw.h"
> +#include "hw/tpm_tis.h"
> +#include "hw/pc.h"
> +
> +/* #define DEBUG_TPM */
> +
> +#ifdef DEBUG_TPM
> +#define dprintf(fmt, ...) \
> +    do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
> +#else
> +#define dprintf(fmt, ...) \
> +    do { } while (0)
> +#endif
> +
> +/* data structures */
> +
> +typedef struct TPMPassthruThreadParams {
> +    TPMState *tpm_state;
> +
> +    TPMRecvDataCB *recv_data_callback;
> +    TPMBackend *tb;
> +} TPMPassthruThreadParams;
> +
> +struct TPMPassthruState {
> +    QemuThread thread;
> +    bool thread_terminate;
> +    bool thread_running;
> +
> +    TPMPassthruThreadParams tpm_thread_params;
> +
> +    char tpm_dev[64];
> +    int tpm_fd;
> +    bool had_startup_error;
> +};
> +
> +#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
> +
> +/* borrowed from qemu-char.c */
> +static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
> +{
> +    int ret, len1;
> +
> +    len1 = len;
> +    while (len1 > 0) {
> +        ret = write(fd, buf, len1);
> +        if (ret < 0) {
> +            if (errno != EINTR && errno != EAGAIN) {
> +                return -1;
> +            }
> +        } else if (ret == 0) {
> +            break;
> +        } else {
> +            buf  += ret;
> +            len1 -= ret;
> +        }
> +    }
> +    return len - len1;
> +}
> +
> +static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
> +{
> +    int ret, len1;
> +    uint8_t *buf1;
> +
> +    len1 = len;
> +    buf1 = buf;
> +    while ((len1 > 0) && (ret = read(fd, buf1, len1)) != 0) {
> +        if (ret < 0) {
> +            if (errno != EINTR && errno != EAGAIN) {
> +                return -1;
> +            }
> +        } else {
> +            buf1 += ret;
> +            len1 -= ret;
> +        }
> +    }
> +    return len - len1;
> +}
> +
> +static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
> +{
> +    return be32_to_cpu(*(uint32_t *)&buf[2]);
> +}
> +
> +static int tpm_passthrough_unix_tx_bufs(int tpm_fd,
> +                                        const uint8_t *in, uint32_t in_len,
> +                                        uint8_t *out, uint32_t out_len)
> +{
> +    int ret;
> +
> +    ret = tpm_passthrough_unix_write(tpm_fd, in, in_len);
> +    if (ret < 0) {
> +        error_report("tpm_passthrough: error while transmitting data "
> +                     "to TPM: %s (%i)\n",
> +                     strerror(errno), errno);
> +        goto err_exit;
> +    }
> +
> +    ret = tpm_passthrough_unix_read(tpm_fd, out, out_len);
> +    if (ret < 0) {
> +        error_report("tpm_passthrough: error while reading data from "
> +                     "TPM: %s (%i)\n",
> +                     strerror(errno), errno);
> +    } else if (ret < sizeof(struct tpm_resp_hdr) ||
> +               tpm_passthrough_get_size_from_buffer(out) != ret) {
> +        ret = -1;
> +        error_report("tpm_passthrough: received invalid response "
> +                     "packet from TPM\n");
> +    }
> +
> +err_exit:
> +    if (ret < 0) {
> +        tpm_write_fatal_error_response(out, out_len);
> +    }
> +
> +    return ret;
> +}
> +
> +static int tpm_passthrough_unix_transfer(int tpm_fd,
> +                                         const TPMLocality *cmd_locty)
> +{
> +    return tpm_passthrough_unix_tx_bufs(tpm_fd,
> +                                        cmd_locty->w_buffer.buffer,
> +                                        cmd_locty->w_offset,
> +                                        cmd_locty->r_buffer.buffer,
> +                                        cmd_locty->r_buffer.size);
> +}
> +
> +static void *tpm_passthrough_thread(void *d)
> +{
> +    TPMPassthruThreadParams *thr_parms = d;
> +    TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
> +    uint32_t in_len;
> +
> +    dprintf("tpm_passthrough: THREAD IS STARTING\n");
> +
> +    /* start command processing */
> +    while (!tpm_pt->thread_terminate) {

IMO you want to checvk thread_terminate undet a lock/

> +        /* receive and handle commands */
> +        in_len = 0;
> +        do {
> +            dprintf("tpm_passthrough: waiting for commands...\n");
> +
> +            if (tpm_pt->thread_terminate) {
> +                break;
> +            }
> +

This will have to be duplicate in each backend, no?
So I would say make this logic part of backend
and expose some api.
You can then call it from backend or invoke
backend callbacks from frontend.

> +            qemu_mutex_lock(&thr_parms->tpm_state->state_lock);
> +
> +            /* in case we were to slow and missed the signal, the
> +               to_tpm_execute boolean tells us about a pending command */
> +            if (!thr_parms->tpm_state->to_tpm_execute) {
> +                qemu_cond_wait(&thr_parms->tpm_state->to_tpm_cond,
> +                               &thr_parms->tpm_state->state_lock);
> +            }
> +
> +            thr_parms->tpm_state->to_tpm_execute = false;
> +
> +            qemu_mutex_unlock(&thr_parms->tpm_state->state_lock);
> +
> +            if (tpm_pt->thread_terminate) {
> +                break;
> +            }
> +
> +            in_len = thr_parms->tpm_state->cmd_locty->w_offset;
> +
> +            tpm_passthrough_unix_transfer(tpm_pt->tpm_fd,
> +                                          thr_parms->tpm_state->cmd_locty);
> +
> +            thr_parms->recv_data_callback(thr_parms->tpm_state,
> +                                          thr_parms->tpm_state->command_locty);
> +        } while (in_len > 0);
> +    }
> +
> +    dprintf("tpm_passthrough: THREAD IS ENDING\n");
> +
> +    tpm_pt->thread_running = false;
> +
> +    return NULL;
> +}
> +
> +static void tpm_passthrough_terminate_tpm_thread(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    if (!tpm_pt->thread_running) {
> +        return;
> +    }
> +
> +    dprintf("tpm_passthrough: TERMINATING RUNNING TPM THREAD\n");
> +
> +    if (!tpm_pt->thread_terminate) {
> +        tpm_pt->thread_terminate = true;
> +
> +        qemu_mutex_lock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);
> +        qemu_cond_signal(&tpm_pt->tpm_thread_params.tpm_state->to_tpm_cond);
> +        qemu_mutex_unlock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);
> +
> +        if (tpm_pt->thread_running) {
> +            qemu_thread_join(&tpm_pt->thread);
> +        }
> +        memset(&tpm_pt->thread, 0, sizeof(tpm_pt->thread));
> +    }
> +}
> +
> +/**
> + * Start the TPM (thread). If it had been started before, then terminate
> + * and start it again.
> + */
> +static int tpm_passthrough_do_startup_tpm(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    /* terminate a running TPM */
> +    tpm_passthrough_terminate_tpm_thread(tb);
> +
> +    /* reset the flag so the thread keeps on running */
> +    tpm_pt->thread_terminate = false;
> +
> +    qemu_thread_create(&tpm_pt->thread, tpm_passthrough_thread,
> +                       &tpm_pt->tpm_thread_params, 0);
> +
> +    tpm_pt->thread_running = true;
> +
> +    return 0;
> +}
> +
> +static int tpm_passthrough_startup_tpm(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +    int rc;
> +
> +    rc = tpm_passthrough_do_startup_tpm(tb);
> +    if (rc) {
> +        tpm_pt->had_startup_error = true;
> +    }
> +    return rc;
> +}
> +
> +static void tpm_passthrough_reset(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    dprintf("tpm_passthrough: CALL TO TPM_RESET!\n");
> +
> +    tpm_passthrough_terminate_tpm_thread(tb);
> +
> +    tpm_pt->had_startup_error = false;
> +}
> +
> +static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
> +                                TPMRecvDataCB *recv_data_cb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    tpm_pt->tpm_thread_params.tpm_state = s;
> +    tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb;
> +    tpm_pt->tpm_thread_params.tb = tb;
> +
> +    tpm_pt->thread_running = false;
> +
> +    return 0;
> +}
> +
> +static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
> +{
> +    return false;
> +}
> +
> +static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    return tpm_pt->had_startup_error;
> +}
> +
> +static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
> +{
> +    size_t wanted_size = 4096; /* Linux tpm.c buffer size */
> +
> +    if (sb->size != wanted_size) {
> +        sb->buffer = g_realloc(sb->buffer, wanted_size);
> +        if (sb->buffer != NULL) {
> +            sb->size = wanted_size;
> +        } else {
> +            sb->size = 0;
> +        }
> +    }
> +    return sb->size;
> +}
> +
> +static const char *tpm_passthrough_create_desc(void)
> +{
> +    return "Passthrough TPM backend driver";
> +}
> +
> +/* A basic test of a TPM device. We expect a well formatted response header
> + * (error response is fine) within one second.
> + */
> +static int tpm_passthrough_test_tpmdev(int fd)
> +{
> +    struct tpm_req_hdr req = {
> +        .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND),
> +        .len = cpu_to_be32(sizeof(req)),
> +        .ordinal = cpu_to_be32(TPM_ORD_GetTicks),
> +    };
> +    struct tpm_resp_hdr *resp;
> +    fd_set readfds;
> +    int n;
> +    struct timeval tv = {
> +        .tv_sec = 1,
> +        .tv_usec = 0,
> +    };
> +    unsigned char buf[1024];
> +
> +    n = write(fd, &req, sizeof(req));
> +    if (n < 0) {
> +        return errno;
> +    }
> +    if (n != sizeof(req)) {
> +        return EFAULT;
> +    }
> +
> +    FD_ZERO(&readfds);
> +    FD_SET(fd, &readfds);
> +
> +    /* wait for a second */
> +    n = select(fd + 1, &readfds, NULL, NULL, &tv);
> +    if (n != 1) {
> +        return errno;
> +    }
> +
> +    n = read(fd, &buf, sizeof(buf));
> +    if (n < sizeof(struct tpm_resp_hdr)) {
> +        return EFAULT;
> +    }
> +
> +    resp = (struct tpm_resp_hdr *)buf;
> +    /* check the header */
> +    if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND ||
> +        be32_to_cpu(resp->len) != n) {
> +        return EBADMSG;
> +    }
> +
> +    return 0;
> +}
> +
> +static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
> +{
> +    const char *value;
> +    char buf[64];
> +    int n;
> +
> +    value = qemu_opt_get(opts, "path");
> +    if (!value) {
> +        value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
> +    }
> +
> +    n = snprintf(tb->s.tpm_pt->tpm_dev, sizeof(tb->s.tpm_pt->tpm_dev),
> +                 "%s", value);
> +
> +    if (n >= sizeof(tb->s.tpm_pt->tpm_dev)) {
> +        error_report("TPM device path is too long.\n");
> +        goto err_exit;
> +    }
> +
> +    snprintf(buf, sizeof(buf), "path=%s", tb->s.tpm_pt->tpm_dev);
> +
> +    tb->parameters = g_strdup(buf);
> +
> +    if (tb->parameters == NULL) {
> +        return 1;
> +    }
> +
> +    tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
> +    if (tb->s.tpm_pt->tpm_fd < 0) {
> +        error_report("Cannot access TPM device using '%s'.\n",
> +                     tb->s.tpm_pt->tpm_dev);
> +        goto err_exit;
> +    }
> +
> +    if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
> +        error_report("'%s' is not a TPM device.\n",
> +                     tb->s.tpm_pt->tpm_dev);
> +        goto err_close_tpmdev;
> +    }
> +
> +    return 0;
> +
> + err_close_tpmdev:
> +    close(tb->s.tpm_pt->tpm_fd);
> +    tb->s.tpm_pt->tpm_fd = -1;
> +
> + err_exit:
> +    g_free(tb->parameters);
> +    tb->parameters = NULL;
> +
> +    return 1;
> +}
> +
> +
> +static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
> +{
> +    TPMBackend *tb;
> +
> +    tb = g_malloc0(sizeof(TPMBackend));
> +    if (tb == NULL) {
> +        error_report("tpm_passthrough: Could not allocate memory.\n");
> +        return NULL;
> +    }
> +
> +    tb->s.tpm_pt = g_malloc0(sizeof(TPMPassthruState));
> +    if (tb->s.tpm_pt == NULL) {
> +        error_report("tpm_passthrough: Could not allocate memory.\n");
> +        g_free(tb);
> +        return NULL;
> +    }
> +
> +    tb->id = g_strdup(id);
> +    if (tb->id == NULL) {
> +        error_report("tpm_passthrough: Could not allocate memory.\n");
> +        goto err_exit;
> +    }
> +
> +    tb->ops = &tpm_passthrough_driver;
> +
> +    if (tpm_passthrough_handle_device_opts(opts, tb)) {
> +        goto err_exit;
> +    }
> +
> +    return tb;
> +
> +err_exit:
> +    g_free(tb->id);
> +    g_free(tb->s.tpm_pt);
> +    g_free(tb);
> +
> +    return NULL;
> +}
> +
> +static void tpm_passthrough_destroy(TPMBackend *tb)
> +{
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> +    tpm_passthrough_terminate_tpm_thread(tb);
> +
> +    close(tpm_pt->tpm_fd);
> +
> +    g_free(tb->id);
> +    g_free(tb->parameters);
> +    g_free(tb->s.tpm_pt);
> +    g_free(tb);
> +}
> +
> +const TPMDriverOps tpm_passthrough_driver = {
> +    .id                       = "passthrough",
> +    .desc                     = tpm_passthrough_create_desc,
> +    .create                   = tpm_passthrough_create,
> +    .destroy                  = tpm_passthrough_destroy,
> +    .init                     = tpm_passthrough_init,
> +    .startup_tpm              = tpm_passthrough_startup_tpm,
> +    .realloc_buffer           = tpm_passthrough_realloc_buffer,
> +    .reset                    = tpm_passthrough_reset,
> +    .had_startup_error        = tpm_passthrough_get_startup_error,
> +    .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
> +};
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 40a63b1..10ae499 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -1916,6 +1916,7 @@ The general form of a TPM device option is:
>  @item -tpmdev @var{backend} ,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.
> @@ -1927,6 +1928,38 @@ Use ? to print all available TPM backend types.
>  qemu -tpmdev ?
>  @end example
>  
> +@item -tpmdev passthrough, id=@var{id}, path=@var{path}
> +
> +(Linux-host only) Enable 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}.
> +@option{path} is optional and by default @code{/dev/tpm0} is used.
> +
> +Some notes about using the host's TPM with the passthrough driver:
> +
> +The TPM device accessed by the passthrough driver must not be
> +used by any other application on the host.
> +
> +Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
> +the VM's firmware (BIOS/UEFI) will not be able to initialize the
> +TPM again and may therefore not show a TPM-specific menu that would
> +otherwise allow the user to configure the TPM, e.g., allow the user to
> +enable/disable or activate/deactivate the TPM.
> +Further, if TPM ownership is released from within a VM then the host's TPM
> +will get disabled and deactivated. To enable and activate the
> +TPM again afterwards, the host has to be rebooted and the user is
> +required to enter the firmware's menu to enable and activate the TPM.
> +If the TPM is left disabled and/or deactivated most TPM commands will fail.
> +
> +To create a passthrough TPM use the following two options:
> +@example
> +-tpmdev passthrough,id=tpm0 -device tpm-tis,tpmdev=tpm0
> +@end example
> +Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by
> +@code{tpmdev=tpm0} in the device option.
> +
>  @end table
>  
>  ETEXI
> diff --git a/tpm.c b/tpm.c
> index 408d319..ee38107 100644
> --- a/tpm.c
> +++ b/tpm.c
> @@ -24,9 +24,25 @@ static QLIST_HEAD(, TPMBackend) tpm_backends =
>  #ifdef CONFIG_TPM
>  
>  static const TPMDriverOps *bes[] = {
> +#ifdef CONFIG_TPM_PASSTHROUGH
> +    &tpm_passthrough_driver,
> +#endif
>      NULL,
>  };
>  
> +/* Write an error message in the given output buffer.
> + */
> +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len)
> +{
> +    if (out_len >= sizeof(struct tpm_resp_hdr)) {
> +        struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
> +
> +        resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND);
> +        resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
> +        resp->errcode = cpu_to_be32(TPM_FAIL);
> +    }
> +}
> +
>  const TPMDriverOps *tpm_get_backend_driver(const char *id)
>  {
>      int i;
> diff --git a/tpm.h b/tpm.h
> index 075de3f..1b278cc 100644
> --- a/tpm.h
> +++ b/tpm.h
> @@ -18,12 +18,18 @@
>  struct TPMDriverOps;
>  typedef struct TPMDriverOps TPMDriverOps;
>  
> +typedef struct TPMPassthruState TPMPassthruState;
> +
>  typedef struct TPMBackend {
>      char *id;
>      const char *fe_model;
>      char *parameters;
>      const TPMDriverOps *ops;
>  
> +    union {
> +        TPMPassthruState *tpm_pt;
> +    } s;
> +
>      QLIST_ENTRY(TPMBackend) list;
>  } TPMBackend;
>  
> @@ -76,11 +82,38 @@ struct TPMDriverOps {
>  
>  #define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
>  
> +struct tpm_req_hdr {
> +    uint16_t tag;
> +    uint32_t len;
> +    uint32_t ordinal;
> +} __attribute__((packed));
> +
> +struct tpm_resp_hdr {
> +    uint16_t tag;
> +    uint32_t len;
> +    uint32_t errcode;
> +} __attribute__((packed));
> +
> +#define TPM_TAG_RQU_COMMAND       0xc1
> +#define TPM_TAG_RQU_AUTH1_COMMAND 0xc2
> +#define TPM_TAG_RQU_AUTH2_COMMAND 0xc3
> +
> +#define TPM_TAG_RSP_COMMAND       0xc4
> +#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5
> +#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6
> +
> +#define TPM_FAIL                  9
> +
> +#define TPM_ORD_GetTicks          0xf1
> +
>  int tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
>  int tpm_init(void);
>  void tpm_cleanup(void);
>  TPMBackend *qemu_find_tpm(const char *id);
>  void tpm_display_backend_drivers(void);
>  const TPMDriverOps *tpm_get_backend_driver(const char *id);
> +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len);
> +
> +extern const TPMDriverOps tpm_passthrough_driver;
>  
>  #endif /* QEMU_TPM_H */
> -- 
> 1.7.6.4
>
Stefan Berger Feb. 21, 2012, 12:30 a.m. UTC | #7
On 02/20/2012 04:30 PM, Michael S. Tsirkin wrote:
> On Wed, Dec 14, 2011 at 08:43:20AM -0500, Stefan Berger wrote:
>> +
>> +static void *tpm_passthrough_thread(void *d)
>> +{
>> +    TPMPassthruThreadParams *thr_parms = d;
>> +    TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
>> +    uint32_t in_len;
>> +
>> +    dprintf("tpm_passthrough: THREAD IS STARTING\n");
>> +
>> +    /* start command processing */
>> +    while (!tpm_pt->thread_terminate) {
> IMO you want to checvk thread_terminate undet a lock/
>

The way the termination flag is used along with the condition it isn't 
necessary to call it with a lock held. It may run into the condition but 
then be woken up with a signal and then again check the flag and terminate.

>> +        /* receive and handle commands */
>> +        in_len = 0;
>> +        do {
>> +            dprintf("tpm_passthrough: waiting for commands...\n");
>> +
>> +            if (tpm_pt->thread_terminate) {
>> +                break;
>> +            }
>> +
> This will have to be duplicate in each backend, no?

There is this sequence of code around the condition that helps us 
waiting for the front-end to send a command that is indeed repeated in 
every backend. I isolated that now but would like to keep the general 
thread more flexible and inside the individual backends. There is quite 
some code that is different in every backend.


> So I would say make this logic part of backend
> and expose some api.

Ok, a single function for now:

void tpm_backend_wait_for_command(TPMState *tpm_state);


> You can then call it from backend or invoke
> backend callbacks from frontend.
>

  I was only going to invoke it from within the backend thread.


    Stefan
Stefan Berger Feb. 21, 2012, 1:03 a.m. UTC | #8
On 02/20/2012 04:15 PM, Michael S. Tsirkin wrote:
> On Mon, Feb 20, 2012 at 03:25:37PM -0500, Stefan Berger wrote:
>> On 02/20/2012 02:51 PM, Michael S. Tsirkin wrote:
>>> On Wed, Dec 14, 2011 at 08:43:20AM -0500, 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.
>>>>
>>>> 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>
>>> So this was mentioned by Blue Swirl and Anthony and
>>> I have to agree: it's not clear why this wants its own
>>> thread.
>> This is not only due to how the Linux TPM driver works but also, as
>> previously mentioned, due to how I would like the libtpms driver to
>> work. The former does not support select() (yes, this is probably a
>> TPM driver problem but it has been around for ages and didn't matter
>> so far because only the TSS (TrouSerS) was meant to access the TPM).
>> The latter will certainly support creation of 2048 bit RSA keys and
>> disappear into e crypto function that also isn't select() able,
>> unless you end up introducing another thread here.
>> And you most
>> probably don't want the main thread to be busy for several seconds
>> (depending on availability of CPU cycles) to have that key created.
>> So in effect having this thread allows us to have a common
>> architecture for both passthrough and libtpms backends.
>>
> OK, so let's say this is a work around linux tpm
> backend limitations. But it is unfortunate that this
> spills into frontend code.
> How about taking the global qemu lock instead?
>
> Also, you need to be more careful with
> running your thread, e.g. it must be stopped on
> vmstop, etc. It is probably a good idea to use
> a genetic thread pool with all that logic.

Some backends may be able to react to a pthread_kill() 
(qemu_thread_kill() ?) which should be able to deliver a SIG_TERM to a 
thread. The passthrough thread, however, is not going to be able to 
react to this if it is stuck in the write() call to /dev/tpm0. I am 
sending the passthrough backend a signal to terminate and then do a 
qemu_thread_join() to wait for it to end. The libtpms backend on the 
other hand may be able to support a pthread_kill().
Why would I need a thread pool ?

    Stefan



>
>>> I would go further and claim that forcing a threaded build for a
>>> periferal just looks wrong. What's going on, select+unblocking access
>>> does not work for tpm?
>> Yep.
> Then maybe it's better to make tpm *depend* on threads,
> not force them.

And how would that change the code ?


    Stefan
Anthony Liguori March 21, 2012, 11:27 p.m. UTC | #9
On 02/20/2012 07:03 PM, Stefan Berger wrote:
> On 02/20/2012 04:15 PM, Michael S. Tsirkin wrote:
>> On Mon, Feb 20, 2012 at 03:25:37PM -0500, Stefan Berger wrote:
>>> On 02/20/2012 02:51 PM, Michael S. Tsirkin wrote:
>>>> On Wed, Dec 14, 2011 at 08:43:20AM -0500, 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.
>>>>>
>>>>> 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>
>>>> So this was mentioned by Blue Swirl and Anthony and
>>>> I have to agree: it's not clear why this wants its own
>>>> thread.
>>> This is not only due to how the Linux TPM driver works but also, as
>>> previously mentioned, due to how I would like the libtpms driver to
>>> work. The former does not support select() (yes, this is probably a
>>> TPM driver problem but it has been around for ages and didn't matter
>>> so far because only the TSS (TrouSerS) was meant to access the TPM).
>>> The latter will certainly support creation of 2048 bit RSA keys and
>>> disappear into e crypto function that also isn't select() able,
>>> unless you end up introducing another thread here.
>>> And you most
>>> probably don't want the main thread to be busy for several seconds
>>> (depending on availability of CPU cycles) to have that key created.
>>> So in effect having this thread allows us to have a common
>>> architecture for both passthrough and libtpms backends.
>>>
>> OK, so let's say this is a work around linux tpm
>> backend limitations. But it is unfortunate that this
>> spills into frontend code.
>> How about taking the global qemu lock instead?
>>
>> Also, you need to be more careful with
>> running your thread, e.g. it must be stopped on
>> vmstop, etc. It is probably a good idea to use
>> a genetic thread pool with all that logic.
>
> Some backends may be able to react to a pthread_kill() (qemu_thread_kill() ?)
> which should be able to deliver a SIG_TERM to a thread. The passthrough thread,
> however, is not going to be able to react to this if it is stuck in the write()
> call to /dev/tpm0. I am sending the passthrough backend a signal to terminate
> and then do a qemu_thread_join() to wait for it to end. The libtpms backend on
> the other hand may be able to support a pthread_kill().
> Why would I need a thread pool ?

The model for handling this in QEMU is to use a thread pool.  We do this for 
AIO, virtio-9p, VNC, etc.

What's run in the thread pool is typically minimal (a single system call) but in 
the case of the libtpms backend, may be a full library call to the libtpms 
driver.  The key point is that we're not mixing re-entrant code (the code that 
interfaces with libtpms) with non re-entrant code (QEMU).

Submitting to the thread pool and processing the response is all done via the 
thread pool.

Regards,

Anthony Liguori

>
> Stefan
>
>
>
>>
>>>> I would go further and claim that forcing a threaded build for a
>>>> periferal just looks wrong. What's going on, select+unblocking access
>>>> does not work for tpm?
>>> Yep.
>> Then maybe it's better to make tpm *depend* on threads,
>> not force them.
>
> And how would that change the code ?
>
>
> Stefan
>
>
diff mbox

Patch

diff --git a/Makefile.target b/Makefile.target
index 0e1c032..2744a5c 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -207,6 +207,7 @@  obj-$(CONFIG_NO_KVM) += kvm-stub.o
 obj-y += memory.o
 obj-y += tpm.o
 obj-$(CONFIG_TPM) += tpm_tis.o
+obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
 LIBS+=-lz
 
 QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
diff --git a/configure b/configure
index 385feb4..25995bc 100755
--- a/configure
+++ b/configure
@@ -3721,6 +3721,9 @@  fi
 
 if test "$tpm" = "yes"; then
   if test "$target_softmmu" = "yes" ; then
+    if test "$linux" = "yes" ; then
+      echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_target_mak
+    fi
     echo "CONFIG_TPM=y" >> $config_host_mak
   fi
 fi
diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
new file mode 100644
index 0000000..76e4e35
--- /dev/null
+++ b/hw/tpm_passthrough.c
@@ -0,0 +1,493 @@ 
+/*
+ *  passthrough TPM driver
+ *
+ *  Copyright (c) 2010, 2011 IBM Corporation
+ *  Authors:
+ *    Stefan Berger <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 "qemu-error.h"
+#include "tpm.h"
+#include "hw/hw.h"
+#include "hw/tpm_tis.h"
+#include "hw/pc.h"
+
+/* #define DEBUG_TPM */
+
+#ifdef DEBUG_TPM
+#define dprintf(fmt, ...) \
+    do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define dprintf(fmt, ...) \
+    do { } while (0)
+#endif
+
+/* data structures */
+
+typedef struct TPMPassthruThreadParams {
+    TPMState *tpm_state;
+
+    TPMRecvDataCB *recv_data_callback;
+    TPMBackend *tb;
+} TPMPassthruThreadParams;
+
+struct TPMPassthruState {
+    QemuThread thread;
+    bool thread_terminate;
+    bool thread_running;
+
+    TPMPassthruThreadParams tpm_thread_params;
+
+    char tpm_dev[64];
+    int tpm_fd;
+    bool had_startup_error;
+};
+
+#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
+
+/* borrowed from qemu-char.c */
+static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
+{
+    int ret, len1;
+
+    len1 = len;
+    while (len1 > 0) {
+        ret = write(fd, buf, len1);
+        if (ret < 0) {
+            if (errno != EINTR && errno != EAGAIN) {
+                return -1;
+            }
+        } else if (ret == 0) {
+            break;
+        } else {
+            buf  += ret;
+            len1 -= ret;
+        }
+    }
+    return len - len1;
+}
+
+static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
+{
+    int ret, len1;
+    uint8_t *buf1;
+
+    len1 = len;
+    buf1 = buf;
+    while ((len1 > 0) && (ret = read(fd, buf1, len1)) != 0) {
+        if (ret < 0) {
+            if (errno != EINTR && errno != EAGAIN) {
+                return -1;
+            }
+        } else {
+            buf1 += ret;
+            len1 -= ret;
+        }
+    }
+    return len - len1;
+}
+
+static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
+{
+    return be32_to_cpu(*(uint32_t *)&buf[2]);
+}
+
+static int tpm_passthrough_unix_tx_bufs(int tpm_fd,
+                                        const uint8_t *in, uint32_t in_len,
+                                        uint8_t *out, uint32_t out_len)
+{
+    int ret;
+
+    ret = tpm_passthrough_unix_write(tpm_fd, in, in_len);
+    if (ret < 0) {
+        error_report("tpm_passthrough: error while transmitting data "
+                     "to TPM: %s (%i)\n",
+                     strerror(errno), errno);
+        goto err_exit;
+    }
+
+    ret = tpm_passthrough_unix_read(tpm_fd, out, out_len);
+    if (ret < 0) {
+        error_report("tpm_passthrough: error while reading data from "
+                     "TPM: %s (%i)\n",
+                     strerror(errno), errno);
+    } else if (ret < sizeof(struct tpm_resp_hdr) ||
+               tpm_passthrough_get_size_from_buffer(out) != ret) {
+        ret = -1;
+        error_report("tpm_passthrough: received invalid response "
+                     "packet from TPM\n");
+    }
+
+err_exit:
+    if (ret < 0) {
+        tpm_write_fatal_error_response(out, out_len);
+    }
+
+    return ret;
+}
+
+static int tpm_passthrough_unix_transfer(int tpm_fd,
+                                         const TPMLocality *cmd_locty)
+{
+    return tpm_passthrough_unix_tx_bufs(tpm_fd,
+                                        cmd_locty->w_buffer.buffer,
+                                        cmd_locty->w_offset,
+                                        cmd_locty->r_buffer.buffer,
+                                        cmd_locty->r_buffer.size);
+}
+
+static void *tpm_passthrough_thread(void *d)
+{
+    TPMPassthruThreadParams *thr_parms = d;
+    TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
+    uint32_t in_len;
+
+    dprintf("tpm_passthrough: THREAD IS STARTING\n");
+
+    /* start command processing */
+    while (!tpm_pt->thread_terminate) {
+        /* receive and handle commands */
+        in_len = 0;
+        do {
+            dprintf("tpm_passthrough: waiting for commands...\n");
+
+            if (tpm_pt->thread_terminate) {
+                break;
+            }
+
+            qemu_mutex_lock(&thr_parms->tpm_state->state_lock);
+
+            /* in case we were to slow and missed the signal, the
+               to_tpm_execute boolean tells us about a pending command */
+            if (!thr_parms->tpm_state->to_tpm_execute) {
+                qemu_cond_wait(&thr_parms->tpm_state->to_tpm_cond,
+                               &thr_parms->tpm_state->state_lock);
+            }
+
+            thr_parms->tpm_state->to_tpm_execute = false;
+
+            qemu_mutex_unlock(&thr_parms->tpm_state->state_lock);
+
+            if (tpm_pt->thread_terminate) {
+                break;
+            }
+
+            in_len = thr_parms->tpm_state->cmd_locty->w_offset;
+
+            tpm_passthrough_unix_transfer(tpm_pt->tpm_fd,
+                                          thr_parms->tpm_state->cmd_locty);
+
+            thr_parms->recv_data_callback(thr_parms->tpm_state,
+                                          thr_parms->tpm_state->command_locty);
+        } while (in_len > 0);
+    }
+
+    dprintf("tpm_passthrough: THREAD IS ENDING\n");
+
+    tpm_pt->thread_running = false;
+
+    return NULL;
+}
+
+static void tpm_passthrough_terminate_tpm_thread(TPMBackend *tb)
+{
+    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+    if (!tpm_pt->thread_running) {
+        return;
+    }
+
+    dprintf("tpm_passthrough: TERMINATING RUNNING TPM THREAD\n");
+
+    if (!tpm_pt->thread_terminate) {
+        tpm_pt->thread_terminate = true;
+
+        qemu_mutex_lock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);
+        qemu_cond_signal(&tpm_pt->tpm_thread_params.tpm_state->to_tpm_cond);
+        qemu_mutex_unlock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);
+
+        if (tpm_pt->thread_running) {
+            qemu_thread_join(&tpm_pt->thread);
+        }
+        memset(&tpm_pt->thread, 0, sizeof(tpm_pt->thread));
+    }
+}
+
+/**
+ * Start the TPM (thread). If it had been started before, then terminate
+ * and start it again.
+ */
+static int tpm_passthrough_do_startup_tpm(TPMBackend *tb)
+{
+    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+    /* terminate a running TPM */
+    tpm_passthrough_terminate_tpm_thread(tb);
+
+    /* reset the flag so the thread keeps on running */
+    tpm_pt->thread_terminate = false;
+
+    qemu_thread_create(&tpm_pt->thread, tpm_passthrough_thread,
+                       &tpm_pt->tpm_thread_params, 0);
+
+    tpm_pt->thread_running = true;
+
+    return 0;
+}
+
+static int tpm_passthrough_startup_tpm(TPMBackend *tb)
+{
+    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+    int rc;
+
+    rc = tpm_passthrough_do_startup_tpm(tb);
+    if (rc) {
+        tpm_pt->had_startup_error = true;
+    }
+    return rc;
+}
+
+static void tpm_passthrough_reset(TPMBackend *tb)
+{
+    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+    dprintf("tpm_passthrough: CALL TO TPM_RESET!\n");
+
+    tpm_passthrough_terminate_tpm_thread(tb);
+
+    tpm_pt->had_startup_error = false;
+}
+
+static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
+                                TPMRecvDataCB *recv_data_cb)
+{
+    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+    tpm_pt->tpm_thread_params.tpm_state = s;
+    tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb;
+    tpm_pt->tpm_thread_params.tb = tb;
+
+    tpm_pt->thread_running = false;
+
+    return 0;
+}
+
+static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
+{
+    return false;
+}
+
+static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
+{
+    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+    return tpm_pt->had_startup_error;
+}
+
+static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
+{
+    size_t wanted_size = 4096; /* Linux tpm.c buffer size */
+
+    if (sb->size != wanted_size) {
+        sb->buffer = g_realloc(sb->buffer, wanted_size);
+        if (sb->buffer != NULL) {
+            sb->size = wanted_size;
+        } else {
+            sb->size = 0;
+        }
+    }
+    return sb->size;
+}
+
+static const char *tpm_passthrough_create_desc(void)
+{
+    return "Passthrough TPM backend driver";
+}
+
+/* A basic test of a TPM device. We expect a well formatted response header
+ * (error response is fine) within one second.
+ */
+static int tpm_passthrough_test_tpmdev(int fd)
+{
+    struct tpm_req_hdr req = {
+        .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND),
+        .len = cpu_to_be32(sizeof(req)),
+        .ordinal = cpu_to_be32(TPM_ORD_GetTicks),
+    };
+    struct tpm_resp_hdr *resp;
+    fd_set readfds;
+    int n;
+    struct timeval tv = {
+        .tv_sec = 1,
+        .tv_usec = 0,
+    };
+    unsigned char buf[1024];
+
+    n = write(fd, &req, sizeof(req));
+    if (n < 0) {
+        return errno;
+    }
+    if (n != sizeof(req)) {
+        return EFAULT;
+    }
+
+    FD_ZERO(&readfds);
+    FD_SET(fd, &readfds);
+
+    /* wait for a second */
+    n = select(fd + 1, &readfds, NULL, NULL, &tv);
+    if (n != 1) {
+        return errno;
+    }
+
+    n = read(fd, &buf, sizeof(buf));
+    if (n < sizeof(struct tpm_resp_hdr)) {
+        return EFAULT;
+    }
+
+    resp = (struct tpm_resp_hdr *)buf;
+    /* check the header */
+    if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND ||
+        be32_to_cpu(resp->len) != n) {
+        return EBADMSG;
+    }
+
+    return 0;
+}
+
+static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
+{
+    const char *value;
+    char buf[64];
+    int n;
+
+    value = qemu_opt_get(opts, "path");
+    if (!value) {
+        value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
+    }
+
+    n = snprintf(tb->s.tpm_pt->tpm_dev, sizeof(tb->s.tpm_pt->tpm_dev),
+                 "%s", value);
+
+    if (n >= sizeof(tb->s.tpm_pt->tpm_dev)) {
+        error_report("TPM device path is too long.\n");
+        goto err_exit;
+    }
+
+    snprintf(buf, sizeof(buf), "path=%s", tb->s.tpm_pt->tpm_dev);
+
+    tb->parameters = g_strdup(buf);
+
+    if (tb->parameters == NULL) {
+        return 1;
+    }
+
+    tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
+    if (tb->s.tpm_pt->tpm_fd < 0) {
+        error_report("Cannot access TPM device using '%s'.\n",
+                     tb->s.tpm_pt->tpm_dev);
+        goto err_exit;
+    }
+
+    if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
+        error_report("'%s' is not a TPM device.\n",
+                     tb->s.tpm_pt->tpm_dev);
+        goto err_close_tpmdev;
+    }
+
+    return 0;
+
+ err_close_tpmdev:
+    close(tb->s.tpm_pt->tpm_fd);
+    tb->s.tpm_pt->tpm_fd = -1;
+
+ err_exit:
+    g_free(tb->parameters);
+    tb->parameters = NULL;
+
+    return 1;
+}
+
+
+static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
+{
+    TPMBackend *tb;
+
+    tb = g_malloc0(sizeof(TPMBackend));
+    if (tb == NULL) {
+        error_report("tpm_passthrough: Could not allocate memory.\n");
+        return NULL;
+    }
+
+    tb->s.tpm_pt = g_malloc0(sizeof(TPMPassthruState));
+    if (tb->s.tpm_pt == NULL) {
+        error_report("tpm_passthrough: Could not allocate memory.\n");
+        g_free(tb);
+        return NULL;
+    }
+
+    tb->id = g_strdup(id);
+    if (tb->id == NULL) {
+        error_report("tpm_passthrough: Could not allocate memory.\n");
+        goto err_exit;
+    }
+
+    tb->ops = &tpm_passthrough_driver;
+
+    if (tpm_passthrough_handle_device_opts(opts, tb)) {
+        goto err_exit;
+    }
+
+    return tb;
+
+err_exit:
+    g_free(tb->id);
+    g_free(tb->s.tpm_pt);
+    g_free(tb);
+
+    return NULL;
+}
+
+static void tpm_passthrough_destroy(TPMBackend *tb)
+{
+    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+    tpm_passthrough_terminate_tpm_thread(tb);
+
+    close(tpm_pt->tpm_fd);
+
+    g_free(tb->id);
+    g_free(tb->parameters);
+    g_free(tb->s.tpm_pt);
+    g_free(tb);
+}
+
+const TPMDriverOps tpm_passthrough_driver = {
+    .id                       = "passthrough",
+    .desc                     = tpm_passthrough_create_desc,
+    .create                   = tpm_passthrough_create,
+    .destroy                  = tpm_passthrough_destroy,
+    .init                     = tpm_passthrough_init,
+    .startup_tpm              = tpm_passthrough_startup_tpm,
+    .realloc_buffer           = tpm_passthrough_realloc_buffer,
+    .reset                    = tpm_passthrough_reset,
+    .had_startup_error        = tpm_passthrough_get_startup_error,
+    .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
+};
diff --git a/qemu-options.hx b/qemu-options.hx
index 40a63b1..10ae499 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1916,6 +1916,7 @@  The general form of a TPM device option is:
 @item -tpmdev @var{backend} ,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.
@@ -1927,6 +1928,38 @@  Use ? to print all available TPM backend types.
 qemu -tpmdev ?
 @end example
 
+@item -tpmdev passthrough, id=@var{id}, path=@var{path}
+
+(Linux-host only) Enable 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}.
+@option{path} is optional and by default @code{/dev/tpm0} is used.
+
+Some notes about using the host's TPM with the passthrough driver:
+
+The TPM device accessed by the passthrough driver must not be
+used by any other application on the host.
+
+Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
+the VM's firmware (BIOS/UEFI) will not be able to initialize the
+TPM again and may therefore not show a TPM-specific menu that would
+otherwise allow the user to configure the TPM, e.g., allow the user to
+enable/disable or activate/deactivate the TPM.
+Further, if TPM ownership is released from within a VM then the host's TPM
+will get disabled and deactivated. To enable and activate the
+TPM again afterwards, the host has to be rebooted and the user is
+required to enter the firmware's menu to enable and activate the TPM.
+If the TPM is left disabled and/or deactivated most TPM commands will fail.
+
+To create a passthrough TPM use the following two options:
+@example
+-tpmdev passthrough,id=tpm0 -device tpm-tis,tpmdev=tpm0
+@end example
+Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by
+@code{tpmdev=tpm0} in the device option.
+
 @end table
 
 ETEXI
diff --git a/tpm.c b/tpm.c
index 408d319..ee38107 100644
--- a/tpm.c
+++ b/tpm.c
@@ -24,9 +24,25 @@  static QLIST_HEAD(, TPMBackend) tpm_backends =
 #ifdef CONFIG_TPM
 
 static const TPMDriverOps *bes[] = {
+#ifdef CONFIG_TPM_PASSTHROUGH
+    &tpm_passthrough_driver,
+#endif
     NULL,
 };
 
+/* Write an error message in the given output buffer.
+ */
+void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len)
+{
+    if (out_len >= sizeof(struct tpm_resp_hdr)) {
+        struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
+
+        resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND);
+        resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
+        resp->errcode = cpu_to_be32(TPM_FAIL);
+    }
+}
+
 const TPMDriverOps *tpm_get_backend_driver(const char *id)
 {
     int i;
diff --git a/tpm.h b/tpm.h
index 075de3f..1b278cc 100644
--- a/tpm.h
+++ b/tpm.h
@@ -18,12 +18,18 @@ 
 struct TPMDriverOps;
 typedef struct TPMDriverOps TPMDriverOps;
 
+typedef struct TPMPassthruState TPMPassthruState;
+
 typedef struct TPMBackend {
     char *id;
     const char *fe_model;
     char *parameters;
     const TPMDriverOps *ops;
 
+    union {
+        TPMPassthruState *tpm_pt;
+    } s;
+
     QLIST_ENTRY(TPMBackend) list;
 } TPMBackend;
 
@@ -76,11 +82,38 @@  struct TPMDriverOps {
 
 #define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
 
+struct tpm_req_hdr {
+    uint16_t tag;
+    uint32_t len;
+    uint32_t ordinal;
+} __attribute__((packed));
+
+struct tpm_resp_hdr {
+    uint16_t tag;
+    uint32_t len;
+    uint32_t errcode;
+} __attribute__((packed));
+
+#define TPM_TAG_RQU_COMMAND       0xc1
+#define TPM_TAG_RQU_AUTH1_COMMAND 0xc2
+#define TPM_TAG_RQU_AUTH2_COMMAND 0xc3
+
+#define TPM_TAG_RSP_COMMAND       0xc4
+#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5
+#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6
+
+#define TPM_FAIL                  9
+
+#define TPM_ORD_GetTicks          0xf1
+
 int tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
 int tpm_init(void);
 void tpm_cleanup(void);
 TPMBackend *qemu_find_tpm(const char *id);
 void tpm_display_backend_drivers(void);
 const TPMDriverOps *tpm_get_backend_driver(const char *id);
+void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len);
+
+extern const TPMDriverOps tpm_passthrough_driver;
 
 #endif /* QEMU_TPM_H */