[coreboot-gerrit] Patch set updated for coreboot: drivers/i2c/tpm: Add support for cr50 TPM

Duncan Laurie (dlaurie@chromium.org) gerrit at coreboot.org
Fri Sep 2 01:22:10 CEST 2016


Duncan Laurie (dlaurie at chromium.org) just uploaded a new patch set to gerrit, which you can find at https://review.coreboot.org/16396

-gerrit

commit 92be696950ab14b20e4ab5a174c5ea1ee362e938
Author: Duncan Laurie <dlaurie at chromium.org>
Date:   Thu Sep 1 15:50:22 2016 -0700

    drivers/i2c/tpm: Add support for cr50 TPM
    
    Add support for the cr50 TPM used in apollolake chromebooks.
    This requires custom handling due to chip limitations, which
    may be revisited but are needed to get things working today.
    
    - timeouts need to be longer
    - must use the older style write+wait+read read protocol
    - all 4 bytes of status register must be read at once
    - same limitation applies when reading burst count from status reg
    - burst count max is 63 bytes, and burst count behaves
    slightly differently than other I2C TPMs
    - TPM expects the host to drain the full burst count (63 bytes)
    from the FIFO on a read
    
    Luckily the existing driver provides most abstraction needed to
    make this work seamlessly.  To maximize code re-use the support
    for cr50 is added directly instead of as a separate driver and the
    style is kept similar to the rest of the driver code.
    
    This was tested with the cr50 TPM on a reef board with vboot
    use of TPM for secdata storage and factory initialization.
    
    Change-Id: I9b0bc282e41e779da8bf9184be0a11649735a101
    Signed-off-by: Duncan Laurie <dlaurie at chromium.org>
---
 src/drivers/i2c/tpm/tpm.c | 202 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 191 insertions(+), 11 deletions(-)

diff --git a/src/drivers/i2c/tpm/tpm.c b/src/drivers/i2c/tpm/tpm.c
index c447001..0e0d531 100644
--- a/src/drivers/i2c/tpm/tpm.c
+++ b/src/drivers/i2c/tpm/tpm.c
@@ -36,6 +36,7 @@
 #include <console/console.h>
 #include <device/i2c.h>
 #include <endian.h>
+#include <timer.h>
 #include "tpm.h"
 
 /* Address of the TPM on the I2C bus */
@@ -44,6 +45,9 @@
 /* max. number of iterations after I2C NAK */
 #define MAX_COUNT 3
 
+/* cr50 max burst count */
+#define CR50_MAX_BURSTCOUNT 63
+
 #define SLEEP_DURATION 60 /* in usec */
 #define SLEEP_DURATION_LONG 210 /* in usec */
 #define SLEEP_DURATION_SAFE 750 /* in usec */
@@ -58,16 +62,19 @@
 /* expected value for DIDVID register */
 #define TPM_TIS_I2C_DID_VID_9635 0x000b15d1L
 #define TPM_TIS_I2C_DID_VID_9645 0x001a15d1L
+#define TPM_TIS_I2C_DID_VID_CR50 0x00281ae0L
 
 enum i2c_chip_type {
 	SLB9635,
 	SLB9645,
+	CR50,
 	UNKNOWN,
 };
 
 static const char * const chip_name[] = {
 	[SLB9635] = "slb9635tt",
 	[SLB9645] = "slb9645tt",
+	[CR50] = "cr50",
 	[UNKNOWN] = "unknown/fallback to slb9635",
 };
 
@@ -105,8 +112,11 @@ static int iic_tpm_read(uint8_t addr, uint8_t *buffer, size_t len)
 
 	if (tpm_dev->bus < 0)
 		return -1;
-	if ((tpm_dev->chip_type == SLB9635) ||
-	    (tpm_dev->chip_type == UNKNOWN)) {
+
+	switch (tpm_dev->chip_type) {
+	case SLB9635:
+	case CR50:
+	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,
@@ -132,7 +142,10 @@ static int iic_tpm_read(uint8_t addr, uint8_t *buffer, size_t len)
 				break;  /* success, break to skip sleep */
 
 		}
-	} else {
+		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.
@@ -151,6 +164,7 @@ static int iic_tpm_read(uint8_t addr, uint8_t *buffer, size_t len)
 			udelay(tpm_dev->sleep_short);
 		}
 	}
+	}
 
 	/* take care of 'guard time' */
 	udelay(tpm_dev->sleep_short);
@@ -486,6 +500,162 @@ out_err:
 	return -1;
 }
 
+/*
+ * cr50 is a TPM 2.0 capable device that requries special
+ * handling for the I2C interface.
+ */
+
+/* cr50 requires all 4 bytes of status register to be read */
+static uint8_t cr50_tis_i2c_status(struct tpm_chip *chip)
+{
+	uint8_t buf[4];
+	if (iic_tpm_read(TPM_STS(chip->vendor.locality), buf, sizeof(buf)) < 0)
+		return 0;
+	return buf[0];
+}
+
+/* cr50 requires all 4 bytes of status register to be written */
+static void cr50_tis_i2c_ready(struct tpm_chip *chip)
+{
+	uint8_t buf[4] = { TPM_STS_COMMAND_READY };
+	iic_tpm_write_long(TPM_STS(chip->vendor.locality), buf, sizeof(buf));
+}
+
+/* cr50 uses bytes 3:2 of status register for burst count and
+ * all 4 bytes must be read */
+static ssize_t cr50_get_burstcount(struct tpm_chip *chip)
+{
+	uint8_t buf[4];
+	ssize_t burstcnt;
+	struct stopwatch sw;
+
+	stopwatch_init_usecs_expire(&sw, 10 * SLEEP_DURATION_SAFE);
+
+	while (!stopwatch_expired(&sw)) {
+		if (iic_tpm_read(TPM_STS(chip->vendor.locality),
+				 buf, sizeof(buf)) != 0)
+			return -1;
+
+		burstcnt = (buf[2] << 8) + buf[1];
+		if (burstcnt > 0 && burstcnt <= CR50_MAX_BURSTCOUNT)
+			return burstcnt;
+
+		udelay(SLEEP_DURATION_SAFE);
+	}
+
+	return -1;
+}
+
+static int cr50_tis_i2c_recv(struct tpm_chip *chip, uint8_t *buf,
+			     size_t buf_len)
+{
+	uint32_t expected_buf;
+	ssize_t burstcnt;
+	size_t current, len, expected;
+	int status;
+	uint8_t addr = TPM_DATA_FIFO(chip->vendor.locality);
+	uint8_t extra[CR50_MAX_BURSTCOUNT];
+
+	if (buf_len < TPM_HEADER_SIZE)
+		goto out;
+
+	burstcnt = cr50_get_burstcount(chip);
+	if (burstcnt <= 0 || burstcnt > buf_len)
+		goto out;
+
+	/* Read first chunk of burstcnt bytes */
+	if (iic_tpm_read(addr, buf, burstcnt) != 0)
+		goto out;
+
+	memcpy(&expected_buf, buf + TPM_RSP_SIZE_BYTE, sizeof(expected_buf));
+	expected = (size_t)be32_to_cpu(expected_buf);
+	if (expected > buf_len)
+		goto out;
+
+	/* Now read the rest of the data */
+	current = burstcnt;
+	while (current < expected) {
+		len = min(burstcnt, expected - current);
+
+		if (iic_tpm_read(addr, buf + current, len) != 0)
+			goto out;
+
+		current += len;
+	}
+
+	/* cr50 expects the host to drain the FIFO */
+	len = current % burstcnt;
+	if (len && iic_tpm_read(addr, extra, len) != 0)
+		goto out;
+
+	wait_for_stat(chip, TPM_STS_VALID, &status);
+	if (status & TPM_STS_DATA_AVAIL) {
+		printk(BIOS_ERR, "TPM: Data Still Available\n");
+		goto out;
+	}
+
+	cr50_tis_i2c_ready(chip);
+	return current;
+
+out:
+	printk(BIOS_ERR, "%s failed\n", __func__);
+	cr50_tis_i2c_ready(chip);
+	return -1;
+}
+
+static int cr50_tis_i2c_send(struct tpm_chip *chip, uint8_t *buf, size_t len)
+{
+	int status;
+	ssize_t burstcnt;
+	size_t sent = 0;
+	uint8_t tpm_go[4] = { TPM_STS_GO };
+
+	if (len > TPM_BUFSIZE)
+		return -1;
+
+	/* Wait until TPM is ready for a command */
+	status = chip->vendor.status(chip);
+	if ((status & TPM_STS_COMMAND_READY) == 0) {
+		cr50_tis_i2c_ready(chip);
+		if (wait_for_stat(chip, TPM_STS_COMMAND_READY, &status) < 0) {
+			printk(BIOS_ERR, "TPM: Command Ready Timeout\n");
+			goto out;
+		}
+	}
+
+	/* Get the burst count */
+	burstcnt = cr50_get_burstcount(chip);
+	if (burstcnt < 0)
+		goto out;
+
+	while (len > 0) {
+		ssize_t limit = min(burstcnt - 1, len);
+
+		if (iic_tpm_write(TPM_DATA_FIFO(chip->vendor.locality),
+				  &(buf[sent]), limit) != 0)
+			goto out;
+
+		sent += limit;
+		len -= limit;
+	}
+
+	/* Ensure TPM is not expecting more data */
+	wait_for_stat(chip, TPM_STS_VALID, &status);
+	if ((status & TPM_STS_DATA_EXPECT) != 0) {
+		printk(BIOS_ERR, "TPM: Data Still Expected\n");
+			goto out;
+	}
+
+	/* Start the TPM command */
+	iic_tpm_write(TPM_STS(chip->vendor.locality), tpm_go, sizeof(tpm_go));
+	return sent;
+
+out:
+	printk(BIOS_ERR, "%s failed\n", __func__);
+	cr50_tis_i2c_ready(chip);
+	return -1;
+}
+
 /* Initialization of I2C TPM */
 
 int tpm_vendor_init(struct tpm_chip *chip, unsigned bus, uint32_t dev_addr)
@@ -506,10 +676,6 @@ int tpm_vendor_init(struct tpm_chip *chip, unsigned bus, uint32_t dev_addr)
 	memset(&chip->vendor, 0, sizeof(struct tpm_vendor_specific));
 	chip->is_open = 1;
 
-	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;
 	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;
@@ -528,16 +694,30 @@ int tpm_vendor_init(struct tpm_chip *chip, unsigned bus, uint32_t dev_addr)
 		tpm_dev->chip_type = SLB9645;
 	} else if (be32_to_cpu(vendor) == TPM_TIS_I2C_DID_VID_9635) {
 		tpm_dev->chip_type = SLB9635;
+	} else if (vendor == TPM_TIS_I2C_DID_VID_CR50) {
+		tpm_dev->chip_type = CR50;
 	} else {
 		printk(BIOS_DEBUG, "Vendor ID 0x%08x not recognized.\n", vendor);
 		goto out_release;
 	}
 
-	tpm_dev->sleep_short = SLEEP_DURATION;
-	tpm_dev->sleep_long = SLEEP_DURATION_LONG;
+	if (tpm_dev->chip_type == CR50) {
+		chip->vendor.status = &cr50_tis_i2c_status;
+		chip->vendor.recv = &cr50_tis_i2c_recv;
+		chip->vendor.send = &cr50_tis_i2c_send;
+		chip->vendor.cancel = &cr50_tis_i2c_ready;
+	} else {
+		tpm_dev->sleep_short = SLEEP_DURATION;
+		tpm_dev->sleep_long = SLEEP_DURATION_LONG;
+		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;
+	}
 
-	printk(BIOS_DEBUG, "1.2 TPM (chip type %s device-id 0x%X)\n",
-		 chip_name[tpm_dev->chip_type], vendor >> 16);
+	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.



More information about the coreboot-gerrit mailing list