From patchwork Thu Jun 6 13:32:43 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Corey Bryant X-Patchwork-Id: 249416 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 8F38F2C008A for ; Thu, 6 Jun 2013 23:33:37 +1000 (EST) Received: from localhost ([::1]:60664 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UkaJr-0001tl-Kx for incoming@patchwork.ozlabs.org; Thu, 06 Jun 2013 09:33:35 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:39283) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UkaJM-0001oo-Uk for qemu-devel@nongnu.org; Thu, 06 Jun 2013 09:33:10 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1UkaJH-0001RL-Hk for qemu-devel@nongnu.org; Thu, 06 Jun 2013 09:33:04 -0400 Received: from e7.ny.us.ibm.com ([32.97.182.137]:48706) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UkaJH-0001RF-Bc for qemu-devel@nongnu.org; Thu, 06 Jun 2013 09:32:59 -0400 Received: from /spool/local by e7.ny.us.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Thu, 6 Jun 2013 09:32:58 -0400 Received: from d01dlp02.pok.ibm.com (9.56.250.167) by e7.ny.us.ibm.com (192.168.1.107) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Thu, 6 Jun 2013 09:32:56 -0400 Received: from d01relay04.pok.ibm.com (d01relay04.pok.ibm.com [9.56.227.236]) by d01dlp02.pok.ibm.com (Postfix) with ESMTP id 4FD8C6E803F for ; Thu, 6 Jun 2013 09:32:52 -0400 (EDT) Received: from d03av03.boulder.ibm.com (d03av03.boulder.ibm.com [9.17.195.169]) by d01relay04.pok.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id r56DWtkg281734 for ; Thu, 6 Jun 2013 09:32:55 -0400 Received: from d03av03.boulder.ibm.com (loopback [127.0.0.1]) by d03av03.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id r56DWplp023846 for ; Thu, 6 Jun 2013 07:32:51 -0600 Received: from localhost ([9.80.98.244]) by d03av03.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id r56DWpLS023758; Thu, 6 Jun 2013 07:32:51 -0600 From: Corey Bryant To: qemu-devel@nongnu.org Date: Thu, 6 Jun 2013 09:32:43 -0400 Message-Id: <1370525564-16912-2-git-send-email-coreyb@linux.vnet.ibm.com> X-Mailer: git-send-email 1.8.1.4 In-Reply-To: <1370525564-16912-1-git-send-email-coreyb@linux.vnet.ibm.com> References: <1370525564-16912-1-git-send-email-coreyb@linux.vnet.ibm.com> X-TM-AS-MML: No X-Content-Scanned: Fidelis XPS MAILER x-cbid: 13060613-5806-0000-0000-000021904597 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x-2.6.x [generic] X-Received-From: 32.97.182.137 Cc: kwolf@redhat.com, aliguori@us.ibm.com, stefanb@linux.vnet.ibm.com, Corey Bryant , mdroth@linux.vnet.ibm.com, jschopp@linux.vnet.ibm.com, stefanha@redhat.com Subject: [Qemu-devel] [PATCH v3 1/3] nvram: Add TPM NVRAM implementation X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Provides TPM NVRAM implementation that enables storing of TPM NVRAM data in a persistent image file. The block driver is used to read/write the drive image. This will enable, for example, an encrypted QCOW2 image to be used to store sensitive keys. This patch provides APIs that a TPM backend can use to read and write data. Signed-off-by: Corey Bryant --- v2 -Use non bit-rotting DPRINTF (stefanha@redhat.com) -Use DIV_ROUND_UP (stefanha@redhat.com) -Use bdrv_pread/bdrv_pwrite in coroutine - causes global sector to byte I/O changes (stefanha@redhat.com, kwolf@redhat.com) -Add tpm_nvram_required_size_kb() and update tpm_nvram_adjust_size() -Drop qemu_aio_wait() from tpm_nvram_do_co_read/write() and move any completion code path to coroutines -Replace tpm_nvram_rwrequest_free() with g_free(rwr) -Remove unneeded arg checks from tpm_nvram_read/write() (stefanha@redhat.com) -Init rwr->rc to -EINPROGRESS and wrap qemu_cond_wait() with check (stefanha@redhat.com) -Add init of completion_mutex and completion condition v3 -Set rwr->rc within locked completion_mutex (stefanha@redhat.com) -Move tpm_nvram_adjust_size to tpm_nvram_co_write (stefanha@redhat.com) --- hw/tpm/Makefile.objs | 1 + hw/tpm/tpm_nvram.c | 324 ++++++++++++++++++++++++++++++++++++++++++++++++++ hw/tpm/tpm_nvram.h | 25 ++++ 3 files changed, 350 insertions(+), 0 deletions(-) create mode 100644 hw/tpm/tpm_nvram.c create mode 100644 hw/tpm/tpm_nvram.h diff --git a/hw/tpm/Makefile.objs b/hw/tpm/Makefile.objs index 99f5983..49faef4 100644 --- a/hw/tpm/Makefile.objs +++ b/hw/tpm/Makefile.objs @@ -1,2 +1,3 @@ common-obj-$(CONFIG_TPM_TIS) += tpm_tis.o +common-obj-$(CONFIG_TPM_TIS) += tpm_nvram.o common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o diff --git a/hw/tpm/tpm_nvram.c b/hw/tpm/tpm_nvram.c new file mode 100644 index 0000000..a0b547b --- /dev/null +++ b/hw/tpm/tpm_nvram.c @@ -0,0 +1,324 @@ +/* + * TPM NVRAM - enables storage of persistent NVRAM data on an image file + * + * Copyright (C) 2013 IBM Corporation + * + * Authors: + * Stefan Berger + * Corey Bryant + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "tpm_nvram.h" +#include "block/block_int.h" +#include "qemu/thread.h" +#include "sysemu/sysemu.h" + +#define TPM_NVRAM_DEBUG 0 +#define DPRINTF(fmt, ...) \ + do { \ + if (TPM_NVRAM_DEBUG) { \ + fprintf(stderr, fmt, ## __VA_ARGS__); \ + } \ + } while (0) + +/* Read/write request data */ +typedef struct TPMNvramRWRequest { + BlockDriverState *bdrv; + bool is_write; + uint64_t offset; + uint8_t **blob_r; + uint8_t *blob_w; + uint32_t size; + int rc; + + QemuMutex completion_mutex; + QemuCond completion; + + QSIMPLEQ_ENTRY(TPMNvramRWRequest) list; +} TPMNvramRWRequest; + +/* Mutex protected queue of read/write requests */ +static QemuMutex tpm_nvram_rwrequests_mutex; +static QSIMPLEQ_HEAD(, TPMNvramRWRequest) tpm_nvram_rwrequests = + QSIMPLEQ_HEAD_INITIALIZER(tpm_nvram_rwrequests); + +static QEMUBH *tpm_nvram_bh; + +/* + * Get the disk size in kilobytes needed to store a blob (rounded up to next kb) + */ +static uint64_t tpm_nvram_required_size_kb(uint64_t offset, uint32_t size) +{ + uint64_t required_size = offset + size; + return DIV_ROUND_UP(required_size, 1024); +} + +/* + * Increase the drive size if it's too small to store the blob + */ +static int tpm_nvram_adjust_size(BlockDriverState *bdrv, uint64_t offset, + uint32_t size) +{ + int rc = 0; + int64_t drive_size, required_size; + + rc = bdrv_getlength(bdrv); + if (rc < 0) { + DPRINTF("%s: Unable to determine TPM NVRAM drive size\n", __func__); + return rc; + } + + drive_size = rc; + required_size = tpm_nvram_required_size_kb(offset, size) * 1024; + + if (drive_size < required_size) { + rc = bdrv_truncate(bdrv, required_size); + if (rc < 0) { + DPRINTF("%s: TPM NVRAM drive too small\n", __func__); + } + } + + return rc; +} + +/* + * Coroutine that reads a blob from the drive asynchronously + */ +static void coroutine_fn tpm_nvram_co_read(void *opaque) +{ + int rc; + TPMNvramRWRequest *rwr = opaque; + + *rwr->blob_r = g_malloc(rwr->size); + + rc = bdrv_pread(rwr->bdrv, rwr->offset, *rwr->blob_r, rwr->size); + if (rc != rwr->size) { + g_free(*rwr->blob_r); + *rwr->blob_r = NULL; + } + + qemu_mutex_lock(&rwr->completion_mutex); + rwr->rc = rc; + qemu_cond_signal(&rwr->completion); + qemu_mutex_unlock(&rwr->completion_mutex); +} + +/* + * Coroutine that writes a blob to the drive asynchronously + */ +static void coroutine_fn tpm_nvram_co_write(void *opaque) +{ + int rc; + TPMNvramRWRequest *rwr = opaque; + + rc = tpm_nvram_adjust_size(rwr->bdrv, rwr->offset, rwr->size); + if (rc < 0) { + goto exit; + } + + rc = bdrv_pwrite(rwr->bdrv, rwr->offset, rwr->blob_w, rwr->size); + +exit: + qemu_mutex_lock(&rwr->completion_mutex); + rwr->rc = rc; + qemu_cond_signal(&rwr->completion); + qemu_mutex_unlock(&rwr->completion_mutex); +} + +/* + * Enter a coroutine to read a blob from the drive + */ +static void tpm_nvram_do_co_read(TPMNvramRWRequest *rwr) +{ + Coroutine *co; + + co = qemu_coroutine_create(tpm_nvram_co_read); + qemu_coroutine_enter(co, rwr); +} + +/* + * Enter a coroutine to write a blob to the drive + */ +static void tpm_nvram_do_co_write(TPMNvramRWRequest *rwr) +{ + int rc; + Coroutine *co; + + co = qemu_coroutine_create(tpm_nvram_co_write); + qemu_coroutine_enter(co, rwr); +} + +/* + * Initialization for read requests + */ +static TPMNvramRWRequest *tpm_nvram_rwrequest_init_read(BlockDriverState *bdrv, + uint64_t offset, + uint8_t **blob, + uint32_t size) +{ + TPMNvramRWRequest *rwr; + + rwr = g_new0(TPMNvramRWRequest, 1); + rwr->bdrv = bdrv; + rwr->is_write = false; + rwr->offset = offset; + rwr->blob_r = blob; + rwr->size = size; + rwr->rc = -EINPROGRESS; + + qemu_mutex_init(&rwr->completion_mutex); + qemu_cond_init(&rwr->completion); + + return rwr; +} + +/* + * Initialization for write requests + */ +static TPMNvramRWRequest *tpm_nvram_rwrequest_init_write(BlockDriverState *bdrv, + uint64_t offset, + uint8_t *blob, + uint32_t size) +{ + TPMNvramRWRequest *rwr; + + rwr = g_new0(TPMNvramRWRequest, 1); + rwr->bdrv = bdrv; + rwr->is_write = true; + rwr->offset = offset; + rwr->blob_w = blob; + rwr->size = size; + rwr->rc = -EINPROGRESS; + + qemu_mutex_init(&rwr->completion_mutex); + qemu_cond_init(&rwr->completion); + + return rwr; +} + +/* + * Execute a read or write of TPM NVRAM blob data + */ +static void tpm_nvram_rwrequest_exec(TPMNvramRWRequest *rwr) +{ + if (rwr->is_write) { + tpm_nvram_do_co_write(rwr); + } else { + tpm_nvram_do_co_read(rwr); + } +} + +/* + * Bottom-half callback that is invoked by QEMU's main thread to + * process TPM NVRAM read/write requests. + */ +static void tpm_nvram_rwrequest_callback(void *opaque) +{ + TPMNvramRWRequest *rwr, *next; + + qemu_mutex_lock(&tpm_nvram_rwrequests_mutex); + + QSIMPLEQ_FOREACH_SAFE(rwr, &tpm_nvram_rwrequests, list, next) { + QSIMPLEQ_REMOVE(&tpm_nvram_rwrequests, rwr, TPMNvramRWRequest, list); + + qemu_mutex_unlock(&tpm_nvram_rwrequests_mutex); + tpm_nvram_rwrequest_exec(rwr); + qemu_mutex_lock(&tpm_nvram_rwrequests_mutex); + } + + qemu_mutex_unlock(&tpm_nvram_rwrequests_mutex); +} + +/* + * Schedule a bottom-half to read or write a blob to the TPM NVRAM drive + */ +static void tpm_nvram_rwrequest_schedule(TPMNvramRWRequest *rwr) +{ + qemu_mutex_lock(&tpm_nvram_rwrequests_mutex); + QSIMPLEQ_INSERT_TAIL(&tpm_nvram_rwrequests, rwr, list); + qemu_mutex_unlock(&tpm_nvram_rwrequests_mutex); + + qemu_bh_schedule(tpm_nvram_bh); + + /* Wait for completion of the read/write request */ + qemu_mutex_lock(&rwr->completion_mutex); + while (rwr->rc == -EINPROGRESS) { + qemu_cond_wait(&rwr->completion, &rwr->completion_mutex); + } + qemu_mutex_unlock(&rwr->completion_mutex); +} + +/* + * Initialize a TPM NVRAM drive + */ +int tpm_nvram_init(BlockDriverState *bdrv) +{ + qemu_mutex_init(&tpm_nvram_rwrequests_mutex); + tpm_nvram_bh = qemu_bh_new(tpm_nvram_rwrequest_callback, NULL); + + if (bdrv_is_read_only(bdrv)) { + DPRINTF("%s: TPM NVRAM drive '%s' is read-only\n", __func__, + bdrv->filename); + return -EPERM; + } + + bdrv_lock_medium(bdrv, true); + + DPRINTF("%s: TPM NVRAM drive '%s' initialized successfully\n", __func__, + bdrv->filename); + + return 0; +} + +/* + * Read a TPM NVRAM blob from the drive + */ +int tpm_nvram_read(BlockDriverState *bdrv, uint64_t offset, + uint8_t **blob, uint32_t size) +{ + int rc; + TPMNvramRWRequest *rwr; + + *blob = NULL; + + rwr = tpm_nvram_rwrequest_init_read(bdrv, offset, blob, size); + tpm_nvram_rwrequest_schedule(rwr); + rc = rwr->rc; + + if (rc != rwr->size) { + DPRINTF("%s: TPM NVRAM read failed\n", __func__); + } else { + DPRINTF("%s: TPM NVRAM read successful: offset=%"PRIu64", " + "size=%"PRIu32"\n", __func__, rwr->offset, rwr->size); + } + + g_free(rwr); + return rc; +} + +/* + * Write a TPM NVRAM blob to the drive + */ +int tpm_nvram_write(BlockDriverState *bdrv, uint64_t offset, + uint8_t *blob, uint32_t size) +{ + int rc; + TPMNvramRWRequest *rwr; + + rwr = tpm_nvram_rwrequest_init_write(bdrv, offset, blob, size); + tpm_nvram_rwrequest_schedule(rwr); + rc = rwr->rc; + + if (rc != rwr->size) { + DPRINTF("%s: TPM NVRAM write failed\n", __func__); + } else { + DPRINTF("%s: TPM NVRAM write successful: offset=%"PRIu64", " + "size=%"PRIu32"\n", __func__, rwr->offset, rwr->size); + } + + g_free(rwr); + return rc; +} diff --git a/hw/tpm/tpm_nvram.h b/hw/tpm/tpm_nvram.h new file mode 100644 index 0000000..4b5bbe0 --- /dev/null +++ b/hw/tpm/tpm_nvram.h @@ -0,0 +1,25 @@ +/* + * TPM NVRAM - enables storage of persistent NVRAM data on an image file + * + * Copyright (C) 2013 IBM Corporation + * + * Authors: + * Stefan Berger + * Corey Bryant + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef TPM_TPM_NVRAM_H +#define TPM_TPM_NVRAM_H + +#include "block/block.h" + +int tpm_nvram_init(BlockDriverState *bdrv); +int tpm_nvram_read(BlockDriverState *bdrv, uint64_t offset, + uint8_t **blob, uint32_t size); +int tpm_nvram_write(BlockDriverState *bdrv, uint64_t offset, + uint8_t *blob, uint32_t size); + +#endif