| From 5e9dd9063f514447ea4f54046793f4f01c297ed4 Mon Sep 17 00:00:00 2001 |
| From: Stefan Berger <stefanb@linux.vnet.ibm.com> |
| Date: Sat, 31 Dec 2016 11:23:32 -0500 |
| Subject: [PATCH 4/4] Add support for VM suspend/resume for TPM TIS |
| |
| Extend the TPM TIS code to support suspend/resume. In case a command |
| is being processed by the external TPM when suspending, wait for the command |
| to complete to catch the result. In case the bottom half did not run, |
| run the one function the bottom half is supposed to run. This then |
| makes the resume operation work. |
| |
| The passthrough backend does not support suspend/resume operation |
| and is therefore blocked from suspend/resume and migration. |
| |
| The CUSE TPM's supported capabilities are tested and if sufficient |
| capabilities are implemented, suspend/resume, snapshotting and |
| migration are supported by the CUSE TPM. |
| |
| Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com> |
| |
| Upstream-Status: Pending [https://lists.nongnu.org/archive/html/qemu-devel/2016-06/msg00252.html] |
| Signed-off-by: Patrick Ohly <patrick.ohly@intel.com> |
| --- |
| hw/tpm/tpm_passthrough.c | 130 +++++++++++++++++++++++-- |
| hw/tpm/tpm_tis.c | 137 +++++++++++++++++++++++++- |
| hw/tpm/tpm_tis.h | 2 + |
| hw/tpm/tpm_util.c | 223 +++++++++++++++++++++++++++++++++++++++++++ |
| hw/tpm/tpm_util.h | 7 ++ |
| include/sysemu/tpm_backend.h | 12 +++ |
| 6 files changed, 503 insertions(+), 8 deletions(-) |
| |
| diff --git a/hw/tpm/tpm_passthrough.c b/hw/tpm/tpm_passthrough.c |
| index 44739ebad2..bc8072d0bc 100644 |
| --- a/hw/tpm/tpm_passthrough.c |
| +++ b/hw/tpm/tpm_passthrough.c |
| @@ -34,6 +34,8 @@ |
| #include "tpm_tis.h" |
| #include "tpm_util.h" |
| #include "tpm_ioctl.h" |
| +#include "migration/migration.h" |
| +#include "qapi/error.h" |
| |
| #define DEBUG_TPM 0 |
| |
| @@ -49,6 +51,7 @@ |
| #define TYPE_TPM_CUSE "tpm-cuse" |
| |
| static const TPMDriverOps tpm_passthrough_driver; |
| +static const VMStateDescription vmstate_tpm_cuse; |
| |
| /* data structures */ |
| typedef struct TPMPassthruThreadParams { |
| @@ -79,6 +82,10 @@ struct TPMPassthruState { |
| QemuMutex state_lock; |
| QemuCond cmd_complete; /* singnaled once tpm_busy is false */ |
| bool tpm_busy; |
| + |
| + Error *migration_blocker; |
| + |
| + TPMBlobBuffers tpm_blobs; |
| }; |
| |
| typedef struct TPMPassthruState TPMPassthruState; |
| @@ -306,6 +313,10 @@ static void tpm_passthrough_shutdown(TPMPassthruState *tpm_pt) |
| strerror(errno)); |
| } |
| } |
| + if (tpm_pt->migration_blocker) { |
| + migrate_del_blocker(tpm_pt->migration_blocker); |
| + error_free(tpm_pt->migration_blocker); |
| + } |
| } |
| |
| /* |
| @@ -360,12 +371,14 @@ static int tpm_passthrough_cuse_check_caps(TPMPassthruState *tpm_pt) |
| /* |
| * Initialize the external CUSE TPM |
| */ |
| -static int tpm_passthrough_cuse_init(TPMPassthruState *tpm_pt) |
| +static int tpm_passthrough_cuse_init(TPMPassthruState *tpm_pt, |
| + bool is_resume) |
| { |
| int rc = 0; |
| - ptm_init init = { |
| - .u.req.init_flags = PTM_INIT_FLAG_DELETE_VOLATILE, |
| - }; |
| + ptm_init init; |
| + if (is_resume) { |
| + init.u.req.init_flags = PTM_INIT_FLAG_DELETE_VOLATILE; |
| + } |
| |
| if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) { |
| if (ioctl(tpm_pt->tpm_fd, PTM_INIT, &init) < 0) { |
| @@ -394,7 +407,7 @@ static int tpm_passthrough_startup_tpm(TPMBackend *tb) |
| tpm_passthrough_worker_thread, |
| &tpm_pt->tpm_thread_params); |
| |
| - tpm_passthrough_cuse_init(tpm_pt); |
| + tpm_passthrough_cuse_init(tpm_pt, false); |
| |
| return 0; |
| } |
| @@ -466,6 +479,32 @@ static int tpm_passthrough_reset_tpm_established_flag(TPMBackend *tb, |
| return rc; |
| } |
| |
| +static int tpm_cuse_get_state_blobs(TPMBackend *tb, |
| + bool decrypted_blobs, |
| + TPMBlobBuffers *tpm_blobs) |
| +{ |
| + TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
| + |
| + assert(TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)); |
| + |
| + return tpm_util_cuse_get_state_blobs(tpm_pt->tpm_fd, decrypted_blobs, |
| + tpm_blobs); |
| +} |
| + |
| +static int tpm_cuse_set_state_blobs(TPMBackend *tb, |
| + TPMBlobBuffers *tpm_blobs) |
| +{ |
| + TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
| + |
| + assert(TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)); |
| + |
| + if (tpm_util_cuse_set_state_blobs(tpm_pt->tpm_fd, tpm_blobs)) { |
| + return 1; |
| + } |
| + |
| + return tpm_passthrough_cuse_init(tpm_pt, true); |
| +} |
| + |
| static bool tpm_passthrough_get_startup_error(TPMBackend *tb) |
| { |
| TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
| @@ -488,7 +527,7 @@ static void tpm_passthrough_deliver_request(TPMBackend *tb) |
| { |
| TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
| |
| - /* TPM considered busy once TPM Request scheduled for processing */ |
| + /* TPM considered busy once TPM request scheduled for processing */ |
| qemu_mutex_lock(&tpm_pt->state_lock); |
| tpm_pt->tpm_busy = true; |
| qemu_mutex_unlock(&tpm_pt->state_lock); |
| @@ -601,6 +640,25 @@ static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb) |
| return fd; |
| } |
| |
| +static void tpm_passthrough_block_migration(TPMPassthruState *tpm_pt) |
| +{ |
| + ptm_cap caps; |
| + |
| + if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) { |
| + caps = PTM_CAP_GET_STATEBLOB | PTM_CAP_SET_STATEBLOB | |
| + PTM_CAP_STOP; |
| + if (!TPM_CUSE_IMPLEMENTS_ALL(tpm_pt, caps)) { |
| + error_setg(&tpm_pt->migration_blocker, |
| + "Migration disabled: CUSE TPM lacks necessary capabilities"); |
| + migrate_add_blocker(tpm_pt->migration_blocker); |
| + } |
| + } else { |
| + error_setg(&tpm_pt->migration_blocker, |
| + "Migration disabled: Passthrough TPM does not support migration"); |
| + migrate_add_blocker(tpm_pt->migration_blocker); |
| + } |
| +} |
| + |
| static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb) |
| { |
| TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
| @@ -642,7 +700,7 @@ static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb) |
| goto err_close_tpmdev; |
| } |
| /* init TPM for probing */ |
| - if (tpm_passthrough_cuse_init(tpm_pt)) { |
| + if (tpm_passthrough_cuse_init(tpm_pt, false)) { |
| goto err_close_tpmdev; |
| } |
| } |
| @@ -659,6 +717,7 @@ static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb) |
| } |
| } |
| |
| + tpm_passthrough_block_migration(tpm_pt); |
| |
| return 0; |
| |
| @@ -766,10 +825,13 @@ static void tpm_passthrough_inst_init(Object *obj) |
| |
| qemu_mutex_init(&tpm_pt->state_lock); |
| qemu_cond_init(&tpm_pt->cmd_complete); |
| + |
| + vmstate_register(NULL, -1, &vmstate_tpm_cuse, obj); |
| } |
| |
| static void tpm_passthrough_inst_finalize(Object *obj) |
| { |
| + vmstate_unregister(NULL, &vmstate_tpm_cuse, obj); |
| } |
| |
| static void tpm_passthrough_class_init(ObjectClass *klass, void *data) |
| @@ -802,6 +864,60 @@ static const char *tpm_passthrough_cuse_create_desc(void) |
| return "CUSE TPM backend driver"; |
| } |
| |
| +static void tpm_cuse_pre_save(void *opaque) |
| +{ |
| + TPMPassthruState *tpm_pt = opaque; |
| + TPMBackend *tb = &tpm_pt->parent; |
| + |
| + qemu_mutex_lock(&tpm_pt->state_lock); |
| + /* wait for TPM to finish processing */ |
| + if (tpm_pt->tpm_busy) { |
| + qemu_cond_wait(&tpm_pt->cmd_complete, &tpm_pt->state_lock); |
| + } |
| + qemu_mutex_unlock(&tpm_pt->state_lock); |
| + |
| + /* get the decrypted state blobs from the TPM */ |
| + tpm_cuse_get_state_blobs(tb, TRUE, &tpm_pt->tpm_blobs); |
| +} |
| + |
| +static int tpm_cuse_post_load(void *opaque, |
| + int version_id __attribute__((unused))) |
| +{ |
| + TPMPassthruState *tpm_pt = opaque; |
| + TPMBackend *tb = &tpm_pt->parent; |
| + |
| + return tpm_cuse_set_state_blobs(tb, &tpm_pt->tpm_blobs); |
| +} |
| + |
| +static const VMStateDescription vmstate_tpm_cuse = { |
| + .name = "cuse-tpm", |
| + .version_id = 1, |
| + .minimum_version_id = 0, |
| + .minimum_version_id_old = 0, |
| + .pre_save = tpm_cuse_pre_save, |
| + .post_load = tpm_cuse_post_load, |
| + .fields = (VMStateField[]) { |
| + VMSTATE_UINT32(tpm_blobs.permanent_flags, TPMPassthruState), |
| + VMSTATE_UINT32(tpm_blobs.permanent.size, TPMPassthruState), |
| + VMSTATE_VBUFFER_ALLOC_UINT32(tpm_blobs.permanent.buffer, |
| + TPMPassthruState, 1, NULL, |
| + tpm_blobs.permanent.size), |
| + |
| + VMSTATE_UINT32(tpm_blobs.volatil_flags, TPMPassthruState), |
| + VMSTATE_UINT32(tpm_blobs.volatil.size, TPMPassthruState), |
| + VMSTATE_VBUFFER_ALLOC_UINT32(tpm_blobs.volatil.buffer, |
| + TPMPassthruState, 1, NULL, |
| + tpm_blobs.volatil.size), |
| + |
| + VMSTATE_UINT32(tpm_blobs.savestate_flags, TPMPassthruState), |
| + VMSTATE_UINT32(tpm_blobs.savestate.size, TPMPassthruState), |
| + VMSTATE_VBUFFER_ALLOC_UINT32(tpm_blobs.savestate.buffer, |
| + TPMPassthruState, 1, NULL, |
| + tpm_blobs.savestate.size), |
| + VMSTATE_END_OF_LIST() |
| + } |
| +}; |
| + |
| static const TPMDriverOps tpm_cuse_driver = { |
| .type = TPM_TYPE_CUSE_TPM, |
| .opts = tpm_passthrough_cmdline_opts, |
| diff --git a/hw/tpm/tpm_tis.c b/hw/tpm/tpm_tis.c |
| index 14d9e83ea2..9b660cf737 100644 |
| --- a/hw/tpm/tpm_tis.c |
| +++ b/hw/tpm/tpm_tis.c |
| @@ -368,6 +368,8 @@ static void tpm_tis_receive_bh(void *opaque) |
| TPMTISEmuState *tis = &s->s.tis; |
| uint8_t locty = s->locty_number; |
| |
| + tis->bh_scheduled = false; |
| + |
| qemu_mutex_lock(&s->state_lock); |
| |
| tpm_tis_sts_set(&tis->loc[locty], |
| @@ -415,6 +417,8 @@ static void tpm_tis_receive_cb(TPMState *s, uint8_t locty, |
| qemu_mutex_unlock(&s->state_lock); |
| |
| qemu_bh_schedule(tis->bh); |
| + |
| + tis->bh_scheduled = true; |
| } |
| |
| /* |
| @@ -1030,9 +1034,140 @@ static void tpm_tis_reset(DeviceState *dev) |
| tpm_tis_do_startup_tpm(s); |
| } |
| |
| + |
| +/* persistent state handling */ |
| + |
| +static void tpm_tis_pre_save(void *opaque) |
| +{ |
| + TPMState *s = opaque; |
| + TPMTISEmuState *tis = &s->s.tis; |
| + uint8_t locty = tis->active_locty; |
| + |
| + DPRINTF("tpm_tis: suspend: locty = %d : r_offset = %d, w_offset = %d\n", |
| + locty, tis->loc[0].r_offset, tis->loc[0].w_offset); |
| +#ifdef DEBUG_TIS |
| + tpm_tis_dump_state(opaque, 0); |
| +#endif |
| + |
| + qemu_mutex_lock(&s->state_lock); |
| + |
| + /* wait for outstanding request to complete */ |
| + if (TPM_TIS_IS_VALID_LOCTY(locty) && |
| + tis->loc[locty].state == TPM_TIS_STATE_EXECUTION) { |
| + /* |
| + * If we get here when the bh is scheduled but did not run, |
| + * we won't get notified... |
| + */ |
| + if (!tis->bh_scheduled) { |
| + /* backend thread to notify us */ |
| + qemu_cond_wait(&s->cmd_complete, &s->state_lock); |
| + } |
| + if (tis->loc[locty].state == TPM_TIS_STATE_EXECUTION) { |
| + /* bottom half did not run - run its function */ |
| + qemu_mutex_unlock(&s->state_lock); |
| + tpm_tis_receive_bh(opaque); |
| + qemu_mutex_lock(&s->state_lock); |
| + } |
| + } |
| + |
| + qemu_mutex_unlock(&s->state_lock); |
| + |
| + /* copy current active read or write buffer into the buffer |
| + written to disk */ |
| + if (TPM_TIS_IS_VALID_LOCTY(locty)) { |
| + switch (tis->loc[locty].state) { |
| + case TPM_TIS_STATE_RECEPTION: |
| + memcpy(tis->buf, |
| + tis->loc[locty].w_buffer.buffer, |
| + MIN(sizeof(tis->buf), |
| + tis->loc[locty].w_buffer.size)); |
| + tis->offset = tis->loc[locty].w_offset; |
| + break; |
| + case TPM_TIS_STATE_COMPLETION: |
| + memcpy(tis->buf, |
| + tis->loc[locty].r_buffer.buffer, |
| + MIN(sizeof(tis->buf), |
| + tis->loc[locty].r_buffer.size)); |
| + tis->offset = tis->loc[locty].r_offset; |
| + break; |
| + default: |
| + /* leak nothing */ |
| + memset(tis->buf, 0x0, sizeof(tis->buf)); |
| + break; |
| + } |
| + } |
| +} |
| + |
| +static int tpm_tis_post_load(void *opaque, |
| + int version_id __attribute__((unused))) |
| +{ |
| + TPMState *s = opaque; |
| + TPMTISEmuState *tis = &s->s.tis; |
| + |
| + uint8_t locty = tis->active_locty; |
| + |
| + if (TPM_TIS_IS_VALID_LOCTY(locty)) { |
| + switch (tis->loc[locty].state) { |
| + case TPM_TIS_STATE_RECEPTION: |
| + memcpy(tis->loc[locty].w_buffer.buffer, |
| + tis->buf, |
| + MIN(sizeof(tis->buf), |
| + tis->loc[locty].w_buffer.size)); |
| + tis->loc[locty].w_offset = tis->offset; |
| + break; |
| + case TPM_TIS_STATE_COMPLETION: |
| + memcpy(tis->loc[locty].r_buffer.buffer, |
| + tis->buf, |
| + MIN(sizeof(tis->buf), |
| + tis->loc[locty].r_buffer.size)); |
| + tis->loc[locty].r_offset = tis->offset; |
| + break; |
| + default: |
| + break; |
| + } |
| + } |
| + |
| + DPRINTF("tpm_tis: resume : locty = %d : r_offset = %d, w_offset = %d\n", |
| + locty, tis->loc[0].r_offset, tis->loc[0].w_offset); |
| + |
| + return 0; |
| +} |
| + |
| +static const VMStateDescription vmstate_locty = { |
| + .name = "loc", |
| + .version_id = 1, |
| + .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_UINT32(sts, TPMLocality), |
| + VMSTATE_UINT32(iface_id, TPMLocality), |
| + VMSTATE_END_OF_LIST(), |
| + } |
| +}; |
| + |
| static const VMStateDescription vmstate_tpm_tis = { |
| .name = "tpm", |
| - .unmigratable = 1, |
| + .version_id = 1, |
| + .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(s.tis.offset, TPMState), |
| + VMSTATE_BUFFER(s.tis.buf, TPMState), |
| + VMSTATE_UINT8(s.tis.active_locty, TPMState), |
| + VMSTATE_UINT8(s.tis.aborting_locty, TPMState), |
| + VMSTATE_UINT8(s.tis.next_locty, TPMState), |
| + |
| + VMSTATE_STRUCT_ARRAY(s.tis.loc, TPMState, TPM_TIS_NUM_LOCALITIES, 1, |
| + vmstate_locty, TPMLocality), |
| + |
| + VMSTATE_END_OF_LIST() |
| + } |
| }; |
| |
| static Property tpm_tis_properties[] = { |
| diff --git a/hw/tpm/tpm_tis.h b/hw/tpm/tpm_tis.h |
| index a1df41fa21..b7fc0ea1a9 100644 |
| --- a/hw/tpm/tpm_tis.h |
| +++ b/hw/tpm/tpm_tis.h |
| @@ -54,6 +54,8 @@ typedef struct TPMLocality { |
| |
| typedef struct TPMTISEmuState { |
| QEMUBH *bh; |
| + bool bh_scheduled; /* bh scheduled but did not run yet */ |
| + |
| uint32_t offset; |
| uint8_t buf[TPM_TIS_BUFFER_MAX]; |
| |
| diff --git a/hw/tpm/tpm_util.c b/hw/tpm/tpm_util.c |
| index 7b35429725..b6ff74d946 100644 |
| --- a/hw/tpm/tpm_util.c |
| +++ b/hw/tpm/tpm_util.c |
| @@ -22,6 +22,17 @@ |
| #include "qemu/osdep.h" |
| #include "tpm_util.h" |
| #include "tpm_int.h" |
| +#include "tpm_ioctl.h" |
| +#include "qemu/error-report.h" |
| + |
| +#define DEBUG_TPM 0 |
| + |
| +#define DPRINTF(fmt, ...) do { \ |
| + if (DEBUG_TPM) { \ |
| + fprintf(stderr, fmt, ## __VA_ARGS__); \ |
| + } \ |
| +} while (0) |
| + |
| |
| /* |
| * A basic test of a TPM device. We expect a well formatted response header |
| @@ -125,3 +136,215 @@ int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version) |
| |
| return 1; |
| } |
| + |
| +static void tpm_sized_buffer_reset(TPMSizedBuffer *tsb) |
| +{ |
| + g_free(tsb->buffer); |
| + tsb->buffer = NULL; |
| + tsb->size = 0; |
| +} |
| + |
| +/* |
| + * Transfer a TPM state blob from the TPM into a provided buffer. |
| + * |
| + * @fd: file descriptor to talk to the CUSE TPM |
| + * @type: the type of blob to transfer |
| + * @decrypted_blob: whether we request to receive decrypted blobs |
| + * @tsb: the TPMSizeBuffer to fill with the blob |
| + * @flags: the flags to return to the caller |
| + */ |
| +static int tpm_util_cuse_get_state_blob(int fd, |
| + uint8_t type, |
| + bool decrypted_blob, |
| + TPMSizedBuffer *tsb, |
| + uint32_t *flags) |
| +{ |
| + ptm_getstate pgs; |
| + uint16_t offset = 0; |
| + ptm_res res; |
| + ssize_t n; |
| + size_t to_read; |
| + |
| + tpm_sized_buffer_reset(tsb); |
| + |
| + pgs.u.req.state_flags = (decrypted_blob) ? PTM_STATE_FLAG_DECRYPTED : 0; |
| + pgs.u.req.type = type; |
| + pgs.u.req.offset = offset; |
| + |
| + if (ioctl(fd, PTM_GET_STATEBLOB, &pgs) < 0) { |
| + error_report("CUSE TPM PTM_GET_STATEBLOB ioctl failed: %s", |
| + strerror(errno)); |
| + goto err_exit; |
| + } |
| + res = pgs.u.resp.tpm_result; |
| + if (res != 0 && (res & 0x800) == 0) { |
| + error_report("Getting the stateblob (type %d) failed with a TPM " |
| + "error 0x%x", type, res); |
| + goto err_exit; |
| + } |
| + |
| + *flags = pgs.u.resp.state_flags; |
| + |
| + tsb->buffer = g_malloc(pgs.u.resp.totlength); |
| + memcpy(tsb->buffer, pgs.u.resp.data, pgs.u.resp.length); |
| + tsb->size = pgs.u.resp.length; |
| + |
| + /* if there are bytes left to get use read() interface */ |
| + while (tsb->size < pgs.u.resp.totlength) { |
| + to_read = pgs.u.resp.totlength - tsb->size; |
| + if (unlikely(to_read > SSIZE_MAX)) { |
| + to_read = SSIZE_MAX; |
| + } |
| + |
| + n = read(fd, &tsb->buffer[tsb->size], to_read); |
| + if (n != to_read) { |
| + error_report("Could not read stateblob (type %d) : %s", |
| + type, strerror(errno)); |
| + goto err_exit; |
| + } |
| + tsb->size += to_read; |
| + } |
| + |
| + DPRINTF("tpm_util: got state blob type %d, %d bytes, flags 0x%08x, " |
| + "decrypted=%d\n", type, tsb->size, *flags, decrypted_blob); |
| + |
| + return 0; |
| + |
| +err_exit: |
| + return 1; |
| +} |
| + |
| +int tpm_util_cuse_get_state_blobs(int tpm_fd, |
| + bool decrypted_blobs, |
| + TPMBlobBuffers *tpm_blobs) |
| +{ |
| + if (tpm_util_cuse_get_state_blob(tpm_fd, PTM_BLOB_TYPE_PERMANENT, |
| + decrypted_blobs, |
| + &tpm_blobs->permanent, |
| + &tpm_blobs->permanent_flags) || |
| + tpm_util_cuse_get_state_blob(tpm_fd, PTM_BLOB_TYPE_VOLATILE, |
| + decrypted_blobs, |
| + &tpm_blobs->volatil, |
| + &tpm_blobs->volatil_flags) || |
| + tpm_util_cuse_get_state_blob(tpm_fd, PTM_BLOB_TYPE_SAVESTATE, |
| + decrypted_blobs, |
| + &tpm_blobs->savestate, |
| + &tpm_blobs->savestate_flags)) { |
| + goto err_exit; |
| + } |
| + |
| + return 0; |
| + |
| + err_exit: |
| + tpm_sized_buffer_reset(&tpm_blobs->volatil); |
| + tpm_sized_buffer_reset(&tpm_blobs->permanent); |
| + tpm_sized_buffer_reset(&tpm_blobs->savestate); |
| + |
| + return 1; |
| +} |
| + |
| +static int tpm_util_cuse_do_set_stateblob_ioctl(int fd, |
| + uint32_t flags, |
| + uint32_t type, |
| + uint32_t length) |
| +{ |
| + ptm_setstate pss; |
| + |
| + pss.u.req.state_flags = flags; |
| + pss.u.req.type = type; |
| + pss.u.req.length = length; |
| + |
| + if (ioctl(fd, PTM_SET_STATEBLOB, &pss) < 0) { |
| + error_report("CUSE TPM PTM_SET_STATEBLOB ioctl failed: %s", |
| + strerror(errno)); |
| + return 1; |
| + } |
| + |
| + if (pss.u.resp.tpm_result != 0) { |
| + error_report("Setting the stateblob (type %d) failed with a TPM " |
| + "error 0x%x", type, pss.u.resp.tpm_result); |
| + return 1; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| + |
| +/* |
| + * Transfer a TPM state blob to the CUSE TPM. |
| + * |
| + * @fd: file descriptor to talk to the CUSE TPM |
| + * @type: the type of TPM state blob to transfer |
| + * @tsb: TPMSizeBuffer containing the TPM state blob |
| + * @flags: Flags describing the (encryption) state of the TPM state blob |
| + */ |
| +static int tpm_util_cuse_set_state_blob(int fd, |
| + uint32_t type, |
| + TPMSizedBuffer *tsb, |
| + uint32_t flags) |
| +{ |
| + uint32_t offset = 0; |
| + ssize_t n; |
| + size_t to_write; |
| + |
| + /* initiate the transfer to the CUSE TPM */ |
| + if (tpm_util_cuse_do_set_stateblob_ioctl(fd, flags, type, 0)) { |
| + return 1; |
| + } |
| + |
| + /* use the write() interface for transferring the state blob */ |
| + while (offset < tsb->size) { |
| + to_write = tsb->size - offset; |
| + if (unlikely(to_write > SSIZE_MAX)) { |
| + to_write = SSIZE_MAX; |
| + } |
| + |
| + n = write(fd, &tsb->buffer[offset], to_write); |
| + if (n != to_write) { |
| + error_report("Writing the stateblob (type %d) failed: %s", |
| + type, strerror(errno)); |
| + goto err_exit; |
| + } |
| + offset += to_write; |
| + } |
| + |
| + /* inidicate that the transfer is finished */ |
| + if (tpm_util_cuse_do_set_stateblob_ioctl(fd, flags, type, 0)) { |
| + goto err_exit; |
| + } |
| + |
| + DPRINTF("tpm_util: set the state blob type %d, %d bytes, flags 0x%08x\n", |
| + type, tsb->size, flags); |
| + |
| + return 0; |
| + |
| +err_exit: |
| + return 1; |
| +} |
| + |
| +int tpm_util_cuse_set_state_blobs(int tpm_fd, |
| + TPMBlobBuffers *tpm_blobs) |
| +{ |
| + ptm_res res; |
| + |
| + if (ioctl(tpm_fd, PTM_STOP, &res) < 0) { |
| + error_report("tpm_passthrough: Could not stop " |
| + "the CUSE TPM: %s (%i)", |
| + strerror(errno), errno); |
| + return 1; |
| + } |
| + |
| + if (tpm_util_cuse_set_state_blob(tpm_fd, PTM_BLOB_TYPE_PERMANENT, |
| + &tpm_blobs->permanent, |
| + tpm_blobs->permanent_flags) || |
| + tpm_util_cuse_set_state_blob(tpm_fd, PTM_BLOB_TYPE_VOLATILE, |
| + &tpm_blobs->volatil, |
| + tpm_blobs->volatil_flags) || |
| + tpm_util_cuse_set_state_blob(tpm_fd, PTM_BLOB_TYPE_SAVESTATE, |
| + &tpm_blobs->savestate, |
| + tpm_blobs->savestate_flags)) { |
| + return 1; |
| + } |
| + |
| + return 0; |
| +} |
| diff --git a/hw/tpm/tpm_util.h b/hw/tpm/tpm_util.h |
| index df76245e6e..c24071d812 100644 |
| --- a/hw/tpm/tpm_util.h |
| +++ b/hw/tpm/tpm_util.h |
| @@ -26,4 +26,11 @@ |
| |
| int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version); |
| |
| +int tpm_util_cuse_get_state_blobs(int tpm_fd, |
| + bool decrypted_blobs, |
| + TPMBlobBuffers *tpm_blobs); |
| + |
| +int tpm_util_cuse_set_state_blobs(int tpm_fd, |
| + TPMBlobBuffers *tpm_blobs); |
| + |
| #endif /* TPM_TPM_UTIL_H */ |
| diff --git a/include/sysemu/tpm_backend.h b/include/sysemu/tpm_backend.h |
| index b58f52d39f..3403821b9d 100644 |
| --- a/include/sysemu/tpm_backend.h |
| +++ b/include/sysemu/tpm_backend.h |
| @@ -62,6 +62,18 @@ typedef struct TPMSizedBuffer { |
| uint8_t *buffer; |
| } TPMSizedBuffer; |
| |
| +/* blobs from the TPM; part of VM state when migrating */ |
| +typedef struct TPMBlobBuffers { |
| + uint32_t permanent_flags; |
| + TPMSizedBuffer permanent; |
| + |
| + uint32_t volatil_flags; |
| + TPMSizedBuffer volatil; |
| + |
| + uint32_t savestate_flags; |
| + TPMSizedBuffer savestate; |
| +} TPMBlobBuffers; |
| + |
| struct TPMDriverOps { |
| enum TpmType type; |
| const QemuOptDesc *opts; |
| -- |
| 2.11.0 |
| |