summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authorScott <scollyer@chromium.org>2016-08-02 12:36:52 -0700
committerchrome-bot <chrome-bot@chromium.org>2016-09-05 23:22:13 -0700
commite4f389a275fb81c112b1186df0465e48ff163cfd (patch)
tree8cb4b98a9a79a8f89461f5472c7f8e947e373e9c /common
parentd6c69cef59b9abb31da6ed362b61340a5d8318b9 (diff)
downloadchrome-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.mk3
-rw-r--r--common/i2cs_tpm.c191
-rw-r--r--common/tpm_registers.c32
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;
}
}
}