diff mbox

[1/7] Add TPM 1.2 device interface

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

Commit Message

Andreas Niederl Dec. 13, 2010, 6:04 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 backend driver infrastructure is provided to be able to use different
device backends.

Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
---
 hw/tpm.h         |    6 +
 hw/tpm_backend.c |   63 +++++
 hw/tpm_int.h     |   36 +++
 hw/tpm_tis.c     |  711 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 816 insertions(+), 0 deletions(-)
 create mode 100644 hw/tpm.h
 create mode 100644 hw/tpm_backend.c
 create mode 100644 hw/tpm_int.h
 create mode 100644 hw/tpm_tis.c
diff mbox

Patch

diff --git a/hw/tpm.h b/hw/tpm.h
new file mode 100644
index 0000000..844c95e
--- /dev/null
+++ b/hw/tpm.h
@@ -0,0 +1,6 @@ 
+#ifndef TPM_H
+#define TPM_H
+
+int qemu_tpm_add(QemuOpts *opts);
+
+#endif /* TPM_H */
diff --git a/hw/tpm_backend.c b/hw/tpm_backend.c
new file mode 100644
index 0000000..a0bec7c
--- /dev/null
+++ b/hw/tpm_backend.c
@@ -0,0 +1,63 @@ 
+
+#include "qemu-option.h"
+
+#include "hw/tpm.h"
+#include "hw/tpm_int.h"
+
+
+static QLIST_HEAD(, TPMDriver) tpm_drivers =
+    QLIST_HEAD_INITIALIZER(tpm_drivers);
+
+TPMDriver *tpm_get_driver(const char *id)
+{
+    TPMDriver *drv;
+    QLIST_FOREACH(drv, &tpm_drivers, list) {
+        if (!strcmp(drv->id, id)) {
+            return drv;
+        }
+    }
+    return NULL;
+}
+
+
+typedef struct {
+    const char *name;
+    TPMDriver *(*open)(QemuOpts *opts);
+} TPMDriverTable;
+
+static const TPMDriverTable driver_table[] = {
+};
+
+int qemu_tpm_add(QemuOpts *opts) {
+    TPMDriver *drv = NULL;
+    int i;
+
+    if (qemu_opts_id(opts) == NULL) {
+        fprintf(stderr, "tpm: no id specified\n");
+        return -1;
+    }
+
+    for (i = 0; i < ARRAY_SIZE(driver_table); i++) {
+        if (strcmp(driver_table[i].name, qemu_opt_get(opts, "type")) == 0) {
+            break;
+        }
+    }
+
+    if (i == ARRAY_SIZE(driver_table)) {
+        fprintf(stderr, "tpm: backend type %s not found\n",
+                    qemu_opt_get(opts, "type"));
+        return -1;
+    }
+
+    drv = driver_table[i].open(opts);
+
+    if (drv == NULL) {
+        return -1;
+    }
+
+    drv->id = qemu_strdup(qemu_opts_id(opts));
+
+    QLIST_INSERT_HEAD(&tpm_drivers, drv, list);
+
+    return 0;
+}
diff --git a/hw/tpm_int.h b/hw/tpm_int.h
new file mode 100644
index 0000000..d52d7e2
--- /dev/null
+++ b/hw/tpm_int.h
@@ -0,0 +1,36 @@ 
+#ifndef TPM_INT_H
+#define TPM_INT_H
+
+
+#include <inttypes.h>
+#include "qemu-queue.h"
+#include "qemu-option.h"
+
+
+typedef struct TPMDriver TPMDriver;
+struct TPMDriver {
+    char *id;
+
+    uint8_t  locty;
+    uint8_t *buf;
+
+    int (*send)(TPMDriver *drv, uint8_t locty, uint32_t len);
+    int (*recv)(TPMDriver *drv, uint8_t locty, uint32_t len);
+
+    QLIST_ENTRY(TPMDriver) list;
+};
+
+TPMDriver *tpm_get_driver(const char *id);
+
+#define DEBUG_TPM
+#ifdef DEBUG_TPM
+void show_buff(unsigned char *buff, const char *string);
+#define DPRINTF(fmt, ...) \
+    fprintf(stderr, "tpm_tis: %s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+#define DSHOW_BUFF(buf, info) show_buff(buf, info)
+#else
+#define DPRINTF(fmt, ...)
+#define DSHOW_BUFF(buf, info)
+#endif
+
+#endif /* TPM_INT_H */
diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c
new file mode 100644
index 0000000..0cee917
--- /dev/null
+++ b/hw/tpm_tis.c
@@ -0,0 +1,711 @@ 
+/*
+ * 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/
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.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-timer.h"
+
+#include "hw/tpm_int.h"
+
+
+#define TPM_MAX_PKT            4096
+#define TPM_MAX_PATH           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
+
+/* locality data */
+typedef struct {
+    uint32_t state;
+    uint32_t inte;
+    uint32_t ints;
+    uint8_t  access;
+    uint8_t  sts;
+} TPMLocality;
+
+/* TPM device state */
+typedef struct {
+    ISADevice  dev;
+    qemu_irq   pic;
+    uint32_t   irq;
+    uint8_t    irq_pending;
+
+    QEMUTimer *poll_timer;
+    int        poll_attempts;
+
+    uint8_t    aborting;   /* boolean value */
+    uint32_t   offset;
+    uint8_t    buf[TPM_MAX_PKT];
+
+    uint8_t    active_locty;
+    uint8_t  aborting_locty;
+    uint8_t      next_locty;
+
+    TPMLocality loc[NUM_LOCALITIES];
+
+    char *backend;
+
+    TPMDriver *drv;
+} TPMState;
+
+
+/**********************************************************************
+ 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);
+}
+
+
+#ifdef DEBUG_TPM
+/****************************************************************************/
+/*                                                                          */
+/* optional verbose logging of data to/from tpm chip                        */
+/*                                                                          */
+/****************************************************************************/
+void show_buff(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
+
+
+/* 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) {
+            DPRINTF("Raising IRQ for flag %08x\n",irqmask);
+            qemu_set_irq(s->pic, 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_locty = s->next_locty;
+
+    /*
+     * 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 */
+}
+
+/**********************************************************************/
+
+/*
+ * Prepare the next interrupt for example after a command has
+ * been sent out for the purpose of receiving the response.
+ * Depending on how many interrupts (used for polling on the fd) have
+ * already been schedule, this function determines the delta in time
+ * to the next interrupt. This accomodates for commands that finish
+ * quickly.
+ */
+static void tis_prep_next_interrupt(TPMState *s)
+{
+    int64_t expiration;
+    int rate = 5; /* 5 times per second */
+
+    /*
+     * poll often at the beginning for quickly finished commands,
+     * then back off
+     */
+    if (s->poll_attempts < 5) {
+        rate = 20;
+    } else if (s->poll_attempts < 10) {
+        rate = 10;
+    }
+
+    expiration = qemu_get_clock(vm_clock) + (get_ticks_per_sec() / rate);
+    qemu_mod_timer(s->poll_timer, expiration);
+    s->poll_attempts++;
+}
+
+
+/*
+ * The polling routine called when the 'timer interrupt' fires.
+ * Tries to receive a command from the vTPM.
+ */
+static void tis_poll_timer(void *opaque)
+{
+    TPMState *s   = opaque;
+    uint8_t locty = s->active_locty;
+
+    if (!IS_VALID_LOC(locty) ||
+        (!(s->loc[locty].inte & INT_ENABLED) &&
+          (s->aborting_locty != NO_LOCALITY))) {
+        /* no more interrupts requested, so no more polling needed */
+        qemu_del_timer(s->poll_timer);
+    }
+
+    if (s->aborting_locty != NO_LOCALITY) {
+        int n = s->drv->recv(s->drv, locty, TPM_MAX_PKT);
+        DPRINTF("Receiving for abort.\n");
+        if (n > 0) {
+            tis_abort(s);
+            DPRINTF("Abort is complete.\n");
+        } else {
+            tis_prep_next_interrupt(s);
+        }
+    } else if (IS_VALID_LOC(locty)) {
+        if (s->loc[locty].state == STATE_EXECUTION) {
+            int n = s->drv->recv(s->drv, locty, TPM_MAX_PKT);
+
+            /* poll for result */
+            if (n > 0) {
+                s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE;
+                s->loc[locty].state = STATE_COMPLETION;
+                tis_raise_irq(s, locty, INT_DATA_AVAILABLE);
+            } else {
+                /* nothing received */
+                tis_prep_next_interrupt(s);
+            }
+        }
+    }
+}
+
+
+/*
+ * Try to receive a response from the backend TPM
+ */
+static void tis_attempt_receive(TPMState *s, uint8_t locty)
+{
+    /*
+     * Attempt to read from the backend TPM here if
+     * - not aborting a command
+     * - command has been sent and state is 'EXECUTION' now
+     * - no data are already available (data have already been read)
+     */
+    if (!IS_VALID_LOC(s->aborting_locty)) {
+        if (s->loc[locty].state == STATE_EXECUTION) {
+            if (0 == (s->loc[locty].sts & STS_DATA_AVAILABLE)){
+                int n = s->drv->recv(s->drv, locty, TPM_MAX_PKT);
+                if (n > 0) {
+                    s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE;
+                    s->loc[locty].state = STATE_COMPLETION;
+                    tis_raise_irq(s, locty, INT_DATA_AVAILABLE);
+                }
+            }
+        }
+    }
+}
+
+/*
+ * read a byte of response data
+ */
+static uint32_t tpm_data_read(TPMState *s, uint8_t locty)
+{
+    uint32_t ret, len;
+
+    /* try to receive data, if none are there it is ok */
+    tis_attempt_receive(s, locty);
+
+    if (s->loc[locty].state != STATE_COMPLETION) {
+        DPRINTF("tpm_data_read with no data available!\n");
+        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;
+    }
+    DPRINTF("tpm_data_read byte x%02x [%d]\n", ret, s->offset - 1);
+    return ret;
+}
+
+
+/* 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) {
+        /* start timer and inside the timer wait for the result */
+        s->poll_attempts = 0;
+        tis_prep_next_interrupt(s);
+    } 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     =  opaque;
+    uint16_t offset =  addr & 0xffc;
+    uint8_t shift   = (addr & 0x3) * 8;
+    uint32_t val    = 0;
+    uint8_t locty   = locality_from_addr(addr);
+
+    switch (offset) {
+        case TPM_REG_ACCESS:
+            if (s->active_locty == locty) {
+                s->loc[locty].access |= (1 << 5);
+            } else {
+                s->loc[locty].access &= ~(1 << 5);
+            }
+            val = s->loc[locty].access;
+            break;
+        case TPM_REG_INT_ENABLE:
+            val = s->loc[locty].inte;
+            break;
+        case TPM_REG_INT_VECTOR:
+            val = s->irq;
+            break;
+        case TPM_REG_INT_STATUS:
+            tis_attempt_receive(s, locty);
+            val = s->loc[locty].ints;
+            break;
+        case TPM_REG_INTF_CAPABILITY:
+            val = CAPABILITIES_SUPPORTED;
+            break;
+        case TPM_REG_STS: /* status register */
+            tis_attempt_receive(s, locty);
+            val = (sizeof(s->buf) - s->offset) << 8 | s->loc[locty].sts;
+            break;
+        case TPM_REG_DATA_FIFO:
+            val = tpm_data_read(s, locty);
+            break;
+        case TPM_REG_DID_VID:
+            val = (TPM_DID << 16) | TPM_VID;
+            break;
+        case TPM_REG_RID:
+            val = TPM_RID;
+            break;
+        default:
+            DPRINTF("Bad register offset %#08x\n", offset);
+            break;
+    }
+
+    if (shift && locty == 0) {
+        val >>= shift;
+    }
+
+    DPRINTF("read(0x" TARGET_FMT_plx ") = %#08x\n", addr, val);
+
+    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     = opaque;
+    uint16_t offset = addr & 0xfff;
+    uint8_t locty   = locality_from_addr(addr);
+    uint32_t len;
+
+    DPRINTF("write(0x" TARGET_FMT_plx ") = %#08x\n", addr, val);
+
+    switch (offset) {
+        case TPM_REG_ACCESS:
+            if (val & ACCESS_ACTIVE_LOCALITY) {
+                /* give up locality if currently owned */
+                if (s->active_locty == 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_locty && IS_VALID_LOC(s->active_locty)) {
+                    s->loc[s->active_locty].access |= ACCESS_BEEN_SEIZED;
+                    s->loc[locty].access = ACCESS_TPM_REG_VALID_STS;
+                    tis_prep_abort(s, s->active_locty, locty);
+                }
+            }
+            if (val & ACCESS_REQUEST_USE) {
+                if (IS_VALID_LOC(s->active_locty)) {
+                    /* locality election */
+                    s->loc[s->active_locty].access |= ACCESS_PENDING_REQUEST;
+                } else {
+                    /* no locality active -> make this one active now */
+                    s->loc[locty].access |= ACCESS_ACTIVE_LOCALITY;
+                    s->active_locty = locty;
+                    tis_raise_irq(s, locty, INT_LOCALITY_CHANGED);
+                }
+            }
+            break;
+        case TPM_REG_INT_ENABLE:
+            s->loc[locty].inte = (val & (INT_ENABLED | (0x3 << 3) |
+                        INTERRUPTS_SUPPORTED));
+            break;
+        case TPM_REG_INT_VECTOR:
+            /*
+             * isa_init_irq would exit qemu
+             * if the requested IRQ is already assigned
+             */
+            /*isa_init_irq(&s->dev, &s->pic, val);*/
+            DPRINTF("writes to TPM_INT_VECTOR not supported!\n");
+            break;
+        case TPM_REG_INT_STATUS:
+            /* clearing of interrupt flags */
+            if ((val & INTERRUPTS_SUPPORTED) &&
+                    (s->loc[locty].ints & INTERRUPTS_SUPPORTED)) {
+                qemu_set_irq(s->pic, 0);
+                s->irq_pending = 0;
+            }
+            s->loc[locty].ints &= ~(val & INTERRUPTS_SUPPORTED);
+            break;
+        case 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) {
+                if (s->loc[locty].state == STATE_RECEPTION) {
+                    int n = s->drv->send(s->drv, locty,
+                            tpm_get_size_from_buffer(s->buf));
+                    if (n > 0) {
+                        /* sending of data was successful */
+                        s->offset = 0;
+                        s->loc[locty].state = STATE_EXECUTION;
+                        if (s->loc[locty].inte &
+                                (INT_ENABLED | INT_DATA_AVAILABLE)) {
+                            s->poll_attempts = 0;
+                            tis_prep_next_interrupt(s);
+                        }
+                    }
+                }
+            }
+            if (val & STS_RESPONSE_RETRY) {
+                s->offset = 0;
+            }
+            break;
+        case 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 {
+                DPRINTF("Byte to send to TPM: %02x at offset: %03d\n", val,
+                        s->offset);
+                s->loc[locty].state = STATE_RECEPTION;
+
+                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 (len > s->offset) {
+                        s->loc[locty].sts = STS_EXPECT | STS_VALID;
+                    } else {
+                        s->loc[locty].sts = STS_VALID;
+                    }
+                }
+            }
+            break;
+        default:
+            DPRINTF("Bad register offset %#08x\n", offset);
+            break;
+    }
+}
+
+
+static CPUReadMemoryFunc *  const tis_readfn[3] = {
+    tis_mem_readl,
+    tis_mem_readl,
+    tis_mem_readl
+};
+
+static CPUWriteMemoryFunc * const tis_writefn[3] = {
+    tis_mem_writel,
+    tis_mem_writel,
+    tis_mem_writel
+};
+
+/*
+ * Need to get any outstanding responses from the TPM back, so
+ * this might delay the suspend for a while.
+ */
+static void tpm_pre_save(void *opaque)
+{
+    TPMState *s = opaque;
+    uint8_t locty = s->active_locty;
+
+    /* 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_locty].sts & STS_DATA_AVAILABLE)) {
+            sleep(1);
+        }
+    }
+}
+
+
+static void tpm_reset(TPMState *s)
+{
+    int c;
+
+    s->offset       = 0;
+    s->aborting     = 0;
+    s->active_locty = NO_LOCALITY;
+
+    for (c = 0; c < NUM_LOCALITIES; c++) {
+        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;
+    }
+    s->aborting_locty = NO_LOCALITY;
+
+    memset(s->buf, 0, sizeof(s->buf));
+}
+
+static const VMStateDescription vmstate_loc = {
+    .name       = "loc",
+    .version_id = 0,
+    .fields     = (VMStateField []) {
+        VMSTATE_UINT32(state, TPMLocality),
+        VMSTATE_UINT32(inte,  TPMLocality),
+        VMSTATE_UINT32(ints,  TPMLocality),
+        VMSTATE_UINT8(access, TPMLocality),
+        VMSTATE_UINT8(sts,    TPMLocality),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription vmstate_tpm = {
+    .name       = "tpm",
+    .version_id = 0,
+    .minimum_version_id     = 0,
+    .minimum_version_id_old = 0,
+    .pre_save   = tpm_pre_save,
+    .fields     = (VMStateField []) {
+        VMSTATE_UINT32(irq,    TPMState),
+        VMSTATE_UINT32(offset, TPMState),
+        VMSTATE_BUFFER(buf,    TPMState),
+        VMSTATE_UINT8(  active_locty, TPMState),
+        VMSTATE_UINT8(aborting_locty, TPMState),
+        VMSTATE_UINT8(    next_locty, TPMState),
+        VMSTATE_STRUCT_ARRAY(loc, TPMState, NUM_LOCALITIES, 0,
+                             vmstate_loc, TPMLocality),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+/*
+ * initialize TIS interface
+ */
+static int tpm_init(ISADevice *dev)
+{
+    TPMState  *s      = DO_UPCAST(TPMState, dev, dev);
+    int        isairq = 11;
+    TPMDriver *drv    = NULL;
+    int        iomem;
+    int        i;
+
+    if (!s->backend) {
+        fprintf(stderr, "tpm: no backend selected!\n");
+        return -1;
+    }
+    drv = tpm_get_driver(s->backend);
+    if (drv == NULL) {
+        fprintf(stderr, "tpm: backend id %s not found\n", s->backend);
+        return -1;
+    }
+
+    tpm_reset(s);
+
+    drv->buf = s->buf;
+    s->drv   = drv;
+
+    vmstate_register(&dev->qdev, 1, &vmstate_tpm, s);
+    for(i = 0; i < NUM_LOCALITIES; i++) {
+        vmstate_register(&dev->qdev, 1, &vmstate_loc, &s->loc[i]);
+    }
+
+    isa_init_irq(dev, &s->pic, isairq);
+    s->irq = isairq;
+
+    iomem = cpu_register_io_memory(tis_readfn, tis_writefn, s,
+            DEVICE_LITTLE_ENDIAN);
+    cpu_register_physical_memory(TIS_ADDR_BASE,
+            0x1000 * NUM_LOCALITIES, iomem);
+
+    s->poll_timer = qemu_new_timer(vm_clock, tis_poll_timer, s);
+
+    return 0;
+}
+
+static void qdev_tpm_reset(DeviceState *dev)
+{
+    TPMState *s = DO_UPCAST(TPMState, dev.qdev, dev);
+    tpm_reset(s);
+}
+
+static ISADeviceInfo tpm_info = {
+    .init          = tpm_init,
+    .qdev.name     = "tpm",
+    .qdev.desc     = "TPM TIS Interface",
+    .qdev.size     = sizeof (TPMState),
+    .qdev.vmsd     = &vmstate_tpm,
+    .qdev.reset    = qdev_tpm_reset,
+    .qdev.props    = (Property[]) {
+        DEFINE_PROP_STRING("backend", TPMState, backend),
+        DEFINE_PROP_END_OF_LIST(),
+    }
+};
+
+static void tpm_register (void)
+{
+    isa_qdev_register(&tpm_info);
+}
+device_init (tpm_register)