From f3d483ad3e662362a2916e211231c4d40251448b Mon Sep 17 00:00:00 2001 From: Mary Ruthven Date: Tue, 5 Jan 2021 18:11:38 -0800 Subject: coil: rename i2cs files to i2cp BUG=b:175244613 TEST=make buildall -j Change-Id: Iea0b26d4aec99509bc2db0ccc3ad8da701d63e79 Signed-off-by: Mary Ruthven Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2613505 --- board/cr50/board.c | 2 +- chip/g/build.mk | 2 +- chip/g/i2cp.c | 469 +++++++++++++++++++++++++++++++++++++++++++++++++++++ chip/g/i2cp.h | 64 ++++++++ chip/g/i2cs.c | 469 ----------------------------------------------------- chip/g/i2cs.h | 64 -------- common/build.mk | 2 +- common/i2cp_tpm.c | 290 +++++++++++++++++++++++++++++++++ common/i2cs_tpm.c | 290 --------------------------------- 9 files changed, 826 insertions(+), 826 deletions(-) create mode 100644 chip/g/i2cp.c create mode 100644 chip/g/i2cp.h delete mode 100644 chip/g/i2cs.c delete mode 100644 chip/g/i2cs.h create mode 100644 common/i2cp_tpm.c delete mode 100644 common/i2cs_tpm.c diff --git a/board/cr50/board.c b/board/cr50/board.c index 6be3dff779..6968ac44bb 100644 --- a/board/cr50/board.c +++ b/board/cr50/board.c @@ -19,7 +19,7 @@ #include "ite_sync.h" #include "hooks.h" #include "i2c.h" -#include "i2cs.h" +#include "i2cp.h" #include "init_chip.h" #include "nvmem.h" #include "nvmem_vars.h" diff --git a/chip/g/build.mk b/chip/g/build.mk index 05e1827a8f..ff767851e5 100644 --- a/chip/g/build.mk +++ b/chip/g/build.mk @@ -83,7 +83,7 @@ chip-$(CONFIG_RBOX)+=rbox.o chip-$(CONFIG_STREAM_USB)+=usb-stream.o chip-$(CONFIG_STREAM_USART)+=usart.o chip-$(CONFIG_I2C_CONTROLLER)+= i2cm.o -chip-$(CONFIG_I2C_PERIPH)+= i2cs.o +chip-$(CONFIG_I2C_PERIPH)+= i2cp.o chip-$(CONFIG_LOW_POWER_IDLE)+=idle.o diff --git a/chip/g/i2cp.c b/chip/g/i2cp.c new file mode 100644 index 0000000000..801ce05968 --- /dev/null +++ b/chip/g/i2cp.c @@ -0,0 +1,469 @@ +/* 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. + */ + +/* + * This is a driver for the I2C peripheral (i2cp) of the g chip. + * + * The driver 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 quantities, so the driver + * must provide adaptation to concatenate messages with sizes not 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 driver accessed the file last, comparing it + * with its previous 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 associated + * with it showing where was the latest BIT the driver transmitted. + * + * The driver can generate interrupts on three different conditions: + * - beginning of a read cycle + * - end of a read cycle + * - end of a write cycle + * + * Since this driver's major role is to serve as a TPM interface, it is safe + * 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 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 single byte payload, a read transaction + * would follow immediately. + * + * 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 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. + * On read accesses the callback function retrieves data from the TPM and puts + * it into the read register file to be available to the master to retrieve in + * the following read access. In both cases the callback function completes + * processing on the invoking interrupt context. + * + * The driver API consists of two functions, one to register the callback to + * process interrupts, another one - to add a byte to the master read register + * file. See the accompanying .h file for details. + * + * TODO: + * - figure out flow control - clock stretching can be challenging with this + * driver. + * - detect and recover from overflow/underflow situations + */ + +#include "common.h" +#include "console.h" +#include "flash_log.h" +#include "gpio.h" +#include "hooks.h" +#include "i2cp.h" +#include "pmu.h" +#include "registers.h" +#include "system.h" +#include "task.h" + +#define REGISTER_FILE_SIZE BIT(6) /* 64 bytes. */ +#define REGISTER_FILE_MASK (REGISTER_FILE_SIZE - 1) + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_I2C, outstr) +#define CPRINTF(format, args...) cprints(CC_I2C, format, ## args) + +/* Pointer to the function to invoke on the write complete interrupts. */ +static wr_complete_handler_f write_complete_handler_; + +/* A buffer to normalize the received data to pass it to the user. */ +static uint8_t i2cp_buffer[REGISTER_FILE_SIZE]; + +/* + * Pointer where the CPU stopped retrieving the write data sent by the master + * last time the write access was processed. + */ +static uint16_t last_write_pointer; + +/* + * Pointer where the CPU stopped writing data for the master to read last time + * the read data was prepared. + */ +static uint16_t last_read_pointer; + +/* + * Keep track number of times the "hosed slave" condition was encountered. + */ +static uint16_t i2cp_read_recovery_count; +static uint16_t i2cp_sda_low_count; + +static void check_i2cp_state(void) +{ + if (gpio_get_level(GPIO_MONITOR_I2CP_SDA)) + return; + + /* + * The bus might be stuck; + * Generate a stop sequence to unwedge. + */ + board_unwedge_i2cp(); +} + +static void i2cp_init(void) +{ + + /* First decide if i2c is even needed for this platform. */ + /* if (i2cp is not needed) return; */ + if (!board_tpm_uses_i2c()) + return; + + pmu_clock_en(PERIPH_I2CP); + + memset(i2cp_buffer, 0, sizeof(i2cp_buffer)); + + i2cp_set_pinmux(); + + check_i2cp_state(); + + /* Reset read and write pointers. */ + last_write_pointer = 0; + last_read_pointer = 0; + i2cp_sda_low_count = 0; + GWRITE(I2CS, READ_PTR, 0); + GWRITE(I2CS, WRITE_PTR, 0); + + /* Just in case we were wedged and the master starts with a read. */ + *GREG32_ADDR(I2CS, READ_BUFFER0) = ~0; + + /* Enable I2CP interrupt */ + GWRITE_FIELD(I2CS, INT_ENABLE, INTR_WRITE_COMPLETE, 1); + + /* Slave address is hardcoded to 0x50. */ + GWRITE(I2CS, SLAVE_DEVADDRVAL, 0x50); +} + +/* Forward declaration of the hook function. */ +static void poll_read_state(void); +DECLARE_DEFERRED(poll_read_state); + +/* Interval to poll SDA line when detecting the "hosed" condition. This value + * must be larger then the maximum i2c transaction time. They are normally less + * than 1 ms. The value multiplied by the threshold must also be larger than + * the ap_is_on debounce time, which is 2 seconds. + */ +#define READ_STATUS_CHECK_INTERVAL (700 * MSEC) + +/* Number of times SDA must be low between i2c writes before the i2cp driver + * is restarted. + * + * Three was chosen because we can have two i2c transactions in between write + * complete interrupts. + * + * Consider the following timeline: + * 1) START STOP + * 2) Write complete handler runs (i2cp_sda_low_count = 0) + * 3) START + STOP (i2cp_sda_low_count++) + * 4) START + STOP (i2cp_sda_low_count++) + * 5) Write complete handler runs + * + * If the poller happened to run during time 3 and time 4 while SDA was low, + * i2cp_sda_low_count would = 2. This is not considered an error case. If we + * were to see a third low value before time 5, we can assume the bus is stuck, + * or the master performed multiple reads between writes (which is not + * expected). + * + * If we were to enable the read complete interrupt and use it to clear + * i2cp_sda_low_count we could get away with a threshold of two. This would also + * support multiple reads after a write. + * + * We could in theory use the FIFO read/write pointers to determine if the bus + * is stuck. This was not chosen because we would need to take the following + * into account: + * 1) The poller could run at time 3 between the final ACK bit being asserted + * and the stop condition happening. This would not increment any pointers. + * 2) The poller could run at time 4 between the start condition and the first + * data byte being ACKed. The write pointer can only address full bytes, + * unlike the read pointer. + * These two edge cases would force us to poll at least three times. + */ +#define READ_STATUS_CHECK_THRESHOLD 3 + +/* + * Restart the i2cp driver if the driver gets stuck transmitting a 0 on + * SDA. + * + * This can happen anytime the i2cp driver has control of SDA and the master + * happens to fail and stops clocking. + * + * For example when the i2cp driver is: + * 1) Transmitting an ACK for the slave address byte. + * 2) Transmitting an ACK for a write transaction. + * 3) Transmitting byte data for a read transaction. + * + * The reason this is problematic is because the master can't recover the bus + * by issuing a new transaction. A start condition is defined as the master + * pulling SDA low while SCL is high. The master can only initiate the start + * condition when the bus is free (i.e., SDA is high), otherwise the master + * thinks that it lost arbitration. + * + * We don't have to deal with the scenario where the driver gets stuck + * transmitting a 1 on SDA since the master can recover the bus by issuing a + * normal transaction. The master will at minimum clock 9 times on any + * transaction. This is enough for the slave to complete its current operation + * and NACK. + */ +static void poll_read_state(void) +{ + if (!ap_is_on() || gpio_get_level(GPIO_I2CP_SDA)) { + /* + * When the AP is off, the SDA line might drop low since the + * pull ups might not be powered. + * + * If the AP is on, the bus is either idle, the master has + * stopped clocking while SDA is high, or we have polled in the + * middle of a transaction where SDA happens to be high. + */ + i2cp_sda_low_count = 0; + } else { + /* + * The master has stopped clocking while the slave is holding + * SDA low, or we have polled in the middle of a transaction + * where SDA happens to be low. + */ + i2cp_sda_low_count++; + + /* + * SDA line has been stuck low without any write transactions + * occurring. We will assume the driver is stuck. + * Reinitialize the i2c driver (which will also restart this + * polling function). + */ + if (i2cp_sda_low_count == READ_STATUS_CHECK_THRESHOLD) { + i2cp_sda_low_count = 0; + i2cp_read_recovery_count++; + CPRINTF("I2CP bus is stuck"); + /* + * i2cp_register_write_complete_handler will call + * hook_call_deferred. + */ + i2cp_register_write_complete_handler( + write_complete_handler_); + +#ifdef CONFIG_FLASH_LOG + flash_log_add_event(FE_TPM_I2C_ERROR, 0, NULL); +#endif + return; + } + } + + hook_call_deferred(&poll_read_state_data, READ_STATUS_CHECK_INTERVAL); +} + +/* Process the 'end of a write cycle' interrupt. */ +void __attribute__((used)) _i2cp_write_complete_int(void) +{ + /* Reset the IRQ condition. */ + GWRITE_FIELD(I2CS, INT_STATE, INTR_WRITE_COMPLETE, 1); + + /* We're receiving some bytes, so don't sleep */ + disable_sleep(SLEEP_MASK_I2C_PERIPH); + + if (write_complete_handler_) { + uint16_t bytes_written; + uint16_t bytes_processed; + uint32_t word_in_value = 0; + + /* How many bytes has the master just written. */ + bytes_written = ((uint16_t)GREAD(I2CS, WRITE_PTR) - + last_write_pointer) & REGISTER_FILE_MASK; + + /* How many have been processed yet. */ + bytes_processed = 0; + + /* Make sure we start with something. */ + if (last_write_pointer & 3) + word_in_value = *(GREG32_ADDR(I2CS, WRITE_BUFFER0) + + (last_write_pointer >> 2)); + while (bytes_written != bytes_processed) { + /* + * This loop iterates over bytes retrieved from the + * 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. + */ + + if (!(last_write_pointer & 3)) + /* Time to get a new value. */ + word_in_value = *(GREG32_ADDR( + I2CS, WRITE_BUFFER0) + + (last_write_pointer >> 2)); + + /* Save the next byte in the adaptation buffer. */ + i2cp_buffer[bytes_processed] = + word_in_value >> (8 * (last_write_pointer & 3)); + + /* The pointer wraps at the register file size. */ + last_write_pointer = (last_write_pointer + 1) & + REGISTER_FILE_MASK; + bytes_processed++; + } + + /* Invoke the callback to process the message. */ + write_complete_handler_(i2cp_buffer, bytes_processed); + } + + /* The transaction is complete so the slave has released SDA. */ + i2cp_sda_low_count = 0; + + /* + * Could be the end of a TPM trasaction. Set sleep to be reenabled in 1 + * second. If this is not the end of a TPM response, then sleep will be + * disabled again in the next I2CP interrupt. + */ + delay_sleep_by(1 * SECOND); + enable_sleep(SLEEP_MASK_I2C_PERIPH); +} +DECLARE_IRQ(GC_IRQNUM_I2CS0_INTR_WRITE_COMPLETE_INT, + _i2cp_write_complete_int, 1); + +void i2cp_post_read_data(uint8_t byte_to_read) +{ + volatile uint32_t *value_addr; + uint32_t word_out_value; + uint32_t shift; + + /* + * Find out which register of the register file the byte needs to go + * to. + */ + value_addr = GREG32_ADDR(I2CS, READ_BUFFER0) + (last_read_pointer >> 2); + + /* Read-modify-write the register adding the new byte there. */ + word_out_value = *value_addr; + shift = (last_read_pointer & 3) * 8; + word_out_value = (word_out_value & ~(0xff << shift)) | + (((uint32_t)byte_to_read) << shift); + *value_addr = word_out_value; + last_read_pointer = (last_read_pointer + 1) & REGISTER_FILE_MASK; +} + +void i2cp_post_read_fill_fifo(uint8_t *buffer, size_t len) +{ + volatile uint32_t *value_addr; + uint32_t word_out_value; + uint32_t addr_offset; + uint32_t remainder_bytes; + uint32_t start_offset; + uint32_t num_words; + int i, j; + + /* Get offset into 1st fifo word*/ + start_offset = last_read_pointer & 0x3; + /* Number of bytes to fill out 1st word */ + remainder_bytes = (4 - start_offset) & 0x3; + /* Get pointer to base of fifo and offset */ + addr_offset = last_read_pointer >> 2; + value_addr = GREG32_ADDR(I2CS, READ_BUFFER0); + /* Update read_pointer to reflect final value */ + last_read_pointer = (last_read_pointer + len) & + REGISTER_FILE_MASK; + + /* Insert bytes until fifo is word aligned */ + if (remainder_bytes) { + /* mask the bytes to be kept */ + word_out_value = value_addr[addr_offset]; + word_out_value &= (1 << (8 * start_offset)) - 1; + /* Write in remainder bytes */ + for (i = 0; i < remainder_bytes; i++) + word_out_value |= *buffer++ << (8 * (start_offset + i)); + /* Write to fifo register */ + value_addr[addr_offset] = word_out_value; + addr_offset = (addr_offset + 1) & (REGISTER_FILE_MASK >> 2); + /* Account for bytes consumed */ + len -= remainder_bytes; + } + + /* HW fifo is now word aligned */ + num_words = len >> 2; + for (i = 0; i < num_words; i++) { + word_out_value = 0; + for (j = 0; j < 4; j++) + word_out_value |= *buffer++ << (j * 8); + /* Write word to fifo and update fifo offset */ + value_addr[addr_offset] = word_out_value; + addr_offset = (addr_offset + 1) & (REGISTER_FILE_MASK >> 2); + } + len -= (num_words << 2); + + /* Now process remaining bytes (if any), will be <= 3 at this point */ + remainder_bytes = len; + if (remainder_bytes) { + /* read from HW fifo */ + word_out_value = value_addr[addr_offset]; + /* Mask bytes that need to be kept */ + word_out_value &= (0xffffffff << (8 * remainder_bytes)); + for (i = 0; i < remainder_bytes; i++) + word_out_value |= *buffer++ << (8 * i); + value_addr[addr_offset] = word_out_value; + } +} + +int i2cp_register_write_complete_handler(wr_complete_handler_f wc_handler) +{ + task_disable_irq(GC_IRQNUM_I2CS0_INTR_WRITE_COMPLETE_INT); + + if (!wc_handler) + return 0; + + i2cp_init(); + write_complete_handler_ = wc_handler; + task_enable_irq(GC_IRQNUM_I2CS0_INTR_WRITE_COMPLETE_INT); + + /* + * Start a self perpetuating polling function to check for 'hosed' + * condition periodically. + */ + hook_call_deferred(&poll_read_state_data, READ_STATUS_CHECK_INTERVAL); + + return 0; +} + +size_t i2cp_zero_read_fifo_buffer_depth(void) +{ + uint32_t hw_read_pointer; + size_t depth; + + /* + * Get the current value of the HW I2CS read pointer. Note that the read + * pointer is b8:b3 of the I2CS_READ_PTR register. The lower 3 bits of + * this register are used to support bit accesses by a host. + */ + hw_read_pointer = GREAD(I2CS, READ_PTR) >> 3; + /* Determine the number of bytes buffered in the HW fifo */ + depth = (last_read_pointer - hw_read_pointer) & REGISTER_FILE_MASK; + /* + * If queue depth is not zero, force it to 0 by adjusting + * last_read_pointer to where the hw read pointer is. + */ + if (depth) + last_read_pointer = (uint16_t)hw_read_pointer; + /* + * Return number of bytes queued when this funciton is called so it can + * be tracked or logged by caller if desired. + */ + return depth; +} + +void i2cp_get_status(struct i2cp_status *status) +{ + status->read_recovery_count = i2cp_read_recovery_count; +} diff --git a/chip/g/i2cp.h b/chip/g/i2cp.h new file mode 100644 index 0000000000..5ab7286fc7 --- /dev/null +++ b/chip/g/i2cp.h @@ -0,0 +1,64 @@ +/* + * 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. + */ +#ifndef __CHIP_G_I2CP_H +#define __CHIP_G_I2CP_H + +#include + +/* + * Write complete interrupt callback function prototype. This function expects + * two parameters: the address of the buffer containing received data and + * number of bytes in the buffer. + */ +typedef void (*wr_complete_handler_f)(void *i2cp_data, size_t i2cp_data_size); + +/* Register the write complete interrupt handler. */ +int i2cp_register_write_complete_handler(wr_complete_handler_f wc_handler); + +/* + * Post a byte for the master to read. Blend the byte into the appropriate + * 4byte register of the master read register file. + */ +void i2cp_post_read_data(uint8_t byte_to_read); + +/* + * Configure the pinmux registers required to connect the I2CP interface. This + * function is board specific and so it exists in the associated board.c file. + */ +void i2cp_set_pinmux(void); + +/* + * Ensure no bytes are currently buffered in the I2CP READ fifo. This + * value is calculated by finding the difference between read pointer that's + * used by FW to add bytes to the HW fifo and the current value of the + * I2CS_READ_PTR register. + * + * @returns: the number of bytes buffered when the function is called + */ +size_t i2cp_zero_read_fifo_buffer_depth(void); + +/* + * Write buffer of data into the I2CS HW read fifo. The function will operate a + * byte at a time until the fifo write pointer is word aligned. Then it will + * consume all remaining words of input data. There is another stage to handle + * any excess bytes. The efficiency benefits relative the byte at a time + * function diminish as the buffer size gets smaller and therefore not intended + * to be used for <= 4 byte buffers. + */ +void i2cp_post_read_fill_fifo(uint8_t *buffer, size_t len); + +/* + * Provide upper layers with information with the I2CP interface + * status/statistics. The only piece of information currently provided is the + * counter of "hosed" i2c interface occurences, where i2c clocking stopped + * while slave was transmitting a zero. + */ +struct i2cp_status { + uint16_t read_recovery_count; +}; +void i2cp_get_status(struct i2cp_status *status); + +#endif /* ! __CHIP_G_I2CP_H */ diff --git a/chip/g/i2cs.c b/chip/g/i2cs.c deleted file mode 100644 index f301878235..0000000000 --- a/chip/g/i2cs.c +++ /dev/null @@ -1,469 +0,0 @@ -/* 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. - */ - -/* - * This is a driver for the I2C peripheral (i2cp) of the g chip. - * - * The driver 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 quantities, so the driver - * must provide adaptation to concatenate messages with sizes not 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 driver accessed the file last, comparing it - * with its previous 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 associated - * with it showing where was the latest BIT the driver transmitted. - * - * The driver can generate interrupts on three different conditions: - * - beginning of a read cycle - * - end of a read cycle - * - end of a write cycle - * - * Since this driver's major role is to serve as a TPM interface, it is safe - * 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 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 single byte payload, a read transaction - * would follow immediately. - * - * 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 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. - * On read accesses the callback function retrieves data from the TPM and puts - * it into the read register file to be available to the master to retrieve in - * the following read access. In both cases the callback function completes - * processing on the invoking interrupt context. - * - * The driver API consists of two functions, one to register the callback to - * process interrupts, another one - to add a byte to the master read register - * file. See the accompanying .h file for details. - * - * TODO: - * - figure out flow control - clock stretching can be challenging with this - * driver. - * - detect and recover from overflow/underflow situations - */ - -#include "common.h" -#include "console.h" -#include "flash_log.h" -#include "gpio.h" -#include "hooks.h" -#include "i2cs.h" -#include "pmu.h" -#include "registers.h" -#include "system.h" -#include "task.h" - -#define REGISTER_FILE_SIZE BIT(6) /* 64 bytes. */ -#define REGISTER_FILE_MASK (REGISTER_FILE_SIZE - 1) - -/* Console output macros */ -#define CPUTS(outstr) cputs(CC_I2C, outstr) -#define CPRINTF(format, args...) cprints(CC_I2C, format, ## args) - -/* Pointer to the function to invoke on the write complete interrupts. */ -static wr_complete_handler_f write_complete_handler_; - -/* A buffer to normalize the received data to pass it to the user. */ -static uint8_t i2cp_buffer[REGISTER_FILE_SIZE]; - -/* - * Pointer where the CPU stopped retrieving the write data sent by the master - * last time the write access was processed. - */ -static uint16_t last_write_pointer; - -/* - * Pointer where the CPU stopped writing data for the master to read last time - * the read data was prepared. - */ -static uint16_t last_read_pointer; - -/* - * Keep track number of times the "hosed slave" condition was encountered. - */ -static uint16_t i2cp_read_recovery_count; -static uint16_t i2cp_sda_low_count; - -static void check_i2cp_state(void) -{ - if (gpio_get_level(GPIO_MONITOR_I2CP_SDA)) - return; - - /* - * The bus might be stuck; - * Generate a stop sequence to unwedge. - */ - board_unwedge_i2cp(); -} - -static void i2cp_init(void) -{ - - /* First decide if i2c is even needed for this platform. */ - /* if (i2cp is not needed) return; */ - if (!board_tpm_uses_i2c()) - return; - - pmu_clock_en(PERIPH_I2CP); - - memset(i2cp_buffer, 0, sizeof(i2cp_buffer)); - - i2cp_set_pinmux(); - - check_i2cp_state(); - - /* Reset read and write pointers. */ - last_write_pointer = 0; - last_read_pointer = 0; - i2cp_sda_low_count = 0; - GWRITE(I2CS, READ_PTR, 0); - GWRITE(I2CS, WRITE_PTR, 0); - - /* Just in case we were wedged and the master starts with a read. */ - *GREG32_ADDR(I2CS, READ_BUFFER0) = ~0; - - /* Enable I2CP interrupt */ - GWRITE_FIELD(I2CS, INT_ENABLE, INTR_WRITE_COMPLETE, 1); - - /* Slave address is hardcoded to 0x50. */ - GWRITE(I2CS, SLAVE_DEVADDRVAL, 0x50); -} - -/* Forward declaration of the hook function. */ -static void poll_read_state(void); -DECLARE_DEFERRED(poll_read_state); - -/* Interval to poll SDA line when detecting the "hosed" condition. This value - * must be larger then the maximum i2c transaction time. They are normally less - * than 1 ms. The value multiplied by the threshold must also be larger than - * the ap_is_on debounce time, which is 2 seconds. - */ -#define READ_STATUS_CHECK_INTERVAL (700 * MSEC) - -/* Number of times SDA must be low between i2c writes before the i2cp driver - * is restarted. - * - * Three was chosen because we can have two i2c transactions in between write - * complete interrupts. - * - * Consider the following timeline: - * 1) START STOP - * 2) Write complete handler runs (i2cp_sda_low_count = 0) - * 3) START + STOP (i2cp_sda_low_count++) - * 4) START + STOP (i2cp_sda_low_count++) - * 5) Write complete handler runs - * - * If the poller happened to run during time 3 and time 4 while SDA was low, - * i2cp_sda_low_count would = 2. This is not considered an error case. If we - * were to see a third low value before time 5, we can assume the bus is stuck, - * or the master performed multiple reads between writes (which is not - * expected). - * - * If we were to enable the read complete interrupt and use it to clear - * i2cp_sda_low_count we could get away with a threshold of two. This would also - * support multiple reads after a write. - * - * We could in theory use the FIFO read/write pointers to determine if the bus - * is stuck. This was not chosen because we would need to take the following - * into account: - * 1) The poller could run at time 3 between the final ACK bit being asserted - * and the stop condition happening. This would not increment any pointers. - * 2) The poller could run at time 4 between the start condition and the first - * data byte being ACKed. The write pointer can only address full bytes, - * unlike the read pointer. - * These two edge cases would force us to poll at least three times. - */ -#define READ_STATUS_CHECK_THRESHOLD 3 - -/* - * Restart the i2cp driver if the driver gets stuck transmitting a 0 on - * SDA. - * - * This can happen anytime the i2cp driver has control of SDA and the master - * happens to fail and stops clocking. - * - * For example when the i2cp driver is: - * 1) Transmitting an ACK for the slave address byte. - * 2) Transmitting an ACK for a write transaction. - * 3) Transmitting byte data for a read transaction. - * - * The reason this is problematic is because the master can't recover the bus - * by issuing a new transaction. A start condition is defined as the master - * pulling SDA low while SCL is high. The master can only initiate the start - * condition when the bus is free (i.e., SDA is high), otherwise the master - * thinks that it lost arbitration. - * - * We don't have to deal with the scenario where the driver gets stuck - * transmitting a 1 on SDA since the master can recover the bus by issuing a - * normal transaction. The master will at minimum clock 9 times on any - * transaction. This is enough for the slave to complete its current operation - * and NACK. - */ -static void poll_read_state(void) -{ - if (!ap_is_on() || gpio_get_level(GPIO_I2CP_SDA)) { - /* - * When the AP is off, the SDA line might drop low since the - * pull ups might not be powered. - * - * If the AP is on, the bus is either idle, the master has - * stopped clocking while SDA is high, or we have polled in the - * middle of a transaction where SDA happens to be high. - */ - i2cp_sda_low_count = 0; - } else { - /* - * The master has stopped clocking while the slave is holding - * SDA low, or we have polled in the middle of a transaction - * where SDA happens to be low. - */ - i2cp_sda_low_count++; - - /* - * SDA line has been stuck low without any write transactions - * occurring. We will assume the driver is stuck. - * Reinitialize the i2c driver (which will also restart this - * polling function). - */ - if (i2cp_sda_low_count == READ_STATUS_CHECK_THRESHOLD) { - i2cp_sda_low_count = 0; - i2cp_read_recovery_count++; - CPRINTF("I2CP bus is stuck"); - /* - * i2cp_register_write_complete_handler will call - * hook_call_deferred. - */ - i2cp_register_write_complete_handler( - write_complete_handler_); - -#ifdef CONFIG_FLASH_LOG - flash_log_add_event(FE_TPM_I2C_ERROR, 0, NULL); -#endif - return; - } - } - - hook_call_deferred(&poll_read_state_data, READ_STATUS_CHECK_INTERVAL); -} - -/* Process the 'end of a write cycle' interrupt. */ -void __attribute__((used)) _i2cp_write_complete_int(void) -{ - /* Reset the IRQ condition. */ - GWRITE_FIELD(I2CS, INT_STATE, INTR_WRITE_COMPLETE, 1); - - /* We're receiving some bytes, so don't sleep */ - disable_sleep(SLEEP_MASK_I2C_PERIPH); - - if (write_complete_handler_) { - uint16_t bytes_written; - uint16_t bytes_processed; - uint32_t word_in_value = 0; - - /* How many bytes has the master just written. */ - bytes_written = ((uint16_t)GREAD(I2CS, WRITE_PTR) - - last_write_pointer) & REGISTER_FILE_MASK; - - /* How many have been processed yet. */ - bytes_processed = 0; - - /* Make sure we start with something. */ - if (last_write_pointer & 3) - word_in_value = *(GREG32_ADDR(I2CS, WRITE_BUFFER0) + - (last_write_pointer >> 2)); - while (bytes_written != bytes_processed) { - /* - * This loop iterates over bytes retrieved from the - * 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. - */ - - if (!(last_write_pointer & 3)) - /* Time to get a new value. */ - word_in_value = *(GREG32_ADDR( - I2CS, WRITE_BUFFER0) + - (last_write_pointer >> 2)); - - /* Save the next byte in the adaptation buffer. */ - i2cp_buffer[bytes_processed] = - word_in_value >> (8 * (last_write_pointer & 3)); - - /* The pointer wraps at the register file size. */ - last_write_pointer = (last_write_pointer + 1) & - REGISTER_FILE_MASK; - bytes_processed++; - } - - /* Invoke the callback to process the message. */ - write_complete_handler_(i2cp_buffer, bytes_processed); - } - - /* The transaction is complete so the slave has released SDA. */ - i2cp_sda_low_count = 0; - - /* - * Could be the end of a TPM trasaction. Set sleep to be reenabled in 1 - * second. If this is not the end of a TPM response, then sleep will be - * disabled again in the next I2CP interrupt. - */ - delay_sleep_by(1 * SECOND); - enable_sleep(SLEEP_MASK_I2C_PERIPH); -} -DECLARE_IRQ(GC_IRQNUM_I2CS0_INTR_WRITE_COMPLETE_INT, - _i2cp_write_complete_int, 1); - -void i2cp_post_read_data(uint8_t byte_to_read) -{ - volatile uint32_t *value_addr; - uint32_t word_out_value; - uint32_t shift; - - /* - * Find out which register of the register file the byte needs to go - * to. - */ - value_addr = GREG32_ADDR(I2CS, READ_BUFFER0) + (last_read_pointer >> 2); - - /* Read-modify-write the register adding the new byte there. */ - word_out_value = *value_addr; - shift = (last_read_pointer & 3) * 8; - word_out_value = (word_out_value & ~(0xff << shift)) | - (((uint32_t)byte_to_read) << shift); - *value_addr = word_out_value; - last_read_pointer = (last_read_pointer + 1) & REGISTER_FILE_MASK; -} - -void i2cp_post_read_fill_fifo(uint8_t *buffer, size_t len) -{ - volatile uint32_t *value_addr; - uint32_t word_out_value; - uint32_t addr_offset; - uint32_t remainder_bytes; - uint32_t start_offset; - uint32_t num_words; - int i, j; - - /* Get offset into 1st fifo word*/ - start_offset = last_read_pointer & 0x3; - /* Number of bytes to fill out 1st word */ - remainder_bytes = (4 - start_offset) & 0x3; - /* Get pointer to base of fifo and offset */ - addr_offset = last_read_pointer >> 2; - value_addr = GREG32_ADDR(I2CS, READ_BUFFER0); - /* Update read_pointer to reflect final value */ - last_read_pointer = (last_read_pointer + len) & - REGISTER_FILE_MASK; - - /* Insert bytes until fifo is word aligned */ - if (remainder_bytes) { - /* mask the bytes to be kept */ - word_out_value = value_addr[addr_offset]; - word_out_value &= (1 << (8 * start_offset)) - 1; - /* Write in remainder bytes */ - for (i = 0; i < remainder_bytes; i++) - word_out_value |= *buffer++ << (8 * (start_offset + i)); - /* Write to fifo register */ - value_addr[addr_offset] = word_out_value; - addr_offset = (addr_offset + 1) & (REGISTER_FILE_MASK >> 2); - /* Account for bytes consumed */ - len -= remainder_bytes; - } - - /* HW fifo is now word aligned */ - num_words = len >> 2; - for (i = 0; i < num_words; i++) { - word_out_value = 0; - for (j = 0; j < 4; j++) - word_out_value |= *buffer++ << (j * 8); - /* Write word to fifo and update fifo offset */ - value_addr[addr_offset] = word_out_value; - addr_offset = (addr_offset + 1) & (REGISTER_FILE_MASK >> 2); - } - len -= (num_words << 2); - - /* Now process remaining bytes (if any), will be <= 3 at this point */ - remainder_bytes = len; - if (remainder_bytes) { - /* read from HW fifo */ - word_out_value = value_addr[addr_offset]; - /* Mask bytes that need to be kept */ - word_out_value &= (0xffffffff << (8 * remainder_bytes)); - for (i = 0; i < remainder_bytes; i++) - word_out_value |= *buffer++ << (8 * i); - value_addr[addr_offset] = word_out_value; - } -} - -int i2cp_register_write_complete_handler(wr_complete_handler_f wc_handler) -{ - task_disable_irq(GC_IRQNUM_I2CS0_INTR_WRITE_COMPLETE_INT); - - if (!wc_handler) - return 0; - - i2cp_init(); - write_complete_handler_ = wc_handler; - task_enable_irq(GC_IRQNUM_I2CS0_INTR_WRITE_COMPLETE_INT); - - /* - * Start a self perpetuating polling function to check for 'hosed' - * condition periodically. - */ - hook_call_deferred(&poll_read_state_data, READ_STATUS_CHECK_INTERVAL); - - return 0; -} - -size_t i2cp_zero_read_fifo_buffer_depth(void) -{ - uint32_t hw_read_pointer; - size_t depth; - - /* - * Get the current value of the HW I2CS read pointer. Note that the read - * pointer is b8:b3 of the I2CS_READ_PTR register. The lower 3 bits of - * this register are used to support bit accesses by a host. - */ - hw_read_pointer = GREAD(I2CS, READ_PTR) >> 3; - /* Determine the number of bytes buffered in the HW fifo */ - depth = (last_read_pointer - hw_read_pointer) & REGISTER_FILE_MASK; - /* - * If queue depth is not zero, force it to 0 by adjusting - * last_read_pointer to where the hw read pointer is. - */ - if (depth) - last_read_pointer = (uint16_t)hw_read_pointer; - /* - * Return number of bytes queued when this funciton is called so it can - * be tracked or logged by caller if desired. - */ - return depth; -} - -void i2cp_get_status(struct i2cp_status *status) -{ - status->read_recovery_count = i2cp_read_recovery_count; -} diff --git a/chip/g/i2cs.h b/chip/g/i2cs.h deleted file mode 100644 index a3ca8641a6..0000000000 --- a/chip/g/i2cs.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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. - */ -#ifndef __CHIP_G_I2CS_H -#define __CHIP_G_I2CS_H - -#include - -/* - * Write complete interrupt callback function prototype. This function expects - * two parameters: the address of the buffer containing received data and - * number of bytes in the buffer. - */ -typedef void (*wr_complete_handler_f)(void *i2cp_data, size_t i2cp_data_size); - -/* Register the write complete interrupt handler. */ -int i2cp_register_write_complete_handler(wr_complete_handler_f wc_handler); - -/* - * Post a byte for the master to read. Blend the byte into the appropriate - * 4byte register of the master read register file. - */ -void i2cp_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 i2cp_set_pinmux(void); - -/* - * Ensure no bytes are currently buffered in the I2CS READ fifo. This - * value is calculated by finding the difference between read pointer that's - * used by FW to add bytes to the HW fifo and the current value of the - * I2CS_READ_PTR register. - * - * @returns: the number of bytes buffered when the function is called - */ -size_t i2cp_zero_read_fifo_buffer_depth(void); - -/* - * Write buffer of data into the I2CS HW read fifo. The function will operate a - * byte at a time until the fifo write pointer is word aligned. Then it will - * consume all remaining words of input data. There is another stage to handle - * any excess bytes. The efficiency benefits relative the byte at a time - * function diminish as the buffer size gets smaller and therefore not intended - * to be used for <= 4 byte buffers. - */ -void i2cp_post_read_fill_fifo(uint8_t *buffer, size_t len); - -/* - * Provide upper layers with information with the I2CS interface - * status/statistics. The only piece of information currently provided is the - * counter of "hosed" i2c interface occurences, where i2c clocking stopped - * while slave was transmitting a zero. - */ -struct i2cp_status { - uint16_t read_recovery_count; -}; -void i2cp_get_status(struct i2cp_status *status); - -#endif /* ! __CHIP_G_I2CS_H */ diff --git a/common/build.mk b/common/build.mk index 79938a7fc8..4ab052d9be 100644 --- a/common/build.mk +++ b/common/build.mk @@ -119,7 +119,7 @@ common-$(CONFIG_TEMP_SENSOR)+=temp_sensor.o common-$(CONFIG_THROTTLE_AP)+=thermal.o throttle_ap.o common-$(CONFIG_THROTTLE_AP_ON_BAT_DISCHG_CURRENT)+=throttle_ap.o common-$(CONFIG_THROTTLE_AP_ON_BAT_VOLTAGE)+=throttle_ap.o -common-$(CONFIG_TPM_I2CP)+=i2cs_tpm.o +common-$(CONFIG_TPM_I2CP)+=i2cp_tpm.o common-$(CONFIG_U2F)+=u2f.o common-$(CONFIG_USB_CONSOLE_STREAM)+=usb_console_stream.o common-$(CONFIG_USB_I2C)+=usb_i2c.o diff --git a/common/i2cp_tpm.c b/common/i2cp_tpm.c new file mode 100644 index 0000000000..48af3a22b3 --- /dev/null +++ b/common/i2cp_tpm.c @@ -0,0 +1,290 @@ +/* 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 "i2cp.h" +#include "registers.h" +#include "system.h" +#include "tpm_registers.h" + +/* + * This implements adaptaition layer between i2cp (i2c periph) port and TPM. + * + * The adaptation layer is stateless, it processes the i2cp "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 */ + {0x1c, 4, 0xfe0}, /* TPM_BOARD_CFG */ +}; + +/* Used to track number of times i2cp hw read fifo was adjusted */ +static uint32_t i2cp_fifo_adjust_count; +/* Used to track number of write mismatch errors */ +static uint32_t i2cp_write_error_count; + +static bool int_ap_extension_enabled_; + +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 i2cp 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 (i2cp_zero_read_fifo_buffer_depth()) + /* Count each instance that fifo was adjusted */ + i2cp_fifo_adjust_count++; + for (i = 0; i < reg_size; i++) + i2cp_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 I2CP HW fifo */ + i2cp_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 i2cp_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, i2cp_data_size); + return; + } + + if (i2cp_data_size != reg_size) { + i2cp_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 *i2cp_data, size_t i2cp_data_size) +{ + size_t i; + uint16_t tpm_reg; + uint8_t *data = i2cp_data; + const struct i2c_tpm_reg_map *i2c_reg_entry = NULL; + uint16_t reg_size; + + if (i2cp_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; + + i2cp_data_size--; + data++; + + if (!i2cp_data_size) + process_read_access(reg_size, tpm_reg, data); + else + process_write_access(reg_size, tpm_reg, + data, i2cp_data_size); + + if (assert_int_ap()) { + gpio_enable_interrupt(GPIO_MONITOR_I2CP_SDA); + return; + } + + /* + * 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); + + tick_delay(2); + + gpio_set_level(GPIO_INT_AP_L, 1); +} + +void i2cp_sda_isr(enum gpio_signal signal) +{ + gpio_disable_interrupt(GPIO_MONITOR_I2CP_SDA); + + deassert_int_ap(); +} + +static void i2cp_if_stop(void) +{ + if (int_ap_extension_enabled_) + int_ap_extension_stop_pulse(); + + i2cp_register_write_complete_handler(NULL); +} + +static void i2cp_if_start(void) +{ + i2cp_register_write_complete_handler(wr_complete_handler); +} + +/* Function that sets up for I2CP to enable INT_AP_L extension. */ +static void i2cp_int_ap_extension_enable_(void) +{ + int_ap_extension_enabled_ = true; +} + +static void i2cp_if_register(void) +{ + if (!board_tpm_uses_i2c()) + return; + + tpm_register_interface(i2cp_if_start, i2cp_if_stop); + i2cp_fifo_adjust_count = 0; + i2cp_write_error_count = 0; + + int_ap_register(i2cp_int_ap_extension_enable_); +} +DECLARE_HOOK(HOOK_INIT, i2cp_if_register, HOOK_PRIO_INIT_CR50_BOARD - 1); + +static int command_i2cp(int argc, char **argv) +{ + static uint16_t base_read_recovery_count; + struct i2cp_status status; + + i2cp_get_status(&status); + + ccprintf("rd fifo adjust cnt = %d\n", i2cp_fifo_adjust_count); + ccprintf("wr mismatch cnt = %d\n", i2cp_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")) { + i2cp_fifo_adjust_count = 0; + i2cp_write_error_count = 0; + base_read_recovery_count = status.read_recovery_count; + ccprintf("i2cp error counts reset\n"); + } else + return EC_ERROR_PARAM1; + + return EC_SUCCESS; +} +DECLARE_SAFE_CONSOLE_COMMAND(i2cptpm, command_i2cp, + "reset", + "Display fifo adjust count"); diff --git a/common/i2cs_tpm.c b/common/i2cs_tpm.c deleted file mode 100644 index 362fc57c4d..0000000000 --- a/common/i2cs_tpm.c +++ /dev/null @@ -1,290 +0,0 @@ -/* 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 i2cp (i2c periph) port and TPM. - * - * The adaptation layer is stateless, it processes the i2cp "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 */ - {0x1c, 4, 0xfe0}, /* TPM_BOARD_CFG */ -}; - -/* Used to track number of times i2cp hw read fifo was adjusted */ -static uint32_t i2cp_fifo_adjust_count; -/* Used to track number of write mismatch errors */ -static uint32_t i2cp_write_error_count; - -static bool int_ap_extension_enabled_; - -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 i2cp 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 (i2cp_zero_read_fifo_buffer_depth()) - /* Count each instance that fifo was adjusted */ - i2cp_fifo_adjust_count++; - for (i = 0; i < reg_size; i++) - i2cp_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 I2CP HW fifo */ - i2cp_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 i2cp_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, i2cp_data_size); - return; - } - - if (i2cp_data_size != reg_size) { - i2cp_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 *i2cp_data, size_t i2cp_data_size) -{ - size_t i; - uint16_t tpm_reg; - uint8_t *data = i2cp_data; - const struct i2c_tpm_reg_map *i2c_reg_entry = NULL; - uint16_t reg_size; - - if (i2cp_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; - - i2cp_data_size--; - data++; - - if (!i2cp_data_size) - process_read_access(reg_size, tpm_reg, data); - else - process_write_access(reg_size, tpm_reg, - data, i2cp_data_size); - - if (assert_int_ap()) { - gpio_enable_interrupt(GPIO_MONITOR_I2CP_SDA); - return; - } - - /* - * 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); - - tick_delay(2); - - gpio_set_level(GPIO_INT_AP_L, 1); -} - -void i2cp_sda_isr(enum gpio_signal signal) -{ - gpio_disable_interrupt(GPIO_MONITOR_I2CP_SDA); - - deassert_int_ap(); -} - -static void i2cp_if_stop(void) -{ - if (int_ap_extension_enabled_) - int_ap_extension_stop_pulse(); - - i2cp_register_write_complete_handler(NULL); -} - -static void i2cp_if_start(void) -{ - i2cp_register_write_complete_handler(wr_complete_handler); -} - -/* Function that sets up for I2CP to enable INT_AP_L extension. */ -static void i2cp_int_ap_extension_enable_(void) -{ - int_ap_extension_enabled_ = true; -} - -static void i2cp_if_register(void) -{ - if (!board_tpm_uses_i2c()) - return; - - tpm_register_interface(i2cp_if_start, i2cp_if_stop); - i2cp_fifo_adjust_count = 0; - i2cp_write_error_count = 0; - - int_ap_register(i2cp_int_ap_extension_enable_); -} -DECLARE_HOOK(HOOK_INIT, i2cp_if_register, HOOK_PRIO_INIT_CR50_BOARD - 1); - -static int command_i2cp(int argc, char **argv) -{ - static uint16_t base_read_recovery_count; - struct i2cp_status status; - - i2cp_get_status(&status); - - ccprintf("rd fifo adjust cnt = %d\n", i2cp_fifo_adjust_count); - ccprintf("wr mismatch cnt = %d\n", i2cp_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")) { - i2cp_fifo_adjust_count = 0; - i2cp_write_error_count = 0; - base_read_recovery_count = status.read_recovery_count; - ccprintf("i2cp error counts reset\n"); - } else - return EC_ERROR_PARAM1; - - return EC_SUCCESS; -} -DECLARE_SAFE_CONSOLE_COMMAND(i2cptpm, command_i2cp, - "reset", - "Display fifo adjust count"); -- cgit v1.2.1