blob: e4770b35e4a5a5f5d2d70c41c96d66f8e01501bd [file] [log] [blame]
// Implementation of a TPM driver for the TPM TIS interface
//
// Copyright (C) 2006-2011 IBM Corporation
//
// Authors:
// Stefan Berger <stefanb@linux.vnet.ibm.com>
//
// 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)
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);
}