qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 3/5]: Add TPM (frontend) hardware interface (TPM TIS


From: Stefan Berger
Subject: [Qemu-devel] [PATCH 3/5]: Add TPM (frontend) hardware interface (TPM TIS) to Qemu
Date: Thu, 24 Feb 2011 15:04:22 -0500

This patch adds the main code of the TPM frontend driver, the TPM TIS
interface, to Qemu. The code is largely based on my 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 I wrote have a fairly extensive test suite as a SeaBIOS patch
since there full access to all the registers is possible.


Signed-off-by: Stefan Berger <address@hidden>

---
 hw/pc.c      |    3 
 hw/tpm_tis.c | 1011 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/tpm_tis.h |  158 +++++++++
 3 files changed, 1172 insertions(+)

Index: qemu-git/hw/tpm_tis.c
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_tis.c
@@ -0,0 +1,1011 @@
+/*
+ * tpm_tis.c - QEMU emulator for a 1.2 TPM with TIS interface
+ *
+ * Copyright (C) 2006,2010 IBM Corporation
+ *
+ * Author: Stefan Berger <address@hidden>
+ *         David Safford <address@hidden>
+ *
+ * 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 <stdio.h>
+
+#include "block.h"
+#include "hw/hw.h"
+#include "hw/pc.h"
+#include "hw/tpm_tis.h"
+
+//#define DEBUG_TIS
+//#define DEBUG_TIS_SR
+
+/* 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
+
+/* vendor-specific registers */
+#define TIS_REG_DEBUG                 0xf90
+
+#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);
+
+
+static const struct backend_tpm_driver *bes[] = {
+    NULL,
+};
+
+/* active backend driver */
+static const struct backend_tpm_driver *active_be;
+
+const struct backend_tpm_driver *tis_get_active_backend(void)
+{
+    return active_be;
+}
+
+const struct backend_tpm_driver *tis_set_backend_driver(const char *id)
+{
+    int i;
+
+    for (i = 0; bes[i] != NULL; i++)
+        if (!strcmp(bes[i]->id, id))
+            break;
+
+    active_be = bes[i];
+
+    return active_be;
+}
+
+void tis_display_backend_drivers(FILE *out)
+{
+    int i;
+
+    fprintf(out, "Supported TPM types (choose only one):\n");
+
+    for (i = 0; bes[i] != NULL; i++) {
+        fprintf(out, "%7s   %s",
+                bes[i]->id, bes[i]->desc());
+        fprintf(out, "\n");
+    }
+    fprintf(out, "\n");
+}
+
+
+#ifdef DEBUG_TIS
+static void showBuff(const TPMSizedBuffer *sb, const char *string)
+{
+    uint16_t len;
+
+    len = tpm_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 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
+    showBuff(&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
+ */
+static uint32_t tpm_data_read(TPMState *s, uint8_t locty)
+{
+    uint32_t ret = TPM_NO_DATA_BYTE;
+    uint16_t len;
+
+    qemu_mutex_lock(&s->state_lock);
+
+    if ((s->loc[locty].sts & STS_DATA_AVAILABLE)) {
+        len = tpm_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: tpm_data_read byte 0x%02x   [%d]\n",
+                ret,s->loc[locty].r_offset-1);
+#endif
+    }
+
+    qemu_mutex_unlock(&s->state_lock);
+
+    return ret;
+}
+
+
+static void tis_dump_state(void *opaque, target_phys_addr_t addr)
+{
+    static const unsigned regs[] = {
+        TIS_REG_ACCESS,
+        TIS_REG_INT_ENABLE,
+        TIS_REG_INT_VECTOR,
+        TIS_REG_INT_STATUS,
+        TIS_REG_INTF_CAPABILITY,
+        TIS_REG_STS,
+        TIS_REG_DID_VID,
+        TIS_REG_RID,
+        0xfff};
+    int idx;
+    uint8_t locty = locality_from_addr(addr);
+    target_phys_addr_t base = addr & ~0xfff;
+    TPMState *s = (TPMState *)opaque;
+
+    fprintf(stdout,
+            "tpm_tis: active locality      : %d\n"
+            "tpm_tis: state of locality %d : %d\n"
+            "tpm_tis: register dump:\n",
+            s->active_locty,
+            locty, s->loc[locty].state);
+
+    for (idx = 0; regs[idx] != 0xfff; idx++)
+        fprintf(stdout, "tpm_tis: 0x%04x : 0x%08x\n", regs[idx],
+                        tis_mem_readl(opaque, base + regs[idx]));
+
+    fprintf(stdout,
+            "tpm_tis: read offset   : %d\n"
+            "tpm_tis: result buffer : ",
+            s->loc[locty].r_offset);
+    for (idx = 0;
+         idx < tpm_get_size_from_buffer(&s->loc[locty].r_buffer);
+         idx++)
+        fprintf(stdout, "%c%02x%s",
+                s->loc[locty].r_offset == idx ? '>' : ' ',
+                s->loc[locty].r_buffer.buffer[idx],
+                ((idx & 0xf) == 0xf) ? "\ntpm_tis:                 " : "");
+    fprintf(stdout,
+            "\n"
+            "tpm_tis: write offset  : %d\n"
+            "tpm_tis: request buffer: ",
+            s->loc[locty].w_offset);
+    for (idx = 0;
+         idx < tpm_get_size_from_buffer(&s->loc[locty].w_buffer);
+         idx++)
+        fprintf(stdout, "%c%02x%s",
+                s->loc[locty].w_offset == idx ? '>' : ' ',
+                s->loc[locty].w_buffer.buffer[idx],
+                ((idx & 0xf) == 0xf) ? "\ntpm_tis:                 " : "");
+    fprintf(stdout,"\n");
+}
+
+
+/*
+ * 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 = 0xff;
+    uint8_t locty = locality_from_addr(addr);
+
+    if (!s->tpm_initialized) {
+        active_be->late_startup_tpm();
+        s->tpm_initialized = true;
+    }
+
+    if (active_be->had_startup_error())
+        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 |= !active_be->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 =  (tpm_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 = tpm_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;
+    case TIS_REG_DEBUG:
+        tis_dump_state(opaque, addr);
+        break;
+    }
+
+    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= (TPMState *)opaque;
+    uint16_t off = addr & 0xfff;
+    uint8_t locty = 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
+
+    if (!s->tpm_initialized) {
+        active_be->late_startup_tpm();
+        s->tpm_initialized = true;
+    }
+
+    if (active_be->had_startup_error())
+        return;
+
+    qemu_mutex_lock(&s->state_lock);
+
+    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",
+                        __FUNCTION__);
+#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 = tpm_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;
+    active_be->reset();
+}
+
+
+/*
+ * This function is called when the machine starts, resets or due to
+ * S3 resume.
+ * In case of S3 resume we don't reset the TIS but maintain its state.
+ */
+static void tis_reset(TPMState *s)
+{
+    int c;
+
+    s->tpm_initialized = false;
+
+    active_be->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;
+        active_be->realloc_buffer(&s->loc[c].w_buffer);
+        s->loc[c].r_offset = 0;
+        active_be->realloc_buffer(&s->loc[c].r_buffer);
+    }
+}
+
+
+static void tpm_tis_reset(DeviceState *d)
+{
+    TPMState *s = container_of(d, TPMState, busdev.qdev);
+    tis_reset(s);
+}
+
+
+static int tpm_tis_init(ISADevice *dev)
+{
+    TPMState *s = DO_UPCAST(TPMState, busdev, dev);
+    int iomemtype;
+
+    qemu_mutex_init(&s->state_lock);
+    qemu_cond_init(&s->from_tpm_cond);
+    qemu_cond_init(&s->to_tpm_cond);
+
+    if (active_be->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);
+
+    return 0;
+
+ err_exit:
+    exit(1);
+}
+
+/* persistent state handling */
+
+static void tpm_tis_pre_save(void *opaque)
+{
+    TPMState *s= (TPMState *)opaque;
+    uint8_t locty = s->active_locty;
+
+    qemu_mutex_lock(&s->state_lock);
+
+    /* wait for outstanding requests to complete */
+    if (IS_VALID_LOCTY(locty) && s->loc[locty].state == STATE_EXECUTION)
+        qemu_cond_wait(&s->from_tpm_cond, &s->state_lock);
+
+
+#ifdef DEBUG_TIS_SR
+    fprintf(stderr,"tpm_tis: suspend: locty 0 : r_offset = %d, w_offset = 
%d\n",
+        s->loc[0].r_offset,
+        s->loc[0].w_offset);
+    if (s->loc[0].r_offset)
+        tis_dump_state(opaque, 0);
+#endif
+
+    qemu_mutex_unlock(&s->state_lock);
+
+    /* copy current active read or write buffer into the buffer
+       written to disk */
+    if (IS_VALID_LOCTY(locty)) {
+        switch (s->loc[locty].state) {
+        case STATE_RECEPTION:
+            memcpy(s->buf,
+                   s->loc[locty].w_buffer.buffer,
+                   MIN(sizeof(s->buf),
+                       s->loc[locty].w_buffer.size));
+            s->offset = s->loc[locty].w_offset;
+        break;
+        case STATE_COMPLETION:
+            memcpy(s->buf,
+                   s->loc[locty].r_buffer.buffer,
+                   MIN(sizeof(s->buf),
+                       s->loc[locty].r_buffer.size));
+            s->offset = s->loc[locty].r_offset;
+        break;
+        default:
+            /* leak nothing */
+            memset(s->buf, 0x0, sizeof(s->buf));
+        break;
+        }
+    }
+
+    tis_get_active_backend()->save_volatile_data();
+}
+
+
+static int tpm_tis_post_load(void *opaque, int version_id)
+{
+    TPMState *s = (TPMState *)opaque;
+    (void)version_id;
+
+    uint8_t locty = s->active_locty;
+
+    if (IS_VALID_LOCTY(locty)) {
+        switch (s->loc[locty].state) {
+        case STATE_RECEPTION:
+            memcpy(s->loc[locty].w_buffer.buffer,
+                   s->buf,
+                   MIN(sizeof(s->buf),
+                       s->loc[locty].w_buffer.size));
+            s->loc[locty].w_offset = s->offset;
+        break;
+        case STATE_COMPLETION:
+            memcpy(s->loc[locty].r_buffer.buffer,
+                   s->buf,
+                   MIN(sizeof(s->buf),
+                       s->loc[locty].r_buffer.size));
+            s->loc[locty].r_offset = s->offset;
+        break;
+        default:
+        break;
+        }
+    }
+
+#ifdef DEBUG_TIS_SR
+    fprintf(stderr,"tpm_tis: resume : locty 0 : r_offset = %d, w_offset = 
%d\n",
+            s->loc[0].r_offset,
+            s->loc[0].w_offset);
+#endif
+
+    return tis_get_active_backend()->load_volatile_data(s);
+}
+
+
+static const VMStateDescription vmstate_locty = {
+    .name = "loc",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .minimum_version_id_old = 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_tis = {
+    .name = "tpm",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .minimum_version_id_old = 0,
+    .pre_save  = tpm_tis_pre_save,
+    .post_load = tpm_tis_post_load,
+    .fields      = (VMStateField []) {
+        VMSTATE_UINT32(irq_num       , 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, 1,
+                             vmstate_locty, TPMLocality),
+
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+
+static ISADeviceInfo tpm_tis_device_info = {
+    .init         = tpm_tis_init,
+    .qdev.name    = "tpm-tis",
+    .qdev.size    = sizeof(TPMState),
+    .qdev.no_user = 1,
+    .qdev.vmsd    = &vmstate_tpm_tis,
+    .qdev.reset   = tpm_tis_reset,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_UINT8("active_locality", TPMState,
+                          active_locty, NO_LOCALITY),
+        DEFINE_PROP_UINT32("irq", TPMState,
+                           irq_num, TPM_TIS_IRQ),
+        DEFINE_PROP_END_OF_LIST(),
+    },
+};
+
+
+static void tpm_tis_register_device(void)
+{
+    isa_qdev_register(&tpm_tis_device_info);
+}
+
+device_init(tpm_tis_register_device)
Index: qemu-git/hw/tpm_tis.h
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_tis.h
@@ -0,0 +1,158 @@
+/*
+ * tpm_tis.h - include file for tpm_tis.c
+ *
+ * Copyright (C) 2006,2010,2011 IBM Corporation
+ *
+ * Author: Stefan Berger <address@hidden>
+ *         David Safford <address@hidden>
+ *
+ * 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.
+ */
+#ifndef _HW_TPM_TIS_H
+#define _HW_TPM_TIS_H
+
+#include <stdint.h>
+
+#include "isa.h"
+#include "block_int.h"
+#include "qemu-thread.h"
+
+#define TIS_ADDR_BASE       0xFED40000
+
+#define NUM_LOCALITIES      5     /* per spec */
+#define NO_LOCALITY         0xff
+
+#define IS_VALID_LOCTY(x)   ((x) < NUM_LOCALITIES)
+
+
+#define TPM_TIS_IRQ         11
+
+#define TIS_TPM_BUFFER_MAX  4096
+
+
+typedef struct TPMSizedBuffer
+{
+    uint32_t size;
+    uint8_t  *buffer;
+} TPMSizedBuffer;
+
+
+enum tis_state {
+    STATE_IDLE = 0,
+    STATE_READY,
+    STATE_COMPLETION,
+    STATE_EXECUTION,
+    STATE_RECEPTION,
+};
+
+
+/* locality data  -- all fields are persisted */
+typedef struct TPMLocality {
+    enum tis_state state;
+    uint8_t access;
+    uint8_t sts;
+    uint32_t inte;
+    uint32_t ints;
+
+    uint16_t w_offset;
+    uint16_t r_offset;
+    TPMSizedBuffer w_buffer;
+    TPMSizedBuffer r_buffer;
+} TPMLocality;
+
+
+/* overall state of the TPM interface; 's' marks a persisted field */
+typedef struct TPMState {
+    ISADevice busdev;
+
+    uint32_t offset;
+    uint8_t buf[TIS_TPM_BUFFER_MAX];
+
+    uint8_t active_locty;
+    uint8_t aborting_locty;
+    uint8_t next_locty;
+
+    uint8_t command_locty;
+    TPMLocality loc[NUM_LOCALITIES];
+
+    qemu_irq irq;
+    uint32_t irq_num;
+
+    QemuMutex state_lock;
+    QemuCond  from_tpm_cond;
+    QemuCond  to_tpm_cond;
+    bool      to_tpm_execute;
+
+    bool      tpm_initialized;
+} TPMState;
+
+
+void tis_reset_for_snapshot_resume(TPMState *s);
+const struct backend_tpm_driver *tis_get_active_backend(void);
+
+typedef void (TPMRecvDataCB)(TPMState *s, uint8_t locty);
+
+struct backend_tpm_driver {
+    const char *id;
+    const char *(*desc)(void);
+
+    bool (*handle_options)(QemuOpts *);
+
+    int (*init)(TPMState *s, TPMRecvDataCB *datacb);
+    int (*late_startup_tpm)(void);
+    bool (*had_startup_error)(void);
+
+    size_t (*realloc_buffer)(TPMSizedBuffer *sb);
+
+    void (*reset)(void);
+
+    int (*save_volatile_data)(void);
+    int (*load_volatile_data)(TPMState *s);
+
+    bool (*get_tpm_established_flag)(void);
+};
+
+extern struct backend_tpm_driver builtin;
+
+
+/* utility functions */
+
+static inline uint16_t tpm_get_size_from_buffer(const TPMSizedBuffer *sb)
+{
+    return (sb->buffer[4] << 8) + sb->buffer[5];
+}
+
+static inline void clear_sized_buffer(TPMSizedBuffer *tpmsb)
+{
+    if (tpmsb->buffer) {
+       tpmsb->size = 0;
+       qemu_free(tpmsb->buffer);
+       tpmsb->buffer = NULL;
+    }
+}
+
+static inline void set_sized_buffer(TPMSizedBuffer *tpmsb,
+                                    uint8_t *buffer, uint32_t size)
+{
+    clear_sized_buffer(tpmsb);
+    tpmsb->size = size;
+    tpmsb->buffer = buffer;
+}
+
+static inline void dumpBuffer(FILE *stream,
+                              unsigned char *buffer, unsigned int len)
+{
+    int i;
+
+    for (i = 0; i < len; i++) {
+        if (i && !(i % 16))
+            fprintf(stream, "\n");
+         fprintf(stream, "%.2X ", buffer[i]);
+    }
+    fprintf(stream, "\n");
+}
+
+#endif /* _HW_TPM_TIS_H */
Index: qemu-git/hw/pc.c
===================================================================
--- qemu-git.orig/hw/pc.c
+++ qemu-git/hw/pc.c
@@ -1153,6 +1153,9 @@ void pc_basic_device_init(qemu_irq *isa_
         fd[i] = drive_get(IF_FLOPPY, 0, i);
     }
     fdctrl_init_isa(fd);
+
+    if (has_tpm)
+        isa_create_simple("tpm-tis");
 }
 
 void pc_pci_device_init(PCIBus *pci_bus)




reply via email to

[Prev in Thread] Current Thread [Next in Thread]