diff options
author | Duncan Laurie <dlaurie@chromium.org> | 2016-09-01 15:50:22 -0700 |
---|---|---|
committer | Duncan Laurie <dlaurie@chromium.org> | 2016-09-06 22:58:26 +0200 |
commit | ffa765f6eb49d242df6c74f63c4aa1e6f65143e3 (patch) | |
tree | 17e681cfca3917824abf449e4c71866ce411963a /src/drivers | |
parent | efa579fdc217b92d0696b737cda76a69d1fe1433 (diff) |
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@chromium.org>
Reviewed-on: https://review.coreboot.org/16396
Tested-by: build bot (Jenkins)
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
Reviewed-by: Philipp Deppenwiese <zaolin.daisuki@googlemail.com>
Diffstat (limited to 'src/drivers')
-rw-r--r-- | src/drivers/i2c/tpm/tpm.c | 242 |
1 files changed, 231 insertions, 11 deletions
diff --git a/src/drivers/i2c/tpm/tpm.c b/src/drivers/i2c/tpm/tpm.c index 0534c63464..5e3c0c1bc8 100644 --- a/src/drivers/i2c/tpm/tpm.c +++ b/src/drivers/i2c/tpm/tpm.c @@ -29,6 +29,7 @@ */ #include <arch/early_variables.h> +#include <commonlib/endian.h> #include <stdint.h> #include <string.h> #include <types.h> @@ -36,6 +37,7 @@ #include <console/console.h> #include <device/i2c.h> #include <endian.h> +#include <timer.h> #include "tpm.h" /* max. number of iterations after I2C NAK */ @@ -55,16 +57,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", }; @@ -102,8 +107,11 @@ static int iic_tpm_read(uint8_t addr, uint8_t *buffer, size_t len) if (tpm_dev->addr == 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, @@ -129,7 +137,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. @@ -148,6 +159,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); @@ -483,6 +495,204 @@ out_err: return -1; } +/* + * cr50 is a TPM 2.0 capable device that requries special + * handling for the I2C interface. + * + * - Timeouts need to be longer + * - Must use the older SLB9635 style write+wait+read read protocol + * - All 4 bytes of status register must be read at once + * - Burst count max is 63 bytes, and burst count behaves + * slightly differently than other I2C TPMs + * - When reading from FIFO the full burstcnt must be read + * instead of just reading header and determining the remainder + */ + +/* cr50 max burst count */ +#define CR50_MAX_BURSTCOUNT 63 + +/* 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) { + printk(BIOS_ERR, "%s: Failed to read status\n", __func__); + 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 int cr50_wait_burst_status(struct tpm_chip *chip, uint8_t mask, + size_t *burst, int *status) +{ + uint8_t buf[4]; + 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) { + printk(BIOS_WARNING, "%s: Read failed\n", __func__); + udelay(SLEEP_DURATION_SAFE); + continue; + } + + *status = buf[0]; + *burst = read_le16(&buf[1]); + + /* Check if mask matches and burst is valid */ + if ((*status & mask) == mask && + *burst > 0 && *burst <= CR50_MAX_BURSTCOUNT) + return 0; + + udelay(SLEEP_DURATION_SAFE); + } + + printk(BIOS_ERR, "%s: Timeout reading burst and status\n", __func__); + return -1; +} + +static int cr50_tis_i2c_recv(struct tpm_chip *chip, uint8_t *buf, + size_t buf_len) +{ + size_t burstcnt, current, len, expected; + uint8_t addr = TPM_DATA_FIFO(chip->vendor.locality); + int status; + int ret = -1; + + if (buf_len < TPM_HEADER_SIZE) + goto out; + + if (cr50_wait_burst_status(chip, TPM_STS_VALID, &burstcnt, &status) < 0) + goto out; + if (!(status & TPM_STS_DATA_AVAIL)) { + printk(BIOS_ERR, "%s: First chunk not available\n", __func__); + goto out; + } + + /* Read first chunk of burstcnt bytes */ + if (iic_tpm_read(addr, buf, burstcnt) != 0) { + printk(BIOS_ERR, "%s: Read failed\n", __func__); + goto out; + } + + /* Determine expected data in the return buffer */ + expected = read_be32(buf + TPM_RSP_SIZE_BYTE); + if (expected > buf_len) { + printk(BIOS_ERR, "%s: Too much data: %zu > %zu\n", + __func__, expected, buf_len); + goto out; + } + + /* Now read the rest of the data */ + current = burstcnt; + while (current < expected) { + /* Read updated burst count and check status */ + if (cr50_wait_burst_status(chip, TPM_STS_VALID, + &burstcnt, &status) < 0) + goto out; + if (!(status & TPM_STS_DATA_AVAIL)) { + printk(BIOS_ERR, "%s: Data not available\n", __func__); + goto out; + } + + len = min(burstcnt, expected - current); + if (iic_tpm_read(addr, buf + current, len) != 0) { + printk(BIOS_ERR, "%s: Read failed\n", __func__); + goto out; + } + + current += len; + } + + /* Ensure TPM is done reading data */ + if (cr50_wait_burst_status(chip, TPM_STS_VALID, &burstcnt, &status) < 0) + goto out; + if (status & TPM_STS_DATA_AVAIL) { + printk(BIOS_ERR, "%s: Data still available\n", __func__); + goto out; + } + + ret = current; + +out: + cr50_tis_i2c_ready(chip); + return ret; +} + +static int cr50_tis_i2c_send(struct tpm_chip *chip, uint8_t *buf, size_t len) +{ + int status; + size_t burstcnt, limit, 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 = cr50_tis_i2c_status(chip); + if (!(status & TPM_STS_COMMAND_READY)) { + cr50_tis_i2c_ready(chip); + if (cr50_wait_burst_status(chip, TPM_STS_COMMAND_READY, + &burstcnt, &status) < 0) + goto out; + } + + while (len > 0) { + /* Read burst count and check status */ + if (cr50_wait_burst_status(chip, TPM_STS_VALID, + &burstcnt, &status) < 0) + goto out; + if (sent > 0 && !(status & TPM_STS_DATA_EXPECT)) { + printk(BIOS_ERR, "%s: Data not expected\n", __func__); + goto out; + } + + /* Use burstcnt - 1 to account for the address byte + * that is inserted by iic_tpm_write() */ + limit = min(burstcnt - 1, len); + if (iic_tpm_write(TPM_DATA_FIFO(chip->vendor.locality), + &buf[sent], limit) != 0) { + printk(BIOS_ERR, "%s: Write failed\n", __func__); + goto out; + } + + sent += limit; + len -= limit; + } + + /* Ensure TPM is not expecting more data */ + if (cr50_wait_burst_status(chip, TPM_STS_VALID, &burstcnt, &status) < 0) + goto out; + if (status & TPM_STS_DATA_EXPECT) { + printk(BIOS_ERR, "%s: Data still expected\n", __func__); + goto out; + } + + /* Start the TPM command */ + if (iic_tpm_write(TPM_STS(chip->vendor.locality), tpm_go, + sizeof(tpm_go)) < 0) { + printk(BIOS_ERR, "%s: Start command failed\n", __func__); + goto out; + } + return sent; + +out: + 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) @@ -505,10 +715,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; @@ -527,16 +733,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_err; } - 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. |