summaryrefslogtreecommitdiff
path: root/common/i2cp_tpm.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/i2cp_tpm.c')
-rw-r--r--common/i2cp_tpm.c290
1 files changed, 290 insertions, 0 deletions
diff --git a/common/i2cp_tpm.c b/common/i2cp_tpm.c
new file mode 100644
index 0000000000..48af3a22b3
--- /dev/null
+++ b/common/i2cp_tpm.c
@@ -0,0 +1,290 @@
+/* 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 "gpio.h"
+#include "hooks.h"
+#include "i2cp.h"
+#include "registers.h"
+#include "system.h"
+#include "tpm_registers.h"
+
+/*
+ * This implements adaptaition layer between i2cp (i2c periph) port and TPM.
+ *
+ * The adaptation layer is stateless, it processes the i2cp "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 */
+ {0xa, 4, 0x14}, /* TPM TPM_INTF_CAPABILITY */
+ {0xe, 1, 0xf04}, /* TPM RID */
+ {0xf, 0, 0xf90}, /* TPM_FW_VER */
+ {0x1c, 4, 0xfe0}, /* TPM_BOARD_CFG */
+};
+
+/* Used to track number of times i2cp hw read fifo was adjusted */
+static uint32_t i2cp_fifo_adjust_count;
+/* Used to track number of write mismatch errors */
+static uint32_t i2cp_write_error_count;
+
+static bool int_ap_extension_enabled_;
+
+static void process_read_access(uint16_t reg_size,
+ uint16_t tpm_reg, uint8_t *data)
+{
+ int i;
+ uint8_t reg_value[4];
+
+ /*
+ * The master wants to read the register, read the value and pass it
+ * to the controller.
+ */
+ if (reg_size == 1 || reg_size == 4) {
+ /* Always read regsize number of bytes */
+ tpm_register_get(tpm_reg, reg_value, reg_size);
+ /*
+ * For 1 or 4 byte register reads there should not be any data
+ * buffered in the i2cp hw read fifo. This function will check
+ * the current fifo queue depth and if non-zero, will adjust the
+ * fw pointer to force it to 0.
+ */
+ if (i2cp_zero_read_fifo_buffer_depth())
+ /* Count each instance that fifo was adjusted */
+ i2cp_fifo_adjust_count++;
+ for (i = 0; i < reg_size; i++)
+ i2cp_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 I2CP HW fifo */
+ i2cp_post_read_fill_fifo(data, reg_size);
+}
+
+static void process_write_access(uint16_t reg_size, uint16_t tpm_reg,
+ uint8_t *data, size_t i2cp_data_size)
+{
+ /* 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, i2cp_data_size);
+ return;
+ }
+
+ if (i2cp_data_size != reg_size) {
+ i2cp_write_error_count++;
+ return;
+ }
+
+ /* Write the data to the appropriate TPM register */
+ tpm_register_put(tpm_reg, data, reg_size);
+}
+
+static void wr_complete_handler(void *i2cp_data, size_t i2cp_data_size)
+{
+ size_t i;
+ uint16_t tpm_reg;
+ uint8_t *data = i2cp_data;
+ const struct i2c_tpm_reg_map *i2c_reg_entry = NULL;
+ uint16_t reg_size;
+
+ if (i2cp_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;
+
+ i2cp_data_size--;
+ data++;
+
+ if (!i2cp_data_size)
+ process_read_access(reg_size, tpm_reg, data);
+ else
+ process_write_access(reg_size, tpm_reg,
+ data, i2cp_data_size);
+
+ if (assert_int_ap()) {
+ gpio_enable_interrupt(GPIO_MONITOR_I2CP_SDA);
+ return;
+ }
+
+ /*
+ * Since cr50 does not provide i2c clock stretching, we need some
+ * onther means of flow controlling the host. Let's generate a pulse
+ * on the AP interrupt line for that.
+ */
+ gpio_set_level(GPIO_INT_AP_L, 0);
+
+ tick_delay(2);
+
+ gpio_set_level(GPIO_INT_AP_L, 1);
+}
+
+void i2cp_sda_isr(enum gpio_signal signal)
+{
+ gpio_disable_interrupt(GPIO_MONITOR_I2CP_SDA);
+
+ deassert_int_ap();
+}
+
+static void i2cp_if_stop(void)
+{
+ if (int_ap_extension_enabled_)
+ int_ap_extension_stop_pulse();
+
+ i2cp_register_write_complete_handler(NULL);
+}
+
+static void i2cp_if_start(void)
+{
+ i2cp_register_write_complete_handler(wr_complete_handler);
+}
+
+/* Function that sets up for I2CP to enable INT_AP_L extension. */
+static void i2cp_int_ap_extension_enable_(void)
+{
+ int_ap_extension_enabled_ = true;
+}
+
+static void i2cp_if_register(void)
+{
+ if (!board_tpm_uses_i2c())
+ return;
+
+ tpm_register_interface(i2cp_if_start, i2cp_if_stop);
+ i2cp_fifo_adjust_count = 0;
+ i2cp_write_error_count = 0;
+
+ int_ap_register(i2cp_int_ap_extension_enable_);
+}
+DECLARE_HOOK(HOOK_INIT, i2cp_if_register, HOOK_PRIO_INIT_CR50_BOARD - 1);
+
+static int command_i2cp(int argc, char **argv)
+{
+ static uint16_t base_read_recovery_count;
+ struct i2cp_status status;
+
+ i2cp_get_status(&status);
+
+ ccprintf("rd fifo adjust cnt = %d\n", i2cp_fifo_adjust_count);
+ ccprintf("wr mismatch cnt = %d\n", i2cp_write_error_count);
+ ccprintf("read recovered cnt = %d\n", status.read_recovery_count
+ - base_read_recovery_count);
+ if (argc < 2)
+ return EC_SUCCESS;
+
+ if (!strcasecmp(argv[1], "reset")) {
+ i2cp_fifo_adjust_count = 0;
+ i2cp_write_error_count = 0;
+ base_read_recovery_count = status.read_recovery_count;
+ ccprintf("i2cp error counts reset\n");
+ } else
+ return EC_ERROR_PARAM1;
+
+ return EC_SUCCESS;
+}
+DECLARE_SAFE_CONSOLE_COMMAND(i2cptpm, command_i2cp,
+ "reset",
+ "Display fifo adjust count");