/* 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 "task.h" #include "tpm_registers.h" #include "util.h" /* TPM2 library includes. */ #include "ExecCommand_fp.h" #include "Platform.h" #include "_TPM_Init_fp.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 0x24 #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 */ /* 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. */ /* Volatile registers for FIFO mode */ 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 */ 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), 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), }; static void set_tpm_state(enum tpm_states state) { CPRINTF("state transition from %d to %d\n", tpm_.state, state); tpm_.state = state; if (state == tpm_state_idle) { /* Make sure FIFO is empty. */ tpm_.fifo_read_index = 0; tpm_.fifo_write_index = 0; } } /* * 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 * 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 i; data_size = MIN(data_size, 4); for (i = 0; i < data_size; i++) dest[i] = (value >> (i * 8)) & 0xff; } static void access_reg_write(uint8_t data) { if (!single_bit_set(data)) { CPRINTF("%s: attempt to set acces reg to %02x\n", __func__, data); return; } switch (data) { case request_use: /* * No multiple localities supported, let's just always honor * this request. */ tpm_.regs.access |= active_locality; break; case 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. */ set_tpm_state(tpm_state_idle); 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: set_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: set_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. */ set_tpm_state(tpm_state_executing_cmd); task_set_event(TASK_ID_TPM, TASK_EVENT_WAKE, 0); } 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)) set_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; set_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; } void tpm_register_put(uint32_t regaddr, const uint8_t *data, uint32_t data_size) { uint32_t i; uint32_t idata; memcpy(&idata, data, 4); CPRINTF("%s(0x%06x, %d %x)\n", __func__, regaddr, data_size, idata); switch (regaddr) { case TPM_ACCESS: /* This is a one byte register, ignore extra data, if any */ 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 bytes:", __func__, regaddr, data_size); for (i = 0; i < data_size; i++) CPRINTF(", %02x", data[i]); CPRINTF("\n"); return; } } void fifo_reg_read(uint8_t *dest, uint32_t data_size) { uint32_t still_in_fifo = tpm_.fifo_write_index - tpm_.fifo_read_index; data_size = MIN(data_size, still_in_fifo); memcpy(dest, tpm_.regs.data_fifo + tpm_.fifo_read_index, data_size); tpm_.fifo_read_index += data_size; if (tpm_.fifo_write_index == tpm_.fifo_read_index) tpm_.regs.sts &= ~(data_avail | command_ready); } void tpm_register_get(uint32_t regaddr, uint8_t *dest, uint32_t data_size) { CPRINTF("%s(0x%06x, %d)", __func__, regaddr, 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.access); break; case TPM_STS: CPRINTF(" %x", tpm_.regs.sts); copy_bytes(dest, data_size, tpm_.regs.sts); break; case TPM_DATA_FIFO: fifo_reg_read(dest, data_size); break; default: CPRINTS("%s(0x%06x, %d) => ??", __func__, regaddr, data_size); return; } CPRINTF("\n"); } static void tpm_init(void) { set_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) | sts_valid; /* TPM2 library functions. */ _plat__Signal_PowerOn(); _TPM_Init(); _plat__SetNvAvail(); } void tpm_task(void) { tpm_init(); sps_tpm_enable(); while (1) { uint8_t *response; unsigned response_size; /* Wait for the next command event */ task_wait_event(-1); CPRINTF("%s: received fifo command 0x%04x\n", __func__, be32_to_cpu(tpm_.regs.data_fifo + 6)); ExecuteCommand(tpm_.fifo_write_index, tpm_.regs.data_fifo, &response_size, &response); CPRINTF("got %d bytes in response\n", response_size); if (response_size && (response_size <= sizeof(tpm_.regs.data_fifo))) { memcpy(tpm_.regs.data_fifo, response, response_size); tpm_.fifo_read_index = 0; tpm_.fifo_write_index = response_size; tpm_.regs.sts |= data_avail; set_tpm_state(tpm_state_completing_cmd); } } }