summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--board/cr50/board.h1
-rw-r--r--chip/g/build.mk1
-rw-r--r--chip/g/sps.c8
-rw-r--r--chip/g/sps.h6
-rw-r--r--chip/g/sps_tpm.c282
-rw-r--r--common/build.mk1
-rw-r--r--common/tpm_registers.c166
-rw-r--r--include/config.h6
-rw-r--r--include/tpm_registers.h24
9 files changed, 492 insertions, 3 deletions
diff --git a/board/cr50/board.h b/board/cr50/board.h
index 5cd5c0f621..3d94b4cb5d 100644
--- a/board/cr50/board.h
+++ b/board/cr50/board.h
@@ -27,6 +27,7 @@
/* Enable SPI Slave (SPS) module */
#define CONFIG_SPS
#define CONFIG_HOSTCMD_SPS
+#define CONFIG_TPM_SPS
/* We don't need to send events to the AP */
#undef CONFIG_HOSTCMD_EVENTS
diff --git a/chip/g/build.mk b/chip/g/build.mk
index a9747d3aea..5157dee9a5 100644
--- a/chip/g/build.mk
+++ b/chip/g/build.mk
@@ -20,6 +20,7 @@ chip-y=clock.o gpio.o hwtimer.o jtag.o system.o uart.o
chip-y+= pmu.o
chip-$(CONFIG_SPS)+= sps.o
chip-$(CONFIG_HOSTCMD_SPS)+=sps_hc.o
+chip-$(CONFIG_TPM_SPS)+=sps_tpm.o
chip-$(CONFIG_WATCHDOG)+=watchdog.o
chip-$(CONFIG_USB)+=usb.o usb_endpoints.o
diff --git a/chip/g/sps.c b/chip/g/sps.c
index 837d3c6884..82690b01ba 100644
--- a/chip/g/sps.c
+++ b/chip/g/sps.c
@@ -41,8 +41,6 @@
* - unregister receive callback.
*/
-#define SPS_FIFO_SIZE (1 << 10)
-#define SPS_FIFO_MASK (SPS_FIFO_SIZE - 1)
/*
* Hardware pointers use one extra bit, which means that indexing FIFO and
* values written into the pointers have to have dfferent sizes. Tracked under
@@ -321,6 +319,12 @@ static void sps_cs_deassert_interrupt(uint32_t port)
sps_rx_interrupt(port, 1);
GWRITE_FIELD(SPS, ISTATE_CLR, CS_DEASSERT, 1);
GWRITE_FIELD(SPS, FIFO_CTRL, TXFIFO_EN, 0);
+
+ /*
+ * And transmit FIFO is emptied, so the next transaction doesn't start
+ * by clocking out any bytes left over from this one.
+ */
+ GREG32(SPS, TXFIFO_WPTR) = GREG32(SPS, TXFIFO_RPTR);
}
void _sps0_interrupt(void)
diff --git a/chip/g/sps.h b/chip/g/sps.h
index 52f47c4020..83505909f1 100644
--- a/chip/g/sps.h
+++ b/chip/g/sps.h
@@ -18,13 +18,17 @@ enum sps_mode {
SPS_UNDEF_MODE = 3,
};
+/* Receive and transmit FIFO size and mask. */
+#define SPS_FIFO_SIZE (1 << 10)
+#define SPS_FIFO_MASK (SPS_FIFO_SIZE - 1)
+
/*
* Tx interrupt callback function prototype. This function returns a portion
* of the received SPI data and current status of the CS line. When CS is
* deasserted, this function is called with data_size of zero and a non-zero
* cs_status. This allows the recipient to delineate the SPS frames.
*/
-typedef void (*rx_handler_f)(uint8_t *data, size_t data_size, int cs_status);
+typedef void (*rx_handler_f)(uint8_t *data, size_t data_size, int cs_disabled);
/*
* Push data to the SPS TX FIFO
diff --git a/chip/g/sps_tpm.c b/chip/g/sps_tpm.c
new file mode 100644
index 0000000000..5ce79a661f
--- /dev/null
+++ b/chip/g/sps_tpm.c
@@ -0,0 +1,282 @@
+/* Copyright 2015 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 "sps.h"
+#include "tpm_registers.h"
+#include "util.h"
+
+/*
+ * This implements the TCG's TPM SPI Hardware Protocol on the SPI bus, using
+ * the Cr50 SPS (SPI slave) controller. This turns out to be very similar to
+ * the EC host command protocol, which is itself similar to HDLC. All of those
+ * protocols provide ways to identify data frames over transports that don't
+ * provide them natively. That's the nice thing about standards: there are so
+ * many to choose from.
+ *
+ * ANYWAY, The goal of the TPM protocol is to provide read and write access to
+ * device registers over the SPI bus. It is defined as follows (note that
+ * master clocks the bus and master and slave tramsmit data simultaneously).
+ *
+ * Each transaction starts with the master clocking the bus to transfer 4
+ * bytes:
+ *
+ * The master sends 4 bytes: [R/W+size-1] [Addr] [Addr] [Addr]
+ * The slave also sends 4 bytes: [xx] [xx] [xx] [x?]
+ *
+ * Bytes sent by the master define the direction and size (1-64 bytes) of the
+ * data transfer, and the address of the register to access.
+ *
+ * The final bit of the 4th slave response byte determines whether or not the
+ * slave needs some extra time. If that bit is 1, the master can IMMEDIATELY
+ * clock in (or out) the number of bytes it specified with the header byte 0.
+ *
+ * If the final bit of the 4th response byte is 0, the master clocks eight
+ * more bits and looks again at the new received byte. It repeats this (clock
+ * 8 bits, look at last bit) as long as every eighth bit is 0.
+ *
+ * When the slave is ready to proceed with the data transfer, it returns a 1
+ * for the final bit of the response byte, at which point the master has to
+ * resume transferring valid data for write transactions or to start deading
+ * bytes sent by the slave in case of read transactions.
+ *
+ * So here's what a 4-byte write of value of 0x11223344 to register 0xAABBCC
+ * might look like:
+ *
+ * xfer: 1 2 3 4 5 6 7 8 9 10 11
+ * MOSI: 03 aa bb cc xx xx xx 11 22 33 44
+ * MISO: xx xx xx x0 x0 x0 x1 xx xx xx xx
+ *
+ * Bit 0 of MISO xfer #4 is 0, indicating that the slave needs to stall. The
+ * slave stalled for three bytes before it was ready to continue accepting the
+ * input data from the master. The slave released the stall in xfer #7.
+ *
+ * Here's a 4-byte read from register 0xAABBCC:
+ *
+ * xfer: 1 2 3 4 5 6 7 8 9 10 11
+ * MOSI: 83 aa bb cc xx xx xx xx xx xx xx
+ * MISO: xx xx xx x0 x0 x0 x1 11 22 33 44
+ *
+ * As before, the slave stalled the read for three bytes and indicated it was
+ * done stalling at xfer #7.
+ *
+ * Note that the ONLY place where a stall can be initiated is the last bit of
+ * the fourth MISO byte of the transaction. Once the stall is released,
+ * there's no stopping the rest of the data transfer.
+ */
+
+#define TPM_STALL_ASSERT 0x00
+#define TPM_STALL_DEASSERT 0x01
+
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_TPM, outstr)
+#define CPRINTS(format, args...) cprints(CC_TPM, format, ## args)
+#define CPRINTF(format, args...) cprintf(CC_TPM, format, ## args)
+
+/*
+ * Incoming messages are collected here until they're ready to process. The
+ * buffer will start with a four-byte header, followed by whatever data
+ * is sent by the master (none for a read, 1 to 64 bytes for a write).
+ */
+#define RXBUF_MAX 512 /* chosen arbitrarily */
+static uint8_t rxbuf[RXBUF_MAX];
+static unsigned rxbuf_count; /* num bytes received */
+static unsigned rxbuf_needed; /* num bytes we'd like */
+static unsigned rx_fifo_base; /* RX fifo at transaction start. */
+static unsigned stall_threshold; /* num bytes we'd like */
+static uint32_t bytecount;
+static uint32_t regaddr;
+
+
+/*
+ * Outgoing messages are shoved in here. We need a TPM_STALL_DEASSERT byte to
+ * mark the start of the data stream before the data itself.
+ */
+#define TXBUF_MAX 512 /* chosen arbitrarily */
+static uint8_t txbuf[1 + TXBUF_MAX];
+
+static enum sps_state {
+ /* Receiving header */
+ SPS_TPM_STATE_RECEIVING_HEADER,
+
+ /* Receiving data. */
+ SPS_TPM_STATE_RECEIVING_WRITE_DATA,
+
+ /* Finished rx processing, waiting for SPI transaction to finish. */
+ SPS_TPM_STATE_PONDERING,
+
+ /* Something went wrong. */
+ SPS_TPM_STATE_RX_BAD,
+} sps_tpm_state;
+
+/* Set initial conditions to get ready to receive a command. */
+static void init_new_cycle(void)
+{
+ rxbuf_count = 0;
+ rxbuf_needed = 4;
+ sps_tpm_state = SPS_TPM_STATE_RECEIVING_HEADER;
+ rx_fifo_base = sps_rx_fifo_wrptr();
+ sps_tx_status(TPM_STALL_ASSERT);
+}
+
+/* Extract R/W bit, register addresss, and data count from 4-byte header */
+static int header_says_to_read(uint8_t *data, uint32_t *reg, uint32_t *count)
+{
+ uint32_t addr = data[1]; /* reg address is MSB first */
+ addr = (addr << 8) + data[2];
+ addr = (addr << 8) + data[3];
+ *reg = addr;
+ *count = (data[0] & 0x3f) + 1; /* bits 5-0: 1 to 64 bytes */
+ return !!(data[0] & 0x80); /* bit 7: 1=read, 0=write */
+}
+
+/* actual RX FIFO handler (runs in interrupt context) */
+static void process_rx_data(uint8_t *data, size_t data_size)
+{
+ /* We're collecting incoming bytes ... */
+ if ((rxbuf_count + data_size) > RXBUF_MAX) {
+ CPRINTS("TPM SPI input overflow: %d + %d > %d in state %d",
+ rxbuf_count, data_size, RXBUF_MAX, sps_tpm_state);
+ sps_tx_status(TPM_STALL_DEASSERT);
+ sps_tpm_state = SPS_TPM_STATE_RX_BAD;
+ return;
+ }
+ memcpy(rxbuf + rxbuf_count, data, data_size);
+ rxbuf_count += data_size;
+
+ /* Wait until we have enough. */
+ if (rxbuf_count < rxbuf_needed)
+ return;
+
+ /* Okay, we have enough. Now what? */
+ if (sps_tpm_state == SPS_TPM_STATE_RECEIVING_HEADER) {
+ uint32_t old_wrptr, wrptr;
+
+ /* Got the header. What's it say to do? */
+ if (header_says_to_read(rxbuf, &regaddr, &bytecount)) {
+ /* Send the stall deassert manually */
+ txbuf[0] = TPM_STALL_DEASSERT;
+
+ /* Copy the register contents into the TXFIFO */
+ /* TODO: This is blindly assuming TXFIFO has enough
+ * room. What can we do if it doesn't? */
+ tpm_register_get(regaddr, txbuf + 1, bytecount);
+ sps_transmit(txbuf, bytecount + 1);
+ sps_tpm_state = SPS_TPM_STATE_PONDERING;
+ return;
+ }
+
+ /*
+ * Master is writing, we will need more data.
+ *
+ * This is a tricky part, as we do not know how many dummy
+ * bytes the master has already written. And the actual data
+ * of course will start arriving only after we change the idle
+ * byte to set the LSB to 1.
+ *
+ * What we do know is that the idle byte repeatedly sent on
+ * the MISO line is sampled at the same time as the RX FIFO
+ * write pointer is written by the chip, after clocking in the
+ * next byte. That is, we can synchronize with the line by
+ * waiting for the RX FIFO write pointer to change. Then we
+ * can change the idle byte to indicate that the slave is
+ * ready to receive the rest of the data, and take note of the
+ * RX FIFO write pointer, as the first byte of the message
+ * will show up in the receive FIFO 2 bytes later.
+ */
+
+ /*
+ * Let's wait til the start of the next byte cycle. This must
+ * be done in a tight loop (with interrupts disabled?).
+ */
+ old_wrptr = sps_rx_fifo_wrptr();
+ do {
+ wrptr = sps_rx_fifo_wrptr();
+ } while (old_wrptr == wrptr);
+
+ /*
+ * Write the new idle byte value, it will start transmitting
+ * *next* after the current byte.
+ */
+ sps_tx_status(TPM_STALL_DEASSERT);
+
+ /*
+ * Verify that we managed to change the idle byte value within
+ * the required time (RX FIFO write pointer has not changed)
+ */
+ if (sps_rx_fifo_wrptr() != wrptr) {
+ CPRINTS("%s:"
+ " ERROR: failed to change idle byte in time",
+ __func__);
+ sps_tpm_state = SPS_TPM_STATE_PONDERING;
+ } else {
+ /*
+ * Ok, we're good. Remember where in the receive
+ * stream the actual data will start showing up. It is
+ * two bytes after the current one (the current idle
+ * byte still has the LSB set to zero, the next one
+ * will have the LSB set to one, only after receiving
+ * it the master will start sending the actual data.
+ */
+ stall_threshold =
+ ((wrptr - rx_fifo_base) & SPS_FIFO_MASK) + 2;
+ rxbuf_needed = stall_threshold + bytecount;
+ sps_tpm_state = SPS_TPM_STATE_RECEIVING_WRITE_DATA;
+ }
+ return;
+ }
+
+ if (sps_tpm_state == SPS_TPM_STATE_RECEIVING_WRITE_DATA) {
+ /* Ok, we have all the write data. */
+ tpm_register_put(regaddr, rxbuf + stall_threshold, bytecount);
+ sps_tpm_state = SPS_TPM_STATE_PONDERING;
+ }
+}
+
+static void tpm_rx_handler(uint8_t *data, size_t data_size, int cs_disabled)
+{
+ if ((sps_tpm_state == SPS_TPM_STATE_RECEIVING_HEADER) ||
+ (sps_tpm_state == SPS_TPM_STATE_RECEIVING_WRITE_DATA))
+ process_rx_data(data, data_size);
+
+ if (cs_disabled)
+ init_new_cycle();
+}
+
+static void sps_tpm_enable(void)
+{
+ sps_register_rx_handler(SPS_GENERIC_MODE, tpm_rx_handler);
+ init_new_cycle();
+}
+
+static void sps_tpm_disable(void)
+{
+ sps_tpm_state = SPS_TPM_STATE_PONDERING;
+ sps_unregister_rx_handler();
+}
+
+static int command_sps_tpm(int argc, char **argv)
+{
+ if (argc > 1) {
+ if (0 != strcasecmp(argv[1], "off"))
+ return EC_ERROR_PARAM1;
+
+ sps_tpm_disable();
+ ccprintf("TPM SPI protocol disabled\n");
+ return EC_SUCCESS;
+ }
+
+ sps_tpm_enable();
+ ccprintf("TPM SPI protocol enabled\n");
+ return EC_SUCCESS;
+}
+
+DECLARE_CONSOLE_COMMAND(spstpm, command_sps_tpm,
+ "[off]",
+ "Not sure yet...",
+ NULL);
diff --git a/common/build.mk b/common/build.mk
index 32012e400c..2fb599ea06 100644
--- a/common/build.mk
+++ b/common/build.mk
@@ -81,6 +81,7 @@ common-$(CONFIG_SPI_FLASH)+=spi_flash.o spi_flash_reg.o
common-$(CONFIG_SWITCH)+=switch.o
common-$(CONFIG_SW_CRC)+=crc.o
common-$(CONFIG_TEMP_SENSOR)+=temp_sensor.o thermal.o throttle_ap.o
+common-$(CONFIG_TPM_SPS)+=tpm_registers.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
diff --git a/common/tpm_registers.c b/common/tpm_registers.c
new file mode 100644
index 0000000000..a3f1672b70
--- /dev/null
+++ b/common/tpm_registers.c
@@ -0,0 +1,166 @@
+/* Copyright 2015 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.
+ */
+
+/*
+ * This implements the register interface for the TPM SPI Hardware Protocol.
+ * The master puts or gets between 1 and 64 bytes to a register designated by a
+ * 24-bit address. There is no provision for error reporting at this level.
+ */
+
+#include "common.h"
+#include "console.h"
+#include "hooks.h"
+#include "tpm_registers.h"
+#include "util.h"
+
+#define CPRINTS(format, args...) cprints(CC_TPM, format, ## args)
+#define CPRINTF(format, args...) cprintf(CC_TPM, format, ## args)
+
+
+/* Register addresses for FIFO mode. */
+#define TPM_ACCESS 0
+#define TPM_INT_ENABLE 8
+#define TPM_INT_VECTOR 0xC
+#define TPM_INT_STATUS 0x10
+#define TPM_INTF_CAPABILITY 0x14
+#define TPM_STS 0x18
+#define TPM_DATA_FIFO 0x28
+#define TPM_INTERFACE_ID 0x30
+#define TPM_DID_VID 0xf00
+#define TPM_RID 0xf04
+
+#define GOOGLE_VID 0x1ae0
+#define GOOGLE_DID 0x0028
+#define CR50_RID 0 /* No revision ID yet */
+
+/* A preliminary interface capability register value, will be fine tuned. */
+#define IF_CAPABILITY_REG ((3 << 28) | /* TPM2.0 (interface 1.3) */ \
+ (3 << 9) | /* up to 64 bytes transfers. */ \
+ 0x15) /* Mandatory set to one. */
+
+#define MAX_LOCALITIES 1 /* Eventually there could be five. */
+/* Volatile registers for FIFO mode */
+static struct tpm_register_file {
+ uint8_t tpm_access;
+ uint32_t tpm_int_status;
+ uint32_t tpm_sts;
+ uint8_t tpm_data_fifo[64]; /* this might have to be much deeper. */
+ uint32_t fifo_read_index; /* for read commands */
+ uint32_t fifo_write_index; /* for write commands */
+} tpm_regs;
+
+enum tpm_access_bits {
+ tpm_reg_valid_sts = (1 << 7),
+ active_locality = (1 << 5),
+ request_use = (1 << 1),
+ tpm_establishment = (1 << 0),
+};
+
+enum tpm_sts_bits {
+ tpm_family_shift = 26,
+ tpm_family_mask = ((1 << 2) - 1), /* 2 bits wide */
+ tpm_family_tpm2 = 1,
+ reset_establishment_bit = (1 << 25),
+ command_cancel = (1 << 24),
+ burst_count_shift = 8,
+ burst_count_mask = ((1 << 16) - 1), /* 16 bits wide */
+ sts_valid = (1 << 7),
+ command_ready = (1 << 6),
+ tpm_go = (1 << 5),
+ data_avail = (1 << 4),
+ expect = (1 << 3),
+ self_test_done = (1 << 2),
+ response_retry = (1 << 1),
+};
+
+/*
+ * NOTE: The put/get functions are called in interrupt context! Don't waste a
+ * lot of time here - just copy the data and wake up a task to deal with it
+ * later. Although if the implementation mandates a "busy" bit somewhere, you
+ * might want to set it now to avoid race conditions with back-to-back
+ * interrupts.
+ */
+
+static void copy_bytes(uint8_t *dest, uint32_t data_size, uint32_t value)
+{
+ unsigned real_size, i;
+
+ real_size = MIN(data_size, 4);
+
+ for (i = 0; i < real_size; i++)
+ dest[i] = (value >> (i * 8)) & 0xff;
+}
+
+static void access_reg_write(uint8_t data, uint8_t *reg)
+{
+ /* By definition only one bit can be set at a time. */
+ if (!data || (data & (data-1))) {
+ CPRINTF("%s: attempt to set acces reg to %02x\n",
+ __func__, data);
+ return;
+ }
+
+ switch (data) {
+ case request_use:
+ *reg |= active_locality;
+ break;
+
+ case active_locality:
+ *reg &= ~active_locality;
+ break;
+ }
+}
+
+void tpm_register_put(uint32_t regaddr, const uint8_t *data, uint32_t data_size)
+{
+ uint32_t i;
+ switch (regaddr) {
+ case TPM_ACCESS:
+ /* This is a one byte register, ignore extra data, if any */
+ access_reg_write(data[0], &tpm_regs.tpm_access);
+ break;
+ default:
+ CPRINTF("%s(0x%06x, %d", __func__, regaddr, data_size);
+ for (i = 0; i < data_size; i++)
+ CPRINTF(", %02x", data[i]);
+ CPRINTF("\n");
+ return;
+ }
+
+}
+
+void tpm_register_get(uint32_t regaddr, uint8_t *dest, uint32_t data_size)
+{
+ switch (regaddr) {
+ case TPM_DID_VID:
+ copy_bytes(dest, data_size, (GOOGLE_DID << 16) | GOOGLE_VID);
+ break;
+ case TPM_RID:
+ copy_bytes(dest, data_size, CR50_RID);
+ break;
+ case TPM_INTF_CAPABILITY:
+ copy_bytes(dest, data_size, IF_CAPABILITY_REG);
+ break;
+ case TPM_ACCESS:
+ copy_bytes(dest, data_size, tpm_regs.tpm_access);
+ break;
+ case TPM_STS:
+ copy_bytes(dest, data_size, tpm_regs.tpm_sts);
+ break;
+ default:
+ CPRINTS("%s(0x%06x, %d) => ??", __func__, regaddr, data_size);
+ return;
+ }
+}
+
+
+static void tpm_init(void)
+{
+ tpm_regs.tpm_access = tpm_reg_valid_sts;
+ tpm_regs.tpm_sts = (tpm_family_tpm2 << tpm_family_shift) |
+ (64 << burst_count_shift);
+}
+
+DECLARE_HOOK(HOOK_INIT, tpm_init, HOOK_PRIO_DEFAULT);
diff --git a/include/config.h b/include/config.h
index 17d0712b79..ad28fc8e71 100644
--- a/include/config.h
+++ b/include/config.h
@@ -1457,6 +1457,12 @@
#undef CONFIG_TEMP_SENSOR_POWER_GPIO
/*****************************************************************************/
+/* TPM-like configuration */
+
+/* Speak the TPM SPI Hardware Protocol on the SPI slave interface */
+#undef CONFIG_TPM_SPS
+
+/*****************************************************************************/
/* USART stream config */
#undef CONFIG_STREAM_USART
diff --git a/include/tpm_registers.h b/include/tpm_registers.h
new file mode 100644
index 0000000000..cdf8410a51
--- /dev/null
+++ b/include/tpm_registers.h
@@ -0,0 +1,24 @@
+/* Copyright 2015 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.
+ */
+
+/*
+ * This defines the interface functions for TPM SPI Hardware Protocol. The SPI
+ * master reads or writes between 1 and 64 bytes to a register designated by a
+ * 24-bit address. There is no provision for error reporting at this level.
+ */
+
+#ifndef __CROS_EC_TPM_REGISTERS_H
+#define __CROS_EC_TPM_REGISTERS_H
+
+#include <stdint.h>
+
+/* The SPI master is writing data into a TPM register. */
+void tpm_register_put(uint32_t regaddr,
+ const uint8_t *data, uint32_t data_size);
+
+/* The SPI master is reading data from a TPM register. */
+void tpm_register_get(uint32_t regaddr, uint8_t *dest, uint32_t data_size);
+
+#endif /* __CROS_EC_TPM_REGISTERS_H */