The branch main has been updated by kevans: URL: https://cgit.FreeBSD.org/src/commit/?id=6af3031c951614f2a42dfd67ce2c3a48ee8314ae
commit 6af3031c951614f2a42dfd67ce2c3a48ee8314ae Author: Kyle Evans <[email protected]> AuthorDate: 2026-09-09 09:00:28 +0000 Commit: Kyle Evans <[email protected]> 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 */
