All of lore.kernel.org
 help / color / mirror / Atom feed
From: Corey Bryant <coreyb@linux.vnet.ibm.com>
To: Stefan Hajnoczi <stefanha@redhat.com>
Cc: kwolf@redhat.com, aliguori@us.ibm.com,
	stefanb@linux.vnet.ibm.com, mdroth@linux.vnet.ibm.com,
	qemu-devel@nongnu.org, jschopp@linux.vnet.ibm.com
Subject: Re: [Qemu-devel] [PATCH 1/2] nvram: Add TPM NVRAM implementation
Date: Wed, 05 Jun 2013 09:28:32 -0400	[thread overview]
Message-ID: <51AF3D00.8020005@linux.vnet.ibm.com> (raw)
In-Reply-To: <20130605090536.GB27374@stefanha-thinkpad.muc.redhat.com>

Thanks for reviewing!

On 06/05/2013 05:05 AM, Stefan Hajnoczi wrote:
> On Tue, Jun 04, 2013 at 02:18:40PM -0400, Corey Bryant wrote:
>> 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 ecrypted 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 <coreyb@linux.vnet.ibm.com>
>> ---
>>   hw/tpm/Makefile.objs |    1 +
>>   hw/tpm/tpm_nvram.c   |  399 ++++++++++++++++++++++++++++++++++++++++++++++++++
>>   hw/tpm/tpm_nvram.h   |   25 +++
>>   3 files changed, 425 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..95ff396
>> --- /dev/null
>> +++ b/hw/tpm/tpm_nvram.c
>> @@ -0,0 +1,399 @@
>> +/*
>> + * TPM NVRAM - enables storage of persistent NVRAM data on an image file
>> + *
>> + * Copyright (C) 2013 IBM Corporation
>> + *
>> + * Authors:
>> + *  Stefan Berger    <stefanb@us.ibm.com>
>> + *  Corey Bryant     <coreyb@linux.vnet.ibm.com>
>> + *
>> + * 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 */
>> +
>> +#ifdef TPM_NVRAM_DEBUG
>> +#define DPRINTF(fmt, ...) \
>> +    do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
>> +#else
>> +#define DPRINTF(fmt, ...) \
>> +    do { } while (0)
>> +#endif
>
> I suggest:
>
> #define TPM_NVRAM_DEBUG 0
> #define DPRINTF(fmt, ...) \
>      do { \
>          if (TPM_NVRAM_DEBUG) { \
> 	    fprintf(stderr, fmt, ## __VA_ARGS__); \
> 	} \
>      } while (0)
>
> This approach prevents bitrot since the compiler always parses the
> printf() whether TPM_NVRAM_DEBUG is 0 or 1.  If you #ifdef out the code
> completely, like above, then you don't notice compiler warnings/errors
> until you actually #define TPM_NVRAM_DEBUG (i.e. prone to bitrot).
>

Ok

>> +
>> +/* Round a value up to the next SIZE */
>> +#define ROUNDUP(VAL, SIZE) \
>> +    (((VAL)+(SIZE)-1) & ~((SIZE)-1))
>
> Please drop this macro and use include/qemu/osdep.h:ROUND_UP()
>

Ok

>> +
>> +/* Get the number of sectors required to contain SIZE bytes */
>> +#define NUM_SECTORS(SIZE) \
>> +    (ROUNDUP(SIZE, BDRV_SECTOR_SIZE) / BDRV_SECTOR_SIZE)
>
> Please drop this macro and use include/qemu/osdep.h:DIV_ROUND_UP() instead.
>

Ok

>> +
>> +/* Read/write request data */
>> +typedef struct TPMNvramRWRequest {
>> +    BlockDriverState *bdrv;
>> +    bool is_write;
>> +    uint64_t sector_num;
>> +    int num_sectors;
>> +    uint8_t **blob_r;
>> +    uint8_t *blob_w;
>> +    uint32_t size;
>> +    QEMUIOVector *qiov;
>> +    bool done;
>> +    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;
>> +
>> +/*
>> + * Increase the drive size if it's too small to store the blob
>> + */
>> +static int tpm_nvram_adjust_size(BlockDriverState *bdrv, uint64_t sector_num,
>> +                                 int num_sectors)
>> +{
>> +    int rc = 0;
>> +    int64_t drive_size, required_size;
>> +
>> +    drive_size = bdrv_getlength(bdrv);
>> +    if (drive_size < 0) {
>> +        DPRINTF("%s: Unable to determine TPM NVRAM drive size\n", __func__);
>> +        rc = drive_size;
>> +        goto err_exit;
>> +    }
>> +
>> +    required_size = (sector_num + num_sectors) * BDRV_SECTOR_SIZE;
>> +
>> +    if (drive_size < required_size) {
>> +        rc = bdrv_truncate(bdrv, required_size);
>> +        if (rc < 0) {
>> +            DPRINTF("%s: TPM NVRAM drive too small\n", __func__);
>> +        }
>> +    }
>> +
>> +err_exit:
>> +    return rc;
>> +}
>> +
>> +/*
>> + * Coroutine that reads a blob from the drive asynchronously
>> + */
>> +static void coroutine_fn tpm_nvram_co_read(void *opaque)
>> +{
>> +    TPMNvramRWRequest *rwr = opaque;
>> +
>> +    rwr->rc = bdrv_co_readv(rwr->bdrv,
>> +                            rwr->sector_num,
>> +                            rwr->num_sectors,
>> +                            rwr->qiov);
>> +    rwr->done = true;
>> +}
>> +
>> +/*
>> + * Coroutine that writes a blob to the drive asynchronously
>> + */
>> +static void coroutine_fn tpm_nvram_co_write(void *opaque)
>> +{
>> +    TPMNvramRWRequest *rwr = opaque;
>> +
>> +    rwr->rc = bdrv_co_writev(rwr->bdrv,
>> +                             rwr->sector_num,
>> +                             rwr->num_sectors,
>> +                             rwr->qiov);
>> +    rwr->done = true;
>> +}
>> +
>> +/*
>> + * Prepare for and enter a coroutine to read a blob from the drive
>> + */
>> +static void epm_nvram_do_co_read(TPMNvramRWRequest *rwr)
>> +{
>> +    Coroutine *co;
>> +    size_t buf_len = rwr->num_sectors * BDRV_SECTOR_SIZE;
>> +    uint8_t *buf = g_malloc(buf_len);
>> +
>> +    memset(buf, 0x0, buf_len);
>> +
>> +    struct iovec iov = {
>> +        .iov_base = (void *)buf,
>> +        .iov_len = rwr->size,
>> +    };
>> +
>> +    qemu_iovec_init_external(rwr->qiov, &iov, 1);
>> +
>> +    co = qemu_coroutine_create(tpm_nvram_co_read);
>> +    qemu_coroutine_enter(co, rwr);
>> +
>> +    while (!rwr->done) {
>> +        qemu_aio_wait();
>> +    }
>
> The qemu_aio_wait() loop makes this function synchronous - it will block
> the current thread until the request completes.
>
> You need to use bdrv_aio_readv()/bdrv_aio_writev() or let the
> tpm_nvram_co_read()/tpm_nvram_co_write() coroutine perform the
> completion code path instead of waiting for it here.
>

Ok, I think I can just have the coroutine perform the completion path.

>> +
>> +    if (rwr->rc == 0) {
>> +        rwr->rc = rwr->num_sectors;
>> +        *rwr->blob_r = g_malloc(rwr->size);
>> +        memcpy(*rwr->blob_r, buf, rwr->size);
>
> Use bdrv_pread()/bdrv_pwrite() for byte-granularity I/O instead of
> duplicating the buffering yourself.
>

Aren't bdrv_pread()/bdrv_pwrite() synchronous?  Wouldn't using them 
block the main QEMU thread?  That is why I switched to using the 
coroutine versions.

>> +    } else {
>> +        *rwr->blob_r = NULL;
>> +    }
>> +
>> +    g_free(buf);
>> +}
>> +
>> +/*
>> + * Prepare for and enter a coroutine to write a blob to the drive
>> + */
>> +static void tpm_nvram_do_co_write(TPMNvramRWRequest *rwr)
>> +{
>> +    Coroutine *co;
>> +    size_t buf_len = rwr->num_sectors * BDRV_SECTOR_SIZE;
>> +    uint8_t *buf = g_malloc(buf_len);
>> +
>> +    memset(buf, 0x0, buf_len);
>> +    memcpy(buf, rwr->blob_w, rwr->size);
>> +
>> +    struct iovec iov = {
>> +        .iov_base = (void *)buf,
>> +        .iov_len = rwr->size,
>> +    };
>> +
>> +    qemu_iovec_init_external(rwr->qiov, &iov, 1);
>> +
>> +    rwr->rc = tpm_nvram_adjust_size(rwr->bdrv, rwr->sector_num,
>> +                                    rwr->num_sectors);
>> +    if (rwr->rc < 0) {
>> +        goto err_exit;
>> +    }
>> +
>> +    co = qemu_coroutine_create(tpm_nvram_co_write);
>> +    qemu_coroutine_enter(co, rwr);
>> +
>> +    while (!rwr->done) {
>> +        qemu_aio_wait();
>> +    }
>> +
>> +    if (rwr->rc == 0) {
>> +        rwr->rc = rwr->num_sectors;
>> +    }
>> +
>> +err_exit:
>> +    g_free(buf);
>> +}
>> +
>> +/*
>> + * Initialization for read requests
>> + */
>> +static TPMNvramRWRequest *tpm_nvram_rwrequest_init_read(BlockDriverState *bdrv,
>> +                                                        int64_t sector_num,
>> +                                                        uint8_t **blob,
>> +                                                        uint32_t size)
>> +{
>> +    TPMNvramRWRequest *rwr;
>> +
>> +    rwr = g_new0(TPMNvramRWRequest, 1);
>> +    rwr->bdrv = bdrv;
>> +    rwr->is_write = false;
>> +    rwr->sector_num = sector_num;
>> +    rwr->num_sectors = NUM_SECTORS(size);
>> +    rwr->blob_r = blob;
>> +    rwr->size = size;
>> +    rwr->qiov = g_new0(QEMUIOVector, 1);
>
> Why allocate qiov on the heap instead of making it a TPMNvramRWRequest field?
>

No reason, I'll change that.

>> +    rwr->done = false;
>> +
>> +    return rwr;
>> +}
>> +
>> +/*
>> + * Initialization for write requests
>> + */
>> +static TPMNvramRWRequest *tpm_nvram_rwrequest_init_write(BlockDriverState *bdrv,
>> +                                                         int64_t sector_num,
>> +                                                         uint8_t *blob,
>> +                                                         uint32_t size)
>> +{
>> +    TPMNvramRWRequest *rwr;
>> +
>> +    rwr = g_new0(TPMNvramRWRequest, 1);
>> +    rwr->bdrv = bdrv;
>> +    rwr->is_write = true;
>> +    rwr->sector_num = sector_num;
>> +    rwr->num_sectors = NUM_SECTORS(size);
>> +    rwr->blob_w = blob;
>> +    rwr->size = size;
>> +    rwr->qiov = g_new0(QEMUIOVector, 1);
>> +    rwr->done = false;
>> +
>> +    return rwr;
>> +}
>> +
>> +/*
>> + * Free read/write request memory
>> + */
>> +static void tpm_nvram_rwrequest_free(TPMNvramRWRequest *rwr)
>> +{
>> +    g_free(rwr->qiov);
>> +    g_free(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);
>> +    }
>> +
>> +    qemu_mutex_lock(&rwr->completion_mutex);
>> +    qemu_cond_signal(&rwr->completion);
>> +    qemu_mutex_unlock(&rwr->completion_mutex);
>> +}
>> +
>> +/*
>> + * 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);
>> +    qemu_cond_wait(&rwr->completion, &rwr->completion_mutex);
>> +    qemu_mutex_unlock(&rwr->completion_mutex);
>
> Race condition: what if the request completes before we reach
> qemu_cond_wait()?  I suggest initializing rwr->rc to -EINPROGRESS and
> doing:
>
> qemu_mutex_lock(&rwr->completion_mutex);
> while (rwr->rc == -EINPROGRESS) {
>      qemu_cond_wait(&rwr->completion, &rwr->completion_mutex);
> }
> qemu_mutex_unlock(&rwr->completion_mutex);
>

Good catch.  Thanks.

>> +}
>> +
>> +/*
>> + * 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.  On success, returns the
>> + * number of sectors used by this blob.
>> + */
>> +int tpm_nvram_read(BlockDriverState *bdrv, int64_t sector_num,
>> +                   uint8_t **blob, uint32_t size)
>> +{
>> +    int rc;
>> +    TPMNvramRWRequest *rwr;
>> +
>> +    *blob = NULL;
>> +
>> +    if (!bdrv) {
>> +        return -EPERM;
>> +    }
>> +
>> +    if (sector_num < 0) {
>> +        return -EINVAL;
>> +    }
>
> Can you let block.c handle these checks to avoid duplication?
>

Most likely I can remove these.

>> +
>> +    rwr = tpm_nvram_rwrequest_init_read(bdrv, sector_num, blob, size);
>> +    tpm_nvram_rwrequest_schedule(rwr);
>> +    rc = rwr->rc;
>> +
>> +#ifdef TPM_NVRAM_DEBUG
>> +    if (rc < 0) {
>> +        DPRINTF("%s: TPM NVRAM read failed\n", __func__);
>> +    } else {
>> +        DPRINTF("%s: TPM NVRAM read successful: sector_num=%"PRIu64", "
>> +                "size=%"PRIu32", num_sectors=%d\n", __func__,
>> +                rwr->sector_num, rwr->size, rwr->num_sectors);
>> +    }
>> +#endif
>
> #ifdef is unnecessary here.  The compiler can drop deadcode.
>

Ok

>> +
>> +    tpm_nvram_rwrequest_free(rwr);
>> +    return rc;
>> +}
>> +
>> +/*
>> + * Write a TPM NVRAM blob to the drive.  On success, returns the
>> + * number of sectors used by this blob.
>> + */
>> +int tpm_nvram_write(BlockDriverState *bdrv, int64_t sector_num,
>> +                    uint8_t *blob, uint32_t size)
>> +{
>> +    int rc;
>> +    TPMNvramRWRequest *rwr;
>> +
>> +    if (!bdrv) {
>> +        return -EPERM;
>> +    }
>> +
>> +    if (sector_num < 0 || !blob) {
>> +        return -EINVAL;
>> +    }
>> +
>> +    rwr = tpm_nvram_rwrequest_init_write(bdrv, sector_num, blob, size);
>> +    tpm_nvram_rwrequest_schedule(rwr);
>> +    rc = rwr->rc;
>> +
>> +#ifdef TPM_NVRAM_DEBUG
>> +    if (rc < 0) {
>> +        DPRINTF("%s: TPM NVRAM write failed\n", __func__);
>> +    } else {
>> +        DPRINTF("%s: TPM NVRAM write successful: sector_num=%"PRIu64", "
>> +                "size=%"PRIu32", num_sectors=%d\n", __func__,
>> +                rwr->sector_num, rwr->size, rwr->num_sectors);
>> +    }
>> +#endif
>> +
>> +    tpm_nvram_rwrequest_free(rwr);
>> +    return rc;
>> +}
>> diff --git a/hw/tpm/tpm_nvram.h b/hw/tpm/tpm_nvram.h
>> new file mode 100644
>> index 0000000..fceb4d0
>> --- /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    <stefanb@us.ibm.com>
>> + *  Corey Bryant     <coreyb@linux.vnet.ibm.com>
>> + *
>> + * 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, int64_t sector_num,
>> +                   uint8_t **blob, uint32_t size);
>> +int tpm_nvram_write(BlockDriverState *bdrv, int64_t sector_num,
>> +                    uint8_t *blob, uint32_t size);
>> +
>> +#endif
>> --
>> 1.7.1
>>
>
>
>

-- 
Regards,
Corey Bryant

  reply	other threads:[~2013-06-05 13:29 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-06-04 18:18 [Qemu-devel] [PATCH 0/2] TPM NVRAM persistent storage Corey Bryant
2013-06-04 18:18 ` [Qemu-devel] [PATCH 1/2] nvram: Add TPM NVRAM implementation Corey Bryant
2013-06-05  9:05   ` Stefan Hajnoczi
2013-06-05 13:28     ` Corey Bryant [this message]
2013-06-05 13:42       ` Kevin Wolf
2013-06-05 13:57         ` Corey Bryant
2013-06-04 18:18 ` [Qemu-devel] [PATCH 2/2] nvram: Add tpm-tis drive support Corey Bryant
2013-06-04 19:23 ` [Qemu-devel] [PATCH 0/2] TPM NVRAM persistent storage Eric Blake
2013-06-04 19:35   ` Corey Bryant
2013-06-04 19:45     ` Eric Blake

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=51AF3D00.8020005@linux.vnet.ibm.com \
    --to=coreyb@linux.vnet.ibm.com \
    --cc=aliguori@us.ibm.com \
    --cc=jschopp@linux.vnet.ibm.com \
    --cc=kwolf@redhat.com \
    --cc=mdroth@linux.vnet.ibm.com \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanb@linux.vnet.ibm.com \
    --cc=stefanha@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.