git: 4e0f283fb97a - main - tpm: Bound TPM 1.2 locality ownership

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Tue, 01 Sep 2026 18:24:25 UTC
The branch main has been updated by kbowling:

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

commit 4e0f283fb97a7b0903adc7bc3ef2f6e07167c73e
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-27 13:42:49 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-01 18:23:36 +0000

    tpm: Bound TPM 1.2 locality ownership
    
    A TIS locality must remain active while a command is in flight, but
    should be relinquished once the command completes or is abandoned.  The
    driver retained locality zero after probe, initialization, and resume,
    and several transaction error paths returned without releasing it.
    Closing the device after writing a command without reading its response
    had the same effect.
    
    Track locality ownership and whether a command is awaiting its response.
    Release locality after probe, initialization, and resume; retain it only
    across a successful command write and its matching response read.  Abort
    and release on errors, replacement commands, close, and detach.
    
    Wait for locality during ISA probe instead of assuming an immediate
    grant, release locality acquired by the probe, and stop treating the
    command-style TPM_ACCESS register as restorable state.
    
    On a ThinkPad T440p with an STMicro TPM 1.2, the old driver left
    TPM_ACCESS at 0xa1 immediately after attach.  The new driver left it at
    0x81 after attach, completed PCR reads, and closing with an unread
    response.  PCR reads also survived an unload and reload without TPM or
    locking diagnostics.
    
    Reviewed by:    kevants
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
    Differential Revision:  https://reviews.freebsd.org/D59237
---
 sys/dev/tpm/tpm.c    | 171 +++++++++++++++++++++++++++++++++++++++------------
 sys/dev/tpm/tpmvar.h |   2 +
 2 files changed, 134 insertions(+), 39 deletions(-)

diff --git a/sys/dev/tpm/tpm.c b/sys/dev/tpm/tpm.c
index 8c12abb39d1a..f37ce6955f17 100644
--- a/sys/dev/tpm/tpm.c
+++ b/sys/dev/tpm/tpm.c
@@ -194,7 +194,9 @@ int tpm_legacy_write(struct tpm_softc *, void *, int);
 int tpm_legacy_end(struct tpm_softc *, int, int);
 
 static int tpm_transmit_header(struct tpm_softc *, uint32_t, uint32_t *);
+static void tpm_tis12_abort(struct tpm_softc *);
 static int tpm_tis12_devid_index(uint32_t);
+static void tpm_tis12_relinquish_locality(struct tpm_softc *);
 static int tpm_tis12_resume(struct tpm_softc *);
 
 
@@ -225,6 +227,8 @@ tpm_attach(device_t dev)
 	sc->sc_flags = 0;
 	sc->sc_suspend = 0;
 	sc->sc_dying = false;
+	sc->sc_locality = false;
+	sc->sc_command_pending = false;
 
 	sc->mem_rid = 0;
 	sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid,
@@ -245,9 +249,6 @@ tpm_attach(device_t dev)
 	else
 		irq = IRQUNK;
 
-	/* In case PnP probe this may contain some initialization. */
-	tpm_tis12_probe(sc->sc_bt, sc->sc_bh);
-
 	if (tpm_legacy_probe(sc->sc_bt, sc->sc_bh)) {
 		sc->sc_init = tpm_legacy_init;
 		sc->sc_start = tpm_legacy_start;
@@ -315,6 +316,10 @@ tpm_detach(device_t dev)
 		destroy_dev(sc->sc_cdev);
 		sc->sc_cdev = NULL;
 	}
+	sx_xlock(&sc->sc_lock);
+	if (sc->mem_res != NULL && sc->sc_init == tpm_tis12_init)
+		tpm_tis12_abort(sc);
+	sx_xunlock(&sc->sc_lock);
 	if (sc->intr_cookie != NULL) {
 		bus_teardown_intr(dev, sc->irq_res, sc->intr_cookie);
 		sc->intr_cookie = NULL;
@@ -341,7 +346,9 @@ int
 tpm_tis12_probe(bus_space_tag_t bt, bus_space_handle_t bh)
 {
 	u_int32_t r;
-	u_int8_t save, reg;
+	u_int8_t reg;
+	bool acquired;
+	int to;
 
 	r = bus_space_read_4(bt, bh, TPM_INTF_CAPABILITIES);
 	if (r == 0xffffffff)
@@ -358,15 +365,34 @@ tpm_tis12_probe(bus_space_tag_t bt, bus_space_handle_t bh)
 		return 0;
 	}
 
-	save = bus_space_read_1(bt, bh, TPM_ACCESS);
-	bus_space_write_1(bt, bh, TPM_ACCESS, TPM_ACCESS_REQUEST_USE);
 	reg = bus_space_read_1(bt, bh, TPM_ACCESS);
-	if ((reg & TPM_ACCESS_VALID) && (reg & TPM_ACCESS_ACTIVE_LOCALITY) &&
-	    bus_space_read_4(bt, bh, TPM_ID) != 0xffffffff)
-		return 1;
+	acquired = false;
+	if ((reg & (TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY)) !=
+	    (TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY)) {
+		bus_space_write_1(bt, bh, TPM_ACCESS,
+		    TPM_ACCESS_REQUEST_USE);
+		to = TPM_ACCESS_TMO;	/* Steps of one millisecond. */
+		do {
+			reg = bus_space_read_1(bt, bh, TPM_ACCESS);
+			if ((reg & (TPM_ACCESS_VALID |
+			    TPM_ACCESS_ACTIVE_LOCALITY)) ==
+			    (TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY)) {
+				acquired = true;
+				break;
+			}
+			DELAY(1000);
+		} while (--to != 0);
+	}
 
-	bus_space_write_1(bt, bh, TPM_ACCESS, save);
-	return 0;
+	if ((reg & (TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY)) ==
+	    (TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY))
+		r = bus_space_read_4(bt, bh, TPM_ID);
+	else
+		r = UINT32_MAX;
+	if (acquired)
+		bus_space_write_1(bt, bh, TPM_ACCESS,
+		    TPM_ACCESS_ACTIVE_LOCALITY);
+	return (r != UINT32_MAX);
 }
 
 /*
@@ -427,7 +453,7 @@ int
 tpm_tis12_init(struct tpm_softc *sc, int irq, const char *name)
 {
 	u_int32_t r;
-	int i;
+	int error, i;
 
 	sx_assert(&sc->sc_lock, SA_XLOCKED);
 	r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTF_CAPABILITIES);
@@ -451,16 +477,22 @@ tpm_tis12_init(struct tpm_softc *sc, int irq, const char *name)
 	else
 		printf(": device 0x%08x rev 0x%x\n", sc->sc_devid, sc->sc_rev);
 
-	if (tpm_request_locality(sc, 0))
+	error = tpm_request_locality(sc, 0);
+	if (error != 0)
 		return 1;
 
-	if (tpm_tis12_irqinit(sc, irq, i))
-		return 1;
+	error = tpm_tis12_irqinit(sc, irq, i);
+	if (error != 0)
+		goto out;
 
 	/* Abort whatever it thought it was doing. */
 	bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS, TPM_STS_CMD_READY);
+	error = tpm_waitfor(sc, TPM_STS_CMD_READY, TPM_READY_TMO,
+	    sc->sc_write);
 
-	return 0;
+out:
+	tpm_tis12_relinquish_locality(sc);
+	return (error != 0);
 }
 
 /* Restore TIS state which is not guaranteed to survive S3. */
@@ -489,13 +521,16 @@ tpm_tis12_resume(struct tpm_softc *sc)
 		return (error);
 	error = tpm_tis12_irqinit(sc, irq, i);
 	if (error != 0)
-		return (error);
+		goto out;
 
 	/* Abort firmware residue and leave the command FIFO ready. */
 	bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS,
 	    TPM_STS_CMD_READY);
-	return (tpm_waitfor(sc, TPM_STS_CMD_READY, TPM_READY_TMO,
-	    sc->sc_write));
+	error = tpm_waitfor(sc, TPM_STS_CMD_READY, TPM_READY_TMO,
+	    sc->sc_write);
+out:
+	tpm_tis12_relinquish_locality(sc);
+	return (error);
 }
 
 int
@@ -507,11 +542,15 @@ tpm_request_locality(struct tpm_softc *sc, int l)
 	sx_assert(&sc->sc_lock, SA_XLOCKED);
 	if (l != 0)
 		return EINVAL;
+	KASSERT(!sc->sc_locality, ("%s: locality already owned", __func__));
 
 	if ((bus_space_read_1(sc->sc_bt, sc->sc_bh, TPM_ACCESS) &
 	    (TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY)) ==
-	    (TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY))
+	    (TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY)) {
+		sc->sc_locality = true;
 		return 0;
+	}
+	sc->sc_locality = false;
 
 	bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_ACCESS,
 	    TPM_ACCESS_REQUEST_USE);
@@ -526,6 +565,14 @@ tpm_request_locality(struct tpm_softc *sc, int l)
 #ifdef TPM_DEBUG
 			printf("tpm_request_locality: interrupted %d\n", rv);
 #endif
+			r = bus_space_read_1(sc->sc_bt, sc->sc_bh,
+			    TPM_ACCESS);
+			if ((r & (TPM_ACCESS_VALID |
+			    TPM_ACCESS_ACTIVE_LOCALITY)) ==
+			    (TPM_ACCESS_VALID | TPM_ACCESS_ACTIVE_LOCALITY)) {
+				sc->sc_locality = true;
+				tpm_tis12_relinquish_locality(sc);
+			}
 			return rv;
 		}
 	}
@@ -538,9 +585,34 @@ tpm_request_locality(struct tpm_softc *sc, int l)
 		return EBUSY;
 	}
 
+	sc->sc_locality = true;
 	return 0;
 }
 
+static void
+tpm_tis12_relinquish_locality(struct tpm_softc *sc)
+{
+
+	sx_assert(&sc->sc_lock, SA_XLOCKED);
+	sc->sc_command_pending = false;
+	if (!sc->sc_locality)
+		return;
+	bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_ACCESS,
+	    TPM_ACCESS_ACTIVE_LOCALITY);
+	sc->sc_locality = false;
+}
+
+static void
+tpm_tis12_abort(struct tpm_softc *sc)
+{
+
+	sx_assert(&sc->sc_lock, SA_XLOCKED);
+	if (sc->sc_locality)
+		bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS,
+		    TPM_STS_CMD_READY);
+	tpm_tis12_relinquish_locality(sc);
+}
+
 int
 tpm_getburst(struct tpm_softc *sc)
 {
@@ -921,14 +993,23 @@ tpm_tis12_start(struct tpm_softc *sc, int flag)
 
 	sx_assert(&sc->sc_lock, SA_XLOCKED);
 	if (flag == UIO_READ) {
+		if (!sc->sc_locality || !sc->sc_command_pending) {
+			tpm_tis12_abort(sc);
+			return (EIO);
+		}
 		rv = tpm_waitfor(sc, TPM_STS_DATA_AVAIL | TPM_STS_VALID,
 		    TPM_READ_TMO, sc->sc_read);
-		return rv;
+		if (rv != 0)
+			tpm_tis12_abort(sc);
+		return (rv);
 	}
 
+	/* Abort an incomplete command before starting another one. */
+	tpm_tis12_abort(sc);
+
 	/* Own our (0th) locality. */
 	if ((rv = tpm_request_locality(sc, 0)) != 0)
-		return rv;
+		return (rv);
 
 	sc->sc_stat = tpm_status(sc);
 	if (sc->sc_stat & TPM_STS_CMD_READY) {
@@ -950,7 +1031,8 @@ tpm_tis12_start(struct tpm_softc *sc, int flag)
 #ifdef TPM_DEBUG
 		printf("tpm_tis12_start: UIO_WRITE readying failed %d\n", rv);
 #endif
-		return rv;
+		tpm_tis12_abort(sc);
+		return (rv);
 	}
 
 #ifdef TPM_DEBUG
@@ -1013,8 +1095,8 @@ tpm_tis12_write(struct tpm_softc *sc, void *buf, int len)
 #endif
 
 	sx_assert(&sc->sc_lock, SA_XLOCKED);
-	if ((rv = tpm_request_locality(sc, 0)) != 0)
-		return rv;
+	if (!sc->sc_locality)
+		return (EIO);
 
 	cnt = 0;
 	while (cnt < len - 1) {
@@ -1070,27 +1152,31 @@ tpm_tis12_end(struct tpm_softc *sc, int flag, int err)
 
 	sx_assert(&sc->sc_lock, SA_XLOCKED);
 	if (flag == UIO_READ) {
-		if ((rv = tpm_waitfor(sc, TPM_STS_VALID, TPM_READ_TMO,
-		    sc->sc_read)))
-			return rv;
+		if (!sc->sc_locality) {
+			tpm_tis12_abort(sc);
+			return (err != 0 ? 0 : EIO);
+		}
+		if (err == 0)
+			rv = tpm_waitfor(sc, TPM_STS_VALID, TPM_READ_TMO,
+			    sc->sc_read);
 
 		/* Still more data? */
-		sc->sc_stat = tpm_status(sc);
-		if (!err && ((sc->sc_stat & TPM_STS_DATA_AVAIL) == TPM_STS_DATA_AVAIL)) {
+		if (err == 0 && rv == 0)
+			sc->sc_stat = tpm_status(sc);
+		if (err == 0 && rv == 0 &&
+		    (sc->sc_stat & TPM_STS_DATA_AVAIL) != 0) {
 #ifdef TPM_DEBUG
 			printf("tpm_tis12_end: read failed stat=%b\n",
 			    sc->sc_stat, TPM_STS_BITS);
 #endif
 			rv = EIO;
 		}
-
-		bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS,
-		    TPM_STS_CMD_READY);
-
-		/* Release our (0th) locality. */
-		bus_space_write_1(sc->sc_bt, sc->sc_bh,TPM_ACCESS,
-		    TPM_ACCESS_ACTIVE_LOCALITY);
+		tpm_tis12_abort(sc);
 	} else {
+		if (!sc->sc_locality) {
+			tpm_tis12_abort(sc);
+			return (err != 0 ? 0 : EIO);
+		}
 		/* Hungry for more? */
 		sc->sc_stat = tpm_status(sc);
 		if (!err && (sc->sc_stat & TPM_STS_DATA_EXPECT)) {
@@ -1101,8 +1187,13 @@ tpm_tis12_end(struct tpm_softc *sc, int flag, int err)
 			rv = EIO;
 		}
 
-		bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS,
-		    err ? TPM_STS_CMD_READY : TPM_STS_GO);
+		if (err != 0 || rv != 0)
+			tpm_tis12_abort(sc);
+		else {
+			bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS,
+			    TPM_STS_GO);
+			sc->sc_command_pending = true;
+		}
 	}
 
 	return rv;
@@ -1368,6 +1459,8 @@ tpmclose(struct cdev *dev, int flag, int mode, struct thread *td)
 	if ((sc->sc_flags & TPM_OPEN) == 0)
 		error = EINVAL;
 	else {
+		if (sc->sc_init == tpm_tis12_init)
+			tpm_tis12_abort(sc);
 		sc->sc_flags &= ~TPM_OPEN;
 		error = 0;
 	}
diff --git a/sys/dev/tpm/tpmvar.h b/sys/dev/tpm/tpmvar.h
index fc11d08fa4c1..61e5c17a8ffb 100644
--- a/sys/dev/tpm/tpmvar.h
+++ b/sys/dev/tpm/tpmvar.h
@@ -60,6 +60,8 @@ struct tpm_softc {
 	struct mtx sc_intr_lock;
 	struct cv sc_intr_cv;
 	bool sc_dying;
+	bool sc_locality;
+	bool sc_command_pending;
 #endif
 
 #ifndef __FreeBSD__