// Implementation of a TPM driver for the TPM TIS interface // // Copyright (C) 2006-2011 IBM Corporation // // Authors: // Stefan Berger // // This file may be distributed under the terms of the GNU LGPLv3 license. #include "byteorder.h" // be32_to_cpu #include "config.h" // CONFIG_TPM_TIS_SHA1THRESHOLD #include "hw/tpm_drivers.h" // struct tpm_driver #include "std/tcg.h" // TCG_RESPONSE_TIMEOUT #include "output.h" // warn_timeout #include "stacks.h" // yield #include "string.h" // memcpy #include "util.h" // timer_calc_usec #include "x86.h" // readl /* low level driver implementation */ struct tpm_driver { u32 *timeouts; u32 *durations; void (*set_timeouts)(u32 timeouts[4], u32 durations[3]); u32 (*probe)(void); TPMVersion (*get_tpm_version)(void); u32 (*init)(void); u32 (*activate)(u8 locty); u32 (*ready)(void); u32 (*senddata)(const u8 *const data, u32 len); u32 (*readresp)(u8 *buffer, u32 *len); u32 (*waitdatavalid)(void); u32 (*waitrespready)(enum tpmDurationType to_t); }; extern struct tpm_driver tpm_drivers[]; #define TIS_DRIVER_IDX 0 #define CRB_DRIVER_IDX 1 #define TPM_NUM_DRIVERS 2 #define TPM_INVALID_DRIVER 0xf static const u32 tis_default_timeouts[4] = { TIS_DEFAULT_TIMEOUT_A, TIS_DEFAULT_TIMEOUT_B, TIS_DEFAULT_TIMEOUT_C, TIS_DEFAULT_TIMEOUT_D, }; static const u32 tpm_default_durations[3] = { TPM_DEFAULT_DURATION_SHORT, TPM_DEFAULT_DURATION_MEDIUM, TPM_DEFAULT_DURATION_LONG, }; /* determined values */ static u32 tpm_default_dur[3]; static u32 tpm_default_to[4]; static u32 crb_cmd_size; static void *crb_cmd; static u32 crb_resp_size; static void *crb_resp; static u32 wait_reg8(u8* reg, u32 time, u8 mask, u8 expect) { if (!CONFIG_TCGBIOS) return 0; u32 rc = 1; u32 end = timer_calc_usec(time); for (;;) { u8 value = readl(reg); if ((value & mask) == expect) { rc = 0; break; } if (timer_check(end)) { warn_timeout(); break; } yield(); } return rc; } static u32 tis_wait_access(u8 locty, u32 time, u8 mask, u8 expect) { return wait_reg8(TIS_REG(locty, TIS_REG_ACCESS), time, mask, expect); } static u32 tis_wait_sts(u8 locty, u32 time, u8 mask, u8 expect) { return wait_reg8(TIS_REG(locty, TIS_REG_STS), time, mask, expect); } static u32 crb_wait_reg(u8 locty, u16 reg, u32 time, u8 mask, u8 expect) { return wait_reg8(CRB_REG(locty, reg), time, mask, expect); } /* if device is not there, return '0', '1' otherwise */ static u32 tis_probe(void) { if (!CONFIG_TCGBIOS) return 0; /* Wait for the interface to report it's ready */ u32 rc = tis_wait_access(0, TIS_DEFAULT_TIMEOUT_A, TIS_ACCESS_TPM_REG_VALID_STS, TIS_ACCESS_TPM_REG_VALID_STS); if (rc) return 0; u32 didvid = readl(TIS_REG(0, TIS_REG_DID_VID)); if ((didvid != 0) && (didvid != 0xffffffff)) rc = 1; /* TPM 2 has an interface register */ u32 ifaceid = readl(TIS_REG(0, TIS_REG_IFACE_ID)); if ((ifaceid & 0xf) != 0xf) { if ((ifaceid & 0xf) == 1) { /* CRB is active; no TIS */ return 0; } if ((ifaceid & (1 << 13)) == 0) { /* TIS cannot be selected */ return 0; } /* write of 0 to bits 17-18 selects TIS */ writel(TIS_REG(0, TIS_REG_IFACE_ID), 0); /* since we only support TIS, we lock it */ writel(TIS_REG(0, TIS_REG_IFACE_ID), (1 << 19)); } return rc; } static TPMVersion tis_get_tpm_version(void) { u32 reg = readl(TIS_REG(0, TIS_REG_IFACE_ID)); /* * FIFO interface as defined in TIS1.3 is active * Interface capabilities are defined in TIS_REG_INTF_CAPABILITY */ if ((reg & 0xf) == 0xf) { reg = readl(TIS_REG(0, TIS_REG_INTF_CAPABILITY)); /* Interface 1.3 for TPM 2.0 */ if (((reg >> 28) & 0x7) == 3) return TPM_VERSION_2; } /* FIFO interface as defined in PTP for TPM 2.0 is active */ else if ((reg & 0xf) == 0) { return TPM_VERSION_2; } return TPM_VERSION_1_2; } static void init_timeout(int driver) { if (tpm_drivers[driver].durations == NULL) { u32 *durations = tpm_default_dur; memcpy(durations, tpm_default_durations, sizeof(tpm_default_durations)); tpm_drivers[driver].durations = durations; } if (tpm_drivers[driver].timeouts == NULL) { u32 *timeouts = tpm_default_to; memcpy(timeouts, tis_default_timeouts, sizeof(tis_default_timeouts)); tpm_drivers[driver].timeouts = timeouts; } } static u32 tis_init(void) { if (!CONFIG_TCGBIOS) return 1; writeb(TIS_REG(0, TIS_REG_INT_ENABLE), 0); init_timeout(TIS_DRIVER_IDX); return 1; } static void set_timeouts(u32 timeouts[4], u32 durations[3]) { if (!CONFIG_TCGBIOS) return; u32 *tos = tpm_drivers[TIS_DRIVER_IDX].timeouts; u32 *dus = tpm_drivers[TIS_DRIVER_IDX].durations; if (tos && tos != tis_default_timeouts && timeouts) memcpy(tos, timeouts, 4 * sizeof(u32)); if (dus && dus != tpm_default_durations && durations) memcpy(dus, durations, 3 * sizeof(u32)); } static u32 tis_activate(u8 locty) { if (!CONFIG_TCGBIOS) return 0; u32 rc = 0; u8 acc; int l; u32 timeout_a = tpm_drivers[TIS_DRIVER_IDX].timeouts[TIS_TIMEOUT_TYPE_A]; if (!(readb(TIS_REG(locty, TIS_REG_ACCESS)) & TIS_ACCESS_ACTIVE_LOCALITY)) { /* release locality in use top-downwards */ for (l = 4; l >= 0; l--) writeb(TIS_REG(l, TIS_REG_ACCESS), TIS_ACCESS_ACTIVE_LOCALITY); } /* request access to locality */ writeb(TIS_REG(locty, TIS_REG_ACCESS), TIS_ACCESS_REQUEST_USE); acc = readb(TIS_REG(locty, TIS_REG_ACCESS)); if ((acc & TIS_ACCESS_ACTIVE_LOCALITY)) { writeb(TIS_REG(locty, TIS_REG_STS), TIS_STS_COMMAND_READY); rc = tis_wait_sts(locty, timeout_a, TIS_STS_COMMAND_READY, TIS_STS_COMMAND_READY); } return rc; } static u32 tis_find_active_locality(void) { if (!CONFIG_TCGBIOS) return 0; u8 locty; for (locty = 0; locty <= 4; locty++) { if ((readb(TIS_REG(locty, TIS_REG_ACCESS)) & TIS_ACCESS_ACTIVE_LOCALITY)) return locty; } tis_activate(0); return 0; } static u32 tis_ready(void) { if (!CONFIG_TCGBIOS) return 0; u32 rc = 0; u8 locty = tis_find_active_locality(); u32 timeout_b = tpm_drivers[TIS_DRIVER_IDX].timeouts[TIS_TIMEOUT_TYPE_B]; writeb(TIS_REG(locty, TIS_REG_STS), TIS_STS_COMMAND_READY); rc = tis_wait_sts(locty, timeout_b, TIS_STS_COMMAND_READY, TIS_STS_COMMAND_READY); return rc; } static u32 tis_senddata(const u8 *const data, u32 len) { if (!CONFIG_TCGBIOS) return 0; u32 rc = 0; u32 offset = 0; u32 end_loop = 0; u16 burst = 0; u8 locty = tis_find_active_locality(); u32 timeout_d = tpm_drivers[TIS_DRIVER_IDX].timeouts[TIS_TIMEOUT_TYPE_D]; u32 end = timer_calc_usec(timeout_d); do { while (burst == 0) { burst = readl(TIS_REG(locty, TIS_REG_STS)) >> 8; if (burst == 0) { if (timer_check(end)) { warn_timeout(); break; } yield(); } } if (burst == 0) { rc = TCG_RESPONSE_TIMEOUT; break; } while (1) { writeb(TIS_REG(locty, TIS_REG_DATA_FIFO), data[offset++]); burst--; if (burst == 0 || offset == len) break; } if (offset == len) end_loop = 1; } while (end_loop == 0); return rc; } static u32 tis_readresp(u8 *buffer, u32 *len) { if (!CONFIG_TCGBIOS) return 0; u32 rc = 0; u32 offset = 0; u32 sts; u8 locty = tis_find_active_locality(); while (offset < *len) { buffer[offset] = readb(TIS_REG(locty, TIS_REG_DATA_FIFO)); offset++; sts = readb(TIS_REG(locty, TIS_REG_STS)); /* data left ? */ if ((sts & TIS_STS_DATA_AVAILABLE) == 0) break; } *len = offset; return rc; } static u32 tis_waitdatavalid(void) { if (!CONFIG_TCGBIOS) return 0; u32 rc = 0; u8 locty = tis_find_active_locality(); u32 timeout_c = tpm_drivers[TIS_DRIVER_IDX].timeouts[TIS_TIMEOUT_TYPE_C]; if (tis_wait_sts(locty, timeout_c, TIS_STS_VALID, TIS_STS_VALID) != 0) rc = 1; return rc; } static u32 tis_waitrespready(enum tpmDurationType to_t) { if (!CONFIG_TCGBIOS) return 0; u32 rc = 0; u8 locty = tis_find_active_locality(); u32 timeout = tpm_drivers[TIS_DRIVER_IDX].durations[to_t]; writeb(TIS_REG(locty ,TIS_REG_STS), TIS_STS_TPM_GO); if (tis_wait_sts(locty, timeout, TIS_STS_DATA_AVAILABLE, TIS_STS_DATA_AVAILABLE) != 0) rc = 1; return rc; } #define CRB_STATE_VALID_STS 0b10000000 #define CRB_STATE_LOC_ASSIGNED 0x00000010 #define CRB_STATE_READY_MASK (CRB_STATE_VALID_STS | CRB_STATE_LOC_ASSIGNED) /* if device is not there, return '0', '1' otherwise */ static u32 crb_probe(void) { if (!CONFIG_TCGBIOS) return 0; /* Wait for the interface to report it's ready */ u32 rc = crb_wait_reg(0, CRB_REG_LOC_STATE, TIS2_DEFAULT_TIMEOUT_D, CRB_STATE_READY_MASK, CRB_STATE_VALID_STS); if (rc) return 0; u32 ifaceid = readl(CRB_REG(0, CRB_REG_INTF_ID)); if ((ifaceid & 0xf) != 0xf) { if ((ifaceid & 0xf) == 1) { /* CRB is active */ } else if ((ifaceid & (1 << 14)) == 0) { /* CRB cannot be selected */ return 0; } /* write of 1 to bits 17-18 selects CRB */ writel(CRB_REG(0, CRB_REG_INTF_ID), (1 << 17)); /* lock it */ writel(CRB_REG(0, CRB_REG_INTF_ID), (1 << 19)); } /* no support for 64 bit addressing yet */ if (readl(CRB_REG(0, CRB_REG_CTRL_CMD_HADDR))) return 0; u64 addr = readq(CRB_REG(0, CRB_REG_CTRL_RSP_ADDR)); if (addr > 0xffffffff) return 0; return 1; } static TPMVersion crb_get_tpm_version(void) { /* CRB is supposed to be TPM 2.0 only */ return TPM_VERSION_2; } static u32 crb_init(void) { if (!CONFIG_TCGBIOS) return 1; crb_cmd = (void*)readl(CRB_REG(0, CRB_REG_CTRL_CMD_LADDR)); crb_cmd_size = readl(CRB_REG(0, CRB_REG_CTRL_CMD_SIZE)); crb_resp = (void*)readl(CRB_REG(0, CRB_REG_CTRL_RSP_ADDR)); crb_resp_size = readl(CRB_REG(0, CRB_REG_CTRL_RSP_SIZE)); init_timeout(CRB_DRIVER_IDX); return 0; } static u32 crb_activate(u8 locty) { if (!CONFIG_TCGBIOS) return 0; writeb(CRB_REG(locty, CRB_REG_LOC_CTRL), 1); return 0; } static u32 crb_find_active_locality(void) { if (!CONFIG_TCGBIOS) return 0; return 0; } #define CRB_CTRL_REQ_CMD_READY 0b1 #define CRB_START_INVOKE 0b1 #define CRB_CTRL_STS_ERROR 0b1 static u32 crb_ready(void) { if (!CONFIG_TCGBIOS) return 0; u32 rc = 0; u8 locty = crb_find_active_locality(); u32 timeout_c = tpm_drivers[CRB_DRIVER_IDX].timeouts[TIS_TIMEOUT_TYPE_C]; writel(CRB_REG(locty, CRB_REG_CTRL_REQ), CRB_CTRL_REQ_CMD_READY); rc = crb_wait_reg(locty, CRB_REG_CTRL_REQ, timeout_c, CRB_CTRL_REQ_CMD_READY, 0); return rc; } static u32 crb_senddata(const u8 *const data, u32 len) { if (!CONFIG_TCGBIOS) return 0; if (len > crb_cmd_size) return 1; u8 locty = crb_find_active_locality(); memcpy(crb_cmd, data, len); writel(CRB_REG(locty, CRB_REG_CTRL_START), CRB_START_INVOKE); return 0; } static u32 crb_readresp(u8 *buffer, u32 *len) { if (!CONFIG_TCGBIOS) return 0; u8 locty = crb_find_active_locality(); if (readl(CRB_REG(locty, CRB_REG_CTRL_STS)) & CRB_CTRL_STS_ERROR) return 1; if (*len < 6) return 1; memcpy(buffer, crb_resp, 6); u32 expected = be32_to_cpu(*(u32 *) &buffer[2]); if (expected < 6) return 1; *len = (*len < expected) ? *len : expected; memcpy(buffer + 6, crb_resp + 6, *len - 6); return 0; } static u32 crb_waitdatavalid(void) { return 0; } static u32 crb_waitrespready(enum tpmDurationType to_t) { if (!CONFIG_TCGBIOS) return 0; u32 rc = 0; u8 locty = crb_find_active_locality(); u32 timeout = tpm_drivers[CRB_DRIVER_IDX].durations[to_t]; rc = crb_wait_reg(locty, CRB_REG_CTRL_START, timeout, CRB_START_INVOKE, 0); return rc; } struct tpm_driver tpm_drivers[TPM_NUM_DRIVERS] = { [TIS_DRIVER_IDX] = { .timeouts = NULL, .durations = NULL, .set_timeouts = set_timeouts, .probe = tis_probe, .get_tpm_version = tis_get_tpm_version, .init = tis_init, .activate = tis_activate, .ready = tis_ready, .senddata = tis_senddata, .readresp = tis_readresp, .waitdatavalid = tis_waitdatavalid, .waitrespready = tis_waitrespready, }, [CRB_DRIVER_IDX] = { .timeouts = NULL, .durations = NULL, .set_timeouts = set_timeouts, .probe = crb_probe, .get_tpm_version = crb_get_tpm_version, .init = crb_init, .activate = crb_activate, .ready = crb_ready, .senddata = crb_senddata, .readresp = crb_readresp, .waitdatavalid = crb_waitdatavalid, .waitrespready = crb_waitrespready, }, }; static u8 TPMHW_driver_to_use = TPM_INVALID_DRIVER; TPMVersion tpmhw_probe(void) { unsigned int i; for (i = 0; i < TPM_NUM_DRIVERS; i++) { struct tpm_driver *td = &tpm_drivers[i]; if (td->probe() != 0) { td->init(); TPMHW_driver_to_use = i; return td->get_tpm_version(); } } return TPM_VERSION_NONE; } int tpmhw_is_present(void) { return TPMHW_driver_to_use != TPM_INVALID_DRIVER; } int tpmhw_transmit(u8 locty, struct tpm_req_header *req, void *respbuffer, u32 *respbufferlen, enum tpmDurationType to_t) { if (TPMHW_driver_to_use == TPM_INVALID_DRIVER) return -1; struct tpm_driver *td = &tpm_drivers[TPMHW_driver_to_use]; u32 irc = td->activate(locty); if (irc != 0) { /* tpm could not be activated */ return -1; } irc = td->senddata((void*)req, be32_to_cpu(req->totlen)); if (irc != 0) return -1; irc = td->waitdatavalid(); if (irc != 0) return -1; irc = td->waitrespready(to_t); if (irc != 0) return -1; irc = td->readresp(respbuffer, respbufferlen); if (irc != 0 || *respbufferlen < sizeof(struct tpm_rsp_header)) return -1; td->ready(); return 0; } void tpmhw_set_timeouts(u32 timeouts[4], u32 durations[3]) { struct tpm_driver *td = &tpm_drivers[TPMHW_driver_to_use]; td->set_timeouts(timeouts, durations); }