git: 96ed6005082f - main - tpm: Restore TPM 1.2 TIS state after resume
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 01 Sep 2026 18:20:34 UTC
The branch main has been updated by kbowling:
URL: https://cgit.FreeBSD.org/src/commit/?id=96ed6005082fc63d633408ba3ad697e8757e80f9
commit 96ed6005082fc63d633408ba3ad697e8757e80f9
Author: Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-26 10:33:38 +0000
Commit: Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-01 18:19:50 +0000
tpm: Restore TPM 1.2 TIS state after resume
Firmware restores the state saved by TPM_ORD_SaveState, but the TIS
interrupt, locality, and command FIFO state are not guaranteed to
survive S3. The legacy driver previously treated resume as a no-op.
Revalidate the interface and device identity, disable and acknowledge
stale interrupts, restore the configured interrupt vector, reacquire
locality zero, and return the FIFO to command-ready state. Also disable
TIS interrupts during initial setup when the device uses polling so
firmware settings cannot leave an unhandled interrupt enabled.
TIS 1.3 Table 22 makes the interrupt control registers locality
protected. Acquire locality before disabling or programming them during
initial setup and resume rather than relying on probe retaining
locality.
Keep TPM self-test outside the resume critical path. It can take
minutes on some TPM 1.2 devices and is not required to restore the
transport state.
The two-commit suspend and resume series completed two consecutive S3
cycles on a ThinkPad T440p with its STMicro TPM 1.2 Security Chip
enabled. PCR 0 was readable with the same value before and after each
cycle, and no SaveState or TIS restoration errors were logged.
The locality ordering completed another two consecutive S3 cycles on a
ThinkPad T430 with the same STMicro TPM in polling mode. PCR 0 again
remained stable, and TPM access recovered without errors after each
resume.
PR: 291067
Reviewed by: kevans
Sponsored by: BBOX.io
MFC after: 2 weeks
Differential Revision: https://reviews.freebsd.org/D59193
---
sys/dev/tpm/tpm.c | 91 ++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 70 insertions(+), 21 deletions(-)
diff --git a/sys/dev/tpm/tpm.c b/sys/dev/tpm/tpm.c
index 855dad8eee85..c877f617fdad 100644
--- a/sys/dev/tpm/tpm.c
+++ b/sys/dev/tpm/tpm.c
@@ -195,6 +195,8 @@ 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 int tpm_tis12_devid_index(uint32_t);
+static int tpm_tis12_resume(struct tpm_softc *);
/*
@@ -329,19 +331,14 @@ tpm_tis12_probe(bus_space_tag_t bt, bus_space_handle_t bh)
}
/*
- * Setup interrupt vector if one is provided and interrupts are know to
- * work on that particular chip.
+ * Setup the interrupt vector if one is provided and interrupts are known
+ * to work on that particular chip. The caller must hold locality zero.
*/
int
tpm_tis12_irqinit(struct tpm_softc *sc, int irq, int idx)
{
u_int32_t r;
- if ((irq == IRQUNK) || (tpm_devs[idx].flags & TPM_DEV_NOINTS)) {
- sc->sc_vector = IRQUNK;
- return 0;
- }
-
/* Ack and disable all interrupts. */
bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE,
bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE) &
@@ -349,6 +346,11 @@ tpm_tis12_irqinit(struct tpm_softc *sc, int irq, int idx)
bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INT_STATUS,
bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INT_STATUS));
+ if ((irq == IRQUNK) || (tpm_devs[idx].flags & TPM_DEV_NOINTS)) {
+ sc->sc_vector = IRQUNK;
+ return 0;
+ }
+
/* Program interrupt vector. */
bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_INT_VECTOR, irq);
sc->sc_vector = irq;
@@ -365,6 +367,17 @@ tpm_tis12_irqinit(struct tpm_softc *sc, int irq, int idx)
return 0;
}
+static int
+tpm_tis12_devid_index(uint32_t devid)
+{
+ int i;
+
+ for (i = 0; tpm_devs[i].devid != 0; i++)
+ if (tpm_devs[i].devid == devid)
+ break;
+ return (i);
+}
+
/* Setup TPM using TIS 1.2 interface. */
int
tpm_tis12_init(struct tpm_softc *sc, int irq, const char *name)
@@ -386,19 +399,17 @@ tpm_tis12_init(struct tpm_softc *sc, int irq, const char *name)
sc->sc_devid = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_ID);
sc->sc_rev = bus_space_read_1(sc->sc_bt, sc->sc_bh, TPM_REV);
- for (i = 0; tpm_devs[i].devid; i++)
- if (tpm_devs[i].devid == sc->sc_devid)
- break;
+ i = tpm_tis12_devid_index(sc->sc_devid);
if (tpm_devs[i].devid)
printf(": %s rev 0x%x\n", tpm_devs[i].name, sc->sc_rev);
else
printf(": device 0x%08x rev 0x%x\n", sc->sc_devid, sc->sc_rev);
- if (tpm_tis12_irqinit(sc, irq, i))
+ if (tpm_request_locality(sc, 0))
return 1;
- if (tpm_request_locality(sc, 0))
+ if (tpm_tis12_irqinit(sc, irq, i))
return 1;
/* Abort whatever it thought it was doing. */
@@ -407,6 +418,40 @@ tpm_tis12_init(struct tpm_softc *sc, int irq, const char *name)
return 0;
}
+/* Restore TIS state which is not guaranteed to survive S3. */
+static int
+tpm_tis12_resume(struct tpm_softc *sc)
+{
+ uint32_t capabilities, devid;
+ int error, i, irq;
+
+ capabilities = bus_space_read_4(sc->sc_bt, sc->sc_bh,
+ TPM_INTF_CAPABILITIES);
+ if ((capabilities & TPM_CAPSREQ) != TPM_CAPSREQ ||
+ (capabilities & (TPM_INTF_INT_EDGE_RISING |
+ TPM_INTF_INT_LEVEL_LOW)) == 0)
+ return (ENXIO);
+ devid = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_ID);
+ if (devid == UINT32_MAX || devid != sc->sc_devid)
+ return (ENXIO);
+
+ sc->sc_capabilities = capabilities;
+ i = tpm_tis12_devid_index(devid);
+ irq = sc->sc_vector;
+ error = tpm_request_locality(sc, 0);
+ if (error != 0)
+ return (error);
+ error = tpm_tis12_irqinit(sc, irq, i);
+ if (error != 0)
+ return (error);
+
+ /* 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));
+}
+
int
tpm_request_locality(struct tpm_softc *sc, int l)
{
@@ -584,21 +629,25 @@ tpm_suspend(device_t dev)
return (0);
}
-/*
- * Handle resume event. Actually nothing to do as the BIOS is supposed
- * to restore the previously saved state.
- */
+/* Handle resume after firmware has restored the saved TPM state. */
int
tpm_resume(device_t dev)
{
- struct tpm_softc *sc = device_get_softc(dev);
- int why = 0;
+ struct tpm_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+ error = 0;
+ if (sc->sc_init == tpm_tis12_init)
+ error = tpm_tis12_resume(sc);
#ifdef TPM_DEBUG
- printf("tpm_resume: resume: %d -> %d\n", sc->sc_suspend, why);
+ device_printf(dev, "resume: %d -> 0\n", sc->sc_suspend);
#endif
- sc->sc_suspend = why;
+ sc->sc_suspend = 0;
+ if (error != 0)
+ device_printf(dev, "failed to restore TIS state: %d\n", error);
- return 0;
+ return (error);
}
/* Dispatch suspend and resume events. */