tpm(4): add support for tpm2 CRB interface
authordv <dv@openbsd.org>
Sat, 23 Oct 2021 16:39:03 +0000 (16:39 +0000)
committerdv <dv@openbsd.org>
Sat, 23 Oct 2021 16:39:03 +0000 (16:39 +0000)
Some modern tpm2 devices require or prefer drivers communicate via
the CRB interface and not the TIS/fifo interface. This change adds
basic support for detecting CRB start mode and using CRB to issue
commands required for proper S4 hibernation. As a result, this also
defines a new struct definition for the TPM2 acpi table required
for start mode detection.

This fixes recent S4 regressions on the Surface Go 2 caused by a
change in firmware from Microsoft.

Other CRB start methods may need implementing in the future to
support additional hardware.

tested by deraadt@ and many others, ok kettenis@

sys/dev/acpi/acpireg.h
sys/dev/acpi/tpm.c

index f2ff1f0..67f2a42 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: acpireg.h,v 1.55 2021/03/23 09:41:12 patrick Exp $    */
+/*     $OpenBSD: acpireg.h,v 1.56 2021/10/23 16:39:03 dv Exp $ */
 /*
  * Copyright (c) 2005 Thorsten Lockert <tholo@sigmasoft.com>
  * Copyright (c) 2005 Marco Peereboom <marco@openbsd.org>
@@ -466,6 +466,14 @@ struct acpi_facs {
        uint8_t         reserved[31];
 } __packed;
 
+struct acpi_tpm2 {
+       struct acpi_table_header        hdr;
+#define TPM2_SIG       "TPM2"
+       uint32_t        reserved;
+       uint64_t        control_addr;
+       uint32_t        start_method;
+} __packed;
+
 /*
  * Intel ACPI DMA Remapping Entries
  */
index 6907c6a..6f3bafa 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: tpm.c,v 1.12 2021/09/11 23:22:38 deraadt Exp $ */
+/* $OpenBSD: tpm.c,v 1.13 2021/10/23 16:39:03 dv Exp $ */
 
 /*
  * Minimal interface to Trusted Platform Module chips implementing the
 #define TPM_READ_TMO                   120000  /* 2 minutes */
 #define TPM_BURST_TMO                  2000    /* 2sec */
 
+#define TPM2_START_METHOD_TIS          6
+#define TPM2_START_METHOD_CRB          7
+
+#define TPM_CRB_LOC_STATE              0x0
+#define TPM_CRB_LOC_CTRL               0x8
+#define TPM_LOC_STS                    0xC
+#define TPM_CRB_INTF_ID                        0x30
+#define TPM_CRB_CTRL_EXT               0x38
+#define TPM_CRB_CTRL_REQ               0x40
+#define TPM_CRB_CTRL_STS               0x44
+#define TPM_CRB_CTRL_CANCEL            0x48
+#define TPM_CRB_CTRL_START             0x4C
+#define TPM_CRB_CTRL_CMD_SIZE          0x58
+#define TPM_CRB_CTRL_CMD_LADDR         0x5C
+#define TPM_CRB_CTRL_CMD_HADDR         0x60
+#define TPM_CRB_CTRL_RSP_SIZE          0x64
+#define TPM_CRB_CTRL_RSP_LADDR         0x68
+#define TPM_CRB_CTRL_RSP_HADDR         0x6c
+#define TPM_CRB_DATA_BUFFER            0x80
+
+#define TPM_CRB_LOC_STATE_ESTB         (1 << 0)
+#define TPM_CRB_LOC_STATE_ASSIGNED     (1 << 1)
+#define TPM_CRB_LOC_ACTIVE_MASK        0x009c
+#define TPM_CRB_LOC_VALID              (1 << 7)
+
+#define TPM_CRB_LOC_REQUEST            (1 << 0)
+#define TPM_CRB_LOC_RELEASE            (1 << 1)
+
+#define TPM_CRB_CTRL_REQ_GO_READY      (1 << 0)
+#define TPM_CRB_CTRL_REQ_GO_IDLE       (1 << 1)
+
+#define TPM_CRB_CTRL_STS_ERR_BIT       (1 << 0)
+#define TPM_CRB_CTRL_STS_IDLE_BIT      (1 << 1)
+
+#define TPM_CRB_CTRL_CANCEL_CMD                0x1
+#define TPM_CRB_CTRL_CANCEL_CLEAR      0x0
+
+#define TPM_CRB_CTRL_START_CMD         (1 << 0)
+#define TPM_CRB_INT_ENABLED_BIT                (1 << 31)
+
+#define TPM2_RC_SUCCESS                        0x0000
+#define TPM2_RC_INITIALIZE             0x0100
+#define TPM2_RC_FAILURE                        0x0101
+#define TPM2_RC_DISABLED               0x0120
+#define TPM2_RC_RETRY                  0x0922
+
 struct tpm_softc {
        struct device           sc_dev;
 
        bus_space_tag_t         sc_bt;
        bus_space_handle_t      sc_bh;
+       bus_size_t              sc_bbase;
 
        struct acpi_softc       *sc_acpi;
        struct aml_node         *sc_devnode;
 
        uint32_t                sc_devid;
        uint32_t                sc_rev;
+
        int                     sc_tpm20;
+       int                     sc_tpm_mode;
+#define TPM_TIS                0
+#define TPM_CRB                1
+       bus_size_t              sc_cmd_off;
+       bus_size_t              sc_rsp_off;
+       size_t                  sc_cmd_sz;
+       size_t                  sc_rsp_sz;
 
        int                     sc_enabled;
 };
@@ -148,18 +203,27 @@ void      tpm_attach(struct device *, struct device *, void *);
 int    tpm_activate(struct device *, int);
 
 int    tpm_probe(bus_space_tag_t, bus_space_handle_t);
-int    tpm_init(struct tpm_softc *);
-int    tpm_read(struct tpm_softc *, void *, int, size_t *, int);
-int    tpm_write(struct tpm_softc *, void *, int);
+int    tpm_init_tis(struct tpm_softc *);
+int    tpm_init_crb(struct tpm_softc *);
+int    tpm_read_tis(struct tpm_softc *, void *, int, size_t *, int);
+int    tpm_read_crb(struct tpm_softc *, void *, int);
+int    tpm_write_tis(struct tpm_softc *, void *, int);
+int    tpm_write_crb(struct tpm_softc *, void *, int);
 int    tpm_suspend(struct tpm_softc *);
 int    tpm_resume(struct tpm_softc *);
 
-int    tpm_waitfor(struct tpm_softc *, uint8_t, int);
-int    tpm_request_locality(struct tpm_softc *, int);
-void   tpm_release_locality(struct tpm_softc *);
-int    tpm_getburst(struct tpm_softc *);
+int    tpm_waitfor(struct tpm_softc *, bus_space_handle_t, uint32_t, uint32_t, int);
+int    tpm_waitfor_status(struct tpm_softc *, uint8_t, int);
+int    tpm_request_locality_tis(struct tpm_softc *, int);
+int    tpm_request_locality_crb(struct tpm_softc *, int);
+void   tpm_release_locality_tis(struct tpm_softc *);
+void   tpm_release_locality_crb(struct tpm_softc *);
+int    tpm_getburst_tis(struct tpm_softc *);
+int    tpm_getburst_crb(struct tpm_softc *);
 uint8_t        tpm_status(struct tpm_softc *);
 
+uint32_t tpm2_start_method(struct acpi_softc *);
+
 struct cfattach tpm_ca = {
        sizeof(struct tpm_softc),
        tpm_match,
@@ -199,16 +263,35 @@ tpm_attach(struct device *parent, struct device *self, void *aux)
        struct tpm_softc        *sc = (struct tpm_softc *)self;
        struct acpi_attach_args *aaa = aux;
        int64_t                 sta;
+       uint32_t                start_method;
 
        sc->sc_acpi = (struct acpi_softc *)parent;
        sc->sc_devnode = aaa->aaa_node;
        sc->sc_enabled = 0;
+       sc->sc_tpm_mode = TPM_TIS;
 
        printf(" %s", sc->sc_devnode->name);
 
        if (strcmp(aaa->aaa_dev, "MSFT0101") == 0 ||
-           strcmp(aaa->aaa_cdev, "MSFT0101") == 0)
+           strcmp(aaa->aaa_cdev, "MSFT0101") == 0) {
                sc->sc_tpm20 = 1;
+               /* Identify if using 1.2 TIS or 2.0's CRB methods */
+               start_method = tpm2_start_method(sc->sc_acpi);
+               switch (start_method) {
+               case TPM2_START_METHOD_TIS:
+                       /* Already default */
+                       break;
+               case TPM2_START_METHOD_CRB:
+                       sc->sc_tpm_mode = TPM_CRB;
+                       break;
+               default:
+                       printf(": unsupported TPM2 start method\n");
+                       return;
+               }
+       }
+
+       printf(" %s (%s)", sc->sc_tpm20 ? "2.0" : "1.2",
+           sc->sc_tpm_mode == TPM_TIS ? "TIS" : "CRB");
 
        sta = acpi_getsta(sc->sc_acpi, sc->sc_devnode);
        if ((sta & (STA_PRESENT | STA_ENABLED | STA_DEV_OK)) !=
@@ -223,6 +306,7 @@ tpm_attach(struct device *parent, struct device *self, void *aux)
        }
 
        printf(" addr 0x%llx/0x%llx", aaa->aaa_addr[0], aaa->aaa_size[0]);
+       sc->sc_bbase = aaa->aaa_addr[0];
 
        sc->sc_bt = aaa->aaa_bst[0];
        if (bus_space_map(sc->sc_bt, aaa->aaa_addr[0], aaa->aaa_size[0],
@@ -231,14 +315,21 @@ tpm_attach(struct device *parent, struct device *self, void *aux)
                return;
        }
 
-       if (!tpm_probe(sc->sc_bt, sc->sc_bh)) {
-               printf(": probe failed\n");
-               return;
-       }
+       if (sc->sc_tpm_mode == TPM_TIS) {
+               if (!tpm_probe(sc->sc_bt, sc->sc_bh)) {
+                       printf(": probe failed\n");
+                       return;
+               }
 
-       if (tpm_init(sc) != 0) {
-               printf(": init failed\n");
-               return;
+               if (tpm_init_tis(sc) != 0) {
+                       printf(": init failed\n");
+                       return;
+               }
+       } else {
+               if (tpm_init_crb(sc) != 0) {
+                       printf(": init failed\n");
+                       return;
+               }
        }
 
        printf("\n");
@@ -305,9 +396,15 @@ tpm_suspend(struct tpm_softc *sc)
         * Tell the chip to save its state so the BIOS can then restore it upon
         * resume.
         */
-       tpm_write(sc, command, commandlen);
-       tpm_read(sc, command, commandlen, NULL, TPM_HDRSIZE);
-
+       if (sc->sc_tpm_mode == TPM_TIS) {
+               tpm_write_tis(sc, command, commandlen);
+               memset(command, 0, commandlen);
+               tpm_read_tis(sc, command, commandlen, NULL, TPM_HDRSIZE);
+       } else {
+               tpm_write_crb(sc, command, commandlen);
+               memset(command, 0, commandlen);
+               tpm_read_crb(sc, command, commandlen);
+       }
        return 0;
 }
 
@@ -324,6 +421,28 @@ tpm_resume(struct tpm_softc *sc)
        return 0;
 }
 
+uint32_t
+tpm2_start_method(struct acpi_softc *sc)
+{
+       struct acpi_q *entry;
+       struct acpi_tpm2 *p_tpm2 = NULL;
+
+       SIMPLEQ_FOREACH(entry, &sc->sc_tables, q_next) {
+               if (memcmp(entry->q_table, TPM2_SIG,
+                   sizeof(TPM2_SIG) - 1) == 0) {
+                       p_tpm2 = entry->q_table;
+                       break;
+               }
+       }
+
+       if (!p_tpm2) {
+               DPRINTF((", no TPM2 table"));
+               return 0;
+       }
+
+       return p_tpm2->start_method;
+}
+
 int
 tpm_probe(bus_space_tag_t bt, bus_space_handle_t bh)
 {
@@ -350,7 +469,7 @@ tpm_probe(bus_space_tag_t bt, bus_space_handle_t bh)
 }
 
 int
-tpm_init(struct tpm_softc *sc)
+tpm_init_tis(struct tpm_softc *sc)
 {
        uint32_t r, intmask;
        int i;
@@ -369,7 +488,7 @@ tpm_init(struct tpm_softc *sc)
        intmask &= ~TPM_GLOBAL_INT_ENABLE;
        bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE, intmask);
 
-       if (tpm_request_locality(sc, 0)) {
+       if (tpm_request_locality_tis(sc, 0)) {
                printf(", requesting locality failed\n");
                return 1;
        }
@@ -390,7 +509,66 @@ tpm_init(struct tpm_softc *sc)
 }
 
 int
-tpm_request_locality(struct tpm_softc *sc, int l)
+tpm_init_crb(struct tpm_softc *sc)
+{
+       uint32_t intmask;
+       int i;
+
+       if (tpm_request_locality_crb(sc, 0)) {
+               printf(", request locality failed\n");
+               return 1;
+       }
+
+       /* ack and disable all interrupts, we'll be using polling only */
+       intmask = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE);
+       intmask &= ~TPM_CRB_INT_ENABLED_BIT;
+       bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE, intmask);
+
+       /* Identify command and response registers and sizes */
+       sc->sc_cmd_off = bus_space_read_4(sc->sc_bt, sc->sc_bh,
+           TPM_CRB_CTRL_CMD_LADDR);
+       sc->sc_cmd_off |= ((uint64_t) bus_space_read_4(sc->sc_bt, sc->sc_bh,
+           TPM_CRB_CTRL_CMD_HADDR) << 32);
+       sc->sc_cmd_sz = bus_space_read_4(sc->sc_bt, sc->sc_bh,
+           TPM_CRB_CTRL_CMD_SIZE);
+
+       sc->sc_rsp_off = bus_space_read_4(sc->sc_bt, sc->sc_bh,
+           TPM_CRB_CTRL_RSP_LADDR);
+       sc->sc_rsp_off |= ((uint64_t) bus_space_read_4(sc->sc_bt, sc->sc_bh,
+           TPM_CRB_CTRL_RSP_HADDR) << 32);
+       sc->sc_rsp_sz = bus_space_read_4(sc->sc_bt, sc->sc_bh,
+           TPM_CRB_CTRL_RSP_SIZE);
+
+       DPRINTF((", cmd @ 0x%lx, %ld, rsp @ 0x%lx, %ld", sc->sc_cmd_off,
+           sc->sc_cmd_sz, sc->sc_rsp_off, sc->sc_rsp_sz));
+
+       sc->sc_cmd_off = sc->sc_cmd_off - sc->sc_bbase;
+       sc->sc_rsp_off = sc->sc_rsp_off - sc->sc_bbase;
+
+       tpm_release_locality_crb(sc);
+
+       /* If it's a unified buffer, the sizes must be the same. */
+       if (sc->sc_cmd_off == sc->sc_rsp_off) {
+               if (sc->sc_cmd_sz != sc->sc_rsp_sz) {
+                       printf(", invalid buffer sizes\n");
+                       return 1;
+               }
+       }
+
+       for (i = 0; tpm_devs[i].devid; i++)
+               if (tpm_devs[i].devid == sc->sc_devid)
+                       break;
+
+       if (tpm_devs[i].devid)
+               printf(", %s rev 0x%x", tpm_devs[i].name, sc->sc_rev);
+       else
+               printf(", device 0x%08x rev 0x%x", sc->sc_devid, sc->sc_rev);
+
+       return 0;
+}
+
+int
+tpm_request_locality_tis(struct tpm_softc *sc, int l)
 {
        uint32_t r;
        int to;
@@ -424,8 +602,38 @@ tpm_request_locality(struct tpm_softc *sc, int l)
        return 0;
 }
 
+int
+tpm_request_locality_crb(struct tpm_softc *sc, int l)
+{
+       uint32_t r, mask;
+       int to;
+
+       if (l != 0)
+               return EINVAL;
+
+       r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_CTRL);
+       bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_CTRL,
+           r | TPM_CRB_LOC_REQUEST);
+
+       to = TPM_ACCESS_TMO * 200;
+       mask = TPM_CRB_LOC_STATE_ASSIGNED | TPM_CRB_LOC_VALID;
+
+       r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_STATE);
+       while ((r & mask) != mask && to--) {
+               DELAY(10);
+               r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_STATE);
+       }
+
+       if ((r & mask) != mask) {
+               printf(", CRB loc FAILED");
+               return EBUSY;
+       }
+
+       return 0;
+}
+
 void
-tpm_release_locality(struct tpm_softc *sc)
+tpm_release_locality_tis(struct tpm_softc *sc)
 {
        if ((bus_space_read_1(sc->sc_bt, sc->sc_bh, TPM_ACCESS) &
            (TPM_ACCESS_REQUEST_PENDING|TPM_ACCESS_VALID)) ==
@@ -436,6 +644,16 @@ tpm_release_locality(struct tpm_softc *sc)
        }
 }
 
+void
+tpm_release_locality_crb(struct tpm_softc *sc)
+{
+       uint32_t r;
+
+       r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_CTRL);
+       bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_CTRL,
+           r | TPM_CRB_LOC_RELEASE);
+}
+
 int
 tpm_getburst(struct tpm_softc *sc)
 {
@@ -474,7 +692,33 @@ tpm_status(struct tpm_softc *sc)
 }
 
 int
-tpm_waitfor(struct tpm_softc *sc, uint8_t mask, int msecs)
+tpm_waitfor(struct tpm_softc *sc, bus_size_t offset, uint32_t mask,
+    uint32_t val, int msecs)
+{
+       int usecs;
+       uint32_t r;
+
+       usecs = msecs * 1000;
+
+       r = bus_space_read_4(sc->sc_bt, sc->sc_bh, offset);
+       if ((r & mask) == val)
+               return 0;
+
+       while (usecs > 0) {
+               r = bus_space_read_4(sc->sc_bt, sc->sc_bh, offset);
+               if ((r & mask) == val)
+                       return 0;
+               DELAY(1);
+               usecs--;
+       }
+
+       DPRINTF(("%s: %s: timed out, status 0x%x != 0x%x\n",
+                   sc->sc_dev.dv_xname, __func__, r, mask));
+       return ETIMEDOUT;
+}
+
+int
+tpm_waitfor_status(struct tpm_softc *sc, uint8_t mask, int msecs)
 {
        int usecs;
        uint8_t status;
@@ -496,7 +740,7 @@ tpm_waitfor(struct tpm_softc *sc, uint8_t mask, int msecs)
 }
 
 int
-tpm_read(struct tpm_softc *sc, void *buf, int len, size_t *count,
+tpm_read_tis(struct tpm_softc *sc, void *buf, int len, size_t *count,
     int flags)
 {
        uint8_t *p = buf;
@@ -508,8 +752,8 @@ tpm_read(struct tpm_softc *sc, void *buf, int len, size_t *count,
 
        cnt = 0;
        while (len > 0) {
-               if ((rv = tpm_waitfor(sc, TPM_STS_DATA_AVAIL | TPM_STS_VALID,
-                   TPM_READ_TMO)))
+               if ((rv = tpm_waitfor_status(sc,
+                   TPM_STS_DATA_AVAIL | TPM_STS_VALID, TPM_READ_TMO)))
                        return rv;
 
                bcnt = tpm_getburst(sc);
@@ -535,14 +779,76 @@ tpm_read(struct tpm_softc *sc, void *buf, int len, size_t *count,
 }
 
 int
-tpm_write(struct tpm_softc *sc, void *buf, int len)
+tpm_read_crb(struct tpm_softc *sc, void *buf, int len)
+{
+       uint8_t *p = buf;
+       uint32_t sz = 0, mask, rc;
+       size_t count = 0;
+       int r;
+
+       DPRINTF(("%s: %s %d:", sc->sc_dev.dv_xname, __func__, len));
+
+       if (len < TPM_HDRSIZE) {
+               printf("%s: %s buf len too small\n", sc->sc_dev.dv_xname,
+                   __func__);
+               return EINVAL;
+       }
+
+       while (count < TPM_HDRSIZE) {
+               *p = bus_space_read_1(sc->sc_bt, sc->sc_bh,
+                   sc->sc_rsp_off + count);
+               DPRINTF((" %02x", *p));
+               count++;
+               p++;
+       }
+       DPRINTF(("\n"));
+
+       /* Response length is bytes 2-5 in the response header. */
+       p = buf;
+       sz = be32toh(*(uint32_t *) (p + 2));
+       if (sz < TPM_HDRSIZE || sz > sc->sc_rsp_sz) {
+               printf("%s: invalid response size %d\n",
+                   sc->sc_dev.dv_xname, sz);
+               return EIO;
+       }
+       if (sz > len)
+               printf("%s: response size too large, truncated to %d\n",
+                   sc->sc_dev.dv_xname, len);
+
+       /* Response code is bytes 6-9. */
+       rc = be32toh(*(uint32_t *) (p + 6));
+       if (rc != TPM2_RC_SUCCESS) {
+               printf("%s: command failed (0x%04x)\n", sc->sc_dev.dv_xname,
+                   rc);
+               /* Nothing we can do on failure. Still try to idle the tpm. */
+       }
+
+       /* Tell the device to go idle. */
+       r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ);
+       bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ,
+           r | TPM_CRB_CTRL_REQ_GO_IDLE);
+
+       mask = TPM_CRB_CTRL_STS_IDLE_BIT;
+       if (tpm_waitfor(sc, TPM_CRB_CTRL_STS, mask, mask, 200)) {
+               printf("%s: failed to transition to idle state after read\n",
+                   sc->sc_dev.dv_xname);
+       }
+
+       tpm_release_locality_crb(sc);
+
+       DPRINTF(("%s: %s completed\n", sc->sc_dev.dv_xname, __func__));
+       return 0;
+}
+
+int
+tpm_write_tis(struct tpm_softc *sc, void *buf, int len)
 {
        uint8_t *p = buf;
        uint8_t status;
        size_t count = 0;
        int rv, r;
 
-       if ((rv = tpm_request_locality(sc, 0)) != 0)
+       if ((rv = tpm_request_locality_tis(sc, 0)) != 0)
                return rv;
 
        DPRINTF(("%s: %s %d:", sc->sc_dev.dv_xname, __func__, len));
@@ -556,7 +862,8 @@ tpm_write(struct tpm_softc *sc, void *buf, int len)
                /* abort! */
                bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS,
                    TPM_STS_CMD_READY);
-               if ((rv = tpm_waitfor(sc, TPM_STS_CMD_READY, TPM_READ_TMO))) {
+               if ((rv = tpm_waitfor_status(sc, TPM_STS_CMD_READY,
+                   TPM_READ_TMO))) {
                        DPRINTF(("%s: failed waiting for ready after abort "
                            "(0x%x)\n", sc->sc_dev.dv_xname, rv));
                        return rv;
@@ -570,7 +877,7 @@ tpm_write(struct tpm_softc *sc, void *buf, int len)
                        bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_DATA, *p++);
                        count++;
                }
-               if ((rv = tpm_waitfor(sc, TPM_STS_VALID | TPM_STS_DATA_EXPECT,
+               if ((rv = tpm_waitfor_status(sc, TPM_STS_VALID | TPM_STS_DATA_EXPECT,
                    TPM_READ_TMO))) {
                        DPRINTF(("%s: %s: failed waiting for next byte (%d)\n",
                            sc->sc_dev.dv_xname, __func__, rv));
@@ -583,7 +890,7 @@ tpm_write(struct tpm_softc *sc, void *buf, int len)
        bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_DATA, *p);
        count++;
 
-       if ((rv = tpm_waitfor(sc, TPM_STS_VALID, TPM_READ_TMO))) {
+       if ((rv = tpm_waitfor_status(sc, TPM_STS_VALID, TPM_READ_TMO))) {
                DPRINTF(("%s: %s: failed after last byte (%d)\n",
                    sc->sc_dev.dv_xname, __func__, rv));
                return rv;
@@ -603,3 +910,87 @@ tpm_write(struct tpm_softc *sc, void *buf, int len)
 
        return 0;
 }
+
+int
+tpm_write_crb(struct tpm_softc *sc, void *buf, int len)
+{
+       uint8_t *p = buf;
+       size_t count = 0;
+       uint32_t r, mask;
+
+       if (len > sc->sc_cmd_sz) {
+               printf("%s: requested write length larger than cmd buffer\n",
+                   sc->sc_dev.dv_xname);
+               return EINVAL;
+       }
+
+       if (bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_STS)
+           & TPM_CRB_CTRL_STS_ERR_BIT) {
+               printf("%s: device error bit set\n", sc->sc_dev.dv_xname);
+               return EIO;
+       }
+
+       if (tpm_request_locality_crb(sc, 0)) {
+               printf("%s: failed to acquire locality\n", sc->sc_dev.dv_xname);
+               return EIO;
+       }
+
+       /* Clear cancellation bit */
+       bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_CANCEL,
+           TPM_CRB_CTRL_CANCEL_CLEAR);
+
+       /* Toggle to idle state (if needed) and then to ready */
+       r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_STS);
+       if(!(r & TPM_CRB_CTRL_STS_IDLE_BIT)) {
+               printf("%s: asking device to idle\n", sc->sc_dev.dv_xname);
+               r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ);
+               bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ,
+                   r | TPM_CRB_CTRL_REQ_GO_IDLE);
+
+               mask = TPM_CRB_CTRL_STS_IDLE_BIT;
+               if (tpm_waitfor(sc, TPM_CRB_CTRL_STS, mask, mask, 200)) {
+                       printf("%s: failed to transition to idle state before "
+                           "write\n", sc->sc_dev.dv_xname);
+                       return EIO;
+               }
+       }
+       r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ);
+       bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ,
+           r | TPM_CRB_CTRL_REQ_GO_READY);
+       mask = TPM_CRB_CTRL_REQ_GO_READY;
+       if (tpm_waitfor(sc, TPM_CRB_CTRL_STS, mask, !mask, 200)) {
+               printf("%s: failed to transition to ready state\n",
+                   sc->sc_dev.dv_xname);
+               return EIO;
+       }
+
+       /* Write the command */
+       DPRINTF(("%s: %s %d:", sc->sc_dev.dv_xname, __func__, len));
+       while (count < len) {
+               DPRINTF((" %02x", (uint8_t)(*p)));
+               bus_space_write_1(sc->sc_bt, sc->sc_bh, sc->sc_cmd_off + count,
+                   *p++);
+               count++;
+       }
+       DPRINTF(("\n"));
+       bus_space_barrier(sc->sc_bt, sc->sc_bh, sc->sc_cmd_off, len,
+           BUS_SPACE_BARRIER_WRITE);
+       DPRINTF(("%s: %s wrote %lu bytes\n", sc->sc_dev.dv_xname, __func__,
+           count));
+
+       /* Send the Start Command request */
+       bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_START,
+           TPM_CRB_CTRL_START_CMD);
+       bus_space_barrier(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_START, 4,
+           BUS_SPACE_BARRIER_WRITE);
+
+       /* Check if command was processed */
+       mask = ~0;
+       if (tpm_waitfor(sc, TPM_CRB_CTRL_START, mask, ~mask, 200)) {
+               printf("%s: timeout waiting for device to process command\n",
+                   sc->sc_dev.dv_xname);
+               return EIO;
+       }
+
+       return 0;
+}