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 --- 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(+) 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 + */ + +#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);