diff options
-rw-r--r-- | board/cr50/board.h | 1 | ||||
-rw-r--r-- | chip/g/build.mk | 1 | ||||
-rw-r--r-- | chip/g/sps.c | 8 | ||||
-rw-r--r-- | chip/g/sps.h | 6 | ||||
-rw-r--r-- | chip/g/sps_tpm.c | 282 | ||||
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | common/tpm_registers.c | 166 | ||||
-rw-r--r-- | include/config.h | 6 | ||||
-rw-r--r-- | include/tpm_registers.h | 24 |
9 files changed, 492 insertions, 3 deletions
diff --git a/board/cr50/board.h b/board/cr50/board.h index 5cd5c0f621..3d94b4cb5d 100644 --- a/board/cr50/board.h +++ b/board/cr50/board.h @@ -27,6 +27,7 @@ /* Enable SPI Slave (SPS) module */ #define CONFIG_SPS #define CONFIG_HOSTCMD_SPS +#define CONFIG_TPM_SPS /* We don't need to send events to the AP */ #undef CONFIG_HOSTCMD_EVENTS diff --git a/chip/g/build.mk b/chip/g/build.mk index a9747d3aea..5157dee9a5 100644 --- a/chip/g/build.mk +++ b/chip/g/build.mk @@ -20,6 +20,7 @@ chip-y=clock.o gpio.o hwtimer.o jtag.o system.o uart.o chip-y+= pmu.o chip-$(CONFIG_SPS)+= sps.o chip-$(CONFIG_HOSTCMD_SPS)+=sps_hc.o +chip-$(CONFIG_TPM_SPS)+=sps_tpm.o chip-$(CONFIG_WATCHDOG)+=watchdog.o chip-$(CONFIG_USB)+=usb.o usb_endpoints.o diff --git a/chip/g/sps.c b/chip/g/sps.c index 837d3c6884..82690b01ba 100644 --- a/chip/g/sps.c +++ b/chip/g/sps.c @@ -41,8 +41,6 @@ * - unregister receive callback. */ -#define SPS_FIFO_SIZE (1 << 10) -#define SPS_FIFO_MASK (SPS_FIFO_SIZE - 1) /* * Hardware pointers use one extra bit, which means that indexing FIFO and * values written into the pointers have to have dfferent sizes. Tracked under @@ -321,6 +319,12 @@ static void sps_cs_deassert_interrupt(uint32_t port) sps_rx_interrupt(port, 1); GWRITE_FIELD(SPS, ISTATE_CLR, CS_DEASSERT, 1); GWRITE_FIELD(SPS, FIFO_CTRL, TXFIFO_EN, 0); + + /* + * And transmit FIFO is emptied, so the next transaction doesn't start + * by clocking out any bytes left over from this one. + */ + GREG32(SPS, TXFIFO_WPTR) = GREG32(SPS, TXFIFO_RPTR); } void _sps0_interrupt(void) diff --git a/chip/g/sps.h b/chip/g/sps.h index 52f47c4020..83505909f1 100644 --- a/chip/g/sps.h +++ b/chip/g/sps.h @@ -18,13 +18,17 @@ enum sps_mode { SPS_UNDEF_MODE = 3, }; +/* Receive and transmit FIFO size and mask. */ +#define SPS_FIFO_SIZE (1 << 10) +#define SPS_FIFO_MASK (SPS_FIFO_SIZE - 1) + /* * Tx interrupt callback function prototype. This function returns a portion * of the received SPI data and current status of the CS line. When CS is * deasserted, this function is called with data_size of zero and a non-zero * cs_status. This allows the recipient to delineate the SPS frames. */ -typedef void (*rx_handler_f)(uint8_t *data, size_t data_size, int cs_status); +typedef void (*rx_handler_f)(uint8_t *data, size_t data_size, int cs_disabled); /* * Push data to the SPS TX FIFO diff --git a/chip/g/sps_tpm.c b/chip/g/sps_tpm.c new file mode 100644 index 0000000000..5ce79a661f --- /dev/null +++ b/chip/g/sps_tpm.c @@ -0,0 +1,282 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "common.h" +#include "console.h" +#include "hooks.h" +#include "sps.h" +#include "tpm_registers.h" +#include "util.h" + +/* + * This implements the TCG's TPM SPI Hardware Protocol on the SPI bus, using + * the Cr50 SPS (SPI slave) controller. This turns out to be very similar to + * the EC host command protocol, which is itself similar to HDLC. All of those + * protocols provide ways to identify data frames over transports that don't + * provide them natively. That's the nice thing about standards: there are so + * many to choose from. + * + * ANYWAY, The goal of the TPM protocol is to provide read and write access to + * device registers over the SPI bus. It is defined as follows (note that + * master clocks the bus and master and slave tramsmit data simultaneously). + * + * Each transaction starts with the master clocking the bus to transfer 4 + * bytes: + * + * The master sends 4 bytes: [R/W+size-1] [Addr] [Addr] [Addr] + * The slave also sends 4 bytes: [xx] [xx] [xx] [x?] + * + * Bytes sent by the master define the direction and size (1-64 bytes) of the + * data transfer, and the address of the register to access. + * + * The final bit of the 4th slave response byte determines whether or not the + * slave needs some extra time. If that bit is 1, the master can IMMEDIATELY + * clock in (or out) the number of bytes it specified with the header byte 0. + * + * If the final bit of the 4th response byte is 0, the master clocks eight + * more bits and looks again at the new received byte. It repeats this (clock + * 8 bits, look at last bit) as long as every eighth bit is 0. + * + * When the slave is ready to proceed with the data transfer, it returns a 1 + * for the final bit of the response byte, at which point the master has to + * resume transferring valid data for write transactions or to start deading + * bytes sent by the slave in case of read transactions. + * + * So here's what a 4-byte write of value of 0x11223344 to register 0xAABBCC + * might look like: + * + * xfer: 1 2 3 4 5 6 7 8 9 10 11 + * MOSI: 03 aa bb cc xx xx xx 11 22 33 44 + * MISO: xx xx xx x0 x0 x0 x1 xx xx xx xx + * + * Bit 0 of MISO xfer #4 is 0, indicating that the slave needs to stall. The + * slave stalled for three bytes before it was ready to continue accepting the + * input data from the master. The slave released the stall in xfer #7. + * + * Here's a 4-byte read from register 0xAABBCC: + * + * xfer: 1 2 3 4 5 6 7 8 9 10 11 + * MOSI: 83 aa bb cc xx xx xx xx xx xx xx + * MISO: xx xx xx x0 x0 x0 x1 11 22 33 44 + * + * As before, the slave stalled the read for three bytes and indicated it was + * done stalling at xfer #7. + * + * Note that the ONLY place where a stall can be initiated is the last bit of + * the fourth MISO byte of the transaction. Once the stall is released, + * there's no stopping the rest of the data transfer. + */ + +#define TPM_STALL_ASSERT 0x00 +#define TPM_STALL_DEASSERT 0x01 + + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_TPM, outstr) +#define CPRINTS(format, args...) cprints(CC_TPM, format, ## args) +#define CPRINTF(format, args...) cprintf(CC_TPM, format, ## args) + +/* + * Incoming messages are collected here until they're ready to process. The + * buffer will start with a four-byte header, followed by whatever data + * is sent by the master (none for a read, 1 to 64 bytes for a write). + */ +#define RXBUF_MAX 512 /* chosen arbitrarily */ +static uint8_t rxbuf[RXBUF_MAX]; +static unsigned rxbuf_count; /* num bytes received */ +static unsigned rxbuf_needed; /* num bytes we'd like */ +static unsigned rx_fifo_base; /* RX fifo at transaction start. */ +static unsigned stall_threshold; /* num bytes we'd like */ +static uint32_t bytecount; +static uint32_t regaddr; + + +/* + * Outgoing messages are shoved in here. We need a TPM_STALL_DEASSERT byte to + * mark the start of the data stream before the data itself. + */ +#define TXBUF_MAX 512 /* chosen arbitrarily */ +static uint8_t txbuf[1 + TXBUF_MAX]; + +static enum sps_state { + /* Receiving header */ + SPS_TPM_STATE_RECEIVING_HEADER, + + /* Receiving data. */ + SPS_TPM_STATE_RECEIVING_WRITE_DATA, + + /* Finished rx processing, waiting for SPI transaction to finish. */ + SPS_TPM_STATE_PONDERING, + + /* Something went wrong. */ + SPS_TPM_STATE_RX_BAD, +} sps_tpm_state; + +/* Set initial conditions to get ready to receive a command. */ +static void init_new_cycle(void) +{ + rxbuf_count = 0; + rxbuf_needed = 4; + sps_tpm_state = SPS_TPM_STATE_RECEIVING_HEADER; + rx_fifo_base = sps_rx_fifo_wrptr(); + sps_tx_status(TPM_STALL_ASSERT); +} + +/* Extract R/W bit, register addresss, and data count from 4-byte header */ +static int header_says_to_read(uint8_t *data, uint32_t *reg, uint32_t *count) +{ + uint32_t addr = data[1]; /* reg address is MSB first */ + addr = (addr << 8) + data[2]; + addr = (addr << 8) + data[3]; + *reg = addr; + *count = (data[0] & 0x3f) + 1; /* bits 5-0: 1 to 64 bytes */ + return !!(data[0] & 0x80); /* bit 7: 1=read, 0=write */ +} + +/* actual RX FIFO handler (runs in interrupt context) */ +static void process_rx_data(uint8_t *data, size_t data_size) +{ + /* We're collecting incoming bytes ... */ + if ((rxbuf_count + data_size) > RXBUF_MAX) { + CPRINTS("TPM SPI input overflow: %d + %d > %d in state %d", + rxbuf_count, data_size, RXBUF_MAX, sps_tpm_state); + sps_tx_status(TPM_STALL_DEASSERT); + sps_tpm_state = SPS_TPM_STATE_RX_BAD; + return; + } + memcpy(rxbuf + rxbuf_count, data, data_size); + rxbuf_count += data_size; + + /* Wait until we have enough. */ + if (rxbuf_count < rxbuf_needed) + return; + + /* Okay, we have enough. Now what? */ + if (sps_tpm_state == SPS_TPM_STATE_RECEIVING_HEADER) { + uint32_t old_wrptr, wrptr; + + /* Got the header. What's it say to do? */ + if (header_says_to_read(rxbuf, ®addr, &bytecount)) { + /* Send the stall deassert manually */ + txbuf[0] = TPM_STALL_DEASSERT; + + /* Copy the register contents into the TXFIFO */ + /* TODO: This is blindly assuming TXFIFO has enough + * room. What can we do if it doesn't? */ + tpm_register_get(regaddr, txbuf + 1, bytecount); + sps_transmit(txbuf, bytecount + 1); + sps_tpm_state = SPS_TPM_STATE_PONDERING; + return; + } + + /* + * Master is writing, we will need more data. + * + * This is a tricky part, as we do not know how many dummy + * bytes the master has already written. And the actual data + * of course will start arriving only after we change the idle + * byte to set the LSB to 1. + * + * What we do know is that the idle byte repeatedly sent on + * the MISO line is sampled at the same time as the RX FIFO + * write pointer is written by the chip, after clocking in the + * next byte. That is, we can synchronize with the line by + * waiting for the RX FIFO write pointer to change. Then we + * can change the idle byte to indicate that the slave is + * ready to receive the rest of the data, and take note of the + * RX FIFO write pointer, as the first byte of the message + * will show up in the receive FIFO 2 bytes later. + */ + + /* + * Let's wait til the start of the next byte cycle. This must + * be done in a tight loop (with interrupts disabled?). + */ + old_wrptr = sps_rx_fifo_wrptr(); + do { + wrptr = sps_rx_fifo_wrptr(); + } while (old_wrptr == wrptr); + + /* + * Write the new idle byte value, it will start transmitting + * *next* after the current byte. + */ + sps_tx_status(TPM_STALL_DEASSERT); + + /* + * Verify that we managed to change the idle byte value within + * the required time (RX FIFO write pointer has not changed) + */ + if (sps_rx_fifo_wrptr() != wrptr) { + CPRINTS("%s:" + " ERROR: failed to change idle byte in time", + __func__); + sps_tpm_state = SPS_TPM_STATE_PONDERING; + } else { + /* + * Ok, we're good. Remember where in the receive + * stream the actual data will start showing up. It is + * two bytes after the current one (the current idle + * byte still has the LSB set to zero, the next one + * will have the LSB set to one, only after receiving + * it the master will start sending the actual data. + */ + stall_threshold = + ((wrptr - rx_fifo_base) & SPS_FIFO_MASK) + 2; + rxbuf_needed = stall_threshold + bytecount; + sps_tpm_state = SPS_TPM_STATE_RECEIVING_WRITE_DATA; + } + return; + } + + if (sps_tpm_state == SPS_TPM_STATE_RECEIVING_WRITE_DATA) { + /* Ok, we have all the write data. */ + tpm_register_put(regaddr, rxbuf + stall_threshold, bytecount); + sps_tpm_state = SPS_TPM_STATE_PONDERING; + } +} + +static void tpm_rx_handler(uint8_t *data, size_t data_size, int cs_disabled) +{ + if ((sps_tpm_state == SPS_TPM_STATE_RECEIVING_HEADER) || + (sps_tpm_state == SPS_TPM_STATE_RECEIVING_WRITE_DATA)) + process_rx_data(data, data_size); + + if (cs_disabled) + init_new_cycle(); +} + +static void sps_tpm_enable(void) +{ + sps_register_rx_handler(SPS_GENERIC_MODE, tpm_rx_handler); + init_new_cycle(); +} + +static void sps_tpm_disable(void) +{ + sps_tpm_state = SPS_TPM_STATE_PONDERING; + sps_unregister_rx_handler(); +} + +static int command_sps_tpm(int argc, char **argv) +{ + if (argc > 1) { + if (0 != strcasecmp(argv[1], "off")) + return EC_ERROR_PARAM1; + + sps_tpm_disable(); + ccprintf("TPM SPI protocol disabled\n"); + return EC_SUCCESS; + } + + sps_tpm_enable(); + ccprintf("TPM SPI protocol enabled\n"); + return EC_SUCCESS; +} + +DECLARE_CONSOLE_COMMAND(spstpm, command_sps_tpm, + "[off]", + "Not sure yet...", + NULL); diff --git a/common/build.mk b/common/build.mk index 32012e400c..2fb599ea06 100644 --- a/common/build.mk +++ b/common/build.mk @@ -81,6 +81,7 @@ common-$(CONFIG_SPI_FLASH)+=spi_flash.o spi_flash_reg.o common-$(CONFIG_SWITCH)+=switch.o common-$(CONFIG_SW_CRC)+=crc.o common-$(CONFIG_TEMP_SENSOR)+=temp_sensor.o thermal.o throttle_ap.o +common-$(CONFIG_TPM_SPS)+=tpm_registers.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 diff --git a/common/tpm_registers.c b/common/tpm_registers.c new file mode 100644 index 0000000000..a3f1672b70 --- /dev/null +++ b/common/tpm_registers.c @@ -0,0 +1,166 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * This implements the register interface for the TPM SPI Hardware Protocol. + * The master puts or gets between 1 and 64 bytes to a register designated by a + * 24-bit address. There is no provision for error reporting at this level. + */ + +#include "common.h" +#include "console.h" +#include "hooks.h" +#include "tpm_registers.h" +#include "util.h" + +#define CPRINTS(format, args...) cprints(CC_TPM, format, ## args) +#define CPRINTF(format, args...) cprintf(CC_TPM, format, ## args) + + +/* Register addresses for FIFO mode. */ +#define TPM_ACCESS 0 +#define TPM_INT_ENABLE 8 +#define TPM_INT_VECTOR 0xC +#define TPM_INT_STATUS 0x10 +#define TPM_INTF_CAPABILITY 0x14 +#define TPM_STS 0x18 +#define TPM_DATA_FIFO 0x28 +#define TPM_INTERFACE_ID 0x30 +#define TPM_DID_VID 0xf00 +#define TPM_RID 0xf04 + +#define GOOGLE_VID 0x1ae0 +#define GOOGLE_DID 0x0028 +#define CR50_RID 0 /* No revision ID yet */ + +/* A preliminary interface capability register value, will be fine tuned. */ +#define IF_CAPABILITY_REG ((3 << 28) | /* TPM2.0 (interface 1.3) */ \ + (3 << 9) | /* up to 64 bytes transfers. */ \ + 0x15) /* Mandatory set to one. */ + +#define MAX_LOCALITIES 1 /* Eventually there could be five. */ +/* Volatile registers for FIFO mode */ +static struct tpm_register_file { + uint8_t tpm_access; + uint32_t tpm_int_status; + uint32_t tpm_sts; + uint8_t tpm_data_fifo[64]; /* this might have to be much deeper. */ + uint32_t fifo_read_index; /* for read commands */ + uint32_t fifo_write_index; /* for write commands */ +} tpm_regs; + +enum tpm_access_bits { + tpm_reg_valid_sts = (1 << 7), + active_locality = (1 << 5), + request_use = (1 << 1), + tpm_establishment = (1 << 0), +}; + +enum tpm_sts_bits { + tpm_family_shift = 26, + tpm_family_mask = ((1 << 2) - 1), /* 2 bits wide */ + tpm_family_tpm2 = 1, + reset_establishment_bit = (1 << 25), + command_cancel = (1 << 24), + burst_count_shift = 8, + burst_count_mask = ((1 << 16) - 1), /* 16 bits wide */ + sts_valid = (1 << 7), + command_ready = (1 << 6), + tpm_go = (1 << 5), + data_avail = (1 << 4), + expect = (1 << 3), + self_test_done = (1 << 2), + response_retry = (1 << 1), +}; + +/* + * NOTE: The put/get functions are called in interrupt context! Don't waste a + * lot of time here - just copy the data and wake up a task to deal with it + * later. Although if the implementation mandates a "busy" bit somewhere, you + * might want to set it now to avoid race conditions with back-to-back + * interrupts. + */ + +static void copy_bytes(uint8_t *dest, uint32_t data_size, uint32_t value) +{ + unsigned real_size, i; + + real_size = MIN(data_size, 4); + + for (i = 0; i < real_size; i++) + dest[i] = (value >> (i * 8)) & 0xff; +} + +static void access_reg_write(uint8_t data, uint8_t *reg) +{ + /* By definition only one bit can be set at a time. */ + if (!data || (data & (data-1))) { + CPRINTF("%s: attempt to set acces reg to %02x\n", + __func__, data); + return; + } + + switch (data) { + case request_use: + *reg |= active_locality; + break; + + case active_locality: + *reg &= ~active_locality; + break; + } +} + +void tpm_register_put(uint32_t regaddr, const uint8_t *data, uint32_t data_size) +{ + uint32_t i; + switch (regaddr) { + case TPM_ACCESS: + /* This is a one byte register, ignore extra data, if any */ + access_reg_write(data[0], &tpm_regs.tpm_access); + break; + default: + CPRINTF("%s(0x%06x, %d", __func__, regaddr, data_size); + for (i = 0; i < data_size; i++) + CPRINTF(", %02x", data[i]); + CPRINTF("\n"); + return; + } + +} + +void tpm_register_get(uint32_t regaddr, uint8_t *dest, uint32_t data_size) +{ + switch (regaddr) { + case TPM_DID_VID: + copy_bytes(dest, data_size, (GOOGLE_DID << 16) | GOOGLE_VID); + break; + case TPM_RID: + copy_bytes(dest, data_size, CR50_RID); + break; + case TPM_INTF_CAPABILITY: + copy_bytes(dest, data_size, IF_CAPABILITY_REG); + break; + case TPM_ACCESS: + copy_bytes(dest, data_size, tpm_regs.tpm_access); + break; + case TPM_STS: + copy_bytes(dest, data_size, tpm_regs.tpm_sts); + break; + default: + CPRINTS("%s(0x%06x, %d) => ??", __func__, regaddr, data_size); + return; + } +} + + +static void tpm_init(void) +{ + tpm_regs.tpm_access = tpm_reg_valid_sts; + tpm_regs.tpm_sts = (tpm_family_tpm2 << tpm_family_shift) | + (64 << burst_count_shift); +} + +DECLARE_HOOK(HOOK_INIT, tpm_init, HOOK_PRIO_DEFAULT); diff --git a/include/config.h b/include/config.h index 17d0712b79..ad28fc8e71 100644 --- a/include/config.h +++ b/include/config.h @@ -1457,6 +1457,12 @@ #undef CONFIG_TEMP_SENSOR_POWER_GPIO /*****************************************************************************/ +/* TPM-like configuration */ + +/* Speak the TPM SPI Hardware Protocol on the SPI slave interface */ +#undef CONFIG_TPM_SPS + +/*****************************************************************************/ /* USART stream config */ #undef CONFIG_STREAM_USART diff --git a/include/tpm_registers.h b/include/tpm_registers.h new file mode 100644 index 0000000000..cdf8410a51 --- /dev/null +++ b/include/tpm_registers.h @@ -0,0 +1,24 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * This defines the interface functions for TPM SPI Hardware Protocol. The SPI + * master reads or writes between 1 and 64 bytes to a register designated by a + * 24-bit address. There is no provision for error reporting at this level. + */ + +#ifndef __CROS_EC_TPM_REGISTERS_H +#define __CROS_EC_TPM_REGISTERS_H + +#include <stdint.h> + +/* The SPI master is writing data into a TPM register. */ +void tpm_register_put(uint32_t regaddr, + const uint8_t *data, uint32_t data_size); + +/* The SPI master is reading data from a TPM register. */ +void tpm_register_get(uint32_t regaddr, uint8_t *dest, uint32_t data_size); + +#endif /* __CROS_EC_TPM_REGISTERS_H */ |