summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2018-01-30 15:20:01 +0000
committerPeter Maydell <peter.maydell@linaro.org>2018-01-30 15:20:01 +0000
commit6521130b0a7f699fdb82446d57df5627bfa7ed3c (patch)
tree4133f36786798486d7222df1dbcc9d1919708116
parent8ebb314b957403c1c9a3f1cf995f73c6ae9d5d10 (diff)
parent4ab6cb4c62273bb46102e5ae1d6af691b47cbcd8 (diff)
downloadfocaccia-qemu-6521130b0a7f699fdb82446d57df5627bfa7ed3c.tar.gz
focaccia-qemu-6521130b0a7f699fdb82446d57df5627bfa7ed3c.zip
Merge remote-tracking branch 'remotes/stefanberger/tags/pull-tpm-2018-01-26-2' into staging
Merge tpm 2018/01/26 v2

# gpg: Signature made Mon 29 Jan 2018 22:20:05 GMT
# gpg:                using RSA key 0x75AD65802A0B4211
# gpg: Good signature from "Stefan Berger <stefanb@linux.vnet.ibm.com>"
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: B818 B9CA DF90 89C2 D5CE  C66B 75AD 6580 2A0B 4211

* remotes/stefanberger/tags/pull-tpm-2018-01-26-2:
  tpm: add CRB device
  tpm: report backend request error
  tpm: replace GThreadPool with AIO threadpool
  tpm: lookup cancel path under tpm device class
  tpm: fix alignment issues
  tpm: Set the flags of the CMD_INIT command to 0

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--backends/tpm.c59
-rw-r--r--default-configs/i386-softmmu.mak1
-rw-r--r--default-configs/x86_64-softmmu.mak1
-rw-r--r--hw/i386/acpi-build.c34
-rw-r--r--hw/tpm/Makefile.objs1
-rw-r--r--hw/tpm/tpm_crb.c303
-rw-r--r--hw/tpm/tpm_emulator.c39
-rw-r--r--hw/tpm/tpm_passthrough.c71
-rw-r--r--hw/tpm/tpm_tis.c3
-rw-r--r--hw/tpm/tpm_util.c75
-rw-r--r--hw/tpm/tpm_util.h17
-rw-r--r--include/hw/acpi/tpm.h51
-rw-r--r--include/sysemu/tpm.h5
-rw-r--r--include/sysemu/tpm_backend.h15
-rw-r--r--qapi/tpm.json5
-rw-r--r--tests/Makefile.include2
-rw-r--r--tests/tpm-crb-test.c275
17 files changed, 818 insertions, 139 deletions
diff --git a/backends/tpm.c b/backends/tpm.c
index 91222c5164..d617ba7c52 100644
--- a/backends/tpm.c
+++ b/backends/tpm.c
@@ -19,30 +19,40 @@
 #include "sysemu/tpm.h"
 #include "qemu/thread.h"
 #include "qemu/main-loop.h"
+#include "block/thread-pool.h"
+#include "qemu/error-report.h"
 
-static void tpm_backend_request_completed_bh(void *opaque)
+static void tpm_backend_request_completed(void *opaque, int ret)
 {
     TPMBackend *s = TPM_BACKEND(opaque);
     TPMIfClass *tic = TPM_IF_GET_CLASS(s->tpmif);
 
-    tic->request_completed(s->tpmif);
+    tic->request_completed(s->tpmif, ret);
+
+    /* no need for atomic, as long the BQL is taken */
+    s->cmd = NULL;
+    object_unref(OBJECT(s));
 }
 
-static void tpm_backend_worker_thread(gpointer data, gpointer user_data)
+static int tpm_backend_worker_thread(gpointer data)
 {
-    TPMBackend *s = TPM_BACKEND(user_data);
+    TPMBackend *s = TPM_BACKEND(data);
     TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
+    Error *err = NULL;
 
-    k->handle_request(s, (TPMBackendCmd *)data);
+    k->handle_request(s, s->cmd, &err);
+    if (err) {
+        error_report_err(err);
+        return -1;
+    }
 
-    qemu_bh_schedule(s->bh);
+    return 0;
 }
 
-static void tpm_backend_thread_end(TPMBackend *s)
+void tpm_backend_finish_sync(TPMBackend *s)
 {
-    if (s->thread_pool) {
-        g_thread_pool_free(s->thread_pool, FALSE, TRUE);
-        s->thread_pool = NULL;
+    while (s->cmd) {
+        aio_poll(qemu_get_aio_context(), true);
     }
 }
 
@@ -74,10 +84,7 @@ int tpm_backend_startup_tpm(TPMBackend *s, size_t buffersize)
     TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
 
     /* terminate a running TPM */
-    tpm_backend_thread_end(s);
-
-    s->thread_pool = g_thread_pool_new(tpm_backend_worker_thread, s, 1, TRUE,
-                                       NULL);
+    tpm_backend_finish_sync(s);
 
     res = k->startup_tpm ? k->startup_tpm(s, buffersize) : 0;
 
@@ -93,7 +100,17 @@ bool tpm_backend_had_startup_error(TPMBackend *s)
 
 void tpm_backend_deliver_request(TPMBackend *s, TPMBackendCmd *cmd)
 {
-    g_thread_pool_push(s->thread_pool, cmd, NULL);
+    ThreadPool *pool = aio_get_thread_pool(qemu_get_aio_context());
+
+    if (s->cmd != NULL) {
+        error_report("There is a TPM request pending");
+        return;
+    }
+
+    s->cmd = cmd;
+    object_ref(OBJECT(s));
+    thread_pool_submit_aio(pool, tpm_backend_worker_thread, s,
+                           tpm_backend_request_completed, s);
 }
 
 void tpm_backend_reset(TPMBackend *s)
@@ -104,7 +121,7 @@ void tpm_backend_reset(TPMBackend *s)
         k->reset(s);
     }
 
-    tpm_backend_thread_end(s);
+    tpm_backend_finish_sync(s);
 
     s->had_startup_error = false;
 }
@@ -159,28 +176,18 @@ TPMInfo *tpm_backend_query_tpm(TPMBackend *s)
     return info;
 }
 
-static void tpm_backend_instance_init(Object *obj)
-{
-    TPMBackend *s = TPM_BACKEND(obj);
-
-    s->bh = qemu_bh_new(tpm_backend_request_completed_bh, s);
-}
-
 static void tpm_backend_instance_finalize(Object *obj)
 {
     TPMBackend *s = TPM_BACKEND(obj);
 
     object_unref(OBJECT(s->tpmif));
     g_free(s->id);
-    tpm_backend_thread_end(s);
-    qemu_bh_delete(s->bh);
 }
 
 static const TypeInfo tpm_backend_info = {
     .name = TYPE_TPM_BACKEND,
     .parent = TYPE_OBJECT,
     .instance_size = sizeof(TPMBackend),
-    .instance_init = tpm_backend_instance_init,
     .instance_finalize = tpm_backend_instance_finalize,
     .class_size = sizeof(TPMBackendClass),
     .abstract = true,
diff --git a/default-configs/i386-softmmu.mak b/default-configs/i386-softmmu.mak
index 95ac4b464a..ac27700e79 100644
--- a/default-configs/i386-softmmu.mak
+++ b/default-configs/i386-softmmu.mak
@@ -37,6 +37,7 @@ CONFIG_APPLESMC=y
 CONFIG_I8259=y
 CONFIG_PFLASH_CFI01=y
 CONFIG_TPM_TIS=$(CONFIG_TPM)
+CONFIG_TPM_CRB=$(CONFIG_TPM)
 CONFIG_MC146818RTC=y
 CONFIG_PCI_PIIX=y
 CONFIG_WDT_IB700=y
diff --git a/default-configs/x86_64-softmmu.mak b/default-configs/x86_64-softmmu.mak
index 0221236825..b2104ade19 100644
--- a/default-configs/x86_64-softmmu.mak
+++ b/default-configs/x86_64-softmmu.mak
@@ -37,6 +37,7 @@ CONFIG_APPLESMC=y
 CONFIG_I8259=y
 CONFIG_PFLASH_CFI01=y
 CONFIG_TPM_TIS=$(CONFIG_TPM)
+CONFIG_TPM_CRB=$(CONFIG_TPM)
 CONFIG_MC146818RTC=y
 CONFIG_PCI_PIIX=y
 CONFIG_WDT_IB700=y
diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
index dc4b2b9ffe..ed78c4ed9f 100644
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -2224,6 +2224,22 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
             aml_append(sb_scope, scope);
         }
     }
+
+    if (TPM_IS_CRB(tpm_find())) {
+        dev = aml_device("TPM");
+        aml_append(dev, aml_name_decl("_HID", aml_string("MSFT0101")));
+        crs = aml_resource_template();
+        aml_append(crs, aml_memory32_fixed(TPM_CRB_ADDR_BASE,
+                                           TPM_CRB_ADDR_SIZE, AML_READ_WRITE));
+        aml_append(dev, aml_name_decl("_CRS", crs));
+
+        method = aml_method("_STA", 0, AML_NOTSERIALIZED);
+        aml_append(method, aml_return(aml_int(0x0f)));
+        aml_append(dev, method);
+
+        aml_append(sb_scope, dev);
+    }
+
     aml_append(dsdt, sb_scope);
 
     /* copy AML table into ACPI tables blob and patch header there */
@@ -2285,18 +2301,20 @@ build_tpm2(GArray *table_data, BIOSLinker *linker, GArray *tcpalog)
     if (TPM_IS_TIS(tpm_find())) {
         tpm2_ptr->control_area_address = cpu_to_le64(0);
         tpm2_ptr->start_method = cpu_to_le32(TPM2_START_METHOD_MMIO);
-
-        tpm2_ptr->log_area_minimum_length =
-            cpu_to_le32(TPM_LOG_AREA_MINIMUM_SIZE);
-
-        /* log area start address to be filled by Guest linker */
-        bios_linker_loader_add_pointer(linker,
-            ACPI_BUILD_TABLE_FILE, log_addr_offset, log_addr_size,
-            ACPI_BUILD_TPMLOG_FILE, 0);
+    } else if (TPM_IS_CRB(tpm_find())) {
+        tpm2_ptr->control_area_address = cpu_to_le64(TPM_CRB_ADDR_CTRL);
+        tpm2_ptr->start_method = cpu_to_le32(TPM2_START_METHOD_CRB);
     } else {
         g_warn_if_reached();
     }
 
+    tpm2_ptr->log_area_minimum_length =
+        cpu_to_le32(TPM_LOG_AREA_MINIMUM_SIZE);
+
+    /* log area start address to be filled by Guest linker */
+    bios_linker_loader_add_pointer(linker, ACPI_BUILD_TABLE_FILE,
+                                   log_addr_offset, log_addr_size,
+                                   ACPI_BUILD_TPMLOG_FILE, 0);
     build_header(linker, table_data,
                  (void *)tpm2_ptr, "TPM2", sizeof(*tpm2_ptr), 4, NULL, NULL);
 }
diff --git a/hw/tpm/Makefile.objs b/hw/tpm/Makefile.objs
index 7a93b24636..1dc9f8bf2c 100644
--- a/hw/tpm/Makefile.objs
+++ b/hw/tpm/Makefile.objs
@@ -1,4 +1,5 @@
 common-obj-y += tpm_util.o
 common-obj-$(CONFIG_TPM_TIS) += tpm_tis.o
+common-obj-$(CONFIG_TPM_CRB) += tpm_crb.o
 common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
 common-obj-$(CONFIG_TPM_EMULATOR) += tpm_emulator.o
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
new file mode 100644
index 0000000000..687d2557b7
--- /dev/null
+++ b/hw/tpm/tpm_crb.c
@@ -0,0 +1,303 @@
+/*
+ * tpm_crb.c - QEMU's TPM CRB interface emulator
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * Authors:
+ *   Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * tpm_crb is a device for TPM 2.0 Command Response Buffer (CRB) Interface
+ * as defined in TCG PC Client Platform TPM Profile (PTP) Specification
+ * Family “2.0” Level 00 Revision 01.03 v22
+ */
+
+#include "qemu/osdep.h"
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "exec/address-spaces.h"
+
+#include "hw/qdev-core.h"
+#include "hw/qdev-properties.h"
+#include "hw/pci/pci_ids.h"
+#include "hw/acpi/tpm.h"
+#include "migration/vmstate.h"
+#include "sysemu/tpm_backend.h"
+#include "tpm_int.h"
+#include "tpm_util.h"
+
+typedef struct CRBState {
+    DeviceState parent_obj;
+
+    TPMBackend *tpmbe;
+    TPMBackendCmd cmd;
+    uint32_t regs[TPM_CRB_R_MAX];
+    MemoryRegion mmio;
+    MemoryRegion cmdmem;
+
+    size_t be_buffer_size;
+} CRBState;
+
+#define CRB(obj) OBJECT_CHECK(CRBState, (obj), TYPE_TPM_CRB)
+
+#define DEBUG_CRB 0
+
+#define DPRINTF(fmt, ...) do {                  \
+        if (DEBUG_CRB) {                        \
+            printf(fmt, ## __VA_ARGS__);        \
+        }                                       \
+    } while (0)
+
+#define CRB_INTF_TYPE_CRB_ACTIVE 0b1
+#define CRB_INTF_VERSION_CRB 0b1
+#define CRB_INTF_CAP_LOCALITY_0_ONLY 0b0
+#define CRB_INTF_CAP_IDLE_FAST 0b0
+#define CRB_INTF_CAP_XFER_SIZE_64 0b11
+#define CRB_INTF_CAP_FIFO_NOT_SUPPORTED 0b0
+#define CRB_INTF_CAP_CRB_SUPPORTED 0b1
+#define CRB_INTF_IF_SELECTOR_CRB 0b1
+
+#define CRB_CTRL_CMD_SIZE (TPM_CRB_ADDR_SIZE - A_CRB_DATA_BUFFER)
+
+enum crb_loc_ctrl {
+    CRB_LOC_CTRL_REQUEST_ACCESS = BIT(0),
+    CRB_LOC_CTRL_RELINQUISH = BIT(1),
+    CRB_LOC_CTRL_SEIZE = BIT(2),
+    CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT = BIT(3),
+};
+
+enum crb_ctrl_req {
+    CRB_CTRL_REQ_CMD_READY = BIT(0),
+    CRB_CTRL_REQ_GO_IDLE = BIT(1),
+};
+
+enum crb_start {
+    CRB_START_INVOKE = BIT(0),
+};
+
+enum crb_cancel {
+    CRB_CANCEL_INVOKE = BIT(0),
+};
+
+static uint64_t tpm_crb_mmio_read(void *opaque, hwaddr addr,
+                                  unsigned size)
+{
+    CRBState *s = CRB(opaque);
+    void *regs = (void *)&s->regs + (addr & ~3);
+    unsigned offset = addr & 3;
+    uint32_t val = *(uint32_t *)regs >> (8 * offset);
+
+    DPRINTF("CRB read 0x" TARGET_FMT_plx " len:%u val: 0x%" PRIx32 "\n",
+            addr, size, val);
+    return val;
+}
+
+static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
+                               uint64_t val, unsigned size)
+{
+    CRBState *s = CRB(opaque);
+    DPRINTF("CRB write 0x" TARGET_FMT_plx " len:%u val: 0x%" PRIx64 "\n",
+            addr, size, val);
+
+    switch (addr) {
+    case A_CRB_CTRL_REQ:
+        switch (val) {
+        case CRB_CTRL_REQ_CMD_READY:
+            ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS,
+                             tpmIdle, 0);
+            break;
+        case CRB_CTRL_REQ_GO_IDLE:
+            ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS,
+                             tpmIdle, 1);
+            break;
+        }
+        break;
+    case A_CRB_CTRL_CANCEL:
+        if (val == CRB_CANCEL_INVOKE &&
+            s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) {
+            tpm_backend_cancel_cmd(s->tpmbe);
+        }
+        break;
+    case A_CRB_CTRL_START:
+        if (val == CRB_START_INVOKE &&
+            !(s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE)) {
+            void *mem = memory_region_get_ram_ptr(&s->cmdmem);
+
+            s->regs[R_CRB_CTRL_START] |= CRB_START_INVOKE;
+            s->cmd = (TPMBackendCmd) {
+                .in = mem,
+                .in_len = MIN(tpm_cmd_get_size(mem), s->be_buffer_size),
+                .out = mem,
+                .out_len = s->be_buffer_size,
+            };
+
+            tpm_backend_deliver_request(s->tpmbe, &s->cmd);
+        }
+        break;
+    case A_CRB_LOC_CTRL:
+        switch (val) {
+        case CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT:
+            /* not loc 3 or 4 */
+            break;
+        case CRB_LOC_CTRL_RELINQUISH:
+            break;
+        case CRB_LOC_CTRL_REQUEST_ACCESS:
+            ARRAY_FIELD_DP32(s->regs, CRB_LOC_STS,
+                             Granted, 1);
+            ARRAY_FIELD_DP32(s->regs, CRB_LOC_STS,
+                             beenSeized, 0);
+            ARRAY_FIELD_DP32(s->regs, CRB_LOC_STATE,
+                             locAssigned, 1);
+            ARRAY_FIELD_DP32(s->regs, CRB_LOC_STATE,
+                             tpmRegValidSts, 1);
+            break;
+        }
+        break;
+    }
+}
+
+static const MemoryRegionOps tpm_crb_memory_ops = {
+    .read = tpm_crb_mmio_read,
+    .write = tpm_crb_mmio_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+    },
+};
+
+static void tpm_crb_request_completed(TPMIf *ti, int ret)
+{
+    CRBState *s = CRB(ti);
+
+    s->regs[R_CRB_CTRL_START] &= ~CRB_START_INVOKE;
+    if (ret != 0) {
+        ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS,
+                         tpmSts, 1); /* fatal error */
+    }
+}
+
+static enum TPMVersion tpm_crb_get_version(TPMIf *ti)
+{
+    CRBState *s = CRB(ti);
+
+    return tpm_backend_get_tpm_version(s->tpmbe);
+}
+
+static int tpm_crb_pre_save(void *opaque)
+{
+    CRBState *s = opaque;
+
+    tpm_backend_finish_sync(s->tpmbe);
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_tpm_crb = {
+    .name = "tpm-crb",
+    .pre_save = tpm_crb_pre_save,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, CRBState, TPM_CRB_R_MAX),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+static Property tpm_crb_properties[] = {
+    DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tpm_crb_realize(DeviceState *dev, Error **errp)
+{
+    CRBState *s = CRB(dev);
+
+    if (!tpm_find()) {
+        error_setg(errp, "at most one TPM device is permitted");
+        return;
+    }
+    if (!s->tpmbe) {
+        error_setg(errp, "'tpmdev' property is required");
+        return;
+    }
+
+    memory_region_init_io(&s->mmio, OBJECT(s), &tpm_crb_memory_ops, s,
+        "tpm-crb-mmio", sizeof(s->regs));
+    memory_region_init_ram(&s->cmdmem, OBJECT(s),
+        "tpm-crb-cmd", CRB_CTRL_CMD_SIZE, errp);
+
+    memory_region_add_subregion(get_system_memory(),
+        TPM_CRB_ADDR_BASE, &s->mmio);
+    memory_region_add_subregion(get_system_memory(),
+        TPM_CRB_ADDR_BASE + sizeof(s->regs), &s->cmdmem);
+
+    tpm_backend_reset(s->tpmbe);
+
+    ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+                     InterfaceType, CRB_INTF_TYPE_CRB_ACTIVE);
+    ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+                     InterfaceVersion, CRB_INTF_VERSION_CRB);
+    ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+                     CapLocality, CRB_INTF_CAP_LOCALITY_0_ONLY);
+    ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+                     CapCRBIdleBypass, CRB_INTF_CAP_IDLE_FAST);
+    ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+                     CapDataXferSizeSupport, CRB_INTF_CAP_XFER_SIZE_64);
+    ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+                     CapFIFO, CRB_INTF_CAP_FIFO_NOT_SUPPORTED);
+    ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+                     CapCRB, CRB_INTF_CAP_CRB_SUPPORTED);
+    ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+                     InterfaceSelector, CRB_INTF_IF_SELECTOR_CRB);
+    ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+                     RID, 0b0000);
+    ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID2,
+                     VID, PCI_VENDOR_ID_IBM);
+
+    s->regs[R_CRB_CTRL_CMD_SIZE] = CRB_CTRL_CMD_SIZE;
+    s->regs[R_CRB_CTRL_CMD_LADDR] = TPM_CRB_ADDR_BASE + A_CRB_DATA_BUFFER;
+    s->regs[R_CRB_CTRL_RSP_SIZE] = CRB_CTRL_CMD_SIZE;
+    s->regs[R_CRB_CTRL_RSP_ADDR] = TPM_CRB_ADDR_BASE + A_CRB_DATA_BUFFER;
+
+    s->be_buffer_size = MIN(tpm_backend_get_buffer_size(s->tpmbe),
+                            CRB_CTRL_CMD_SIZE);
+
+    tpm_backend_startup_tpm(s->tpmbe, s->be_buffer_size);
+}
+
+static void tpm_crb_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    TPMIfClass *tc = TPM_IF_CLASS(klass);
+
+    dc->realize = tpm_crb_realize;
+    dc->props = tpm_crb_properties;
+    dc->vmsd  = &vmstate_tpm_crb;
+    dc->user_creatable = true;
+    tc->model = TPM_MODEL_TPM_CRB;
+    tc->get_version = tpm_crb_get_version;
+    tc->request_completed = tpm_crb_request_completed;
+
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo tpm_crb_info = {
+    .name = TYPE_TPM_CRB,
+    /* could be TYPE_SYS_BUS_DEVICE (or LPC etc) */
+    .parent = TYPE_DEVICE,
+    .instance_size = sizeof(CRBState),
+    .class_init  = tpm_crb_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_TPM_IF },
+        { }
+    }
+};
+
+static void tpm_crb_register(void)
+{
+    type_register_static(&tpm_crb_info);
+}
+
+type_init(tpm_crb_register)
diff --git a/hw/tpm/tpm_emulator.c b/hw/tpm/tpm_emulator.c
index 35c78de5a9..710a9ec718 100644
--- a/hw/tpm/tpm_emulator.c
+++ b/hw/tpm/tpm_emulator.c
@@ -120,7 +120,6 @@ static int tpm_emulator_unix_tx_bufs(TPMEmulator *tpm_emu,
 {
     ssize_t ret;
     bool is_selftest = false;
-    const struct tpm_resp_hdr *hdr = NULL;
 
     if (selftest_done) {
         *selftest_done = false;
@@ -132,22 +131,21 @@ static int tpm_emulator_unix_tx_bufs(TPMEmulator *tpm_emu,
         return -1;
     }
 
-    ret = qio_channel_read_all(tpm_emu->data_ioc, (char *)out, sizeof(*hdr),
-                               err);
+    ret = qio_channel_read_all(tpm_emu->data_ioc, (char *)out,
+              sizeof(struct tpm_resp_hdr), err);
     if (ret != 0) {
         return -1;
     }
 
-    hdr = (struct tpm_resp_hdr *)out;
-    out += sizeof(*hdr);
-    ret = qio_channel_read_all(tpm_emu->data_ioc, (char *)out,
-                               be32_to_cpu(hdr->len) - sizeof(*hdr) , err);
+    ret = qio_channel_read_all(tpm_emu->data_ioc,
+              (char *)out + sizeof(struct tpm_resp_hdr),
+              tpm_cmd_get_size(out) - sizeof(struct tpm_resp_hdr), err);
     if (ret != 0) {
         return -1;
     }
 
     if (is_selftest) {
-        *selftest_done = (be32_to_cpu(hdr->errcode) == 0);
+        *selftest_done = tpm_cmd_get_errcode(out) == 0;
     }
 
     return 0;
@@ -185,28 +183,19 @@ static int tpm_emulator_set_locality(TPMEmulator *tpm_emu, uint8_t locty_number,
     return 0;
 }
 
-static void tpm_emulator_handle_request(TPMBackend *tb, TPMBackendCmd *cmd)
+static void tpm_emulator_handle_request(TPMBackend *tb, TPMBackendCmd *cmd,
+                                        Error **errp)
 {
     TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
-    Error *err = NULL;
 
     DPRINTF("processing TPM command");
 
-    if (tpm_emulator_set_locality(tpm_emu, cmd->locty, &err) < 0) {
-        goto error;
-    }
-
-    if (tpm_emulator_unix_tx_bufs(tpm_emu, cmd->in, cmd->in_len,
+    if (tpm_emulator_set_locality(tpm_emu, cmd->locty, errp) < 0 ||
+        tpm_emulator_unix_tx_bufs(tpm_emu, cmd->in, cmd->in_len,
                                   cmd->out, cmd->out_len,
-                                  &cmd->selftest_done, &err) < 0) {
-        goto error;
+                                  &cmd->selftest_done, errp) < 0) {
+        tpm_util_write_fatal_error_response(cmd->out, cmd->out_len);
     }
-
-    return;
-
-error:
-    tpm_util_write_fatal_error_response(cmd->out, cmd->out_len);
-    error_report_err(err);
 }
 
 static int tpm_emulator_probe_caps(TPMEmulator *tpm_emu)
@@ -320,7 +309,9 @@ static int tpm_emulator_set_buffer_size(TPMBackend *tb,
 static int tpm_emulator_startup_tpm(TPMBackend *tb, size_t buffersize)
 {
     TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
-    ptm_init init;
+    ptm_init init = {
+        .u.req.init_flags = 0,
+    };
     ptm_res res;
 
     if (buffersize != 0 &&
diff --git a/hw/tpm/tpm_passthrough.c b/hw/tpm/tpm_passthrough.c
index 29142f38bb..a495fe07f4 100644
--- a/hw/tpm/tpm_passthrough.c
+++ b/hw/tpm/tpm_passthrough.c
@@ -80,14 +80,14 @@ static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
     }
     return ret;
 }
-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,
-                                        bool *selftest_done)
+
+static void tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt,
+                                         const uint8_t *in, uint32_t in_len,
+                                         uint8_t *out, uint32_t out_len,
+                                         bool *selftest_done, Error **errp)
 {
     ssize_t ret;
     bool is_selftest;
-    const struct tpm_resp_hdr *hdr;
 
     /* FIXME: protect shared variables or use other sync mechanism */
     tpm_pt->tpm_op_canceled = false;
@@ -99,9 +99,8 @@ static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt,
     ret = qemu_write_full(tpm_pt->tpm_fd, in, in_len);
     if (ret != in_len) {
         if (!tpm_pt->tpm_op_canceled || errno != ECANCELED) {
-            error_report("tpm_passthrough: error while transmitting data "
-                         "to TPM: %s (%i)",
-                         strerror(errno), errno);
+            error_setg_errno(errp, errno, "tpm_passthrough: error while "
+                             "transmitting data to TPM");
         }
         goto err_exit;
     }
@@ -111,20 +110,18 @@ static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt,
     ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len);
     if (ret < 0) {
         if (!tpm_pt->tpm_op_canceled || errno != ECANCELED) {
-            error_report("tpm_passthrough: error while reading data from "
-                         "TPM: %s (%i)",
-                         strerror(errno), errno);
+            error_setg_errno(errp, errno, "tpm_passthrough: error while "
+                             "reading data from TPM");
         }
     } else if (ret < sizeof(struct tpm_resp_hdr) ||
-               be32_to_cpu(((struct tpm_resp_hdr *)out)->len) != ret) {
+               tpm_cmd_get_size(out) != ret) {
         ret = -1;
-        error_report("tpm_passthrough: received invalid response "
-                     "packet from TPM");
+        error_setg_errno(errp, errno, "tpm_passthrough: received invalid "
+                     "response packet from TPM");
     }
 
     if (is_selftest && (ret >= sizeof(struct tpm_resp_hdr))) {
-        hdr = (struct tpm_resp_hdr *)out;
-        *selftest_done = (be32_to_cpu(hdr->errcode) == 0);
+        *selftest_done = tpm_cmd_get_errcode(out) == 0;
     }
 
 err_exit:
@@ -133,18 +130,18 @@ err_exit:
     }
 
     tpm_pt->tpm_executing = false;
-
-    return ret;
 }
 
-static void tpm_passthrough_handle_request(TPMBackend *tb, TPMBackendCmd *cmd)
+static void tpm_passthrough_handle_request(TPMBackend *tb, TPMBackendCmd *cmd,
+                                           Error **errp)
 {
     TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
 
     DPRINTF("tpm_passthrough: processing command %p\n", cmd);
 
     tpm_passthrough_unix_tx_bufs(tpm_pt, cmd->in, cmd->in_len,
-                                 cmd->out, cmd->out_len, &cmd->selftest_done);
+                                 cmd->out, cmd->out_len, &cmd->selftest_done,
+                                 errp);
 }
 
 static void tpm_passthrough_reset(TPMBackend *tb)
@@ -216,7 +213,8 @@ static size_t tpm_passthrough_get_buffer_size(TPMBackend *tb)
  * 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.
- * From /dev/tpm0 create /sys/class/misc/tpm0/device/cancel
+ * From /dev/tpm0 create /sys/class/tpm/tpm0/device/cancel
+ * before 4.0: /sys/class/misc/tpm0/device/cancel
  */
 static int tpm_passthrough_open_sysfs_cancel(TPMPassthruState *tpm_pt)
 {
@@ -227,26 +225,35 @@ static int tpm_passthrough_open_sysfs_cancel(TPMPassthruState *tpm_pt)
     if (tpm_pt->options->cancel_path) {
         fd = qemu_open(tpm_pt->options->cancel_path, O_WRONLY);
         if (fd < 0) {
-            error_report("Could not open TPM cancel path : %s",
+            error_report("tpm_passthrough: Could not open TPM cancel path: %s",
                          strerror(errno));
         }
         return fd;
     }
 
     dev = strrchr(tpm_pt->tpm_dev, '/');
-    if (dev) {
-        dev++;
-        if (snprintf(path, sizeof(path), "/sys/class/misc/%s/device/cancel",
-                     dev) < sizeof(path)) {
-            fd = qemu_open(path, O_WRONLY);
-            if (fd < 0) {
-                error_report("tpm_passthrough: Could not open TPM cancel "
-                             "path %s : %s", path, strerror(errno));
+    if (!dev) {
+        error_report("tpm_passthrough: Bad TPM device path %s",
+                     tpm_pt->tpm_dev);
+        return -1;
+    }
+
+    dev++;
+    if (snprintf(path, sizeof(path), "/sys/class/tpm/%s/device/cancel",
+                 dev) < sizeof(path)) {
+        fd = qemu_open(path, O_WRONLY);
+        if (fd < 0) {
+            if (snprintf(path, sizeof(path), "/sys/class/misc/%s/device/cancel",
+                         dev) < sizeof(path)) {
+                fd = qemu_open(path, O_WRONLY);
             }
         }
+    }
+
+    if (fd < 0) {
+        error_report("tpm_passthrough: Could not guess TPM cancel path");
     } else {
-       error_report("tpm_passthrough: Bad TPM device path %s",
-                    tpm_pt->tpm_dev);
+        tpm_pt->options->cancel_path = g_strdup(path);
     }
 
     return fd;
diff --git a/hw/tpm/tpm_tis.c b/hw/tpm/tpm_tis.c
index 8b5eb01a2c..08f41d2707 100644
--- a/hw/tpm/tpm_tis.c
+++ b/hw/tpm/tpm_tis.c
@@ -393,7 +393,7 @@ static void tpm_tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty)
 /*
  * Callback from the TPM to indicate that the response was received.
  */
-static void tpm_tis_request_completed(TPMIf *ti)
+static void tpm_tis_request_completed(TPMIf *ti, int ret)
 {
     TPMState *s = TPM(ti);
     uint8_t locty = s->cmd.locty;
@@ -405,6 +405,7 @@ static void tpm_tis_request_completed(TPMIf *ti)
         }
     }
 
+    /* FIXME: report error if ret != 0 */
     tpm_tis_sts_set(&s->loc[locty],
                     TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE);
     s->loc[locty].state = TPM_TIS_STATE_COMPLETION;
diff --git a/hw/tpm/tpm_util.c b/hw/tpm/tpm_util.c
index 747075e244..8abde59915 100644
--- a/hw/tpm/tpm_util.c
+++ b/hw/tpm/tpm_util.c
@@ -106,20 +106,16 @@ const PropertyInfo qdev_prop_tpm = {
 void tpm_util_write_fatal_error_response(uint8_t *out, uint32_t out_len)
 {
     if (out_len >= sizeof(struct tpm_resp_hdr)) {
-        struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
-
-        resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND);
-        resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
-        resp->errcode = cpu_to_be32(TPM_FAIL);
+        stw_be_p(out, TPM_TAG_RSP_COMMAND);
+        stl_be_p(out + 2, sizeof(struct tpm_resp_hdr));
+        stl_be_p(out + 6, TPM_FAIL);
     }
 }
 
 bool tpm_util_is_selftest(const uint8_t *in, uint32_t in_len)
 {
-    struct tpm_req_hdr *hdr = (struct tpm_req_hdr *)in;
-
-    if (in_len >= sizeof(*hdr)) {
-        return (be32_to_cpu(hdr->ordinal) == TPM_ORD_ContinueSelfTest);
+    if (in_len >= sizeof(struct tpm_req_hdr)) {
+        return tpm_cmd_get_ordinal(in) == TPM_ORD_ContinueSelfTest;
     }
 
     return false;
@@ -129,12 +125,11 @@ bool tpm_util_is_selftest(const uint8_t *in, uint32_t in_len)
  * Send request to a TPM device. We expect a response within one second.
  */
 static int tpm_util_request(int fd,
-                            unsigned char *request,
+                            const void *request,
                             size_t requestlen,
-                            unsigned char *response,
+                            void *response,
                             size_t responselen)
 {
-    struct tpm_resp_hdr *resp;
     fd_set readfds;
     int n;
     struct timeval tv = {
@@ -164,9 +159,8 @@ static int tpm_util_request(int fd,
         return -EFAULT;
     }
 
-    resp = (struct tpm_resp_hdr *)response;
     /* check the header */
-    if (be32_to_cpu(resp->len) != n) {
+    if (tpm_cmd_get_size(response) != n) {
         return -EMSGSIZE;
     }
 
@@ -178,12 +172,11 @@ static int tpm_util_request(int fd,
  * (error response is fine).
  */
 static int tpm_util_test(int fd,
-                         unsigned char *request,
+                         const void *request,
                          size_t requestlen,
                          uint16_t *return_tag)
 {
-    struct tpm_resp_hdr *resp;
-    unsigned char buf[1024];
+    char buf[1024];
     ssize_t ret;
 
     ret = tpm_util_request(fd, request, requestlen,
@@ -192,8 +185,7 @@ static int tpm_util_test(int fd,
         return ret;
     }
 
-    resp = (struct tpm_resp_hdr *)buf;
-    *return_tag = be16_to_cpu(resp->tag);
+    *return_tag = tpm_cmd_get_tag(buf);
 
     return 0;
 }
@@ -228,7 +220,7 @@ int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version)
     int ret;
 
     /* Send TPM 2 command */
-    ret = tpm_util_test(tpm_fd, (unsigned char *)&test_req_tpm2,
+    ret = tpm_util_test(tpm_fd, &test_req_tpm2,
                         sizeof(test_req_tpm2), &return_tag);
     /* TPM 2 would respond with a tag of TPM2_ST_NO_SESSIONS */
     if (!ret && return_tag == TPM2_ST_NO_SESSIONS) {
@@ -237,7 +229,7 @@ int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version)
     }
 
     /* Send TPM 1.2 command */
-    ret = tpm_util_test(tpm_fd, (unsigned char *)&test_req,
+    ret = tpm_util_test(tpm_fd, &test_req,
                         sizeof(test_req), &return_tag);
     if (!ret && return_tag == TPM_TAG_RSP_COMMAND) {
         *tpm_version = TPM_VERSION_1_2;
@@ -253,7 +245,6 @@ int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version)
 int tpm_util_get_buffer_size(int tpm_fd, TPMVersion tpm_version,
                              size_t *buffersize)
 {
-    unsigned char buf[1024];
     int ret;
 
     switch (tpm_version) {
@@ -277,26 +268,27 @@ int tpm_util_get_buffer_size(int tpm_fd, TPMVersion tpm_version,
             struct tpm_resp_hdr hdr;
             uint32_t len;
             uint32_t buffersize;
-        } QEMU_PACKED *tpm_resp = (struct tpm_resp_get_buffer_size *)buf;
+        } QEMU_PACKED tpm_resp;
 
-        ret = tpm_util_request(tpm_fd, (unsigned char *)&tpm_get_buffer_size,
-                               sizeof(tpm_get_buffer_size), buf, sizeof(buf));
+        ret = tpm_util_request(tpm_fd, &tpm_get_buffer_size,
+                               sizeof(tpm_get_buffer_size),
+                               &tpm_resp, sizeof(tpm_resp));
         if (ret < 0) {
             return ret;
         }
 
-        if (be32_to_cpu(tpm_resp->hdr.len) != sizeof(*tpm_resp) ||
-            be32_to_cpu(tpm_resp->len) != sizeof(uint32_t)) {
+        if (be32_to_cpu(tpm_resp.hdr.len) != sizeof(tpm_resp) ||
+            be32_to_cpu(tpm_resp.len) != sizeof(uint32_t)) {
             DPRINTF("tpm_resp->hdr.len = %u, expected = %zu\n",
-                    be32_to_cpu(tpm_resp->hdr.len), sizeof(*tpm_resp));
+                    be32_to_cpu(tpm_resp.hdr.len), sizeof(tpm_resp));
             DPRINTF("tpm_resp->len = %u, expected = %zu\n",
-                    be32_to_cpu(tpm_resp->len), sizeof(uint32_t));
+                    be32_to_cpu(tpm_resp.len), sizeof(uint32_t));
             error_report("tpm_util: Got unexpected response to "
                          "TPM_GetCapability; errcode: 0x%x",
-                         be32_to_cpu(tpm_resp->hdr.errcode));
+                         be32_to_cpu(tpm_resp.hdr.errcode));
             return -EFAULT;
         }
-        *buffersize = be32_to_cpu(tpm_resp->buffersize);
+        *buffersize = be32_to_cpu(tpm_resp.buffersize);
         break;
     }
     case TPM_VERSION_2_0: {
@@ -324,27 +316,28 @@ int tpm_util_get_buffer_size(int tpm_fd, TPMVersion tpm_version,
             uint32_t value1;
             uint32_t property2;
             uint32_t value2;
-        } QEMU_PACKED *tpm2_resp = (struct tpm2_resp_get_buffer_size *)buf;
+        } QEMU_PACKED tpm2_resp;
 
-        ret = tpm_util_request(tpm_fd, (unsigned char *)&tpm2_get_buffer_size,
-                               sizeof(tpm2_get_buffer_size), buf, sizeof(buf));
+        ret = tpm_util_request(tpm_fd, &tpm2_get_buffer_size,
+                               sizeof(tpm2_get_buffer_size),
+                               &tpm2_resp, sizeof(tpm2_resp));
         if (ret < 0) {
             return ret;
         }
 
-        if (be32_to_cpu(tpm2_resp->hdr.len) != sizeof(*tpm2_resp) ||
-            be32_to_cpu(tpm2_resp->count) != 2) {
+        if (be32_to_cpu(tpm2_resp.hdr.len) != sizeof(tpm2_resp) ||
+            be32_to_cpu(tpm2_resp.count) != 2) {
             DPRINTF("tpm2_resp->hdr.len = %u, expected = %zu\n",
-                    be32_to_cpu(tpm2_resp->hdr.len), sizeof(*tpm2_resp));
+                    be32_to_cpu(tpm2_resp.hdr.len), sizeof(tpm2_resp));
             DPRINTF("tpm2_resp->len = %u, expected = %u\n",
-                    be32_to_cpu(tpm2_resp->count), 2);
+                    be32_to_cpu(tpm2_resp.count), 2);
             error_report("tpm_util: Got unexpected response to "
                          "TPM2_GetCapability; errcode: 0x%x",
-                         be32_to_cpu(tpm2_resp->hdr.errcode));
+                         be32_to_cpu(tpm2_resp.hdr.errcode));
             return -EFAULT;
         }
-        *buffersize = MAX(be32_to_cpu(tpm2_resp->value1),
-                          be32_to_cpu(tpm2_resp->value2));
+        *buffersize = MAX(be32_to_cpu(tpm2_resp.value1),
+                          be32_to_cpu(tpm2_resp.value2));
         break;
     }
     case TPM_VERSION_UNSPEC:
diff --git a/hw/tpm/tpm_util.h b/hw/tpm/tpm_util.h
index 19b28474ae..f003d15615 100644
--- a/hw/tpm/tpm_util.h
+++ b/hw/tpm/tpm_util.h
@@ -31,9 +31,24 @@ bool tpm_util_is_selftest(const uint8_t *in, uint32_t in_len);
 
 int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version);
 
+static inline uint16_t tpm_cmd_get_tag(const void *b)
+{
+    return lduw_be_p(b);
+}
+
 static inline uint32_t tpm_cmd_get_size(const void *b)
 {
-    return be32_to_cpu(*(const uint32_t *)(b + 2));
+    return ldl_be_p(b + 2);
+}
+
+static inline uint32_t tpm_cmd_get_ordinal(const void *b)
+{
+    return ldl_be_p(b + 6);
+}
+
+static inline uint32_t tpm_cmd_get_errcode(const void *b)
+{
+    return ldl_be_p(b + 6);
 }
 
 int tpm_util_get_buffer_size(int tpm_fd, TPMVersion tpm_version,
diff --git a/include/hw/acpi/tpm.h b/include/hw/acpi/tpm.h
index 6d516c6a7f..96fd3a92f7 100644
--- a/include/hw/acpi/tpm.h
+++ b/include/hw/acpi/tpm.h
@@ -16,11 +16,61 @@
 #ifndef HW_ACPI_TPM_H
 #define HW_ACPI_TPM_H
 
+#include "hw/registerfields.h"
+
 #define TPM_TIS_ADDR_BASE           0xFED40000
 #define TPM_TIS_ADDR_SIZE           0x5000
 
 #define TPM_TIS_IRQ                 5
 
+REG32(CRB_LOC_STATE, 0x00)
+  FIELD(CRB_LOC_STATE, tpmEstablished, 0, 1)
+  FIELD(CRB_LOC_STATE, locAssigned, 1, 1)
+  FIELD(CRB_LOC_STATE, activeLocality, 2, 3)
+  FIELD(CRB_LOC_STATE, reserved, 5, 2)
+  FIELD(CRB_LOC_STATE, tpmRegValidSts, 7, 1)
+REG32(CRB_LOC_CTRL, 0x08)
+REG32(CRB_LOC_STS, 0x0C)
+  FIELD(CRB_LOC_STS, Granted, 0, 1)
+  FIELD(CRB_LOC_STS, beenSeized, 1, 1)
+REG32(CRB_INTF_ID, 0x30)
+  FIELD(CRB_INTF_ID, InterfaceType, 0, 4)
+  FIELD(CRB_INTF_ID, InterfaceVersion, 4, 4)
+  FIELD(CRB_INTF_ID, CapLocality, 8, 1)
+  FIELD(CRB_INTF_ID, CapCRBIdleBypass, 9, 1)
+  FIELD(CRB_INTF_ID, Reserved1, 10, 1)
+  FIELD(CRB_INTF_ID, CapDataXferSizeSupport, 11, 2)
+  FIELD(CRB_INTF_ID, CapFIFO, 13, 1)
+  FIELD(CRB_INTF_ID, CapCRB, 14, 1)
+  FIELD(CRB_INTF_ID, CapIFRes, 15, 2)
+  FIELD(CRB_INTF_ID, InterfaceSelector, 17, 2)
+  FIELD(CRB_INTF_ID, IntfSelLock, 19, 1)
+  FIELD(CRB_INTF_ID, Reserved2, 20, 4)
+  FIELD(CRB_INTF_ID, RID, 24, 8)
+REG32(CRB_INTF_ID2, 0x34)
+  FIELD(CRB_INTF_ID2, VID, 0, 16)
+  FIELD(CRB_INTF_ID2, DID, 16, 16)
+REG32(CRB_CTRL_EXT, 0x38)
+REG32(CRB_CTRL_REQ, 0x40)
+REG32(CRB_CTRL_STS, 0x44)
+  FIELD(CRB_CTRL_STS, tpmSts, 0, 1)
+  FIELD(CRB_CTRL_STS, tpmIdle, 1, 1)
+REG32(CRB_CTRL_CANCEL, 0x48)
+REG32(CRB_CTRL_START, 0x4C)
+REG32(CRB_INT_ENABLED, 0x50)
+REG32(CRB_INT_STS, 0x54)
+REG32(CRB_CTRL_CMD_SIZE, 0x58)
+REG32(CRB_CTRL_CMD_LADDR, 0x5C)
+REG32(CRB_CTRL_CMD_HADDR, 0x60)
+REG32(CRB_CTRL_RSP_SIZE, 0x64)
+REG32(CRB_CTRL_RSP_ADDR, 0x68)
+REG32(CRB_DATA_BUFFER, 0x80)
+
+#define TPM_CRB_ADDR_BASE           0xFED40000
+#define TPM_CRB_ADDR_SIZE           0x1000
+#define TPM_CRB_ADDR_CTRL           (TPM_CRB_ADDR_BASE + A_CRB_CTRL_REQ)
+#define TPM_CRB_R_MAX               R_CRB_DATA_BUFFER
+
 #define TPM_LOG_AREA_MINIMUM_SIZE   (64 * 1024)
 
 #define TPM_TCPA_ACPI_CLASS_CLIENT  0
@@ -30,5 +80,6 @@
 #define TPM2_ACPI_CLASS_SERVER      1
 
 #define TPM2_START_METHOD_MMIO      6
+#define TPM2_START_METHOD_CRB       7
 
 #endif /* HW_ACPI_TPM_H */
diff --git a/include/sysemu/tpm.h b/include/sysemu/tpm.h
index 852e02687c..233b1a3fc3 100644
--- a/include/sysemu/tpm.h
+++ b/include/sysemu/tpm.h
@@ -41,14 +41,17 @@ typedef struct TPMIfClass {
     InterfaceClass parent_class;
 
     enum TpmModel model;
-    void (*request_completed)(TPMIf *obj);
+    void (*request_completed)(TPMIf *obj, int ret);
     enum TPMVersion (*get_version)(TPMIf *obj);
 } TPMIfClass;
 
 #define TYPE_TPM_TIS                "tpm-tis"
+#define TYPE_TPM_CRB                "tpm-crb"
 
 #define TPM_IS_TIS(chr)                             \
     object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS)
+#define TPM_IS_CRB(chr)                             \
+    object_dynamic_cast(OBJECT(chr), TYPE_TPM_CRB)
 
 /* returns NULL unless there is exactly one TPM device */
 static inline TPMIf *tpm_find(void)
diff --git a/include/sysemu/tpm_backend.h b/include/sysemu/tpm_backend.h
index 0d6c994a62..7e166ef954 100644
--- a/include/sysemu/tpm_backend.h
+++ b/include/sysemu/tpm_backend.h
@@ -18,6 +18,7 @@
 #include "qapi-types.h"
 #include "qemu/option.h"
 #include "sysemu/tpm.h"
+#include "qapi/error.h"
 
 #define TYPE_TPM_BACKEND "tpm-backend"
 #define TPM_BACKEND(obj) \
@@ -45,9 +46,8 @@ struct TPMBackend {
     /*< protected >*/
     TPMIf *tpmif;
     bool opened;
-    GThreadPool *thread_pool;
     bool had_startup_error;
-    QEMUBH *bh;
+    TPMBackendCmd *cmd;
 
     /* <public> */
     char *id;
@@ -85,7 +85,7 @@ struct TPMBackendClass {
 
     TpmTypeOptions *(*get_tpm_options)(TPMBackend *t);
 
-    void (*handle_request)(TPMBackend *s, TPMBackendCmd *cmd);
+    void (*handle_request)(TPMBackend *s, TPMBackendCmd *cmd, Error **errp);
 };
 
 /**
@@ -197,6 +197,15 @@ TPMVersion tpm_backend_get_tpm_version(TPMBackend *s);
 size_t tpm_backend_get_buffer_size(TPMBackend *s);
 
 /**
+ * tpm_backend_finish_sync:
+ * @s: the backend to call into
+ *
+ * Finish the pending command synchronously (this will call aio_poll()
+ * on qemu main AIOContext until it ends)
+ */
+void tpm_backend_finish_sync(TPMBackend *s);
+
+/**
  * tpm_backend_query_tpm:
  * @s: the backend
  *
diff --git a/qapi/tpm.json b/qapi/tpm.json
index 7093f268fb..d50deef5e9 100644
--- a/qapi/tpm.json
+++ b/qapi/tpm.json
@@ -11,10 +11,11 @@
 # An enumeration of TPM models
 #
 # @tpm-tis: TPM TIS model
+# @tpm-crb: TPM CRB model (since 2.12)
 #
 # Since: 1.5
 ##
-{ 'enum': 'TpmModel', 'data': [ 'tpm-tis' ] }
+{ 'enum': 'TpmModel', 'data': [ 'tpm-tis', 'tpm-crb' ] }
 
 ##
 # @query-tpm-models:
@@ -28,7 +29,7 @@
 # Example:
 #
 # -> { "execute": "query-tpm-models" }
-# <- { "return": [ "tpm-tis" ] }
+# <- { "return": [ "tpm-tis", "tpm-crb" ] }
 #
 ##
 { 'command': 'query-tpm-models', 'returns': ['TpmModel'] }
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 851aafe9d1..ca82e0c0cc 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -286,6 +286,7 @@ check-qtest-i386-$(CONFIG_VHOST_USER_NET_TEST_i386) += tests/vhost-user-test$(EX
 ifeq ($(CONFIG_VHOST_USER_NET_TEST_i386),)
 check-qtest-x86_64-$(CONFIG_VHOST_USER_NET_TEST_x86_64) += tests/vhost-user-test$(EXESUF)
 endif
+check-qtest-i386-$(CONFIG_TPM) += tests/tpm-crb-test$(EXESUF)
 check-qtest-i386-$(CONFIG_SLIRP) += tests/test-netfilter$(EXESUF)
 check-qtest-i386-$(CONFIG_POSIX) += tests/test-filter-mirror$(EXESUF)
 check-qtest-i386-$(CONFIG_POSIX) += tests/test-filter-redirector$(EXESUF)
@@ -708,6 +709,7 @@ tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
 tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
 tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \
         tests/io-channel-helpers.o $(test-io-obj-y)
+tests/tpm-crb-test$(EXESUF): tests/tpm-crb-test.o $(test-io-obj-y)
 tests/test-io-channel-file$(EXESUF): tests/test-io-channel-file.o \
         tests/io-channel-helpers.o $(test-io-obj-y)
 tests/test-io-channel-tls$(EXESUF): tests/test-io-channel-tls.o \
diff --git a/tests/tpm-crb-test.c b/tests/tpm-crb-test.c
new file mode 100644
index 0000000000..8bf1507e00
--- /dev/null
+++ b/tests/tpm-crb-test.c
@@ -0,0 +1,275 @@
+/*
+ * QTest testcase for TPM CRB
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * Authors:
+ *   Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+
+#include "hw/acpi/tpm.h"
+#include "hw/tpm/tpm_ioctl.h"
+#include "io/channel-socket.h"
+#include "libqtest.h"
+#include "qapi/error.h"
+
+#define TPM_RC_FAILURE 0x101
+#define TPM2_ST_NO_SESSIONS 0x8001
+
+struct tpm_hdr {
+    uint16_t tag;
+    uint32_t len;
+    uint32_t code; /*ordinal/error */
+    char buffer[];
+} QEMU_PACKED;
+
+typedef struct TestState {
+    CompatGMutex data_mutex;
+    CompatGCond data_cond;
+    SocketAddress *addr;
+    QIOChannel *tpm_ioc;
+    GThread *emu_tpm_thread;
+    struct tpm_hdr *tpm_msg;
+} TestState;
+
+static void test_wait_cond(TestState *s)
+{
+    gint64 end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+
+    g_mutex_lock(&s->data_mutex);
+    if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) {
+        g_assert_not_reached();
+    }
+    g_mutex_unlock(&s->data_mutex);
+}
+
+static void *emu_tpm_thread(void *data)
+{
+    TestState *s = data;
+    QIOChannel *ioc = s->tpm_ioc;
+
+    s->tpm_msg = g_new(struct tpm_hdr, 1);
+    while (true) {
+        int minhlen = sizeof(s->tpm_msg->tag) + sizeof(s->tpm_msg->len);
+
+        if (!qio_channel_read(ioc, (char *)s->tpm_msg, minhlen, &error_abort)) {
+            break;
+        }
+        s->tpm_msg->tag = be16_to_cpu(s->tpm_msg->tag);
+        s->tpm_msg->len = be32_to_cpu(s->tpm_msg->len);
+        g_assert_cmpint(s->tpm_msg->len, >=, minhlen);
+        g_assert_cmpint(s->tpm_msg->tag, ==, TPM2_ST_NO_SESSIONS);
+
+        s->tpm_msg = g_realloc(s->tpm_msg, s->tpm_msg->len);
+        qio_channel_read(ioc, (char *)&s->tpm_msg->code,
+                         s->tpm_msg->len - minhlen, &error_abort);
+        s->tpm_msg->code = be32_to_cpu(s->tpm_msg->code);
+
+        /* reply error */
+        s->tpm_msg->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
+        s->tpm_msg->len = cpu_to_be32(sizeof(struct tpm_hdr));
+        s->tpm_msg->code = cpu_to_be32(TPM_RC_FAILURE);
+        qio_channel_write(ioc, (char *)s->tpm_msg, be32_to_cpu(s->tpm_msg->len),
+                          &error_abort);
+    }
+
+    g_free(s->tpm_msg);
+    s->tpm_msg = NULL;
+    object_unref(OBJECT(s->tpm_ioc));
+    return NULL;
+}
+
+static void *emu_ctrl_thread(void *data)
+{
+    TestState *s = data;
+    QIOChannelSocket *lioc = qio_channel_socket_new();
+    QIOChannel *ioc;
+
+    qio_channel_socket_listen_sync(lioc, s->addr, &error_abort);
+    g_cond_signal(&s->data_cond);
+
+    qio_channel_wait(QIO_CHANNEL(lioc), G_IO_IN);
+    ioc = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
+    g_assert(ioc);
+
+    {
+        uint32_t cmd = 0;
+        struct iovec iov = { .iov_base = &cmd, .iov_len = sizeof(cmd) };
+        int *pfd = NULL;
+        size_t nfd = 0;
+
+        qio_channel_readv_full(ioc, &iov, 1, &pfd, &nfd, &error_abort);
+        cmd = be32_to_cpu(cmd);
+        g_assert_cmpint(cmd, ==, CMD_SET_DATAFD);
+        g_assert_cmpint(nfd, ==, 1);
+        s->tpm_ioc = QIO_CHANNEL(qio_channel_socket_new_fd(*pfd, &error_abort));
+        g_free(pfd);
+
+        cmd = 0;
+        qio_channel_write(ioc, (char *)&cmd, sizeof(cmd), &error_abort);
+
+        s->emu_tpm_thread = g_thread_new(NULL, emu_tpm_thread, s);
+    }
+
+    while (true) {
+        uint32_t cmd;
+        ssize_t ret;
+
+        ret = qio_channel_read(ioc, (char *)&cmd, sizeof(cmd), NULL);
+        if (ret <= 0) {
+            break;
+        }
+
+        cmd = be32_to_cpu(cmd);
+        switch (cmd) {
+        case CMD_GET_CAPABILITY: {
+            ptm_cap cap = cpu_to_be64(0x3fff);
+            qio_channel_write(ioc, (char *)&cap, sizeof(cap), &error_abort);
+            break;
+        }
+        case CMD_INIT: {
+            ptm_init init;
+            qio_channel_read(ioc, (char *)&init.u.req, sizeof(init.u.req),
+                              &error_abort);
+            init.u.resp.tpm_result = 0;
+            qio_channel_write(ioc, (char *)&init.u.resp, sizeof(init.u.resp),
+                              &error_abort);
+            break;
+        }
+        case CMD_SHUTDOWN: {
+            ptm_res res = 0;
+            qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort);
+            qio_channel_close(s->tpm_ioc, &error_abort);
+            g_thread_join(s->emu_tpm_thread);
+            break;
+        }
+        case CMD_STOP: {
+            ptm_res res = 0;
+            qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort);
+            break;
+        }
+        case CMD_SET_BUFFERSIZE: {
+            ptm_setbuffersize sbs;
+            qio_channel_read(ioc, (char *)&sbs.u.req, sizeof(sbs.u.req),
+                             &error_abort);
+            sbs.u.resp.buffersize = sbs.u.req.buffersize ?: cpu_to_be32(4096);
+            sbs.u.resp.tpm_result = 0;
+            sbs.u.resp.minsize = cpu_to_be32(128);
+            sbs.u.resp.maxsize = cpu_to_be32(4096);
+            qio_channel_write(ioc, (char *)&sbs.u.resp, sizeof(sbs.u.resp),
+                              &error_abort);
+            break;
+        }
+        case CMD_SET_LOCALITY: {
+            ptm_loc loc;
+            /* Note: this time it's not u.req / u.resp... */
+            qio_channel_read(ioc, (char *)&loc, sizeof(loc), &error_abort);
+            g_assert_cmpint(loc.u.req.loc, ==, 0);
+            loc.u.resp.tpm_result = 0;
+            qio_channel_write(ioc, (char *)&loc, sizeof(loc), &error_abort);
+            break;
+        }
+        default:
+            g_debug("unimplemented %u", cmd);
+            g_assert_not_reached();
+        }
+    }
+
+    object_unref(OBJECT(ioc));
+    object_unref(OBJECT(lioc));
+    return NULL;
+}
+
+#define TPM_CMD "\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00"
+
+static void tpm_crb_test(const void *data)
+{
+    const TestState *s = data;
+    uint32_t intfid = readl(TPM_CRB_ADDR_BASE + A_CRB_INTF_ID);
+    uint32_t csize = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_SIZE);
+    uint64_t caddr = readq(TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_LADDR);
+    uint32_t rsize = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_SIZE);
+    uint64_t raddr = readq(TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_ADDR);
+
+    g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, InterfaceType), ==, 1);
+    g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, InterfaceVersion), ==, 1);
+    g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapLocality), ==, 0);
+    g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapCRBIdleBypass), ==, 0);
+    g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapDataXferSizeSupport),
+                    ==, 3);
+    g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapFIFO), ==, 0);
+    g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapCRB), ==, 1);
+    g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, InterfaceSelector), ==, 1);
+    g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, RID), ==, 0);
+
+    g_assert_cmpint(csize, >=, 128);
+    g_assert_cmpint(rsize, >=, 128);
+    g_assert_cmpint(caddr, >, TPM_CRB_ADDR_BASE);
+    g_assert_cmpint(raddr, >, TPM_CRB_ADDR_BASE);
+
+    memwrite(caddr, TPM_CMD, sizeof(TPM_CMD));
+
+    uint32_t sts, start = 1;
+    uint64_t end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+    writel(TPM_CRB_ADDR_BASE + A_CRB_CTRL_START, start);
+    do {
+        start = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
+        if ((start & 1) == 0) {
+            break;
+        }
+    } while (g_get_monotonic_time() < end_time);
+    start = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
+    g_assert_cmpint(start & 1, ==, 0);
+    sts = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS);
+    g_assert_cmpint(sts & 1, ==, 0);
+
+    struct tpm_hdr tpm_msg;
+    memread(raddr, &tpm_msg, sizeof(tpm_msg));
+    g_assert_cmpmem(&tpm_msg, sizeof(tpm_msg), s->tpm_msg, sizeof(*s->tpm_msg));
+}
+
+int main(int argc, char **argv)
+{
+    int ret;
+    char *args, *tmp_path = g_dir_make_tmp("qemu-tpm-crb-test.XXXXXX", NULL);
+    GThread *thread;
+    TestState test;
+
+    module_call_init(MODULE_INIT_QOM);
+    g_test_init(&argc, &argv, NULL);
+
+    test.addr = g_new0(SocketAddress, 1);
+    test.addr->type = SOCKET_ADDRESS_TYPE_UNIX;
+    test.addr->u.q_unix.path = g_build_filename(tmp_path, "sock", NULL);
+    g_mutex_init(&test.data_mutex);
+    g_cond_init(&test.data_cond);
+
+    thread = g_thread_new(NULL, emu_ctrl_thread, &test);
+    test_wait_cond(&test);
+
+    args = g_strdup_printf(
+        "-chardev socket,id=chr,path=%s "
+        "-tpmdev emulator,id=dev,chardev=chr "
+        "-device tpm-crb,tpmdev=dev",
+        test.addr->u.q_unix.path);
+    qtest_start(args);
+
+    qtest_add_data_func("/tpm-crb/test", &test, tpm_crb_test);
+    ret = g_test_run();
+
+    qtest_end();
+
+    g_thread_join(thread);
+    g_unlink(test.addr->u.q_unix.path);
+    qapi_free_SocketAddress(test.addr);
+    g_rmdir(tmp_path);
+    g_free(tmp_path);
+    g_free(args);
+    return ret;
+}