git: a229432d3ed0 - main - tpm_tis: Restore validated interrupts after resume

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Thu, 03 Sep 2026 03:56:10 UTC
The branch main has been updated by kbowling:

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

commit a229432d3ed05282f7cf1510a06d6d93d554c1df
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-26 10:54:59 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-03 03:55:24 +0000

    tpm_tis: Restore validated interrupts after resume
    
    TIS interrupt routing and enable registers may lose their state across
    S3, while the driver retains its software indication that interrupts
    work.  A subsequent locality or command wait can then sleep for an
    interrupt that cannot arrive.
    
    Remember whether interrupts worked before suspend and restore the
    vector, pending status, and enable mask before TPM2_Startup.  Put the
    transport in polling mode first; the interrupt handler promotes it back
    to interrupt waits only after observing an interrupt from the restored
    configuration.  If register restoration fails, Startup and subsequent
    commands continue using polling.
    
    Preserve the initial interrupt-enable mask, including the firmware's
    trigger and polarity selection proven by the attach time interrupt test,
    and restore that exact mask rather than accepting post-S3 defaults.
    
    Program the same safe baseline for polling devices during attach and
    resume.  Acquire locality, disable global interrupt delivery, and
    acknowledge pending status so firmware cannot leave interrupts armed
    without a handler.
    
    Use the same register programming helper during attach and resume, and
    stop trying to configure interrupts after a locality acquisition
    failure.
    
    Three consecutive device suspend and resume cycles completed on a Lenovo
    TPM2 FIFO device without an IRQ resource.  GetRandom succeeded after
    each resume, and module detach completed without errors.
    
    Reviewed by:    kevans
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
    Differential Revision:  https://reviews.freebsd.org/D59197
---
 sys/dev/tpm/tpm20.h        |   1 +
 sys/dev/tpm/tpm_tis_core.c | 104 ++++++++++++++++++++++++++++++++++-----------
 2 files changed, 81 insertions(+), 24 deletions(-)

diff --git a/sys/dev/tpm/tpm20.h b/sys/dev/tpm/tpm20.h
index 7570812219b9..3bf59112bed8 100644
--- a/sys/dev/tpm/tpm20.h
+++ b/sys/dev/tpm/tpm20.h
@@ -127,6 +127,7 @@ struct tpm_sc {
 
 	void 		*intr_cookie;
 	int 		intr_type;	/* Current event type */
+	uint32_t	intr_mask;	/* Saved TIS interrupt configuration */
 	bool 		interrupts;
 	bool		common_initialized;
 
diff --git a/sys/dev/tpm/tpm_tis_core.c b/sys/dev/tpm/tpm_tis_core.c
index 15370e648f1e..f4b8ef23b3c7 100644
--- a/sys/dev/tpm/tpm_tis_core.c
+++ b/sys/dev/tpm/tpm_tis_core.c
@@ -79,7 +79,9 @@ static int tpmtis_detach(device_t dev);
 
 static void tpmtis_intr_handler(void *arg);
 
+static bool tpmtis_program_intr(struct tpm_sc *sc, bool enable);
 static void tpmtis_setup_intr(struct tpm_sc *sc);
+static int tpmtis_resume(device_t dev);
 
 static bool tpmtis_read_bytes(struct tpm_sc *sc, size_t count, uint8_t *buf);
 static bool tpmtis_write_bytes(struct tpm_sc *sc, size_t count, uint8_t *buf);
@@ -131,8 +133,7 @@ skip_irq:
 		tpmtis_detach(dev);
 		return (result);
 	}
-	if (sc->intr_cookie != NULL)
-		tpmtis_setup_intr(sc);
+	tpmtis_setup_intr(sc);
 
 	return (0);
 }
@@ -182,43 +183,98 @@ tpmtis_test_intr(struct tpm_sc *sc)
 	sx_xunlock(&sc->dev_lock);
 }
 
-static void
-tpmtis_setup_intr(struct tpm_sc *sc)
+static bool
+tpmtis_program_intr(struct tpm_sc *sc, bool enable)
 {
+	rman_res_t irq;
 	uint32_t reg;
-	uint8_t irq;
 
-	irq = bus_get_resource_start(sc->dev, SYS_RES_IRQ, sc->irq_rid);
+	sx_assert(&sc->dev_lock, SA_XLOCKED);
 
-	/*
-	 * SIRQ has to be between 1 - 15.
-	 * I found a system with ACPI table that reported a value of 0x2d.
-	 * An attempt to use such value resulted in an interrupt storm.
-	 */
-	if (irq == 0 || irq > 0xF)
-		return;
+	if (enable) {
+		irq = bus_get_resource_start(sc->dev, SYS_RES_IRQ,
+		    sc->irq_rid);
+
+		/*
+		 * SIRQ has to be between 1 - 15.  A system reporting 0x2d
+		 * produced an interrupt storm when that value was used.
+		 */
+		if (irq == 0 || irq > 0xF)
+			return (false);
+	}
 
-	if(!tpmtis_request_locality(sc, 0))
-		sc->interrupts = false;
+	if (!tpmtis_request_locality(sc, 0))
+		return (false);
 
-	TPM_WRITE_1(sc->dev, TPM_INT_VECTOR, irq);
+	/* Disable delivery before acknowledging or reconfiguring interrupts. */
+	reg = TPM_READ_4(sc->dev, TPM_INT_ENABLE);
+	reg &= ~TPM_INT_ENABLE_GLOBAL_ENABLE;
+	TPM_WRITE_4(sc->dev, TPM_INT_ENABLE, reg);
 
 	/* Clear all pending interrupts. */
 	reg = TPM_READ_4(sc->dev, TPM_INT_STS);
 	TPM_WRITE_4(sc->dev, TPM_INT_STS, reg);
 
-	reg = TPM_READ_4(sc->dev, TPM_INT_ENABLE);
-	reg |= TPM_INT_ENABLE_GLOBAL_ENABLE |
-	    TPM_INT_ENABLE_DATA_AVAIL |
-	    TPM_INT_ENABLE_LOC_CHANGE |
-	    TPM_INT_ENABLE_CMD_RDY |
-	    TPM_INT_ENABLE_STS_VALID;
-	TPM_WRITE_4(sc->dev, TPM_INT_ENABLE, reg);
+	if (enable) {
+		TPM_WRITE_1(sc->dev, TPM_INT_VECTOR, (uint8_t)irq);
+
+		if (sc->intr_mask == 0) {
+			reg = TPM_READ_4(sc->dev, TPM_INT_ENABLE);
+			reg |= TPM_INT_ENABLE_DATA_AVAIL |
+			    TPM_INT_ENABLE_LOC_CHANGE |
+			    TPM_INT_ENABLE_CMD_RDY |
+			    TPM_INT_ENABLE_STS_VALID;
+			reg &= ~TPM_INT_ENABLE_GLOBAL_ENABLE;
+			sc->intr_mask = reg;
+		}
+		TPM_WRITE_4(sc->dev, TPM_INT_ENABLE,
+		    sc->intr_mask | TPM_INT_ENABLE_GLOBAL_ENABLE);
+	}
 
 	tpmtis_relinquish_locality(sc);
+	return (true);
+}
+
+static void
+tpmtis_setup_intr(struct tpm_sc *sc)
+{
+	bool configured, enable;
+
+	sc->interrupts = false;
+	enable = sc->intr_cookie != NULL;
+	sx_xlock(&sc->dev_lock);
+	configured = tpmtis_program_intr(sc, enable);
+	sx_xunlock(&sc->dev_lock);
+	if (!configured || !enable)
+		return;
 	tpmtis_test_intr(sc);
 }
 
+static int
+tpmtis_resume(device_t dev)
+{
+	struct tpm_sc *sc;
+	bool restore_intr;
+
+	sc = device_get_softc(dev);
+	sx_xlock(&sc->dev_lock);
+	restore_intr = sc->interrupts;
+
+	/*
+	 * Interrupt routing and enable state may be lost across suspend.  Keep
+	 * the transport in polling mode until a restored interrupt is actually
+	 * observed by the handler.
+	 */
+	sc->interrupts = false;
+	if (!tpmtis_program_intr(sc, restore_intr))
+		device_printf(dev,
+		    "failed to %s interrupts; using polling\n",
+		    restore_intr ? "restore" : "disable");
+	sx_xunlock(&sc->dev_lock);
+
+	return (tpm20_resume(dev));
+}
+
 static void
 tpmtis_intr_handler(void *arg)
 {
@@ -501,7 +557,7 @@ static device_method_t tpmtis_methods[] = {
 	DEVMETHOD(device_detach,	tpmtis_detach),
 	DEVMETHOD(device_shutdown,	tpm20_shutdown),
 	DEVMETHOD(device_suspend,	tpm20_suspend),
-	DEVMETHOD(device_resume,	tpm20_resume),
+	DEVMETHOD(device_resume,	tpmtis_resume),
 	DEVMETHOD(tpm_transmit,		tpmtis_transmit),
 	DEVMETHOD_END
 };