git: 6af3031c9516 - main - tpm: crb: make the Pluton startmethod more resilient

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Wed, 09 Sep 2026 09:01:12 UTC
The branch main has been updated by kevans:

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

commit 6af3031c951614f2a42dfd67ce2c3a48ee8314ae
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2026-09-09 09:00:28 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2026-09-09 09:00:28 +0000

    tpm: crb: make the Pluton startmethod more resilient
    
    The original implementation assumed that the start/reply doorbells
    lived within the device _CRS space, but that isn't always the case.  On
    my AMD Ryzen 7640U-based frame.work laptop, device memory runs from
    0xc0500000-0xc0500fff while the doorbells are up around 0xc0508000.
    
    Stop sanity checking the addresses and just map them in to work reliably
    whether they're within the device range or not.
    
    pluton_wait_reply is cribbed from tpm_wait_for_u32, but rewritten
    slightly to read in just one place and to read one last time before
    giving up at the end of the timeout, just in case.
    
    Reviewed by:    kbowling
    Differential Revision:  https://reviews.freebsd.org/D59327
---
 sys/dev/tpm/tpm_crb.c | 115 ++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 87 insertions(+), 28 deletions(-)

diff --git a/sys/dev/tpm/tpm_crb.c b/sys/dev/tpm/tpm_crb.c
index 3e567e215313..e5bbec5d46da 100644
--- a/sys/dev/tpm/tpm_crb.c
+++ b/sys/dev/tpm/tpm_crb.c
@@ -76,18 +76,21 @@
 #define	TPM_CRB_INT_ENABLE_BIT		BIT(31)
 
 struct tpmcrb_sc;
-/* Attach */
+/* Attach/detach callbacks */
 typedef bool (sm_attach_t)(struct tpmcrb_sc *, void *, size_t);
+typedef void (sm_detach_t)(struct tpmcrb_sc *);
 /* State change notification (timeout == 0 for 'no timeout') */
 typedef bool (sm_statechange_t)(struct tpmcrb_sc *, int);
 
 struct tpmcrb_sm_cfg {
 	sm_attach_t		*sm_attach;
+	sm_detach_t		*sm_detach;
 	sm_statechange_t	*sm_statechange;
 	sm_statechange_t	*sm_cmdready;
 };
 
 static sm_attach_t		pluton_attach;
+static sm_detach_t		pluton_detach;
 static sm_statechange_t		pluton_doorbell;
 
 static const struct tpmcrb_sm_cfg_map {
@@ -105,6 +108,7 @@ static const struct tpmcrb_sm_cfg_map {
 		.desc = "Trusted Platform Module 2.0, CRB mode (Pluton)",
 		.sm_cfg = {
 			.sm_attach = &pluton_attach,
+			.sm_detach = &pluton_detach,
 			.sm_statechange = &pluton_doorbell,
 			.sm_cmdready = &pluton_doorbell,
 		},
@@ -115,10 +119,15 @@ struct tpmcrb_sc {
 	struct tpm_sc	base;
 	const struct tpmcrb_sm_cfg	*sm_cfg;
 	union {
-		/* StartMethod data */
+		/*
+		 * StartMethod data.  The Pluton start/reply mailbox
+		 * registers may live outside of the _CRS window, so they get
+		 * their own bus_space mapping.
+		 */
 		struct {
-			uint64_t	 start_reg;
-			uint64_t	 reply_reg;
+			bus_space_tag_t		bst;
+			bus_space_handle_t	start_bsh;
+			bus_space_handle_t	reply_bsh;
 		} pluton;
 	};
 	bus_size_t	cmd_off;
@@ -333,11 +342,16 @@ tpmcrb_attach(device_t dev)
 static int
 tpmcrb_detach(device_t dev)
 {
+	struct tpmcrb_sc *crb_sc;
 	struct tpm_sc *sc;
 
-	sc = device_get_softc(dev);
+	crb_sc = device_get_softc(dev);
+	sc = &crb_sc->base;
 	tpm20_release(sc);
 
+	if (crb_sc->sm_cfg != NULL && crb_sc->sm_cfg->sm_detach != NULL)
+		(*crb_sc->sm_cfg->sm_detach)(crb_sc);
+
 	if (sc->mem_res != NULL)
 		bus_release_resource(dev, SYS_RES_MEMORY,
 		    sc->mem_rid, sc->mem_res);
@@ -618,7 +632,7 @@ pluton_attach(struct tpmcrb_sc *crb_sc, void *smdataregion, size_t datasz)
 {
 	struct tpmcrb_startmethod_pluton *smdata;
 	struct tpm_sc *sc;
-	rman_res_t base_addr, end_addr;
+	bus_space_tag_t bst;
 
 	if (datasz < sizeof(*smdata))
 		return (false);
@@ -626,38 +640,83 @@ pluton_attach(struct tpmcrb_sc *crb_sc, void *smdataregion, size_t datasz)
 	smdata = smdataregion;
 	sc = &crb_sc->base;
 
-	base_addr = rman_get_start(sc->mem_res);
-	end_addr = rman_get_end(sc->mem_res);
-	/* Sanity check */
-	if (smdata->sm_startaddr < base_addr ||
-	    smdata->sm_startaddr > end_addr ||
-	    smdata->sm_replyaddr < base_addr ||
-	    smdata->sm_replyaddr > end_addr)
+	/*
+	 * The start/reply mailbox registers are not necessarily part of
+	 * the CRB register window described by _CRS, so we map them
+	 * independently.
+	 */
+	bst = rman_get_bustag(sc->mem_res);
+	if (bus_space_map(bst, smdata->sm_startaddr, sizeof(uint32_t), 0,
+	    &crb_sc->pluton.start_bsh) != 0) {
+		device_printf(sc->dev,
+		    "Failed to map Pluton start register at %#jx\n",
+		    (uintmax_t)smdata->sm_startaddr);
+		return (false);
+	}
+
+	if (bus_space_map(bst, smdata->sm_replyaddr, sizeof(uint32_t), 0,
+	    &crb_sc->pluton.reply_bsh) != 0) {
+		device_printf(sc->dev,
+		    "Failed to map Pluton reply register at %#jx\n",
+		    (uintmax_t)smdata->sm_replyaddr);
+		bus_space_unmap(bst, crb_sc->pluton.start_bsh,
+		    sizeof(uint32_t));
 		return (false);
+	}
 
-	crb_sc->pluton.start_reg = smdata->sm_startaddr - base_addr;
-	crb_sc->pluton.reply_reg = smdata->sm_replyaddr - base_addr;
+	crb_sc->pluton.bst = bst;
 	return (true);
 }
 
-static bool
-pluton_doorbell(struct tpmcrb_sc *crb_sc, int timeout)
+static void
+pluton_detach(struct tpmcrb_sc *crb_sc)
 {
-	struct tpm_sc *sc;
-	device_t dev;
 
-	sc = &crb_sc->base;
-	dev = sc->dev;
-	TPM_WRITE_4(dev, crb_sc->pluton.start_reg, 1);
-	TPM_WRITE_BARRIER(dev, crb_sc->pluton.start_reg, 4);
+	if (crb_sc->pluton.bst == 0)
+		return;
 
-	if (timeout > 0) {
-		if (!tpm_wait_for_u32(sc, crb_sc->pluton.reply_reg, ~0U, 1,
-		    timeout))
-			return (false);
+	bus_space_unmap(crb_sc->pluton.bst, crb_sc->pluton.start_bsh,
+	    sizeof(uint32_t));
+	bus_space_unmap(crb_sc->pluton.bst, crb_sc->pluton.reply_bsh,
+	    sizeof(uint32_t));
+}
+
+static bool
+pluton_wait_reply(struct tpmcrb_sc *crb_sc, int32_t timeout)
+{
+	for (;;) {
+		/*
+		 * Always read at least once, and try one more time after we hit
+		 * the timeout.
+		 */
+		if (bus_space_read_4(crb_sc->pluton.bst,
+		    crb_sc->pluton.reply_bsh, 0) == 1)
+			return (true);
+		else if (timeout <= 0)
+			break;
+
+		pause("TPM in polling mode", 1);
+		timeout -= tick;
 	}
 
-	return (true);
+	return (false);
+}
+
+static bool
+pluton_doorbell(struct tpmcrb_sc *crb_sc, int timeout)
+{
+	bus_space_write_4(crb_sc->pluton.bst, crb_sc->pluton.start_bsh, 0, 1);
+	bus_space_barrier(crb_sc->pluton.bst, crb_sc->pluton.start_bsh, 0,
+	    sizeof(uint32_t), BUS_SPACE_BARRIER_WRITE);
+
+	/*
+	 * We assume at timeout == 0 that they're not really interested in a
+	 * reply immediately.  Otherwise, we poll for a reply at least once,
+	 * even with low timeouts.
+	 */
+	if (timeout <= 0)
+		return (true);
+	return (pluton_wait_reply(crb_sc, timeout));
 }
 
 /* ACPI Driver */