diff mbox

[1/3] Add TPM 1.2 host device passthrough interface

Message ID 1282753982-1761-1-git-send-email-andreas.niederl@iaik.tugraz.at
State New
Headers show

Commit Message

Andreas Niederl Aug. 25, 2010, 4:33 p.m. UTC
This implementation is based on the TPM 1.2 interface for virtualized TPM
devices from the Xen-4.0.0 ioemu-qemu-xen fork.

A separate thread is used for I/O to the host TPM device because the Linux TPM
driver does not allow for non-blocking I/O.

Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
---
 Makefile.target |    3 +
 configure       |    9 +
 hw/pc_piix.c    |    8 +
 hw/tpm_tis.c    |  831 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 851 insertions(+), 0 deletions(-)
 create mode 100644 hw/tpm_tis.c

Comments

Anthony Liguori Aug. 25, 2010, 4:46 p.m. UTC | #1
On 08/25/2010 11:33 AM, Andreas Niederl wrote:
> This implementation is based on the TPM 1.2 interface for virtualized TPM
> devices from the Xen-4.0.0 ioemu-qemu-xen fork.
>
> A separate thread is used for I/O to the host TPM device because the Linux TPM
> driver does not allow for non-blocking I/O.
>
> Signed-off-by: Andreas Niederl<andreas.niederl@iaik.tugraz.at>
>    

Interesting.  A few high level comments.

You need to make the device qdev-aware and VMState aware.

It would be better to use the generic thread pool series floating around 
on the list than to introduce a device specific thread.

The command line interface should separate our the host passthrough from 
a future vTPM interface.  IOW, something like a -tpm 
host,path=/dev/tpm0,id=vtpm0 -device tpm,driver=vtpm0

Regards,

Anthony Liguori

> ---
>   Makefile.target |    3 +
>   configure       |    9 +
>   hw/pc_piix.c    |    8 +
>   hw/tpm_tis.c    |  831 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>   4 files changed, 851 insertions(+), 0 deletions(-)
>   create mode 100644 hw/tpm_tis.c
>
> diff --git a/Makefile.target b/Makefile.target
> index c8281e9..2226c75 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -193,6 +193,9 @@ obj-y += e1000.o
>   # Inter-VM PCI shared memory
>   obj-$(CONFIG_KVM) += ivshmem.o
>
> +# TPM passthrough device
> +obj-$(CONFIG_TPM) += tpm_tis.o qemu-thread.o
> +
>   # Hardware support
>   obj-i386-y += vga.o
>   obj-i386-y += mc146818rtc.o i8259.o pc.o
> diff --git a/configure b/configure
> index a20371c..e1b55a8 100755
> --- a/configure
> +++ b/configure
> @@ -315,6 +315,7 @@ pkgversion=""
>   check_utests="no"
>   user_pie="no"
>   zero_malloc=""
> +tpm="no"
>
>   # OS specific
>   if check_define __linux__ ; then
> @@ -448,6 +449,7 @@ AIX)
>     usb="linux"
>     if [ "$cpu" = "i386" -o "$cpu" = "x86_64" ] ; then
>       audio_possible_drivers="$audio_possible_drivers fmod"
> +    tpm="yes"
>     fi
>   ;;
>   esac
> @@ -707,6 +709,8 @@ for opt do
>     ;;
>     --enable-vhost-net) vhost_net="yes"
>     ;;
> +  --disable-tpm) tpm="no"
> +  ;;
>     --*dir)
>     ;;
>     *) echo "ERROR: unknown option $opt"; show_help="yes"
> @@ -895,6 +899,7 @@ echo "  --enable-docs            enable documentation build"
>   echo "  --disable-docs           disable documentation build"
>   echo "  --disable-vhost-net      disable vhost-net acceleration support"
>   echo "  --enable-vhost-net       enable vhost-net acceleration support"
> +echo "  --disable-tpm            disable tpm passthrough device emulation"
>   echo ""
>   echo "NOTE: The object files are built at the place where configure is launched"
>   exit 1
> @@ -2187,6 +2192,7 @@ echo "preadv support    $preadv"
>   echo "fdatasync         $fdatasync"
>   echo "uuid support      $uuid"
>   echo "vhost-net support $vhost_net"
> +echo "tpm support       $tpm"
>
>   if test $sdl_too_old = "yes"; then
>   echo "->  Your SDL version is too old - please upgrade to have SDL support"
> @@ -2423,6 +2429,9 @@ fi
>   if test "$fdatasync" = "yes" ; then
>     echo "CONFIG_FDATASYNC=y">>  $config_host_mak
>   fi
> +if test "$tpm" = "yes" ; then
> +  echo "CONFIG_TPM=y">>  $config_host_mak
> +fi
>
>   # XXX: suppress that
>   if [ "$bsd" = "yes" ] ; then
> diff --git a/hw/pc_piix.c b/hw/pc_piix.c
> index 32a1057..ebc3478 100644
> --- a/hw/pc_piix.c
> +++ b/hw/pc_piix.c
> @@ -38,6 +38,10 @@
>
>   #define MAX_IDE_BUS 2
>
> +#ifdef CONFIG_TPM
> +void tpm_tis_init(qemu_irq irq);
> +#endif
> +
>   static const int ide_iobase[MAX_IDE_BUS] = { 0x1f0, 0x170 };
>   static const int ide_iobase2[MAX_IDE_BUS] = { 0x3f6, 0x376 };
>   static const int ide_irq[MAX_IDE_BUS] = { 14, 15 };
> @@ -147,6 +151,10 @@ static void pc_init1(ram_addr_t ram_size,
>           }
>       }
>
> +#ifdef CONFIG_TPM
> +    tpm_tis_init(isa_reserve_irq(11));
> +#endif
> +
>       pc_audio_init(pci_enabled ? pci_bus : NULL, isa_irq);
>
>       pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device,
> diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c
> new file mode 100644
> index 0000000..e109e1a
> --- /dev/null
> +++ b/hw/tpm_tis.c
> @@ -0,0 +1,831 @@
> +/*
> + * tpm_tis.c - QEMU emulator for a 1.2 TPM with TIS interface
> + *
> + * Copyright (C) 2006 IBM Corporation
> + * Copyright (C) 2010 IAIK, Graz University of Technology
> + *
> + * Author: Stefan Berger<stefanb@us.ibm.com>
> + *         David Safford<safford@us.ibm.com>
> + *
> + * Author: Andreas Niederl<andreas.niederl@iaik.tugraz.at>
> + * Pass through a TPM device rather than using the emulator
> + * Modified to use a separate thread for IO to/from TPM as the Linux TPM driver
> + * framework does not allow non-blocking IO
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation, version 2 of the
> + * License.
> + *
> + *
> + * Implementation of the TIS interface according to specs at
> + * https://www.trustedcomputinggroup.org/groups/pc_client/TCG_PCClientTPMSpecification_1-20_1-00_FINAL.pdf
> + *
> + */
> +
> +#include<sys/types.h>
> +#include<sys/stat.h>
> +#include<errno.h>
> +#include<string.h>
> +
> +#include "qemu-option.h"
> +#include "qemu-config.h"
> +#include "hw/hw.h"
> +#include "hw/pc.h"
> +#include "hw/pci.h"
> +#include "hw/pci_ids.h"
> +#include "qemu-thread.h"
> +
> +//#define TPM_DEBUG
> +
> +#define TPM_MAX_PKT            4096
> +
> +#define TIS_ADDR_BASE                 0xFED40000
> +
> +/* tis registers */
> +#define TPM_REG_ACCESS                0x00
> +#define TPM_REG_INT_ENABLE            0x08
> +#define TPM_REG_INT_VECTOR            0x0c
> +#define TPM_REG_INT_STATUS            0x10
> +#define TPM_REG_INTF_CAPABILITY       0x14
> +#define TPM_REG_STS                   0x18
> +#define TPM_REG_DATA_FIFO             0x24
> +#define TPM_REG_DID_VID               0xf00
> +#define TPM_REG_RID                   0xf04
> +
> +#define STS_VALID                    (1<<  7)
> +#define STS_COMMAND_READY            (1<<  6)
> +#define STS_TPM_GO                   (1<<  5)
> +#define STS_DATA_AVAILABLE           (1<<  4)
> +#define STS_EXPECT                   (1<<  3)
> +#define STS_RESPONSE_RETRY           (1<<  1)
> +
> +#define ACCESS_TPM_REG_VALID_STS     (1<<  7)
> +#define ACCESS_ACTIVE_LOCALITY       (1<<  5)
> +#define ACCESS_BEEN_SEIZED           (1<<  4)
> +#define ACCESS_SEIZE                 (1<<  3)
> +#define ACCESS_PENDING_REQUEST       (1<<  2)
> +#define ACCESS_REQUEST_USE           (1<<  1)
> +#define ACCESS_TPM_ESTABLISHMENT     (1<<  0)
> +
> +#define INT_ENABLED                  (1<<  31)
> +#define INT_DATA_AVAILABLE           (1<<  0)
> +#define INT_LOCALITY_CHANGED         (1<<  2)
> +#define INT_COMMAND_READY            (1<<  7)
> +
> +#define INTERRUPTS_SUPPORTED         (INT_LOCALITY_CHANGED | \
> +                                      INT_DATA_AVAILABLE   | \
> +                                      INT_COMMAND_READY)
> +#define CAPABILITIES_SUPPORTED       ((1<<  4) |            \
> +                                      INTERRUPTS_SUPPORTED)
> +
> +enum {
> +  STATE_IDLE = 0,
> +  STATE_READY,
> +  STATE_COMPLETION,
> +  STATE_EXECUTION,
> +  STATE_RECEPTION
> +};
> +
> +#define NUM_LOCALITIES   5
> +#define NO_LOCALITY      0xff
> +
> +#define IS_VALID_LOC(x) ((x)<  NUM_LOCALITIES)
> +
> +#define TPM_DID          0x0001
> +#define TPM_VID          0x0001
> +#define TPM_RID          0x0001
> +
> +
> +const char *tpm_device = NULL;
> +
> +/* locality data */
> +typedef struct TPMLocal {
> +    uint32_t state;
> +    uint8_t access;
> +    uint8_t sts;
> +    uint32_t inte;
> +    uint32_t ints;
> +} tpmLoc;
> +
> +/* overall state of the TPM interface; 's' marks as save upon suspension */
> +typedef struct TPMState {
> +    uint8_t    transmit;   /* boolean value */
> +                           /* set when tpm transmit in progress */
> +    uint8_t    aborting;   /* boolean value */
> +    uint32_t   offset;           /* s */
> +    uint8_t    buf[TPM_MAX_PKT]; /* s */
> +    int        tpmfd;            /* s */
> +
> +    /* path to host tpm device */
> +    const char *path;            /* s */
> +    uint32_t    path_len;        /* s */
> +
> +    QemuMutex  tpm_thread_lock;
> +    QemuMutex  tpm_thread_abort_lock;
> +    QemuCond   tpm_thread_cond_data_avail;
> +    QemuThread tpm_thread_id;
> +
> +    uint8_t active_loc;         /* s */
> +    uint8_t aborting_locty;
> +    uint8_t next_locty;
> +    uint8_t irq_pending;        /* s */
> +    tpmLoc loc[NUM_LOCALITIES]; /* s */
> +    qemu_irq_handler set_irq;
> +    void *irq_opaque;
> +    int irq;
> +} tpmState;
> +
> +
> +/* local prototypes */
> +static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask);
> +static void tis_abort(tpmState *s);
> +
> +#ifdef TPM_DEBUG
> +static void showBuff(unsigned char *buff, const char *string);
> +#endif
> +
> +
> +/**********************************************************************
> + helper functions
> + *********************************************************************/
> +
> +static inline uint32_t tpm_get_size_from_buffer(const uint8_t *buffer)
> +{
> +    uint32_t len = (buffer[4]<<  8) + buffer[5];
> +    return len;
> +}
> +
> +static inline uint8_t locality_from_addr(target_phys_addr_t addr)
> +{
> +    return (uint8_t)((addr>>  12)&  0x7);
> +}
> +
> +
> +static void die2(int err, const char *what)
> +{
> +    fprintf(stderr, "%s failed: %s\n", what, strerror(err));
> +    abort();
> +}
> +
> +static void die(const char *what)
> +{
> +    die2(errno, what);
> +}
> +
> +
> +/* borrowed from qemu-char.c */
> +static int unix_write(int fd, const uint8_t *buf, uint32_t len1)
> +{
> +    int ret, len;
> +
> +    len = len1;
> +    while (len>  0) {
> +        ret = write(fd, buf, len);
> +        if (ret<  0) {
> +            if (errno != EINTR&&  errno != EAGAIN)
> +                return -1;
> +        } else if (ret == 0) {
> +            break;
> +        } else {
> +            buf += ret;
> +            len -= ret;
> +        }
> +    }
> +    return len1 - len;
> +}
> +
> +static int unix_tpm_read(int fd, uint8_t *buf)
> +{
> +    int ret, len, len1;
> +    uint8_t *buf1;
> +
> +    buf1 = buf;
> +    len  = sizeof(buf) + TPM_MAX_PKT;
> +    while ((buf1 - buf)<  6) {
> +        ret = read(fd, buf1, len);
> +        if (ret<  0) {
> +            if (errno != EINTR&&  errno != EAGAIN)
> +                return -1;
> +        } else {
> +            buf1 += ret;
> +        }
> +    }
> +
> +    len1 = tpm_get_size_from_buffer(buf);
> +    if (len1>  TPM_MAX_PKT) {
> +        fprintf(stderr, "Error: received invalid tpm response size: %d\n",
> +                len1);
> +        return -1;
> +    }
> +
> +    /* response size minus already read data */
> +    len  = len1 - (buf1 - buf);
> +    while (len>  0) {
> +        ret = read(fd, buf, len);
> +        if (ret<  0) {
> +            if (errno != EINTR&&  errno != EAGAIN)
> +                return -1;
> +        } else if (ret == 0) {
> +            break;
> +        } else {
> +            buf += ret;
> +            len -= ret;
> +        }
> +    }
> +    return len1 - len;
> +}
> +
> +/****************************************************************************/
> +/* Transmit request to TPM and read Response                                */
> +/****************************************************************************/
> +
> +static void *tpm_thread(void *opaque)
> +{
> +    tpmState *s = opaque;
> +    sigset_t  set;
> +    uint32_t  size = 0;
> +    /* hardcode locality 0 */
> +    uint8_t   locty = 0;
> +#ifdef TPM_DEBUG
> +    uint32_t  ret;
> +#endif
> +
> +    /* block all signals */
> +    if (sigfillset(&set)) die("sigfillset");
> +    if (sigprocmask(SIG_BLOCK,&set, NULL)) die("sigprocmask");
> +
> +    qemu_mutex_lock(&s->tpm_thread_lock);
> +    while(1) {
> +        if(!s->transmit)
> +            qemu_cond_wait(&s->tpm_thread_cond_data_avail,&s->tpm_thread_lock);
> +        qemu_mutex_unlock(&s->tpm_thread_lock);
> +
> +#ifdef TPM_DEBUG
> +        showBuff(s->buf, "To TPM");
> +#endif
> +        size = tpm_get_size_from_buffer(s->buf);
> +
> +        if(unix_write(s->tpmfd, s->buf, size)<  0) {
> +            fprintf(stderr, "Error: while transmitting data to host tpm"
> +                    ": %s (%i)\n",
> +                    strerror(errno), errno);
> +            tis_abort(s);
> +            qemu_mutex_lock(&s->tpm_thread_lock);
> +            continue;
> +        }
> +
> +        if(unix_tpm_read(s->tpmfd, s->buf)<  0) {
> +            fprintf(stderr, "Error: while reading data from host tpm"
> +                    ": %s (%i)\n",
> +                    strerror(errno), errno);
> +            tis_abort(s);
> +            qemu_mutex_lock(&s->tpm_thread_lock);
> +            continue;
> +        }
> +
> +        qemu_mutex_lock(&s->tpm_thread_abort_lock);
> +        if(!s->aborting) {
> +#ifdef TPM_DEBUG
> +            showBuff(s->buf, "From TPM");
> +
> +            ret = (s->buf[8])*256 + s->buf[9];
> +            if (ret)
> +                printf("tpm command failed with error %d\n", ret);
> +            else
> +                printf("tpm command succeeded\n");
> +#endif
> +
> +            qemu_mutex_lock(&s->tpm_thread_lock);
> +            s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE;
> +            s->loc[locty].state = STATE_COMPLETION;
> +            s->transmit = 0;
> +            tis_raise_irq(s, locty, INT_DATA_AVAILABLE);
> +        } else {
> +            qemu_mutex_lock(&s->tpm_thread_lock);
> +            tis_abort(s);
> +#ifdef TPM_DEBUG
> +            printf("Abort is complete.\n");
> +#endif
> +            s->transmit  = 0;
> +        }
> +        qemu_mutex_unlock(&s->tpm_thread_abort_lock);
> +    }
> +
> +    return NULL;
> +}
> +
> +
> +/**********************************************************************/
> +
> +/*
> + * read a byte of response data
> + */
> +static uint32_t tpm_data_read(tpmState *s, uint8_t locty)
> +{
> +    uint32_t ret, len;
> +
> +    if (s->loc[locty].state != STATE_COMPLETION) {
> +#ifdef TPM_DEBUG
> +        printf("tpm_data_read with no data available!\n");
> +#endif
> +        return 0xff;
> +    }
> +
> +    len = tpm_get_size_from_buffer(s->buf);
> +    ret = s->buf[s->offset++];
> +    if (s->offset>= len) {
> +        s->loc[locty].sts = STS_VALID ;
> +        s->offset = 0;
> +    }
> +#ifdef TPM_DEBUG
> +    printf("tpm_data_read byte x%02x   [%d]\n",ret,s->offset-1);
> +#endif
> +    return ret;
> +}
> +
> +/* raise an interrupt if allowed */
> +static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask)
> +{
> +    if (!s->irq_pending&&
> +        (s->loc[locty].inte&  INT_ENABLED)&&
> +        (s->loc[locty].inte&  irqmask)) {
> +        if ((irqmask&  s->loc[locty].ints) == 0) {
> +#ifdef TPM_DEBUG
> +            printf("Raising IRQ for flag %08x\n",irqmask);
> +#endif
> +            s->set_irq(s->irq_opaque, s->irq, 1);
> +            s->irq_pending = 1;
> +            s->loc[locty].ints |= irqmask;
> +        }
> +    }
> +}
> +
> +/* abort execution of command */
> +static void tis_abort(tpmState *s)
> +{
> +    s->offset = 0;
> +    s->active_loc = s->next_locty;
> +    qemu_mutex_lock(&s->tpm_thread_lock);
> +    s->transmit  = 0;
> +    qemu_mutex_unlock(&s->tpm_thread_lock);
> +
> +    /*
> +     * Need to react differently depending on who's aborting now and
> +     * which locality will become active afterwards.
> +     */
> +    if (s->aborting_locty == s->next_locty) {
> +        s->loc[s->aborting_locty].state = STATE_READY;
> +        s->loc[s->aborting_locty].sts   = STS_COMMAND_READY;
> +        tis_raise_irq(s, s->aborting_locty, INT_COMMAND_READY);
> +    }
> +
> +    /* locality after abort is another one than the current one */
> +    if (s->aborting_locty != s->next_locty&&  s->next_locty != NO_LOCALITY) {
> +        s->loc[s->aborting_locty].access&= ~ACCESS_ACTIVE_LOCALITY;
> +        s->loc[s->next_locty].access     |=  ACCESS_ACTIVE_LOCALITY;
> +        tis_raise_irq(s, s->next_locty, INT_LOCALITY_CHANGED);
> +    }
> +
> +    s->aborting_locty = NO_LOCALITY; /* nobody's aborting a command anymore */
> +}
> +
> +/* abort current command */
> +static void tis_prep_abort(tpmState *s, uint8_t locty, uint8_t newlocty)
> +{
> +    s->aborting_locty = locty; /* current locality */
> +    s->next_locty = newlocty;  /* locality after successful abort */
> +
> +    s->next_locty = 0;  /* only locality 0 available */
> +
> +    /*
> +     * only abort a command using an interrupt if currently executing
> +     * a command AND if there's a valid connection to the vTPM.
> +     */
> +    if (s->loc[locty].state == STATE_EXECUTION) {
> +        qemu_mutex_lock(&s->tpm_thread_abort_lock);
> +        s->aborting = 1;
> +        qemu_mutex_unlock(&s->tpm_thread_abort_lock);
> +    } else {
> +        tis_abort(s);
> +    }
> +}
> +
> +
> +/*
> + * Read a register of the TIS interface
> + * See specs pages 33-63 for description of the registers
> + */
> +static uint32_t tis_mem_readl(void *opaque, target_phys_addr_t addr)
> +{
> +    tpmState *s = (tpmState *)opaque;
> +    uint16_t offset = addr&  0xffc;
> +    uint8_t shift = (addr&  0x3) * 8;
> +    uint32_t val = 0;
> +    uint8_t locty = locality_from_addr(addr);
> +
> +    if (offset == TPM_REG_ACCESS) {
> +        if (locty>  0) {
> +            /* no access to localities other than 0 possible */
> +            val = 0;
> +        } else {
> +            if (s->active_loc == locty) {
> +                s->loc[locty].access |= (1<<  5);
> +            } else {
> +                s->loc[locty].access&= ~(1<<  5);
> +            }
> +            val = s->loc[locty].access;
> +        }
> +    } else
> +    if (locty>  0) {
> +        /* higher localities deactivated */
> +        val = 0xffff;
> +    } else
> +    if (offset == TPM_REG_INT_ENABLE) {
> +        val = s->loc[locty].inte;
> +    } else
> +    if (offset == TPM_REG_INT_VECTOR) {
> +        val = s->irq;
> +    } else
> +    if (offset == TPM_REG_INT_STATUS) {
> +        val = s->loc[locty].ints;
> +    } else
> +    if (offset == TPM_REG_INTF_CAPABILITY) {
> +        val = CAPABILITIES_SUPPORTED;
> +    } else
> +    if (offset == TPM_REG_STS) { /* status register */
> +        /* ??? - appears to be just some value as we aren't a hardware TPM */
> +        val = (sizeof(s->buf) - s->offset)<<  8 | s->loc[locty].sts;
> +    } else
> +    if (offset == TPM_REG_DATA_FIFO) {
> +      val = tpm_data_read(s, locty);
> +    } else
> +    if (offset == TPM_REG_DID_VID) {
> +        val = (TPM_DID<<  16) | TPM_VID;
> +    } else
> +    if (offset == TPM_REG_RID) {
> +         val = TPM_RID;
> +    }
> +
> +    if (shift&&  locty == 0) {
> +        val>>= shift;
> +    }
> +
> +#ifdef TPM_DEBUG
> +    printf(" read(%08x) = %08x\n",
> +           (int)addr,
> +           val);
> +#endif
> +
> +    return val;
> +}
> +
> +/*
> + * Write a value to a register of the TIS interface
> + * See specs pages 33-63 for description of the registers
> + */
> +static void tis_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    tpmState* s=(tpmState*)opaque;
> +    uint16_t off = addr&  0xfff;
> +    uint8_t locty = locality_from_addr(addr);
> +    uint32_t len;
> +
> +#ifdef TPM_DEBUG
> +    printf("write(%08x) = %08x\n",
> +           (int)addr,
> +           val);
> +#endif
> +
> +    /* linux tpm_tis driver only uses locality 0 */
> +    if (locty>  0) {
> +        fprintf(stderr, "Warning: Write to unavailable locality: %d \n", locty);
> +        return;
> +    }
> +#ifdef TPM_DEBUG
> +    if(s->transmit) {
> +        fprintf(stderr, "Warning: tpm received data while transmit"
> +                " in progress.\n");
> +        printf("WARN: write(%08x) = %08x\n",
> +                (int)addr,
> +                val);
> +    }
> +#endif
> +
> +    if (off == TPM_REG_ACCESS) {
> +        if (val&  ACCESS_ACTIVE_LOCALITY) {
> +            /* give up locality if currently owned */
> +            if (s->active_loc == locty) {
> +                /*uint8_t newlocty = NO_LOCALITY;*/
> +                s->loc[locty].access&= ~(ACCESS_PENDING_REQUEST);
> +                /* anybody wants the locality ? */
> +                if (s->loc[locty].access&  ACCESS_REQUEST_USE) {
> +                    s->loc[locty].access |= ACCESS_TPM_REG_VALID_STS;
> +                    s->loc[locty].access&= ~ACCESS_REQUEST_USE;
> +                }
> +                tis_prep_abort(s, locty, locty);
> +            }
> +        }
> +        if (val&  ACCESS_BEEN_SEIZED) {
> +            /* clear the flag */
> +            s->loc[locty].access&= ~ACCESS_BEEN_SEIZED;
> +        }
> +        if (val&  ACCESS_SEIZE) {
> +            if (locty>  s->active_loc&&  IS_VALID_LOC(s->active_loc)) {
> +                s->loc[s->active_loc].access |= ACCESS_BEEN_SEIZED;
> +                s->loc[locty].access = ACCESS_TPM_REG_VALID_STS;
> +                tis_prep_abort(s, s->active_loc, locty);
> +            }
> +        }
> +        if (val&  ACCESS_REQUEST_USE) {
> +            if (IS_VALID_LOC(s->active_loc)) {
> +                /* locality election */
> +                s->loc[s->active_loc].access |= ACCESS_PENDING_REQUEST;
> +            } else {
> +                /* no locality active ->  make this one active now */
> +                s->loc[locty].access |= ACCESS_ACTIVE_LOCALITY;
> +                s->active_loc = locty;
> +                tis_raise_irq(s, locty, INT_LOCALITY_CHANGED);
> +            }
> +        }
> +    } else
> +    if (off == TPM_REG_INT_ENABLE) {
> +        s->loc[locty].inte = (val&  (INT_ENABLED | (0x3<<  3) |
> +                                     INTERRUPTS_SUPPORTED));
> +    } else
> +    if (off == TPM_REG_INT_STATUS) {
> +        /* clearing of interrupt flags */
> +        if ((val&  INTERRUPTS_SUPPORTED)&&
> +            (s->loc[locty].ints&  INTERRUPTS_SUPPORTED)) {
> +            s->set_irq(s->irq_opaque, s->irq, 0);
> +            s->irq_pending = 0;
> +        }
> +        s->loc[locty].ints&= ~(val&  INTERRUPTS_SUPPORTED);
> +    } else
> +    if (off == TPM_REG_STS) {
> +        if (val&  STS_COMMAND_READY) {
> +            if (s->loc[locty].state == STATE_IDLE) {
> +                s->loc[locty].sts   = STS_COMMAND_READY;
> +                s->loc[locty].state = STATE_READY;
> +                tis_raise_irq(s, locty, INT_COMMAND_READY);
> +            } else if (s->loc[locty].state == STATE_COMPLETION ||
> +                       s->loc[locty].state == STATE_EXECUTION  ||
> +                       s->loc[locty].state == STATE_RECEPTION) {
> +                /* abort currently running command */
> +                tis_prep_abort(s, locty, locty);
> +            }
> +        }
> +        if (val&  STS_TPM_GO) {
> +            s->offset   = 0;
> +
> +            if (s->transmit != 1) {
> +                qemu_mutex_lock(&s->tpm_thread_lock);
> +                s->transmit = 1;
> +                s->loc[locty].state = STATE_EXECUTION;
> +                qemu_mutex_unlock(&s->tpm_thread_lock);
> +                qemu_cond_signal(&s->tpm_thread_cond_data_avail);
> +            }
> +        }
> +        if (val&  STS_RESPONSE_RETRY) {
> +            s->offset = 0;
> +        }
> +    } else if (off == TPM_REG_DATA_FIFO) {
> +        /* data fifo */
> +        if (s->loc[locty].state == STATE_IDLE ||
> +            s->loc[locty].state == STATE_EXECUTION ||
> +            s->loc[locty].state == STATE_COMPLETION) {
> +            /* drop the byte */
> +        } else {
> +#ifdef TPM_DEBUG
> +        printf("Byte to send to TPM: %02x at offset: %03d\n", val, s->offset);
> +#endif
> +            s->loc[locty].state = STATE_RECEPTION;
> +            s->loc[locty].sts   = STS_EXPECT | STS_VALID;
> +
> +            if (s->offset<  TPM_MAX_PKT)
> +                s->buf[s->offset++] = (uint8_t)val;
> +
> +            if (s->offset>  5) {
> +                /* we have a packet length - see if we have all of it */
> +                len = tpm_get_size_from_buffer(s->buf);
> +                if (s->offset>= len) {
> +#ifdef TPM_DEBUG
> +                    printf("We have a complete packet of %u bytes\n", len);
> +#endif
> +                    s->offset   = 0;
> +
> +                    qemu_mutex_lock(&s->tpm_thread_lock);
> +                    s->transmit = 1;
> +                    qemu_mutex_unlock(&s->tpm_thread_lock);
> +                    qemu_cond_signal(&s->tpm_thread_cond_data_avail);
> +                    s->loc[locty].sts = STS_VALID;
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +
> +static CPUReadMemoryFunc *tis_readfn[3]={
> +    tis_mem_readl,
> +    tis_mem_readl,
> +    tis_mem_readl
> +};
> +
> +static CPUWriteMemoryFunc *tis_writefn[3]={
> +    tis_mem_writel,
> +    tis_mem_writel,
> +    tis_mem_writel
> +};
> +
> +/*
> + * Save the internal state of this interface for later resumption.
> + * Need to get any outstanding responses from the vTPM back, so
> + * this might delay the suspend for a while.
> + */
> +static void tpm_save(QEMUFile* f,void* opaque)
> +{
> +    tpmState* s=(tpmState*)opaque;
> +    uint8_t locty = s->active_loc;
> +    int c;
> +
> +    /* need to wait for outstanding requests to complete */
> +    if (s->loc[locty].state == STATE_EXECUTION) {
> +        int repeats = 30; /* 30 seconds; really should be infty */
> +        while (repeats>  0&&
> +               !(s->loc[s->active_loc].sts&  STS_DATA_AVAILABLE)) {
> +            sleep(1);
> +        }
> +    }
> +
> +    close(s->tpmfd);
> +
> +    qemu_put_be32s(f,&s->offset);
> +    qemu_put_buffer(f, s->buf, TPM_MAX_PKT);
> +    qemu_put_8s(f,&s->active_loc);
> +    qemu_put_8s(f,&s->irq_pending);
> +    for (c = 0; c<  NUM_LOCALITIES; c++) {
> +        qemu_put_be32s(f,&s->loc[c].state);
> +        qemu_put_8s(f,&s->loc[c].access);
> +        qemu_put_8s(f,&s->loc[c].sts);
> +        qemu_put_be32s(f,&s->loc[c].inte);
> +        qemu_put_be32s(f,&s->loc[c].ints);
> +    }
> +    qemu_put_buffer(f,  (uint8_t*) s->path, s->path_len);
> +    qemu_put_be32s(f,&s->path_len);
> +}
> +
> +
> +/*
> + * load TIS interface state
> + */
> +static int tpm_load(QEMUFile* f,void* opaque,int version_id)
> +{
> +    tpmState* s=(tpmState*)opaque;
> +    int c;
> +
> +    if (version_id != 1)
> +        return -EINVAL;
> +
> +    qemu_get_be32s(f,&s->offset);
> +    qemu_get_buffer(f, s->buf, TPM_MAX_PKT);
> +    qemu_get_8s(f,&s->active_loc);
> +    qemu_get_8s(f,&s->irq_pending);
> +    for (c = 0; c<  NUM_LOCALITIES; c++) {
> +        qemu_get_be32s(f,&s->loc[c].state);
> +        qemu_get_8s(f,&s->loc[c].access);
> +        qemu_get_8s(f,&s->loc[c].sts);
> +        qemu_get_be32s(f,&s->loc[c].inte);
> +        qemu_get_be32s(f,&s->loc[c].ints);
> +    }
> +    qemu_get_be32s(f,&s->path_len);
> +    qemu_get_buffer(f,  (uint8_t*) s->path, s->path_len);
> +
> +    s->tpmfd = open(s->path, O_RDWR);
> +
> +    if(s->tpmfd<  0)
> +        hw_error("Cannot open %s: %s (%i)\n", s->path, strerror(errno), errno);
> +
> +    qemu_mutex_init(&s->tpm_thread_lock);
> +    qemu_cond_init(&s->tpm_thread_cond_data_avail);
> +    qemu_mutex_init(&s->tpm_thread_abort_lock);
> +
> +    qemu_thread_create(&s->tpm_thread_id,&tpm_thread, s);
> +
> +    return 0;
> +}
> +
> +
> +typedef struct LPCtpmState {
> +    tpmState tpm;
> +    int mem;
> +} LPCtpmState;
> +
> +
> +/*
> + * initialize TIS interface
> + */
> +
> +struct IRQState {
> +    qemu_irq_handler handler;
> +    void *opaque;
> +    int n;
> +};
> +
> +void tpm_tis_init(qemu_irq irq);
> +
> +void tpm_tis_init(qemu_irq irq)
> +{
> +    LPCtpmState *d;
> +    tpmState *s;
> +    int c = 0;
> +    int tpmfd = -1;
> +
> +    if(tpm_device == NULL) {
> +        return;
> +    }
> +
> +    tpmfd = open(tpm_device, O_RDWR);
> +    if(tpmfd<  0) {
> +        fprintf(stderr, "Cannot open %s: %s (%i)\n", tpm_device,
> +                strerror(errno), errno);
> +        return;
> +    }
> +
> +    d = qemu_mallocz(sizeof(LPCtpmState));
> +    d->mem = cpu_register_io_memory(tis_readfn, tis_writefn, d);
> +
> +    if (d->mem == -1) {
> +        fprintf(stderr, "Failed to register IO memory TPM TIS\n");
> +        return;
> +    }
> +
> +    cpu_register_physical_memory(TIS_ADDR_BASE,
> +                                 0x1000 * NUM_LOCALITIES, d->mem);
> +
> +    /* initialize tpmState */
> +    s =&d->tpm;
> +
> +    s->offset     = 0;
> +    s->transmit   = 0;
> +    s->aborting   = 0;
> +    s->active_loc = NO_LOCALITY;
> +
> +    while (c<  NUM_LOCALITIES) {
> +        s->loc[c].access = (1<<  7);
> +        s->loc[c].sts = 0;
> +        s->loc[c].inte = (1<<  3);
> +        s->loc[c].ints = 0;
> +        s->loc[c].state = STATE_IDLE;
> +        c++;
> +    }
> +    s->set_irq    = irq->handler;
> +    s->irq_opaque = irq->opaque;
> +    s->irq        = irq->n;
> +    s->aborting_locty = NO_LOCALITY;
> +    s->tpmfd      = tpmfd;
> +    s->path_len   = strlen(tpm_device);
> +    s->path       = tpm_device;
> +
> +    memset(s->buf,0,sizeof(s->buf));
> +
> +    qemu_mutex_init(&s->tpm_thread_lock);
> +    qemu_cond_init(&s->tpm_thread_cond_data_avail);
> +    qemu_mutex_init(&s->tpm_thread_abort_lock);
> +
> +    qemu_thread_create(&s->tpm_thread_id,&tpm_thread, s);
> +
> +    register_savevm(NULL, "tpm-tis", 0, 1, tpm_save, tpm_load, s);
> +}
> +
> +
> +int tpm_init(QemuOpts *opts) {
> +    if(tpm_device == NULL) {
> +        tpm_device = qemu_opt_get(opts, "dev");
> +    }
> +    return 0;
> +}
> +
> +/****************************************************************************/
> +/*                                                                          */
> +/* optional verbose logging of data to/from tpm chip                        */
> +/*                                                                          */
> +/****************************************************************************/
> +#ifdef TPM_DEBUG
> +static void showBuff(unsigned char *buff, const char *string)
> +{
> +    uint32_t i, len;
> +
> +    len = tpm_get_size_from_buffer(buff);
> +    printf("%s length=%d\n", string, len);
> +    for (i = 0; i<  len; i++) {
> +        if (i&&  !(i % 16)) {
> +            printf("\n");
> +        }
> +        printf("%.2X ", buff[i]);
> +    }
> +    printf("\n");
> +}
> +#endif
> +
> +
>
Blue Swirl Aug. 25, 2010, 7:46 p.m. UTC | #2
On Wed, Aug 25, 2010 at 4:33 PM, Andreas Niederl
<andreas.niederl@iaik.tugraz.at> wrote:
> This implementation is based on the TPM 1.2 interface for virtualized TPM
> devices from the Xen-4.0.0 ioemu-qemu-xen fork.
>
> A separate thread is used for I/O to the host TPM device because the Linux TPM
> driver does not allow for non-blocking I/O.
>
> Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at>

In general, please read CODING_STYLE.

Ideally, the host and guest devices should be decoupled so that the
guest visible device should still be functional without host TPM
support.

> ---
>  Makefile.target |    3 +
>  configure       |    9 +
>  hw/pc_piix.c    |    8 +
>  hw/tpm_tis.c    |  831 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 851 insertions(+), 0 deletions(-)
>  create mode 100644 hw/tpm_tis.c
>
> diff --git a/Makefile.target b/Makefile.target
> index c8281e9..2226c75 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -193,6 +193,9 @@ obj-y += e1000.o
>  # Inter-VM PCI shared memory
>  obj-$(CONFIG_KVM) += ivshmem.o
>
> +# TPM passthrough device
> +obj-$(CONFIG_TPM) += tpm_tis.o qemu-thread.o

The device does not have target dependencies, so please move these
lines to Makefile.objs.

> +
>  # Hardware support
>  obj-i386-y += vga.o
>  obj-i386-y += mc146818rtc.o i8259.o pc.o
> diff --git a/configure b/configure
> index a20371c..e1b55a8 100755
> --- a/configure
> +++ b/configure
> @@ -315,6 +315,7 @@ pkgversion=""
>  check_utests="no"
>  user_pie="no"
>  zero_malloc=""
> +tpm="no"
>
>  # OS specific
>  if check_define __linux__ ; then
> @@ -448,6 +449,7 @@ AIX)
>   usb="linux"
>   if [ "$cpu" = "i386" -o "$cpu" = "x86_64" ] ; then
>     audio_possible_drivers="$audio_possible_drivers fmod"
> +    tpm="yes"
>   fi
>  ;;
>  esac
> @@ -707,6 +709,8 @@ for opt do
>   ;;
>   --enable-vhost-net) vhost_net="yes"
>   ;;
> +  --disable-tpm) tpm="no"
> +  ;;
>   --*dir)
>   ;;
>   *) echo "ERROR: unknown option $opt"; show_help="yes"
> @@ -895,6 +899,7 @@ echo "  --enable-docs            enable documentation build"
>  echo "  --disable-docs           disable documentation build"
>  echo "  --disable-vhost-net      disable vhost-net acceleration support"
>  echo "  --enable-vhost-net       enable vhost-net acceleration support"
> +echo "  --disable-tpm            disable tpm passthrough device emulation"
>  echo ""
>  echo "NOTE: The object files are built at the place where configure is launched"
>  exit 1
> @@ -2187,6 +2192,7 @@ echo "preadv support    $preadv"
>  echo "fdatasync         $fdatasync"
>  echo "uuid support      $uuid"
>  echo "vhost-net support $vhost_net"
> +echo "tpm support       $tpm"
>
>  if test $sdl_too_old = "yes"; then
>  echo "-> Your SDL version is too old - please upgrade to have SDL support"
> @@ -2423,6 +2429,9 @@ fi
>  if test "$fdatasync" = "yes" ; then
>   echo "CONFIG_FDATASYNC=y" >> $config_host_mak
>  fi
> +if test "$tpm" = "yes" ; then
> +  echo "CONFIG_TPM=y" >> $config_host_mak
> +fi
>
>  # XXX: suppress that
>  if [ "$bsd" = "yes" ] ; then
> diff --git a/hw/pc_piix.c b/hw/pc_piix.c
> index 32a1057..ebc3478 100644
> --- a/hw/pc_piix.c
> +++ b/hw/pc_piix.c
> @@ -38,6 +38,10 @@
>
>  #define MAX_IDE_BUS 2
>
> +#ifdef CONFIG_TPM
> +void tpm_tis_init(qemu_irq irq);
> +#endif

This belongs to a header file.

> +
>  static const int ide_iobase[MAX_IDE_BUS] = { 0x1f0, 0x170 };
>  static const int ide_iobase2[MAX_IDE_BUS] = { 0x3f6, 0x376 };
>  static const int ide_irq[MAX_IDE_BUS] = { 14, 15 };
> @@ -147,6 +151,10 @@ static void pc_init1(ram_addr_t ram_size,
>         }
>     }
>
> +#ifdef CONFIG_TPM
> +    tpm_tis_init(isa_reserve_irq(11));
> +#endif
> +
>     pc_audio_init(pci_enabled ? pci_bus : NULL, isa_irq);
>
>     pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device,
> diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c
> new file mode 100644
> index 0000000..e109e1a
> --- /dev/null
> +++ b/hw/tpm_tis.c
> @@ -0,0 +1,831 @@
> +/*
> + * tpm_tis.c - QEMU emulator for a 1.2 TPM with TIS interface
> + *
> + * Copyright (C) 2006 IBM Corporation
> + * Copyright (C) 2010 IAIK, Graz University of Technology
> + *
> + * Author: Stefan Berger <stefanb@us.ibm.com>
> + *         David Safford <safford@us.ibm.com>
> + *
> + * Author: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
> + * Pass through a TPM device rather than using the emulator
> + * Modified to use a separate thread for IO to/from TPM as the Linux TPM driver
> + * framework does not allow non-blocking IO
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation, version 2 of the
> + * License.
> + *
> + *
> + * Implementation of the TIS interface according to specs at
> + * https://www.trustedcomputinggroup.org/groups/pc_client/TCG_PCClientTPMSpecification_1-20_1-00_FINAL.pdf
> + *
> + */
> +
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <errno.h>
> +#include <string.h>
> +
> +#include "qemu-option.h"
> +#include "qemu-config.h"
> +#include "hw/hw.h"
> +#include "hw/pc.h"
> +#include "hw/pci.h"
> +#include "hw/pci_ids.h"
> +#include "qemu-thread.h"
> +
> +//#define TPM_DEBUG
> +
> +#define TPM_MAX_PKT            4096
> +
> +#define TIS_ADDR_BASE                 0xFED40000
> +
> +/* tis registers */
> +#define TPM_REG_ACCESS                0x00
> +#define TPM_REG_INT_ENABLE            0x08
> +#define TPM_REG_INT_VECTOR            0x0c
> +#define TPM_REG_INT_STATUS            0x10
> +#define TPM_REG_INTF_CAPABILITY       0x14
> +#define TPM_REG_STS                   0x18
> +#define TPM_REG_DATA_FIFO             0x24
> +#define TPM_REG_DID_VID               0xf00
> +#define TPM_REG_RID                   0xf04
> +
> +#define STS_VALID                    (1 << 7)
> +#define STS_COMMAND_READY            (1 << 6)
> +#define STS_TPM_GO                   (1 << 5)
> +#define STS_DATA_AVAILABLE           (1 << 4)
> +#define STS_EXPECT                   (1 << 3)
> +#define STS_RESPONSE_RETRY           (1 << 1)
> +
> +#define ACCESS_TPM_REG_VALID_STS     (1 << 7)
> +#define ACCESS_ACTIVE_LOCALITY       (1 << 5)
> +#define ACCESS_BEEN_SEIZED           (1 << 4)
> +#define ACCESS_SEIZE                 (1 << 3)
> +#define ACCESS_PENDING_REQUEST       (1 << 2)
> +#define ACCESS_REQUEST_USE           (1 << 1)
> +#define ACCESS_TPM_ESTABLISHMENT     (1 << 0)
> +
> +#define INT_ENABLED                  (1 << 31)
> +#define INT_DATA_AVAILABLE           (1 << 0)
> +#define INT_LOCALITY_CHANGED         (1 << 2)
> +#define INT_COMMAND_READY            (1 << 7)
> +
> +#define INTERRUPTS_SUPPORTED         (INT_LOCALITY_CHANGED | \
> +                                      INT_DATA_AVAILABLE   | \
> +                                      INT_COMMAND_READY)
> +#define CAPABILITIES_SUPPORTED       ((1 << 4) |            \
> +                                      INTERRUPTS_SUPPORTED)
> +
> +enum {
> +  STATE_IDLE = 0,
> +  STATE_READY,
> +  STATE_COMPLETION,
> +  STATE_EXECUTION,
> +  STATE_RECEPTION
> +};
> +
> +#define NUM_LOCALITIES   5
> +#define NO_LOCALITY      0xff
> +
> +#define IS_VALID_LOC(x) ((x) < NUM_LOCALITIES)
> +
> +#define TPM_DID          0x0001
> +#define TPM_VID          0x0001
> +#define TPM_RID          0x0001
> +
> +
> +const char *tpm_device = NULL;

This should be part of TPMState, not a global.

> +
> +/* locality data */
> +typedef struct TPMLocal {
> +    uint32_t state;
> +    uint8_t access;
> +    uint8_t sts;
> +    uint32_t inte;
> +    uint32_t ints;
> +} tpmLoc;

Typedefs should use CamelCase, so this should be named TPMLocal. Why
not TPMLocality (for both)?

> +
> +/* overall state of the TPM interface; 's' marks as save upon suspension */
> +typedef struct TPMState {
> +    uint8_t    transmit;   /* boolean value */
> +                           /* set when tpm transmit in progress */
> +    uint8_t    aborting;   /* boolean value */
> +    uint32_t   offset;           /* s */
> +    uint8_t    buf[TPM_MAX_PKT]; /* s */
> +    int        tpmfd;            /* s */
> +
> +    /* path to host tpm device */
> +    const char *path;            /* s */
> +    uint32_t    path_len;        /* s */
> +
> +    QemuMutex  tpm_thread_lock;
> +    QemuMutex  tpm_thread_abort_lock;
> +    QemuCond   tpm_thread_cond_data_avail;
> +    QemuThread tpm_thread_id;
> +
> +    uint8_t active_loc;         /* s */
> +    uint8_t aborting_locty;
> +    uint8_t next_locty;
> +    uint8_t irq_pending;        /* s */
> +    tpmLoc loc[NUM_LOCALITIES]; /* s */
> +    qemu_irq_handler set_irq;
> +    void *irq_opaque;
> +    int irq;
> +} tpmState;

Same here, TPMState.

> +
> +
> +/* local prototypes */
> +static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask);
> +static void tis_abort(tpmState *s);

It's usually easy to reorder the code so that forward declarations are
not needed.

> +
> +#ifdef TPM_DEBUG
> +static void showBuff(unsigned char *buff, const char *string);

Function names should not use CamelCase, so this should be renamed to
show_buff().

> +#endif
> +
> +
> +/**********************************************************************
> + helper functions
> + *********************************************************************/
> +
> +static inline uint32_t tpm_get_size_from_buffer(const uint8_t *buffer)
> +{
> +    uint32_t len = (buffer[4] << 8) + buffer[5];
> +    return len;
> +}
> +
> +static inline uint8_t locality_from_addr(target_phys_addr_t addr)
> +{
> +    return (uint8_t)((addr >> 12) & 0x7);
> +}
> +
> +
> +static void die2(int err, const char *what)
> +{
> +    fprintf(stderr, "%s failed: %s\n", what, strerror(err));
> +    abort();
> +}
> +
> +static void die(const char *what)
> +{
> +    die2(errno, what);
> +}

There's hw_error() and errx(), no need to reinvent the wheel.

> +
> +
> +/* borrowed from qemu-char.c */
> +static int unix_write(int fd, const uint8_t *buf, uint32_t len1)
> +{
> +    int ret, len;
> +
> +    len = len1;
> +    while (len > 0) {
> +        ret = write(fd, buf, len);
> +        if (ret < 0) {
> +            if (errno != EINTR && errno != EAGAIN)
> +                return -1;
> +        } else if (ret == 0) {
> +            break;
> +        } else {
> +            buf += ret;
> +            len -= ret;
> +        }
> +    }
> +    return len1 - len;
> +}
> +
> +static int unix_tpm_read(int fd, uint8_t *buf)

For consistency, this should be named 'unix_read'.

> +{
> +    int ret, len, len1;
> +    uint8_t *buf1;
> +
> +    buf1 = buf;
> +    len  = sizeof(buf) + TPM_MAX_PKT;
> +    while ((buf1 - buf) < 6) {
> +        ret = read(fd, buf1, len);
> +        if (ret < 0) {
> +            if (errno != EINTR && errno != EAGAIN)
> +                return -1;
> +        } else {
> +            buf1 += ret;
> +        }
> +    }
> +
> +    len1 = tpm_get_size_from_buffer(buf);
> +    if (len1 > TPM_MAX_PKT) {
> +        fprintf(stderr, "Error: received invalid tpm response size: %d\n",
> +                len1);
> +        return -1;
> +    }
> +
> +    /* response size minus already read data */
> +    len  = len1 - (buf1 - buf);
> +    while (len > 0) {
> +        ret = read(fd, buf, len);
> +        if (ret < 0) {
> +            if (errno != EINTR && errno != EAGAIN)
> +                return -1;
> +        } else if (ret == 0) {
> +            break;
> +        } else {
> +            buf += ret;
> +            len -= ret;
> +        }
> +    }
> +    return len1 - len;
> +}
> +
> +/****************************************************************************/
> +/* Transmit request to TPM and read Response                                */
> +/****************************************************************************/
> +
> +static void *tpm_thread(void *opaque)
> +{
> +    tpmState *s = opaque;
> +    sigset_t  set;
> +    uint32_t  size = 0;
> +    /* hardcode locality 0 */
> +    uint8_t   locty = 0;
> +#ifdef TPM_DEBUG
> +    uint32_t  ret;
> +#endif
> +
> +    /* block all signals */
> +    if (sigfillset(&set)) die("sigfillset");

die()/errx() must be on a line of its own, also below.

> +    if (sigprocmask(SIG_BLOCK, &set, NULL)) die("sigprocmask");
> +
> +    qemu_mutex_lock(&s->tpm_thread_lock);
> +    while(1) {
> +        if(!s->transmit)

There should be a space between 'if' and '(', also in other places.

> +            qemu_cond_wait(&s->tpm_thread_cond_data_avail, &s->tpm_thread_lock);
> +        qemu_mutex_unlock(&s->tpm_thread_lock);
> +
> +#ifdef TPM_DEBUG
> +        showBuff(s->buf, "To TPM");
> +#endif
> +        size = tpm_get_size_from_buffer(s->buf);
> +
> +        if(unix_write(s->tpmfd, s->buf, size) < 0) {
> +            fprintf(stderr, "Error: while transmitting data to host tpm"
> +                    ": %s (%i)\n",
> +                    strerror(errno), errno);
> +            tis_abort(s);
> +            qemu_mutex_lock(&s->tpm_thread_lock);
> +            continue;
> +        }
> +
> +        if(unix_tpm_read(s->tpmfd, s->buf) < 0) {
> +            fprintf(stderr, "Error: while reading data from host tpm"
> +                    ": %s (%i)\n",
> +                    strerror(errno), errno);
> +            tis_abort(s);
> +            qemu_mutex_lock(&s->tpm_thread_lock);
> +            continue;
> +        }
> +
> +        qemu_mutex_lock(&s->tpm_thread_abort_lock);
> +        if(!s->aborting) {
> +#ifdef TPM_DEBUG
> +            showBuff(s->buf, "From TPM");
> +
> +            ret = (s->buf[8])*256 + s->buf[9];
> +            if (ret)
> +                printf("tpm command failed with error %d\n", ret);
> +            else
> +                printf("tpm command succeeded\n");
> +#endif
> +
> +            qemu_mutex_lock(&s->tpm_thread_lock);
> +            s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE;
> +            s->loc[locty].state = STATE_COMPLETION;
> +            s->transmit = 0;
> +            tis_raise_irq(s, locty, INT_DATA_AVAILABLE);
> +        } else {
> +            qemu_mutex_lock(&s->tpm_thread_lock);
> +            tis_abort(s);
> +#ifdef TPM_DEBUG
> +            printf("Abort is complete.\n");
> +#endif
> +            s->transmit  = 0;
> +        }
> +        qemu_mutex_unlock(&s->tpm_thread_abort_lock);
> +    }
> +
> +    return NULL;
> +}
> +
> +
> +/**********************************************************************/
> +
> +/*
> + * read a byte of response data
> + */
> +static uint32_t tpm_data_read(tpmState *s, uint8_t locty)
> +{
> +    uint32_t ret, len;
> +
> +    if (s->loc[locty].state != STATE_COMPLETION) {
> +#ifdef TPM_DEBUG
> +        printf("tpm_data_read with no data available!\n");
> +#endif

Please introduce a macro DPRINTF instead. See other devices for examples.

> +        return 0xff;
> +    }
> +
> +    len = tpm_get_size_from_buffer(s->buf);
> +    ret = s->buf[s->offset++];
> +    if (s->offset >= len) {
> +        s->loc[locty].sts = STS_VALID ;
> +        s->offset = 0;
> +    }
> +#ifdef TPM_DEBUG
> +    printf("tpm_data_read byte x%02x   [%d]\n",ret,s->offset-1);
> +#endif
> +    return ret;
> +}
> +
> +/* raise an interrupt if allowed */
> +static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask)
> +{
> +    if (!s->irq_pending &&
> +        (s->loc[locty].inte & INT_ENABLED) &&
> +        (s->loc[locty].inte & irqmask)) {
> +        if ((irqmask & s->loc[locty].ints) == 0) {
> +#ifdef TPM_DEBUG
> +            printf("Raising IRQ for flag %08x\n",irqmask);
> +#endif
> +            s->set_irq(s->irq_opaque, s->irq, 1);
> +            s->irq_pending = 1;
> +            s->loc[locty].ints |= irqmask;
> +        }
> +    }
> +}
> +
> +/* abort execution of command */
> +static void tis_abort(tpmState *s)
> +{
> +    s->offset = 0;
> +    s->active_loc = s->next_locty;
> +    qemu_mutex_lock(&s->tpm_thread_lock);
> +    s->transmit  = 0;
> +    qemu_mutex_unlock(&s->tpm_thread_lock);
> +
> +    /*
> +     * Need to react differently depending on who's aborting now and
> +     * which locality will become active afterwards.
> +     */
> +    if (s->aborting_locty == s->next_locty) {
> +        s->loc[s->aborting_locty].state = STATE_READY;
> +        s->loc[s->aborting_locty].sts   = STS_COMMAND_READY;
> +        tis_raise_irq(s, s->aborting_locty, INT_COMMAND_READY);
> +    }
> +
> +    /* locality after abort is another one than the current one */
> +    if (s->aborting_locty != s->next_locty && s->next_locty != NO_LOCALITY) {
> +        s->loc[s->aborting_locty].access &= ~ACCESS_ACTIVE_LOCALITY;
> +        s->loc[s->next_locty].access     |=  ACCESS_ACTIVE_LOCALITY;
> +        tis_raise_irq(s, s->next_locty, INT_LOCALITY_CHANGED);
> +    }
> +
> +    s->aborting_locty = NO_LOCALITY; /* nobody's aborting a command anymore */
> +}
> +
> +/* abort current command */
> +static void tis_prep_abort(tpmState *s, uint8_t locty, uint8_t newlocty)
> +{
> +    s->aborting_locty = locty; /* current locality */
> +    s->next_locty = newlocty;  /* locality after successful abort */
> +
> +    s->next_locty = 0;  /* only locality 0 available */
> +
> +    /*
> +     * only abort a command using an interrupt if currently executing
> +     * a command AND if there's a valid connection to the vTPM.
> +     */
> +    if (s->loc[locty].state == STATE_EXECUTION) {
> +        qemu_mutex_lock(&s->tpm_thread_abort_lock);
> +        s->aborting = 1;
> +        qemu_mutex_unlock(&s->tpm_thread_abort_lock);
> +    } else {
> +        tis_abort(s);
> +    }
> +}
> +
> +
> +/*
> + * Read a register of the TIS interface
> + * See specs pages 33-63 for description of the registers
> + */
> +static uint32_t tis_mem_readl(void *opaque, target_phys_addr_t addr)
> +{
> +    tpmState *s = (tpmState *)opaque;

Useless cast in C.

> +    uint16_t offset = addr & 0xffc;
> +    uint8_t shift = (addr & 0x3) * 8;
> +    uint32_t val = 0;
> +    uint8_t locty = locality_from_addr(addr);
> +
> +    if (offset == TPM_REG_ACCESS) {

Please use switch() instead of this chain of 'if/else if'.

> +        if (locty > 0) {
> +            /* no access to localities other than 0 possible */
> +            val = 0;
> +        } else {
> +            if (s->active_loc == locty) {
> +                s->loc[locty].access |= (1 << 5);
> +            } else {
> +                s->loc[locty].access &= ~(1 << 5);
> +            }
> +            val = s->loc[locty].access;
> +        }
> +    } else
> +    if (locty > 0) {
> +        /* higher localities deactivated */
> +        val = 0xffff;
> +    } else
> +    if (offset == TPM_REG_INT_ENABLE) {
> +        val = s->loc[locty].inte;
> +    } else
> +    if (offset == TPM_REG_INT_VECTOR) {
> +        val = s->irq;
> +    } else
> +    if (offset == TPM_REG_INT_STATUS) {
> +        val = s->loc[locty].ints;
> +    } else
> +    if (offset == TPM_REG_INTF_CAPABILITY) {
> +        val = CAPABILITIES_SUPPORTED;
> +    } else
> +    if (offset == TPM_REG_STS) { /* status register */
> +        /* ??? - appears to be just some value as we aren't a hardware TPM */
> +        val = (sizeof(s->buf) - s->offset) << 8 | s->loc[locty].sts;
> +    } else
> +    if (offset == TPM_REG_DATA_FIFO) {
> +      val = tpm_data_read(s, locty);
> +    } else
> +    if (offset == TPM_REG_DID_VID) {
> +        val = (TPM_DID << 16) | TPM_VID;
> +    } else
> +    if (offset == TPM_REG_RID) {
> +         val = TPM_RID;
> +    }
> +
> +    if (shift && locty == 0) {
> +        val >>= shift;
> +    }
> +
> +#ifdef TPM_DEBUG
> +    printf(" read(%08x) = %08x\n",
> +           (int)addr,

We have FMT_plx, please use that instead of cast.

> +           val);
> +#endif
> +
> +    return val;
> +}
> +
> +/*
> + * Write a value to a register of the TIS interface
> + * See specs pages 33-63 for description of the registers
> + */
> +static void tis_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    tpmState* s=(tpmState*)opaque;
> +    uint16_t off = addr & 0xfff;
> +    uint8_t locty = locality_from_addr(addr);
> +    uint32_t len;
> +
> +#ifdef TPM_DEBUG
> +    printf("write(%08x) = %08x\n",
> +           (int)addr,
> +           val);
> +#endif
> +
> +    /* linux tpm_tis driver only uses locality 0 */
> +    if (locty > 0) {
> +        fprintf(stderr, "Warning: Write to unavailable locality: %d \n", locty);
> +        return;
> +    }
> +#ifdef TPM_DEBUG
> +    if(s->transmit) {
> +        fprintf(stderr, "Warning: tpm received data while transmit"
> +                " in progress.\n");
> +        printf("WARN: write(%08x) = %08x\n",
> +                (int)addr,
> +                val);
> +    }
> +#endif
> +
> +    if (off == TPM_REG_ACCESS) {
> +        if (val & ACCESS_ACTIVE_LOCALITY) {
> +            /* give up locality if currently owned */
> +            if (s->active_loc == locty) {
> +                /*uint8_t newlocty = NO_LOCALITY;*/
> +                s->loc[locty].access &= ~(ACCESS_PENDING_REQUEST);
> +                /* anybody wants the locality ? */
> +                if (s->loc[locty].access & ACCESS_REQUEST_USE) {
> +                    s->loc[locty].access |= ACCESS_TPM_REG_VALID_STS;
> +                    s->loc[locty].access &= ~ACCESS_REQUEST_USE;
> +                }
> +                tis_prep_abort(s, locty, locty);
> +            }
> +        }
> +        if (val & ACCESS_BEEN_SEIZED) {
> +            /* clear the flag */
> +            s->loc[locty].access &= ~ACCESS_BEEN_SEIZED;
> +        }
> +        if (val & ACCESS_SEIZE) {
> +            if (locty > s->active_loc && IS_VALID_LOC(s->active_loc)) {
> +                s->loc[s->active_loc].access |= ACCESS_BEEN_SEIZED;
> +                s->loc[locty].access = ACCESS_TPM_REG_VALID_STS;
> +                tis_prep_abort(s, s->active_loc, locty);
> +            }
> +        }
> +        if (val & ACCESS_REQUEST_USE) {
> +            if (IS_VALID_LOC(s->active_loc)) {
> +                /* locality election */
> +                s->loc[s->active_loc].access |= ACCESS_PENDING_REQUEST;
> +            } else {
> +                /* no locality active -> make this one active now */
> +                s->loc[locty].access |= ACCESS_ACTIVE_LOCALITY;
> +                s->active_loc = locty;
> +                tis_raise_irq(s, locty, INT_LOCALITY_CHANGED);
> +            }
> +        }
> +    } else
> +    if (off == TPM_REG_INT_ENABLE) {
> +        s->loc[locty].inte = (val & (INT_ENABLED | (0x3 << 3) |
> +                                     INTERRUPTS_SUPPORTED));
> +    } else
> +    if (off == TPM_REG_INT_STATUS) {
> +        /* clearing of interrupt flags */
> +        if ((val & INTERRUPTS_SUPPORTED) &&
> +            (s->loc[locty].ints & INTERRUPTS_SUPPORTED)) {
> +            s->set_irq(s->irq_opaque, s->irq, 0);
> +            s->irq_pending = 0;
> +        }
> +        s->loc[locty].ints &= ~(val & INTERRUPTS_SUPPORTED);
> +    } else
> +    if (off == TPM_REG_STS) {
> +        if (val & STS_COMMAND_READY) {
> +            if (s->loc[locty].state == STATE_IDLE) {
> +                s->loc[locty].sts   = STS_COMMAND_READY;
> +                s->loc[locty].state = STATE_READY;
> +                tis_raise_irq(s, locty, INT_COMMAND_READY);
> +            } else if (s->loc[locty].state == STATE_COMPLETION ||
> +                       s->loc[locty].state == STATE_EXECUTION  ||
> +                       s->loc[locty].state == STATE_RECEPTION) {
> +                /* abort currently running command */
> +                tis_prep_abort(s, locty, locty);
> +            }
> +        }
> +        if (val & STS_TPM_GO) {
> +            s->offset   = 0;
> +
> +            if (s->transmit != 1) {
> +                qemu_mutex_lock(&s->tpm_thread_lock);
> +                s->transmit = 1;
> +                s->loc[locty].state = STATE_EXECUTION;
> +                qemu_mutex_unlock(&s->tpm_thread_lock);
> +                qemu_cond_signal(&s->tpm_thread_cond_data_avail);
> +            }
> +        }
> +        if (val & STS_RESPONSE_RETRY) {
> +            s->offset = 0;
> +        }
> +    } else if (off == TPM_REG_DATA_FIFO) {
> +        /* data fifo */
> +        if (s->loc[locty].state == STATE_IDLE ||
> +            s->loc[locty].state == STATE_EXECUTION ||
> +            s->loc[locty].state == STATE_COMPLETION) {
> +            /* drop the byte */
> +        } else {
> +#ifdef TPM_DEBUG
> +        printf("Byte to send to TPM: %02x at offset: %03d\n", val, s->offset);
> +#endif
> +            s->loc[locty].state = STATE_RECEPTION;
> +            s->loc[locty].sts   = STS_EXPECT | STS_VALID;
> +
> +            if (s->offset < TPM_MAX_PKT)
> +                s->buf[s->offset++] = (uint8_t)val;
> +
> +            if (s->offset > 5) {
> +                /* we have a packet length - see if we have all of it */
> +                len = tpm_get_size_from_buffer(s->buf);
> +                if (s->offset >= len) {
> +#ifdef TPM_DEBUG
> +                    printf("We have a complete packet of %u bytes\n", len);
> +#endif
> +                    s->offset   = 0;
> +
> +                    qemu_mutex_lock(&s->tpm_thread_lock);
> +                    s->transmit = 1;
> +                    qemu_mutex_unlock(&s->tpm_thread_lock);
> +                    qemu_cond_signal(&s->tpm_thread_cond_data_avail);
> +                    s->loc[locty].sts = STS_VALID;
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +
> +static CPUReadMemoryFunc *tis_readfn[3]={

'const'

> +    tis_mem_readl,
> +    tis_mem_readl,
> +    tis_mem_readl
> +};
> +
> +static CPUWriteMemoryFunc *tis_writefn[3]={
> +    tis_mem_writel,
> +    tis_mem_writel,
> +    tis_mem_writel
> +};
> +
> +/*
> + * Save the internal state of this interface for later resumption.
> + * Need to get any outstanding responses from the vTPM back, so
> + * this might delay the suspend for a while.
> + */
> +static void tpm_save(QEMUFile* f,void* opaque)

Please convert the save and load functions to VMState.

> +{
> +    tpmState* s=(tpmState*)opaque;
> +    uint8_t locty = s->active_loc;
> +    int c;
> +
> +    /* need to wait for outstanding requests to complete */
> +    if (s->loc[locty].state == STATE_EXECUTION) {
> +        int repeats = 30; /* 30 seconds; really should be infty */
> +        while (repeats > 0 &&
> +               !(s->loc[s->active_loc].sts & STS_DATA_AVAILABLE)) {
> +            sleep(1);
> +        }
> +    }
> +
> +    close(s->tpmfd);
> +
> +    qemu_put_be32s(f,&s->offset);
> +    qemu_put_buffer(f, s->buf, TPM_MAX_PKT);
> +    qemu_put_8s(f, &s->active_loc);
> +    qemu_put_8s(f, &s->irq_pending);
> +    for (c = 0; c < NUM_LOCALITIES; c++) {
> +        qemu_put_be32s(f, &s->loc[c].state);
> +        qemu_put_8s(f, &s->loc[c].access);
> +        qemu_put_8s(f, &s->loc[c].sts);
> +        qemu_put_be32s(f, &s->loc[c].inte);
> +        qemu_put_be32s(f, &s->loc[c].ints);
> +    }
> +    qemu_put_buffer(f,  (uint8_t*) s->path, s->path_len);
> +    qemu_put_be32s(f,  &s->path_len);
> +}
> +
> +
> +/*
> + * load TIS interface state
> + */
> +static int tpm_load(QEMUFile* f,void* opaque,int version_id)
> +{
> +    tpmState* s=(tpmState*)opaque;
> +    int c;
> +
> +    if (version_id != 1)
> +        return -EINVAL;
> +
> +    qemu_get_be32s(f,&s->offset);
> +    qemu_get_buffer(f, s->buf, TPM_MAX_PKT);
> +    qemu_get_8s(f, &s->active_loc);
> +    qemu_get_8s(f, &s->irq_pending);
> +    for (c = 0; c < NUM_LOCALITIES; c++) {
> +        qemu_get_be32s(f, &s->loc[c].state);
> +        qemu_get_8s(f, &s->loc[c].access);
> +        qemu_get_8s(f, &s->loc[c].sts);
> +        qemu_get_be32s(f, &s->loc[c].inte);
> +        qemu_get_be32s(f, &s->loc[c].ints);
> +    }
> +    qemu_get_be32s(f,  &s->path_len);
> +    qemu_get_buffer(f,  (uint8_t*) s->path, s->path_len);
> +
> +    s->tpmfd = open(s->path, O_RDWR);

What happens to old s->tpmfd? The user can issue multiple 'loadvm' commands.

> +
> +    if(s->tpmfd < 0)
> +        hw_error("Cannot open %s: %s (%i)\n", s->path, strerror(errno), errno);
> +
> +    qemu_mutex_init(&s->tpm_thread_lock);
> +    qemu_cond_init( &s->tpm_thread_cond_data_avail);
> +    qemu_mutex_init(&s->tpm_thread_abort_lock);
> +
> +    qemu_thread_create(&s->tpm_thread_id, &tpm_thread, s);
> +
> +    return 0;
> +}
> +
> +
> +typedef struct LPCtpmState {
> +    tpmState tpm;
> +    int mem;
> +} LPCtpmState;
> +
> +
> +/*
> + * initialize TIS interface
> + */
> +
> +struct IRQState {
> +    qemu_irq_handler handler;
> +    void *opaque;
> +    int n;
> +};

Why is this needed?

> +
> +void tpm_tis_init(qemu_irq irq);

This belongs to a header file somewhere.

> +
> +void tpm_tis_init(qemu_irq irq)
> +{

Please convert to qdev. Some of the initialization code can be
refactored to a reset handler as well.

> +    LPCtpmState *d;
> +    tpmState *s;
> +    int c = 0;
> +    int tpmfd = -1;
> +
> +    if(tpm_device == NULL) {
> +        return;
> +    }
> +
> +    tpmfd = open(tpm_device, O_RDWR);
> +    if(tpmfd < 0) {
> +        fprintf(stderr, "Cannot open %s: %s (%i)\n", tpm_device,
> +                strerror(errno), errno);
> +        return;
> +    }
> +
> +    d = qemu_mallocz(sizeof(LPCtpmState));
> +    d->mem = cpu_register_io_memory(tis_readfn, tis_writefn, d);
> +
> +    if (d->mem == -1) {
> +        fprintf(stderr, "Failed to register IO memory TPM TIS\n");
> +        return;
> +    }
> +
> +    cpu_register_physical_memory(TIS_ADDR_BASE,
> +                                 0x1000 * NUM_LOCALITIES, d->mem);
> +
> +    /* initialize tpmState */
> +    s = &d->tpm;
> +
> +    s->offset     = 0;
> +    s->transmit   = 0;
> +    s->aborting   = 0;
> +    s->active_loc = NO_LOCALITY;
> +
> +    while (c < NUM_LOCALITIES) {
> +        s->loc[c].access = (1 << 7);
> +        s->loc[c].sts = 0;
> +        s->loc[c].inte = (1 << 3);
> +        s->loc[c].ints = 0;
> +        s->loc[c].state = STATE_IDLE;
> +        c++;
> +    }
> +    s->set_irq    = irq->handler;
> +    s->irq_opaque = irq->opaque;
> +    s->irq        = irq->n;
> +    s->aborting_locty = NO_LOCALITY;
> +    s->tpmfd      = tpmfd;
> +    s->path_len   = strlen(tpm_device);
> +    s->path       = tpm_device;
> +
> +    memset(s->buf,0,sizeof(s->buf));
> +
> +    qemu_mutex_init(&s->tpm_thread_lock);
> +    qemu_cond_init( &s->tpm_thread_cond_data_avail);
> +    qemu_mutex_init(&s->tpm_thread_abort_lock);
> +
> +    qemu_thread_create(&s->tpm_thread_id, &tpm_thread, s);
> +
> +    register_savevm(NULL, "tpm-tis", 0, 1, tpm_save, tpm_load, s);
> +}
> +
> +
> +int tpm_init(QemuOpts *opts) {
> +    if(tpm_device == NULL) {
> +        tpm_device = qemu_opt_get(opts, "dev");
> +    }
> +    return 0;
> +}
> +
> +/****************************************************************************/
> +/*                                                                          */
> +/* optional verbose logging of data to/from tpm chip                        */
> +/*                                                                          */
> +/****************************************************************************/
> +#ifdef TPM_DEBUG
> +static void showBuff(unsigned char *buff, const char *string)
> +{
> +    uint32_t i, len;
> +
> +    len = tpm_get_size_from_buffer(buff);
> +    printf("%s length=%d\n", string, len);
> +    for (i = 0; i < len; i++) {
> +        if (i && !(i % 16)) {
> +            printf("\n");
> +        }
> +        printf("%.2X ", buff[i]);
> +    }
> +    printf("\n");
> +}
> +#endif
> +
> +
> --
> 1.7.2.2
>
>
>
malc Aug. 25, 2010, 8:46 p.m. UTC | #3
On Wed, 25 Aug 2010, Blue Swirl wrote:

> On Wed, Aug 25, 2010 at 4:33 PM, Andreas Niederl
> <andreas.niederl@iaik.tugraz.at> wrote:
> > This implementation is based on the TPM 1.2 interface for virtualized TPM
> > devices from the Xen-4.0.0 ioemu-qemu-xen fork.
> >
> > A separate thread is used for I/O to the host TPM device because the Linux TPM
> > driver does not allow for non-blocking I/O.
> >
> > Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
> 
> In general, please read CODING_STYLE.
> 
> Ideally, the host and guest devices should be decoupled so that the
> guest visible device should still be functional without host TPM
> support.
> 
> > ---

[..snip..]

> > +static inline uint32_t tpm_get_size_from_buffer(const uint8_t *buffer)
> > +{
> > +    uint32_t len = (buffer[4] << 8) + buffer[5];
> > +    return len;
> > +}
> > +
> > +static inline uint8_t locality_from_addr(target_phys_addr_t addr)
> > +{
> > +    return (uint8_t)((addr >> 12) & 0x7);
> > +}
> > +
> > +
> > +static void die2(int err, const char *what)
> > +{
> > +    fprintf(stderr, "%s failed: %s\n", what, strerror(err));
> > +    abort();
> > +}
> > +
> > +static void die(const char *what)
> > +{
> > +    die2(errno, what);
> > +}
> 
> There's hw_error() and errx(), no need to reinvent the wheel.

Since it looks as if this code was based on mine, i take issues with
your analysis.

Firstly hw_error is for hardware errors and does not print message
corresponding to errno code and lastly there is no errx[1]

[..snip..]

[1] err(3):
    CONFORMING TO
       These functions are non-standard BSD extensions.
Blue Swirl Aug. 26, 2010, 5:22 p.m. UTC | #4
On Wed, Aug 25, 2010 at 8:46 PM, malc <av1474@comtv.ru> wrote:
> On Wed, 25 Aug 2010, Blue Swirl wrote:
>
>> On Wed, Aug 25, 2010 at 4:33 PM, Andreas Niederl
>> <andreas.niederl@iaik.tugraz.at> wrote:
>> > This implementation is based on the TPM 1.2 interface for virtualized TPM
>> > devices from the Xen-4.0.0 ioemu-qemu-xen fork.
>> >
>> > A separate thread is used for I/O to the host TPM device because the Linux TPM
>> > driver does not allow for non-blocking I/O.
>> >
>> > Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
>>
>> In general, please read CODING_STYLE.
>>
>> Ideally, the host and guest devices should be decoupled so that the
>> guest visible device should still be functional without host TPM
>> support.
>>
>> > ---
>
> [..snip..]
>
>> > +static inline uint32_t tpm_get_size_from_buffer(const uint8_t *buffer)
>> > +{
>> > +    uint32_t len = (buffer[4] << 8) + buffer[5];
>> > +    return len;
>> > +}
>> > +
>> > +static inline uint8_t locality_from_addr(target_phys_addr_t addr)
>> > +{
>> > +    return (uint8_t)((addr >> 12) & 0x7);
>> > +}
>> > +
>> > +
>> > +static void die2(int err, const char *what)
>> > +{
>> > +    fprintf(stderr, "%s failed: %s\n", what, strerror(err));
>> > +    abort();
>> > +}
>> > +
>> > +static void die(const char *what)
>> > +{
>> > +    die2(errno, what);
>> > +}
>>
>> There's hw_error() and errx(), no need to reinvent the wheel.
>
> Since it looks as if this code was based on mine, i take issues with
> your analysis.
>
> Firstly hw_error is for hardware errors and does not print message
> corresponding to errno code

Right, the correct function should be error_report().

> and lastly there is no errx[1]
>
> [..snip..]
>
> [1] err(3):
>    CONFORMING TO
>       These functions are non-standard BSD extensions.

err() and errx() are already used somewhere. But these should be
converted to error_report etc., like other printf based local
functions.
malc Aug. 26, 2010, 9:44 p.m. UTC | #5
On Thu, 26 Aug 2010, Blue Swirl wrote:

> On Wed, Aug 25, 2010 at 8:46 PM, malc <av1474@comtv.ru> wrote:
> > On Wed, 25 Aug 2010, Blue Swirl wrote:
> >
> >> On Wed, Aug 25, 2010 at 4:33 PM, Andreas Niederl
> >> <andreas.niederl@iaik.tugraz.at> wrote:
> >> > This implementation is based on the TPM 1.2 interface for virtualized TPM
> >> > devices from the Xen-4.0.0 ioemu-qemu-xen fork.
> >> >

[..snip..]

> >> There's hw_error() and errx(), no need to reinvent the wheel.
> >
> > Since it looks as if this code was based on mine, i take issues with
> > your analysis.
> >
> > Firstly hw_error is for hardware errors and does not print message
> > corresponding to errno code
> 
> Right, the correct function should be error_report().

Which is not at all the same as err/errx or die/die2 (doesn't abort/exit)

[FWIW die/die2 predates error_report, furthermore error_report kind of
breaks the convention that public functions/helpers have a qemu_ prefix]

> 
> > and lastly there is no errx[1]
> >
> > [..snip..]
> >
> > [1] err(3):
> >    CONFORMING TO
> >       These functions are non-standard BSD extensions.
> 
> err() and errx() are already used somewhere. But these should be
> converted to error_report etc., like other printf based local
> functions.
> 

Only in Linux specific nbd code.
Anthony Liguori Aug. 26, 2010, 9:57 p.m. UTC | #6
On 08/26/2010 04:44 PM, malc wrote:
>>      
>>> and lastly there is no errx[1]
>>>
>>> [..snip..]
>>>
>>> [1] err(3):
>>>     CONFORMING TO
>>>        These functions are non-standard BSD extensions.
>>>        
>> err() and errx() are already used somewhere. But these should be
>> converted to error_report etc., like other printf based local
>> functions.
>>
>>      
> Only in Linux specific nbd code.
>    

And the nbd stuff is derived from non-QEMU code that was brought into 
the tree.  It's generally a bad example of QEMU coding style.

Regards,

Anthony Liguori
diff mbox

Patch

diff --git a/Makefile.target b/Makefile.target
index c8281e9..2226c75 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -193,6 +193,9 @@  obj-y += e1000.o
 # Inter-VM PCI shared memory
 obj-$(CONFIG_KVM) += ivshmem.o
 
+# TPM passthrough device
+obj-$(CONFIG_TPM) += tpm_tis.o qemu-thread.o
+
 # Hardware support
 obj-i386-y += vga.o
 obj-i386-y += mc146818rtc.o i8259.o pc.o
diff --git a/configure b/configure
index a20371c..e1b55a8 100755
--- a/configure
+++ b/configure
@@ -315,6 +315,7 @@  pkgversion=""
 check_utests="no"
 user_pie="no"
 zero_malloc=""
+tpm="no"
 
 # OS specific
 if check_define __linux__ ; then
@@ -448,6 +449,7 @@  AIX)
   usb="linux"
   if [ "$cpu" = "i386" -o "$cpu" = "x86_64" ] ; then
     audio_possible_drivers="$audio_possible_drivers fmod"
+    tpm="yes"
   fi
 ;;
 esac
@@ -707,6 +709,8 @@  for opt do
   ;;
   --enable-vhost-net) vhost_net="yes"
   ;;
+  --disable-tpm) tpm="no"
+  ;;
   --*dir)
   ;;
   *) echo "ERROR: unknown option $opt"; show_help="yes"
@@ -895,6 +899,7 @@  echo "  --enable-docs            enable documentation build"
 echo "  --disable-docs           disable documentation build"
 echo "  --disable-vhost-net      disable vhost-net acceleration support"
 echo "  --enable-vhost-net       enable vhost-net acceleration support"
+echo "  --disable-tpm            disable tpm passthrough device emulation"
 echo ""
 echo "NOTE: The object files are built at the place where configure is launched"
 exit 1
@@ -2187,6 +2192,7 @@  echo "preadv support    $preadv"
 echo "fdatasync         $fdatasync"
 echo "uuid support      $uuid"
 echo "vhost-net support $vhost_net"
+echo "tpm support       $tpm"
 
 if test $sdl_too_old = "yes"; then
 echo "-> Your SDL version is too old - please upgrade to have SDL support"
@@ -2423,6 +2429,9 @@  fi
 if test "$fdatasync" = "yes" ; then
   echo "CONFIG_FDATASYNC=y" >> $config_host_mak
 fi
+if test "$tpm" = "yes" ; then
+  echo "CONFIG_TPM=y" >> $config_host_mak
+fi
 
 # XXX: suppress that
 if [ "$bsd" = "yes" ] ; then
diff --git a/hw/pc_piix.c b/hw/pc_piix.c
index 32a1057..ebc3478 100644
--- a/hw/pc_piix.c
+++ b/hw/pc_piix.c
@@ -38,6 +38,10 @@ 
 
 #define MAX_IDE_BUS 2
 
+#ifdef CONFIG_TPM
+void tpm_tis_init(qemu_irq irq);
+#endif
+
 static const int ide_iobase[MAX_IDE_BUS] = { 0x1f0, 0x170 };
 static const int ide_iobase2[MAX_IDE_BUS] = { 0x3f6, 0x376 };
 static const int ide_irq[MAX_IDE_BUS] = { 14, 15 };
@@ -147,6 +151,10 @@  static void pc_init1(ram_addr_t ram_size,
         }
     }
 
+#ifdef CONFIG_TPM
+    tpm_tis_init(isa_reserve_irq(11));
+#endif
+
     pc_audio_init(pci_enabled ? pci_bus : NULL, isa_irq);
 
     pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device,
diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c
new file mode 100644
index 0000000..e109e1a
--- /dev/null
+++ b/hw/tpm_tis.c
@@ -0,0 +1,831 @@ 
+/*
+ * tpm_tis.c - QEMU emulator for a 1.2 TPM with TIS interface
+ *
+ * Copyright (C) 2006 IBM Corporation
+ * Copyright (C) 2010 IAIK, Graz University of Technology
+ *
+ * Author: Stefan Berger <stefanb@us.ibm.com>
+ *         David Safford <safford@us.ibm.com>
+ *
+ * Author: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
+ * Pass through a TPM device rather than using the emulator
+ * Modified to use a separate thread for IO to/from TPM as the Linux TPM driver
+ * framework does not allow non-blocking IO
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ *
+ * Implementation of the TIS interface according to specs at
+ * https://www.trustedcomputinggroup.org/groups/pc_client/TCG_PCClientTPMSpecification_1-20_1-00_FINAL.pdf
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+
+#include "qemu-option.h"
+#include "qemu-config.h"
+#include "hw/hw.h"
+#include "hw/pc.h"
+#include "hw/pci.h"
+#include "hw/pci_ids.h"
+#include "qemu-thread.h"
+
+//#define TPM_DEBUG
+
+#define TPM_MAX_PKT            4096
+
+#define TIS_ADDR_BASE                 0xFED40000
+
+/* tis registers */
+#define TPM_REG_ACCESS                0x00
+#define TPM_REG_INT_ENABLE            0x08
+#define TPM_REG_INT_VECTOR            0x0c
+#define TPM_REG_INT_STATUS            0x10
+#define TPM_REG_INTF_CAPABILITY       0x14
+#define TPM_REG_STS                   0x18
+#define TPM_REG_DATA_FIFO             0x24
+#define TPM_REG_DID_VID               0xf00
+#define TPM_REG_RID                   0xf04
+
+#define STS_VALID                    (1 << 7)
+#define STS_COMMAND_READY            (1 << 6)
+#define STS_TPM_GO                   (1 << 5)
+#define STS_DATA_AVAILABLE           (1 << 4)
+#define STS_EXPECT                   (1 << 3)
+#define STS_RESPONSE_RETRY           (1 << 1)
+
+#define ACCESS_TPM_REG_VALID_STS     (1 << 7)
+#define ACCESS_ACTIVE_LOCALITY       (1 << 5)
+#define ACCESS_BEEN_SEIZED           (1 << 4)
+#define ACCESS_SEIZE                 (1 << 3)
+#define ACCESS_PENDING_REQUEST       (1 << 2)
+#define ACCESS_REQUEST_USE           (1 << 1)
+#define ACCESS_TPM_ESTABLISHMENT     (1 << 0)
+
+#define INT_ENABLED                  (1 << 31)
+#define INT_DATA_AVAILABLE           (1 << 0)
+#define INT_LOCALITY_CHANGED         (1 << 2)
+#define INT_COMMAND_READY            (1 << 7)
+
+#define INTERRUPTS_SUPPORTED         (INT_LOCALITY_CHANGED | \
+                                      INT_DATA_AVAILABLE   | \
+                                      INT_COMMAND_READY)
+#define CAPABILITIES_SUPPORTED       ((1 << 4) |            \
+                                      INTERRUPTS_SUPPORTED)
+
+enum {
+  STATE_IDLE = 0,
+  STATE_READY,
+  STATE_COMPLETION,
+  STATE_EXECUTION,
+  STATE_RECEPTION
+};
+
+#define NUM_LOCALITIES   5
+#define NO_LOCALITY      0xff
+
+#define IS_VALID_LOC(x) ((x) < NUM_LOCALITIES)
+
+#define TPM_DID          0x0001
+#define TPM_VID          0x0001
+#define TPM_RID          0x0001
+
+
+const char *tpm_device = NULL;
+
+/* locality data */
+typedef struct TPMLocal {
+    uint32_t state;
+    uint8_t access;
+    uint8_t sts;
+    uint32_t inte;
+    uint32_t ints;
+} tpmLoc;
+
+/* overall state of the TPM interface; 's' marks as save upon suspension */
+typedef struct TPMState {
+    uint8_t    transmit;   /* boolean value */
+                           /* set when tpm transmit in progress */
+    uint8_t    aborting;   /* boolean value */
+    uint32_t   offset;           /* s */
+    uint8_t    buf[TPM_MAX_PKT]; /* s */
+    int        tpmfd;            /* s */
+
+    /* path to host tpm device */
+    const char *path;            /* s */
+    uint32_t    path_len;        /* s */
+
+    QemuMutex  tpm_thread_lock;
+    QemuMutex  tpm_thread_abort_lock;
+    QemuCond   tpm_thread_cond_data_avail;
+    QemuThread tpm_thread_id;
+
+    uint8_t active_loc;         /* s */
+    uint8_t aborting_locty;
+    uint8_t next_locty;
+    uint8_t irq_pending;        /* s */
+    tpmLoc loc[NUM_LOCALITIES]; /* s */
+    qemu_irq_handler set_irq;
+    void *irq_opaque;
+    int irq;
+} tpmState;
+
+
+/* local prototypes */
+static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask);
+static void tis_abort(tpmState *s);
+
+#ifdef TPM_DEBUG
+static void showBuff(unsigned char *buff, const char *string);
+#endif
+
+
+/**********************************************************************
+ helper functions
+ *********************************************************************/
+
+static inline uint32_t tpm_get_size_from_buffer(const uint8_t *buffer)
+{
+    uint32_t len = (buffer[4] << 8) + buffer[5];
+    return len;
+}
+
+static inline uint8_t locality_from_addr(target_phys_addr_t addr)
+{
+    return (uint8_t)((addr >> 12) & 0x7);
+}
+
+
+static void die2(int err, const char *what)
+{
+    fprintf(stderr, "%s failed: %s\n", what, strerror(err));
+    abort();
+}
+
+static void die(const char *what)
+{
+    die2(errno, what);
+}
+
+
+/* borrowed from qemu-char.c */
+static int unix_write(int fd, const uint8_t *buf, uint32_t len1)
+{
+    int ret, len;
+
+    len = len1;
+    while (len > 0) {
+        ret = write(fd, buf, len);
+        if (ret < 0) {
+            if (errno != EINTR && errno != EAGAIN)
+                return -1;
+        } else if (ret == 0) {
+            break;
+        } else {
+            buf += ret;
+            len -= ret;
+        }
+    }
+    return len1 - len;
+}
+
+static int unix_tpm_read(int fd, uint8_t *buf)
+{
+    int ret, len, len1;
+    uint8_t *buf1;
+
+    buf1 = buf;
+    len  = sizeof(buf) + TPM_MAX_PKT;
+    while ((buf1 - buf) < 6) {
+        ret = read(fd, buf1, len);
+        if (ret < 0) {
+            if (errno != EINTR && errno != EAGAIN)
+                return -1;
+        } else {
+            buf1 += ret;
+        }
+    }
+
+    len1 = tpm_get_size_from_buffer(buf);
+    if (len1 > TPM_MAX_PKT) {
+        fprintf(stderr, "Error: received invalid tpm response size: %d\n",
+                len1);
+        return -1;
+    }
+
+    /* response size minus already read data */
+    len  = len1 - (buf1 - buf);
+    while (len > 0) {
+        ret = read(fd, buf, len);
+        if (ret < 0) {
+            if (errno != EINTR && errno != EAGAIN)
+                return -1;
+        } else if (ret == 0) {
+            break;
+        } else {
+            buf += ret;
+            len -= ret;
+        }
+    }
+    return len1 - len;
+}
+
+/****************************************************************************/
+/* Transmit request to TPM and read Response                                */
+/****************************************************************************/
+
+static void *tpm_thread(void *opaque)
+{
+    tpmState *s = opaque;
+    sigset_t  set;
+    uint32_t  size = 0;
+    /* hardcode locality 0 */
+    uint8_t   locty = 0;
+#ifdef TPM_DEBUG
+    uint32_t  ret;
+#endif
+
+    /* block all signals */
+    if (sigfillset(&set)) die("sigfillset");
+    if (sigprocmask(SIG_BLOCK, &set, NULL)) die("sigprocmask");
+
+    qemu_mutex_lock(&s->tpm_thread_lock);
+    while(1) {
+        if(!s->transmit)
+            qemu_cond_wait(&s->tpm_thread_cond_data_avail, &s->tpm_thread_lock);
+        qemu_mutex_unlock(&s->tpm_thread_lock);
+
+#ifdef TPM_DEBUG
+        showBuff(s->buf, "To TPM");
+#endif
+        size = tpm_get_size_from_buffer(s->buf);
+
+        if(unix_write(s->tpmfd, s->buf, size) < 0) {
+            fprintf(stderr, "Error: while transmitting data to host tpm"
+                    ": %s (%i)\n",
+                    strerror(errno), errno);
+            tis_abort(s);
+            qemu_mutex_lock(&s->tpm_thread_lock);
+            continue;
+        }
+
+        if(unix_tpm_read(s->tpmfd, s->buf) < 0) {
+            fprintf(stderr, "Error: while reading data from host tpm"
+                    ": %s (%i)\n",
+                    strerror(errno), errno);
+            tis_abort(s);
+            qemu_mutex_lock(&s->tpm_thread_lock);
+            continue;
+        }
+
+        qemu_mutex_lock(&s->tpm_thread_abort_lock);
+        if(!s->aborting) {
+#ifdef TPM_DEBUG
+            showBuff(s->buf, "From TPM");
+
+            ret = (s->buf[8])*256 + s->buf[9];
+            if (ret)
+                printf("tpm command failed with error %d\n", ret);
+            else
+                printf("tpm command succeeded\n");
+#endif
+
+            qemu_mutex_lock(&s->tpm_thread_lock);
+            s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE;
+            s->loc[locty].state = STATE_COMPLETION;
+            s->transmit = 0;
+            tis_raise_irq(s, locty, INT_DATA_AVAILABLE);
+        } else {
+            qemu_mutex_lock(&s->tpm_thread_lock);
+            tis_abort(s);
+#ifdef TPM_DEBUG
+            printf("Abort is complete.\n");
+#endif
+            s->transmit  = 0;
+        }
+        qemu_mutex_unlock(&s->tpm_thread_abort_lock);
+    }
+
+    return NULL;
+}
+
+
+/**********************************************************************/
+
+/*
+ * read a byte of response data
+ */
+static uint32_t tpm_data_read(tpmState *s, uint8_t locty)
+{
+    uint32_t ret, len;
+
+    if (s->loc[locty].state != STATE_COMPLETION) {
+#ifdef TPM_DEBUG
+        printf("tpm_data_read with no data available!\n");
+#endif
+        return 0xff;
+    }
+
+    len = tpm_get_size_from_buffer(s->buf);
+    ret = s->buf[s->offset++];
+    if (s->offset >= len) {
+        s->loc[locty].sts = STS_VALID ;
+        s->offset = 0;
+    }
+#ifdef TPM_DEBUG
+    printf("tpm_data_read byte x%02x   [%d]\n",ret,s->offset-1);
+#endif
+    return ret;
+}
+
+/* raise an interrupt if allowed */
+static void tis_raise_irq(tpmState *s, uint8_t locty, uint32_t irqmask)
+{
+    if (!s->irq_pending &&
+        (s->loc[locty].inte & INT_ENABLED) &&
+        (s->loc[locty].inte & irqmask)) {
+        if ((irqmask & s->loc[locty].ints) == 0) {
+#ifdef TPM_DEBUG
+            printf("Raising IRQ for flag %08x\n",irqmask);
+#endif
+            s->set_irq(s->irq_opaque, s->irq, 1);
+            s->irq_pending = 1;
+            s->loc[locty].ints |= irqmask;
+        }
+    }
+}
+
+/* abort execution of command */
+static void tis_abort(tpmState *s)
+{
+    s->offset = 0;
+    s->active_loc = s->next_locty;
+    qemu_mutex_lock(&s->tpm_thread_lock);
+    s->transmit  = 0;
+    qemu_mutex_unlock(&s->tpm_thread_lock);
+
+    /*
+     * Need to react differently depending on who's aborting now and
+     * which locality will become active afterwards.
+     */
+    if (s->aborting_locty == s->next_locty) {
+        s->loc[s->aborting_locty].state = STATE_READY;
+        s->loc[s->aborting_locty].sts   = STS_COMMAND_READY;
+        tis_raise_irq(s, s->aborting_locty, INT_COMMAND_READY);
+    }
+
+    /* locality after abort is another one than the current one */
+    if (s->aborting_locty != s->next_locty && s->next_locty != NO_LOCALITY) {
+        s->loc[s->aborting_locty].access &= ~ACCESS_ACTIVE_LOCALITY;
+        s->loc[s->next_locty].access     |=  ACCESS_ACTIVE_LOCALITY;
+        tis_raise_irq(s, s->next_locty, INT_LOCALITY_CHANGED);
+    }
+
+    s->aborting_locty = NO_LOCALITY; /* nobody's aborting a command anymore */
+}
+
+/* abort current command */
+static void tis_prep_abort(tpmState *s, uint8_t locty, uint8_t newlocty)
+{
+    s->aborting_locty = locty; /* current locality */
+    s->next_locty = newlocty;  /* locality after successful abort */
+
+    s->next_locty = 0;  /* only locality 0 available */
+
+    /*
+     * only abort a command using an interrupt if currently executing
+     * a command AND if there's a valid connection to the vTPM.
+     */
+    if (s->loc[locty].state == STATE_EXECUTION) {
+        qemu_mutex_lock(&s->tpm_thread_abort_lock);
+        s->aborting = 1;
+        qemu_mutex_unlock(&s->tpm_thread_abort_lock);
+    } else {
+        tis_abort(s);
+    }
+}
+
+
+/*
+ * Read a register of the TIS interface
+ * See specs pages 33-63 for description of the registers
+ */
+static uint32_t tis_mem_readl(void *opaque, target_phys_addr_t addr)
+{
+    tpmState *s = (tpmState *)opaque;
+    uint16_t offset = addr & 0xffc;
+    uint8_t shift = (addr & 0x3) * 8;
+    uint32_t val = 0;
+    uint8_t locty = locality_from_addr(addr);
+
+    if (offset == TPM_REG_ACCESS) {
+        if (locty > 0) {
+            /* no access to localities other than 0 possible */
+            val = 0;
+        } else {
+            if (s->active_loc == locty) {
+                s->loc[locty].access |= (1 << 5);
+            } else {
+                s->loc[locty].access &= ~(1 << 5);
+            }
+            val = s->loc[locty].access;
+        }
+    } else
+    if (locty > 0) {
+        /* higher localities deactivated */
+        val = 0xffff;
+    } else
+    if (offset == TPM_REG_INT_ENABLE) {
+        val = s->loc[locty].inte;
+    } else
+    if (offset == TPM_REG_INT_VECTOR) {
+        val = s->irq;
+    } else
+    if (offset == TPM_REG_INT_STATUS) {
+        val = s->loc[locty].ints;
+    } else
+    if (offset == TPM_REG_INTF_CAPABILITY) {
+        val = CAPABILITIES_SUPPORTED;
+    } else
+    if (offset == TPM_REG_STS) { /* status register */
+        /* ??? - appears to be just some value as we aren't a hardware TPM */
+        val = (sizeof(s->buf) - s->offset) << 8 | s->loc[locty].sts;
+    } else
+    if (offset == TPM_REG_DATA_FIFO) {
+      val = tpm_data_read(s, locty);
+    } else
+    if (offset == TPM_REG_DID_VID) {
+        val = (TPM_DID << 16) | TPM_VID;
+    } else
+    if (offset == TPM_REG_RID) {
+         val = TPM_RID;
+    }
+
+    if (shift && locty == 0) {
+        val >>= shift;
+    }
+
+#ifdef TPM_DEBUG
+    printf(" read(%08x) = %08x\n",
+           (int)addr,
+           val);
+#endif
+
+    return val;
+}
+
+/*
+ * Write a value to a register of the TIS interface
+ * See specs pages 33-63 for description of the registers
+ */
+static void tis_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
+{
+    tpmState* s=(tpmState*)opaque;
+    uint16_t off = addr & 0xfff;
+    uint8_t locty = locality_from_addr(addr);
+    uint32_t len;
+
+#ifdef TPM_DEBUG
+    printf("write(%08x) = %08x\n",
+           (int)addr,
+           val);
+#endif
+
+    /* linux tpm_tis driver only uses locality 0 */
+    if (locty > 0) {
+        fprintf(stderr, "Warning: Write to unavailable locality: %d \n", locty);
+        return;
+    }
+#ifdef TPM_DEBUG
+    if(s->transmit) {
+        fprintf(stderr, "Warning: tpm received data while transmit"
+                " in progress.\n");
+        printf("WARN: write(%08x) = %08x\n",
+                (int)addr,
+                val);
+    }
+#endif
+
+    if (off == TPM_REG_ACCESS) {
+        if (val & ACCESS_ACTIVE_LOCALITY) {
+            /* give up locality if currently owned */
+            if (s->active_loc == locty) {
+                /*uint8_t newlocty = NO_LOCALITY;*/
+                s->loc[locty].access &= ~(ACCESS_PENDING_REQUEST);
+                /* anybody wants the locality ? */
+                if (s->loc[locty].access & ACCESS_REQUEST_USE) {
+                    s->loc[locty].access |= ACCESS_TPM_REG_VALID_STS;
+                    s->loc[locty].access &= ~ACCESS_REQUEST_USE;
+                }
+                tis_prep_abort(s, locty, locty);
+            }
+        }
+        if (val & ACCESS_BEEN_SEIZED) {
+            /* clear the flag */
+            s->loc[locty].access &= ~ACCESS_BEEN_SEIZED;
+        }
+        if (val & ACCESS_SEIZE) {
+            if (locty > s->active_loc && IS_VALID_LOC(s->active_loc)) {
+                s->loc[s->active_loc].access |= ACCESS_BEEN_SEIZED;
+                s->loc[locty].access = ACCESS_TPM_REG_VALID_STS;
+                tis_prep_abort(s, s->active_loc, locty);
+            }
+        }
+        if (val & ACCESS_REQUEST_USE) {
+            if (IS_VALID_LOC(s->active_loc)) {
+                /* locality election */
+                s->loc[s->active_loc].access |= ACCESS_PENDING_REQUEST;
+            } else {
+                /* no locality active -> make this one active now */
+                s->loc[locty].access |= ACCESS_ACTIVE_LOCALITY;
+                s->active_loc = locty;
+                tis_raise_irq(s, locty, INT_LOCALITY_CHANGED);
+            }
+        }
+    } else
+    if (off == TPM_REG_INT_ENABLE) {
+        s->loc[locty].inte = (val & (INT_ENABLED | (0x3 << 3) |
+                                     INTERRUPTS_SUPPORTED));
+    } else
+    if (off == TPM_REG_INT_STATUS) {
+        /* clearing of interrupt flags */
+        if ((val & INTERRUPTS_SUPPORTED) &&
+            (s->loc[locty].ints & INTERRUPTS_SUPPORTED)) {
+            s->set_irq(s->irq_opaque, s->irq, 0);
+            s->irq_pending = 0;
+        }
+        s->loc[locty].ints &= ~(val & INTERRUPTS_SUPPORTED);
+    } else
+    if (off == TPM_REG_STS) {
+        if (val & STS_COMMAND_READY) {
+            if (s->loc[locty].state == STATE_IDLE) {
+                s->loc[locty].sts   = STS_COMMAND_READY;
+                s->loc[locty].state = STATE_READY;
+                tis_raise_irq(s, locty, INT_COMMAND_READY);
+            } else if (s->loc[locty].state == STATE_COMPLETION ||
+                       s->loc[locty].state == STATE_EXECUTION  ||
+                       s->loc[locty].state == STATE_RECEPTION) {
+                /* abort currently running command */
+                tis_prep_abort(s, locty, locty);
+            }
+        }
+        if (val & STS_TPM_GO) {
+            s->offset   = 0;
+
+            if (s->transmit != 1) {
+                qemu_mutex_lock(&s->tpm_thread_lock);
+                s->transmit = 1;
+                s->loc[locty].state = STATE_EXECUTION;
+                qemu_mutex_unlock(&s->tpm_thread_lock);
+                qemu_cond_signal(&s->tpm_thread_cond_data_avail);
+            }
+        }
+        if (val & STS_RESPONSE_RETRY) {
+            s->offset = 0;
+        }
+    } else if (off == TPM_REG_DATA_FIFO) {
+        /* data fifo */
+        if (s->loc[locty].state == STATE_IDLE ||
+            s->loc[locty].state == STATE_EXECUTION ||
+            s->loc[locty].state == STATE_COMPLETION) {
+            /* drop the byte */
+        } else {
+#ifdef TPM_DEBUG
+        printf("Byte to send to TPM: %02x at offset: %03d\n", val, s->offset);
+#endif
+            s->loc[locty].state = STATE_RECEPTION;
+            s->loc[locty].sts   = STS_EXPECT | STS_VALID;
+
+            if (s->offset < TPM_MAX_PKT)
+                s->buf[s->offset++] = (uint8_t)val;
+
+            if (s->offset > 5) {
+                /* we have a packet length - see if we have all of it */
+                len = tpm_get_size_from_buffer(s->buf);
+                if (s->offset >= len) {
+#ifdef TPM_DEBUG
+                    printf("We have a complete packet of %u bytes\n", len);
+#endif
+                    s->offset   = 0;
+
+                    qemu_mutex_lock(&s->tpm_thread_lock);
+                    s->transmit = 1;
+                    qemu_mutex_unlock(&s->tpm_thread_lock);
+                    qemu_cond_signal(&s->tpm_thread_cond_data_avail);
+                    s->loc[locty].sts = STS_VALID;
+                }
+            }
+        }
+    }
+}
+
+
+static CPUReadMemoryFunc *tis_readfn[3]={
+    tis_mem_readl,
+    tis_mem_readl,
+    tis_mem_readl
+};
+
+static CPUWriteMemoryFunc *tis_writefn[3]={
+    tis_mem_writel,
+    tis_mem_writel,
+    tis_mem_writel
+};
+
+/*
+ * Save the internal state of this interface for later resumption.
+ * Need to get any outstanding responses from the vTPM back, so
+ * this might delay the suspend for a while.
+ */
+static void tpm_save(QEMUFile* f,void* opaque)
+{
+    tpmState* s=(tpmState*)opaque;
+    uint8_t locty = s->active_loc;
+    int c;
+
+    /* need to wait for outstanding requests to complete */
+    if (s->loc[locty].state == STATE_EXECUTION) {
+        int repeats = 30; /* 30 seconds; really should be infty */
+        while (repeats > 0 &&
+               !(s->loc[s->active_loc].sts & STS_DATA_AVAILABLE)) {
+            sleep(1);
+        }
+    }
+
+    close(s->tpmfd);
+
+    qemu_put_be32s(f,&s->offset);
+    qemu_put_buffer(f, s->buf, TPM_MAX_PKT);
+    qemu_put_8s(f, &s->active_loc);
+    qemu_put_8s(f, &s->irq_pending);
+    for (c = 0; c < NUM_LOCALITIES; c++) {
+        qemu_put_be32s(f, &s->loc[c].state);
+        qemu_put_8s(f, &s->loc[c].access);
+        qemu_put_8s(f, &s->loc[c].sts);
+        qemu_put_be32s(f, &s->loc[c].inte);
+        qemu_put_be32s(f, &s->loc[c].ints);
+    }
+    qemu_put_buffer(f,  (uint8_t*) s->path, s->path_len);
+    qemu_put_be32s(f,  &s->path_len);
+}
+
+
+/*
+ * load TIS interface state
+ */
+static int tpm_load(QEMUFile* f,void* opaque,int version_id)
+{
+    tpmState* s=(tpmState*)opaque;
+    int c;
+
+    if (version_id != 1)
+        return -EINVAL;
+
+    qemu_get_be32s(f,&s->offset);
+    qemu_get_buffer(f, s->buf, TPM_MAX_PKT);
+    qemu_get_8s(f, &s->active_loc);
+    qemu_get_8s(f, &s->irq_pending);
+    for (c = 0; c < NUM_LOCALITIES; c++) {
+        qemu_get_be32s(f, &s->loc[c].state);
+        qemu_get_8s(f, &s->loc[c].access);
+        qemu_get_8s(f, &s->loc[c].sts);
+        qemu_get_be32s(f, &s->loc[c].inte);
+        qemu_get_be32s(f, &s->loc[c].ints);
+    }
+    qemu_get_be32s(f,  &s->path_len);
+    qemu_get_buffer(f,  (uint8_t*) s->path, s->path_len);
+
+    s->tpmfd = open(s->path, O_RDWR);
+
+    if(s->tpmfd < 0)
+        hw_error("Cannot open %s: %s (%i)\n", s->path, strerror(errno), errno);
+
+    qemu_mutex_init(&s->tpm_thread_lock);
+    qemu_cond_init( &s->tpm_thread_cond_data_avail);
+    qemu_mutex_init(&s->tpm_thread_abort_lock);
+
+    qemu_thread_create(&s->tpm_thread_id, &tpm_thread, s);
+
+    return 0;
+}
+
+
+typedef struct LPCtpmState {
+    tpmState tpm;
+    int mem;
+} LPCtpmState;
+
+
+/*
+ * initialize TIS interface
+ */
+
+struct IRQState {
+    qemu_irq_handler handler;
+    void *opaque;
+    int n;
+};
+
+void tpm_tis_init(qemu_irq irq);
+
+void tpm_tis_init(qemu_irq irq)
+{
+    LPCtpmState *d;
+    tpmState *s;
+    int c = 0;
+    int tpmfd = -1;
+
+    if(tpm_device == NULL) {
+        return;
+    }
+
+    tpmfd = open(tpm_device, O_RDWR);
+    if(tpmfd < 0) {
+        fprintf(stderr, "Cannot open %s: %s (%i)\n", tpm_device,
+                strerror(errno), errno);
+        return;
+    }
+
+    d = qemu_mallocz(sizeof(LPCtpmState));
+    d->mem = cpu_register_io_memory(tis_readfn, tis_writefn, d);
+
+    if (d->mem == -1) {
+        fprintf(stderr, "Failed to register IO memory TPM TIS\n");
+        return;
+    }
+
+    cpu_register_physical_memory(TIS_ADDR_BASE,
+                                 0x1000 * NUM_LOCALITIES, d->mem);
+
+    /* initialize tpmState */
+    s = &d->tpm;
+
+    s->offset     = 0;
+    s->transmit   = 0;
+    s->aborting   = 0;
+    s->active_loc = NO_LOCALITY;
+
+    while (c < NUM_LOCALITIES) {
+        s->loc[c].access = (1 << 7);
+        s->loc[c].sts = 0;
+        s->loc[c].inte = (1 << 3);
+        s->loc[c].ints = 0;
+        s->loc[c].state = STATE_IDLE;
+        c++;
+    }
+    s->set_irq    = irq->handler;
+    s->irq_opaque = irq->opaque;
+    s->irq        = irq->n;
+    s->aborting_locty = NO_LOCALITY;
+    s->tpmfd      = tpmfd;
+    s->path_len   = strlen(tpm_device);
+    s->path       = tpm_device;
+
+    memset(s->buf,0,sizeof(s->buf));
+
+    qemu_mutex_init(&s->tpm_thread_lock);
+    qemu_cond_init( &s->tpm_thread_cond_data_avail);
+    qemu_mutex_init(&s->tpm_thread_abort_lock);
+
+    qemu_thread_create(&s->tpm_thread_id, &tpm_thread, s);
+
+    register_savevm(NULL, "tpm-tis", 0, 1, tpm_save, tpm_load, s);
+}
+
+
+int tpm_init(QemuOpts *opts) {
+    if(tpm_device == NULL) {
+        tpm_device = qemu_opt_get(opts, "dev");
+    }
+    return 0;
+}
+
+/****************************************************************************/
+/*                                                                          */
+/* optional verbose logging of data to/from tpm chip                        */
+/*                                                                          */
+/****************************************************************************/
+#ifdef TPM_DEBUG
+static void showBuff(unsigned char *buff, const char *string)
+{
+    uint32_t i, len;
+
+    len = tpm_get_size_from_buffer(buff);
+    printf("%s length=%d\n", string, len);
+    for (i = 0; i < len; i++) {
+        if (i && !(i % 16)) {
+            printf("\n");
+        }
+        printf("%.2X ", buff[i]);
+    }
+    printf("\n");
+}
+#endif
+
+