git: f0ac0f73aea7 - main - tpm: crb: Improve ACPI control and buffer addresses

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Fri, 30 Oct 2026 22:07:23 UTC
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=f0ac0f73aea71fdcd70408c046cfca88d8a34076

commit f0ac0f73aea71fdcd70408c046cfca88d8a34076
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-09-18 06:35:27 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-10-30 22:06:53 +0000

    tpm: crb: Improve ACPI control and buffer addresses
    
    The TPM2 ControlAddress identifies the control area, not necessarily a
    PTP locality block.  Hyper-V exposes a start-method-7 control area at the
    beginning of its ACPI memory resource, with command and response buffers
    in separate reserved memory pages.  Assuming control registers are at
    resource + 0x40 instead writes CANCEL as LOC_CTRL and fails attach.
    
    Map the control area using ControlAddress and map buffers using their
    reported physical addresses.  Reuse the resource mapping for contained
    ranges and track ownership of independent mappings for cleanup.  Request
    locality only when the resource contains the preceding 64-byte PTP header.
    Keep the existing Pluton notification callbacks and locality behavior for
    that layout.  Control, locality, and buffer accesses use their respective
    mappings directly, without the tpm_bus accessor class.
    
    Bound transfers by the mapped buffer sizes and TPM_BUFSIZE.  Clamp an
    overstated buffer that starts within the ACPI resource to its remaining
    extent, while requiring room for a complete response header.  Keep strict
    control-area bounds and the shared-buffer size consistency check.
    
    Write locality and state requests directly and wait for the corresponding
    CTRL_REQ bit to clear.  The old ready wait instead tested the fatal-status
    bit in CTRL_STS.  Enter ready state before reading buffer configuration,
    and check fatal status again before reading a response, while locality
    is still held.  Retain common error cleanup and diagnose attach-time idle
    transition failures without replacing an earlier error.
    
    These layouts and register semantics are defined by TCG ACPI 1.5 Table 7
    and PC Client PTP 1.03 sections 5.5.3.1, 5.5.3.4, and 5.5.3.5.
    
    Validation: a Windows Server 2025 Hyper-V FreeBSD guest attached and
    passed manufacturer queries, 700 GetRandom/PCR_Read pairs, concurrent
    clients, five detach/reattach cycles, and device suspend/resume
    with entropy harvesting active.
    
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
    Differential Revision:  https://reviews.freebsd.org/D59786
---
 sys/dev/tpm/tpm_crb.c | 413 +++++++++++++++++++++++++++++++++-----------------
 1 file changed, 276 insertions(+), 137 deletions(-)

diff --git a/sys/dev/tpm/tpm_crb.c b/sys/dev/tpm/tpm_crb.c
index f62d350948c8..ac472ce40eda 100644
--- a/sys/dev/tpm/tpm_crb.c
+++ b/sys/dev/tpm/tpm_crb.c
@@ -29,27 +29,29 @@
 #include "tpm20.h"
 
 /*
- * CRB register space as defined in
- * TCG_PC_Client_Platform_TPM_Profile_PTP_2.0_r1.03_v22
+ * PTP locality registers, relative to the start of the ACPI memory resource.
+ * TCG PC Client PTP 1.03, section 5.5.3.1, Table 23.
  */
 #define	TPM_LOC_STATE			0x0
 #define	TPM_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_INT_ENABLE		0x50
-#define	TPM_CRB_INT_STS			0x54
-#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_ADDR		0x68
-#define	TPM_CRB_CTRL_RSP_HADDR		0x6c
-#define	TPM_CRB_DATA_BUFFER		0x80
+#define	TPM_CRB_LOCALITY_SIZE		0x40
+
+/* Relative to the ControlAddress in the TPM2 ACPI table, not the resource. */
+#define	TPM_CRB_CTRL_REQ		0x00
+#define	TPM_CRB_CTRL_STS		0x04
+#define	TPM_CRB_CTRL_CANCEL		0x08
+#define	TPM_CRB_CTRL_START		0x0C
+#define	TPM_CRB_INT_ENABLE		0x10
+#define	TPM_CRB_INT_STS			0x14
+#define	TPM_CRB_CTRL_CMD_SIZE		0x18
+#define	TPM_CRB_CTRL_CMD_LADDR		0x1C
+#define	TPM_CRB_CTRL_CMD_HADDR		0x20
+#define	TPM_CRB_CTRL_RSP_SIZE		0x24
+#define	TPM_CRB_CTRL_RSP_ADDR		0x28
+#define	TPM_CRB_CTRL_SIZE		0x30
 
 #define	TPM_LOC_STATE_ESTB		BIT(0)
 #define	TPM_LOC_STATE_ASSIGNED		BIT(1)
@@ -115,6 +117,12 @@ static const struct tpmcrb_sm_cfg_map {
 	},
 };
 
+struct tpmcrb_mapping {
+	bus_space_handle_t	bsh;
+	bus_size_t		size;
+	bool			mapped;	/* Owns a mapping outside mem_res. */
+};
+
 struct tpmcrb_sc {
 	struct tpm_sc	base;
 	const struct tpmcrb_sm_cfg	*sm_cfg;
@@ -130,10 +138,11 @@ struct tpmcrb_sc {
 			bus_space_handle_t	reply_bsh;
 		} pluton;
 	};
-	bus_size_t	cmd_off;
-	bus_size_t	rsp_off;
-	size_t		cmd_buf_size;
-	size_t		rsp_buf_size;
+	bus_space_tag_t	bst;
+	struct tpmcrb_mapping ctrl;
+	struct tpmcrb_mapping cmd;
+	struct tpmcrb_mapping rsp;
+	bool		has_locality;
 };
 
 int tpmcrb_transmit(device_t dev, struct tpm_priv *priv, size_t size);
@@ -142,13 +151,52 @@ static int tpmcrb_acpi_probe(device_t dev);
 static int tpmcrb_attach(device_t dev);
 static int tpmcrb_detach(device_t dev);
 
-static ACPI_STATUS tpmcrb_fix_buff_offsets(ACPI_RESOURCE *res, void *arg);
-
-static bool tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off,
+static bool tpmcrb_ctrl_wait(struct tpmcrb_sc *crb_sc, bus_size_t off,
     uint32_t mask, uint32_t val, int32_t timeout);
-static bool tpmcrb_request_locality(struct tpm_sc *sc, int locality);
-static void tpmcrb_relinquish_locality(struct tpm_sc *sc);
-static bool tpmcrb_cancel_cmd(struct tpm_sc *sc);
+static bool tpmcrb_request_locality(struct tpmcrb_sc *crb_sc, int locality);
+static void tpmcrb_relinquish_locality(struct tpmcrb_sc *crb_sc);
+static bool tpmcrb_cancel_cmd(struct tpmcrb_sc *crb_sc);
+static bool tpmcrb_state_idle(struct tpmcrb_sc *crb_sc, bool wait);
+static bool tpmcrb_state_ready(struct tpmcrb_sc *crb_sc, bool wait);
+
+/* All control-register offsets are relative to the mapped control area. */
+static uint32_t
+tpmcrb_ctrl_read_4(struct tpmcrb_sc *crb_sc, bus_size_t off)
+{
+
+	return (bus_space_read_4(crb_sc->bst, crb_sc->ctrl.bsh, off));
+}
+
+static uint64_t
+tpmcrb_ctrl_read_8(struct tpmcrb_sc *crb_sc, bus_size_t off)
+{
+#ifdef __i386__
+	uint64_t val;
+
+	val = tpmcrb_ctrl_read_4(crb_sc, off);
+	val |= (uint64_t)tpmcrb_ctrl_read_4(crb_sc, off + 4) << 32;
+	return (val);
+#else
+
+	return (bus_space_read_8(crb_sc->bst, crb_sc->ctrl.bsh, off));
+#endif
+}
+
+static void
+tpmcrb_ctrl_write_4(struct tpmcrb_sc *crb_sc, bus_size_t off, uint32_t val)
+{
+
+	bus_space_write_4(crb_sc->bst, crb_sc->ctrl.bsh, off, val);
+}
+
+static void
+tpmcrb_ctrl_write_barrier(struct tpmcrb_sc *crb_sc, bus_size_t off,
+    bus_size_t length)
+{
+
+	bus_space_barrier(crb_sc->bst, crb_sc->ctrl.bsh, off, length,
+	    BUS_SPACE_BARRIER_WRITE);
+}
 
 char *tpmcrb_ids[] = {"MSFT0101", NULL};
 
@@ -182,6 +230,10 @@ tpmcrb_acpi_probe(device_t dev)
 	status = AcpiGetTable(ACPI_SIG_TPM2, 1, (ACPI_TABLE_HEADER **) &tbl);
 	if (ACPI_FAILURE(status))
 		return (ENXIO);
+	if (tbl->Header.Length < sizeof(*tbl)) {
+		AcpiPutTable((ACPI_TABLE_HEADER *)tbl);
+		return (ENXIO);
+	}
 
 	smethod = tbl->StartMethod;
 	AcpiPutTable((ACPI_TABLE_HEADER *)tbl);
@@ -194,31 +246,65 @@ tpmcrb_acpi_probe(device_t dev)
 	return (0);
 }
 
-static ACPI_STATUS
-tpmcrb_fix_buff_offsets(ACPI_RESOURCE *res, void *arg)
+/* Reuse the resource mapping when possible; buffers may be in reserved RAM. */
+static int
+tpmcrb_map(struct tpmcrb_sc *crb_sc, struct tpmcrb_mapping *map,
+    uint64_t addr, bus_size_t size)
 {
-	struct tpmcrb_sc *crb_sc;
-	size_t length;
-	uint32_t base_addr;
-
-	crb_sc = (struct tpmcrb_sc *)arg;
-
-	if (res->Type != ACPI_RESOURCE_TYPE_FIXED_MEMORY32)
-		return (AE_OK);
+	struct resource *res = crb_sc->base.mem_res;
+	rman_res_t start, end;
+	int error;
+
+	if (addr == 0 || size == 0 || addr > BUS_SPACE_MAXADDR ||
+	    size - 1 > BUS_SPACE_MAXADDR - addr)
+		return (EINVAL);
+	start = rman_get_start(res);
+	end = rman_get_end(res);
+	if (addr <= end && addr + size - 1 >= start) {
+		if (addr < start || addr + size - 1 > end)
+			return (EINVAL);
+		error = bus_space_subregion(crb_sc->bst, rman_get_bushandle(res),
+		    addr - start, size, &map->bsh);
+	} else {
+		error = bus_space_map(crb_sc->bst, addr, size, 0, &map->bsh);
+		if (error == 0)
+			map->mapped = true;
+	}
+	if (error == 0)
+		map->size = size;
+	return (error);
+}
 
-	base_addr = res->Data.FixedMemory32.Address;
-	length = res->Data.FixedMemory32.AddressLength;
+static void
+tpmcrb_unmap(struct tpmcrb_sc *crb_sc, struct tpmcrb_mapping *map)
+{
 
-	if (crb_sc->cmd_off > base_addr && crb_sc->cmd_off < base_addr + length)
-		crb_sc->cmd_off -= base_addr;
-	if (crb_sc->rsp_off > base_addr && crb_sc->rsp_off < base_addr + length)
-		crb_sc->rsp_off -= base_addr;
+	if (map->mapped)
+		bus_space_unmap(crb_sc->bst, map->bsh, map->size);
+	memset(map, 0, sizeof(*map));
+}
 
-	return (AE_OK);
+static int
+tpmcrb_map_buffer(struct tpmcrb_sc *crb_sc, struct tpmcrb_mapping *map,
+    uint64_t addr, uint32_t size)
+{
+	struct resource *res = crb_sc->base.mem_res;
+	rman_res_t start, end;
+
+	/* The character device never transfers more than TPM_BUFSIZE bytes. */
+	size = MIN(size, TPM_BUFSIZE);
+	start = rman_get_start(res);
+	end = rman_get_end(res);
+	/* Firmware can report a buffer larger than its ACPI memory resource. */
+	if (addr >= start && addr <= end && end - addr + 1 < size)
+		size = end - addr + 1;
+	if (size < TPM_HEADER_SIZE)
+		return (EINVAL);
+	return (tpmcrb_map(crb_sc, map, addr, size));
 }
 
 static bool
-tpmcrb_attach_startmethod(struct tpmcrb_sc *crb_sc)
+tpmcrb_attach_acpi(struct tpmcrb_sc *crb_sc)
 {
 	const struct tpmcrb_sm_cfg_map *sm_cfg_map;
 	const struct tpmcrb_sm_cfg *sm_cfg;
@@ -227,12 +313,30 @@ tpmcrb_attach_startmethod(struct tpmcrb_sc *crb_sc)
 	ACPI_STATUS status;
 	bool ret;
 
-	/*
-	 * Grab what we need from the StartMethod.
-	 */
 	status = AcpiGetTable(ACPI_SIG_TPM2, 1, (ACPI_TABLE_HEADER **)(void **)&tbl);
 	if (ACPI_FAILURE(status))
 		return (false);
+	if (tbl->Header.Length < sizeof(*tbl)) {
+		AcpiPutTable((ACPI_TABLE_HEADER *)tbl);
+		return (false);
+	}
+
+	/*
+	 * TCG ACPI 1.5, Table 7: ControlAddress points at CTRL_REQ.  Only
+	 * the PTP layout (PTP 1.03, Table 23) has locality registers in the
+	 * preceding 64 bytes.  A control-area-only interface has no such
+	 * registers; do not interpret its Cancel register as LOC_CTRL.
+	 */
+	if ((tbl->ControlAddress & 7) != 0 ||
+	    tpmcrb_map(crb_sc, &crb_sc->ctrl, tbl->ControlAddress,
+	    TPM_CRB_CTRL_SIZE) != 0) {
+		device_printf(crb_sc->base.dev, "Cannot map CRB control area\n");
+		AcpiPutTable((ACPI_TABLE_HEADER *)tbl);
+		return (false);
+	}
+	crb_sc->has_locality = tbl->ControlAddress >= TPM_CRB_LOCALITY_SIZE &&
+	    tbl->ControlAddress - TPM_CRB_LOCALITY_SIZE ==
+	    rman_get_start(crb_sc->base.mem_res);
 
 	sm_cfg_map = tpmcrb_acpi_startmethod_cfg(tbl->StartMethod);
 	MPASS(sm_cfg_map != NULL);
@@ -256,13 +360,12 @@ tpmcrb_attach(device_t dev)
 {
 	struct tpmcrb_sc *crb_sc;
 	struct tpm_sc *sc;
-	ACPI_HANDLE handle;
-	ACPI_STATUS status;
+	uint64_t cmd_addr, rsp_addr;
+	uint32_t cmd_size, rsp_size;
 	int result;
 
 	crb_sc = device_get_softc(dev);
 	sc = &crb_sc->base;
-	handle = acpi_get_handle(dev);
 	sc->dev = dev;
 
 	sx_init(&sc->dev_lock, "TPM driver lock");
@@ -274,65 +377,78 @@ tpmcrb_attach(device_t dev)
 		tpmcrb_detach(dev);
 		return (ENXIO);
 	}
+	crb_sc->bst = rman_get_bustag(sc->mem_res);
 
-	if (!tpmcrb_attach_startmethod(crb_sc)) {
+	if (!tpmcrb_attach_acpi(crb_sc)) {
 		tpmcrb_detach(dev);
 		return (ENXIO);
 	}
 
-	if(!tpmcrb_request_locality(sc, 0)) {
+	if (!tpmcrb_request_locality(crb_sc, 0)) {
+		device_printf(dev, "Failed to obtain locality\n");
 		tpmcrb_detach(dev);
 		return (ENXIO);
 	}
+	if (!tpmcrb_state_ready(crb_sc, true)) {
+		device_printf(dev, "Failed to transition to ready state\n");
+		result = ENXIO;
+		goto out;
+	}
 
 	/*
 	 * Disable all interrupts for now, since I don't have a device that
 	 * works in CRB mode and supports them.
 	 */
-	AND4(sc, TPM_CRB_INT_ENABLE, ~TPM_CRB_INT_ENABLE_BIT);
+	tpmcrb_ctrl_write_4(crb_sc, TPM_CRB_INT_ENABLE,
+	    tpmcrb_ctrl_read_4(crb_sc, TPM_CRB_INT_ENABLE) &
+	    ~TPM_CRB_INT_ENABLE_BIT);
 	sc->interrupts = false;
 
 	/*
 	 * Read addresses of Tx/Rx buffers and their sizes. Note that they
-	 * can be implemented by a single buffer. Also for some reason CMD
-	 * addr is stored in two 4 byte neighboring registers, whereas RSP is
-	 * stored in a single 8 byte one.
+	 * can be implemented by a single buffer.  PTP 1.03, section 5.5.3.1
+	 * requires naturally aligned accesses: the command address needs two
+	 * 32-bit reads, whereas the response address permits a 64-bit read.
 	 */
-#ifdef __amd64__
-	crb_sc->rsp_off = TPM_READ_8(sc->dev, TPM_CRB_CTRL_RSP_ADDR);
-#else
-	crb_sc->rsp_off = TPM_READ_4(sc->dev, TPM_CRB_CTRL_RSP_ADDR);
-	crb_sc->rsp_off |= ((uint64_t) TPM_READ_4(sc->dev, TPM_CRB_CTRL_RSP_HADDR) << 32);
-#endif
-	crb_sc->cmd_off = TPM_READ_4(sc->dev, TPM_CRB_CTRL_CMD_LADDR);
-	crb_sc->cmd_off |= ((uint64_t) TPM_READ_4(sc->dev, TPM_CRB_CTRL_CMD_HADDR) << 32);
-	crb_sc->cmd_buf_size = TPM_READ_4(sc->dev, TPM_CRB_CTRL_CMD_SIZE);
-	crb_sc->rsp_buf_size = TPM_READ_4(sc->dev, TPM_CRB_CTRL_RSP_SIZE);
-
-	tpmcrb_relinquish_locality(sc);
-
-	/* Emulator returns address in acpi space instead of an offset */
-	status = AcpiWalkResources(handle, "_CRS", tpmcrb_fix_buff_offsets,
-		    (void *)crb_sc);
-	if (ACPI_FAILURE(status)) {
-		tpmcrb_detach(dev);
-		return (ENXIO);
+	rsp_addr = tpmcrb_ctrl_read_8(crb_sc, TPM_CRB_CTRL_RSP_ADDR);
+	cmd_addr = tpmcrb_ctrl_read_4(crb_sc, TPM_CRB_CTRL_CMD_LADDR);
+	cmd_addr |= (uint64_t)tpmcrb_ctrl_read_4(crb_sc,
+	    TPM_CRB_CTRL_CMD_HADDR) << 32;
+	cmd_size = tpmcrb_ctrl_read_4(crb_sc, TPM_CRB_CTRL_CMD_SIZE);
+	rsp_size = tpmcrb_ctrl_read_4(crb_sc, TPM_CRB_CTRL_RSP_SIZE);
+
+	/* PTP 1.03, Table 23 requires equal sizes for a shared buffer. */
+	if (cmd_size < TPM_HEADER_SIZE || rsp_size < TPM_HEADER_SIZE ||
+	    (cmd_addr == rsp_addr && cmd_size != rsp_size)) {
+		device_printf(dev, "Invalid CRB buffer sizes (%u, %u)\n",
+		    cmd_size, rsp_size);
+		result = ENXIO;
+		goto out;
+	}
+	result = tpmcrb_map_buffer(crb_sc, &crb_sc->cmd, cmd_addr, cmd_size);
+	if (result != 0) {
+		device_printf(dev, "Cannot map CRB command buffer\n");
+		goto out;
+	}
+	if (cmd_addr == rsp_addr) {
+		crb_sc->rsp = crb_sc->cmd;
+		crb_sc->rsp.mapped = false;
+	} else {
+		result = tpmcrb_map_buffer(crb_sc, &crb_sc->rsp, rsp_addr, rsp_size);
+		if (result != 0)
+			device_printf(dev, "Cannot map CRB response buffer\n");
 	}
 
-	if (crb_sc->rsp_off == crb_sc->cmd_off) {
-		/*
-		 * If Tx/Rx buffers are implemented as one they have to be of
-		 * same size
-		 */
-		if (crb_sc->cmd_buf_size != crb_sc->rsp_buf_size) {
-			device_printf(sc->dev,
-			    "Overlapping Tx/Rx buffers have different sizes\n");
-			tpmcrb_detach(dev);
-			return (ENXIO);
-		}
+out:
+	if (!tpmcrb_state_idle(crb_sc, true)) {
+		device_printf(dev, "Failed to transition to idle state\n");
+		if (result == 0)
+			result = ENXIO;
 	}
+	tpmcrb_relinquish_locality(crb_sc);
+	if (result == 0)
+		result = tpm20_init(sc);
 
-	result = tpm20_init(sc);
 	if (result != 0)
 		tpmcrb_detach(dev);
 
@@ -351,25 +467,31 @@ tpmcrb_detach(device_t dev)
 
 	if (crb_sc->sm_cfg != NULL && crb_sc->sm_cfg->sm_detach != NULL)
 		(*crb_sc->sm_cfg->sm_detach)(crb_sc);
+	crb_sc->sm_cfg = NULL;
+	tpmcrb_unmap(crb_sc, &crb_sc->rsp);
+	tpmcrb_unmap(crb_sc, &crb_sc->cmd);
+	tpmcrb_unmap(crb_sc, &crb_sc->ctrl);
 
-	if (sc->mem_res != NULL)
+	if (sc->mem_res != NULL) {
 		bus_release_resource(dev, SYS_RES_MEMORY,
 		    sc->mem_rid, sc->mem_res);
+		sc->mem_res = NULL;
+	}
 
 	return (0);
 }
 
 static bool
-tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off, uint32_t mask, uint32_t val,
-    int32_t timeout)
+tpmcrb_ctrl_wait(struct tpmcrb_sc *crb_sc, bus_size_t off, uint32_t mask,
+    uint32_t val, int32_t timeout)
 {
 
 	/* Check for condition */
-	if ((TPM_READ_4(sc->dev, off) & mask) == val)
+	if ((tpmcrb_ctrl_read_4(crb_sc, off) & mask) == val)
 		return (true);
 
 	while (timeout > 0) {
-		if ((TPM_READ_4(sc->dev, off) & mask) == val)
+		if ((tpmcrb_ctrl_read_4(crb_sc, off) & mask) == val)
 			return (true);
 
 		pause("TPM in polling mode", 1);
@@ -379,44 +501,52 @@ tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off, uint32_t mask, uint32_t val,
 }
 
 static bool
-tpmcrb_request_locality(struct tpm_sc *sc, int locality)
+tpmcrb_request_locality(struct tpmcrb_sc *crb_sc, int locality)
 {
+	struct resource *res = crb_sc->base.mem_res;
 	uint32_t mask;
+	int timeout;
 
 	/* Currently we only support Locality 0 */
 	if (locality != 0)
 		return (false);
+	if (!crb_sc->has_locality)
+		return (true);
 
 	mask = TPM_LOC_STATE_VALID | TPM_LOC_STATE_ASSIGNED;
 
-	OR4(sc, TPM_LOC_CTRL, TPM_LOC_CTRL_REQUEST);
-	if (!tpm_wait_for_u32(sc, TPM_LOC_STATE, mask, mask, TPM_TIMEOUT_C))
-		return (false);
-
-	return (true);
+	bus_write_4(res, TPM_LOC_CTRL, TPM_LOC_CTRL_REQUEST);
+	for (timeout = TPM_TIMEOUT_C; timeout > 0; timeout -= tick) {
+		if ((bus_read_4(res, TPM_LOC_STATE) & mask) == mask)
+			return (true);
+		pause("TPM locality", 1);
+	}
+	return (false);
 }
 
 static void
-tpmcrb_relinquish_locality(struct tpm_sc *sc)
+tpmcrb_relinquish_locality(struct tpmcrb_sc *crb_sc)
 {
 
-	OR4(sc, TPM_LOC_CTRL, TPM_LOC_CTRL_RELINQUISH);
+	if (crb_sc->has_locality)
+		bus_write_4(crb_sc->base.mem_res, TPM_LOC_CTRL,
+		    TPM_LOC_CTRL_RELINQUISH);
 }
 
 static bool
-tpmcrb_cancel_cmd(struct tpm_sc *sc)
+tpmcrb_cancel_cmd(struct tpmcrb_sc *crb_sc)
 {
 	uint32_t mask = ~0;
 
-	TPM_WRITE_4(sc->dev, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CMD);
-	if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_START,
+	tpmcrb_ctrl_write_4(crb_sc, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CMD);
+	if (!tpmcrb_ctrl_wait(crb_sc, TPM_CRB_CTRL_START,
 		    mask, ~mask, TPM_TIMEOUT_B)) {
-		device_printf(sc->dev,
+		device_printf(crb_sc->base.dev,
 		    "Device failed to cancel command\n");
 		return (false);
 	}
 
-	TPM_WRITE_4(sc->dev, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CLEAR);
+	tpmcrb_ctrl_write_4(crb_sc, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CLEAR);
 	return (true);
 }
 
@@ -447,20 +577,19 @@ tpmcrb_notify_state_changing(struct tpmcrb_sc *crb_sc, int timeout)
 static bool
 tpmcrb_state_idle(struct tpmcrb_sc *crb_sc, bool wait)
 {
-	struct tpm_sc *sc;
 	int mask, timeout;
 
 	timeout = wait ? TPM_TIMEOUT_C : 0;
 
-	sc = &crb_sc->base;
-	OR4(sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_IDLE);
+	tpmcrb_ctrl_write_4(crb_sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_IDLE);
 
 	if (!tpmcrb_notify_state_changing(crb_sc, timeout))
 		return (false);
 
 	if (timeout > 0) {
-		mask = TPM_CRB_CTRL_STS_IDLE_BIT;
-		if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_STS, mask, mask,
+		/* PTP 1.03, 5.5.3.4: the TPM clears the request to acknowledge. */
+		mask = TPM_CRB_CTRL_REQ_GO_IDLE;
+		if (!tpmcrb_ctrl_wait(crb_sc, TPM_CRB_CTRL_REQ, mask, 0,
 		    timeout))
 			return (false);
 	}
@@ -471,20 +600,18 @@ tpmcrb_state_idle(struct tpmcrb_sc *crb_sc, bool wait)
 static bool
 tpmcrb_state_ready(struct tpmcrb_sc *crb_sc, bool wait)
 {
-	struct tpm_sc *sc;
 	int mask, timeout;
 
 	timeout = wait ? TPM_TIMEOUT_C : 0;
 
-	sc = &crb_sc->base;
-	OR4(sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_READY);
+	tpmcrb_ctrl_write_4(crb_sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_READY);
 
 	if (!tpmcrb_notify_state_changing(crb_sc, timeout))
 		return (false);
 
 	if (timeout > 0) {
 		mask = TPM_CRB_CTRL_REQ_GO_READY;
-		if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_STS, mask, !mask,
+		if (!tpmcrb_ctrl_wait(crb_sc, TPM_CRB_CTRL_REQ, mask, 0,
 		    timeout))
 			return (false);
 	}
@@ -496,25 +623,23 @@ int
 tpmcrb_transmit(device_t dev, struct tpm_priv *priv, size_t length)
 {
 	struct tpmcrb_sc *crb_sc;
-	struct tpm_sc *sc;
 	uint32_t mask, curr_cmd;
 	int bytes_available, error, timeout;
 	bool command_started, locality;
 
 	crb_sc = device_get_softc(dev);
-	sc = &crb_sc->base;
 
-	sx_assert(&sc->dev_lock, SA_XLOCKED);
+	sx_assert(&crb_sc->base.dev_lock, SA_XLOCKED);
 	command_started = false;
 	locality = false;
 
-	if (length > crb_sc->cmd_buf_size) {
+	if (length > crb_sc->cmd.size) {
 		device_printf(dev,
 		    "Requested transfer is bigger than buffer size\n");
 		return (E2BIG);
 	}
 
-	if (!tpmcrb_request_locality(sc, 0)) {
+	if (!tpmcrb_request_locality(crb_sc, 0)) {
 		device_printf(dev,
 		    "Failed to obtain locality\n");
 		return (EIO);
@@ -527,17 +652,19 @@ tpmcrb_transmit(device_t dev, struct tpm_priv *priv, size_t length)
 	 * before then.  An AMD Pluton fTPM using the plain CRB start method
 	 * reads all-ones, which looks like a permanently stuck tpmSts.
 	 */
-	if (TPM_READ_4(dev, TPM_CRB_CTRL_STS) & TPM_CRB_CTRL_STS_ERR_BIT) {
+	if (tpmcrb_ctrl_read_4(crb_sc, TPM_CRB_CTRL_STS) &
+	    TPM_CRB_CTRL_STS_ERR_BIT) {
 		device_printf(dev,
 		    "Device has Error bit set\n");
 		error = EIO;
 		goto out;
 	}
 	/* Clear cancellation bit */
-	TPM_WRITE_4(dev, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CLEAR);
+	tpmcrb_ctrl_write_4(crb_sc, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CLEAR);
 
 	/* Switch device to idle state if necessary */
-	if (!(TPM_READ_4(dev, TPM_CRB_CTRL_STS) & TPM_CRB_CTRL_STS_IDLE_BIT)) {
+	if (!(tpmcrb_ctrl_read_4(crb_sc, TPM_CRB_CTRL_STS) &
+	    TPM_CRB_CTRL_STS_IDLE_BIT)) {
 		if (!tpmcrb_state_idle(crb_sc, true)) {
 			device_printf(dev,
 			    "Failed to transition to idle state\n");
@@ -562,39 +689,51 @@ tpmcrb_transmit(device_t dev, struct tpm_priv *priv, size_t length)
 	timeout = tpm20_get_timeout(curr_cmd);
 
 	/* Send command and tell device to process it. */
-	bus_write_region_stream_1(sc->mem_res, crb_sc->cmd_off,
+	bus_space_write_region_stream_1(crb_sc->bst, crb_sc->cmd.bsh, 0,
 	    priv->buf, length);
-	TPM_WRITE_BARRIER(dev, crb_sc->cmd_off, length);
+	bus_space_barrier(crb_sc->bst, crb_sc->cmd.bsh, 0, length,
+	    BUS_SPACE_BARRIER_WRITE);
 
-	TPM_WRITE_4(dev, TPM_CRB_CTRL_START, TPM_CRB_CTRL_START_CMD);
-	TPM_WRITE_BARRIER(dev, TPM_CRB_CTRL_START, 4);
+	tpmcrb_ctrl_write_4(crb_sc, TPM_CRB_CTRL_START, TPM_CRB_CTRL_START_CMD);
+	tpmcrb_ctrl_write_barrier(crb_sc, TPM_CRB_CTRL_START, 4);
 	command_started = true;
 
 	if (!tpmcrb_notify_cmdready(crb_sc, timeout)) {
 		device_printf(dev,
 		    "Timeout while waiting for device to ready\n");
-		if (!tpmcrb_cancel_cmd(sc)) {
+		if (!tpmcrb_cancel_cmd(crb_sc)) {
 			error = EIO;
 			goto out;
 		}
 	}
 
 	mask = ~0;
-	if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_START, mask, ~mask, timeout)) {
+	if (!tpmcrb_ctrl_wait(crb_sc, TPM_CRB_CTRL_START, mask, ~mask, timeout)) {
 		device_printf(dev,
 		    "Timeout while waiting for device to process cmd\n");
-		if (!tpmcrb_cancel_cmd(sc)) {
+		if (!tpmcrb_cancel_cmd(crb_sc)) {
 			error = EIO;
 			goto out;
 		}
 	}
 
+	/* A fatal error during execution may leave no valid response. */
+	if (tpmcrb_ctrl_read_4(crb_sc, TPM_CRB_CTRL_STS) &
+	    TPM_CRB_CTRL_STS_ERR_BIT) {
+		device_printf(dev, "Device has Error bit set after command\n");
+		error = EIO;
+		goto out;
+	}
+
 	/* Read response header. Length is passed in bytes 2 - 6. */
-	bus_read_region_stream_1(sc->mem_res, crb_sc->rsp_off,
+	bus_space_barrier(crb_sc->bst, crb_sc->rsp.bsh, 0, crb_sc->rsp.size,
+	    BUS_SPACE_BARRIER_READ);
+	bus_space_read_region_stream_1(crb_sc->bst, crb_sc->rsp.bsh, 0,
 	    priv->buf, TPM_HEADER_SIZE);
 	bytes_available = be32toh(*(uint32_t *) (&priv->buf[2]));
 
-	if (bytes_available > TPM_BUFSIZE || bytes_available < TPM_HEADER_SIZE) {
+	if (bytes_available < TPM_HEADER_SIZE ||
+	    bytes_available > crb_sc->rsp.size) {
 		device_printf(dev,
 		    "Incorrect response size: %d\n",
 		    bytes_available);
@@ -602,8 +741,9 @@ tpmcrb_transmit(device_t dev, struct tpm_priv *priv, size_t length)
 		goto out;
 	}
 
-	bus_read_region_stream_1(sc->mem_res, crb_sc->rsp_off + TPM_HEADER_SIZE,
-	      &priv->buf[TPM_HEADER_SIZE], bytes_available - TPM_HEADER_SIZE);
+	bus_space_read_region_stream_1(crb_sc->bst, crb_sc->rsp.bsh,
+	    TPM_HEADER_SIZE, &priv->buf[TPM_HEADER_SIZE],
+	    bytes_available - TPM_HEADER_SIZE);
 
 	priv->offset = 0;
 	priv->len = bytes_available;
@@ -612,16 +752,16 @@ tpmcrb_transmit(device_t dev, struct tpm_priv *priv, size_t length)
 out:
 	if (locality) {
 		if (error != 0 && command_started &&
-		    (TPM_READ_4(dev, TPM_CRB_CTRL_START) &
+		    (tpmcrb_ctrl_read_4(crb_sc, TPM_CRB_CTRL_START) &
 		    TPM_CRB_CTRL_START_CMD) != 0)
-			(void)tpmcrb_cancel_cmd(sc);
+			(void)tpmcrb_cancel_cmd(crb_sc);
 		if (!tpmcrb_state_idle(crb_sc, false)) {
 			device_printf(dev,
 			    "Failed to transition to idle state post-send\n");
 			if (error == 0)
 				error = EIO;
 		}
-		tpmcrb_relinquish_locality(sc);
+		tpmcrb_relinquish_locality(crb_sc);
 	}
 
 	return (error);
@@ -739,7 +879,6 @@ static device_method_t	tpmcrb_methods[] = {
 	{0, 0}
 };
 
-DEFINE_CLASS_1(tpmcrb, tpmcrb_driver, tpmcrb_methods, sizeof(struct tpmcrb_sc),
-    tpm_bus_driver);
+DEFINE_CLASS_0(tpmcrb, tpmcrb_driver, tpmcrb_methods, sizeof(struct tpmcrb_sc));
 
 DRIVER_MODULE(tpmcrb, acpi, tpmcrb_driver, 0, 0);