Patchwork [V7,02/13] Add TPM (frontend) hardware interface (TPM TIS) to Qemu

login
register
mail settings
Submitter Stefan Berger
Date Aug. 10, 2011, 7:29 p.m.
Message ID <20110810193010.720188815@linux.vnet.ibm.com>
Download mbox | patch
Permalink /patch/109430/
State New
Headers show

Comments

Stefan Berger - Aug. 10, 2011, 7:29 p.m.
This patch adds the main code of the TPM frontend driver, the TPM TIS
interface, to Qemu. The code is largely based on the previous implementation
for Xen but has been significantly extended to meet the standard's
requirements, such as the support for changing of localities and all the
functionality of the available flags.

Communication with the backend (i.e., for Xen or the libtpms-based one)
is cleanly separated through an interface which the backend driver needs
to implement.

The TPM TIS driver's backend was previously chosen in the code added
to arch_init. The frontend holds a pointer to the chosen backend (interface).

Communication with the backend is largely based on signals and conditions.
Whenever the frontend has collected a complete packet, it will signal
the backend, which then starts processing the command. Once the result
has been returned, the backend invokes a callback function
(tis_tpm_receive_cb()).

The one tricky part is support for VM suspend while the TPM is processing
a command. In this case the frontend driver is waiting for the backend
to return the result of the last command before shutting down. It waits
on a condition for a signal from the backend, which is delivered in 
tis_tpm_receive_cb().

Testing the proper functioning of the different flags and localities 
cannot be done from user space when running in Linux for example, since
access to the address space of the TPM TIS interface is not possible. Also
the Linux driver itself does not exercise all functionality. So, for
testing there is a fairly extensive test suite as part of the SeaBIOS patches
since from within the BIOS one can have full access to all the TPM's registers.

v5:
  - adding comment to tis_data_read
  - refactoring following support for split command line options
    -tpmdev and -device
  - code handling the configuration of the TPM device was moved to tpm.c
  - removed empty line at end of file

v3:
  - prefixing functions with tis_
  - added a function to the backend interface 'early_startup_tpm' that
    allows to detect the presence of the block storage and gracefully fails
    Qemu if it's not available. This works with migration using shared
    storage but doesn't support migration with block storage migration.
    For encyrypted QCoW2 and in case of a snapshot resue the late_startup_tpm
    interface function is called

Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>

---
 hw/tpm_tis.c |  840 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 840 insertions(+)

Patch

Index: qemu-git/hw/tpm_tis.c
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_tis.c
@@ -0,0 +1,840 @@ 
+/*
+ * tpm_tis.c - QEMU emulator for a 1.2 TPM with TIS interface
+ *
+ * Copyright (C) 2006,2010 IBM Corporation
+ *
+ * Author: Stefan Berger <stefanb@us.ibm.com>
+ *         David Safford <safford@us.ibm.com>
+ *
+ * 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 "tpm.h"
+#include "block.h"
+#include "hw/hw.h"
+#include "hw/pc.h"
+#include "hw/tpm_tis.h"
+
+#include <stdio.h>
+
+//#define DEBUG_TIS
+
+/* whether the STS interrupt is supported */
+//#define RAISE_STS_IRQ
+
+/* tis registers */
+#define TIS_REG_ACCESS                0x00
+#define TIS_REG_INT_ENABLE            0x08
+#define TIS_REG_INT_VECTOR            0x0c
+#define TIS_REG_INT_STATUS            0x10
+#define TIS_REG_INTF_CAPABILITY       0x14
+#define TIS_REG_STS                   0x18
+#define TIS_REG_DATA_FIFO             0x24
+#define TIS_REG_DID_VID               0xf00
+#define TIS_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_STS_VALID                (1 << 1)
+#define INT_LOCALITY_CHANGED         (1 << 2)
+#define INT_COMMAND_READY            (1 << 7)
+
+#ifndef RAISE_STS_IRQ
+
+# define INTERRUPTS_SUPPORTED         (INT_LOCALITY_CHANGED | \
+                                       INT_DATA_AVAILABLE   | \
+                                       INT_COMMAND_READY)
+
+#else
+
+# define INTERRUPTS_SUPPORTED         (INT_LOCALITY_CHANGED | \
+                                       INT_DATA_AVAILABLE   | \
+                                       INT_STS_VALID | \
+                                       INT_COMMAND_READY)
+
+#endif
+
+#define CAPABILITIES_SUPPORTED       ((1 << 4) |            \
+                                      INTERRUPTS_SUPPORTED)
+
+#define TPM_DID          0x0001
+#define TPM_VID          0x0001
+#define TPM_RID          0x0001
+
+#define TPM_NO_DATA_BYTE 0xff
+
+/* prototypes */
+static uint32_t tis_mem_readl(void *opaque, target_phys_addr_t addr);
+
+
+#ifdef DEBUG_TIS
+static void tis_show_buffer(const TPMSizedBuffer *sb, const char *string)
+{
+    uint16_t len;
+
+    len = tis_get_size_from_buffer(sb);
+    fprintf(stderr, "tpm_tis: %s length = %d\n", string, len);
+    dumpBuffer(stderr, sb->buffer, len);
+}
+#endif
+
+
+static inline uint8_t tis_locality_from_addr(target_phys_addr_t addr)
+{
+    return (uint8_t)((addr >> 12) & 0x7);
+}
+
+
+/*
+ * Send a TPM request.
+ * Call this with the state_lock held so we can sync with the receive
+ * callback.
+ */
+static void tis_tpm_send(TPMState *s, uint8_t locty)
+{
+#ifdef DEBUG_TIS
+    tis_show_buffer(&s->loc[locty].w_buffer, "tpm_tis: To TPM");
+#endif
+    s->command_locty = locty;
+
+    /* w_offset serves as length indicator for length of data;
+       it's reset when the response comes back */
+    s->loc[locty].state = STATE_EXECUTION;
+    s->loc[locty].sts &= ~STS_EXPECT;
+
+    s->to_tpm_execute = true;
+    qemu_cond_signal(&s->to_tpm_cond);
+}
+
+
+/* raise an interrupt if allowed */
+static void tis_raise_irq(TPMState *s, uint8_t locty, uint32_t irqmask)
+{
+    if (!IS_VALID_LOCTY(locty)) {
+        return;
+    }
+
+    if ((s->loc[locty].inte & INT_ENABLED) &&
+        (s->loc[locty].inte & irqmask)) {
+#ifdef DEBUG_TIS
+        fprintf(stderr, "tpm_tis: Raising IRQ for flag %08x\n", irqmask);
+#endif
+        qemu_irq_raise(s->irq);
+        s->loc[locty].ints |= irqmask;
+    }
+}
+
+
+static uint32_t tis_check_request_use_except(TPMState *s, uint8_t locty)
+{
+    uint8_t l;
+
+    for (l = 0; l < NUM_LOCALITIES; l++) {
+        if (l == locty) {
+            continue;
+        }
+        if ((s->loc[l].access & ACCESS_REQUEST_USE)) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+
+static void tis_new_active_locality(TPMState *s, uint8_t new_active_locty)
+{
+    int change = (s->active_locty != new_active_locty);
+
+    if (change && IS_VALID_LOCTY(s->active_locty)) {
+        /* reset flags on the old active locality */
+        s->loc[s->active_locty].access &= ~(ACCESS_ACTIVE_LOCALITY|
+                                            ACCESS_REQUEST_USE);
+        if (IS_VALID_LOCTY(new_active_locty) &&
+            s->loc[new_active_locty].access & ACCESS_SEIZE) {
+            s->loc[s->active_locty].access |= ACCESS_BEEN_SEIZED;
+        }
+    }
+
+    s->active_locty = new_active_locty;
+#ifdef DEBUG_TIS
+    fprintf(stderr, "tpm_tis: Active locality is now %d\n", s->active_locty);
+#endif
+
+    if (IS_VALID_LOCTY(new_active_locty)) {
+        /* set flags on the new active locality */
+        s->loc[new_active_locty].access |= ACCESS_ACTIVE_LOCALITY;
+        s->loc[new_active_locty].access &= ~(ACCESS_REQUEST_USE |
+                                             ACCESS_SEIZE);
+    }
+
+    if (change) {
+        tis_raise_irq(s, s->active_locty, INT_LOCALITY_CHANGED);
+    }
+}
+
+
+/* abort -- this function switches the locality */
+static void tis_abort(TPMState *s, uint8_t locty)
+{
+    s->loc[locty].r_offset = 0;
+    s->loc[locty].w_offset = 0;
+
+#ifdef DEBUG_TIS
+    fprintf(stderr, "tpm_tis: tis_abort: new active locality is %d\n",
+            s->next_locty);
+#endif
+
+    /*
+     * 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 */
+    tis_new_active_locality(s, s->next_locty);
+
+    s->next_locty = NO_LOCALITY;
+    s->aborting_locty = NO_LOCALITY; /* nobody's aborting a command anymore */
+}
+
+
+/* prepare aborting current command */
+static void tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty)
+{
+    uint8_t busy_locty;
+
+    s->aborting_locty = locty;
+    s->next_locty = newlocty;  /* locality after successful abort */
+
+    /*
+     * only abort a command using an interrupt if currently executing
+     * a command AND if there's a valid connection to the vTPM.
+     */
+    for (busy_locty = 0; busy_locty < NUM_LOCALITIES; busy_locty++) {
+        if (s->loc[busy_locty].state == STATE_EXECUTION) {
+            /* there is currently no way to interrupt the TPM's operations
+               while it's executing a command; once the TPM is done and
+               returns the buffer, it will switch to the next_locty; */
+#ifdef DEBUG_TIS
+            fprintf(stderr, "tpm_tis: Locality %d is busy - "
+                            "deferring abort\n", busy_locty);
+#endif
+            return;
+        }
+    }
+
+    tis_abort(s, locty);
+}
+
+
+/*
+ * Callback from the TPM to indicate that the response was received.
+ */
+static void tis_tpm_receive_cb(TPMState *s, uint8_t locty)
+{
+    qemu_mutex_lock(&s->state_lock);
+
+    s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE;
+    s->loc[locty].state = STATE_COMPLETION;
+    s->loc[locty].r_offset = 0;
+    s->loc[locty].w_offset = 0;
+
+    if (IS_VALID_LOCTY(s->next_locty)) {
+        tis_abort(s, locty);
+    }
+
+    qemu_cond_signal(&s->from_tpm_cond);
+
+    qemu_mutex_unlock(&s->state_lock);
+
+#ifndef RAISE_STS_IRQ
+    tis_raise_irq(s, locty, INT_DATA_AVAILABLE);
+#else
+    tis_raise_irq(s, locty, INT_DATA_AVAILABLE | INT_STS_VALID);
+#endif
+}
+
+
+/*
+ * read a byte of response data
+ * call this with s->state_lock held
+ */
+static uint32_t tis_data_read(TPMState *s, uint8_t locty)
+{
+    uint32_t ret = TPM_NO_DATA_BYTE;
+    uint16_t len;
+
+    if ((s->loc[locty].sts & STS_DATA_AVAILABLE)) {
+        len = tis_get_size_from_buffer(&s->loc[locty].r_buffer);
+
+        ret = s->loc[locty].r_buffer.buffer[s->loc[locty].r_offset++];
+        if (s->loc[locty].r_offset >= len) {
+            /* got last byte */
+            s->loc[locty].sts = STS_VALID;
+#ifdef RAISE_STS_IRQ
+            tis_raise_irq(s, locty, INT_STS_VALID);
+#endif
+        }
+#ifdef DEBUG_TIS
+        fprintf(stderr, "tpm_tis: tis_data_read byte 0x%02x   [%d]\n",
+                ret, s->loc[locty].r_offset-1);
+#endif
+    }
+
+    return ret;
+}
+
+
+/*
+ * 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 = 0xff;
+    uint8_t locty = tis_locality_from_addr(addr);
+
+    qemu_mutex_lock(&s->state_lock);
+
+    if (!s->tpm_initialized) {
+        s->be_driver->ops->late_startup_tpm();
+        s->tpm_initialized = true;
+    }
+
+    if (s->be_driver->ops->had_startup_error()) {
+        qemu_mutex_unlock(&s->state_lock);
+        return 0xFFFFFFFF;
+    }
+
+    switch (offset) {
+    case TIS_REG_ACCESS:
+        /* never show the SEIZE flag even though we use it internally */
+        val = s->loc[locty].access & ~ACCESS_SEIZE;
+        /* the pending flag is alawys calculated */
+        if (tis_check_request_use_except(s, locty)) {
+            val |= ACCESS_PENDING_REQUEST;
+        }
+        val |= !s->be_driver->ops->get_tpm_established_flag();
+        break;
+    case TIS_REG_INT_ENABLE:
+        val = s->loc[locty].inte;
+        break;
+    case TIS_REG_INT_VECTOR:
+        val = s->irq_num;
+        break;
+    case TIS_REG_INT_STATUS:
+        val = s->loc[locty].ints;
+        break;
+    case TIS_REG_INTF_CAPABILITY:
+        val = CAPABILITIES_SUPPORTED;
+        break;
+    case TIS_REG_STS:
+        if (s->active_locty == locty) {
+            if ((s->loc[locty].sts & STS_DATA_AVAILABLE)) {
+                val =  (tis_get_size_from_buffer(&s->loc[locty].r_buffer) -
+                        s->loc[locty].r_offset) << 8 | s->loc[locty].sts;
+            } else {
+                val = (s->loc[locty].w_buffer.size -
+                       s->loc[locty].w_offset) << 8 | s->loc[locty].sts;
+            }
+        }
+        break;
+    case TIS_REG_DATA_FIFO:
+        if (s->active_locty == locty) {
+            switch (s->loc[locty].state) {
+            case STATE_COMPLETION:
+                val = tis_data_read(s, locty);
+                break;
+            default:
+                val = TPM_NO_DATA_BYTE;
+                break;
+            }
+        }
+        break;
+    case TIS_REG_DID_VID:
+        val = (TPM_DID << 16) | TPM_VID;
+        break;
+    case TIS_REG_RID:
+        val = TPM_RID;
+        break;
+    }
+
+    qemu_mutex_unlock(&s->state_lock);
+
+    if (shift) {
+        val >>= shift;
+    }
+
+#ifdef DEBUG_TIS
+    fprintf(stderr, "tpm_tis:  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_intern(void *opaque, target_phys_addr_t addr,
+                                  uint32_t val, bool hw_access)
+{
+    TPMState *s = opaque;
+    uint16_t off = addr & 0xfff;
+    uint8_t locty = tis_locality_from_addr(addr);
+    uint8_t active_locty, l;
+    int c, set_new_locty = 1;
+    uint16_t len;
+
+#ifdef DEBUG_TIS
+    fprintf(stderr, "tpm_tis: write(%08x) = %08x\n", (int)addr, val);
+#endif
+
+    qemu_mutex_lock(&s->state_lock);
+
+    if (!s->tpm_initialized) {
+        s->be_driver->ops->late_startup_tpm();
+        s->tpm_initialized = true;
+    }
+
+    if (s->be_driver->ops->had_startup_error()) {
+        qemu_mutex_unlock(&s->state_lock);
+        return;
+    }
+
+    switch (off) {
+    case TIS_REG_ACCESS:
+
+        if ((val & ACCESS_SEIZE)) {
+            val &= ~(ACCESS_REQUEST_USE | ACCESS_ACTIVE_LOCALITY);
+        }
+
+        active_locty = s->active_locty;
+
+        if ((val & ACCESS_ACTIVE_LOCALITY)) {
+            /* give up locality if currently owned */
+            if (s->active_locty == locty) {
+#ifdef DEBUG_TIS
+                fprintf(stderr, "tpm_tis: Releasing locality %d\n", locty);
+#endif
+                uint8_t newlocty = NO_LOCALITY;
+                /* anybody wants the locality ? */
+                for (c = NUM_LOCALITIES - 1; c >= 0; c--) {
+                    if ((s->loc[c].access & ACCESS_REQUEST_USE)) {
+#ifdef DEBUG_TIS
+                        fprintf(stderr, "tpm_tis: Locality %d requests use.\n",
+                                c);
+#endif
+                        newlocty = c;
+                        break;
+                    }
+                }
+#ifdef DEBUG_TIS
+                fprintf(stderr, "tpm_tis: ACCESS_ACTIVE_LOCALITY: "
+                                "Next active locality: %d\n",
+                                newlocty);
+#endif
+                if (IS_VALID_LOCTY(newlocty)) {
+                    set_new_locty = 0;
+                    tis_prep_abort(s, locty, newlocty);
+                } else
+                    active_locty = NO_LOCALITY;
+            } else {
+                /* not currently the owner; clear a pending request */
+                s->loc[locty].access &= ~ACCESS_REQUEST_USE;
+            }
+        }
+
+        if ((val & ACCESS_BEEN_SEIZED)) {
+            s->loc[locty].access &= ~ACCESS_BEEN_SEIZED;
+        }
+
+        if ((val & ACCESS_SEIZE)) {
+            /* allow seize if a locality is active and the requesting
+               locality is higher than the one that's active
+               OR
+               allow seize for requesting locality if no locality is
+               active */
+            while ((IS_VALID_LOCTY(s->active_locty) &&
+                    locty > s->active_locty) ||
+                   (!IS_VALID_LOCTY(s->active_locty))) {
+
+                /* already a pending SEIZE ? */
+                if ((s->loc[locty].access & ACCESS_SEIZE)) {
+                    break;
+                }
+
+                /* check for ongoing seize by a higher locality */
+                for (l = locty + 1; l < NUM_LOCALITIES; l++) {
+                    if ((s->loc[l].access & ACCESS_SEIZE)) {
+                        break;
+                    }
+                }
+
+                /* cancel any seize by a lower locality */
+                for (l = 0; l < locty - 1; l++) {
+                    s->loc[l].access &= ~ACCESS_SEIZE;
+                }
+
+                s->loc[locty].access |= ACCESS_SEIZE;
+#ifdef DEBUG_TIS
+                fprintf(stderr, "tpm_tis: ACCESS_SEIZE: "
+                                "Locality %d seized from locality %d\n",
+                                locty, s->active_locty);
+                fprintf(stderr, "tpm_tis: ACCESS_SEIZE: Initiating abort.\n");
+#endif
+                set_new_locty = 0;
+                tis_prep_abort(s, s->active_locty, locty);
+                break;
+            }
+        }
+
+        if ((val & ACCESS_REQUEST_USE)) {
+            if (s->active_locty != locty) {
+                if (IS_VALID_LOCTY(s->active_locty)) {
+                    s->loc[locty].access |= ACCESS_REQUEST_USE;
+                } else {
+                    /* no locality active -> make this one active now */
+                    active_locty = locty;
+                }
+            }
+        }
+
+        if (set_new_locty) {
+            tis_new_active_locality(s, active_locty);
+        }
+
+        break;
+    case TIS_REG_INT_ENABLE:
+        if (s->active_locty != locty) {
+            break;
+        }
+
+        s->loc[locty].inte = (val & (INT_ENABLED | (0x3 << 3) |
+                                     INTERRUPTS_SUPPORTED));
+        break;
+    case TIS_REG_INT_VECTOR:
+        /* hard wired -- ignore */
+        break;
+    case TIS_REG_INT_STATUS:
+        if (s->active_locty != locty) {
+            break;
+        }
+
+        /* clearing of interrupt flags */
+        if (((val & INTERRUPTS_SUPPORTED)) &&
+            (s->loc[locty].ints & INTERRUPTS_SUPPORTED)) {
+            s->loc[locty].ints &= ~val;
+            if (s->loc[locty].ints == 0) {
+                qemu_irq_lower(s->irq);
+#ifdef DEBUG_TIS
+                fprintf(stderr, "tpm_tis: Lowering IRQ\n");
+#endif
+            }
+        }
+        s->loc[locty].ints &= ~(val & INTERRUPTS_SUPPORTED);
+        break;
+    case TIS_REG_STS:
+        if (s->active_locty != locty) {
+            break;
+        }
+
+        val &= (STS_COMMAND_READY | STS_TPM_GO | STS_RESPONSE_RETRY);
+
+        if (val == STS_COMMAND_READY) {
+            switch (s->loc[locty].state) {
+
+            case STATE_READY:
+                s->loc[locty].w_offset = 0;
+                s->loc[locty].r_offset = 0;
+            break;
+
+            case STATE_IDLE:
+                s->loc[locty].sts   = STS_COMMAND_READY;
+                s->loc[locty].state = STATE_READY;
+                tis_raise_irq(s, locty, INT_COMMAND_READY);
+            break;
+
+            case STATE_EXECUTION:
+            case STATE_RECEPTION:
+                /* abort currently running command */
+#ifdef DEBUG_TIS
+                fprintf(stderr, "tpm_tis: %s: Initiating abort.\n",
+                        __func__);
+#endif
+                tis_prep_abort(s, locty, locty);
+            break;
+
+            case STATE_COMPLETION:
+                s->loc[locty].w_offset = 0;
+                s->loc[locty].r_offset = 0;
+                /* shortcut to ready state with C/R set */
+                s->loc[locty].state = STATE_READY;
+                if (!(s->loc[locty].sts & STS_COMMAND_READY)) {
+                    s->loc[locty].sts   = STS_COMMAND_READY;
+                    tis_raise_irq(s, locty, INT_COMMAND_READY);
+                }
+            break;
+
+            }
+        } else if (val == STS_TPM_GO) {
+            switch (s->loc[locty].state) {
+            case STATE_RECEPTION:
+                tis_tpm_send(s, locty);
+                break;
+            default:
+                /* ignore */
+                break;
+            }
+        } else if (val == STS_RESPONSE_RETRY) {
+            switch (s->loc[locty].state) {
+            case STATE_COMPLETION:
+                s->loc[locty].r_offset = 0;
+                s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE;
+                break;
+            default:
+                /* ignore */
+                break;
+            }
+        }
+        break;
+    case TIS_REG_DATA_FIFO:
+        /* data fifo */
+        if (s->active_locty != locty) {
+            break;
+        }
+
+        if (s->loc[locty].state == STATE_IDLE ||
+            s->loc[locty].state == STATE_EXECUTION ||
+            s->loc[locty].state == STATE_COMPLETION) {
+            /* drop the byte */
+        } else {
+#ifdef DEBUG_TIS
+            fprintf(stderr, "tpm_tis: Byte to send to TPM: %02x\n", val);
+#endif
+            if (s->loc[locty].state == STATE_READY) {
+                s->loc[locty].state = STATE_RECEPTION;
+                s->loc[locty].sts = STS_EXPECT | STS_VALID;
+            }
+
+            if ((s->loc[locty].sts & STS_EXPECT)) {
+                if (s->loc[locty].w_offset < s->loc[locty].w_buffer.size) {
+                    s->loc[locty].w_buffer.buffer[s->loc[locty].w_offset++] =
+                        (uint8_t)val;
+                } else {
+                    s->loc[locty].sts = STS_VALID;
+                }
+            }
+
+            /* check for complete packet */
+            if (s->loc[locty].w_offset > 5 &&
+                (s->loc[locty].sts & STS_EXPECT)) {
+                /* we have a packet length - see if we have all of it */
+#ifdef RAISE_STS_IRQ
+                bool needIrq = !(s->loc[locty].sts & STS_VALID);
+#endif
+                len = tis_get_size_from_buffer(&s->loc[locty].w_buffer);
+                if (len > s->loc[locty].w_offset) {
+                    s->loc[locty].sts = STS_EXPECT | STS_VALID;
+                } else {
+                    /* packet complete */
+                    s->loc[locty].sts = STS_VALID;
+                }
+#ifdef RAISE_STS_IRQ
+                if (needIrq) {
+                    tis_raise_irq(s, locty, INT_STS_VALID);
+                }
+#endif
+            }
+        }
+        break;
+    }
+
+    qemu_mutex_unlock(&s->state_lock);
+}
+
+
+static void tis_mem_writel(void *opaque, target_phys_addr_t addr,
+                           uint32_t val)
+{
+    return tis_mem_writel_intern(opaque, addr, val, false);
+}
+
+
+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
+};
+
+
+/*
+ * This function gets called when resuming a snapshot. In that
+ * case we received the TIS state from persistent storage and
+ * just need to reset.
+ */
+void tis_reset_for_snapshot_resume(TPMState *s)
+{
+    s->tpm_initialized = false;
+    s->be_driver->ops->reset();
+    /* early startup not possible here */
+}
+
+
+static int tis_do_early_startup_tpm(TPMState *s)
+{
+    int rc;
+    /*
+     * Attempt an early startup of the backend; this only works
+     * if the block storage is not encrypted since the key is not
+     * available yet.
+     */
+    rc = s->be_driver->ops->early_startup_tpm();
+
+    switch (rc) {
+    case 0:
+#ifdef DEBUG_TIS
+        fprintf(stderr, "tpm_tis: Early startup worked -- "
+                "TPM backend is initialized\n");
+#endif
+        s->tpm_initialized = true;
+        break;
+
+    case -ENOKEY:
+#ifdef DEBUG_TIS
+        fprintf(stderr, "tpm_tis: Early startup failed -- "
+                "no key for encrypted drive\n");
+#endif
+        break;
+
+    default:
+        break;
+    }
+
+    return rc;
+}
+
+
+/*
+ * This function is called when the machine starts, resets or due to
+ * S3 resume.
+ */
+static void tis_s_reset(TPMState *s)
+{
+    int c;
+
+    s->tpm_initialized = false;
+
+    s->be_driver->ops->reset();
+
+    s->active_locty = NO_LOCALITY;
+    s->next_locty = NO_LOCALITY;
+    s->aborting_locty = NO_LOCALITY;
+
+    for (c = 0; c < NUM_LOCALITIES; c++) {
+        s->loc[c].access = ACCESS_TPM_REG_VALID_STS;
+        s->loc[c].sts = 0;
+        s->loc[c].inte = (1 << 3);
+        s->loc[c].ints = 0;
+        s->loc[c].state = STATE_IDLE;
+
+        s->loc[c].w_offset = 0;
+        s->be_driver->ops->realloc_buffer(&s->loc[c].w_buffer);
+        s->loc[c].r_offset = 0;
+        s->be_driver->ops->realloc_buffer(&s->loc[c].r_buffer);
+    }
+
+    tis_do_early_startup_tpm(s);
+}
+
+
+static void tis_reset(DeviceState *d)
+{
+    TPMState *s = container_of(d, TPMState, busdev.qdev);
+    tis_s_reset(s);
+}
+
+
+static int tis_init(ISADevice *dev)
+{
+    TPMState *s = DO_UPCAST(TPMState, busdev, dev);
+    int iomemtype, rc;
+    const char *backend = s->backend ? s->backend : TPM_DEFAULT_DEVICE_ID;
+
+    qemu_mutex_init(&s->state_lock);
+    qemu_cond_init(&s->from_tpm_cond);
+    qemu_cond_init(&s->to_tpm_cond);
+
+    s->be_driver = qemu_find_tpm(backend);
+    if (!s->be_driver) {
+        fprintf(stderr,
+                "tpm_tis: backend driver with id %s could not be found.n\n",
+                backend);
+        return -1;
+    }
+
+    if (s->be_driver->ops->init(s, tis_tpm_receive_cb)) {
+        goto err_exit;
+    }
+
+    isa_init_irq(dev, &s->irq, s->irq_num);
+
+    iomemtype = cpu_register_io_memory(tis_readfn, tis_writefn, s,
+                                       DEVICE_LITTLE_ENDIAN);
+    cpu_register_physical_memory(TIS_ADDR_BASE, 0x1000 * NUM_LOCALITIES,
+                                 iomemtype);
+
+    /*
+     * startup the TPM backend early to detect problems early
+     */
+    rc = tis_do_early_startup_tpm(s);
+    if (rc != 0 && rc != -ENOKEY) {
+        fprintf(stderr,
+                "tpm_tis: Fatal error accessing TPM's block storage.\n");
+        goto err_exit;
+    }
+
+    return 0;
+
+ err_exit:
+    return -1;
+}