diff options
author | Scott <scollyer@chromium.org> | 2016-08-02 12:36:52 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2016-09-05 23:22:13 -0700 |
commit | e4f389a275fb81c112b1186df0465e48ff163cfd (patch) | |
tree | 8cb4b98a9a79a8f89461f5472c7f8e947e373e9c /common | |
parent | d6c69cef59b9abb31da6ed362b61340a5d8318b9 (diff) | |
download | chrome-ec-e4f389a275fb81c112b1186df0465e48ff163cfd.tar.gz |
Cr50: Preliminary I2CS TPM2.0 driver
This CL includes changes in Cr50 required to support TPM via
the I2CS interface.
BRANCH=none
BUG=chrome-os-partner:40397
TEST=manual
Limited testing so far. Verified that the I2CS interface is
initialized properly and that register reads occur when
initiated on the AP console via command i2cget -y 8 0x50 0x1 w
Change-Id: I16ac17c7c82d420a384908e4b5a9867a3b24bc9e
Reviewed-on: https://chromium-review.googlesource.com/356241
Commit-Ready: Scott Collyer <scollyer@chromium.org>
Tested-by: Scott Collyer <scollyer@chromium.org>
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
Diffstat (limited to 'common')
-rw-r--r-- | common/build.mk | 3 | ||||
-rw-r--r-- | common/i2cs_tpm.c | 191 | ||||
-rw-r--r-- | common/tpm_registers.c | 32 |
3 files changed, 222 insertions, 4 deletions
diff --git a/common/build.mk b/common/build.mk index a699637129..44e3e1c33d 100644 --- a/common/build.mk +++ b/common/build.mk @@ -86,7 +86,7 @@ common-$(CONFIG_SWITCH)+=switch.o common-$(CONFIG_SW_CRC)+=crc.o common-$(CONFIG_TEMP_SENSOR)+=temp_sensor.o common-$(CONFIG_THROTTLE_AP)+=thermal.o throttle_ap.o -common-$(CONFIG_TPM_SPS)+=tpm_registers.o +common-$(CONFIG_TPM_I2CS)+=i2cs_tpm.o common-$(CONFIG_USB_CHARGER)+=usb_charger.o common-$(CONFIG_USB_PORT_POWER_DUMB)+=usb_port_power_dumb.o common-$(CONFIG_USB_PORT_POWER_SMART)+=usb_port_power_smart.o @@ -105,6 +105,7 @@ common-$(HAS_TASK_PDCMD)+=host_command_pd.o common-$(HAS_TASK_KEYSCAN)+=keyboard_scan.o common-$(HAS_TASK_LIGHTBAR)+=lb_common.o lightbar.o common-$(HAS_TASK_MOTIONSENSE)+=motion_sense.o +common-$(HAS_TASK_TPM)+=tpm_registers.o ifeq ($(CTS_MODULE),) common-$(TEST_BUILD)+=test_util.o diff --git a/common/i2cs_tpm.c b/common/i2cs_tpm.c new file mode 100644 index 0000000000..0e3071b590 --- /dev/null +++ b/common/i2cs_tpm.c @@ -0,0 +1,191 @@ +/* Copyright 2016 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "common.h" +#include "console.h" +#include "hooks.h" +#include "i2cs.h" +#include "registers.h" +#include "tpm_registers.h" + +/* + * This implements adaptaition layer between i2cs (i2c slave) port and TPM. + * + * The adaptation layer is stateless, it processes the i2cs "write complete" + * interrupts on the interrupt context. + * + * Each "write complete" interrupt is associated with some data receved from + * the master. If the package received from the master contains just one byte + * payload, the value of this byte is considered the address of the TPM2 + * register to reach, read or write. + * + * Real TPM register addresses can be two bytes in size (even within locality + * zero), to keep the i2c protocol simple and efficient, the real TPM register + * addresses are re-mapped into i2c specific TPM register addresses. + * + * If the payload includes bytes following the address byte - those are the + * data to be written to the addressed register. The number of bytes of data + * could be anything between 1 and 62. The HW fifo is 64 bytes deep and that + * means that only 63 bytes can be written without the write pointer wrapping + * around to itself. Outside of the TPM fifo register, all other registers are + * either 1 byte or 4 byte writes. + * + * The master knows how many bytes to write into FIFO or to read from it by + * consulting the "burst size" field of the TPM status register. This happens + * transparently for this layer. + * + * Data destined to and coming from the FIFO register is treated as a byte + * stream. + * + * Data for and from all other registers are either 1 byte or 4 bytes as + * specified in a register's "reg_size" field of the I2C -> TPM mapping + * table. Multi-byte registers are received and transmitted in CPU byte order + * which for the Cr50 is little endian. + * TODO (scollyer crosbug.com/p/56539): Should modify the register access code + * so that the Host can access 1-4 bytes of a given register. + * + * Master write accesses followed by data result in the register address + * mapped, data converted, if necessary, and passed to the tpm register task. + * + * Master write accesses requesting register reads result in the register + * address mappend and accessing the tpm task to retrieve the proper register + * data, converting it, if necessary, and passing it to the 12cs controller to + * make available for master read accesses. + * + * Again, both read and write accesses complete on the same interrupt context + * they were invoked on. + */ + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_I2C, outstr) +#define CPRINTF(format, args...) cprintf(CC_I2C, format, ## args) + +struct i2c_tpm_reg_map { + uint8_t i2c_address; + uint8_t reg_size; + uint16_t tpm_address; +}; +static const struct i2c_tpm_reg_map i2c_to_tpm[] = { + {0, 1, 0}, /* TPM Access */ + {1, 4, 0x18}, /* TPM Status */ + {5, 0, 0x24}, /* TPM Fifo, variable size. */ + {6, 4, 0xf00}, /* TPM DID VID */ +}; + +static void wr_complete_handler(void *i2cs_data, size_t i2cs_data_size) +{ + size_t i; + uint16_t tpm_reg; + uint8_t *data = i2cs_data; + uint8_t reg_value[4]; + const struct i2c_tpm_reg_map *i2c_reg_entry = NULL; + uint16_t reg_size; + + if (i2cs_data_size < 1) { + /* + * This is a misformatted request, should never happen, just + * ignore it. + */ + CPRINTF("%s: empty receive payload\n", __func__); + return; + } + + /* Let's find real TPM register address. */ + for (i = 0; i < ARRAY_SIZE(i2c_to_tpm); i++) + if (i2c_to_tpm[i].i2c_address == *data) { + i2c_reg_entry = i2c_to_tpm + i; + break; + } + + if (!i2c_reg_entry) { + CPRINTF("%s: unsupported i2c tpm address 0x%x\n", + __func__, *data); + return; + } + + /* + * OK, we know the tpm register address. Note that only full register + * accesses are supported for multybyte registers, + * TODO (scollyer crosbug.com/p/56539): Look at modifying this so we + * can handle 1 - 4 byte accesses at any any I2C register address we + * support. + */ + tpm_reg = i2c_reg_entry->tpm_address; + reg_size = i2c_reg_entry->reg_size; + + i2cs_data_size--; + data++; + + if (!i2cs_data_size) { + /* + * The master wants to read the register, read the value and + * pass it to the controller. + */ + if (reg_size == 1) { + uint8_t byte_reg; + + /* Always read 4 bytes. */ + tpm_register_get(tpm_reg, &byte_reg, sizeof(byte_reg)); + i2cs_post_read_data(byte_reg); + return; + } + + if (reg_size == 4) { + tpm_register_get(tpm_reg, reg_value, sizeof(reg_value)); + + /* Write data to I2CS HW fifo */ + for (i = 0; i < sizeof(reg_value); i++) + i2cs_post_read_data(reg_value[i]); + return; + } + + /* + * FIFO accesses do not require endianness conversion, but to + * find out how many bytes to read we need to consult the + * burst size field of the tpm status register. + */ + reg_size = tpm_get_burst_size(); + /* + * Now, this is a hack, but we are short on SRAM, so let's + * reuse the receive buffer for the FIFO data sotrage. We know + * that the ISR has a 64 byte buffer were it moves received + * data. + */ + /* Back pointer up by one to point to beginning of buffer */ + data -= 1; + tpm_register_get(tpm_reg, data, reg_size); + /* Transfer TPM fifo data to the I2CS HW fifo */ + for (i = 0; i < reg_size; i++) + i2cs_post_read_data(data[i]); + return; + } + + /* This is an actual write request. */ + + /* + * If reg_size is 0, then this is a fifo register write. Send the stream + * down directly + */ + if (reg_size == 0) { + tpm_register_put(tpm_reg, data, i2cs_data_size); + return; + } + + if (i2cs_data_size != reg_size) { + CPRINTF("%s: data size mismatch for reg 0x%x " + "(rx %d, need %d)\n", __func__, tpm_reg, + i2cs_data_size, reg_size); + return; + } + + /* Write the data to the appropriate TPM register */ + tpm_register_put(tpm_reg, data, reg_size); +} + +static void i2cs_tpm_init(void) +{ + i2cs_register_write_complete_handler(wr_complete_handler); +} +DECLARE_HOOK(HOOK_INIT, i2cs_tpm_init, HOOK_PRIO_LAST); diff --git a/common/tpm_registers.c b/common/tpm_registers.c index a7dfa84963..0158e22f2f 100644 --- a/common/tpm_registers.c +++ b/common/tpm_registers.c @@ -411,6 +411,7 @@ void fifo_reg_read(uint8_t *dest, uint32_t data_size) { uint32_t still_in_fifo = tpm_.fifo_write_index - tpm_.fifo_read_index; + uint32_t tpm_sts; data_size = MIN(data_size, still_in_fifo); memcpy(dest, @@ -418,8 +419,23 @@ void fifo_reg_read(uint8_t *dest, uint32_t data_size) data_size); tpm_.fifo_read_index += data_size; - if (tpm_.fifo_write_index == tpm_.fifo_read_index) - tpm_.regs.sts &= ~(data_avail | command_ready); + + tpm_sts = tpm_.regs.sts; + tpm_sts &= ~(burst_count_mask << burst_count_shift); + if (tpm_.fifo_write_index == tpm_.fifo_read_index) { + tpm_sts &= ~(data_avail | command_ready); + /* Birst size for the following write requests. */ + tpm_sts |= 63 << burst_count_shift; + } else { + /* + * Tell the master how much there is to read in the next + * burst. + */ + tpm_sts |= MIN(tpm_.fifo_write_index - + tpm_.fifo_read_index, 63) << burst_count_shift; + } + + tpm_.regs.sts = tpm_sts; } @@ -513,6 +529,11 @@ static void tpm_init(void) _plat__SetNvAvail(); } +size_t tpm_get_burst_size(void) +{ + return (tpm_.regs.sts >> burst_count_shift) & burst_count_mask; +} + #ifdef CONFIG_EXTENSION_COMMAND static void call_extension_command(struct tpm_cmd_header *tpmh, @@ -579,6 +600,7 @@ void tpm_task(void) CPRINTF("got %d bytes in response\n", response_size); if (response_size && (response_size <= sizeof(tpm_.regs.data_fifo))) { + uint32_t tpm_sts; /* * TODO(vbendeb): revisit this when * crosbug.com/p/55667 has been addressed. @@ -598,8 +620,12 @@ void tpm_task(void) } tpm_.fifo_read_index = 0; tpm_.fifo_write_index = response_size; - tpm_.regs.sts |= data_avail; set_tpm_state(tpm_state_completing_cmd); + tpm_sts = tpm_.regs.sts; + tpm_sts &= ~(burst_count_mask << burst_count_shift); + tpm_sts |= (MIN(response_size, 63) << burst_count_shift) + | data_avail; + tpm_.regs.sts = tpm_sts; } } } |