summaryrefslogtreecommitdiff
path: root/common/tpm_registers.c
diff options
context:
space:
mode:
authorVadim Bendebury <vbendeb@chromium.org>2015-07-22 17:53:12 -0700
committerChromeOS Commit Bot <chromeos-commit-bot@chromium.org>2015-08-01 02:52:50 +0000
commit0267aa062fade24964cb89473f89dec799b0955e (patch)
tree1be452969deb9a84a2c202ad4b022bfde4d3477f /common/tpm_registers.c
parentd74949e3e68c000ead762d5acf42a5e967d2d976 (diff)
downloadchrome-ec-0267aa062fade24964cb89473f89dec799b0955e.tar.gz
cr50: initial tpm state machine support
This patch implements FIFO mode state machine transitions as described in Table 22 of the PC Client Platform document. The 'go' command is still not being handled, as processing needs to run on a task, not on interrupt context. FIFO block integrity is somewhat verified by comparing the actual block size to the length value in the block body. BRANCH=None BUG=chrome-os-partner:43025 TEST=not much. Observed trunksd happily initializing the session and sending the Startup command. The target reports: fifo_reg_write: received fifo command 0x0144 Change-Id: I76d8b0fc3a52db2cc487c781fe92799df0ee259e Signed-off-by: Vadim Bendebury <vbendeb@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/288365 Reviewed-by: Utkarsh Sanghi <usanghi@chromium.org>
Diffstat (limited to 'common/tpm_registers.c')
-rw-r--r--common/tpm_registers.c254
1 files changed, 232 insertions, 22 deletions
diff --git a/common/tpm_registers.c b/common/tpm_registers.c
index a3f1672b70..ba6b4f7492 100644
--- a/common/tpm_registers.c
+++ b/common/tpm_registers.c
@@ -26,7 +26,7 @@
#define TPM_INT_STATUS 0x10
#define TPM_INTF_CAPABILITY 0x14
#define TPM_STS 0x18
-#define TPM_DATA_FIFO 0x28
+#define TPM_DATA_FIFO 0x24
#define TPM_INTERFACE_ID 0x30
#define TPM_DID_VID 0xf00
#define TPM_RID 0xf04
@@ -35,22 +35,40 @@
#define GOOGLE_DID 0x0028
#define CR50_RID 0 /* No revision ID yet */
+/* Tpm state machine states. */
+enum tpm_states {
+ tpm_state_idle,
+ tpm_state_ready,
+ tpm_state_receiving_cmd,
+ tpm_state_executing_cmd,
+ tpm_state_completing_cmd,
+};
+
/* 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. */
+struct tpm_register_file {
+ uint8_t access;
+ uint32_t int_status;
+ uint32_t sts;
+ uint8_t data_fifo[64]; /* this might have to be much deeper. */
+};
+
+/*
+ * Tpm representation. This is a file scope variable, only one locality is
+ * supported.
+ */
+static struct {
+ enum tpm_states state;
uint32_t fifo_read_index; /* for read commands */
uint32_t fifo_write_index; /* for write commands */
-} tpm_regs;
+ struct tpm_register_file regs;
+} tpm_;
+/* Bit definitions for some TPM registers. */
enum tpm_access_bits {
tpm_reg_valid_sts = (1 << 7),
active_locality = (1 << 5),
@@ -76,6 +94,16 @@ enum tpm_sts_bits {
};
/*
+ * Some TPM registers allow writing of only exactly one bit. This helper
+ * function allows to verify that a value is compliant with this
+ * requirement
+ */
+static int single_bit_set(uint32_t value)
+{
+ return value && !(value & (value - 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
@@ -85,18 +113,17 @@ enum tpm_sts_bits {
static void copy_bytes(uint8_t *dest, uint32_t data_size, uint32_t value)
{
- unsigned real_size, i;
+ unsigned i;
- real_size = MIN(data_size, 4);
+ data_size = MIN(data_size, 4);
- for (i = 0; i < real_size; i++)
+ for (i = 0; i < data_size; i++)
dest[i] = (value >> (i * 8)) & 0xff;
}
-static void access_reg_write(uint8_t data, uint8_t *reg)
+static void access_reg_write(uint8_t data)
{
- /* By definition only one bit can be set at a time. */
- if (!data || (data & (data-1))) {
+ if (!single_bit_set(data)) {
CPRINTF("%s: attempt to set acces reg to %02x\n",
__func__, data);
return;
@@ -104,25 +131,207 @@ static void access_reg_write(uint8_t data, uint8_t *reg)
switch (data) {
case request_use:
- *reg |= active_locality;
+ /*
+ * No multiple localities supported, let's just always honor
+ * this request.
+ */
+ tpm_.regs.access |= active_locality;
break;
case active_locality:
- *reg &= ~active_locality;
+ switch (tpm_.state) {
+ case tpm_state_ready:
+ case tpm_state_idle:
+ break;
+ default:
+ /*
+ * TODO: need to decide what to do if there is a
+ * command in progress.
+ */
+ CPRINTF("%s: locality release request in state %d\n",
+ __func__, tpm_.state);
+ break;
+ }
+ tpm_.regs.access &= ~active_locality;
+ /* No matter what we do, fall into idle state. */
+ tpm_.state = tpm_state_idle;
+ tpm_.fifo_read_index = 0;
+ tpm_.fifo_write_index = 0;
+ break;
+
+ default:
+ CPRINTF("%s: attempt to set access reg to 0x%02x\n",
+ __func__, data);
break;
}
}
+/*
+ * Process writes into the 'important' sts register bits. Actions on all
+ * depends on the current state of the device.
+ */
+static void sts_reg_write_cr(void)
+{
+ switch (tpm_.state) {
+ case tpm_state_idle:
+ tpm_.state = tpm_state_ready;
+ tpm_.regs.sts |= command_ready;
+ break;
+ case tpm_state_ready:
+ tpm_.regs.sts |= command_ready;
+ break;
+ case tpm_state_completing_cmd:
+ case tpm_state_executing_cmd:
+ case tpm_state_receiving_cmd:
+ tpm_.state = tpm_state_idle;
+ tpm_.regs.sts &= ~command_ready;
+ break;
+ }
+}
+
+static void sts_reg_write_tg(void)
+{
+ switch (tpm_.state) {
+ case tpm_state_completing_cmd:
+ case tpm_state_executing_cmd:
+ case tpm_state_idle:
+ case tpm_state_ready:
+ break; /* Ignore setting this bit in these states. */
+ case tpm_state_receiving_cmd:
+ if (!(tpm_.state & expect))
+ /* This should trigger actual command execution. */
+ tpm_.state = tpm_state_executing_cmd;
+ break;
+ }
+}
+
+static void sts_reg_write_rr(void)
+{
+ switch (tpm_.state) {
+ case tpm_state_idle:
+ case tpm_state_ready:
+ case tpm_state_receiving_cmd:
+ case tpm_state_executing_cmd:
+ break;
+ case tpm_state_completing_cmd:
+ tpm_.fifo_read_index = 0;
+ break;
+ }
+}
+
+/*
+ * TPM_STS register both reports current state machine state and controls some
+ * of state machine transitions.
+ */
+static void sts_reg_write(const uint8_t *data, uint32_t data_size)
+{
+ uint32_t value;
+
+ data_size = MIN(data_size, 4);
+ memcpy(&value, data, data_size);
+
+ /* By definition only one bit can be set at a time. */
+ if (!single_bit_set(value)) {
+ CPRINTF("%s: attempt to set acces reg to %02x\n",
+ __func__, data);
+ return;
+ }
+
+ switch (value) {
+ case command_ready:
+ sts_reg_write_cr();
+ break;
+ case tpm_go:
+ sts_reg_write_tg();
+ break;
+ case response_retry:
+ sts_reg_write_rr();
+ break;
+ case command_cancel:
+ /* TODO: this also needs to be handled, fall through for now. */
+ default:
+ CPRINTF("requested to write %08x to sts\n", value);
+ break;
+ }
+}
+
+/* We presume to be running on a little endian CPU. */
+static uint32_t be32_to_cpu(const uint8_t *data)
+{
+ int i;
+ uint32_t value = 0;
+
+ for (i = 0; i < sizeof(value); i++)
+ value |= ((uint32_t)data[i]) << (8 * (sizeof(value) - i - 1));
+
+ return value;
+}
+
+/* Collect received data in the local buffer and change state accordingly. */
+static void fifo_reg_write(const uint8_t *data, uint32_t data_size)
+{
+ uint32_t packet_size;
+ /*
+ * Make sure we are in the approriate sate, otherwise ignore this
+ * access.
+ */
+ if ((tpm_.state == tpm_state_ready) && (tpm_.fifo_write_index == 0))
+ tpm_.state = tpm_state_receiving_cmd;
+
+ if (tpm_.state != tpm_state_receiving_cmd) {
+ CPRINTF("%s: ignoring data in state %d\n",
+ __func__, tpm_.state);
+ return;
+ }
+
+ if ((tpm_.fifo_write_index + data_size) > sizeof(tpm_.regs.data_fifo)) {
+ CPRINTF("%s: receive buffer overflow: %d in addition to %d\n",
+ __func__, data_size, tpm_.fifo_write_index);
+ tpm_.fifo_write_index = 0;
+ tpm_.state = tpm_state_ready;
+ return;
+ }
+
+ /* Copy data into the local buffer. */
+ memcpy(tpm_.regs.data_fifo + tpm_.fifo_write_index,
+ data, data_size);
+
+ tpm_.fifo_write_index += data_size;
+
+ /* Verify that size in the header matches the block size */
+ if (tpm_.fifo_write_index < 6) {
+ tpm_.regs.sts |= expect; /* More data is needed. */
+ return;
+ }
+
+ packet_size = be32_to_cpu(tpm_.regs.data_fifo + 2);
+ if (tpm_.fifo_write_index < packet_size) {
+ tpm_.regs.sts |= expect; /* More data is needed. */
+ return;
+ }
+
+ /* All data has been receved, Ready for the 'go' command. */
+ tpm_.regs.sts &= ~expect;
+ CPRINTF("%s: received fifo command 0x%04x\n",
+ __func__, be32_to_cpu(tpm_.regs.data_fifo + 6));
+}
+
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);
+ access_reg_write(data[0]);
+ break;
+ case TPM_STS:
+ sts_reg_write(data, data_size);
+ break;
+ case TPM_DATA_FIFO:
+ fifo_reg_write(data, data_size);
break;
default:
- CPRINTF("%s(0x%06x, %d", __func__, regaddr, data_size);
+ CPRINTF("%s(0x%06x, %d bytes:", __func__, regaddr, data_size);
for (i = 0; i < data_size; i++)
CPRINTF(", %02x", data[i]);
CPRINTF("\n");
@@ -144,10 +353,10 @@ void tpm_register_get(uint32_t regaddr, uint8_t *dest, uint32_t data_size)
copy_bytes(dest, data_size, IF_CAPABILITY_REG);
break;
case TPM_ACCESS:
- copy_bytes(dest, data_size, tpm_regs.tpm_access);
+ copy_bytes(dest, data_size, tpm_.regs.access);
break;
case TPM_STS:
- copy_bytes(dest, data_size, tpm_regs.tpm_sts);
+ copy_bytes(dest, data_size, tpm_.regs.sts);
break;
default:
CPRINTS("%s(0x%06x, %d) => ??", __func__, regaddr, data_size);
@@ -158,8 +367,9 @@ void tpm_register_get(uint32_t regaddr, uint8_t *dest, uint32_t data_size)
static void tpm_init(void)
{
- tpm_regs.tpm_access = tpm_reg_valid_sts;
- tpm_regs.tpm_sts = (tpm_family_tpm2 << tpm_family_shift) |
+ tpm_.state = tpm_state_idle;
+ tpm_.regs.access = tpm_reg_valid_sts;
+ tpm_.regs.sts = (tpm_family_tpm2 << tpm_family_shift) |
(64 << burst_count_shift);
}