diff options
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. |