diff mbox

[V4,06/10] Add a TPM backend skeleton implementation

Message ID 20110506173246.617414609@linux.vnet.ibm.com
State New
Headers show

Commit Message

Stefan Berger May 6, 2011, 5:32 p.m. UTC
This patch provides a TPM backend skeleton implementation. It doesn't do
anything useful (except for returning error response for every TPM command)
but it compiles.

v3:
  - in tpm_builtin.c all functions prefixed with tpm_builtin_
  - build the builtin TPM driver available at this point; it returns
    a failure response message for every command
  - do not try to join the TPM thread but poll for its termination;
    the libtpms-based driver will require Qemu's main thread to write
    data to the block storage device while trying to join

V2:
  - only terminating thread in tpm_atexit if it's running

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

---
 Makefile.target  |    5 
 configure        |    1 
 hw/tpm_builtin.c |  425 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/tpm_tis.c     |    3 
 hw/tpm_tis.h     |    2 
 5 files changed, 436 insertions(+)
diff mbox

Patch

Index: qemu-git/hw/tpm_builtin.c
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_builtin.c
@@ -0,0 +1,425 @@ 
+/*
+ *  builtin 'null' TPM driver
+ *
+ *  Copyright (c) 2010, 2011 IBM Corporation
+ *  Copyright (c) 2010, 2011 Stefan Berger
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "qemu-common.h"
+#include "hw/hw.h"
+#include "hw/tpm_tis.h"
+#include "hw/pc.h"
+
+
+//#define DEBUG_TPM
+//#define DEBUG_TPM_SR /* suspend - resume */
+
+
+/* data structures */
+
+typedef struct ThreadParams {
+    TPMState *tpm_state;
+
+    TPMRecvDataCB *recv_data_callback;
+} ThreadParams;
+
+
+/* local variables */
+
+static QemuThread thread;
+
+static QemuMutex state_mutex; /* protects *_state below */
+static QemuMutex tpm_initialized_mutex; /* protect tpm_initialized */
+
+static bool thread_terminate = false;
+static bool tpm_initialized = false;
+static bool had_fatal_error = false;
+static bool had_startup_error = false;
+static bool thread_running = false;
+
+static ThreadParams tpm_thread_params;
+
+/* locality of the command being executed by libtpms */
+static uint8_t g_locty;
+
+static const unsigned char tpm_std_fatal_error_response[10] = {
+    0x00, 0xc4, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09 /* TPM_FAIL */
+};
+
+static char dev_description[80];
+
+
+static void *tpm_builtin_main_loop(void *d)
+{
+    int res = 1;
+    ThreadParams *thr_parms = d;
+    uint32_t in_len, out_len;
+    uint8_t *in, *out;
+    uint32_t resp_size; /* total length of response */
+
+#ifdef DEBUG_TPM
+    fprintf(stderr, "tpm: THREAD IS STARTING\n");
+#endif
+
+    if (res != 0) {
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+        fprintf(stderr, "tpm: Error: TPM initialization failed (rc=%d)\n",
+                res);
+#endif
+	had_fatal_error = true;
+    } else {
+        qemu_mutex_lock(&tpm_initialized_mutex);
+
+        tpm_initialized = true;
+
+        qemu_mutex_unlock(&tpm_initialized_mutex);
+    }
+
+    /* start command processing */
+    while (!thread_terminate) {
+        /* receive and handle commands */
+        in_len = 0;
+        do {
+#ifdef DEBUG_TPM
+            fprintf(stderr, "tpm: waiting for commands...\n");
+#endif
+
+            if (thread_terminate) {
+                break;
+            }
+
+            qemu_mutex_lock(&thr_parms->tpm_state->state_lock);
+
+            /* in case we were to slow and missed the signal, the
+               to_tpm_execute boolean tells us about a pending command */
+            if (!thr_parms->tpm_state->to_tpm_execute) {
+                qemu_cond_wait(&thr_parms->tpm_state->to_tpm_cond,
+                               &thr_parms->tpm_state->state_lock);
+            }
+
+            thr_parms->tpm_state->to_tpm_execute = false;
+
+            qemu_mutex_unlock(&thr_parms->tpm_state->state_lock);
+
+            if (thread_terminate) {
+                break;
+            }
+
+            g_locty = thr_parms->tpm_state->command_locty;
+
+            in = thr_parms->tpm_state->loc[g_locty].w_buffer.buffer;
+            in_len = thr_parms->tpm_state->loc[g_locty].w_offset;
+
+            if (!had_fatal_error) {
+
+                out_len = thr_parms->tpm_state->loc[g_locty].r_buffer.size;
+
+#ifdef DEBUG_TPM
+                fprintf(stderr,
+                        "tpm: received %d bytes from VM in locality %d\n",
+                        in_len,
+                        g_locty);
+                dumpBuffer(stderr, in, in_len);
+#endif
+
+                resp_size = 0;
+
+                /* !!! Send command to TPM & wait for response */
+
+                if (res != 0) {
+#ifdef DEBUG_TPM
+                    fprintf(stderr,
+                            "tpm: Sending/receiving TPM request/response "
+                            "failed.\n");
+#endif
+                    had_fatal_error = 1;
+                }
+            }
+
+            if (had_fatal_error) {
+                out = thr_parms->tpm_state->loc[g_locty].r_buffer.buffer;
+                resp_size = sizeof(tpm_std_fatal_error_response);
+                memcpy(out, tpm_std_fatal_error_response, resp_size);
+                out[1] = (in_len > 2 && in[1] >= 0xc1 && in[1] <= 0xc3)
+                       ? in[1] + 3
+                       : 0xc4;
+            }
+#ifdef DEBUG_TPM
+            fprintf(stderr, "tpm: sending %d bytes to VM\n", resp_size);
+            dumpBuffer(stderr,
+                       thr_parms->tpm_state->loc[g_locty].r_buffer.buffer,
+                       resp_size);
+#endif
+            thr_parms->recv_data_callback(thr_parms->tpm_state, g_locty);
+        } while (in_len > 0);
+    }
+
+    qemu_mutex_lock(&tpm_initialized_mutex);
+
+    if (tpm_initialized) {
+        tpm_initialized = false;
+    }
+
+    qemu_mutex_unlock(&tpm_initialized_mutex);
+
+#ifdef DEBUG_TPM
+    fprintf(stderr, "tpm: THREAD IS ENDING\n");
+#endif
+
+    thread_running = false;
+
+    return NULL;
+}
+
+
+static void tpm_builtin_terminate_tpm_thread(void)
+{
+    if (!thread_running) {
+        return;
+    }
+
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    fprintf(stderr, "tpm: TERMINATING RUNNING TPM THREAD\n");
+#endif
+
+    if (!thread_terminate) {
+        thread_terminate = true;
+
+        qemu_mutex_lock  (&tpm_thread_params.tpm_state->state_lock);
+        qemu_cond_signal (&tpm_thread_params.tpm_state->to_tpm_cond);
+        qemu_mutex_unlock(&tpm_thread_params.tpm_state->state_lock);
+
+        /* The thread will set thread_running = false; it may
+         * still ask us to write data to the disk, though.
+         */
+        while (thread_running) {
+            /* !!! write data to disk if necessary */
+            usleep(100000);
+        }
+
+        memset(&thread, 0, sizeof(thread));
+    }
+}
+
+
+static void tpm_builtin_tpm_atexit(void)
+{
+    tpm_builtin_terminate_tpm_thread();
+}
+
+
+/**
+ * Start the TPM (thread). If it had been started before, then terminate
+ * and start it again.
+ */
+static int tpm_builtin_startup_tpm(void)
+{
+    /* terminate a running TPM */
+    tpm_builtin_terminate_tpm_thread();
+
+    /* reset the flag so the thread keeps on running */
+    thread_terminate = false;
+
+    qemu_thread_create(&thread, tpm_builtin_main_loop, &tpm_thread_params);
+
+    thread_running = true;
+
+    return 0;
+}
+
+
+static int tpm_builtin_do_startup_tpm(void)
+{
+    return tpm_builtin_startup_tpm();
+}
+
+
+/*
+ * Startup the TPM early. This only works for non-encrytped
+ * BlockStorage, since we would not have the key yet.
+ */
+static int tpm_builtin_early_startup_tpm(void)
+{
+    return tpm_builtin_do_startup_tpm();
+}
+
+
+/*
+ * Start up the TPM before it sees the first command.
+ * We need to do this late since only now we will have the
+ * block storage encryption key and can read the previous
+ * TPM state. During 'reset' the key would not be available.
+ */
+static int tpm_builtin_late_startup_tpm(void)
+{
+    /* give it a new try */
+    had_fatal_error = false;
+    had_startup_error = false;
+
+    if (tpm_builtin_do_startup_tpm()) {
+        had_fatal_error = true;
+    }
+
+    return had_fatal_error;
+}
+
+
+static void tpm_builtin_reset(void)
+{
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    fprintf(stderr, "tpm: CALL TO TPM_RESET!\n");
+#endif
+
+    tpm_builtin_terminate_tpm_thread();
+
+    had_fatal_error = false;
+    had_startup_error = false;
+}
+
+
+/*
+ * restore TPM volatile state from given data
+ *
+ * The data are ignore by this driver, instead we read the volatile state
+ * from the TPM block store.
+ *
+ * This function gets called by Qemu when
+ * (1) resuming after a suspend
+ * (2) resuming a snapshot
+ *
+ * (1) works fine since we get call to the reset function as well
+ * (2) requires us to call the reset function ourselves; we do this
+ *     indirectly by calling the tis_reset_for_snapshot_resume();
+ *     a sure indicator of whether this function is called due to a resume
+ *     of a snapshot is that the tread_running variable is 'true'.
+ *
+ */
+static int tpm_builtin_instantiate_with_volatile_data(TPMState *s)
+{
+    if (thread_running) {
+#ifdef DEBUG_TPM_SR
+        fprintf(stderr, "tpm: This is resume of a SNAPSHOT\n");
+#endif
+        tis_reset_for_snapshot_resume(s);
+    }
+
+    return 0;
+}
+
+
+static int tpm_builtin_init(TPMState *s, TPMRecvDataCB *recv_data_cb)
+{
+    tpm_thread_params.tpm_state = s;
+    tpm_thread_params.recv_data_callback = recv_data_cb;
+
+    qemu_mutex_init(&state_mutex);
+    qemu_mutex_init(&tpm_initialized_mutex);
+
+    atexit(tpm_builtin_tpm_atexit);
+
+    return 0;
+}
+
+
+static bool tpm_builtin_get_tpm_established_flag(void)
+{
+    return false;
+}
+
+
+static bool tpm_builtin_get_startup_error(void)
+{
+    return had_startup_error;
+}
+
+
+/**
+ * This function is called by tpm_tis.c once the TPM has processed
+ * the last command and returned the response to the TIS.
+ */
+static int tpm_builtin_save_volatile_data(void)
+{
+    if (!tpm_initialized) {
+        /* TPM was never initialized
+           volatile_state.buffer may be NULL if TPM was never used.
+         */
+        return 0;
+    }
+
+    return 0;
+}
+
+
+static size_t tpm_builtin_realloc_buffer(TPMSizedBuffer *sb)
+{
+    size_t wanted_size = 4096;
+
+    if (sb->size != wanted_size) {
+        sb->buffer = qemu_realloc(sb->buffer, wanted_size);
+        if (sb->buffer != NULL) {
+            sb->size = wanted_size;
+        } else {
+            sb->size = 0;
+        }
+    }
+    return sb->size;
+}
+
+
+static const char *tpm_builtin_create_desc(void)
+{
+    static int done;
+
+    if (!done) {
+        snprintf(dev_description, sizeof(dev_description),
+                 "Skeleton TPM backend");
+        done = 1;
+    }
+
+    return dev_description;
+}
+
+
+static bool tpm_builtin_handle_options(QemuOpts *opts)
+{
+    const char *value;
+
+    value = qemu_opt_get(opts, "path");
+    if (value) {
+        /* !!! handle file path */
+    } else {
+        fprintf(stderr, "-tpm is missing path= parameter\n");
+        return false;
+    }
+    return true;
+}
+
+
+BackendTPMDriver tpm_builtin = {
+    .id                       = "builtin",
+    .desc                     = tpm_builtin_create_desc,
+    .job_for_main_thread      = NULL,
+    .handle_options           = tpm_builtin_handle_options,
+    .init                     = tpm_builtin_init,
+    .early_startup_tpm        = tpm_builtin_early_startup_tpm,
+    .late_startup_tpm         = tpm_builtin_late_startup_tpm,
+    .realloc_buffer           = tpm_builtin_realloc_buffer,
+    .reset                    = tpm_builtin_reset,
+    .had_startup_error        = tpm_builtin_get_startup_error,
+    .save_volatile_data       = tpm_builtin_save_volatile_data,
+    .load_volatile_data       = tpm_builtin_instantiate_with_volatile_data,
+    .get_tpm_established_flag = tpm_builtin_get_tpm_established_flag,
+};
Index: qemu-git/Makefile.target
===================================================================
--- qemu-git.orig/Makefile.target
+++ qemu-git/Makefile.target
@@ -227,6 +227,11 @@  obj-i386-y += debugcon.o multiboot.o
 obj-i386-y += pc_piix.o kvmclock.o
 obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o
 obj-i386-$(CONFIG_TPM) += tpm_tis.o
+obj-i386-$(CONFIG_TPM_BUILTIN) += tpm_builtin.o
+
+ifdef CONFIG_TPM_BUILTIN
+LIBS+=-ltpms
+endif
 
 # shared objects
 obj-ppc-y = ppc.o
Index: qemu-git/hw/tpm_tis.c
===================================================================
--- qemu-git.orig/hw/tpm_tis.c
+++ qemu-git/hw/tpm_tis.c
@@ -95,6 +95,9 @@  static uint32_t tis_mem_readl(void *opaq
 
 
 static const BackendTPMDriver *bes[] = {
+#ifdef CONFIG_TPM_BUILTIN
+    &tpm_builtin,
+#endif
     NULL,
 };
 
Index: qemu-git/configure
===================================================================
--- qemu-git.orig/configure
+++ qemu-git/configure
@@ -3449,6 +3449,7 @@  if test "$tpm" = "yes"; then
   fi
 
   if test "$has_tpm" = "1"; then
+      echo "CONFIG_TPM_BUILTIN=y" >> $config_target_mak
       echo "CONFIG_TPM=y" >> $config_host_mak
   fi
 fi
Index: qemu-git/hw/tpm_tis.h
===================================================================
--- qemu-git.orig/hw/tpm_tis.h
+++ qemu-git/hw/tpm_tis.h
@@ -116,6 +116,8 @@  typedef struct BackendTPMDriver {
 } BackendTPMDriver;
 
 
+extern BackendTPMDriver tpm_builtin;
+
 void tis_reset_for_snapshot_resume(TPMState *s);
 const BackendTPMDriver *tis_get_active_backend(void);