We've found that the SLB9645 TPM sometimes seems to randomly start returning 0xFF bytes for all requests. The exact cause is yet unknown, but we should try to write our TIS code such that it avoids bad interactions with this kind of response (e.g. any wait_for_status() immediately succeeds because all "status bits" are set in the response). At least for status and burstCount readings we can say for sure that the value is nonsensical and we're already reading those in a loop until we get valid results anyway, so let's add code to explicitly discount 0xFF bytes. BRANCH=oak BUG=chrome-os-partner:55764 TEST=None Change-Id: I934d42c36d6847a22a185795cea49d282fa113d9 Signed-off-by: Julius Werner <jwerner@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/420470 Reviewed-by: Nicolas Boichat <drinkcat@chromium.org> Reviewed-on: https://review.coreboot.org/18006 Tested-by: build bot (Jenkins) Reviewed-by: Martin Roth <martinroth@google.com>
584 lines
14 KiB
C
584 lines
14 KiB
C
/*
|
|
* 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
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <arch/early_variables.h>
|
|
#include <commonlib/endian.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <types.h>
|
|
#include <delay.h>
|
|
#include <console/console.h>
|
|
#include <device/i2c.h>
|
|
#include <endian.h>
|
|
#include <timer.h>
|
|
#include "tpm.h"
|
|
|
|
/* max. number of iterations after I2C NAK */
|
|
#define MAX_COUNT 3
|
|
|
|
#define SLEEP_DURATION 60 /* in usec */
|
|
#define SLEEP_DURATION_LONG 210 /* in usec */
|
|
#define SLEEP_DURATION_PROBE_MS 1000 /* in msec */
|
|
|
|
/* max. number of iterations after I2C NAK for 'long' commands
|
|
* we need this especially for sending TPM_READY, since the cleanup after the
|
|
* transtion to the ready state may take some time, but it is unpredictable
|
|
* how long it will take.
|
|
*/
|
|
#define MAX_COUNT_LONG 50
|
|
|
|
/* expected value for DIDVID register */
|
|
#define TPM_TIS_I2C_DID_VID_9635 0x000b15d1L
|
|
#define TPM_TIS_I2C_DID_VID_9645 0x001a15d1L
|
|
|
|
enum i2c_chip_type {
|
|
SLB9635,
|
|
SLB9645,
|
|
UNKNOWN,
|
|
};
|
|
|
|
static const char * const chip_name[] = {
|
|
[SLB9635] = "slb9635tt",
|
|
[SLB9645] = "slb9645tt",
|
|
[UNKNOWN] = "unknown/fallback to slb9635",
|
|
};
|
|
|
|
/* Structure to store I2C TPM specific stuff */
|
|
struct tpm_inf_dev {
|
|
int bus;
|
|
unsigned int addr;
|
|
unsigned int sleep_short; /* Short sleep duration in usec */
|
|
unsigned int sleep_long; /* Long sleep duration in usec */
|
|
uint8_t buf[TPM_BUFSIZE + sizeof(uint8_t)]; // max. buffer size + addr
|
|
enum i2c_chip_type chip_type;
|
|
};
|
|
|
|
static struct tpm_inf_dev g_tpm_dev CAR_GLOBAL;
|
|
|
|
/*
|
|
* iic_tpm_read() - read from TPM register
|
|
* @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 (little-endian format, i.e. first byte is put into buffer[0]).
|
|
*
|
|
* NOTE: TPM is big-endian for multi-byte values. Multi-byte
|
|
* values have to be swapped.
|
|
*
|
|
* Return -1 on error, 0 on success.
|
|
*/
|
|
static int iic_tpm_read(uint8_t addr, uint8_t *buffer, size_t len)
|
|
{
|
|
struct tpm_inf_dev *tpm_dev = car_get_var_ptr(&g_tpm_dev);
|
|
int rc;
|
|
int count;
|
|
|
|
if (tpm_dev->addr == 0)
|
|
return -1;
|
|
|
|
switch (tpm_dev->chip_type) {
|
|
case SLB9635:
|
|
case UNKNOWN:
|
|
/* slb9635 protocol should work in both cases */
|
|
for (count = 0; count < MAX_COUNT; count++) {
|
|
rc = i2c_write_raw(tpm_dev->bus, tpm_dev->addr,
|
|
&addr, 1);
|
|
if (rc == 0)
|
|
break; /* success, break to skip sleep */
|
|
|
|
udelay(tpm_dev->sleep_short);
|
|
}
|
|
|
|
if (rc)
|
|
return -1;
|
|
|
|
/* 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 < MAX_COUNT; count++) {
|
|
udelay(tpm_dev->sleep_short);
|
|
rc = i2c_read_raw(tpm_dev->bus, tpm_dev->addr,
|
|
buffer, len);
|
|
if (rc == 0)
|
|
break; /* success, break to skip sleep */
|
|
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
/* 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.
|
|
*/
|
|
struct i2c_seg aseg = { .read = 0, .chip = tpm_dev->addr,
|
|
.buf = &addr, .len = 1 };
|
|
struct i2c_seg dseg = { .read = 1, .chip = tpm_dev->addr,
|
|
.buf = buffer, .len = len };
|
|
for (count = 0; count < MAX_COUNT; count++) {
|
|
rc = i2c_transfer(tpm_dev->bus, &aseg, 1) ||
|
|
i2c_transfer(tpm_dev->bus, &dseg, 1);
|
|
if (rc == 0)
|
|
break; /* break here to skip sleep */
|
|
udelay(tpm_dev->sleep_short);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* take care of 'guard time' */
|
|
udelay(tpm_dev->sleep_short);
|
|
if (rc)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int iic_tpm_write_generic(uint8_t addr, uint8_t *buffer, size_t len,
|
|
unsigned int sleep_time,
|
|
uint8_t max_count)
|
|
{
|
|
struct tpm_inf_dev *tpm_dev = car_get_var_ptr(&g_tpm_dev);
|
|
int rc = 0;
|
|
int count;
|
|
|
|
if (len > TPM_BUFSIZE) {
|
|
printk(BIOS_DEBUG, "%s: Length %zd is too large\n", __func__, len);
|
|
return -1;
|
|
}
|
|
|
|
/* prepare send buffer */
|
|
tpm_dev->buf[0] = addr;
|
|
memcpy(&(tpm_dev->buf[1]), buffer, len);
|
|
|
|
if (tpm_dev->addr == 0)
|
|
return -1;
|
|
for (count = 0; count < max_count; count++) {
|
|
rc = i2c_write_raw(tpm_dev->bus, tpm_dev->addr,
|
|
tpm_dev->buf, len + 1);
|
|
if (rc == 0)
|
|
break; /* success, break to skip sleep */
|
|
|
|
udelay(sleep_time);
|
|
}
|
|
|
|
/* take care of 'guard time' */
|
|
udelay(tpm_dev->sleep_short);
|
|
if (rc)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* iic_tpm_write() - write to TPM register
|
|
* @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.
|
|
*
|
|
* NOTE: use this function instead of the iic_tpm_write_generic function.
|
|
*
|
|
* Return -EIO on error, 0 on success
|
|
*/
|
|
static int iic_tpm_write(uint8_t addr, uint8_t *buffer, size_t len)
|
|
{
|
|
struct tpm_inf_dev *tpm_dev = car_get_var_ptr(&g_tpm_dev);
|
|
return iic_tpm_write_generic(addr, buffer, len, tpm_dev->sleep_short,
|
|
MAX_COUNT);
|
|
}
|
|
|
|
/*
|
|
* This function is needed especially for the cleanup situation after
|
|
* sending TPM_READY
|
|
* */
|
|
static int iic_tpm_write_long(uint8_t addr, uint8_t *buffer, size_t len)
|
|
{
|
|
struct tpm_inf_dev *tpm_dev = car_get_var_ptr(&g_tpm_dev);
|
|
return iic_tpm_write_generic(addr, buffer, len, tpm_dev->sleep_long,
|
|
MAX_COUNT_LONG);
|
|
}
|
|
|
|
static int check_locality(struct tpm_chip *chip, int loc)
|
|
{
|
|
uint8_t buf;
|
|
|
|
if (iic_tpm_read(TPM_ACCESS(loc), &buf, 1) < 0)
|
|
return -1;
|
|
|
|
if ((buf & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) ==
|
|
(TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) {
|
|
chip->vendor.locality = loc;
|
|
return loc;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void release_locality(struct tpm_chip *chip, int loc, int force)
|
|
{
|
|
uint8_t buf;
|
|
if (iic_tpm_read(TPM_ACCESS(loc), &buf, 1) < 0)
|
|
return;
|
|
|
|
if (force || (buf & (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) ==
|
|
(TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) {
|
|
buf = TPM_ACCESS_ACTIVE_LOCALITY;
|
|
iic_tpm_write(TPM_ACCESS(loc), &buf, 1);
|
|
}
|
|
}
|
|
|
|
static int request_locality(struct tpm_chip *chip, int loc)
|
|
{
|
|
uint8_t buf = TPM_ACCESS_REQUEST_USE;
|
|
|
|
if (check_locality(chip, loc) >= 0)
|
|
return loc; /* we already have the locality */
|
|
|
|
iic_tpm_write(TPM_ACCESS(loc), &buf, 1);
|
|
|
|
/* wait for burstcount */
|
|
int timeout = 2 * 1000; /* 2s timeout */
|
|
while (timeout) {
|
|
if (check_locality(chip, loc) >= 0)
|
|
return loc;
|
|
mdelay(TPM_TIMEOUT);
|
|
timeout--;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static uint8_t tpm_tis_i2c_status(struct tpm_chip *chip)
|
|
{
|
|
/* NOTE: Since I2C read may fail, return 0 in this case --> time-out */
|
|
uint8_t buf;
|
|
if (iic_tpm_read(TPM_STS(chip->vendor.locality), &buf, 1) < 0)
|
|
return 0;
|
|
else if (buf == 0xff) /* Some TPMs sometimes randomly return 0xff. */
|
|
return 0;
|
|
else
|
|
return buf;
|
|
}
|
|
|
|
static void tpm_tis_i2c_ready(struct tpm_chip *chip)
|
|
{
|
|
/* this causes the current command to be aborted */
|
|
uint8_t buf = TPM_STS_COMMAND_READY;
|
|
iic_tpm_write_long(TPM_STS(chip->vendor.locality), &buf, 1);
|
|
}
|
|
|
|
static ssize_t get_burstcount(struct tpm_chip *chip)
|
|
{
|
|
ssize_t burstcnt;
|
|
uint8_t buf[3];
|
|
|
|
/* wait for burstcount */
|
|
int timeout = 2 * 1000; /* 2s timeout */
|
|
while (timeout) {
|
|
/* Note: STS is little endian */
|
|
if (iic_tpm_read(TPM_STS(chip->vendor.locality) + 1, buf, 3) < 0)
|
|
burstcnt = 0;
|
|
else
|
|
burstcnt = (buf[2] << 16) + (buf[1] << 8) + buf[0];
|
|
|
|
if (burstcnt && burstcnt != 0xffffff)
|
|
return burstcnt;
|
|
mdelay(TPM_TIMEOUT);
|
|
timeout--;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int wait_for_stat(struct tpm_chip *chip, uint8_t mask, int *status)
|
|
{
|
|
unsigned long timeout = 2 * 1024;
|
|
while (timeout) {
|
|
*status = tpm_tis_i2c_status(chip);
|
|
if ((*status & mask) == mask)
|
|
return 0;
|
|
mdelay(TPM_TIMEOUT);
|
|
timeout--;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int recv_data(struct tpm_chip *chip, uint8_t *buf, size_t count)
|
|
{
|
|
size_t size = 0;
|
|
|
|
while (size < count) {
|
|
ssize_t burstcnt = get_burstcount(chip);
|
|
int rc;
|
|
|
|
/* burstcount < 0 = 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_DATA_FIFO(chip->vendor.locality),
|
|
&(buf[size]),
|
|
burstcnt);
|
|
if (rc == 0)
|
|
size += burstcnt;
|
|
|
|
}
|
|
return size;
|
|
}
|
|
|
|
static int tpm_tis_i2c_recv(struct tpm_chip *chip, uint8_t *buf, size_t count)
|
|
{
|
|
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(chip, buf, TPM_HEADER_SIZE);
|
|
if (size < TPM_HEADER_SIZE) {
|
|
printk(BIOS_DEBUG, "tpm_tis_i2c_recv: Unable to read header\n");
|
|
goto out;
|
|
}
|
|
|
|
memcpy(&expected, buf + TPM_RSP_SIZE_BYTE, sizeof(expected));
|
|
expected = be32_to_cpu(expected);
|
|
if ((size_t)expected > count) {
|
|
size = -1;
|
|
goto out;
|
|
}
|
|
|
|
size += recv_data(chip, &buf[TPM_HEADER_SIZE],
|
|
expected - TPM_HEADER_SIZE);
|
|
if (size < expected) {
|
|
printk(BIOS_DEBUG, "tpm_tis_i2c_recv: Unable to "
|
|
"read remainder of result\n");
|
|
size = -1;
|
|
goto out;
|
|
}
|
|
|
|
wait_for_stat(chip, TPM_STS_VALID, &status);
|
|
if (status & TPM_STS_DATA_AVAIL) { /* retry? */
|
|
printk(BIOS_DEBUG, "tpm_tis_i2c_recv: Error left over data\n");
|
|
size = -1;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
tpm_tis_i2c_ready(chip);
|
|
|
|
return size;
|
|
}
|
|
|
|
static int tpm_tis_i2c_send(struct tpm_chip *chip, uint8_t *buf, size_t len)
|
|
{
|
|
int status;
|
|
size_t count = 0;
|
|
uint8_t sts = TPM_STS_GO;
|
|
|
|
if (len > TPM_BUFSIZE)
|
|
return -1; /* command is too long for our TPM, sorry */
|
|
|
|
status = tpm_tis_i2c_status(chip);
|
|
if ((status & TPM_STS_COMMAND_READY) == 0) {
|
|
tpm_tis_i2c_ready(chip);
|
|
if (wait_for_stat(chip, TPM_STS_COMMAND_READY, &status) < 0)
|
|
goto out_err;
|
|
}
|
|
|
|
while (count < len - 1) {
|
|
ssize_t burstcnt = get_burstcount(chip);
|
|
|
|
/* burstcount < 0 = TPM is busy */
|
|
if (burstcnt < 0)
|
|
return burstcnt;
|
|
|
|
if (burstcnt > (len-1-count))
|
|
burstcnt = len-1-count;
|
|
|
|
#ifdef CONFIG_TPM_I2C_BURST_LIMITATION
|
|
if (burstcnt > CONFIG_TPM_I2C_BURST_LIMITATION)
|
|
burstcnt = CONFIG_TPM_I2C_BURST_LIMITATION;
|
|
#endif /* CONFIG_TPM_I2C_BURST_LIMITATION */
|
|
|
|
if (iic_tpm_write(TPM_DATA_FIFO(chip->vendor.locality),
|
|
&(buf[count]), burstcnt) == 0)
|
|
count += burstcnt;
|
|
|
|
wait_for_stat(chip, TPM_STS_VALID, &status);
|
|
if ((status & TPM_STS_DATA_EXPECT) == 0)
|
|
goto out_err;
|
|
}
|
|
|
|
/* write last byte */
|
|
iic_tpm_write(TPM_DATA_FIFO(chip->vendor.locality), &(buf[count]), 1);
|
|
|
|
wait_for_stat(chip, TPM_STS_VALID, &status);
|
|
if ((status & TPM_STS_DATA_EXPECT) != 0)
|
|
goto out_err;
|
|
|
|
/* go and do it */
|
|
iic_tpm_write(TPM_STS(chip->vendor.locality), &sts, 1);
|
|
|
|
return len;
|
|
|
|
out_err:
|
|
tpm_tis_i2c_ready(chip);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Initialization of I2C TPM */
|
|
|
|
int tpm_vendor_probe(unsigned bus, uint32_t addr)
|
|
{
|
|
struct tpm_inf_dev *tpm_dev = car_get_var_ptr(&g_tpm_dev);
|
|
struct stopwatch sw;
|
|
uint8_t buf = 0;
|
|
int ret;
|
|
long sw_run_duration = SLEEP_DURATION_PROBE_MS;
|
|
|
|
tpm_dev->chip_type = UNKNOWN;
|
|
tpm_dev->bus = bus;
|
|
tpm_dev->addr = addr;
|
|
tpm_dev->sleep_short = SLEEP_DURATION;
|
|
tpm_dev->sleep_long = SLEEP_DURATION_LONG;
|
|
|
|
/*
|
|
* Probe TPM. Check if the TPM_ACCESS register's ValidSts bit is set(1)
|
|
* If the bit remains clear(0) then claim that init has failed.
|
|
*/
|
|
stopwatch_init_msecs_expire(&sw, sw_run_duration);
|
|
do {
|
|
ret = iic_tpm_read(TPM_ACCESS(0), &buf, 1);
|
|
if (!ret && (buf & TPM_STS_VALID)) {
|
|
sw_run_duration = stopwatch_duration_msecs(&sw);
|
|
break;
|
|
}
|
|
udelay(SLEEP_DURATION);
|
|
} while (!stopwatch_expired(&sw));
|
|
|
|
printk(BIOS_INFO,
|
|
"%s: ValidSts bit %s(%d) in TPM_ACCESS register after %ld ms\n",
|
|
__func__, (buf & TPM_STS_VALID) ? "set" : "clear",
|
|
(buf & TPM_STS_VALID) >> 7, sw_run_duration);
|
|
|
|
/*
|
|
* Claim failure if the ValidSts (bit 7) is clear.
|
|
*/
|
|
if (!(buf & TPM_STS_VALID))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tpm_vendor_init(struct tpm_chip *chip, unsigned bus, uint32_t dev_addr)
|
|
{
|
|
struct tpm_inf_dev *tpm_dev = car_get_var_ptr(&g_tpm_dev);
|
|
uint32_t vendor;
|
|
|
|
if (dev_addr == 0) {
|
|
printk(BIOS_ERR, "%s: missing device address\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
tpm_dev->chip_type = UNKNOWN;
|
|
tpm_dev->bus = bus;
|
|
tpm_dev->addr = dev_addr;
|
|
tpm_dev->sleep_short = SLEEP_DURATION;
|
|
tpm_dev->sleep_long = SLEEP_DURATION_LONG;
|
|
|
|
memset(&chip->vendor, 0, sizeof(struct tpm_vendor_specific));
|
|
chip->is_open = 1;
|
|
|
|
chip->vendor.req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID;
|
|
chip->vendor.req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID;
|
|
chip->vendor.req_canceled = TPM_STS_COMMAND_READY;
|
|
chip->vendor.status = &tpm_tis_i2c_status;
|
|
chip->vendor.recv = &tpm_tis_i2c_recv;
|
|
chip->vendor.send = &tpm_tis_i2c_send;
|
|
chip->vendor.cancel = &tpm_tis_i2c_ready;
|
|
|
|
/* Disable interrupts (not supported) */
|
|
chip->vendor.irq = 0;
|
|
|
|
if (request_locality(chip, 0) != 0)
|
|
return -1;
|
|
|
|
/* Read four bytes from DID_VID register */
|
|
if (iic_tpm_read(TPM_DID_VID(0), (uint8_t *)&vendor, 4) < 0)
|
|
goto out_err;
|
|
|
|
if (vendor == TPM_TIS_I2C_DID_VID_9645) {
|
|
tpm_dev->chip_type = SLB9645;
|
|
} else if (be32_to_cpu(vendor) == TPM_TIS_I2C_DID_VID_9635) {
|
|
tpm_dev->chip_type = SLB9635;
|
|
} else {
|
|
printk(BIOS_DEBUG, "Vendor ID 0x%08x not recognized.\n", vendor);
|
|
goto out_err;
|
|
}
|
|
|
|
printk(BIOS_DEBUG, "I2C TPM %u:%02x (chip type %s device-id 0x%X)\n",
|
|
tpm_dev->bus, tpm_dev->addr,
|
|
chip_name[tpm_dev->chip_type], vendor >> 16);
|
|
|
|
/*
|
|
* A timeout query to TPM can be placed here.
|
|
* Standard timeout values are used so far
|
|
*/
|
|
|
|
return 0;
|
|
|
|
out_err:
|
|
release_locality(chip, 0, 1);
|
|
return -1;
|
|
}
|
|
|
|
void tpm_vendor_cleanup(struct tpm_chip *chip)
|
|
{
|
|
release_locality(chip, chip->vendor.locality, 1);
|
|
}
|