blob: 30630f565d1a2e226d9b43cf10c973874d033e91 [file] [log] [blame]
/* 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 "byteorder.h"
#include "console.h"
#include "extension.h"
#include "link_defs.h"
#include "nvmem.h"
#include "printf.h"
#include "signed_header.h"
#include "sps.h"
#include "system.h"
#include "system_chip.h"
#include "task.h"
#include "tpm_log.h"
#include "tpm_manufacture.h"
#include "tpm_registers.h"
#include "util.h"
#include "watchdog.h"
#include "wp.h"
/* TPM2 library includes. */
#include "ExecCommand_fp.h"
#include "Platform.h"
#include "_TPM_Init_fp.h"
#include "Manufacture_fp.h"
/****************************************************************************/
/*
* CAUTION: Variables defined in this in this file are treated specially.
*
* As always, initialized variables are placed in the .data section, and
* uninitialized variables in the .bss section. This saves space in the
* executable, because the loader can just zero .bss prior to running the
* program.
*
* However, the tpm_reset_request() function will zero the .bss section for THIS
* FILE and all files in the TPM library. Any uninitialized variables defined in
* this file that must be preserved across tpm_reset_request() must be placed in
* a separate section.
*
* On the other hand, initialized variables (in the .data section) are NOT
* affected by tpm_reset_request(), so any variables that should be
* reinitialized must be dealt with manually in the tpm_reset_request()
* function. To prevent initialized variables from being added to the TPM
* library without notice, the compiler will reject any that aren't explicitly
* flagged.
*/
/* This marks uninitialized variables that tpm_reset_request() should ignore */
#define __preserved __attribute__((section(".bss.noreinit")))
/*
* This marks initialized variables that tpm_reset_request() may need to reset
*/
#define __initialized __attribute__((section(".data.noreinit")))
/****************************************************************************/
#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_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 TPM_FW_VER (0xf90)
#define GOOGLE_VID 0x1ae0
#define GOOGLE_DID 0x0028
#define CR50_RID 0 /* No revision ID yet */
static __preserved uint8_t reset_in_progress;
/* 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 sts;
uint8_t data_fifo[2048]; /* this might have to be even 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),
};
/* Used to count bytes read in version string */
static int tpm_fw_ver_index;
/*
* Used to store the full version string, which includes version of the two RO
* and two RW regions in the flash as well as the version string of the four
* cr50 image components. The number is somewhat arbitrary, calculated for the
* worst case scenario when all compontent trees are 'dirty'.
*/
static uint8_t tpm_fw_ver[80];
/*
* We need to be able to report firmware version to the host, both RO and RW
* sections. This copies the information into a static string so that it can be
* passed to the host a little bit at a time.
*/
static void set_version_string(void)
{
enum system_image_copy_t active_ro, active_rw;
size_t offset;
active_ro = system_get_ro_image_copy();
active_rw = system_get_image_copy();
snprintf(tpm_fw_ver, sizeof(tpm_fw_ver), "%s:%d RO_%c:%s",
system_get_chip_revision(),
system_get_board_version(),
(active_ro == SYSTEM_IMAGE_RO ? 'A' : 'B'),
system_get_version(active_ro));
offset = strlen(tpm_fw_ver);
if (offset == sizeof(tpm_fw_ver) - 1)
return;
snprintf(tpm_fw_ver + offset,
sizeof(tpm_fw_ver) - offset, " RW_%c:%s",
(active_rw == SYSTEM_IMAGE_RW ? 'A' : 'B'),
system_get_version(active_rw));
}
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 an unsupported value"
" of 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 = 0;
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 status reg to %02x\n",
__func__, value);
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;
}
}
/* 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;
struct tpm_cmd_header *tpmh;
/*
* Make sure we are in the appropriate state, 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;
}
tpmh = (struct tpm_cmd_header *)tpm_.regs.data_fifo;
packet_size = be32toh(tpmh->size);
if (tpm_.fifo_write_index < packet_size) {
tpm_.regs.sts |= expect; /* More data is needed. */
return;
}
/* All data has been received, Ready for the 'go' command. */
tpm_.regs.sts &= ~expect;
}
/* TODO: data_size is between 1 and 64, but is not trustworthy! Don't write
* past the end of any actual registers if data_size is larger than the spec
* allows. */
void tpm_register_put(uint32_t regaddr, const uint8_t *data, uint32_t data_size)
{
uint32_t i;
if (reset_in_progress)
return;
CPRINTF("%s(0x%03x, %d,", __func__, regaddr, data_size);
for (i = 0; i < data_size && i < 4; i++)
CPRINTF(" %02x", data[i]);
if (data_size > 4)
CPRINTF(" ...");
CPRINTF(")\n");
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;
case TPM_FW_VER:
/* Reset read byte count */
tpm_fw_ver_index = 0;
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;
uint32_t tpm_sts;
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;
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);
/* Burst 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;
}
/* TODO: data_size is between 1 and 64, but is not trustworthy! We must return
* that many bytes, but not leak any secrets if data_size is larger than
* it should be. Return 0x00 or 0xff or whatever the spec says instead. */
void tpm_register_get(uint32_t regaddr, uint8_t *dest, uint32_t data_size)
{
int i;
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;
case TPM_FW_VER:
for (i = 0; i < data_size; i++) {
/*
* Only read while the index remains less than the
* maximum allowed version string size.
*/
if (tpm_fw_ver_index < sizeof(tpm_fw_ver)) {
*dest++ = tpm_fw_ver[tpm_fw_ver_index];
/*
* If reached end of string, then don't update
* the index so that it will keep pointing at
* the end of string character and continue to
* fill *dest with 0s.
*/
if (tpm_fw_ver[tpm_fw_ver_index] != '\0')
tpm_fw_ver_index++;
} else
/* Not in a valid state, just stuff 0s */
*dest++ = 0;
}
break;
default:
CPRINTS("%s(0x%06x, %d) => ??", __func__, regaddr, data_size);
return;
}
CPRINTF("\n");
}
static __preserved interface_control_func if_start;
static __preserved interface_control_func if_stop;
void tpm_register_interface(interface_control_func interface_start,
interface_control_func interface_stop)
{
if_start = interface_start;
if_stop = interface_stop;
}
static void tpm_init(void)
{
/*
* 0xc0 Means successful endorsement. Actual endorsement reasult code
* is added in lower bits to indicate endorsement failure, if any.
*/
uint8_t underrun_char = 0xc0;
/* This is more related to TPM task activity than TPM transactions */
cprints(CC_TASK, "%s", __func__);
if (system_rolling_reboot_suspected()) {
cprints(CC_TASK, "%s interrupted", __func__);
return;
}
set_tpm_state(tpm_state_idle);
tpm_.regs.access = tpm_reg_valid_sts;
/*
* I2CS writes must limit the burstsize to 63 for fifo writes to work
* properly. For I2CS fifo writes the first byte is the I2C TPM address
* and the next up to 62 bytes are the data to write to that register.
*/
tpm_.regs.sts = (tpm_family_tpm2 << tpm_family_shift) |
(63 << burst_count_shift) | sts_valid;
/* TPM2 library functions. */
_plat__Signal_PowerOn();
/* Create version string to be read by host */
set_version_string();
watchdog_reload();
/*
* Make sure NV RAM metadata is initialized, needed to check
* manufactured status. This is a speculative call which will have to
* be repeated in case the TPM has not been through the manufacturing
* sequence yet.
*
* No harm in calling it twice in that case.
*/
_TPM_Init();
if (!tpm_manufactured()) {
enum manufacturing_status endorse_result;
/*
* If tpm has not been manufactured yet - this needs to run on
* every startup. It will wipe out NV RAM, among other things.
*/
TPM_Manufacture(1);
_TPM_Init();
_plat__SetNvAvail();
endorse_result = tpm_endorse();
ccprintf("[%T Endorsement %s]\n",
(endorse_result == mnf_success) ?
"succeeded" : "failed");
if (chip_factory_mode()) {
underrun_char |= endorse_result;
ccprintf("[%T Setting underrun character to 0x%x]\n",
underrun_char);
sps_tx_status(underrun_char);
}
} else {
if (chip_factory_mode())
sps_tx_status(underrun_char | mnf_manufactured);
_plat__SetNvAvail();
}
}
size_t tpm_get_burst_size(void)
{
return (tpm_.regs.sts >> burst_count_shift) & burst_count_mask;
}
#ifdef CONFIG_EXTENSION_COMMAND
/* Recognize both original extension and new vendor-specific command codes */
#define IS_CUSTOM_CODE(code) \
((code == CONFIG_EXTENSION_COMMAND) || \
(code & TPM_CC_VENDOR_BIT_MASK))
static void call_extension_command(struct tpm_cmd_header *tpmh,
size_t *total_size,
uint32_t flags)
{
size_t command_size = be32toh(tpmh->size);
uint32_t rc;
/*
* Note that we don't look for TPM_CC_VENDOR_CR50 anywhere. All
* vendor-specific commands are handled the same way for now.
*/
/* Verify there is room for at least the extension command header. */
if (command_size >= sizeof(struct tpm_cmd_header)) {
struct vendor_cmd_params p = {
.code = be16toh(tpmh->subcommand_code),
/* The header takes room in the buffer. */
.buffer = tpmh + 1,
.in_size = command_size - sizeof(struct tpm_cmd_header),
.out_size = *total_size - sizeof(struct tpm_cmd_header),
.flags = flags
};
rc = extension_route_command(&p);
/* Add the header size back. */
*total_size = p.out_size + sizeof(struct tpm_cmd_header);
tpmh->size = htobe32(*total_size);
/* Flag errors from commands as vendor-specific */
if (rc)
rc |= VENDOR_RC_ERR;
tpmh->command_code = htobe32(rc);
} else {
*total_size = command_size;
}
}
#endif
/*
* Events used on the TPM task context. Make sure there is no collision with
* event(s) defined in chip/g/dcrypto/dcrypto_runtime.c
*/
#define TPM_EVENT_RESET TASK_EVENT_CUSTOM(1 << 1)
#define TPM_EVENT_COMMIT TASK_EVENT_CUSTOM(1 << 2)
#define TPM_EVENT_ALT_EXTENSION TASK_EVENT_CUSTOM(1 << 3)
/*
* Result of executing of the TPM command on the alternative path, could have
* been interrupted by a reset.
*/
enum alt_process_result {
ALT_PROCESS_WAITING,
ALT_PROCESS_DONE,
ALT_PROCESS_INTERRUPTED
};
/*
* This structure stores the context of the alternative TPM command execution
* path.
*
* The command and response share the buffer, when TPM task finishes
* processing the command it sets the 'process_result' field to a non-zero
* value.
*
* The mutex ensures that only one alternative TPM command execution is active
* at a time.
*/
static __preserved struct alt_tpm_interface {
struct tpm_cmd_header *alt_hdr;
size_t alt_buffer_size;
uint32_t process_result;
struct mutex if_mutex;
} alt_if;
void tpm_alt_extension(struct tpm_cmd_header *command, size_t buffer_size)
{
mutex_lock(&alt_if.if_mutex);
memset(&alt_if, 0, sizeof(alt_if));
alt_if.alt_hdr = command;
alt_if.alt_buffer_size = buffer_size;
do {
alt_if.process_result = ALT_PROCESS_WAITING;
task_set_event(TASK_ID_TPM, TPM_EVENT_ALT_EXTENSION, 0);
/*
* This is not very elegant, but simple and acceptable for
* this TPM command execution path, as in most cases it would
* be drven by a human operator.
*
* Use REG32 to make sure that the field is treated as
* volatile.
*/
while (REG32(&alt_if.process_result) == ALT_PROCESS_WAITING)
msleep(10);
/*
* Repeat the request if command execution was interrupted by
* a TPM reset.
*/
} while (REG32(&alt_if.process_result) != ALT_PROCESS_DONE);
mutex_unlock(&alt_if.if_mutex);
}
/* Calling task (singular) to notify when the TPM reset has completed */
static __initialized task_id_t waiting_for_reset = TASK_ID_INVALID;
/* Return value from blocking tpm_reset_request() call */
static __preserved int wipe_result;
/*
* Did tpm_reset_request() request nvmem wipe? (intentionally cleared on reset)
*/
static int wipe_requested;
int tpm_reset_request(int wait_until_done, int wipe_nvmem_first)
{
uint32_t evt;
cprints(CC_TASK, "%s(%d, %d)", __func__,
wait_until_done, wipe_nvmem_first);
if (reset_in_progress) {
cprints(CC_TASK, "%s: already scheduled", __func__);
return EC_ERROR_BUSY;
}
/* Record input parameters as two bits in the data field. */
tpm_log_event(TPM_EVENT_INIT,
(!!wait_until_done << 1) | !!wipe_nvmem_first);
reset_in_progress = 1;
wipe_result = EC_SUCCESS;
/* We can't change our minds about wiping. */
wipe_requested |= wipe_nvmem_first;
if (wait_until_done)
/*
* Completion could take a while, if other things have
* higher priority.
*/
waiting_for_reset = task_get_current();
/* Ask the TPM task to reset itself */
task_set_event(TASK_ID_TPM, TPM_EVENT_RESET, 0);
if (!wait_until_done)
return EC_SUCCESS;
if (in_interrupt_context() ||
task_get_current() == TASK_ID_TPM) {
waiting_for_reset = TASK_ID_INVALID;
return EC_ERROR_BUSY; /* Can't sleep. Clown'll eat me. */
}
evt = task_wait_event_mask(TPM_EVENT_RESET, 5 * SECOND);
/* We were notified of completion */
if (evt & TPM_EVENT_RESET)
return wipe_result;
/* Timeout is bad */
return EC_ERROR_TIMEOUT;
}
/*
* A timeout hook to reinstate NVMEM commits soon after reset.
*
* The TPM task disables nvmem commits during TPM reset, they need to be
* reinstated on the same task context. This is why an event is raised here to
* wake up the TPM task and force it to reinstate nvmem commits instead of
* doing it here directly.
*/
static void reinstate_nvmem_commits(void)
{
tpm_reinstate_nvmem_commits();
}
DECLARE_DEFERRED(reinstate_nvmem_commits);
void tpm_reinstate_nvmem_commits(void)
{
task_set_event(TASK_ID_TPM, TPM_EVENT_COMMIT, 0);
}
static void tpm_reset_now(int wipe_first)
{
/* TPM is not running in factory mode. */
if (!chip_factory_mode())
if_stop();
/* This is more related to TPM task activity than TPM transactions */
cprints(CC_TASK, "%s(%d)", __func__, wipe_first);
if (wipe_first)
/* Now wipe the TPM's nvmem */
wipe_result = nvmem_erase_user_data(NVMEM_TPM);
else
wipe_result = EC_SUCCESS;
/*
* Clear the TPM library's zero-init data. Note that the linker script
* includes this file's .bss in the same section, so it will be cleared
* at the same time.
*/
memset(&__bss_libtpm2_start, 0,
(uintptr_t)(&__bss_libtpm2_end) -
(uintptr_t)(&__bss_libtpm2_start));
/*
* NOTE: If any __initialized variables need reinitializing after
* reset, this is the place to do it.
*/
/*
* If TPM was reset while commits were disabled, save whatever changes
* might have accumulated.
*/
nvmem_enable_commits();
/*
* Prevent NVRAM commits until further notice, unless running in
* factory mode.
*/
if (!chip_factory_mode())
nvmem_disable_commits();
/* Re-initialize our registers */
tpm_init();
if (waiting_for_reset != TASK_ID_INVALID) {
/* Wake the waiting task, if any */
task_set_event(waiting_for_reset, TPM_EVENT_RESET, 0);
waiting_for_reset = TASK_ID_INVALID;
}
cprints(CC_TASK, "%s: done", __func__);
/*
* The host might decide to do it sooner, but let's make sure commits
* do not stay disabled for more than 3 seconds.
*/
hook_call_deferred(&reinstate_nvmem_commits_data, 3 * SECOND);
reset_in_progress = 0;
/*
* In chip factory mode SPI idle byte sent on MISO is used for
* progress reporting. TPM flow control messes it up, do not start TPM
* in factory mode.
*/
if (!chip_factory_mode())
if_start();
}
int tpm_sync_reset(int wipe_first)
{
tpm_reset_now(wipe_first);
return wipe_result;
}
void tpm_stop(void)
{
if_stop();
}
void tpm_task(void)
{
uint32_t evt = 0;
if (!chip_factory_mode()) {
/*
* Just in case there is a resume from deep sleep where AP is
* not out of reset, let's not proceed until AP is actually
* up. No need to worry about the AP state in chip factory
* mode of course.
*/
while (!ap_is_on()) {
/*
* The only events we should expect at this point
* would be the reset request or a command routed
* through TPM task context to make use of the large
* stack.
*/
evt = task_wait_event(-1);
if (evt & (TPM_EVENT_RESET | TPM_EVENT_ALT_EXTENSION)) {
/*
* No need to remember the reset request: tpm
* reset will happen as soon as we break out
* from this while loop,
*/
evt &= TPM_EVENT_ALT_EXTENSION;
break;
}
cprints(CC_TASK, "%s:%d unexpected event %x",
__func__, __LINE__, evt);
}
}
tpm_reset_now(0);
while (1) {
uint8_t *response;
unsigned response_size;
uint32_t command_code;
struct tpm_cmd_header *tpmh;
size_t buffer_size;
uint8_t alt_if_command;
/* Process unprocessed events or wait for the next event */
if (!evt)
evt = task_wait_event(-1);
if (evt & TPM_EVENT_RESET) {
tpm_reset_now(wipe_requested);
if (evt & TPM_EVENT_ALT_EXTENSION) {
/*
* Need to tell the waiting task that
* processing was interrupted.
*/
alt_if.process_result = ALT_PROCESS_INTERRUPTED;
}
/*
* There is no point in looking at other events in
* this situation: the nvram will be committed by TPM
* reset; other tpm commands would be ignored.
*
* Let's just continue. This could change if there are
* other events added to the set.
*/
evt = 0;
continue;
}
if (evt & TPM_EVENT_COMMIT) {
evt &= ~TPM_EVENT_COMMIT;
nvmem_enable_commits();
}
if (evt & TASK_EVENT_WAKE) {
evt &= ~TASK_EVENT_WAKE;
tpmh = (struct tpm_cmd_header *)tpm_.regs.data_fifo;
buffer_size = sizeof(tpm_.regs.data_fifo);
alt_if_command = 0;
} else if (evt & TPM_EVENT_ALT_EXTENSION) {
evt &= ~TPM_EVENT_ALT_EXTENSION;
tpmh = alt_if.alt_hdr;
buffer_size = alt_if.alt_buffer_size;
alt_if_command = 1;
} else {
if (evt) {
cprints(CC_TASK, "%s:%d unexpected event %x",
__func__, __LINE__, evt);
evt = 0;
}
continue;
}
command_code = be32toh(tpmh->command_code);
CPRINTF("%s: received fifo command 0x%04x\n",
__func__, command_code);
watchdog_reload();
#ifdef CONFIG_EXTENSION_COMMAND
if (IS_CUSTOM_CODE(command_code)) {
response_size = buffer_size;
call_extension_command(tpmh, &response_size,
alt_if_command ?
VENDOR_CMD_FROM_USB : 0);
} else
#endif
{
if (board_id_is_mismatched()) {
static const char tpm_broken_response[] = {
0x80, 0x01, /* TPM_ST_NO_SESSIONS */
0, 0, 0, 10, /* Response size. */
0, 0, 9, 0x21 /* TPM_RC_LOCKOUT */
};
CPRINTF("%s: Ignoring TPM commands\n",
__func__);
response = (uint8_t *)tpmh;
response_size = sizeof(tpm_broken_response);
memcpy(response, tpm_broken_response,
response_size);
} else {
ExecuteCommand(tpm_.fifo_write_index,
(uint8_t *)tpmh,
&response_size,
&response);
}
}
CPRINTF("got %d bytes in response\n", response_size);
if (response_size &&
(response_size <= buffer_size)) {
uint32_t tpm_sts;
/*
* TODO(vbendeb): revisit this when
* crosbug.com/p/55667 has been addressed.
*/
if (command_code == TPM2_PCR_Read)
system_process_retry_counter();
#ifdef CONFIG_EXTENSION_COMMAND
if (!IS_CUSTOM_CODE(command_code))
#endif
{
/*
* Extension commands reuse FIFO buffer, the
* rest need to copy.
*/
memcpy(tpmh, response, response_size);
}
if (alt_if_command) {
alt_if.process_result = ALT_PROCESS_DONE;
/* No need to manage TPM registers. */
continue;
}
tpm_.fifo_read_index = 0;
tpm_.fifo_write_index = response_size;
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;
}
}
}