diff mbox

[V25,6/7] Add support for cancelling of a TPM command

Message ID 1361464413-22515-7-git-send-email-stefanb@linux.vnet.ibm.com
State New
Headers show

Commit Message

Stefan Berger Feb. 21, 2013, 4:33 p.m. UTC
This patch adds support for cancelling an executing TPM command.
In Linux for example a user can cancel a command through the TPM's
sysfs 'cancel' entry using

echo "1" > /sysfs/class/misc/tpm0/device/cancel

This patch propagates the cancellation of a command inside a VM
to the host TPM's sysfs entry.
It also uses the possibility to cancel the command before QEMU VM
shutdown or reboot, which helps in preventing QEMU from hanging while
waiting for the completion of the command.
To relieve higher layers or users from having to determine the TPM's
cancel sysfs entry, the driver searches for the entry in well known
locations.

Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com>
---
 qemu-options.hx       |  13 +++-
 tpm/tpm_passthrough.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++----
 vl.c                  |   5 ++
 3 files changed, 168 insertions(+), 16 deletions(-)

Comments

Corey Bryant Feb. 26, 2013, 10:04 p.m. UTC | #1
On 02/21/2013 11:33 AM, Stefan Berger wrote:
> This patch adds support for cancelling an executing TPM command.
> In Linux for example a user can cancel a command through the TPM's
> sysfs 'cancel' entry using
>
> echo "1" > /sysfs/class/misc/tpm0/device/cancel
>
> This patch propagates the cancellation of a command inside a VM
> to the host TPM's sysfs entry.
> It also uses the possibility to cancel the command before QEMU VM
> shutdown or reboot, which helps in preventing QEMU from hanging while
> waiting for the completion of the command.
> To relieve higher layers or users from having to determine the TPM's
> cancel sysfs entry, the driver searches for the entry in well known
> locations.
>
> Signed-off-by: Stefan Berger<stefanb@linux.vnet.ibm.com>
> Reviewed-by: Corey Bryant<coreyb@linux.vnet.ibm.com>
> ---
>   qemu-options.hx       |  13 +++-
>   tpm/tpm_passthrough.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++----
>   vl.c                  |   5 ++
>   3 files changed, 168 insertions(+), 16 deletions(-)
>
> diff --git a/qemu-options.hx b/qemu-options.hx
> index a0e789e..9c8a56f 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -2218,8 +2218,10 @@ DEFHEADING()
>   DEFHEADING(TPM device options:)
>
>   DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
> -    "-tpmdev passthrough,id=id[,path=path]\n"
> -    "                use path to provide path to a character device; default is /dev/tpm0\n",
> +    "-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",
>       QEMU_ARCH_ALL)
>   STEXI
>
> @@ -2241,7 +2243,7 @@ Use 'help' to print all available TPM backend types.
>   qemu -tpmdev help
>   @end example
>
> -@item -tpmdev passthrough, id=@var{id}, path=@var{path}
> +@item -tpmdev passthrough, id=@var{id}, path=@var{path}, path=@var{cancel-path}

The last path= should be cancel-path=

>
>   (Linux-host only) Enable access to the host's TPM using the passthrough
>   driver.
> @@ -2250,6 +2252,11 @@ driver.
>   a Linux host this would be @code{/dev/tpm0}.
>   @option{path} is optional and by default @code{/dev/tpm0} is used.
>
> +@option{cancel-path} specifies the path to the host TPM device's sysfs
> +entry allowing for cancellation of an ongoing TPM command.
> +@option{cancel-path} is optional and by default QEMU will search for the
> +sysfs entry to use.
> +
>   Some notes about using the host's TPM with the passthrough driver:
>
>   The TPM device accessed by the passthrough driver must not be
> diff --git a/tpm/tpm_passthrough.c b/tpm/tpm_passthrough.c
> index 4c09938..8556d8a 100644
> --- a/tpm/tpm_passthrough.c
> +++ b/tpm/tpm_passthrough.c
> @@ -22,6 +22,8 @@
>    * License along with this library; if not, see<http://www.gnu.org/licenses/>
>    */
>
> +#include <dirent.h>
> +
>   #include "qemu-common.h"
>   #include "qapi/error.h"
>   #include "qemu/sockets.h"
> @@ -57,11 +59,18 @@ struct TPMPassthruState {
>
>       char *tpm_dev;
>       int tpm_fd;
> +    bool tpm_executing;
> +    bool tpm_op_canceled;
> +    int cancel_fd;
>       bool had_startup_error;
>   };
>
>   #define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
>
> +/* functions */
> +
> +static void tpm_passthrough_cancel_cmd(TPMBackend *tb);
> +
>   static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
>   {
>       return send_all(fd, buf, len);
> @@ -79,25 +88,36 @@ static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
>       return be32_to_cpu(resp->len);
>   }
>
> -static int tpm_passthrough_unix_tx_bufs(int tpm_fd,
> +static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt,
>                                           const uint8_t *in, uint32_t in_len,
>                                           uint8_t *out, uint32_t out_len)
>   {
>       int ret;
>
> -    ret = tpm_passthrough_unix_write(tpm_fd, in, in_len);
> +    tpm_pt->tpm_op_canceled = false;
> +    tpm_pt->tpm_executing = true;
> +
> +    ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len);
>       if (ret != in_len) {
> -        error_report("tpm_passthrough: error while transmitting data "
> -                     "to TPM: %s (%i)\n",
> -                     strerror(errno), errno);
> +        if (!tpm_pt->tpm_op_canceled ||
> +            (tpm_pt->tpm_op_canceled && errno != ECANCELED)) {
> +            error_report("tpm_passthrough: error while transmitting data "
> +                         "to TPM: %s (%i)\n",
> +                         strerror(errno), errno);
> +        }
>           goto err_exit;
>       }
>
> -    ret = tpm_passthrough_unix_read(tpm_fd, out, out_len);
> +    tpm_pt->tpm_executing = false;
> +
> +    ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len);
>       if (ret < 0) {
> -        error_report("tpm_passthrough: error while reading data from "
> -                     "TPM: %s (%i)\n",
> -                     strerror(errno), errno);
> +        if (!tpm_pt->tpm_op_canceled ||
> +            (tpm_pt->tpm_op_canceled && errno != ECANCELED)) {
> +            error_report("tpm_passthrough: error while reading data from "
> +                         "TPM: %s (%i)\n",
> +                         strerror(errno), errno);
> +        }
>       } else if (ret < sizeof(struct tpm_resp_hdr) ||
>                  tpm_passthrough_get_size_from_buffer(out) != ret) {
>           ret = -1;
> @@ -110,13 +130,15 @@ err_exit:
>           tpm_write_fatal_error_response(out, out_len);
>       }
>
> +    tpm_pt->tpm_executing = false;
> +
>       return ret;
>   }
>
> -static int tpm_passthrough_unix_transfer(int tpm_fd,
> +static int tpm_passthrough_unix_transfer(TPMPassthruState *tpm_pt,
>                                            const TPMLocality *locty_data)
>   {
> -    return tpm_passthrough_unix_tx_bufs(tpm_fd,
> +    return tpm_passthrough_unix_tx_bufs(tpm_pt,
>                                           locty_data->w_buffer.buffer,
>                                           locty_data->w_offset,
>                                           locty_data->r_buffer.buffer,
> @@ -134,7 +156,7 @@ static void tpm_passthrough_worker_thread(gpointer data,
>
>       switch (cmd) {
>       case TPM_BACKEND_CMD_PROCESS_CMD:
> -        tpm_passthrough_unix_transfer(tpm_pt->tpm_fd,
> +        tpm_passthrough_unix_transfer(tpm_pt,
>                                         thr_parms->tpm_state->locty_data);
>
>           thr_parms->recv_data_callback(thr_parms->tpm_state,
> @@ -172,6 +194,8 @@ static void tpm_passthrough_reset(TPMBackend *tb)
>
>       DPRINTF("tpm_passthrough: CALL TO TPM_RESET!\n");
>
> +    tpm_passthrough_cancel_cmd(tb);
> +
>       tpm_backend_thread_end(&tpm_pt->tbt);
>
>       tpm_pt->had_startup_error = false;
> @@ -221,7 +245,29 @@ static void tpm_passthrough_deliver_request(TPMBackend *tb)
>
>   static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
>   {
> -    /* cancelling an ongoing command is known not to work with some TPMs */
> +    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +    int n;
> +
> +    /*
> +     * As of Linux 3.7 the tpm_tis driver does not properly cancel
> +     * commands on all TPM manufacturers' TPMs.
> +     * Only cancel if we're busy so we don't cancel someone else's
> +     * 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\n",
> +                             strerror(errno));
> +            } else {
> +                tpm_pt->tpm_op_canceled = true;
> +            }
> +        } else {
> +            error_report("Cannot cancel TPM command due to missing "
> +                         "TPM sysfs cancel entry");
> +        }
> +    }
>   }
>
>   static const char *tpm_passthrough_create_desc(void)
> @@ -281,10 +327,98 @@ static int tpm_passthrough_test_tpmdev(int fd)
>       return 0;
>   }
>
> +/*
> + * Check whether the given base path, e.g.,  /sys/class/misc/tpm0/device,
> + * is the sysfs directory of a TPM. A TPM sysfs directory should be uniquely
> + * recognizable by the file entries 'pcrs' and 'cancel'.
> + * Upon success 'true' is returned and the basebath buffer has '/cancel'
> + * appended.
> + */
> +static bool tpm_passthrough_check_sysfs_cancel(char *basepath, size_t bufsz)
> +{
> +    char path[PATH_MAX];
> +    struct stat statbuf;
> +
> +    snprintf(path, sizeof(path), "%s/pcrs", basepath);
> +    if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) {
> +        return false;
> +    }
> +
> +    snprintf(path, sizeof(path), "%s/cancel", basepath);
> +    if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) {
> +        return false;
> +    }
> +
> +    strncpy(basepath, path, bufsz);
> +
> +    return true;
> +}
> +
> +/*
> + * Unless path or file descriptor set has been provided by user,
> + * determine the sysfs cancel file following kernel documentation
> + * in Documentation/ABI/stable/sysfs-class-tpm.
> + */
> +static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb)
> +{
> +    int fd = -1;
> +    unsigned int idx;
> +    DIR *pnp_dir;
> +    char path[PATH_MAX];
> +    struct dirent entry, *result;
> +    int len;
> +
> +    if (tb->cancel_path) {
> +        fd = qemu_open(tb->cancel_path, O_WRONLY);
> +        if (fd < 0) {
> +            error_report("Could not open TPM cancel path : %s",
> +                         strerror(errno));
> +        }
> +        return fd;
> +    }
> +
> +    snprintf(path, sizeof(path), "/sys/class/misc");
> +    pnp_dir = opendir(path);
> +    if (pnp_dir != NULL) {
> +        while (readdir_r(pnp_dir, &entry, &result) == 0 &&
> +               result != NULL) {
> +            /*
> +             * only allow /sys/class/misc/tpm%u type of paths
> +             */
> +            if (sscanf(entry.d_name, "tpm%u%n", &idx, &len) < 1 ||
> +                len <= strlen("tpm") ||
> +                len != strlen(entry.d_name)) {
> +                continue;
> +            }
> +
> +            snprintf(path, sizeof(path), "/sys/class/misc/%s/device",
> +                     entry.d_name);
> +            if (!tpm_passthrough_check_sysfs_cancel(path, sizeof(path))) {
> +                continue;
> +            }
> +
> +            fd = qemu_open(path, O_WRONLY);
> +            break;
> +        }
> +        closedir(pnp_dir);
> +    }
> +
> +    if (fd >= 0) {
> +        tb->cancel_path = g_strdup(path);
> +    }
> +
> +    return fd;
> +}
> +
>   static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
>   {
>       const char *value;
>
> +    value = qemu_opt_get(opts, "cancel-path");
> +    if (value) {
> +        tb->cancel_path = g_strdup(value);
> +    }
> +
>       value = qemu_opt_get(opts, "path");
>       if (!value) {
>           value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
> @@ -337,6 +471,8 @@ static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
>           goto err_exit;
>       }
>
> +    tb->s.tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tb);
> +

I think this needs a failure path if cancel_fd == -1.  I'm able to start 
a guest with:

-tpmdev passthrough,id=tpm0,path=/dev/tpm0,cancel-path=/dev/junk

>       return tb;
>
>   err_exit:
> @@ -351,12 +487,16 @@ static void tpm_passthrough_destroy(TPMBackend *tb)
>   {
>       TPMPassthruState *tpm_pt = tb->s.tpm_pt;
>
> +    tpm_passthrough_cancel_cmd(tb);
> +
>       tpm_backend_thread_end(&tpm_pt->tbt);
>
>       qemu_close(tpm_pt->tpm_fd);
> +    qemu_close(tb->s.tpm_pt->cancel_fd);
>
>       g_free(tb->id);
>       g_free(tb->path);
> +    g_free(tb->cancel_path);
>       g_free(tb->s.tpm_pt->tpm_dev);
>       g_free(tb->s.tpm_pt);
>       g_free(tb);
> diff --git a/vl.c b/vl.c
> index 19065d0..5e1032f 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -503,6 +503,11 @@ static QemuOptsList qemu_tpmdev_opts = {
>               .help = "Type of TPM backend",
>           },
>           {
> +            .name = "cancel-path",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Sysfs file entry for canceling TPM commands",
> +        },
> +        {
>               .name = "path",
>               .type = QEMU_OPT_STRING,
>               .help = "Path to TPM device on the host",
> -- 1.7.11.7
>
Stefan Berger Feb. 26, 2013, 11:34 p.m. UTC | #2
On 02/26/2013 05:04 PM, Corey Bryant wrote:
>
>
> On 02/21/2013 11:33 AM, Stefan Berger wrote:
>>
>>
>> -@item -tpmdev passthrough, id=@var{id}, path=@var{path}
>> +@item -tpmdev passthrough, id=@var{id}, path=@var{path}, 
>> path=@var{cancel-path}
>
> The last path= should be cancel-path=

Fixed

>>           goto err_exit;
>>       }
>>
>> +    tb->s.tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tb);
>> +
> @@ -337,6 +471,8 @@ static TPMBackend *tpm_passthrough_create(QemuOpts 
> *opts, const char *id)
>
> I think this needs a failure path if cancel_fd == -1.  I'm able to 
> start a guest with:
>
> -tpmdev passthrough,id=tpm0,path=/dev/tpm0,cancel-path=/dev/junk

Fixed.
diff mbox

Patch

diff --git a/qemu-options.hx b/qemu-options.hx
index a0e789e..9c8a56f 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2218,8 +2218,10 @@  DEFHEADING()
 DEFHEADING(TPM device options:)
 
 DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
-    "-tpmdev passthrough,id=id[,path=path]\n"
-    "                use path to provide path to a character device; default is /dev/tpm0\n",
+    "-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",
     QEMU_ARCH_ALL)
 STEXI
 
@@ -2241,7 +2243,7 @@  Use 'help' to print all available TPM backend types.
 qemu -tpmdev help
 @end example
 
-@item -tpmdev passthrough, id=@var{id}, path=@var{path}
+@item -tpmdev passthrough, id=@var{id}, path=@var{path}, path=@var{cancel-path}
 
 (Linux-host only) Enable access to the host's TPM using the passthrough
 driver.
@@ -2250,6 +2252,11 @@  driver.
 a Linux host this would be @code{/dev/tpm0}.
 @option{path} is optional and by default @code{/dev/tpm0} is used.
 
+@option{cancel-path} specifies the path to the host TPM device's sysfs
+entry allowing for cancellation of an ongoing TPM command.
+@option{cancel-path} is optional and by default QEMU will search for the
+sysfs entry to use.
+
 Some notes about using the host's TPM with the passthrough driver:
 
 The TPM device accessed by the passthrough driver must not be
diff --git a/tpm/tpm_passthrough.c b/tpm/tpm_passthrough.c
index 4c09938..8556d8a 100644
--- a/tpm/tpm_passthrough.c
+++ b/tpm/tpm_passthrough.c
@@ -22,6 +22,8 @@ 
  * License along with this library; if not, see <http://www.gnu.org/licenses/>
  */
 
+#include <dirent.h>
+
 #include "qemu-common.h"
 #include "qapi/error.h"
 #include "qemu/sockets.h"
@@ -57,11 +59,18 @@  struct TPMPassthruState {
 
     char *tpm_dev;
     int tpm_fd;
+    bool tpm_executing;
+    bool tpm_op_canceled;
+    int cancel_fd;
     bool had_startup_error;
 };
 
 #define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
 
+/* functions */
+
+static void tpm_passthrough_cancel_cmd(TPMBackend *tb);
+
 static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
 {
     return send_all(fd, buf, len);
@@ -79,25 +88,36 @@  static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
     return be32_to_cpu(resp->len);
 }
 
-static int tpm_passthrough_unix_tx_bufs(int tpm_fd,
+static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt,
                                         const uint8_t *in, uint32_t in_len,
                                         uint8_t *out, uint32_t out_len)
 {
     int ret;
 
-    ret = tpm_passthrough_unix_write(tpm_fd, in, in_len);
+    tpm_pt->tpm_op_canceled = false;
+    tpm_pt->tpm_executing = true;
+
+    ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len);
     if (ret != in_len) {
-        error_report("tpm_passthrough: error while transmitting data "
-                     "to TPM: %s (%i)\n",
-                     strerror(errno), errno);
+        if (!tpm_pt->tpm_op_canceled ||
+            (tpm_pt->tpm_op_canceled && errno != ECANCELED)) {
+            error_report("tpm_passthrough: error while transmitting data "
+                         "to TPM: %s (%i)\n",
+                         strerror(errno), errno);
+        }
         goto err_exit;
     }
 
-    ret = tpm_passthrough_unix_read(tpm_fd, out, out_len);
+    tpm_pt->tpm_executing = false;
+
+    ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len);
     if (ret < 0) {
-        error_report("tpm_passthrough: error while reading data from "
-                     "TPM: %s (%i)\n",
-                     strerror(errno), errno);
+        if (!tpm_pt->tpm_op_canceled ||
+            (tpm_pt->tpm_op_canceled && errno != ECANCELED)) {
+            error_report("tpm_passthrough: error while reading data from "
+                         "TPM: %s (%i)\n",
+                         strerror(errno), errno);
+        }
     } else if (ret < sizeof(struct tpm_resp_hdr) ||
                tpm_passthrough_get_size_from_buffer(out) != ret) {
         ret = -1;
@@ -110,13 +130,15 @@  err_exit:
         tpm_write_fatal_error_response(out, out_len);
     }
 
+    tpm_pt->tpm_executing = false;
+
     return ret;
 }
 
-static int tpm_passthrough_unix_transfer(int tpm_fd,
+static int tpm_passthrough_unix_transfer(TPMPassthruState *tpm_pt,
                                          const TPMLocality *locty_data)
 {
-    return tpm_passthrough_unix_tx_bufs(tpm_fd,
+    return tpm_passthrough_unix_tx_bufs(tpm_pt,
                                         locty_data->w_buffer.buffer,
                                         locty_data->w_offset,
                                         locty_data->r_buffer.buffer,
@@ -134,7 +156,7 @@  static void tpm_passthrough_worker_thread(gpointer data,
 
     switch (cmd) {
     case TPM_BACKEND_CMD_PROCESS_CMD:
-        tpm_passthrough_unix_transfer(tpm_pt->tpm_fd,
+        tpm_passthrough_unix_transfer(tpm_pt,
                                       thr_parms->tpm_state->locty_data);
 
         thr_parms->recv_data_callback(thr_parms->tpm_state,
@@ -172,6 +194,8 @@  static void tpm_passthrough_reset(TPMBackend *tb)
 
     DPRINTF("tpm_passthrough: CALL TO TPM_RESET!\n");
 
+    tpm_passthrough_cancel_cmd(tb);
+
     tpm_backend_thread_end(&tpm_pt->tbt);
 
     tpm_pt->had_startup_error = false;
@@ -221,7 +245,29 @@  static void tpm_passthrough_deliver_request(TPMBackend *tb)
 
 static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
 {
-    /* cancelling an ongoing command is known not to work with some TPMs */
+    TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+    int n;
+
+    /*
+     * As of Linux 3.7 the tpm_tis driver does not properly cancel
+     * commands on all TPM manufacturers' TPMs.
+     * Only cancel if we're busy so we don't cancel someone else's
+     * 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\n",
+                             strerror(errno));
+            } else {
+                tpm_pt->tpm_op_canceled = true;
+            }
+        } else {
+            error_report("Cannot cancel TPM command due to missing "
+                         "TPM sysfs cancel entry");
+        }
+    }
 }
 
 static const char *tpm_passthrough_create_desc(void)
@@ -281,10 +327,98 @@  static int tpm_passthrough_test_tpmdev(int fd)
     return 0;
 }
 
+/*
+ * Check whether the given base path, e.g.,  /sys/class/misc/tpm0/device,
+ * is the sysfs directory of a TPM. A TPM sysfs directory should be uniquely
+ * recognizable by the file entries 'pcrs' and 'cancel'.
+ * Upon success 'true' is returned and the basebath buffer has '/cancel'
+ * appended.
+ */
+static bool tpm_passthrough_check_sysfs_cancel(char *basepath, size_t bufsz)
+{
+    char path[PATH_MAX];
+    struct stat statbuf;
+
+    snprintf(path, sizeof(path), "%s/pcrs", basepath);
+    if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) {
+        return false;
+    }
+
+    snprintf(path, sizeof(path), "%s/cancel", basepath);
+    if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) {
+        return false;
+    }
+
+    strncpy(basepath, path, bufsz);
+
+    return true;
+}
+
+/*
+ * Unless path or file descriptor set has been provided by user,
+ * determine the sysfs cancel file following kernel documentation
+ * in Documentation/ABI/stable/sysfs-class-tpm.
+ */
+static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb)
+{
+    int fd = -1;
+    unsigned int idx;
+    DIR *pnp_dir;
+    char path[PATH_MAX];
+    struct dirent entry, *result;
+    int len;
+
+    if (tb->cancel_path) {
+        fd = qemu_open(tb->cancel_path, O_WRONLY);
+        if (fd < 0) {
+            error_report("Could not open TPM cancel path : %s",
+                         strerror(errno));
+        }
+        return fd;
+    }
+
+    snprintf(path, sizeof(path), "/sys/class/misc");
+    pnp_dir = opendir(path);
+    if (pnp_dir != NULL) {
+        while (readdir_r(pnp_dir, &entry, &result) == 0 &&
+               result != NULL) {
+            /*
+             * only allow /sys/class/misc/tpm%u type of paths
+             */
+            if (sscanf(entry.d_name, "tpm%u%n", &idx, &len) < 1 ||
+                len <= strlen("tpm") ||
+                len != strlen(entry.d_name)) {
+                continue;
+            }
+
+            snprintf(path, sizeof(path), "/sys/class/misc/%s/device",
+                     entry.d_name);
+            if (!tpm_passthrough_check_sysfs_cancel(path, sizeof(path))) {
+                continue;
+            }
+
+            fd = qemu_open(path, O_WRONLY);
+            break;
+        }
+        closedir(pnp_dir);
+    }
+
+    if (fd >= 0) {
+        tb->cancel_path = g_strdup(path);
+    }
+
+    return fd;
+}
+
 static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
 {
     const char *value;
 
+    value = qemu_opt_get(opts, "cancel-path");
+    if (value) {
+        tb->cancel_path = g_strdup(value);
+    }
+
     value = qemu_opt_get(opts, "path");
     if (!value) {
         value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
@@ -337,6 +471,8 @@  static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
         goto err_exit;
     }
 
+    tb->s.tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tb);
+
     return tb;
 
 err_exit:
@@ -351,12 +487,16 @@  static void tpm_passthrough_destroy(TPMBackend *tb)
 {
     TPMPassthruState *tpm_pt = tb->s.tpm_pt;
 
+    tpm_passthrough_cancel_cmd(tb);
+
     tpm_backend_thread_end(&tpm_pt->tbt);
 
     qemu_close(tpm_pt->tpm_fd);
+    qemu_close(tb->s.tpm_pt->cancel_fd);
 
     g_free(tb->id);
     g_free(tb->path);
+    g_free(tb->cancel_path);
     g_free(tb->s.tpm_pt->tpm_dev);
     g_free(tb->s.tpm_pt);
     g_free(tb);
diff --git a/vl.c b/vl.c
index 19065d0..5e1032f 100644
--- a/vl.c
+++ b/vl.c
@@ -503,6 +503,11 @@  static QemuOptsList qemu_tpmdev_opts = {
             .help = "Type of TPM backend",
         },
         {
+            .name = "cancel-path",
+            .type = QEMU_OPT_STRING,
+            .help = "Sysfs file entry for canceling TPM commands",
+        },
+        {
             .name = "path",
             .type = QEMU_OPT_STRING,
             .help = "Path to TPM device on the host",