From 1264d26fa251ac11a9069f3e602dec6be9d8b9ba Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Fri, 20 Jan 2017 08:51:07 +0100 Subject: qemu: support virtual TPM This enables the use of swtpm (from meta-security) as a virtual TPM in qemu. These patches extend the existing support in qemu for TPM passthrough so that a swtpm daemon can be accessed via CUSE (character device in user space). To use this: - add the meta-security layer including the swtpm enhancements for qemu - bitbake swtpm-native - create a TPM instance and initialize it with: $ mkdir -p my-machine/myvtpm0 $ tmp-glibc/sysroots/x86_64-linux/usr/bin/swtpm_setup_oe.sh --tpm-state my-machine/myvtpm0 --createek Starting vTPM manufacturing as root:root @ Fri 20 Jan 2017 08:56:18 AM CET TPM is listening on TCP port 52167. Successfully created EK. Successfully authored TPM state. Ending vTPM manufacturing @ Fri 20 Jan 2017 08:56:19 AM CET - run swtpm *before each runqemu invocation* (it shuts down after use) and do it as root (required to set up the /dev/vtpm0 CUSE device): $ sudo sh -c 'PATH=`pwd`/tmp-glibc/sysroots/x86_64-linux/usr/bin/:`pwd`/tmp-glibc/sysroots/x86_64-linux/usr/sbin/:$PATH; export TPM_PATH=`pwd`/my-machine/myvtpm0; swtpm_cuse -n vtpm0' && sudo chmod a+rw /dev/vtpm0 - run qemu: $ runqemu 'qemuparams=-tpmdev cuse-tpm,id=tpm0,path=/dev/vtpm0 -device tpm-tis,tpmdev=tpm0' ... The guest kernel has to have TPM support enabled, which can be done with: KERNEL_FEATURES_append = " features/tpm/tpm.scc" Signed-off-by: Patrick Ohly --- .../0001-Provide-support-for-the-CUSE-TPM.patch | 870 +++++++++++++++++++++ ...ondition-to-notify-waiters-of-completed-c.patch | 86 ++ ...condition-in-TPM-backend-for-notification.patch | 79 ++ ...support-for-VM-suspend-resume-for-TPM-TIS.patch | 719 +++++++++++++++++ meta/recipes-devtools/qemu/qemu_2.8.0.bb | 7 + 5 files changed, 1761 insertions(+) create mode 100644 meta/recipes-devtools/qemu/qemu/0001-Provide-support-for-the-CUSE-TPM.patch create mode 100644 meta/recipes-devtools/qemu/qemu/0002-Introduce-condition-to-notify-waiters-of-completed-c.patch create mode 100644 meta/recipes-devtools/qemu/qemu/0003-Introduce-condition-in-TPM-backend-for-notification.patch create mode 100644 meta/recipes-devtools/qemu/qemu/0004-Add-support-for-VM-suspend-resume-for-TPM-TIS.patch (limited to 'meta/recipes-devtools/qemu') diff --git a/meta/recipes-devtools/qemu/qemu/0001-Provide-support-for-the-CUSE-TPM.patch b/meta/recipes-devtools/qemu/qemu/0001-Provide-support-for-the-CUSE-TPM.patch new file mode 100644 index 0000000000..74dc6f5df8 --- /dev/null +++ b/meta/recipes-devtools/qemu/qemu/0001-Provide-support-for-the-CUSE-TPM.patch @@ -0,0 +1,870 @@ +From 8737eef18f39ed087fd911d0a0886e8174d0468c Mon Sep 17 00:00:00 2001 +From: Stefan Berger +Date: Sat, 31 Dec 2016 11:23:32 -0500 +Subject: [PATCH 1/4] Provide support for the CUSE TPM + +Rather than integrating TPM functionality into QEMU directly +using the TPM emulation of libtpms, we now integrate an external +emulated TPM device. This device is expected to implement a Linux +CUSE interface (CUSE = character device in userspace). + +QEMU talks to the CUSE TPM using much functionality of the +passthrough driver. For example, the TPM commands and responses +are sent to the CUSE TPM using the read()/write() interface. +However, some out-of-band control needs to be done using the CUSE +TPM's ioctls. The CUSE TPM currently defines and implements 15 +different ioctls for controlling certain life-cycle aspects of +the emulated TPM. The ioctls can be regarded as a replacement for +direct function calls to a TPM emulator if the TPM were to be +directly integrated into QEMU. + +One of the ioctls allows to get a bitmask of supported capabilities. +Each returned bit indicates which capabilities have been implemented. +An include file defining the various ioctls is added to QEMU. + +The CUSE TPM and associated tools can be found here: + +https://github.com/stefanberger/swtpm + +(please use the latest version) + +To use the external CUSE TPM, the CUSE TPM should be started as follows: + +/usr/bin/swtpm_ioctl -s /dev/vtpm-test + +/usr/bin/swtpm_cuse -n vtpm-test + +QEMU can then be started using the following parameters: + +qemu-system-x86_64 \ + [...] \ + -tpmdev cuse-tpm,id=tpm0,cancel-path=/dev/null,path=/dev/vtpm-test \ + -device tpm-tis,id=tpm0,tpmdev=tpm0 \ + [...] + +Signed-off-by: Stefan Berger +Cc: Eric Blake + +Conflicts: + docs/qmp-commands.txt + +Patch cherry-picked from https://github.com/stefanberger/qemu-tpm, branch v2.8.0+tpm, +commit 27d6cd856d5a14061955df7a93ee490697a7a174. Applied cleanly except for +docs/qmp-commands.txt which did not exist yet in qemu 2.7. + +Upstream-Status: Pending [https://lists.nongnu.org/archive/html/qemu-devel/2016-06/msg00252.html] +Signed-off-by: Patrick Ohly +--- + hmp.c | 6 ++ + hw/tpm/tpm_int.h | 1 + + hw/tpm/tpm_ioctl.h | 215 +++++++++++++++++++++++++++++++++++++ + hw/tpm/tpm_passthrough.c | 274 +++++++++++++++++++++++++++++++++++++++++++++-- + qapi-schema.json | 18 +++- + qemu-options.hx | 21 +++- + tpm.c | 11 +- + 7 files changed, 529 insertions(+), 17 deletions(-) + create mode 100644 hw/tpm/tpm_ioctl.h + +diff --git a/hmp.c b/hmp.c +index cc2056e9e2..277b45ef5a 100644 +--- a/hmp.c ++++ b/hmp.c +@@ -883,6 +883,12 @@ void hmp_info_tpm(Monitor *mon, const QDict *qdict) + tpo->has_cancel_path ? ",cancel-path=" : "", + tpo->has_cancel_path ? tpo->cancel_path : ""); + break; ++ case TPM_TYPE_OPTIONS_KIND_CUSE_TPM: ++ tpo = ti->options->u.passthrough.data; ++ monitor_printf(mon, "%s%s", ++ tpo->has_path ? ",path=" : "", ++ tpo->has_path ? tpo->path : ""); ++ break; + case TPM_TYPE_OPTIONS_KIND__MAX: + break; + } +diff --git a/hw/tpm/tpm_int.h b/hw/tpm/tpm_int.h +index f2f285b3cc..6b2c9c953a 100644 +--- a/hw/tpm/tpm_int.h ++++ b/hw/tpm/tpm_int.h +@@ -61,6 +61,7 @@ struct tpm_resp_hdr { + #define TPM_TAG_RSP_AUTH1_COMMAND 0xc5 + #define TPM_TAG_RSP_AUTH2_COMMAND 0xc6 + ++#define TPM_SUCCESS 0 + #define TPM_FAIL 9 + + #define TPM_ORD_ContinueSelfTest 0x53 +diff --git a/hw/tpm/tpm_ioctl.h b/hw/tpm/tpm_ioctl.h +new file mode 100644 +index 0000000000..a341e15741 +--- /dev/null ++++ b/hw/tpm/tpm_ioctl.h +@@ -0,0 +1,215 @@ ++/* ++ * tpm_ioctl.h ++ * ++ * (c) Copyright IBM Corporation 2014, 2015. ++ * ++ * This file is licensed under the terms of the 3-clause BSD license ++ */ ++#ifndef _TPM_IOCTL_H_ ++#define _TPM_IOCTL_H_ ++ ++#include ++#include ++#include ++#include ++ ++/* ++ * Every response from a command involving a TPM command execution must hold ++ * the ptm_res as the first element. ++ * ptm_res corresponds to the error code of a command executed by the TPM. ++ */ ++ ++typedef uint32_t ptm_res; ++ ++/* PTM_GET_TPMESTABLISHED: get the establishment bit */ ++struct ptm_est { ++ union { ++ struct { ++ ptm_res tpm_result; ++ unsigned char bit; /* TPM established bit */ ++ } resp; /* response */ ++ } u; ++}; ++ ++/* PTM_RESET_TPMESTABLISHED: reset establishment bit */ ++struct ptm_reset_est { ++ union { ++ struct { ++ uint8_t loc; /* locality to use */ ++ } req; /* request */ ++ struct { ++ ptm_res tpm_result; ++ } resp; /* response */ ++ } u; ++}; ++ ++/* PTM_INIT */ ++struct ptm_init { ++ union { ++ struct { ++ uint32_t init_flags; /* see definitions below */ ++ } req; /* request */ ++ struct { ++ ptm_res tpm_result; ++ } resp; /* response */ ++ } u; ++}; ++ ++/* above init_flags */ ++#define PTM_INIT_FLAG_DELETE_VOLATILE (1 << 0) ++ /* delete volatile state file after reading it */ ++ ++/* PTM_SET_LOCALITY */ ++struct ptm_loc { ++ union { ++ struct { ++ uint8_t loc; /* locality to set */ ++ } req; /* request */ ++ struct { ++ ptm_res tpm_result; ++ } resp; /* response */ ++ } u; ++}; ++ ++/* PTM_HASH_DATA: hash given data */ ++struct ptm_hdata { ++ union { ++ struct { ++ uint32_t length; ++ uint8_t data[4096]; ++ } req; /* request */ ++ struct { ++ ptm_res tpm_result; ++ } resp; /* response */ ++ } u; ++}; ++ ++/* ++ * size of the TPM state blob to transfer; x86_64 can handle 8k, ++ * ppc64le only ~7k; keep the response below a 4k page size ++ */ ++#define PTM_STATE_BLOB_SIZE (3 * 1024) ++ ++/* ++ * The following is the data structure to get state blobs from the TPM. ++ * If the size of the state blob exceeds the PTM_STATE_BLOB_SIZE, multiple reads ++ * with this ioctl and with adjusted offset are necessary. All bytes ++ * must be transferred and the transfer is done once the last byte has been ++ * returned. ++ * It is possible to use the read() interface for reading the data; however, ++ * the first bytes of the state blob will be part of the response to the ioctl(); ++ * a subsequent read() is only necessary if the total length (totlength) exceeds ++ * the number of received bytes. seek() is not supported. ++ */ ++struct ptm_getstate { ++ union { ++ struct { ++ uint32_t state_flags; /* may be: PTM_STATE_FLAG_DECRYPTED */ ++ uint32_t type; /* which blob to pull */ ++ uint32_t offset; /* offset from where to read */ ++ } req; /* request */ ++ struct { ++ ptm_res tpm_result; ++ uint32_t state_flags; /* may be: PTM_STATE_FLAG_ENCRYPTED */ ++ uint32_t totlength; /* total length that will be transferred */ ++ uint32_t length; /* number of bytes in following buffer */ ++ uint8_t data[PTM_STATE_BLOB_SIZE]; ++ } resp; /* response */ ++ } u; ++}; ++ ++/* TPM state blob types */ ++#define PTM_BLOB_TYPE_PERMANENT 1 ++#define PTM_BLOB_TYPE_VOLATILE 2 ++#define PTM_BLOB_TYPE_SAVESTATE 3 ++ ++/* state_flags above : */ ++#define PTM_STATE_FLAG_DECRYPTED 1 /* on input: get decrypted state */ ++#define PTM_STATE_FLAG_ENCRYPTED 2 /* on output: state is encrypted */ ++ ++/* ++ * The following is the data structure to set state blobs in the TPM. ++ * If the size of the state blob exceeds the PTM_STATE_BLOB_SIZE, multiple ++ * 'writes' using this ioctl are necessary. The last packet is indicated ++ * by the length being smaller than the PTM_STATE_BLOB_SIZE. ++ * The very first packet may have a length indicator of '0' enabling ++ * a write() with all the bytes from a buffer. If the write() interface ++ * is used, a final ioctl with a non-full buffer must be made to indicate ++ * that all data were transferred (a write with 0 bytes would not work). ++ */ ++struct ptm_setstate { ++ union { ++ struct { ++ uint32_t state_flags; /* may be PTM_STATE_FLAG_ENCRYPTED */ ++ uint32_t type; /* which blob to set */ ++ uint32_t length; /* length of the data; ++ use 0 on the first packet to ++ transfer using write() */ ++ uint8_t data[PTM_STATE_BLOB_SIZE]; ++ } req; /* request */ ++ struct { ++ ptm_res tpm_result; ++ } resp; /* response */ ++ } u; ++}; ++ ++/* ++ * PTM_GET_CONFIG: Data structure to get runtime configuration information ++ * such as which keys are applied. ++ */ ++struct ptm_getconfig { ++ union { ++ struct { ++ ptm_res tpm_result; ++ uint32_t flags; ++ } resp; /* response */ ++ } u; ++}; ++ ++#define PTM_CONFIG_FLAG_FILE_KEY 0x1 ++#define PTM_CONFIG_FLAG_MIGRATION_KEY 0x2 ++ ++ ++typedef uint64_t ptm_cap; ++typedef struct ptm_est ptm_est; ++typedef struct ptm_reset_est ptm_reset_est; ++typedef struct ptm_loc ptm_loc; ++typedef struct ptm_hdata ptm_hdata; ++typedef struct ptm_init ptm_init; ++typedef struct ptm_getstate ptm_getstate; ++typedef struct ptm_setstate ptm_setstate; ++typedef struct ptm_getconfig ptm_getconfig; ++ ++/* capability flags returned by PTM_GET_CAPABILITY */ ++#define PTM_CAP_INIT (1) ++#define PTM_CAP_SHUTDOWN (1<<1) ++#define PTM_CAP_GET_TPMESTABLISHED (1<<2) ++#define PTM_CAP_SET_LOCALITY (1<<3) ++#define PTM_CAP_HASHING (1<<4) ++#define PTM_CAP_CANCEL_TPM_CMD (1<<5) ++#define PTM_CAP_STORE_VOLATILE (1<<6) ++#define PTM_CAP_RESET_TPMESTABLISHED (1<<7) ++#define PTM_CAP_GET_STATEBLOB (1<<8) ++#define PTM_CAP_SET_STATEBLOB (1<<9) ++#define PTM_CAP_STOP (1<<10) ++#define PTM_CAP_GET_CONFIG (1<<11) ++ ++enum { ++ PTM_GET_CAPABILITY = _IOR('P', 0, ptm_cap), ++ PTM_INIT = _IOWR('P', 1, ptm_init), ++ PTM_SHUTDOWN = _IOR('P', 2, ptm_res), ++ PTM_GET_TPMESTABLISHED = _IOR('P', 3, ptm_est), ++ PTM_SET_LOCALITY = _IOWR('P', 4, ptm_loc), ++ PTM_HASH_START = _IOR('P', 5, ptm_res), ++ PTM_HASH_DATA = _IOWR('P', 6, ptm_hdata), ++ PTM_HASH_END = _IOR('P', 7, ptm_res), ++ PTM_CANCEL_TPM_CMD = _IOR('P', 8, ptm_res), ++ PTM_STORE_VOLATILE = _IOR('P', 9, ptm_res), ++ PTM_RESET_TPMESTABLISHED = _IOWR('P', 10, ptm_reset_est), ++ PTM_GET_STATEBLOB = _IOWR('P', 11, ptm_getstate), ++ PTM_SET_STATEBLOB = _IOWR('P', 12, ptm_setstate), ++ PTM_STOP = _IOR('P', 13, ptm_res), ++ PTM_GET_CONFIG = _IOR('P', 14, ptm_getconfig), ++}; ++ ++#endif /* _TPM_IOCTL_H */ +diff --git a/hw/tpm/tpm_passthrough.c b/hw/tpm/tpm_passthrough.c +index e88c0d20bc..050f2ba850 100644 +--- a/hw/tpm/tpm_passthrough.c ++++ b/hw/tpm/tpm_passthrough.c +@@ -33,6 +33,7 @@ + #include "sysemu/tpm_backend_int.h" + #include "tpm_tis.h" + #include "tpm_util.h" ++#include "tpm_ioctl.h" + + #define DEBUG_TPM 0 + +@@ -45,6 +46,7 @@ + #define TYPE_TPM_PASSTHROUGH "tpm-passthrough" + #define TPM_PASSTHROUGH(obj) \ + OBJECT_CHECK(TPMPassthruState, (obj), TYPE_TPM_PASSTHROUGH) ++#define TYPE_TPM_CUSE "tpm-cuse" + + static const TPMDriverOps tpm_passthrough_driver; + +@@ -71,12 +73,18 @@ struct TPMPassthruState { + bool had_startup_error; + + TPMVersion tpm_version; ++ ptm_cap cuse_cap; /* capabilities of the CUSE TPM */ ++ uint8_t cur_locty_number; /* last set locality */ + }; + + typedef struct TPMPassthruState TPMPassthruState; + + #define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0" + ++#define TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt) (tpm_pt->cuse_cap != 0) ++ ++#define TPM_CUSE_IMPLEMENTS_ALL(S, cap) (((S)->cuse_cap & (cap)) == (cap)) ++ + /* functions */ + + static void tpm_passthrough_cancel_cmd(TPMBackend *tb); +@@ -148,7 +156,28 @@ static bool tpm_passthrough_is_selftest(const uint8_t *in, uint32_t in_len) + return false; + } + ++static int tpm_passthrough_set_locality(TPMPassthruState *tpm_pt, ++ uint8_t locty_number) ++{ ++ ptm_loc loc; ++ ++ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) { ++ if (tpm_pt->cur_locty_number != locty_number) { ++ loc.u.req.loc = locty_number; ++ if (ioctl(tpm_pt->tpm_fd, PTM_SET_LOCALITY, &loc) < 0) { ++ error_report("tpm_cuse: could not set locality on " ++ "CUSE TPM: %s", ++ strerror(errno)); ++ return -1; ++ } ++ tpm_pt->cur_locty_number = locty_number; ++ } ++ } ++ return 0; ++} ++ + static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt, ++ uint8_t locality_number, + const uint8_t *in, uint32_t in_len, + uint8_t *out, uint32_t out_len, + bool *selftest_done) +@@ -157,6 +186,11 @@ static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt, + bool is_selftest; + const struct tpm_resp_hdr *hdr; + ++ ret = tpm_passthrough_set_locality(tpm_pt, locality_number); ++ if (ret < 0) { ++ goto err_exit; ++ } ++ + tpm_pt->tpm_op_canceled = false; + tpm_pt->tpm_executing = true; + *selftest_done = false; +@@ -207,10 +241,12 @@ err_exit: + } + + static int tpm_passthrough_unix_transfer(TPMPassthruState *tpm_pt, ++ uint8_t locality_number, + const TPMLocality *locty_data, + bool *selftest_done) + { + return tpm_passthrough_unix_tx_bufs(tpm_pt, ++ locality_number, + locty_data->w_buffer.buffer, + locty_data->w_offset, + locty_data->r_buffer.buffer, +@@ -231,6 +267,7 @@ static void tpm_passthrough_worker_thread(gpointer data, + switch (cmd) { + case TPM_BACKEND_CMD_PROCESS_CMD: + tpm_passthrough_unix_transfer(tpm_pt, ++ thr_parms->tpm_state->locty_number, + thr_parms->tpm_state->locty_data, + &selftest_done); + +@@ -247,6 +284,93 @@ static void tpm_passthrough_worker_thread(gpointer data, + } + + /* ++ * Gracefully shut down the external CUSE TPM ++ */ ++static void tpm_passthrough_shutdown(TPMPassthruState *tpm_pt) ++{ ++ ptm_res res; ++ ++ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) { ++ if (ioctl(tpm_pt->tpm_fd, PTM_SHUTDOWN, &res) < 0) { ++ error_report("tpm_cuse: Could not cleanly shut down " ++ "the CUSE TPM: %s", ++ strerror(errno)); ++ } ++ } ++} ++ ++/* ++ * Probe for the CUSE TPM by sending an ioctl() requesting its ++ * capability flags. ++ */ ++static int tpm_passthrough_cuse_probe(TPMPassthruState *tpm_pt) ++{ ++ int rc = 0; ++ ++ if (ioctl(tpm_pt->tpm_fd, PTM_GET_CAPABILITY, &tpm_pt->cuse_cap) < 0) { ++ error_report("Error: CUSE TPM was requested, but probing failed"); ++ rc = -1; ++ } ++ ++ return rc; ++} ++ ++static int tpm_passthrough_cuse_check_caps(TPMPassthruState *tpm_pt) ++{ ++ int rc = 0; ++ ptm_cap caps = 0; ++ const char *tpm = NULL; ++ ++ /* check for min. required capabilities */ ++ switch (tpm_pt->tpm_version) { ++ case TPM_VERSION_1_2: ++ caps = PTM_CAP_INIT | PTM_CAP_SHUTDOWN | PTM_CAP_GET_TPMESTABLISHED | ++ PTM_CAP_SET_LOCALITY; ++ tpm = "1.2"; ++ break; ++ case TPM_VERSION_2_0: ++ caps = PTM_CAP_INIT | PTM_CAP_SHUTDOWN | PTM_CAP_GET_TPMESTABLISHED | ++ PTM_CAP_SET_LOCALITY | PTM_CAP_RESET_TPMESTABLISHED; ++ tpm = "2"; ++ break; ++ case TPM_VERSION_UNSPEC: ++ error_report("tpm_cuse: %s: TPM version has not been set", ++ __func__); ++ return -1; ++ } ++ ++ if (!TPM_CUSE_IMPLEMENTS_ALL(tpm_pt, caps)) { ++ error_report("tpm_cuse: TPM does not implement minimum set of required " ++ "capabilities for TPM %s (0x%x)", tpm, (int)caps); ++ rc = -1; ++ } ++ ++ return rc; ++} ++ ++/* ++ * Initialize the external CUSE TPM ++ */ ++static int tpm_passthrough_cuse_init(TPMPassthruState *tpm_pt) ++{ ++ int rc = 0; ++ ptm_init 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) { ++ error_report("tpm_cuse: Detected CUSE TPM but could not " ++ "send INIT: %s", ++ strerror(errno)); ++ rc = -1; ++ } ++ } ++ ++ return rc; ++} ++ ++/* + * Start the TPM (thread). If it had been started before, then terminate + * and start it again. + */ +@@ -261,6 +385,8 @@ static int tpm_passthrough_startup_tpm(TPMBackend *tb) + tpm_passthrough_worker_thread, + &tpm_pt->tpm_thread_params); + ++ tpm_passthrough_cuse_init(tpm_pt); ++ + return 0; + } + +@@ -291,14 +417,43 @@ static int tpm_passthrough_init(TPMBackend *tb, TPMState *s, + + static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb) + { ++ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); ++ ptm_est est; ++ ++ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) { ++ if (ioctl(tpm_pt->tpm_fd, PTM_GET_TPMESTABLISHED, &est) < 0) { ++ error_report("tpm_cuse: Could not get the TPM established " ++ "flag from the CUSE TPM: %s", ++ strerror(errno)); ++ return false; ++ } ++ return (est.u.resp.bit != 0); ++ } + return false; + } + + static int tpm_passthrough_reset_tpm_established_flag(TPMBackend *tb, + uint8_t locty) + { ++ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); ++ int rc = 0; ++ ptm_reset_est ptmreset_est; ++ + /* only a TPM 2.0 will support this */ +- return 0; ++ if (tpm_pt->tpm_version == TPM_VERSION_2_0) { ++ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) { ++ ptmreset_est.u.req.loc = tpm_pt->cur_locty_number; ++ ++ if (ioctl(tpm_pt->tpm_fd, PTM_RESET_TPMESTABLISHED, ++ &ptmreset_est) < 0) { ++ error_report("tpm_cuse: Could not reset the establishment bit " ++ "failed: %s", ++ strerror(errno)); ++ rc = -1; ++ } ++ } ++ } ++ return rc; + } + + static bool tpm_passthrough_get_startup_error(TPMBackend *tb) +@@ -329,7 +484,8 @@ static void tpm_passthrough_deliver_request(TPMBackend *tb) + static void tpm_passthrough_cancel_cmd(TPMBackend *tb) + { + TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); +- int n; ++ ptm_res res; ++ static bool error_printed; + + /* + * As of Linux 3.7 the tpm_tis driver does not properly cancel +@@ -338,17 +494,34 @@ static void tpm_passthrough_cancel_cmd(TPMBackend *tb) + * command, e.g., a command executed on the host. + */ + if (tpm_pt->tpm_executing) { +- if (tpm_pt->cancel_fd >= 0) { +- n = write(tpm_pt->cancel_fd, "-", 1); +- if (n != 1) { +- error_report("Canceling TPM command failed: %s", +- strerror(errno)); +- } else { +- tpm_pt->tpm_op_canceled = true; ++ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) { ++ if (TPM_CUSE_IMPLEMENTS_ALL(tpm_pt, PTM_CAP_CANCEL_TPM_CMD)) { ++ if (ioctl(tpm_pt->tpm_fd, PTM_CANCEL_TPM_CMD, &res) < 0) { ++ error_report("tpm_cuse: Could not cancel command on " ++ "CUSE TPM: %s", ++ strerror(errno)); ++ } else if (res != TPM_SUCCESS) { ++ if (!error_printed) { ++ error_report("TPM error code from command " ++ "cancellation of CUSE TPM: 0x%x", res); ++ error_printed = true; ++ } ++ } else { ++ tpm_pt->tpm_op_canceled = true; ++ } + } + } else { +- error_report("Cannot cancel TPM command due to missing " +- "TPM sysfs cancel entry"); ++ if (tpm_pt->cancel_fd >= 0) { ++ if (write(tpm_pt->cancel_fd, "-", 1) != 1) { ++ error_report("Canceling TPM command failed: %s", ++ strerror(errno)); ++ } else { ++ tpm_pt->tpm_op_canceled = true; ++ } ++ } else { ++ error_report("Cannot cancel TPM command due to missing " ++ "TPM sysfs cancel entry"); ++ } + } + } + } +@@ -378,6 +551,11 @@ static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb) + char *dev; + char path[PATH_MAX]; + ++ if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) { ++ /* not needed, but so we have a fd */ ++ return qemu_open("/dev/null", O_WRONLY); ++ } ++ + if (tb->cancel_path) { + fd = qemu_open(tb->cancel_path, O_WRONLY); + if (fd < 0) { +@@ -412,12 +590,22 @@ static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb) + { + TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); + const char *value; ++ bool have_cuse = false; ++ ++ value = qemu_opt_get(opts, "type"); ++ if (value != NULL && !strcmp("cuse-tpm", value)) { ++ have_cuse = true; ++ } + + value = qemu_opt_get(opts, "cancel-path"); + tb->cancel_path = g_strdup(value); + + value = qemu_opt_get(opts, "path"); + if (!value) { ++ if (have_cuse) { ++ error_report("Missing path to access CUSE TPM"); ++ goto err_free_parameters; ++ } + value = TPM_PASSTHROUGH_DEFAULT_DEVICE; + } + +@@ -432,15 +620,36 @@ static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb) + goto err_free_parameters; + } + ++ tpm_pt->cur_locty_number = ~0; ++ ++ if (have_cuse) { ++ if (tpm_passthrough_cuse_probe(tpm_pt)) { ++ goto err_close_tpmdev; ++ } ++ /* init TPM for probing */ ++ if (tpm_passthrough_cuse_init(tpm_pt)) { ++ goto err_close_tpmdev; ++ } ++ } ++ + if (tpm_util_test_tpmdev(tpm_pt->tpm_fd, &tpm_pt->tpm_version)) { + error_report("'%s' is not a TPM device.", + tpm_pt->tpm_dev); + goto err_close_tpmdev; + } + ++ if (have_cuse) { ++ if (tpm_passthrough_cuse_check_caps(tpm_pt)) { ++ goto err_close_tpmdev; ++ } ++ } ++ ++ + return 0; + + err_close_tpmdev: ++ tpm_passthrough_shutdown(tpm_pt); ++ + qemu_close(tpm_pt->tpm_fd); + tpm_pt->tpm_fd = -1; + +@@ -491,6 +700,8 @@ static void tpm_passthrough_destroy(TPMBackend *tb) + + tpm_backend_thread_end(&tpm_pt->tbt); + ++ tpm_passthrough_shutdown(tpm_pt); ++ + qemu_close(tpm_pt->tpm_fd); + qemu_close(tpm_pt->cancel_fd); + +@@ -564,3 +775,44 @@ static void tpm_passthrough_register(void) + } + + type_init(tpm_passthrough_register) ++ ++/* CUSE TPM */ ++static const char *tpm_passthrough_cuse_create_desc(void) ++{ ++ return "CUSE TPM backend driver"; ++} ++ ++static const TPMDriverOps tpm_cuse_driver = { ++ .type = TPM_TYPE_CUSE_TPM, ++ .opts = tpm_passthrough_cmdline_opts, ++ .desc = tpm_passthrough_cuse_create_desc, ++ .create = tpm_passthrough_create, ++ .destroy = tpm_passthrough_destroy, ++ .init = tpm_passthrough_init, ++ .startup_tpm = tpm_passthrough_startup_tpm, ++ .realloc_buffer = tpm_passthrough_realloc_buffer, ++ .reset = tpm_passthrough_reset, ++ .had_startup_error = tpm_passthrough_get_startup_error, ++ .deliver_request = tpm_passthrough_deliver_request, ++ .cancel_cmd = tpm_passthrough_cancel_cmd, ++ .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag, ++ .reset_tpm_established_flag = tpm_passthrough_reset_tpm_established_flag, ++ .get_tpm_version = tpm_passthrough_get_tpm_version, ++}; ++ ++static const TypeInfo tpm_cuse_info = { ++ .name = TYPE_TPM_CUSE, ++ .parent = TYPE_TPM_BACKEND, ++ .instance_size = sizeof(TPMPassthruState), ++ .class_init = tpm_passthrough_class_init, ++ .instance_init = tpm_passthrough_inst_init, ++ .instance_finalize = tpm_passthrough_inst_finalize, ++}; ++ ++static void tpm_cuse_register(void) ++{ ++ type_register_static(&tpm_cuse_info); ++ tpm_register_driver(&tpm_cuse_driver); ++} ++ ++type_init(tpm_cuse_register) +diff --git a/qapi-schema.json b/qapi-schema.json +index 5658723b37..53120d0f63 100644 +--- a/qapi-schema.json ++++ b/qapi-schema.json +@@ -3522,10 +3522,12 @@ + # An enumeration of TPM types + # + # @passthrough: TPM passthrough type ++# @cuse-tpm: CUSE TPM type ++# Since: 2.6 + # + # Since: 1.5 + ## +-{ 'enum': 'TpmType', 'data': [ 'passthrough' ] } ++{ 'enum': 'TpmType', 'data': [ 'passthrough', 'cuse-tpm' ] } + + ## + # @query-tpm-types: +@@ -3554,6 +3556,17 @@ + '*cancel-path' : 'str'} } + + ## ++# @TPMCuseOptions: ++# ++# Information about the CUSE TPM type ++# ++# @path: string describing the path used for accessing the TPM device ++# ++# Since: 2.6 ++## ++{ 'struct': 'TPMCuseOptions', 'data': { 'path' : 'str'}} ++ ++## + # @TpmTypeOptions: + # + # A union referencing different TPM backend types' configuration options +@@ -3563,7 +3576,8 @@ + # Since: 1.5 + ## + { 'union': 'TpmTypeOptions', +- 'data': { 'passthrough' : 'TPMPassthroughOptions' } } ++ 'data': { 'passthrough' : 'TPMPassthroughOptions', ++ 'cuse-tpm' : 'TPMCuseOptions' } } + + ## + # @TpmInfo: +diff --git a/qemu-options.hx b/qemu-options.hx +index a71aaf8ea8..e0f1d8e676 100644 +--- a/qemu-options.hx ++++ b/qemu-options.hx +@@ -2763,7 +2763,10 @@ DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \ + "-tpmdev passthrough,id=id[,path=path][,cancel-path=path]\n" + " use path to provide path to a character device; default is /dev/tpm0\n" + " use cancel-path to provide path to TPM's cancel sysfs entry; if\n" +- " not provided it will be searched for in /sys/class/misc/tpm?/device\n", ++ " not provided it will be searched for in /sys/class/misc/tpm?/device\n" ++ "-tpmdev cuse-tpm,id=id,path=path\n" ++ " use path to provide path to a character device to talk to the\n" ++ " TPM emulator providing a CUSE interface\n", + QEMU_ARCH_ALL) + STEXI + +@@ -2772,8 +2775,8 @@ The general form of a TPM device option is: + + @item -tpmdev @var{backend} ,id=@var{id} [,@var{options}] + @findex -tpmdev +-Backend type must be: +-@option{passthrough}. ++Backend type must be either one of the following: ++@option{passthrough}, @option{cuse-tpm}. + + The specific backend type will determine the applicable options. + The @code{-tpmdev} option creates the TPM backend and requires a +@@ -2823,6 +2826,18 @@ To create a passthrough TPM use the following two options: + Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by + @code{tpmdev=tpm0} in the device option. + ++@item -tpmdev cuse-tpm, id=@var{id}, path=@var{path} ++ ++(Linux-host only) Enable access to a TPM emulator with a CUSE interface. ++ ++@option{path} specifies the path to the CUSE TPM character device. ++ ++To create a backend device accessing the CUSE TPM emulator using /dev/vtpm ++use the following two options: ++@example ++-tpmdev cuse-tpm,id=tpm0,path=/dev/vtpm -device tpm-tis,tpmdev=tpm0 ++@end example ++ + @end table + + ETEXI +diff --git a/tpm.c b/tpm.c +index 9a7c7114d3..5ec2373286 100644 +--- a/tpm.c ++++ b/tpm.c +@@ -25,7 +25,7 @@ static QLIST_HEAD(, TPMBackend) tpm_backends = + + + #define TPM_MAX_MODELS 1 +-#define TPM_MAX_DRIVERS 1 ++#define TPM_MAX_DRIVERS 2 + + static TPMDriverOps const *be_drivers[TPM_MAX_DRIVERS] = { + NULL, +@@ -272,6 +272,15 @@ static TPMInfo *qmp_query_tpm_inst(TPMBackend *drv) + tpo->has_cancel_path = true; + } + break; ++ case TPM_TYPE_CUSE_TPM: ++ res->options->type = TPM_TYPE_OPTIONS_KIND_CUSE_TPM; ++ tpo = g_new0(TPMPassthroughOptions, 1); ++ res->options->u.passthrough.data = tpo; ++ if (drv->path) { ++ tpo->path = g_strdup(drv->path); ++ tpo->has_path = true; ++ } ++ break; + case TPM_TYPE__MAX: + break; + } +-- +2.11.0 + diff --git a/meta/recipes-devtools/qemu/qemu/0002-Introduce-condition-to-notify-waiters-of-completed-c.patch b/meta/recipes-devtools/qemu/qemu/0002-Introduce-condition-to-notify-waiters-of-completed-c.patch new file mode 100644 index 0000000000..c88c98e565 --- /dev/null +++ b/meta/recipes-devtools/qemu/qemu/0002-Introduce-condition-to-notify-waiters-of-completed-c.patch @@ -0,0 +1,86 @@ +From b5ffd3aa4e9bd4edb09cc84c46f78da72697a946 Mon Sep 17 00:00:00 2001 +From: Stefan Berger +Date: Sat, 31 Dec 2016 11:23:32 -0500 +Subject: [PATCH 2/4] Introduce condition to notify waiters of completed + command + +Introduce a lock and a condition to notify anyone waiting for the completion +of the execution of a TPM command by the backend (thread). The backend +uses the condition to signal anyone waiting for command completion. +We need to place the condition in two locations: one is invoked by the +backend thread, the other by the bottom half thread. +We will use the signalling to wait for command completion before VM +suspend. + +Signed-off-by: Stefan Berger + +Upstream-Status: Pending [https://lists.nongnu.org/archive/html/qemu-devel/2016-06/msg00252.html] +Signed-off-by: Patrick Ohly +--- + hw/tpm/tpm_int.h | 3 +++ + hw/tpm/tpm_tis.c | 14 ++++++++++++++ + 2 files changed, 17 insertions(+) + +diff --git a/hw/tpm/tpm_int.h b/hw/tpm/tpm_int.h +index 6b2c9c953a..70be1ad8d9 100644 +--- a/hw/tpm/tpm_int.h ++++ b/hw/tpm/tpm_int.h +@@ -30,6 +30,9 @@ struct TPMState { + char *backend; + TPMBackend *be_driver; + TPMVersion be_tpm_version; ++ ++ QemuMutex state_lock; ++ QemuCond cmd_complete; + }; + + #define TPM(obj) OBJECT_CHECK(TPMState, (obj), TYPE_TPM_TIS) +diff --git a/hw/tpm/tpm_tis.c b/hw/tpm/tpm_tis.c +index 381e7266ea..14d9e83ea2 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; + ++ qemu_mutex_lock(&s->state_lock); ++ + tpm_tis_sts_set(&tis->loc[locty], + TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE); + tis->loc[locty].state = TPM_TIS_STATE_COMPLETION; +@@ -384,6 +386,10 @@ static void tpm_tis_receive_bh(void *opaque) + tpm_tis_raise_irq(s, locty, + TPM_TIS_INT_DATA_AVAILABLE | TPM_TIS_INT_STS_VALID); + #endif ++ ++ /* notify of completed command */ ++ qemu_cond_signal(&s->cmd_complete); ++ qemu_mutex_unlock(&s->state_lock); + } + + /* +@@ -403,6 +409,11 @@ static void tpm_tis_receive_cb(TPMState *s, uint8_t locty, + } + } + ++ qemu_mutex_lock(&s->state_lock); ++ /* notify of completed command */ ++ qemu_cond_signal(&s->cmd_complete); ++ qemu_mutex_unlock(&s->state_lock); ++ + qemu_bh_schedule(tis->bh); + } + +@@ -1072,6 +1083,9 @@ static void tpm_tis_initfn(Object *obj) + memory_region_init_io(&s->mmio, OBJECT(s), &tpm_tis_memory_ops, + s, "tpm-tis-mmio", + TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT); ++ ++ qemu_mutex_init(&s->state_lock); ++ qemu_cond_init(&s->cmd_complete); + } + + static void tpm_tis_class_init(ObjectClass *klass, void *data) +-- +2.11.0 + diff --git a/meta/recipes-devtools/qemu/qemu/0003-Introduce-condition-in-TPM-backend-for-notification.patch b/meta/recipes-devtools/qemu/qemu/0003-Introduce-condition-in-TPM-backend-for-notification.patch new file mode 100644 index 0000000000..e58f019062 --- /dev/null +++ b/meta/recipes-devtools/qemu/qemu/0003-Introduce-condition-in-TPM-backend-for-notification.patch @@ -0,0 +1,79 @@ +From 732a8e046948fd62b32cd1dd76a6798eb1caf4d6 Mon Sep 17 00:00:00 2001 +From: Stefan Berger +Date: Sat, 31 Dec 2016 11:23:32 -0500 +Subject: [PATCH 3/4] Introduce condition in TPM backend for notification + +TPM backends will suspend independently of the frontends. Also +here we need to be able to wait for the TPM command to have been +completely processed. + +Signed-off-by: Stefan Berger + +Upstream-Status: Pending [https://lists.nongnu.org/archive/html/qemu-devel/2016-06/msg00252.html] +Signed-off-by: Patrick Ohly +--- + hw/tpm/tpm_passthrough.c | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/hw/tpm/tpm_passthrough.c b/hw/tpm/tpm_passthrough.c +index 050f2ba850..44739ebad2 100644 +--- a/hw/tpm/tpm_passthrough.c ++++ b/hw/tpm/tpm_passthrough.c +@@ -75,6 +75,10 @@ struct TPMPassthruState { + TPMVersion tpm_version; + ptm_cap cuse_cap; /* capabilities of the CUSE TPM */ + uint8_t cur_locty_number; /* last set locality */ ++ ++ QemuMutex state_lock; ++ QemuCond cmd_complete; /* singnaled once tpm_busy is false */ ++ bool tpm_busy; + }; + + typedef struct TPMPassthruState TPMPassthruState; +@@ -274,6 +278,11 @@ static void tpm_passthrough_worker_thread(gpointer data, + thr_parms->recv_data_callback(thr_parms->tpm_state, + thr_parms->tpm_state->locty_number, + selftest_done); ++ /* result delivered */ ++ qemu_mutex_lock(&tpm_pt->state_lock); ++ tpm_pt->tpm_busy = false; ++ qemu_cond_signal(&tpm_pt->cmd_complete); ++ qemu_mutex_unlock(&tpm_pt->state_lock); + break; + case TPM_BACKEND_CMD_INIT: + case TPM_BACKEND_CMD_END: +@@ -401,6 +410,7 @@ static void tpm_passthrough_reset(TPMBackend *tb) + tpm_backend_thread_end(&tpm_pt->tbt); + + tpm_pt->had_startup_error = false; ++ tpm_pt->tpm_busy = false; + } + + static int tpm_passthrough_init(TPMBackend *tb, TPMState *s, +@@ -478,6 +488,11 @@ static void tpm_passthrough_deliver_request(TPMBackend *tb) + { + TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); + ++ /* 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); ++ + tpm_backend_thread_deliver_request(&tpm_pt->tbt); + } + +@@ -746,6 +761,11 @@ static const TPMDriverOps tpm_passthrough_driver = { + + static void tpm_passthrough_inst_init(Object *obj) + { ++ TPMBackend *tb = TPM_BACKEND(obj); ++ TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); ++ ++ qemu_mutex_init(&tpm_pt->state_lock); ++ qemu_cond_init(&tpm_pt->cmd_complete); + } + + static void tpm_passthrough_inst_finalize(Object *obj) +-- +2.11.0 + diff --git a/meta/recipes-devtools/qemu/qemu/0004-Add-support-for-VM-suspend-resume-for-TPM-TIS.patch b/meta/recipes-devtools/qemu/qemu/0004-Add-support-for-VM-suspend-resume-for-TPM-TIS.patch new file mode 100644 index 0000000000..b8a783d4e9 --- /dev/null +++ b/meta/recipes-devtools/qemu/qemu/0004-Add-support-for-VM-suspend-resume-for-TPM-TIS.patch @@ -0,0 +1,719 @@ +From 5e9dd9063f514447ea4f54046793f4f01c297ed4 Mon Sep 17 00:00:00 2001 +From: Stefan Berger +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 + +Upstream-Status: Pending [https://lists.nongnu.org/archive/html/qemu-devel/2016-06/msg00252.html] +Signed-off-by: Patrick Ohly +--- + 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, 0, ++ 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, 0, ++ 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, 0, ++ 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 + diff --git a/meta/recipes-devtools/qemu/qemu_2.8.0.bb b/meta/recipes-devtools/qemu/qemu_2.8.0.bb index e0527a8fd9..f25aa467e5 100644 --- a/meta/recipes-devtools/qemu/qemu_2.8.0.bb +++ b/meta/recipes-devtools/qemu/qemu_2.8.0.bb @@ -11,6 +11,13 @@ SRC_URI += "file://configure-fix-Darwin-target-detection.patch \ file://target-ppc-fix-user-mode.patch \ " +SRC_URI += " \ + file://0001-Provide-support-for-the-CUSE-TPM.patch \ + file://0002-Introduce-condition-to-notify-waiters-of-completed-c.patch \ + file://0003-Introduce-condition-in-TPM-backend-for-notification.patch \ + file://0004-Add-support-for-VM-suspend-resume-for-TPM-TIS.patch \ +" + SRC_URI =+ "http://wiki.qemu-project.org/download/${BP}.tar.bz2" SRC_URI[md5sum] = "17940dce063b6ce450a12e719a6c9c43" -- cgit 1.2.3-korg