/*- * Copyright (c) 2018 Stormshield. * Copyright (c) 2018 Semihalf. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include "tpm20.h" /* * CRB register space as defined in * TCG_PC_Client_Platform_TPM_Profile_PTP_2.0_r1.03_v22 */ #define TPM_LOC_STATE 0x0 #define TPM_LOC_CTRL 0x8 #define TPM_LOC_STS 0xC #define TPM_CRB_INTF_ID 0x30 #define TPM_CRB_CTRL_EXT 0x38 #define TPM_CRB_CTRL_REQ 0x40 #define TPM_CRB_CTRL_STS 0x44 #define TPM_CRB_CTRL_CANCEL 0x48 #define TPM_CRB_CTRL_START 0x4C #define TPM_CRB_INT_ENABLE 0x50 #define TPM_CRB_INT_STS 0x54 #define TPM_CRB_CTRL_CMD_SIZE 0x58 #define TPM_CRB_CTRL_CMD_LADDR 0x5C #define TPM_CRB_CTRL_CMD_HADDR 0x60 #define TPM_CRB_CTRL_RSP_SIZE 0x64 #define TPM_CRB_CTRL_RSP_ADDR 0x68 #define TPM_CRB_CTRL_RSP_HADDR 0x6c #define TPM_CRB_DATA_BUFFER 0x80 #define TPM_LOC_STATE_ESTB BIT(0) #define TPM_LOC_STATE_ASSIGNED BIT(1) #define TPM_LOC_STATE_ACTIVE_MASK 0x9C #define TPM_LOC_STATE_VALID BIT(7) #define TPM_CRB_INTF_ID_TYPE_CRB 0x1 #define TPM_CRB_INTF_ID_TYPE 0x7 #define TPM_LOC_CTRL_REQUEST BIT(0) #define TPM_LOC_CTRL_RELINQUISH BIT(1) #define TPM_CRB_CTRL_REQ_GO_READY BIT(0) #define TPM_CRB_CTRL_REQ_GO_IDLE BIT(1) #define TPM_CRB_CTRL_STS_ERR_BIT BIT(0) #define TPM_CRB_CTRL_STS_IDLE_BIT BIT(1) #define TPM_CRB_CTRL_CANCEL_CMD 0x1 #define TPM_CRB_CTRL_CANCEL_CLEAR 0x0 #define TPM_CRB_CTRL_START_CMD BIT(0) #define TPM_CRB_INT_ENABLE_BIT BIT(31) struct tpmcrb_sc; /* 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 { int acpi_sm; const char *desc; const struct tpmcrb_sm_cfg sm_cfg; } tpmcrb_sm_cfg_map[] = { { .acpi_sm = TPM2_START_METHOD_CRB, .desc = "Trusted Platform Module 2.0, CRB mode", .sm_cfg = { NULL }, /* No notifications required */ }, { .acpi_sm = ACPI_TPM2_COMMAND_BUFFER_WITH_PLUTON, .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, }, }, }; struct tpmcrb_sc { struct tpm_sc base; const struct tpmcrb_sm_cfg *sm_cfg; union { /* * StartMethod data. The Pluton start/reply mailbox * registers may live outside of the _CRS window, so they get * their own bus_space mapping. */ struct { bus_space_tag_t bst; bus_space_handle_t start_bsh; bus_space_handle_t reply_bsh; } pluton; }; bus_size_t cmd_off; bus_size_t rsp_off; size_t cmd_buf_size; size_t rsp_buf_size; }; int tpmcrb_transmit(device_t dev, struct tpm_priv *priv, size_t size); static int tpmcrb_acpi_probe(device_t dev); static int tpmcrb_attach(device_t dev); static int tpmcrb_detach(device_t dev); static ACPI_STATUS tpmcrb_fix_buff_offsets(ACPI_RESOURCE *res, void *arg); static bool tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off, uint32_t mask, uint32_t val, int32_t timeout); static bool tpmcrb_request_locality(struct tpm_sc *sc, int locality); static void tpmcrb_relinquish_locality(struct tpm_sc *sc); static bool tpmcrb_cancel_cmd(struct tpm_sc *sc); char *tpmcrb_ids[] = {"MSFT0101", NULL}; static const struct tpmcrb_sm_cfg_map * tpmcrb_acpi_startmethod_cfg(int method) { const struct tpmcrb_sm_cfg_map *entry; for (size_t i = 0; i < nitems(tpmcrb_sm_cfg_map); i++) { entry = &tpmcrb_sm_cfg_map[i]; if (method == entry->acpi_sm) return (entry); } return (NULL); } static int tpmcrb_acpi_probe(device_t dev) { int err, smethod; const struct tpmcrb_sm_cfg_map *sm_cfg_map; ACPI_TABLE_TPM23 *tbl; ACPI_STATUS status; err = ACPI_ID_PROBE(device_get_parent(dev), dev, tpmcrb_ids, NULL); if (err > 0) return (err); /*Find TPM2 Header*/ status = AcpiGetTable(ACPI_SIG_TPM2, 1, (ACPI_TABLE_HEADER **) &tbl); if (ACPI_FAILURE(status)) return (ENXIO); smethod = tbl->StartMethod; AcpiPutTable((ACPI_TABLE_HEADER *)tbl); sm_cfg_map = tpmcrb_acpi_startmethod_cfg(smethod); if (sm_cfg_map == NULL) return (ENXIO); device_set_desc(dev, sm_cfg_map->desc); return (0); } static ACPI_STATUS tpmcrb_fix_buff_offsets(ACPI_RESOURCE *res, void *arg) { struct tpmcrb_sc *crb_sc; size_t length; uint32_t base_addr; crb_sc = (struct tpmcrb_sc *)arg; if (res->Type != ACPI_RESOURCE_TYPE_FIXED_MEMORY32) return (AE_OK); base_addr = res->Data.FixedMemory32.Address; length = res->Data.FixedMemory32.AddressLength; if (crb_sc->cmd_off > base_addr && crb_sc->cmd_off < base_addr + length) crb_sc->cmd_off -= base_addr; if (crb_sc->rsp_off > base_addr && crb_sc->rsp_off < base_addr + length) crb_sc->rsp_off -= base_addr; return (AE_OK); } static bool tpmcrb_attach_startmethod(struct tpmcrb_sc *crb_sc) { const struct tpmcrb_sm_cfg_map *sm_cfg_map; const struct tpmcrb_sm_cfg *sm_cfg; ACPI_TABLE_TPM23 *tbl; void *smdata; ACPI_STATUS status; bool ret; /* * Grab what we need from the StartMethod. */ status = AcpiGetTable(ACPI_SIG_TPM2, 1, (ACPI_TABLE_HEADER **)(void **)&tbl); if (ACPI_FAILURE(status)) return (false); sm_cfg_map = tpmcrb_acpi_startmethod_cfg(tbl->StartMethod); MPASS(sm_cfg_map != NULL); sm_cfg = &sm_cfg_map->sm_cfg; crb_sc->sm_cfg = sm_cfg; smdata = tbl + 1; if (sm_cfg->sm_attach != NULL) { ret = (*sm_cfg->sm_attach)(crb_sc, smdata, tbl->Header.Length - sizeof(*tbl)); } else { ret = true; } AcpiPutTable((ACPI_TABLE_HEADER *)tbl); return (ret); } static int tpmcrb_attach(device_t dev) { struct tpmcrb_sc *crb_sc; struct tpm_sc *sc; ACPI_HANDLE handle; ACPI_STATUS status; int result; crb_sc = device_get_softc(dev); sc = &crb_sc->base; handle = acpi_get_handle(dev); sc->dev = dev; sx_init(&sc->dev_lock, "TPM driver lock"); sc->mem_rid = 0; sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid, RF_ACTIVE); if (sc->mem_res == NULL) { tpmcrb_detach(dev); return (ENXIO); } if (!tpmcrb_attach_startmethod(crb_sc)) { tpmcrb_detach(dev); return (ENXIO); } if(!tpmcrb_request_locality(sc, 0)) { tpmcrb_detach(dev); return (ENXIO); } /* * Disable all interrupts for now, since I don't have a device that * works in CRB mode and supports them. */ AND4(sc, TPM_CRB_INT_ENABLE, ~TPM_CRB_INT_ENABLE_BIT); sc->interrupts = false; /* * Read addresses of Tx/Rx buffers and their sizes. Note that they * can be implemented by a single buffer. Also for some reason CMD * addr is stored in two 4 byte neighboring registers, whereas RSP is * stored in a single 8 byte one. */ #ifdef __amd64__ crb_sc->rsp_off = TPM_READ_8(sc->dev, TPM_CRB_CTRL_RSP_ADDR); #else crb_sc->rsp_off = TPM_READ_4(sc->dev, TPM_CRB_CTRL_RSP_ADDR); crb_sc->rsp_off |= ((uint64_t) TPM_READ_4(sc->dev, TPM_CRB_CTRL_RSP_HADDR) << 32); #endif crb_sc->cmd_off = TPM_READ_4(sc->dev, TPM_CRB_CTRL_CMD_LADDR); crb_sc->cmd_off |= ((uint64_t) TPM_READ_4(sc->dev, TPM_CRB_CTRL_CMD_HADDR) << 32); crb_sc->cmd_buf_size = TPM_READ_4(sc->dev, TPM_CRB_CTRL_CMD_SIZE); crb_sc->rsp_buf_size = TPM_READ_4(sc->dev, TPM_CRB_CTRL_RSP_SIZE); tpmcrb_relinquish_locality(sc); /* Emulator returns address in acpi space instead of an offset */ status = AcpiWalkResources(handle, "_CRS", tpmcrb_fix_buff_offsets, (void *)crb_sc); if (ACPI_FAILURE(status)) { tpmcrb_detach(dev); return (ENXIO); } if (crb_sc->rsp_off == crb_sc->cmd_off) { /* * If Tx/Rx buffers are implemented as one they have to be of * same size */ if (crb_sc->cmd_buf_size != crb_sc->rsp_buf_size) { device_printf(sc->dev, "Overlapping Tx/Rx buffers have different sizes\n"); tpmcrb_detach(dev); return (ENXIO); } } result = tpm20_init(sc); if (result != 0) tpmcrb_detach(dev); return (result); } static int tpmcrb_detach(device_t dev) { struct tpmcrb_sc *crb_sc; struct tpm_sc *sc; 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); return (0); } static bool tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off, uint32_t mask, uint32_t val, int32_t timeout) { /* Check for condition */ if ((TPM_READ_4(sc->dev, off) & mask) == val) return (true); while (timeout > 0) { if ((TPM_READ_4(sc->dev, off) & mask) == val) return (true); pause("TPM in polling mode", 1); timeout -= tick; } return (false); } static bool tpmcrb_request_locality(struct tpm_sc *sc, int locality) { uint32_t mask; /* Currently we only support Locality 0 */ if (locality != 0) return (false); mask = TPM_LOC_STATE_VALID | TPM_LOC_STATE_ASSIGNED; OR4(sc, TPM_LOC_CTRL, TPM_LOC_CTRL_REQUEST); if (!tpm_wait_for_u32(sc, TPM_LOC_STATE, mask, mask, TPM_TIMEOUT_C)) return (false); return (true); } static void tpmcrb_relinquish_locality(struct tpm_sc *sc) { OR4(sc, TPM_LOC_CTRL, TPM_LOC_CTRL_RELINQUISH); } static bool tpmcrb_cancel_cmd(struct tpm_sc *sc) { uint32_t mask = ~0; TPM_WRITE_4(sc->dev, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CMD); if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_START, mask, ~mask, TPM_TIMEOUT_B)) { device_printf(sc->dev, "Device failed to cancel command\n"); return (false); } TPM_WRITE_4(sc->dev, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CLEAR); return (true); } static bool tpmcrb_notify_cmdready(struct tpmcrb_sc *crb_sc, int timeout) { sm_statechange_t *cmdready_fn; cmdready_fn = crb_sc->sm_cfg->sm_cmdready; if (cmdready_fn == NULL) return (true); return ((*cmdready_fn)(crb_sc, timeout)); } static bool tpmcrb_notify_state_changing(struct tpmcrb_sc *crb_sc, int timeout) { sm_statechange_t *statechange_fn; statechange_fn = crb_sc->sm_cfg->sm_statechange; if (statechange_fn == NULL) return (true); return ((*statechange_fn)(crb_sc, timeout)); } static bool tpmcrb_state_idle(struct tpmcrb_sc *crb_sc, bool wait) { struct tpm_sc *sc; int mask, timeout; timeout = wait ? TPM_TIMEOUT_C : 0; sc = &crb_sc->base; OR4(sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_IDLE); if (!tpmcrb_notify_state_changing(crb_sc, timeout)) return (false); if (timeout > 0) { mask = TPM_CRB_CTRL_STS_IDLE_BIT; if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_STS, mask, mask, timeout)) return (false); } return (true); } static bool tpmcrb_state_ready(struct tpmcrb_sc *crb_sc, bool wait) { struct tpm_sc *sc; int mask, timeout; timeout = wait ? TPM_TIMEOUT_C : 0; sc = &crb_sc->base; OR4(sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_READY); if (!tpmcrb_notify_state_changing(crb_sc, timeout)) return (false); if (timeout > 0) { mask = TPM_CRB_CTRL_REQ_GO_READY; if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_STS, mask, !mask, timeout)) return (false); } return (true); } int tpmcrb_transmit(device_t dev, struct tpm_priv *priv, size_t length) { struct tpmcrb_sc *crb_sc; struct tpm_sc *sc; uint32_t mask, curr_cmd; int bytes_available, error, timeout; bool command_started, locality; crb_sc = device_get_softc(dev); sc = &crb_sc->base; sx_assert(&sc->dev_lock, SA_XLOCKED); command_started = false; locality = false; if (length > crb_sc->cmd_buf_size) { device_printf(dev, "Requested transfer is bigger than buffer size\n"); return (E2BIG); } if (!tpmcrb_request_locality(sc, 0)) { device_printf(dev, "Failed to obtain locality\n"); return (EIO); } locality = true; /* * Only check for the error once we own the locality: some * implementations do not return meaningful values in the control area * before then. An AMD Pluton fTPM using the plain CRB start method * reads all-ones, which looks like a permanently stuck tpmSts. */ if (TPM_READ_4(dev, TPM_CRB_CTRL_STS) & TPM_CRB_CTRL_STS_ERR_BIT) { device_printf(dev, "Device has Error bit set\n"); error = EIO; goto out; } /* Clear cancellation bit */ TPM_WRITE_4(dev, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CLEAR); /* Switch device to idle state if necessary */ if (!(TPM_READ_4(dev, TPM_CRB_CTRL_STS) & TPM_CRB_CTRL_STS_IDLE_BIT)) { if (!tpmcrb_state_idle(crb_sc, true)) { device_printf(dev, "Failed to transition to idle state\n"); error = EIO; goto out; } } /* Switch to ready state */ if (!tpmcrb_state_ready(crb_sc, true)) { device_printf(dev, "Failed to transition to ready state\n"); error = EIO; goto out; } /* * Calculate timeout for current command. * Command code is passed in bytes 6-10. */ curr_cmd = be32toh(*(uint32_t *) (&priv->buf[6])); timeout = tpm20_get_timeout(curr_cmd); /* Send command and tell device to process it. */ bus_write_region_stream_1(sc->mem_res, crb_sc->cmd_off, priv->buf, length); TPM_WRITE_BARRIER(dev, crb_sc->cmd_off, length); TPM_WRITE_4(dev, TPM_CRB_CTRL_START, TPM_CRB_CTRL_START_CMD); TPM_WRITE_BARRIER(dev, TPM_CRB_CTRL_START, 4); command_started = true; if (!tpmcrb_notify_cmdready(crb_sc, timeout)) { device_printf(dev, "Timeout while waiting for device to ready\n"); if (!tpmcrb_cancel_cmd(sc)) { error = EIO; goto out; } } mask = ~0; if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_START, mask, ~mask, timeout)) { device_printf(dev, "Timeout while waiting for device to process cmd\n"); if (!tpmcrb_cancel_cmd(sc)) { error = EIO; goto out; } } /* Read response header. Length is passed in bytes 2 - 6. */ bus_read_region_stream_1(sc->mem_res, crb_sc->rsp_off, priv->buf, TPM_HEADER_SIZE); bytes_available = be32toh(*(uint32_t *) (&priv->buf[2])); if (bytes_available > TPM_BUFSIZE || bytes_available < TPM_HEADER_SIZE) { device_printf(dev, "Incorrect response size: %d\n", bytes_available); error = EIO; goto out; } bus_read_region_stream_1(sc->mem_res, crb_sc->rsp_off + TPM_HEADER_SIZE, &priv->buf[TPM_HEADER_SIZE], bytes_available - TPM_HEADER_SIZE); priv->offset = 0; priv->len = bytes_available; error = 0; out: if (locality) { if (error != 0 && command_started && (TPM_READ_4(dev, TPM_CRB_CTRL_START) & TPM_CRB_CTRL_START_CMD) != 0) (void)tpmcrb_cancel_cmd(sc); if (!tpmcrb_state_idle(crb_sc, false)) { device_printf(dev, "Failed to transition to idle state post-send\n"); if (error == 0) error = EIO; } tpmcrb_relinquish_locality(sc); } return (error); } /* StartMethod Implementation Details */ /** Pluton **/ struct tpmcrb_startmethod_pluton { uint64_t sm_startaddr; uint64_t sm_replyaddr; }; static bool pluton_attach(struct tpmcrb_sc *crb_sc, void *smdataregion, size_t datasz) { struct tpmcrb_startmethod_pluton *smdata; struct tpm_sc *sc; bus_space_tag_t bst; if (datasz < sizeof(*smdata)) return (false); smdata = smdataregion; sc = &crb_sc->base; /* * 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.bst = bst; return (true); } static void pluton_detach(struct tpmcrb_sc *crb_sc) { if (crb_sc->pluton.bst == 0) return; 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 (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 */ static device_method_t tpmcrb_methods[] = { DEVMETHOD(device_probe, tpmcrb_acpi_probe), DEVMETHOD(device_attach, tpmcrb_attach), DEVMETHOD(device_detach, tpmcrb_detach), DEVMETHOD(device_shutdown, tpm20_shutdown), DEVMETHOD(device_suspend, tpm20_suspend), DEVMETHOD(device_resume, tpm20_resume), DEVMETHOD(tpm_transmit, tpmcrb_transmit), {0, 0} }; DEFINE_CLASS_1(tpmcrb, tpmcrb_driver, tpmcrb_methods, sizeof(struct tpmcrb_sc), tpm_bus_driver); DRIVER_MODULE(tpmcrb, acpi, tpmcrb_driver, 0, 0);