
This driver add support to STMicroelectronics ST33ZP24 I2C TPM.
Signed-off-by: Jean-Luc BLANC jean-luc.blanc@st.com --- README | 7 + drivers/tpm/tpm_i2c_stm_st33.c | 659 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 666 insertions(+) create mode 100644 drivers/tpm/tpm_i2c_stm_st33.c
diff --git a/README b/README index 56c398a..a1eae3e 100644 --- a/README +++ b/README @@ -1351,6 +1351,13 @@ The following options need to be configured: Support additional hash in locality 4 command for STMicroelectronics TPMs (SPI or I2C). Require CONFIG_CMD_TPM.
+ CONFIG_TPM_ST_I2C + Support I2C STMicroelectronics TPM. Require I2C support + + CONFIG_TPM_I2C_BUS + Define the i2c bus number for the TPM device + + - USB Support: At the moment only the UHCI host controller is supported (PIP405, MIP405, MPC5200); define diff --git a/drivers/tpm/tpm_i2c_stm_st33.c b/drivers/tpm/tpm_i2c_stm_st33.c new file mode 100644 index 0000000..ff257af --- /dev/null +++ b/drivers/tpm/tpm_i2c_stm_st33.c @@ -0,0 +1,659 @@ +/* + * STMicroelectronics TPM I2C UBOOT Linux driver for TPM ST33ZP24 + * Copyright (C) 2014 STMicroelectronics + + * + * Description: Device driver for ST33ZP24 I2C TPM TCG. + * + * This device driver implements the TPM interface as defined in + * the TCG TPM Interface Spec version 1.21, revision 1.0 and the + * STMicroelectronics I2C Protocol Stack Specification version 1.2.0. + * + * SPDX-License-Identifier: GPL-2.0+ + * + * @Author: Jean-Luc BLANC jean-luc.blanc@st.com + * + * @File: tpm_i2c_stm_st33.c + */ + +#include <common.h> +#include <i2c.h> +#include <linux/types.h> +#include <tpm.h> +#include <errno.h> +#include <asm/unaligned.h> + +#define MINOR_NUM_I2C 224 + +#define TPM_ACCESS (0x0) +#define TPM_STS (0x18) +#define TPM_HASH_END (0x20) +#define TPM_DATA_FIFO (0x24) +#define TPM_HASH_DATA (0x24) +#define TPM_HASH_START (0x28) +#define TPM_INTF_CAPABILITY (0x14) +#define TPM_INT_STATUS (0x10) +#define TPM_INT_ENABLE (0x08) + +#define TPM_DUMMY_BYTE 0xAA +#define TPM_WRITE_DIRECTION 0x80 +#define TPM_HEADER_SIZE 10 +#define TPM_BUFSIZE 2048 + +#define LOCALITY0 0 +#define LOCALITY4 4 +#define LOCALITY0_I2C_ADDR 0x13 +#define LOCALITY4_I2C_ADDR 0x1B + +/* Index of Count field in TPM response buffer */ +#define TPM_RSP_SIZE_BYTE 2 + +/* Error value returned on various TPM driver errors. */ +#define TPM_DRIVER_ERR (1) + +/* Maximum command duration */ +#define TPM_MAX_COMMAND_DURATION 120000 + +enum stm33zp24_access { + TPM_ACCESS_VALID = 0x80, + TPM_ACCESS_ACTIVE_LOCALITY = 0x20, + TPM_ACCESS_REQUEST_PENDING = 0x04, + TPM_ACCESS_REQUEST_USE = 0x02, +}; + +enum stm33zp24_status { + TPM_STS_VALID = 0x80, + TPM_STS_COMMAND_READY = 0x40, + TPM_STS_GO = 0x20, + TPM_STS_DATA_AVAIL = 0x10, + TPM_STS_DATA_EXPECT = 0x08, +}; + +enum stm33zp24_int_flags { + TPM_GLOBAL_INT_ENABLE = 0x80, + TPM_INTF_CMD_READY_INT = 0x080, + TPM_INTF_FIFO_AVALAIBLE_INT = 0x040, + TPM_INTF_WAKE_UP_READY_INT = 0x020, + TPM_INTF_LOCTPM_BUFSIZE4SOFTRELEASE_INT = 0x008, + TPM_INTF_LOCALITY_CHANGE_INT = 0x004, + TPM_INTF_STS_VALID_INT = 0x002, + TPM_INTF_DATA_AVAIL_INT = 0x001, +}; + +enum tis_defaults { + TIS_SHORT_TIMEOUT = 750, /* ms */ + TIS_LONG_TIMEOUT = 2000, /* 2 sec */ +}; + +struct tpm_chip { + uint addr; + uint i2c_bus; + int is_open; + u8 buf[TPM_BUFSIZE]; + int locality; + unsigned long timeout_a, timeout_b, timeout_c, timeout_d; /* msec */ + unsigned long duration; /* msec */ +}; + +static struct tpm_chip tpm_dev; + +/* + * write8_reg + * Send byte to the TIS register according to the ST33ZP24 I2C protocol. + * @param: tpm_register, the tpm tis register where the data should be written + * @param: tpm_data, the tpm_data to write inside the tpm_register + * @param: tpm_size, The length of the data + * @return: Returns zero in case of success else the negative error code. + */ +static int write8_reg(u8 addr, u8 tpm_register, + const u8 *tpm_data, u16 tpm_size) +{ + u8 data; + + data = tpm_register; + memcpy(&(tpm_dev.buf[0]), &data, sizeof(data)); + memcpy(&(tpm_dev.buf[0])+1, tpm_data, tpm_size); + return i2c_write(addr, 0, 0, &tpm_dev.buf[0], + tpm_size + 1); +} /* write8_reg() */ + +/* +* read8_reg +* Recv byte from the TIS register according to the ST33ZP24 I2C protocol. +* @param: tpm_register, the tpm tis register where the data should be read +* @param: tpm_data, the TPM response +* @param: tpm_size, tpm TPM response size to read. +* @return: Returns zero in case of success else the negative error code. +*/ +static int read8_reg(u8 addr, u8 tpm_register, +u8 *tpm_data, int tpm_size) +{ + u8 status = 0; + u8 data; + + data = TPM_DUMMY_BYTE; + status = write8_reg(addr, tpm_register, &data, 1); + if (status == 0) + status = i2c_read(addr, 0, 0, tpm_data, tpm_size); +return status; +} /* read8_reg() */ + +/* + * I2C_WRITE_DATA + * Send byte to the TIS register according to the ST33ZP24 I2C protocol. + * @param: client, the chip description + * @param: tpm_register, the tpm tis register where the data should be written + * @param: tpm_data, the tpm_data to write inside the tpm_register + * @param: tpm_size, The length of the data + * @return: Returns zero in case of success else the negative error code. + */ +#define I2C_WRITE_DATA(client, tpm_register, tpm_data, tpm_size)\ + (write8_reg(client, tpm_register | \ + TPM_WRITE_DIRECTION, tpm_data, tpm_size)) + +/* + * I2C_READ_DATA + * Recv byte from the TIS register according to the ST33ZP24 I2C protocol. + * @param: tpm, the chip description + * @param: tpm_register, the tpm tis register where the data should be read + * @param: tpm_data, the TPM response + * @param: tpm_size, tpm TPM response size to read. + * @return: Returns zero in case of success else the negative error code. + */ +#define I2C_READ_DATA(client, tpm_register, tpm_data, tpm_size)\ + (read8_reg(client, tpm_register, tpm_data, tpm_size)) + +/* + * release_locality release the active locality + * @param: chip, the tpm chip description. + * @return: Returns zero in case of success else the negative error code. + */ +static int release_locality(struct tpm_chip *chip) +{ + u8 data = TPM_ACCESS_ACTIVE_LOCALITY; + + return I2C_WRITE_DATA(tpm_dev.addr, TPM_ACCESS, &data, 1); +} /* release_locality() */ + + +/* + * check_locality if the locality is active + * @param: chip, the tpm chip description + * @return: the active locality or -TPM_DRIVER_ERR. + */ +static int check_locality(struct tpm_chip *chip) +{ + u8 data; + u8 status; + + status = I2C_READ_DATA(chip->addr, TPM_ACCESS, &data, 1); + if ((status == 0) && (data & + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) + return chip->locality; + return -TPM_DRIVER_ERR; +} /* check_locality() */ + +/* + * request_locality request the TPM locality + * @param: chip, the chip description + * @return: the active locality or -TPM_DRIVER_ERR. + */ +static int request_locality(struct tpm_chip *chip) +{ + unsigned long start, stop; + long rc; + u8 data; + + if (check_locality(chip) == chip->locality) + return chip->locality; + + data = TPM_ACCESS_REQUEST_USE; + rc = I2C_WRITE_DATA(chip->addr, TPM_ACCESS, &data, 1); + if (rc < 0) + goto end; + + /* wait for locality activated */ + start = get_timer(0); + stop = chip->timeout_a; + do { + if (check_locality(chip) >= 0) + return chip->locality; + } while (get_timer(start) < stop); + rc = -TPM_DRIVER_ERR; +end: + return rc; +} /* request_locality() */ + +/* + * tpm_stm_spi_status return the TPM_STS register + * @param: chip, the tpm chip descriptionc + * @return: the TPM_STS register value. + */ +static u8 tpm_stm_i2c_status(struct tpm_chip *chip) +{ + u8 data; + + I2C_READ_DATA(chip->addr, TPM_STS, &data, 1); + return data; +} /* tpm_stm_i2c_status() */ + +/* + * get_burstcount return the burstcount address 0x19 0x1A + * @param: chip, the chip description + * return: the burstcount or -TPM_DRIVER_ERR in case of error. + */ +static int get_burstcount(struct tpm_chip *chip) +{ + unsigned long start, stop; + int burstcnt, status, ret; + u8 tpm_reg, temp; + + /* wait for burstcount */ + start = get_timer(0); + stop = chip->timeout_d; + do { + tpm_reg = TPM_STS + 1; + status = I2C_READ_DATA(chip->addr, tpm_reg, &temp, 1); + if (status < 0) + break; + + tpm_reg = tpm_reg + 1; + burstcnt = temp; + status = I2C_READ_DATA(chip->addr, tpm_reg, &temp, 1); + if (status < 0) + break; + + burstcnt |= temp << 8; + if (burstcnt) { + ret = burstcnt; + goto end; + } + udelay(TIS_SHORT_TIMEOUT*1000); + } while (get_timer(start) < stop); + ret = -TPM_DRIVER_ERR; +end: + return ret; +} /* get_burstcount() */ + +/* + * tpm_stm_i2c_command_ready, move TPM state to Command Ready state. + * @param: chip, tpm_chip description. + * return: 0 on success or -TPM_DRIVER_ERR in case of error. + */ +static int tpm_stm_i2c_command_ready(struct tpm_chip *chip) +{ + unsigned long start, stop; + int ret; + u8 status; + u8 data; + + data = TPM_STS_COMMAND_READY; + I2C_WRITE_DATA(chip->addr, TPM_STS, &data, 1); + start = get_timer(0); + stop = tpm_dev.timeout_b; + do { + status = tpm_stm_i2c_status(chip); + printf("status = %d\n", status); + if ((status & data) == data) + return 0; + } while (get_timer(start) < stop); + ret = -TPM_DRIVER_ERR; +return ret; +} /* tpm_stm_i2c_command_ready() */ + +/* + * recv_data receive data + * @param: chip, the tpm chip description + * @param: buf, the buffer where the data are received + * @param: count, the number of data to receive + * @return: the number of bytes read from TPM FIFO. + */ +static int recv_data(struct tpm_chip *chip, u8 *buf, size_t count) +{ + int size = 0, burstcnt, len; + + while (size < count) { + burstcnt = get_burstcount(chip); + len = count - size; + if ((len) > burstcnt) + len = burstcnt; + if ( + I2C_READ_DATA(chip->addr, TPM_DATA_FIFO, buf + size, len) == 0) + size += len; + else + break; + } + return size; +} /* recv_data() */ + +/* + * tpm_stm_i2c_pool_command_completion pool the TPM_STS register until + * command execution complete + * @param: chip, the chip description + * @return: O when TPM complete command execution or -TPM_DRIVER_ERR. + */ +static int tpm_stm_i2c_pool_command_completion(struct tpm_chip *chip) +{ + unsigned long start, stop; + long rc; + u8 status; + + start = get_timer(0); + stop = tpm_dev.duration; + do { + status = tpm_stm_i2c_status(chip); + if ((status & TPM_STS_DATA_AVAIL) == TPM_STS_DATA_AVAIL) + return 0; + } while (get_timer(start) < stop); + + rc = -TPM_DRIVER_ERR; + return rc; +} /* tpm_stm_i2c_pool_command_completion() */ + +/* + * tpm_stm_i2c_recv received TPM response through the I2C bus. + * @param: chip, tpm_chip description. + * @param: buf, the buffer to store data. + * @param: count, the number of bytes that can received (sizeof buf). + * @return: Returns zero in case of success else -EIO. + */ +static int tpm_stm_i2c_recv(struct tpm_chip *chip, unsigned char *buf, + size_t count) +{ + int size = 0; + int expected; + + if (chip == NULL) + return -ENODEV; + + if (count < TPM_HEADER_SIZE) { + size = -EIO; + goto out; + } + size = recv_data(chip, buf, TPM_HEADER_SIZE); + if (size < TPM_HEADER_SIZE) { + printf("TPM error, unable to read header\n"); + goto out; + } + expected = get_unaligned_be32(buf + TPM_RSP_SIZE_BYTE); + if (expected > count) { + size = -EIO; + goto out; + } + size += recv_data(chip, &buf[TPM_HEADER_SIZE], + expected - TPM_HEADER_SIZE); + if (size < expected) { + printf("TPM error, unable to read remaining bytes of result\n"); + size = -EIO; + goto out; + } + +out: + tpm_stm_i2c_command_ready(chip); + release_locality(chip); + return size; +} /* tpm_stm_i2c_recv() */ + +/* + * tpm_stm_i2c_send send TPM commands through the I2C bus. + * + * @param: chip, tpm_chip description. + * @param: buf, the buffer to send. + * @param: len, the number of bytes to send. + * @return: Returns zero in case of success else the negative error code. + */ +static int tpm_stm_i2c_send(struct tpm_chip *chip, u8 *buf, + size_t len) +{ + u32 ret = 0, + status, + burstcnt = 0, i, size; + u8 data; + + if (chip == NULL) + return -ENODEV; + if (len < TPM_HEADER_SIZE) + return -EIO; + + ret = request_locality(chip); + if (ret < 0) + return ret; + + status = tpm_stm_i2c_status(chip); + if ((status & TPM_STS_COMMAND_READY) == 0) + ret = tpm_stm_i2c_command_ready(chip); + if (ret < 0) + goto out_err; + for (i = 0; i < len - 1;) { + burstcnt = get_burstcount(chip); + size = len - i - 1; + if ((size) > burstcnt) + size = burstcnt; + ret = I2C_WRITE_DATA(chip->addr, TPM_DATA_FIFO, buf, size); + if (ret < 0) + goto out_err; + i += size; + } + + status = tpm_stm_i2c_status(chip); + if ((status & TPM_STS_DATA_EXPECT) == 0) { + ret = -EIO; + goto out_err; + } + + ret = I2C_WRITE_DATA(chip->addr, TPM_DATA_FIFO, buf + len - 1, 1); + if (ret < 0) + goto out_err; + + status = tpm_stm_i2c_status(chip); + if ((status & TPM_STS_DATA_EXPECT) != 0) { + ret = -EIO; + goto out_err; + } + + data = TPM_STS_GO; + ret = I2C_WRITE_DATA(chip->addr, TPM_STS, &data, 1); + if (ret < 0) + goto out_err; + return len; + +out_err: + ret = tpm_stm_i2c_command_ready(chip); + release_locality(chip); + return ret; +} /* tpm_stm_i2c_send() */ + +/* + * tpm_stm_i2c_send_hash send TPM locality 4 hash datas through the I2C bus + * to update the PCR[17]. + * @param: chip, the tpm_chip description. + * @param: buf, the data buffer to send. + * @param: len, the number of bytes to send. + * @return: Returns zero in case of success else the negative error code. + */ +static int tpm_stm_i2c_send_hash(struct tpm_chip *chip, const uint8_t *buf, + size_t len) +{ + int ret = 0; + u8 data; + + if (chip == NULL) + return -EBUSY; + + release_locality(chip); + chip->addr = LOCALITY4_I2C_ADDR; + chip->locality = LOCALITY4; + data = TPM_DUMMY_BYTE; + ret = I2C_WRITE_DATA(chip->addr, TPM_HASH_START, &data, 1); + if (ret != 0) + goto end; + ret = I2C_WRITE_DATA(chip->addr, TPM_DATA_FIFO, buf, len); + if (ret != 0) + goto end; + ret = I2C_WRITE_DATA(chip->addr, TPM_HASH_END, &data, 1); + if (ret != 0) + goto end; + +end: + release_locality(chip); + chip->locality = LOCALITY0; + chip->addr = LOCALITY0_I2C_ADDR; + ret |= request_locality(chip); + return ret; +} /* tpm_stm_i2c_send_hash */ + +/* + * tpm_vendor_init initialize the TPM device + * @param: dev_addr, the i2c address of the tpm. + * @return: 0 in case of success, -ENODEV or TPM_DRIVER_ERR in case of error + */ +int tpm_vendor_init(uint32_t dev_addr, uint32_t dev_bus) +{ + int rc = 0; + + tpm_dev.addr = dev_addr; + tpm_dev.i2c_bus = dev_bus; + if (i2c_set_bus_num(tpm_dev.i2c_bus) != 0) { + rc = -ENODEV; + goto out_err; + } + + /* Default timeouts */ + tpm_dev.timeout_a = TIS_SHORT_TIMEOUT; + tpm_dev.timeout_b = TIS_LONG_TIMEOUT; + tpm_dev.timeout_c = TIS_SHORT_TIMEOUT; + tpm_dev.timeout_d = TIS_SHORT_TIMEOUT; + + tpm_dev.locality = LOCALITY0; + + tpm_dev.duration = TPM_MAX_COMMAND_DURATION; + + if (request_locality(&tpm_dev) != 0) { + rc = -TPM_DRIVER_ERR; + goto out_err; + } + + tpm_dev.is_open = 1; + printf("ST33ZP24 I2C TPM from STMicroelectronics found\n"); + return 0; + +out_err: + tpm_dev.is_open = 0; + return rc; +} /* tpm_vendor_init() */ + + +/* + * tis_init() verify presence of ST33ZP24 I2C TPM device and configure driver + * for it. + * @return: 0 on success (the device is found or was found during an earlier + * invocation) or -ENODEV if the device is not found. + */ +int tis_init(void) +{ + int rc; + uint32_t dev_addr, dev_bus; + + dev_addr = LOCALITY0_I2C_ADDR; + dev_bus = CONFIG_TPM_I2C_BUS; + + rc = tpm_vendor_init(dev_addr, dev_bus); + return rc; +} /* tis_init() */ + +/* + * tis_sendrecv() send the requested data to the TPM and then try to get + * its response + * @param: sendbuf - buffer of the data to send + * @param: send_size size of the data to send + * @param: recvbuf - memory to save the response to + * @param: recv_len - pointer to the size of the response buffer + * + * @return: 0 on success (and places the number of response bytes at recv_len) + * or -TPM_DRIVER_ERR on failure. + */ +int tis_sendrecv(const uint8_t *sendbuf, size_t sbuf_size, + uint8_t *recvbuf, size_t *rbuf_len) +{ + int len, i; + uint8_t buf[TPM_BUFSIZE]; + + if (tpm_dev.is_open == 0) + return -TPM_DRIVER_ERR; + + if (sizeof(buf) < sbuf_size) + return -TPM_DRIVER_ERR; + + memcpy(buf, sendbuf, sbuf_size); + len = tpm_stm_i2c_send(&tpm_dev, buf, sbuf_size); + if (len != sbuf_size) { + printf(" TPM error, command not fully transmitted\n"); + return -TPM_DRIVER_ERR; + } + + if (tpm_stm_i2c_pool_command_completion(&tpm_dev) != 0) + return -TPM_DRIVER_ERR; + + len = tpm_stm_i2c_recv(&tpm_dev, buf, sizeof(buf)); + if (len < 10) { + *rbuf_len = 0; + return -TPM_DRIVER_ERR; + } + + memcpy(recvbuf, buf, len); + *rbuf_len = len; + return 0; +} /* tis_sendrecv() */ + +/* + * tis_sendhashloc4() perform a hash in locality 4 in order to extend PCR17 + * @param: sendbuf - buffer of the data to send + * @param: send_size size of the data to send + * + * @return: 0 on success or -TPM_DRIVER_ERR on failure. + */ +int tis_sendhashloc4(const uint8_t *sendbuf, size_t sbuf_size) +{ + int ret; + + if (tpm_dev.is_open == 0) { + printf("TPM not yet initialized, perform "tpm init" first\n"); + return -TPM_DRIVER_ERR; + } + ret = tpm_stm_i2c_send_hash(&tpm_dev, sendbuf, sbuf_size); + return ret; +} /* tis_sendhashloc4() */ + +/* + * tis_open() requests access to locality 0 for the caller. After all + * commands have beencompleted the caller is supposed to call tis_close(). + * + * @return: 0 on success, -TPM_DRIVER_ERR on failure. + */ +int tis_open(void) +{ + if (tis_close()) + return -TPM_DRIVER_ERR; + + /* now request access to locality. */ + if (request_locality(&tpm_dev) != 0) { + printf("%s:%d - failed to lock locality 0\n", + __FILE__, __LINE__); + return -TPM_DRIVER_ERR; + } + return 0; +} /* tis_open() */ + +/* + * tis_close() terminate the current session with the TPM by releasing + * the locked locality. Returns 0 on success or TPM_DRIVER_ERR on failure + * (in case lock removal did not succeed). + */ +int tis_close(void) +{ + int ret; + + ret = release_locality(&tpm_dev); + return ret; +} /* tis_close() */