From e4f389a275fb81c112b1186df0465e48ff163cfd Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 2 Aug 2016 12:36:52 -0700 Subject: [PATCH] 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 Tested-by: Scott Collyer Reviewed-by: Bill Richardson --- board/cr50/board.c | 22 ++++ board/cr50/board.h | 4 + board/cr50/gpio.inc | 13 ++- board/cr50/tpm2/endorsement.c | 5 +- chip/g/build.mk | 1 + chip/g/i2cs.c | 48 ++++----- chip/g/i2cs.h | 6 ++ common/build.mk | 3 +- common/i2cs_tpm.c | 191 ++++++++++++++++++++++++++++++++++ common/tpm_registers.c | 32 +++++- include/config.h | 2 + include/tpm_registers.h | 3 + 12 files changed, 295 insertions(+), 35 deletions(-) create mode 100644 common/i2cs_tpm.c diff --git a/board/cr50/board.c b/board/cr50/board.c index eb4ab636a7..a45abcfae1 100644 --- a/board/cr50/board.c +++ b/board/cr50/board.c @@ -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? */ + +} diff --git a/board/cr50/board.h b/board/cr50/board.h index 52e9ea6039..2df8aaba6a 100644 --- a/board/cr50/board.h +++ b/board/cr50/board.h @@ -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 */ diff --git a/board/cr50/gpio.inc b/board/cr50/gpio.inc index 235a21b116..454fe729bc 100644 --- a/board/cr50/gpio.inc +++ b/board/cr50/gpio.inc @@ -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 diff --git a/board/cr50/tpm2/endorsement.c b/board/cr50/tpm2/endorsement.c index 9db7f0f136..c3e818501c 100644 --- a/board/cr50/tpm2/endorsement.c +++ b/board/cr50/tpm2/endorsement.c @@ -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" diff --git a/chip/g/build.mk b/chip/g/build.mk index a1f3cabd4b..9cc0edb8a1 100644 --- a/chip/g/build.mk +++ b/chip/g/build.mk @@ -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 diff --git a/chip/g/i2cs.c b/chip/g/i2cs.c index 5d173e86a7..2a0354005d 100644 --- a/chip/g/i2cs.c +++ b/chip/g/i2cs.c @@ -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. */ diff --git a/chip/g/i2cs.h b/chip/g/i2cs.h index e06faebdcd..6fe3fb2f75 100644 --- a/chip/g/i2cs.h +++ b/chip/g/i2cs.h @@ -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 */ diff --git a/common/build.mk b/common/build.mk index a699637129..44e3e1c33d 100644 --- a/common/build.mk +++ b/common/build.mk @@ -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 diff --git a/common/i2cs_tpm.c b/common/i2cs_tpm.c new file mode 100644 index 0000000000..0e3071b590 --- /dev/null +++ b/common/i2cs_tpm.c @@ -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); diff --git a/common/tpm_registers.c b/common/tpm_registers.c index a7dfa84963..0158e22f2f 100644 --- a/common/tpm_registers.c +++ b/common/tpm_registers.c @@ -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; } } } diff --git a/include/config.h b/include/config.h index f381f264bf..6c192c77eb 100644 --- a/include/config.h +++ b/include/config.h @@ -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 */ diff --git a/include/tpm_registers.h b/include/tpm_registers.h index c7e8dd27a9..3416492f03 100644 --- a/include/tpm_registers.h +++ b/include/tpm_registers.h @@ -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.