summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Kconfig4
-rw-r--r--src/arch/x86/boot/acpi.c8
-rw-r--r--src/include/pc80/tpm.h29
-rw-r--r--src/pc80/Makefile.inc1
-rw-r--r--src/pc80/tpm.c554
-rw-r--r--src/vendorcode/google/chromeos/vboot.c201
6 files changed, 797 insertions, 0 deletions
diff --git a/src/Kconfig b/src/Kconfig
index ba1af089cd..f7180a98c0 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -254,6 +254,10 @@ config IOAPIC
bool
default n
+config TPM
+ bool
+ default n
+
# TODO: Can probably be removed once all chipsets have kconfig options for it.
config VIDEO_MB
int
diff --git a/src/arch/x86/boot/acpi.c b/src/arch/x86/boot/acpi.c
index 168933a726..33d730bc8c 100644
--- a/src/arch/x86/boot/acpi.c
+++ b/src/arch/x86/boot/acpi.c
@@ -32,6 +32,9 @@
#include <device/pci.h>
#include <cbmem.h>
#include <cpu/x86/lapic_def.h>
+#if CONFIG_CHROMEOS
+#include <vendorcode/google/chromeos/chromeos.h>
+#endif
u8 acpi_checksum(u8 *table, u32 length)
{
@@ -524,6 +527,11 @@ void *acpi_find_wakeup_vector(void)
if (!acpi_is_wakeup())
return NULL;
+#if CONFIG_CHROMEOS
+ printk(BIOS_DEBUG, "Verified boot TPM initialization.\n");
+ init_vboot();
+#endif
+
printk(BIOS_DEBUG, "Trying to find the wakeup vector...\n");
/* Find RSDP. */
diff --git a/src/include/pc80/tpm.h b/src/include/pc80/tpm.h
new file mode 100644
index 0000000000..2eff15a09a
--- /dev/null
+++ b/src/include/pc80/tpm.h
@@ -0,0 +1,29 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2011 The Chromium OS Authors. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef TPM_H_
+#define TPM_H_
+
+int tis_init(void);
+int tis_open(void);
+int tis_close(void);
+int tis_sendrecv(const u8 *sendbuf, size_t send_size, u8 *recvbuf,
+ size_t *recv_len);
+
+#endif /* TPM_H_ */
diff --git a/src/pc80/Makefile.inc b/src/pc80/Makefile.inc
index 2c8a80ee15..cd6ea33483 100644
--- a/src/pc80/Makefile.inc
+++ b/src/pc80/Makefile.inc
@@ -4,6 +4,7 @@ ramstage-y += i8254.c
ramstage-y += i8259.c
ramstage-$(CONFIG_UDELAY_IO) += udelay_io.c
ramstage-y += keyboard.c
+ramstage-$(CONFIG_TPM) += tpm.c
romstage-$(CONFIG_USE_OPTION_TABLE) += mc146818rtc_early.c
subdirs-y += vga
diff --git a/src/pc80/tpm.c b/src/pc80/tpm.c
new file mode 100644
index 0000000000..1cbf800571
--- /dev/null
+++ b/src/pc80/tpm.c
@@ -0,0 +1,554 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2011 The Chromium OS Authors. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * The code in this file has been heavily based on the article "Writing a TPM
+ * Device Driver" published on http://ptgmedia.pearsoncmg.com and the
+ * submission by Stefan Berger on Qemu-devel mailing list.
+ *
+ * One principal difference is that in the simplest config the other than 0
+ * TPM localities do not get mapped by some devices (for instance, by
+ * Infineon slb9635), so this driver provides access to locality 0 only.
+ */
+
+/* #define DEBUG */
+#include <stdlib.h>
+#include <string.h>
+#include <delay.h>
+#include <arch/io.h>
+#include <arch/byteorder.h>
+#include <console/console.h>
+#include <pc80/tpm.h>
+
+#ifdef DEBUG
+#define TPM_DEBUG_ON 1
+#else
+#define TPM_DEBUG_ON 0
+#endif
+
+#define PREFIX "lpc_tpm: "
+
+/* coreboot wrapper for TPM driver (start) */
+#define TPM_DEBUG(fmt, args...) \
+ if (TPM_DEBUG_ON) { \
+ printk(BIOS_DEBUG, PREFIX); \
+ printk(BIOS_DEBUG, fmt , ##args); \
+ }
+#define printf(x...) printk(BIOS_ERR, x)
+
+#define min(a,b) MIN(a,b)
+#define max(a,b) MAX(a,b)
+#define readb(_a) (*(volatile unsigned char *) (_a))
+#define writeb(_v, _a) (*(volatile unsigned char *) (_a) = (_v))
+#define readl(_a) (*(volatile unsigned long *) (_a))
+#define writel(_v, _a) (*(volatile unsigned long *) (_a) = (_v))
+/* coreboot wrapper for TPM driver (end) */
+
+#ifndef CONFIG_TPM_TIS_BASE_ADDRESS
+/* Base TPM address standard for x86 systems */
+#define CONFIG_TPM_TIS_BASE_ADDRESS 0xfed40000
+#endif
+
+/* the macro accepts the locality value, but only locality 0 is operational */
+#define TIS_REG(LOCALITY, REG) \
+ (void *)(CONFIG_TPM_TIS_BASE_ADDRESS + (LOCALITY << 12) + REG)
+
+/* hardware registers' offsets */
+#define TIS_REG_ACCESS 0x0
+#define TIS_REG_INT_ENABLE 0x8
+#define TIS_REG_INT_VECTOR 0xc
+#define TIS_REG_INT_STATUS 0x10
+#define TIS_REG_INTF_CAPABILITY 0x14
+#define TIS_REG_STS 0x18
+#define TIS_REG_DATA_FIFO 0x24
+#define TIS_REG_DID_VID 0xf00
+#define TIS_REG_RID 0xf04
+
+/* Some registers' bit field definitions */
+#define TIS_STS_VALID (1 << 7) /* 0x80 */
+#define TIS_STS_COMMAND_READY (1 << 6) /* 0x40 */
+#define TIS_STS_TPM_GO (1 << 5) /* 0x20 */
+#define TIS_STS_DATA_AVAILABLE (1 << 4) /* 0x10 */
+#define TIS_STS_EXPECT (1 << 3) /* 0x08 */
+#define TIS_STS_RESPONSE_RETRY (1 << 1) /* 0x02 */
+
+#define TIS_ACCESS_TPM_REG_VALID_STS (1 << 7) /* 0x80 */
+#define TIS_ACCESS_ACTIVE_LOCALITY (1 << 5) /* 0x20 */
+#define TIS_ACCESS_BEEN_SEIZED (1 << 4) /* 0x10 */
+#define TIS_ACCESS_SEIZE (1 << 3) /* 0x08 */
+#define TIS_ACCESS_PENDING_REQUEST (1 << 2) /* 0x04 */
+#define TIS_ACCESS_REQUEST_USE (1 << 1) /* 0x02 */
+#define TIS_ACCESS_TPM_ESTABLISHMENT (1 << 0) /* 0x01 */
+
+#define TIS_STS_BURST_COUNT_MASK (0xffff)
+#define TIS_STS_BURST_COUNT_SHIFT (8)
+
+/*
+ * Error value returned if a tpm register does not enter the expected state
+ * after continuous polling. No actual TPM register reading ever returns ~0,
+ * so this value is a safe error indication to be mixed with possible status
+ * register values.
+ */
+#define TPM_TIMEOUT_ERR (~0)
+
+/* Error value returned on various TPM driver errors */
+#define TPM_DRIVER_ERR (~0)
+
+ /* 1 second is plenty for anything TPM does.*/
+#define MAX_DELAY_US (1000 * 1000)
+
+/* Retrieve burst count value out of the status register contents. */
+#define BURST_COUNT(status) ((u16)(((status) >> TIS_STS_BURST_COUNT_SHIFT) & \
+ TIS_STS_BURST_COUNT_MASK))
+
+/*
+ * Structures defined below allow creating descriptions of TPM vendor/device
+ * ID information for run time discovery. The only device the system knows
+ * about at this time is Infineon slb9635
+ */
+struct device_name {
+ u16 dev_id;
+ const char * const dev_name;
+};
+
+struct vendor_name {
+ u16 vendor_id;
+ const char * vendor_name;
+ struct device_name* dev_names;
+};
+
+static struct device_name infineon_devices[] = {
+ {0xb, "SLB9635 TT 1.2"},
+ {0}
+};
+
+static const struct vendor_name vendor_names[] = {
+ {0x15d1, "Infineon", infineon_devices},
+};
+
+/*
+ * Cached vendor/device ID pair to indicate that the device has been already
+ * discovered
+ */
+static u32 vendor_dev_id;
+
+static int is_byte_reg(u32 reg)
+{
+ /*
+ * These TPM registers are 8 bits wide and as such require byte access
+ * on writes and truncated value on reads.
+ */
+ return ((reg == TIS_REG_ACCESS) ||
+ (reg == TIS_REG_INT_VECTOR) ||
+ (reg == TIS_REG_DATA_FIFO));
+}
+
+/* TPM access functions are carved out to make tracing easier. */
+static u32 tpm_read(int locality, u32 reg)
+{
+ u32 value;
+ /*
+ * Data FIFO register must be read and written in byte access mode,
+ * otherwise the FIFO values are returned 4 bytes at a time.
+ */
+ if (is_byte_reg(reg))
+ value = readb(TIS_REG(locality, reg));
+ else
+ value = readl(TIS_REG(locality, reg));
+
+ TPM_DEBUG("Read reg 0x%x returns 0x%x\n", reg, value);
+ return value;
+}
+
+static void tpm_write(u32 value, int locality, u32 reg)
+{
+ TPM_DEBUG("Write reg 0x%x with 0x%x\n", reg, value);
+
+ if (is_byte_reg(reg))
+ writeb(value & 0xff, TIS_REG(locality, reg));
+ else
+ writel(value, TIS_REG(locality, reg));
+}
+
+/*
+ * tis_wait_reg()
+ *
+ * Wait for at least a second for a register to change its state to match the
+ * expected state. Normally the transition happens within microseconds.
+ *
+ * @reg - the TPM register offset
+ * @locality - locality
+ * @mask - bitmask for the bitfield(s) to watch
+ * @expected - value the field(s) are supposed to be set to
+ *
+ * Returns the register contents in case the expected value was found in the
+ * appropriate register bits, or TPM_TIMEOUT_ERR on timeout.
+ */
+static u32 tis_wait_reg(u8 reg, u8 locality, u8 mask, u8 expected)
+{
+ u32 time_us = MAX_DELAY_US;
+ while (time_us > 0) {
+ u32 value = tpm_read(locality, reg);
+ if ((value & mask) == expected)
+ return value;
+ udelay(1); /* 1 us */
+ time_us--;
+ }
+ return TPM_TIMEOUT_ERR;
+}
+
+/*
+ * Probe the TPM device and try determining its manufacturer/device name.
+ *
+ * Returns 0 on success (the device is found or was found during an earlier
+ * invocation) or TPM_DRIVER_ERR if the device is not found.
+ */
+static u32 tis_probe(void)
+{
+ u32 didvid = tpm_read(0, TIS_REG_DID_VID);
+ int i;
+ const char *device_name = "unknown";
+ const char *vendor_name = device_name;
+ u16 vid, did;
+
+ if (vendor_dev_id)
+ return 0; /* Already probed. */
+
+ if (!didvid || (didvid == 0xffffffff)) {
+ printf("%s: No TPM device found\n", __FUNCTION__);
+ return TPM_DRIVER_ERR;
+ }
+
+ vendor_dev_id = didvid;
+
+ vid = didvid & 0xffff;
+ did = (didvid >> 16) & 0xffff;
+ for (i = 0; i < ARRAY_SIZE(vendor_names); i++) {
+ int j = 0;
+ u16 known_did;
+ if (vid == vendor_names[i].vendor_id) {
+ vendor_name = vendor_names[i].vendor_name;
+ }
+ while ((known_did = vendor_names[i].dev_names[j].dev_id) != 0) {
+ if (known_did == did) {
+ device_name =
+ vendor_names[i].dev_names[j].dev_name;
+ break;
+ }
+ j++;
+ }
+ break;
+ }
+ /* this will have to be converted into debug printout */
+ TPM_DEBUG("Found TPM %s by %s\n", device_name, vendor_name);
+ return 0;
+}
+
+/*
+ * tis_senddata()
+ *
+ * send the passed in data to the TPM device.
+ *
+ * @data - address of the data to send, byte by byte
+ * @len - length of the data to send
+ *
+ * Returns 0 on success, TPM_DRIVER_ERR on error (in case the device does
+ * not accept the entire command).
+ */
+static u32 tis_senddata(const u8 * const data, u32 len)
+{
+ u32 offset = 0;
+ u16 burst = 0;
+ u32 max_cycles = 0;
+ u8 locality = 0;
+ u32 value;
+
+ value = tis_wait_reg(TIS_REG_STS, locality, TIS_STS_COMMAND_READY,
+ TIS_STS_COMMAND_READY);
+ if (value == TPM_TIMEOUT_ERR) {
+ printf("%s:%d - failed to get 'command_ready' status\n",
+ __FILE__, __LINE__);
+ return TPM_DRIVER_ERR;
+ }
+ burst = BURST_COUNT(value);
+
+ while (1) {
+ unsigned count;
+
+ /* Wait till the device is ready to accept more data. */
+ while (!burst) {
+ if (max_cycles++ == MAX_DELAY_US) {
+ printf("%s:%d failed to feed %d bytes of %d\n",
+ __FILE__, __LINE__, len - offset, len);
+ return TPM_DRIVER_ERR;
+ }
+ udelay(1);
+ burst = BURST_COUNT(tpm_read(locality, TIS_REG_STS));
+ }
+
+ max_cycles = 0;
+
+ /*
+ * Calculate number of bytes the TPM is ready to accept in one
+ * shot.
+ *
+ * We want to send the last byte outside of the loop (hence
+ * the -1 below) to make sure that the 'expected' status bit
+ * changes to zero exactly after the last byte is fed into the
+ * FIFO.
+ */
+ count = min(burst, len - offset - 1);
+ while (count--)
+ tpm_write(data[offset++], locality, TIS_REG_DATA_FIFO);
+
+ value = tis_wait_reg(TIS_REG_STS, locality,
+ TIS_STS_VALID, TIS_STS_VALID);
+
+ if ((value == TPM_TIMEOUT_ERR) || !(value & TIS_STS_EXPECT)) {
+ printf("%s:%d TPM command feed overflow\n",
+ __FILE__, __LINE__);
+ return TPM_DRIVER_ERR;
+ }
+
+ burst = BURST_COUNT(value);
+ if ((offset == (len - 1)) && burst)
+ /*
+ * We need to be able to send the last byte to the
+ * device, so burst size must be nonzero before we
+ * break out.
+ */
+ break;
+ }
+
+ /* Send the last byte. */
+ tpm_write(data[offset++], locality, TIS_REG_DATA_FIFO);
+
+ /*
+ * Verify that TPM does not expect any more data as part of this
+ * command.
+ */
+ value = tis_wait_reg(TIS_REG_STS, locality,
+ TIS_STS_VALID, TIS_STS_VALID);
+ if ((value == TPM_TIMEOUT_ERR) || (value & TIS_STS_EXPECT)) {
+ printf("%s:%d unexpected TPM status 0x%x\n",
+ __FILE__, __LINE__, value);
+ return TPM_DRIVER_ERR;
+ }
+
+ /* OK, sitting pretty, let's start the command execution. */
+ tpm_write(TIS_STS_TPM_GO, locality, TIS_REG_STS);
+
+ return 0;
+}
+
+/*
+ * tis_readresponse()
+ *
+ * read the TPM device response after a command was issued.
+ *
+ * @buffer - address where to read the response, byte by byte.
+ * @len - pointer to the size of buffer
+ *
+ * On success stores the number of received bytes to len and returns 0. On
+ * errors (misformatted TPM data or synchronization problems) returns
+ * TPM_DRIVER_ERR.
+ */
+static u32 tis_readresponse(u8 *buffer, size_t *len)
+{
+ u16 burst_count;
+ u32 status;
+ u32 offset = 0;
+ u8 locality = 0;
+ const u32 has_data = TIS_STS_DATA_AVAILABLE | TIS_STS_VALID;
+ u32 expected_count = *len;
+ int max_cycles = 0;
+
+ /* Wait for the TPM to process the command */
+ status = tis_wait_reg(TIS_REG_STS, locality, has_data, has_data);
+ if (status == TPM_TIMEOUT_ERR) {
+ printf("%s:%d failed processing command\n",
+ __FILE__, __LINE__);
+ return TPM_DRIVER_ERR;
+ }
+
+ do {
+ while ((burst_count = BURST_COUNT(status)) == 0) {
+ if (max_cycles++ == MAX_DELAY_US) {
+ printf("%s:%d TPM stuck on read\n",
+ __FILE__, __LINE__);
+ return TPM_DRIVER_ERR;
+ }
+ udelay(1);
+ status = tpm_read(locality, TIS_REG_STS);
+ }
+
+ max_cycles = 0;
+
+ while (burst_count-- && (offset < expected_count)) {
+ buffer[offset++] = (u8) tpm_read(locality,
+ TIS_REG_DATA_FIFO);
+ if (offset == 6) {
+ /*
+ * We got the first six bytes of the reply,
+ * let's figure out how many bytes to expect
+ * total - it is stored as a 4 byte number in
+ * network order, starting with offset 2 into
+ * the body of the reply.
+ */
+ u32 real_length;
+ memcpy(&real_length,
+ buffer + 2,
+ sizeof(real_length));
+ expected_count = be32_to_cpu(real_length);
+
+ if ((expected_count < offset) ||
+ (expected_count > *len)) {
+ printf("%s:%d bad response size %d\n",
+ __FILE__, __LINE__,
+ expected_count);
+ return TPM_DRIVER_ERR;
+ }
+ }
+ }
+
+ /* Wait for the next portion */
+ status = tis_wait_reg(TIS_REG_STS, locality,
+ TIS_STS_VALID, TIS_STS_VALID);
+ if (status == TPM_TIMEOUT_ERR) {
+ printf("%s:%d failed to read response\n",
+ __FILE__, __LINE__);
+ return TPM_DRIVER_ERR;
+ }
+
+ if (offset == expected_count)
+ break; /* We got all we need */
+
+ } while ((status & has_data) == has_data);
+
+ /*
+ * Make sure we indeed read all there was. The TIS_STS_VALID bit is
+ * known to be set.
+ */
+ if (status & TIS_STS_DATA_AVAILABLE) {
+ printf("%s:%d wrong receive status %x\n",
+ __FILE__, __LINE__, status);
+ return TPM_DRIVER_ERR;
+ }
+
+ /* Tell the TPM that we are done. */
+ tpm_write(TIS_STS_COMMAND_READY, locality, TIS_REG_STS);
+
+ *len = offset;
+ return 0;
+}
+
+/*
+ * tis_init()
+ *
+ * Initialize the TPM device. Returns 0 on success or TPM_DRIVER_ERR on
+ * failure (in case device probing did not succeed).
+ */
+int tis_init(void)
+{
+ if (tis_probe())
+ return TPM_DRIVER_ERR;
+ return 0;
+}
+
+/*
+ * tis_open()
+ *
+ * Requests access to locality 0 for the caller. After all commands have been
+ * completed the caller is supposed to call tis_close().
+ *
+ * Returns 0 on success, TPM_DRIVER_ERR on failure.
+ */
+int tis_open(void)
+{
+ u8 locality = 0; /* we use locality zero for everything */
+
+ if (tis_close())
+ return TPM_DRIVER_ERR;
+
+ /* now request access to locality */
+ tpm_write(TIS_ACCESS_REQUEST_USE, locality, TIS_REG_ACCESS);
+
+ /* did we get a lock? */
+ if (tis_wait_reg(TIS_REG_ACCESS, locality,
+ TIS_ACCESS_ACTIVE_LOCALITY,
+ TIS_ACCESS_ACTIVE_LOCALITY) == TPM_TIMEOUT_ERR) {
+ printf("%s:%d - failed to lock locality %d\n",
+ __FILE__, __LINE__, locality);
+ return TPM_DRIVER_ERR;
+ }
+
+ tpm_write(TIS_STS_COMMAND_READY, locality, TIS_REG_STS);
+
+ return 0;
+}
+
+/*
+ * tis_close()
+ *
+ * terminate the currect session with the TPM by releasing the locked
+ * locality. Returns 0 on success of TPM_DRIVER_ERR on failure (in case lock
+ * removal did not succeed).
+ */
+int tis_close(void)
+{
+ u8 locality = 0;
+ if (tpm_read(locality, TIS_REG_ACCESS) &
+ TIS_ACCESS_ACTIVE_LOCALITY) {
+ tpm_write(TIS_ACCESS_ACTIVE_LOCALITY, locality, TIS_REG_ACCESS);
+
+ if (tis_wait_reg(TIS_REG_ACCESS, locality,
+ TIS_ACCESS_ACTIVE_LOCALITY, 0) ==
+ TPM_TIMEOUT_ERR) {
+ printf("%s:%d - failed to release locality %d\n",
+ __FILE__, __LINE__, locality);
+ return TPM_DRIVER_ERR;
+ }
+ }
+ return 0;
+}
+
+/*
+ * tis_sendrecv()
+ *
+ * Send the requested data to the TPM and then try to get its response
+ *
+ * @sendbuf - buffer of the data to send
+ * @send_size size of the data to send
+ * @recvbuf - memory to save the response to
+ * @recv_len - pointer to the size of the response buffer
+ *
+ * Returns 0 on success (and places the number of response bytes at recv_len)
+ * or TPM_DRIVER_ERR on failure.
+ */
+int tis_sendrecv(const uint8_t *sendbuf, size_t send_size,
+ uint8_t *recvbuf, size_t *recv_len)
+{
+ if (tis_senddata(sendbuf, send_size)) {
+ printf("%s:%d failed sending data to TPM\n",
+ __FILE__, __LINE__);
+ return TPM_DRIVER_ERR;
+ }
+
+ return tis_readresponse(recvbuf, recv_len);
+}
diff --git a/src/vendorcode/google/chromeos/vboot.c b/src/vendorcode/google/chromeos/vboot.c
new file mode 100644
index 0000000000..e0a8c9b336
--- /dev/null
+++ b/src/vendorcode/google/chromeos/vboot.c
@@ -0,0 +1,201 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2011 The ChromiumOS Authors. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <types.h>
+#include <console/console.h>
+#include <arch/acpi.h>
+#include <pc80/tpm.h>
+#include "chromeos.h"
+
+//#define EXTRA_LOGGING
+
+#define TPM_LARGE_ENOUGH_COMMAND_SIZE 256 /* saves space in the firmware */
+
+#define TPM_SUCCESS ((u32)0x00000000)
+
+#define TPM_E_IOERROR ((u32)0x0000001f)
+#define TPM_E_COMMUNICATION_ERROR ((u32)0x00005004)
+#define TPM_E_NON_FATAL ((u32)0x00000800)
+#define TPM_E_INVALID_POSTINIT ((u32)0x00000026)
+
+#define TPM_E_NEEDS_SELFTEST ((u32)(TPM_E_NON_FATAL + 1))
+#define TPM_E_DOING_SELFTEST ((u32)(TPM_E_NON_FATAL + 2))
+
+static const struct {
+ u8 buffer[12];
+} tpm_resume_cmd = {
+ { 0x0, 0xc1, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x99, 0x0, 0x2 }
+};
+
+static const struct {
+ u8 buffer[10];
+} tpm_continueselftest_cmd = {
+ { 0x0, 0xc1, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x53 }
+};
+
+static inline void FromTpmUint32(const u8 * buffer, u32 * x)
+{
+ *x = ((buffer[0] << 24) |
+ (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]);
+}
+
+static inline int TpmCommandSize(const u8 * buffer)
+{
+ u32 size;
+ FromTpmUint32(buffer + sizeof(u16), &size);
+ return (int)size;
+}
+
+/* Gets the code field of a TPM command. */
+static inline int TpmCommandCode(const u8 * buffer)
+{
+ u32 code;
+ FromTpmUint32(buffer + sizeof(u16) + sizeof(u32), &code);
+ return code;
+}
+
+/* Gets the return code field of a TPM result. */
+static inline int TpmReturnCode(const u8 * buffer)
+{
+ return TpmCommandCode(buffer);
+}
+
+/* Like TlclSendReceive below, but do not retry if NEEDS_SELFTEST or
+ * DOING_SELFTEST errors are returned.
+ */
+static u32 TlclSendReceiveNoRetry(const u8 * request,
+ u8 * response, int max_length)
+{
+ size_t response_length = max_length;
+ u32 result;
+
+#ifdef EXTRA_LOGGING
+ printk(BIOS_DEBUG, "TPM: command: %x%x %x%x%x%x %x%x%x%x\n",
+ request[0], request[1],
+ request[2], request[3], request[4], request[5],
+ request[6], request[7], request[8], request[9]);
+#endif
+
+ result = TPM_SUCCESS;
+ if (tis_sendrecv
+ (request, TpmCommandSize(request), response, &response_length))
+ result = TPM_E_IOERROR;
+
+ if (0 != result) {
+ /* Communication with TPM failed, so response is garbage */
+ printk(BIOS_DEBUG,
+ "TPM: command 0x%x send/receive failed: 0x%x\n",
+ TpmCommandCode(request), result);
+ return TPM_E_COMMUNICATION_ERROR;
+ }
+ /* Otherwise, use the result code from the response */
+ result = TpmReturnCode(response);
+
+/* TODO: add paranoia about returned response_length vs. max_length
+ * (and possibly expected length from the response header). See
+ * crosbug.com/17017 */
+
+#ifdef EXTRA_LOGGING
+ printk(BIOS_DEBUG, "TPM: response: %x%x %x%x%x%x %x%x%x%x\n",
+ response[0], response[1],
+ response[2], response[3], response[4], response[5],
+ response[6], response[7], response[8], response[9]);
+#endif
+
+ printk(BIOS_DEBUG, "TPM: command 0x%x returned 0x%x\n",
+ TpmCommandCode(request), result);
+
+ return result;
+}
+
+static inline u32 TlclContinueSelfTest(void)
+{
+ u8 response[TPM_LARGE_ENOUGH_COMMAND_SIZE];
+ printk(BIOS_DEBUG, "TPM: Continue self test\n");
+ /* Call the No Retry version of SendReceive to avoid recursion. */
+ return TlclSendReceiveNoRetry(tpm_continueselftest_cmd.buffer,
+ response, sizeof(response));
+}
+
+/* Sends a TPM command and gets a response. Returns 0 if success or the TPM
+ * error code if error. In the firmware, waits for the self test to complete
+ * if needed. In the host, reports the first error without retries. */
+static u32 TlclSendReceive(const u8 * request, u8 * response, int max_length)
+{
+ u32 result = TlclSendReceiveNoRetry(request, response, max_length);
+ /* When compiling for the firmware, hide command failures due to the self
+ * test not having run or completed. */
+ /* If the command fails because the self test has not completed, try it
+ * again after attempting to ensure that the self test has completed. */
+ if (result == TPM_E_NEEDS_SELFTEST || result == TPM_E_DOING_SELFTEST) {
+ result = TlclContinueSelfTest();
+ if (result != TPM_SUCCESS) {
+ return result;
+ }
+#if defined(TPM_BLOCKING_CONTINUESELFTEST) || defined(VB_RECOVERY_MODE)
+ /* Retry only once */
+ result = TlclSendReceiveNoRetry(request, response, max_length);
+#else
+ /* This needs serious testing. The TPM specification says:
+ * "iii. The caller MUST wait for the actions of
+ * TPM_ContinueSelfTest to complete before reissuing the
+ * command C1." But, if ContinueSelfTest is non-blocking, how
+ * do we know that the actions have completed other than trying
+ * again? */
+ do {
+ result =
+ TlclSendReceiveNoRetry(request, response,
+ max_length);
+ } while (result == TPM_E_DOING_SELFTEST);
+#endif
+ }
+
+ return result;
+}
+
+void init_vboot(void)
+{
+ u32 result;
+ u8 response[TPM_LARGE_ENOUGH_COMMAND_SIZE];
+
+ printk(BIOS_DEBUG, "TPM: Init\n");
+ if (tis_init())
+ return;
+
+ printk(BIOS_DEBUG, "TPM: Open\n");
+ if (tis_open())
+ return;
+
+ printk(BIOS_DEBUG, "TPM: Resume\n");
+
+ result =
+ TlclSendReceive(tpm_resume_cmd.buffer, response, sizeof(response));
+
+ if (result == TPM_E_INVALID_POSTINIT) {
+ /* We're on a platform where the TPM maintains power in S3, so
+ * it's already initialized. */
+ printk(BIOS_DEBUG, "TPM: Already initialized.\n");
+ return;
+ }
+ if (result == TPM_SUCCESS) {
+ printk(BIOS_DEBUG, "TPM: OK.\n");
+ return;
+ }
+ // TODO(reinauer) hard reboot?
+}