262 lines
7.9 KiB
C
262 lines
7.9 KiB
C
|
/* 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 "i2cs.h"
|
||
|
#include "registers.h"
|
||
|
#include "system.h"
|
||
|
#include "tpm_registers.h"
|
||
|
|
||
|
/*
|
||
|
* This implements adaptaition layer between i2cs (i2c slave) port and TPM.
|
||
|
*
|
||
|
* The adaptation layer is stateless, it processes the i2cs "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 */
|
||
|
};
|
||
|
|
||
|
/* Used to track number of times i2cs hw read fifo was adjusted */
|
||
|
static uint32_t i2cs_fifo_adjust_count;
|
||
|
/* Used to track number of write mismatch errors */
|
||
|
static uint32_t i2cs_write_error_count;
|
||
|
|
||
|
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 i2cs 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 (i2cs_zero_read_fifo_buffer_depth())
|
||
|
/* Count each instance that fifo was adjusted */
|
||
|
i2cs_fifo_adjust_count++;
|
||
|
for (i = 0; i < reg_size; i++)
|
||
|
i2cs_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 I2CS HW fifo */
|
||
|
i2cs_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 i2cs_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, i2cs_data_size);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (i2cs_data_size != reg_size) {
|
||
|
i2cs_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 *i2cs_data, size_t i2cs_data_size)
|
||
|
{
|
||
|
size_t i;
|
||
|
uint16_t tpm_reg;
|
||
|
uint8_t *data = i2cs_data;
|
||
|
const struct i2c_tpm_reg_map *i2c_reg_entry = NULL;
|
||
|
uint16_t reg_size;
|
||
|
|
||
|
if (i2cs_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;
|
||
|
|
||
|
i2cs_data_size--;
|
||
|
data++;
|
||
|
|
||
|
if (!i2cs_data_size)
|
||
|
process_read_access(reg_size, tpm_reg, data);
|
||
|
else
|
||
|
process_write_access(reg_size, tpm_reg,
|
||
|
data, i2cs_data_size);
|
||
|
|
||
|
/*
|
||
|
* 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);
|
||
|
gpio_set_level(GPIO_INT_AP_L, 1);
|
||
|
}
|
||
|
|
||
|
static void i2cs_if_stop(void)
|
||
|
{
|
||
|
i2cs_register_write_complete_handler(NULL);
|
||
|
}
|
||
|
|
||
|
static void i2cs_if_start(void)
|
||
|
{
|
||
|
i2cs_register_write_complete_handler(wr_complete_handler);
|
||
|
}
|
||
|
|
||
|
static void i2cs_if_register(void)
|
||
|
{
|
||
|
if (!board_tpm_uses_i2c())
|
||
|
return;
|
||
|
|
||
|
tpm_register_interface(i2cs_if_start, i2cs_if_stop);
|
||
|
i2cs_fifo_adjust_count = 0;
|
||
|
i2cs_write_error_count = 0;
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_INIT, i2cs_if_register, HOOK_PRIO_LAST);
|
||
|
|
||
|
static int command_i2cs(int argc, char **argv)
|
||
|
{
|
||
|
static uint16_t base_read_recovery_count;
|
||
|
struct i2cs_status status;
|
||
|
|
||
|
i2cs_get_status(&status);
|
||
|
|
||
|
ccprintf("rd fifo adjust cnt = %d\n", i2cs_fifo_adjust_count);
|
||
|
ccprintf("wr mismatch cnt = %d\n", i2cs_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")) {
|
||
|
i2cs_fifo_adjust_count = 0;
|
||
|
i2cs_write_error_count = 0;
|
||
|
base_read_recovery_count = status.read_recovery_count;
|
||
|
ccprintf("i2cs error counts reset\n");
|
||
|
} else
|
||
|
return EC_ERROR_PARAM1;
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
DECLARE_SAFE_CONSOLE_COMMAND(i2cstpm, command_i2cs,
|
||
|
"reset",
|
||
|
"Display fifo adjust count");
|