diff mbox

[v3,1/6] Provide support for the CUSE TPM

Message ID 1432676024-1046793-2-git-send-email-stefanb@linux.vnet.ibm.com
State New
Headers show

Commit Message

Stefan Berger May 26, 2015, 9:33 p.m. UTC
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 ioctl's. The CUSE TPM currently defines and implements 14
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 ioctl's allows to get a bitmask of supported capabilities.
Each returned bit indicates which capabilties 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


To use the external CUSE TPM, the CUSE TPM should be started as follows:

/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 <stefanb@linux.vnet.ibm.com>
Cc: Eric Blake <eblake@redhat.com>
---
 hmp.c                    |   6 +
 hw/tpm/tpm_int.h         |   1 +
 hw/tpm/tpm_ioctl.h       | 178 +++++++++++++++++++++++++++++
 hw/tpm/tpm_passthrough.c | 286 +++++++++++++++++++++++++++++++++++++++++++++--
 qapi-schema.json         |  17 ++-
 qemu-options.hx          |  21 +++-
 qmp-commands.hx          |   2 +-
 tpm.c                    |  11 +-
 8 files changed, 505 insertions(+), 17 deletions(-)
 create mode 100644 hw/tpm/tpm_ioctl.h

Comments

Eric Blake May 26, 2015, 11:05 p.m. UTC | #1
On 05/26/2015 03:33 PM, Stefan Berger wrote:
> 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 ioctl's. The CUSE TPM currently defines and implements 14
> 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 ioctl's allows to get a bitmask of supported capabilities.
> Each returned bit indicates which capabilties have been implemented.

s/capabilties/capabilities/

> 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
> 
> 
> To use the external CUSE TPM, the CUSE TPM should be started as follows:
> 
> /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 <stefanb@linux.vnet.ibm.com>
> Cc: Eric Blake <eblake@redhat.com>
> ---

At this point, I'm only doing a high-level overview (public interface,
blatant findings) and not a fine-grained reading of the implementation.

> diff --git a/hw/tpm/tpm_ioctl.h b/hw/tpm/tpm_ioctl.h
> new file mode 100644
> index 0000000..d36e702
> --- /dev/null
> +++ b/hw/tpm/tpm_ioctl.h
> @@ -0,0 +1,178 @@
> +/*
> + * tpm_ioctl.h
> + *
> + * This file is licensed under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either version 2.1 of
> + * the License, or (at your option) any later version.
> + */

My understanding of copyleft (and insert the obligatory IANAL
disclaimer) is that it works by exploiting the copyright law - that is,
something cannot be [L]GPL unless there is also an assertion of
copyright ownership (I don't care who, merely that a copyright claim
exists somewhere in the file, nearby the license).


> +/*
> + * Data structure to get state blobs from the TPM. If the size of the
> + * blob exceeds the STATE_BLOB_SIZE, multiple reads with
> + * adjusted offset are necessary. The last packet is indicated by
> + * the length being smaller than the STATE_BLOB_SIZE.

If the read size is exactly STATE_BLOB_SIZE, does that result in 1
packet or 2?  Does it cause a 0-length packet to be attempted, or is it
broken into STATE_BLOB_SIZE-1 and 1?


> +
> +/* state_flags above : */
> +#define STATE_FLAG_DECRYPTED     1 /* on input:  get decrypted state */
> +#define STATE_FLAG_ENCRYPTED     2 /* on output: state is encrytped */

s/encrytped/encrypted/

> +
> +/*
> + * Data structure to set state blobs in the TPM. If the size of the
> + * blob exceeds the STATE_BLOB_SIZE, multiple 'writes' are necessary.
> + * The last packet is indicated by the length being smaller than the
> + * STATE_BLOB_SIZE.
> + */

Same question as on read about an exact STATE_BLOB_SIZE write

> +struct ptm_setstate {
> +    union {
> +        struct {
> +            uint32_t state_flags; /* may be STATE_FLAG_ENCRYPTED */
> +            uint32_t tpm_number;  /* always set to 0 */
> +            uint8_t type;         /* which blob to set */
> +            uint32_t length;
> +            uint8_t data[STATE_BLOB_SIZE];

This struct has padding blanks; is that going to matter?


> +typedef uint64_t ptmcap_t;
> +typedef struct ptmest  ptmest_t;
> +typedef struct ptmreset_est ptmreset_est_t;
> +typedef struct ptmloc  ptmloc_t;
> +typedef struct ptmhdata ptmhdata_t;

Why a change in 1 vs. 2 spaces on some of the types?

Technically, POSIX reserves the entire *_t namespace to itself, I'm a
bit worried that by doing 'typedef struct foo foo_t' we are not being
consistent with the rest of qemu, which does 'typedef struct foo foo'.


> +++ b/hw/tpm/tpm_passthrough.c

> @@ -72,12 +74,18 @@ struct TPMPassthruState {
>      bool had_startup_error;
>  
>      TPMVersion tpm_version;
> +    ptmcap_t cuse_cap; /* capabilties of the CUSE TPM */
> +    uint8_t cur_locty_number; /* last set locality */

s/capabilties/capabilities/

>  };
>  
>  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(tpm_tr, cap) ((tpm_pt->cuse_cap & cap) == cap)

Evaluates cap more than once, which may not be ideal.  Also
under-parenthesized in the face of arbitrary expressions for tpm_tr or cap.

Umm, how does the macro argument tpm_tr get used, and where is the macro
body tpm_pt scoped?

Better might be this (depending on your intent):
#define TPM_CUSE_IMPLEMENTS(tpm_tr, cap) \
   (((tpm_tr)->cuse_cap & (cap)) != 0)

if you know that cap will always be passed as one bit.  But if someone
intends to use the macro to test multiple bits at once, and return true
only if all of the bits are set, then living with multiple evaluation of
'cap' may be better.

> +static int tpm_passthrough_set_locality(TPMPassthruState *tpm_pt,
> +                                        uint8_t locty_number)
> +{
> +    int n;
> +    ptmloc_t loc;
> +
> +    if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
> +        if (tpm_pt->cur_locty_number != locty_number) {
> +            loc.u.req.loc = locty_number;
> +            n = ioctl(tpm_pt->tpm_fd, PTM_SET_LOCALITY, &loc);
> +            if (n < 0) {
> +                error_report("tpm_cuse: could not set locality on "
> +                             "CUSE TPM: %s (%i)",
> +                             strerror(errno), errno);

Hmm, I wonder if error_setg_errno() followed by error_report_err() is
any nicer than manually calling strerror().  Probably not worth worrying
about.

On the other hand, this code is not strictly portable - passing both
errno and strerror(errno) as arguments to a function has no sequencing
point defined on whether errno is collected first or second; if it is
collected second, strerror() may have clobbered errno.  Most code
doesn't bother with printing "%s (%i)" for errors; the %s alone is
sufficient.


>  /*
> + * Gracefully shut down the external CUSE TPM
> + */
> +static void tpm_passthrough_shutdown(TPMPassthruState *tpm_pt)
> +{
> +    int n;
> +    ptmres_t res;
> +
> +    if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
> +        n = ioctl(tpm_pt->tpm_fd, PTM_SHUTDOWN, &res);
> +        if (n < 0) {
> +            error_report("tpm_cuse: Could not cleanly shut down "
> +                         "the CUSE TPM: %s (%i)",
> +                         strerror(errno), errno);

Why not just 'if (ioctl(...) < 0) {' without needing 'n'?

> +        }
> +    }
> +}
> +
> +/*
> + * 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;
> +    int n;
> +
> +    n = ioctl(tpm_pt->tpm_fd, PTM_GET_CAPABILITY, &tpm_pt->cuse_cap);
> +    if (n < 0) {
> +        error_report("Error: CUSE TPM was requested, but probing failed.");

Most qemu error messages intentionally do not end in period

> @@ -306,6 +472,8 @@ static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
>  {
>      TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
>      int n;
> +    ptmres_t res;
> +    static int error_printed;

You're using this as a bool...


> +                } 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;
> +                    }

...so declare it as one.


> +++ b/qapi-schema.json
> @@ -2974,10 +2974,11 @@
>  # An enumeration of TPM types
>  #
>  # @passthrough: TPM passthrough type
> +# @cuse-tpm: CUSE TPM type

Missing '(since 2.4)' designator.
Stefan Berger May 27, 2015, 1:53 a.m. UTC | #2
On 05/26/2015 07:05 PM, Eric Blake wrote:
> On 05/26/2015 03:33 PM, Stefan Berger wrote:
>> 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 ioctl's. The CUSE TPM currently defines and implements 14
>> 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 ioctl's allows to get a bitmask of supported capabilities.
>> Each returned bit indicates which capabilties have been implemented.
> s/capabilties/capabilities/
>
>> 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
>>
>>
>> To use the external CUSE TPM, the CUSE TPM should be started as follows:
>>
>> /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 <stefanb@linux.vnet.ibm.com>
>> Cc: Eric Blake <eblake@redhat.com>
>> ---
> At this point, I'm only doing a high-level overview (public interface,
> blatant findings) and not a fine-grained reading of the implementation.


Thanks anyway.


>
>> diff --git a/hw/tpm/tpm_ioctl.h b/hw/tpm/tpm_ioctl.h
>> new file mode 100644
>> index 0000000..d36e702
>> --- /dev/null
>> +++ b/hw/tpm/tpm_ioctl.h
>> @@ -0,0 +1,178 @@
>> +/*
>> + * tpm_ioctl.h
>> + *
>> + * This file is licensed under the terms of the GNU Lesser General Public
>> + * License as published by the Free Software Foundation; either version 2.1 of
>> + * the License, or (at your option) any later version.
>> + */
> My understanding of copyleft (and insert the obligatory IANAL
> disclaimer) is that it works by exploiting the copyright law - that is,
> something cannot be [L]GPL unless there is also an assertion of
> copyright ownership (I don't care who, merely that a copyright claim
> exists somewhere in the file, nearby the license).

I added the copyright now, which was obviously missing.


>
>> +/*
>> + * Data structure to get state blobs from the TPM. If the size of the
>> + * blob exceeds the STATE_BLOB_SIZE, multiple reads with
>> + * adjusted offset are necessary. The last packet is indicated by
>> + * the length being smaller than the STATE_BLOB_SIZE.
> If the read size is exactly STATE_BLOB_SIZE, does that result in 1
> packet or 2?  Does it cause a 0-length packet to be attempted, or is it
> broken into STATE_BLOB_SIZE-1 and 1?


It would be 2 packets, the 2nd one having 0-length.


>
>> +
>> +/* state_flags above : */
>> +#define STATE_FLAG_DECRYPTED     1 /* on input:  get decrypted state */
>> +#define STATE_FLAG_ENCRYPTED     2 /* on output: state is encrytped */
> s/encrytped/encrypted/
>
>> +
>> +/*
>> + * Data structure to set state blobs in the TPM. If the size of the
>> + * blob exceeds the STATE_BLOB_SIZE, multiple 'writes' are necessary.
>> + * The last packet is indicated by the length being smaller than the
>> + * STATE_BLOB_SIZE.
>> + */
> Same question as on read about an exact STATE_BLOB_SIZE write

Same here. 2 packets.

>> +struct ptm_setstate {
>> +    union {
>> +        struct {
>> +            uint32_t state_flags; /* may be STATE_FLAG_ENCRYPTED */
>> +            uint32_t tpm_number;  /* always set to 0 */
>> +            uint8_t type;         /* which blob to set */
>> +            uint32_t length;
>> +            uint8_t data[STATE_BLOB_SIZE];
> This struct has padding blanks; is that going to matter?


The problem here could be a 64bit variable that would allign differently 
on a 32bit machine versus a 64bit machine or a 32bit executable running 
on a 64bit machine.At least there are no 64bit variables here, so it 
would be ok. However, we can still make the type member 32bit.


>
>> +typedef uint64_t ptmcap_t;
>> +typedef struct ptmest  ptmest_t;
>> +typedef struct ptmreset_est ptmreset_est_t;
>> +typedef struct ptmloc  ptmloc_t;
>> +typedef struct ptmhdata ptmhdata_t;
> Why a change in 1 vs. 2 spaces on some of the types?
>
> Technically, POSIX reserves the entire *_t namespace to itself, I'm a
> bit worried that by doing 'typedef struct foo foo_t' we are not being
> consistent with the rest of qemu, which does 'typedef struct foo foo'.


So remove the _t entirely?


>
>> +++ b/hw/tpm/tpm_passthrough.c
>> @@ -72,12 +74,18 @@ struct TPMPassthruState {
>>       bool had_startup_error;
>>   
>>       TPMVersion tpm_version;
>> +    ptmcap_t cuse_cap; /* capabilties of the CUSE TPM */
>> +    uint8_t cur_locty_number; /* last set locality */
> s/capabilties/capabilities/
>
>>   };
>>   
>>   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(tpm_tr, cap) ((tpm_pt->cuse_cap & cap) == cap)
> Evaluates cap more than once, which may not be ideal.  Also
> under-parenthesized in the face of arbitrary expressions for tpm_tr or cap.
>
> Umm, how does the macro argument tpm_tr get used, and where is the macro
> body tpm_pt scoped?
>
> Better might be this (depending on your intent):
> #define TPM_CUSE_IMPLEMENTS(tpm_tr, cap) \
>     (((tpm_tr)->cuse_cap & (cap)) != 0)
>
> if you know that cap will always be passed as one bit.  But if someone
> intends to use the macro to test multiple bits at once, and return true
> only if all of the bits are set, then living with multiple evaluation of
> 'cap' may be better.

The usage so far asks for whether a certain set of capabilities are 
_all_ implemented and for this the
evaluation above is good in call cases. I'll add the additional 
parenthesis, though.


>
>> +static int tpm_passthrough_set_locality(TPMPassthruState *tpm_pt,
>> +                                        uint8_t locty_number)
>> +{
>> +    int n;
>> +    ptmloc_t loc;
>> +
>> +    if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
>> +        if (tpm_pt->cur_locty_number != locty_number) {
>> +            loc.u.req.loc = locty_number;
>> +            n = ioctl(tpm_pt->tpm_fd, PTM_SET_LOCALITY, &loc);
>> +            if (n < 0) {
>> +                error_report("tpm_cuse: could not set locality on "
>> +                             "CUSE TPM: %s (%i)",
>> +                             strerror(errno), errno);
> Hmm, I wonder if error_setg_errno() followed by error_report_err() is
> any nicer than manually calling strerror().  Probably not worth worrying
> about.
>
> On the other hand, this code is not strictly portable - passing both
> errno and strerror(errno) as arguments to a function has no sequencing
> point defined on whether errno is collected first or second; if it is
> collected second, strerror() may have clobbered errno.  Most code
> doesn't bother with printing "%s (%i)" for errors; the %s alone is
> sufficient.

Ok.

>
>
>>   /*
>> + * Gracefully shut down the external CUSE TPM
>> + */
>> +static void tpm_passthrough_shutdown(TPMPassthruState *tpm_pt)
>> +{
>> +    int n;
>> +    ptmres_t res;
>> +
>> +    if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
>> +        n = ioctl(tpm_pt->tpm_fd, PTM_SHUTDOWN, &res);
>> +        if (n < 0) {
>> +            error_report("tpm_cuse: Could not cleanly shut down "
>> +                         "the CUSE TPM: %s (%i)",
>> +                         strerror(errno), errno);
> Why not just 'if (ioctl(...) < 0) {' without needing 'n'?

Thought it was a coding style requirement .. but it isn't.


>
>> +        }
>> +    }
>> +}
>> +
>> +/*
>> + * 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;
>> +    int n;
>> +
>> +    n = ioctl(tpm_pt->tpm_fd, PTM_GET_CAPABILITY, &tpm_pt->cuse_cap);
>> +    if (n < 0) {
>> +        error_report("Error: CUSE TPM was requested, but probing failed.");
> Most qemu error messages intentionally do not end in period

Removed.

>
>> @@ -306,6 +472,8 @@ static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
>>   {
>>       TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
>>       int n;
>> +    ptmres_t res;
>> +    static int error_printed;
> You're using this as a bool...
>
>
>> +                } 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;
>> +                    }
> ...so declare it as one.

Ok.


>
>
>> +++ b/qapi-schema.json
>> @@ -2974,10 +2974,11 @@
>>   # An enumeration of TPM types
>>   #
>>   # @passthrough: TPM passthrough type
>> +# @cuse-tpm: CUSE TPM type
> Missing '(since 2.4)' designator.
>

Definitely.

Thanks!

    Stefan
diff mbox

Patch

diff --git a/hmp.c b/hmp.c
index f142d36..50cb737 100644
--- a/hmp.c
+++ b/hmp.c
@@ -814,6 +814,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->passthrough;
+            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 f2f285b..6b2c9c9 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 0000000..d36e702
--- /dev/null
+++ b/hw/tpm/tpm_ioctl.h
@@ -0,0 +1,178 @@ 
+/*
+ * tpm_ioctl.h
+ *
+ * This file is licensed under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <stdint.h>
+#include <sys/uio.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+/*
+ * Every response from a command involving a TPM command execution must hold
+ * the ptmres_t as the first element.
+ * ptmres_t corresponds to the error code of a command executed by the TPM.
+ */
+
+typedef uint32_t ptmres_t;
+
+/* PTM_GET_TPMESTABLISHED */
+struct ptmest {
+    ptmres_t tpm_result;
+    unsigned char bit; /* TPM established bit */
+};
+
+/* PTM_RESET_PTMESTABLIHSED: reset establishment bit */
+struct ptmreset_est {
+    union {
+        struct {
+            uint8_t loc; /* locality to use */
+        } req;
+        struct {
+            ptmres_t tpm_result;
+        } resp;
+    } u;
+};
+
+/* PTM_INIT */
+struct ptminit {
+    union {
+        struct {
+            uint32_t init_flags; /* see definitions below */
+        } req;
+        struct {
+            ptmres_t tpm_result;
+        } resp;
+    } u;
+};
+
+/* above init_flags */
+#define INIT_FLAG_DELETE_VOLATILE (1 << 0)
+    /* delete volatile state file after reading it */
+
+/* PTM_SET_LOCALITY */
+struct ptmloc {
+    union {
+        struct {
+            uint8_t loc; /* locality to set */
+        } req;
+        struct {
+            ptmres_t tpm_result;
+        } resp;
+    } u;
+};
+
+/* PTM_HASH_DATA: hash given data */
+struct ptmhdata {
+    union {
+        struct {
+            uint32_t length;
+            uint8_t data[4096];
+        } req;
+        struct {
+            ptmres_t tpm_result;
+        } resp;
+    } 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 STATE_BLOB_SIZE (3 * 1024)
+
+/*
+ * Data structure to get state blobs from the TPM. If the size of the
+ * blob exceeds the STATE_BLOB_SIZE, multiple reads with
+ * adjusted offset are necessary. The last packet is indicated by
+ * the length being smaller than the STATE_BLOB_SIZE.
+ */
+struct ptm_getstate {
+    union {
+        struct {
+            uint32_t state_flags; /* may be: STATE_FLAG_DECRYPTED */
+            uint32_t tpm_number;  /* always set to zero */
+            uint8_t type;         /* which blob to pull */
+            uint32_t offset;      /* offset from where to read */
+        } req;
+        struct {
+            ptmres_t tpm_result;
+            uint32_t state_flags; /* may be: STATE_FLAG_ENCRYPTED */
+            uint32_t length;
+            uint8_t  data[STATE_BLOB_SIZE];
+        } resp;
+    } 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 STATE_FLAG_DECRYPTED     1 /* on input:  get decrypted state */
+#define STATE_FLAG_ENCRYPTED     2 /* on output: state is encrytped */
+
+/*
+ * Data structure to set state blobs in the TPM. If the size of the
+ * blob exceeds the STATE_BLOB_SIZE, multiple 'writes' are necessary.
+ * The last packet is indicated by the length being smaller than the
+ * STATE_BLOB_SIZE.
+ */
+struct ptm_setstate {
+    union {
+        struct {
+            uint32_t state_flags; /* may be STATE_FLAG_ENCRYPTED */
+            uint32_t tpm_number;  /* always set to 0 */
+            uint8_t type;         /* which blob to set */
+            uint32_t length;
+            uint8_t data[STATE_BLOB_SIZE];
+        } req;
+        struct {
+            ptmres_t tpm_result;
+        } resp;
+    } u;
+};
+
+
+typedef uint64_t ptmcap_t;
+typedef struct ptmest  ptmest_t;
+typedef struct ptmreset_est ptmreset_est_t;
+typedef struct ptmloc  ptmloc_t;
+typedef struct ptmhdata ptmhdata_t;
+typedef struct ptminit ptminit_t;
+typedef struct ptm_getstate ptm_getstate_t;
+typedef struct ptm_setstate ptm_setstate_t;
+
+/* 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)
+
+enum {
+    PTM_GET_CAPABILITY     = _IOR('P', 0, ptmcap_t),
+    PTM_INIT               = _IOWR('P', 1, ptminit_t),
+    PTM_SHUTDOWN           = _IOR('P', 2, ptmres_t),
+    PTM_GET_TPMESTABLISHED = _IOR('P', 3, ptmest_t),
+    PTM_SET_LOCALITY       = _IOWR('P', 4, ptmloc_t),
+    PTM_HASH_START         = _IOR('P', 5, ptmres_t),
+    PTM_HASH_DATA          = _IOWR('P', 6, ptmhdata_t),
+    PTM_HASH_END           = _IOR('P', 7, ptmres_t),
+    PTM_CANCEL_TPM_CMD     = _IOR('P', 8, ptmres_t),
+    PTM_STORE_VOLATILE     = _IOR('P', 9, ptmres_t),
+    PTM_RESET_TPMESTABLISHED = _IOWR('P', 10, ptmreset_est_t),
+    PTM_GET_STATEBLOB      = _IOWR('P', 11, ptm_getstate_t),
+    PTM_SET_STATEBLOB      = _IOWR('P', 12, ptm_setstate_t),
+    PTM_STOP               = _IOR('P', 13, ptmres_t),
+};
diff --git a/hw/tpm/tpm_passthrough.c b/hw/tpm/tpm_passthrough.c
index 8d8523a..3926094 100644
--- a/hw/tpm/tpm_passthrough.c
+++ b/hw/tpm/tpm_passthrough.c
@@ -34,6 +34,7 @@ 
 #include "sysemu/tpm_backend_int.h"
 #include "tpm_tis.h"
 #include "tpm_util.h"
+#include "tpm_ioctl.h"
 
 #define DEBUG_TPM 0
 
@@ -46,6 +47,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;
 
@@ -72,12 +74,18 @@  struct TPMPassthruState {
     bool had_startup_error;
 
     TPMVersion tpm_version;
+    ptmcap_t cuse_cap; /* capabilties 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(tpm_tr, cap) ((tpm_pt->cuse_cap & cap) == cap)
+
 /* functions */
 
 static void tpm_passthrough_cancel_cmd(TPMBackend *tb);
@@ -124,7 +132,30 @@  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)
+{
+    int n;
+    ptmloc_t loc;
+
+    if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
+        if (tpm_pt->cur_locty_number != locty_number) {
+            loc.u.req.loc = locty_number;
+            n = ioctl(tpm_pt->tpm_fd, PTM_SET_LOCALITY, &loc);
+            if (n < 0) {
+                error_report("tpm_cuse: could not set locality on "
+                             "CUSE TPM: %s (%i)",
+                             strerror(errno), 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)
@@ -133,6 +164,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;
@@ -183,10 +219,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,
@@ -207,6 +245,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);
 
@@ -223,6 +262,99 @@  static void tpm_passthrough_worker_thread(gpointer data,
 }
 
 /*
+ * Gracefully shut down the external CUSE TPM
+ */
+static void tpm_passthrough_shutdown(TPMPassthruState *tpm_pt)
+{
+    int n;
+    ptmres_t res;
+
+    if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
+        n = ioctl(tpm_pt->tpm_fd, PTM_SHUTDOWN, &res);
+        if (n < 0) {
+            error_report("tpm_cuse: Could not cleanly shut down "
+                         "the CUSE TPM: %s (%i)",
+                         strerror(errno), 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;
+    int n;
+
+    n = ioctl(tpm_pt->tpm_fd, PTM_GET_CAPABILITY, &tpm_pt->cuse_cap);
+    if (n < 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;
+    ptmcap_t 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(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;
+    int n;
+    ptminit_t init = {
+        .u.req.init_flags = INIT_FLAG_DELETE_VOLATILE,
+    };
+
+    if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
+        n = ioctl(tpm_pt->tpm_fd, PTM_INIT, &init);
+        if (n < 0) {
+            error_report("tpm_cuse: Detected CUSE TPM but could not "
+                         "send INIT: %s (%i)",
+                         strerror(errno), errno);
+            rc = -1;
+        }
+    }
+
+    return rc;
+}
+
+/*
  * Start the TPM (thread). If it had been started before, then terminate
  * and start it again.
  */
@@ -237,6 +369,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;
 }
 
@@ -267,14 +401,46 @@  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);
+    ptmest_t est;
+    int n;
+
+    if (TPM_PASSTHROUGH_USES_CUSE_TPM(tpm_pt)) {
+        n = ioctl(tpm_pt->tpm_fd, PTM_GET_TPMESTABLISHED, &est);
+        if (n < 0) {
+            error_report("tpm_cuse: Could not get the TPM established "
+                         "flag from the CUSE TPM: %s (%i)",
+                         strerror(errno), errno);
+            return false;
+        }
+        return (est.bit != 0);
+    }
     return false;
 }
 
 static int tpm_passthrough_reset_tpm_established_flag(TPMBackend *tb,
                                                       uint8_t locty)
 {
+    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
+    int n;
+    int rc = 0;
+    ptmreset_est_t 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;
+
+            n = ioctl(tpm_pt->tpm_fd, PTM_RESET_TPMESTABLISHED, &ptmreset_est);
+            if (n < 0) {
+                error_report("tpm_cuse: Could not reset the establishment bit "
+                             "failed: %s (%i)",
+                             strerror(errno), errno);
+                rc = -1;
+            }
+        }
+    }
+    return rc;
 }
 
 static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
@@ -306,6 +472,8 @@  static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
 {
     TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
     int n;
+    ptmres_t res;
+    static int error_printed;
 
     /*
      * As of Linux 3.7 the tpm_tis driver does not properly cancel
@@ -314,17 +482,36 @@  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(tpm_pt, PTM_CAP_CANCEL_TPM_CMD)) {
+                n = ioctl(tpm_pt->tpm_fd, PTM_CANCEL_TPM_CMD, &res);
+                if (n < 0) {
+                    error_report("tpm_cuse: Could not cancel command on "
+                                 "CUSE TPM: %s (%i)",
+                                 strerror(errno), 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) {
+                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;
+                }
+            } else {
+                error_report("Cannot cancel TPM command due to missing "
+                             "TPM sysfs cancel entry");
+            }
         }
     }
 }
@@ -354,6 +541,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) {
@@ -388,12 +580,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;
     }
 
@@ -408,15 +610,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;
 
@@ -467,6 +690,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);
 
@@ -540,3 +765,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 ac9594d..0d7c7b6 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2974,10 +2974,11 @@ 
 # An enumeration of TPM types
 #
 # @passthrough: TPM passthrough type
+# @cuse-tpm: CUSE TPM type
 #
 # Since: 1.5
 ##
-{ 'enum': 'TpmType', 'data': [ 'passthrough' ] }
+{ 'enum': 'TpmType', 'data': [ 'passthrough', 'cuse-tpm' ] }
 
 ##
 # @query-tpm-types:
@@ -3006,6 +3007,17 @@ 
                                              '*cancel-path' : 'str'} }
 
 ##
+# @TPMCuseOptions:
+#
+# Information about the CUSE TPM type
+#
+# @path: string describing the path used for accessing the TPM device
+#
+# Since: 2.4
+##
+{ 'type': 'TPMCuseOptions', 'data': { 'path' : 'str'}}
+
+##
 # @TpmTypeOptions:
 #
 # A union referencing different TPM backend types' configuration options
@@ -3015,7 +3027,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 319d971..65d4ea0 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2539,7 +2539,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
 
@@ -2548,8 +2551,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
@@ -2599,6 +2602,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/qmp-commands.hx b/qmp-commands.hx
index 3a42ad0..a1f45b1 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -3494,7 +3494,7 @@  Arguments: None
 Example:
 
 -> { "execute": "query-tpm-types" }
-<- { "return": [ "passthrough" ] }
+<- { "return": [ "passthrough", "cuse-tpm" ] }
 
 EQMP
 
diff --git a/tpm.c b/tpm.c
index 963b7ee..5443b7b 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,
@@ -273,6 +273,15 @@  static TPMInfo *qmp_query_tpm_inst(TPMBackend *drv)
             tpo->has_cancel_path = true;
         }
         break;
+    case TPM_TYPE_CUSE_TPM:
+        res->options->kind = TPM_TYPE_OPTIONS_KIND_CUSE_TPM;
+        tpo = g_new0(TPMPassthroughOptions, 1);
+        res->options->passthrough = tpo;
+        if (drv->path) {
+            tpo->path = g_strdup(drv->path);
+            tpo->has_path = true;
+        }
+        break;
     case TPM_TYPE_MAX:
         break;
     }