Cr50: Preliminary I2CS TPM2.0 driver

This CL includes changes in Cr50 required to support TPM via
the I2CS interface.

BRANCH=none
BUG=chrome-os-partner:40397
TEST=manual
Limited testing so far. Verified that the I2CS interface is
initialized properly and that register reads occur when
initiated on the AP console via command i2cget -y 8 0x50 0x1 w

Change-Id: I16ac17c7c82d420a384908e4b5a9867a3b24bc9e
Reviewed-on: https://chromium-review.googlesource.com/356241
Commit-Ready: Scott Collyer <scollyer@chromium.org>
Tested-by: Scott Collyer <scollyer@chromium.org>
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
This commit is contained in:
Scott
2016-08-02 12:36:52 -07:00
committed by chrome-bot
parent d6c69cef59
commit e4f389a275
12 changed files with 295 additions and 35 deletions

View File

@@ -12,6 +12,7 @@
#include "flash_config.h"
#include "gpio.h"
#include "hooks.h"
#include "i2cs.h"
#include "init_chip.h"
#include "registers.h"
#include "nvmem.h"
@@ -592,3 +593,24 @@ uint32_t system_board_properties_callback(void)
{
return board_properties;
}
void i2cs_set_pinmux(void)
{
/* Connect I2CS SDA/SCL output to A1/A9 pads */
GWRITE(PINMUX, DIOA1_SEL, GC_PINMUX_I2CS0_SDA_SEL);
GWRITE(PINMUX, DIOA9_SEL, GC_PINMUX_I2CS0_SCL_SEL);
/* Connect A1/A9 pads to I2CS input SDA/SCL */
GWRITE(PINMUX, I2CS0_SDA_SEL, GC_PINMUX_DIOA1_SEL);
GWRITE(PINMUX, I2CS0_SCL_SEL, GC_PINMUX_DIOA9_SEL);
/* Enable SDA/SCL inputs from A1/A9 pads */
GWRITE_FIELD(PINMUX, DIOA1_CTL, IE, 1); /* I2CS_SDA */
GWRITE_FIELD(PINMUX, DIOA9_CTL, IE, 1); /* I2CS_SCL */
/*
* Enable pull ups on both signals. TODO(vbendeb): consider
* adjusting pull strength.
*/
GWRITE_FIELD(PINMUX, DIOA1_CTL, PU, 1);
GWRITE_FIELD(PINMUX, DIOA9_CTL, PU, 1);
/* TODO(scollyer): Do we need to add wake on SCL activity here? */
}

View File

@@ -193,4 +193,8 @@ enum nvmem_users {
#define CONFIG_NON_HC_FW_UPDATE
#define CONFIG_USB_FW_UPDATE
#define CONFIG_I2C
#define CONFIG_I2C_SLAVE
#define CONFIG_TPM_I2CS
#endif /* __CROS_EC_BOARD_H */

View File

@@ -110,9 +110,16 @@ PINMUX(GPIO(SERVO_UART1_OFF), A7, DIO_INPUT)
PINMUX(GPIO(SERVO_UART2_ON), B5, DIO_INPUT)
PINMUX(GPIO(SERVO_UART2_OFF), B5, DIO_INPUT)
/* I2C pins are bi-directional */
PINMUX(FUNC(I2C0_SCL), B0, DIO_INPUT)
PINMUX(FUNC(I2C0_SDA), B1, DIO_INPUT)
/*
* I2CS pins are bi-directional and would be configured here as shown. However,
* A1 is also used as a strapping option GPIO input which is configured
* above. If a board is configured (via the strapping pins) to support the I2CS
* interface, then the connection of A1 and A9 to/from the I2C0_SDA and I2C0_SCL
* lines is done in the function i2cs_set_pinmux() which lives in board.c.
*
* PINMUX(FUNC(I2C0_SCL), A9, DIO_INPUT)
* PINMUX(FUNC(I2C0_SDA), A1, DIO_INPUT)
*/
/*
* Both SPI master and slave buses are wired directly to specific pads

View File

@@ -3,9 +3,6 @@
* found in the LICENSE file.
*/
#include "tpm_manufacture.h"
#include "tpm_registers.h"
#include "TPM_Types.h"
#include "TpmBuildSwitches.h"
#include "CryptoEngine.h"
@@ -27,6 +24,8 @@
#include "flash_info.h"
#include "printf.h"
#include "registers.h"
#include "tpm_manufacture.h"
#include "tpm_registers.h"
#include "dcrypto.h"

View File

@@ -60,6 +60,7 @@ chip-$(CONFIG_RDD)+=rdd.o
chip-$(CONFIG_RBOX)+=rbox.o
chip-$(CONFIG_STREAM_USB)+=usb-stream.o
chip-$(CONFIG_STREAM_USART)+=usart.o
chip-$(CONFIG_I2C_SLAVE)+= i2cs.o
chip-$(CONFIG_LOW_POWER_IDLE)+=idle.o

View File

@@ -8,13 +8,14 @@
*
* The controller is has two register files, 64 bytes each, one for storing
* data received from the master, and one for storing data to be transmitted
* to the master. Both files are accessed only as 4 byte entities, so the
* to the master. Both files are accessed only as 4 byte quantities, so the
* driver must provide adaptation to concatenate messages with sizes not
* divisible by 4.
* divisible by 4 and or not properly aligned.
*
* The file holding data written by the master has associated with it a
* register showing where the controller accessed the file last, which tells
* the driver how many bytes written by the master are there.
* register showing where the controller accessed the file last, comparing it
* with its pervious value tells the driver how many bytes recently written by
* the master are there.
*
* The file holding data to be read by the master has a register associtated
* with it showing where was the latest BIT the controller transmitted.
@@ -28,21 +29,21 @@
* to assume that the master will always write first, even when it needs to
* read data from the device.
*
* Each write or read access will be started by the master writing the two
* Each write or read access will be started by the master writing the one
* byte address of the TPM register to access.
*
* If the master needs to read this register, the originating write
* transaction will be limited to a two bytes payload, a read transaction
* transaction will be limited to a single byte payload, a read transaction
* would follow immediately.
*
* If the master needs to write this register, the data to be written will be
* included in the same i2c transaction immediately following the two byte
* address.
* If the master needs to write into this register, the data to be written
* will be included in the same i2c transaction immediately following the one
* byte register address.
*
* This protocol allows to keep the driver simple: the only interrupt the
* driver enables is the 'end a write cycle'. The number of bytes received
* from the master gives this driver a hint as of what the master intention
* is, to read or to write.
* from the master gives the callback function a hint as of what the master
* intention is, to read or to write.
*
* In both cases the same callback function is called. On write accesses the
* callback function converts the data as necessary and passes it to the TPM.
@@ -58,8 +59,7 @@
* TODO:
* - figure out flow control - clock stretching can be challenging with this
* controller.
* - transferring data exceeding 64 bytes in size (accessing TPM FIFO).
* - detect and revover overflow/underflow situations
* - detect and recover from overflow/underflow situations
*/
#include "common.h"
@@ -68,6 +68,7 @@
#include "i2cs.h"
#include "pmu.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#define REGISTER_FILE_SIZE (1 << 6) /* 64 bytes. */
@@ -81,7 +82,7 @@
static wr_complete_handler_f write_complete_handler_;
/* A buffer to normalize the received data to pass it to the user. */
static uint8_t i2cs_buffer[64];
static uint8_t i2cs_buffer[REGISTER_FILE_SIZE];
/*
* Pointer where the CPU stopped retrieving the write data sent by the master
@@ -99,18 +100,15 @@ static void i2cs_init(void)
{
/* First decide if i2c is even needed for this platform. */
/* if (i2cs is not needed) return; */
return; /* Let's not do anything yet. */
if (!(system_get_board_properties() & BOARD_SLAVE_CONFIG_I2C))
return;
pmu_clock_en(PERIPH_I2CS);
/*
* i2cs function has been already configured in the gpio.inc table,
* here just enable pull ups on both signals. TODO(vbendeb): consider
* adjusting pull strength.
*/
GWRITE_FIELD(PINMUX, DIOB0_CTL, PU, 1);
GWRITE_FIELD(PINMUX, DIOB1_CTL, PU, 1);
/* Set pinmux registers for I2CS interface */
i2cs_set_pinmux();
/* Enable I2CS interrupt */
GWRITE_FIELD(I2CS, INT_ENABLE, INTR_WRITE_COMPLETE, 1);
/* Slave address is hardcoded to 0x50. */
@@ -140,9 +138,9 @@ static void _i2cs_write_complete_int(void)
while (bytes_written != bytes_processed) {
/*
* This loop iterates over bytes retrieved from the
* master write register file in 4 byte entities. Each
* time the ever incrementing last_write_pointer is
* aligned at 4 bytes, a new value needs to be
* master write register file in 4 byte quantities.
* Each time the ever incrementing last_write_pointer
* is aligned at 4 bytes, a new value needs to be
* retrieved from the next register, indexed by
* last_write_pointer/4.
*/

View File

@@ -24,4 +24,10 @@ int i2cs_register_write_complete_handler(wr_complete_handler_f wc_handler);
*/
void i2cs_post_read_data(uint8_t byte_to_read);
/*
* Configure the pinmux registers required to connect the I2CS interface. This
* function is board specific and so it exists in the associated board.c file.
*/
void i2cs_set_pinmux(void);
#endif /* ! __CHIP_G_I2CS_H */

View File

@@ -86,7 +86,7 @@ common-$(CONFIG_SWITCH)+=switch.o
common-$(CONFIG_SW_CRC)+=crc.o
common-$(CONFIG_TEMP_SENSOR)+=temp_sensor.o
common-$(CONFIG_THROTTLE_AP)+=thermal.o throttle_ap.o
common-$(CONFIG_TPM_SPS)+=tpm_registers.o
common-$(CONFIG_TPM_I2CS)+=i2cs_tpm.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
@@ -105,6 +105,7 @@ common-$(HAS_TASK_PDCMD)+=host_command_pd.o
common-$(HAS_TASK_KEYSCAN)+=keyboard_scan.o
common-$(HAS_TASK_LIGHTBAR)+=lb_common.o lightbar.o
common-$(HAS_TASK_MOTIONSENSE)+=motion_sense.o
common-$(HAS_TASK_TPM)+=tpm_registers.o
ifeq ($(CTS_MODULE),)
common-$(TEST_BUILD)+=test_util.o

191
common/i2cs_tpm.c Normal file
View File

@@ -0,0 +1,191 @@
/* 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 "hooks.h"
#include "i2cs.h"
#include "registers.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 */
};
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;
uint8_t reg_value[4];
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) {
/*
* The master wants to read the register, read the value and
* pass it to the controller.
*/
if (reg_size == 1) {
uint8_t byte_reg;
/* Always read 4 bytes. */
tpm_register_get(tpm_reg, &byte_reg, sizeof(byte_reg));
i2cs_post_read_data(byte_reg);
return;
}
if (reg_size == 4) {
tpm_register_get(tpm_reg, reg_value, sizeof(reg_value));
/* Write data to I2CS HW fifo */
for (i = 0; i < sizeof(reg_value); 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 */
for (i = 0; i < reg_size; i++)
i2cs_post_read_data(data[i]);
return;
}
/* 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) {
CPRINTF("%s: data size mismatch for reg 0x%x "
"(rx %d, need %d)\n", __func__, tpm_reg,
i2cs_data_size, reg_size);
return;
}
/* Write the data to the appropriate TPM register */
tpm_register_put(tpm_reg, data, reg_size);
}
static void i2cs_tpm_init(void)
{
i2cs_register_write_complete_handler(wr_complete_handler);
}
DECLARE_HOOK(HOOK_INIT, i2cs_tpm_init, HOOK_PRIO_LAST);

View File

@@ -411,6 +411,7 @@ 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,
@@ -418,8 +419,23 @@ void fifo_reg_read(uint8_t *dest, uint32_t data_size)
data_size);
tpm_.fifo_read_index += data_size;
if (tpm_.fifo_write_index == tpm_.fifo_read_index)
tpm_.regs.sts &= ~(data_avail | command_ready);
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);
/* Birst 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;
}
@@ -513,6 +529,11 @@ static void tpm_init(void)
_plat__SetNvAvail();
}
size_t tpm_get_burst_size(void)
{
return (tpm_.regs.sts >> burst_count_shift) & burst_count_mask;
}
#ifdef CONFIG_EXTENSION_COMMAND
static void call_extension_command(struct tpm_cmd_header *tpmh,
@@ -579,6 +600,7 @@ void tpm_task(void)
CPRINTF("got %d bytes in response\n", response_size);
if (response_size &&
(response_size <= sizeof(tpm_.regs.data_fifo))) {
uint32_t tpm_sts;
/*
* TODO(vbendeb): revisit this when
* crosbug.com/p/55667 has been addressed.
@@ -598,8 +620,12 @@ void tpm_task(void)
}
tpm_.fifo_read_index = 0;
tpm_.fifo_write_index = response_size;
tpm_.regs.sts |= data_avail;
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;
}
}
}

View File

@@ -1830,6 +1830,8 @@
/* Speak the TPM SPI Hardware Protocol on the SPI slave interface */
#undef CONFIG_TPM_SPS
/* Speak to the TPM 2.0 hardware protocol on the I2C slave interface */
#undef CONFIG_TPM_I2CS
/*****************************************************************************/
/* USART stream config */

View File

@@ -26,6 +26,9 @@ void tpm_register_get(uint32_t regaddr, uint8_t *dest, uint32_t data_size);
/* Enable SPS TPM driver. */
void sps_tpm_enable(void);
/* Get the current value of the burst size field of the status register. */
size_t tpm_get_burst_size(void);
/*
* This structure describes the header of all commands and responses sent and
* received over TPM FIFO.