blob: f1a07ee525e60afa5ebf1f6ee3b104c7e2e72d7a [file] [log] [blame]
/*
* Copyright (C) 2011 Infineon Technologies
*
* Authors:
* Peter Huewe <huewe.external@infineon.com>
*
* Description:
* Device driver for TCG/TCPA TPM (trusted platform module).
* Specifications at www.trustedcomputinggroup.org
*
* This device driver implements the TPM interface as defined in
* the TCG TPM Interface Spec version 1.2, revision 1.0 and the
* Infineon I2C Protocol Stack Specification v0.20.
*
* It is based on the Linux kernel driver tpm.c from Leendert van
* Dorn, Dave Safford, Reiner Sailer, and Kyleen Hall.
*
* Version: 2.1.1
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <endian.h>
#include <stdio.h>
#include "base/time.h"
#include "base/xalloc.h"
#include "drivers/bus/i2c/i2c.h"
#include "drivers/tpm/i2c.h"
#include "drivers/tpm/slb9635_i2c.h"
enum {
TpmTimeout = 1 // msecs
};
// Expected value for DIDVID register.
enum {
TPM_TIS_I2C_DID_VID_9635 = 0x000b15d1L,
TPM_TIS_I2C_DID_VID_9645 = 0x001a15d1L
};
static const char * const chip_name[] = {
[SLB9635] = "slb9635tt",
[SLB9645] = "slb9645tt",
[UNKNOWN] = "unknown/fallback to slb9635",
};
/*
* iic_tpm_read() - read from TPM register
*
* @tpm: pointer to TPM structure
* @addr: register address to read from
* @buffer: provided by caller
* @len: number of bytes to read
*
* Read len bytes from TPM register and put them into buffer.
*
* NOTE: TPM is big-endian for multi-byte values. Multi-byte values have to
* be swapped.
*
* Return nonzero on error, 0 on success.
*/
static int iic_tpm_read(Slb9635I2c *tpm, uint8_t addr, uint8_t *buffer,
size_t len)
{
int rc;
int count;
if (!tpm->base.bus)
return -1;
if ((tpm->chip_type == SLB9635) || (tpm->chip_type == UNKNOWN)) {
// slb9635 protocol should work in both cases.
for (count = 0; count < 50; count++) {
rc = i2c_write_raw(tpm->base.bus, tpm->base.addr,
&addr, 1);
if (rc == 0)
break;
udelay(200);
}
if (rc)
return -rc;
/*
* After the TPM has successfully received the register address
* it needs some time, thus we're sleeping here again, before
* retrieving the data
*/
for (count = 0; count < 50; count++) {
udelay(200);
rc = i2c_read_raw(tpm->base.bus, tpm->base.addr,
buffer, len);
if (rc == 0)
break;
}
} else {
/*
* use a combined read for newer chips
* unfortunately the smbus functions are not suitable due to
* the 32 byte limit of the smbus.
* retries should usually not be needed, but are kept just to
* be safe on the safe side.
*/
I2cSeg aseg = { .read = 0, .chip = tpm->base.addr,
.buf = &addr, .len = 1 };
I2cSeg dseg = { .read = 1, .chip = tpm->base.addr,
.buf = buffer, .len = len };
for (count = 0; count < 50; count++) {
rc = tpm->base.bus->transfer(tpm->base.bus, &aseg, 1) ||
tpm->base.bus->transfer(tpm->base.bus, &dseg, 1);
if (rc == 0)
break;
udelay(200);
}
}
// Take care of 'guard time'.
udelay(60);
if (rc)
return -rc;
return 0;
}
/*
* iic_tpm_write() - write to TPM register
*
* @tpm: pointer to TPM structure
* @addr: register address to write to
* @buffer: containing data to be written
* @len: number of bytes to write
*
* Write len bytes from provided buffer to TPM register (little
* endian format, i.e. buffer[0] is written as first byte).
*
* NOTE: TPM is big-endian for multi-byte values. Multi-byte
* values have to be swapped.
*
* Return nonzero on error, 0 on success
*/
static int iic_tpm_write(Slb9635I2c *tpm, uint8_t addr, const uint8_t *buffer,
size_t len)
{
int rc = 0;
int count;
if (len > MaxTpmBufSize) {
printf("%s: Length %zd is too large\n", __func__, len);
return -1;
}
// Prepare send buffer.
tpm->buf[0] = addr;
memcpy(&tpm->buf[1], buffer, len);
if (!tpm->base.bus)
return -1;
for (count = 0; count < 50; count++) {
rc = i2c_write_raw(tpm->base.bus, tpm->base.addr,
tpm->buf, len + 1);
if (rc == 0)
break;
udelay(200);
}
// Take care of 'guard time'.
udelay(60);
if (rc)
return -rc;
return 0;
}
#define TPM_HEADER_SIZE 10
enum {
TpmAccessValid = 0x80,
TpmAccessActiveLocality = 0x20,
TpmAccessRequestPending = 0x04,
TpmAccessRequestUse = 0x02,
};
enum {
TpmStsValid = 0x80,
TpmStsCommandReady = 0x40,
TpmStsGo = 0x20,
TpmStsDataAvail = 0x10,
TpmStsDataExpect = 0x08,
};
static inline uint8_t tpm_access(uint8_t locality)
{
return 0x0 | (locality << 4);
}
static inline uint8_t tpm_sts(uint8_t locality)
{
return 0x1 | (locality << 4);
}
static inline uint8_t tpm_data_fifo(uint8_t locality)
{
return 0x5 | (locality << 4);
}
static inline uint8_t tpm_did_vid(uint8_t locality)
{
return 0x6 | (locality << 4);
}
static int check_locality(Slb9635I2c *tpm, int loc)
{
uint8_t buf;
int rc;
rc = iic_tpm_read(tpm, tpm_access(loc), &buf, 1);
if (rc < 0)
return rc;
if ((buf & (TpmAccessActiveLocality | TpmAccessValid)) ==
(TpmAccessActiveLocality | TpmAccessValid)) {
tpm->base.locality = loc;
return loc;
}
return -1;
}
static void release_locality(Slb9635I2c *tpm, int loc, int force)
{
uint8_t buf;
if (iic_tpm_read(tpm, tpm_access(loc), &buf, 1) < 0)
return;
if (force || (buf & (TpmAccessRequestPending | TpmAccessValid)) ==
(TpmAccessRequestPending | TpmAccessValid)) {
buf = TpmAccessActiveLocality;
iic_tpm_write(tpm, tpm_access(loc), &buf, 1);
}
}
static int request_locality(Slb9635I2c *tpm, int loc)
{
uint8_t buf = TpmAccessRequestUse;
if (check_locality(tpm, loc) >= 0)
return loc; // We already have the locality.
iic_tpm_write(tpm, tpm_access(loc), &buf, 1);
// Wait for burstcount.
uint64_t start = time_us(0);
while (time_us(start) < 2 * 1000 * 1000) { // Two second timeout.
if (check_locality(tpm, loc) >= 0)
return loc;
mdelay(TpmTimeout);
}
return -1;
}
static uint8_t tpm_status(I2cTpmChipOps *me)
{
Slb9635I2c *tpm = container_of(me, Slb9635I2c, base.chip_ops);
// NOTE: since i2c read may fail, return 0 in this case --> time-out
uint8_t buf;
if (iic_tpm_read(tpm, tpm_sts(tpm->base.locality), &buf, 1) < 0)
return 0;
else
return buf;
}
static void tpm_ready(I2cTpmChipOps *me)
{
Slb9635I2c *tpm = container_of(me, Slb9635I2c, base.chip_ops);
// This causes the current command to be aborted.
uint8_t buf = TpmStsCommandReady;
iic_tpm_write(tpm, tpm_sts(tpm->base.locality), &buf, 1);
}
static ssize_t get_burstcount(Slb9635I2c *tpm)
{
ssize_t burstcnt;
uint8_t buf[3];
// Wait for burstcount.
uint64_t start = time_us(0);
while (time_us(start) < 2 * 1000 * 1000) { // Two second timeout.
// Note: STS is little endian.
if (iic_tpm_read(tpm, tpm_sts(tpm->base.locality) + 1,
buf, 3) < 0)
burstcnt = 0;
else
burstcnt = (buf[2] << 16) + (buf[1] << 8) + buf[0];
if (burstcnt)
return burstcnt;
mdelay(TpmTimeout);
}
return -1;
}
static int wait_for_stat(Slb9635I2c *tpm, uint8_t mask, int *status)
{
uint64_t start = time_us(0);
while (time_us(start) < 2 * 1000 * 1000) { // Two second timeout.
// Check current status.
*status = tpm_status(&tpm->base.chip_ops);
if ((*status & mask) == mask)
return 0;
mdelay(TpmTimeout);
}
return -1;
}
static int recv_data(Slb9635I2c *tpm, uint8_t *buf, size_t count)
{
size_t size = 0;
ssize_t burstcnt;
int rc;
while (size < count) {
burstcnt = get_burstcount(tpm);
// Burstcount < 0 means the tpm is busy.
if (burstcnt < 0)
return burstcnt;
// Limit received data to max left.
if (burstcnt > (count - size))
burstcnt = count - size;
rc = iic_tpm_read(tpm, tpm_data_fifo(tpm->base.locality),
&buf[size], burstcnt);
if (rc == 0)
size += burstcnt;
}
return size;
}
static int tpm_recv(I2cTpmChipOps *me, uint8_t *buf, size_t count)
{
Slb9635I2c *tpm = container_of(me, Slb9635I2c, base.chip_ops);
int size = 0;
uint32_t expected;
int status;
if (count < TPM_HEADER_SIZE) {
size = -1;
goto out;
}
// Read first 10 bytes, including tag, paramsize, and result.
size = recv_data(tpm, buf, TPM_HEADER_SIZE);
if (size < TPM_HEADER_SIZE) {
printf("tpm_tis_i2c_recv: Unable to read header\n");
goto out;
}
memcpy(&expected, buf + TpmCmdCountOffset, sizeof(expected));
expected = be32toh(expected);
if ((size_t)expected > count) {
size = -1;
goto out;
}
size += recv_data(tpm, &buf[TPM_HEADER_SIZE],
expected - TPM_HEADER_SIZE);
if (size < expected) {
printf("tpm_tis_i2c_recv: Unable to "
"read remainder of result\n");
size = -1;
goto out;
}
wait_for_stat(tpm, TpmStsValid, &status);
if (status & TpmStsDataAvail) { // retry?
printf("tpm_tis_i2c_recv: Error left over data\n");
size = -1;
goto out;
}
out:
tpm_ready(&tpm->base.chip_ops);
return size;
}
static int tpm_send(I2cTpmChipOps *me, const uint8_t *buf, size_t len)
{
Slb9635I2c *tpm = container_of(me, Slb9635I2c, base.chip_ops);
int rc, status;
ssize_t burstcnt;
size_t count = 0;
uint8_t sts = TpmStsGo;
if (len > MaxTpmBufSize)
return -1; // Command is too long for our tpm, sorry.
status = tpm->base.chip_ops.status(&tpm->base.chip_ops);
if ((status & TpmStsCommandReady) == 0) {
tpm_ready(&tpm->base.chip_ops);
if (wait_for_stat(tpm, TpmStsCommandReady, &status) < 0) {
rc = -1;
goto out_err;
}
}
while (count < len - 1) {
burstcnt = get_burstcount(tpm);
// Burstcount < 0 means the tpm is busy.
if (burstcnt < 0)
return burstcnt;
if (burstcnt > (len - 1 - count))
burstcnt = len - 1 - count;
rc = iic_tpm_write(tpm, tpm_data_fifo(tpm->base.locality),
&(buf[count]), burstcnt);
if (rc == 0)
count += burstcnt;
wait_for_stat(tpm, TpmStsValid, &status);
if ((status & TpmStsDataExpect) == 0) {
rc = -1;
goto out_err;
}
}
// Write last byte.
iic_tpm_write(tpm, tpm_data_fifo(tpm->base.locality),
&buf[count], 1);
wait_for_stat(tpm, TpmStsValid, &status);
if ((status & TpmStsDataExpect) != 0) {
rc = -1;
goto out_err;
}
// Go and do it.
iic_tpm_write(tpm, tpm_sts(tpm->base.locality), &sts, 1);
return len;
out_err:
tpm_ready(&tpm->base.chip_ops);
return rc;
}
static int tpm_init(I2cTpmChipOps *me)
{
Slb9635I2c *tpm = container_of(me, Slb9635I2c, base.chip_ops);
if (request_locality(tpm, 0) != 0)
return -1;
// Read four bytes from DID_VID register.
uint32_t vendor;
if (iic_tpm_read(tpm, tpm_did_vid(0), (uint8_t *)&vendor, 4) < 0) {
release_locality(tpm, 0, 1);
return -1;
}
if (vendor == TPM_TIS_I2C_DID_VID_9645) {
tpm->chip_type = SLB9645;
} else if (be32toh(vendor) == TPM_TIS_I2C_DID_VID_9635) {
tpm->chip_type = SLB9635;
} else {
printf("Vendor ID 0x%08x not recognized.\n", vendor);
release_locality(tpm, 0, 1);
return -1;
}
printf("1.2 TPM (chip type %s device-id 0x%X)\n",
chip_name[tpm->chip_type], vendor >> 16);
return 0;
}
static int tpm_cleanup(I2cTpmChipOps *me)
{
Slb9635I2c *tpm = container_of(me, Slb9635I2c, base.chip_ops);
release_locality(tpm, tpm->base.locality, 1);
return 0;
}
Slb9635I2c *new_slb9635_i2c(I2cOps *bus, uint8_t addr)
{
Slb9635I2c *tpm = xmalloc(sizeof(*tpm));
i2ctpm_fill_in(&tpm->base, bus, addr, TpmStsDataAvail | TpmStsValid,
TpmStsDataAvail | TpmStsValid, TpmStsCommandReady);
tpm->base.chip_ops.init = &tpm_init;
tpm->base.chip_ops.cleanup = &tpm_cleanup;
tpm->base.chip_ops.recv = &tpm_recv;
tpm->base.chip_ops.send = &tpm_send;
tpm->base.chip_ops.cancel = &tpm_ready;
tpm->base.chip_ops.status = &tpm_status;
return tpm;
}