From b4049a0e96f6335a93877e1e884f9a440487c421 Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Thu, 29 Aug 2013 16:05:02 -0700 Subject: [PATCH] drivers: Add I2C TPM driver to coreboot On ARM platforms the TPM is not attached through LPC but through I2C. This patch adds an I2C TPM driver that supports the following chips: * Infineon SLB9635 * Infineon SLB9645 In order to select the correct TPM implementation cleanly, CONFIG_TPM is moved to src/Kconfig and does the correct choice. BRANCH=none TEST=compile tested on peach_pit (more work needed to use this) BUG=none Change-Id: I2def0e0f86a869d6fcf56fc4ccab0bc935de2bf1 Signed-off-by: Stefan Reinauer Reviewed-on: https://chromium-review.googlesource.com/167543 Reviewed-by: ron minnich --- src/Kconfig | 10 + src/drivers/i2c/Kconfig | 1 + src/drivers/i2c/Makefile.inc | 1 + src/drivers/i2c/tpm/Kconfig | 9 + src/drivers/i2c/tpm/Makefile.inc | 2 + src/drivers/i2c/tpm/tis.h | 74 ++++ src/drivers/i2c/tpm/tis_i2c.c | 103 +++++ src/drivers/i2c/tpm/tpm.c | 451 ++++++++++++++++++++++ src/drivers/i2c/tpm/tpm.h | 158 ++++++++ src/drivers/i2c/tpm/tpm_tis_i2c.c | 604 ++++++++++++++++++++++++++++++ src/drivers/pc80/Kconfig | 2 +- src/drivers/pc80/Makefile.inc | 2 +- 12 files changed, 1415 insertions(+), 2 deletions(-) create mode 100644 src/drivers/i2c/tpm/Kconfig create mode 100644 src/drivers/i2c/tpm/Makefile.inc create mode 100644 src/drivers/i2c/tpm/tis.h create mode 100644 src/drivers/i2c/tpm/tis_i2c.c create mode 100644 src/drivers/i2c/tpm/tpm.c create mode 100644 src/drivers/i2c/tpm/tpm.h create mode 100644 src/drivers/i2c/tpm/tpm_tis_i2c.c diff --git a/src/Kconfig b/src/Kconfig index d134cec332..e6e00df58a 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -269,6 +269,16 @@ menu "Generic Drivers" source src/drivers/Kconfig endmenu +config TPM + bool + default n + select LPC_TPM if ARCH_X86 + select I2C_TPM if ARCH_ARMV7 + help + Enable this option to enable TPM support in coreboot. + + If unsure, say N. + config HEAP_SIZE hex default 0x4000 diff --git a/src/drivers/i2c/Kconfig b/src/drivers/i2c/Kconfig index a6f59f4492..5c55c44d07 100644 --- a/src/drivers/i2c/Kconfig +++ b/src/drivers/i2c/Kconfig @@ -5,5 +5,6 @@ source src/drivers/i2c/i2cmux/Kconfig source src/drivers/i2c/i2cmux2/Kconfig source src/drivers/i2c/lm63/Kconfig source src/drivers/i2c/rtd2132/Kconfig +source src/drivers/i2c/tpm/Kconfig source src/drivers/i2c/w83795/Kconfig source src/drivers/i2c/w83793/Kconfig diff --git a/src/drivers/i2c/Makefile.inc b/src/drivers/i2c/Makefile.inc index ef7ac4b2b2..26d68d8244 100644 --- a/src/drivers/i2c/Makefile.inc +++ b/src/drivers/i2c/Makefile.inc @@ -5,5 +5,6 @@ subdirs-y += i2cmux subdirs-y += i2cmux2 subdirs-y += lm63 subdirs-y += rtd2132 +subdirs-y += tpm subdirs-y += w83795 subdirs-y += w83793 diff --git a/src/drivers/i2c/tpm/Kconfig b/src/drivers/i2c/tpm/Kconfig new file mode 100644 index 0000000000..2456204960 --- /dev/null +++ b/src/drivers/i2c/tpm/Kconfig @@ -0,0 +1,9 @@ +config I2C_TPM + bool "I2C TPM" + depends on !PC80 # for now + +config DRIVER_TPM_I2C_ADDR + hex "I2C TPM chip address" + default 2 # FIXME, workaround for Kconfig BS + depends on I2C_TPM + diff --git a/src/drivers/i2c/tpm/Makefile.inc b/src/drivers/i2c/tpm/Makefile.inc new file mode 100644 index 0000000000..74d9b7e5aa --- /dev/null +++ b/src/drivers/i2c/tpm/Makefile.inc @@ -0,0 +1,2 @@ +ramstage-$(CONFIG_I2C_TPM) += tis_i2c.c tpm.c tpm_tis_i2c.c +romstage-$(CONFIG_I2C_TPM) += tis_i2c.c tpm.c tpm_tis_i2c.c diff --git a/src/drivers/i2c/tpm/tis.h b/src/drivers/i2c/tpm/tis.h new file mode 100644 index 0000000000..348f9a8019 --- /dev/null +++ b/src/drivers/i2c/tpm/tis.h @@ -0,0 +1,74 @@ +/* + * Copyright 2011 Google Inc. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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; either version 2 of + * the License, or (at your option) any later version. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __DRIVERS_TPM_TPM_H__ +#define __DRIVERS_TPM_TPM_H__ + +#include +#include + +void tis_set_i2c_bus(unsigned _bus); + +/* + * tis_init() + * + * Initialize the TPM device. Returns 0 on success or -1 on + * failure (in case device probing did not succeed). + */ +int tis_init(void); + +/* + * 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, -1 on failure. + */ +int tis_open(void); + +/* + * tis_close() + * + * terminate the currect session with the TPM by releasing the locked + * locality. Returns 0 on success of -1 on failure (in case lock + * removal did not succeed). + */ +int tis_close(void); + +/* + * 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 -1 on failure. + */ +int tis_sendrecv(const uint8_t *sendbuf, size_t send_size, uint8_t *recvbuf, + size_t *recv_len); + +#endif /* __DRIVERS_TPM_TPM_H__ */ diff --git a/src/drivers/i2c/tpm/tis_i2c.c b/src/drivers/i2c/tpm/tis_i2c.c new file mode 100644 index 0000000000..59971a2631 --- /dev/null +++ b/src/drivers/i2c/tpm/tis_i2c.c @@ -0,0 +1,103 @@ +/* + * Copyright 2013 Google Inc. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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; either version 2 of + * the License, or (at your option) any later version. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include "tis.h" +#include "tpm.h" + +static unsigned bus = 0; + +static int opened; + +void tis_set_i2c_bus(unsigned _bus) +{ + bus = _bus; +} + +int tis_open(void) +{ + opened = 1; + if (tpm_open(bus, CONFIG_DRIVER_TPM_I2C_ADDR)) { + opened = 0; + return -1; + } + return 0; +} + +int tis_close(void) +{ + opened = 0; + tpm_close(); + return 0; +} + +int tis_init(void) +{ + int chip = CONFIG_DRIVER_TPM_I2C_ADDR; + + /* + * Probe TPM twice; the first probing might fail because TPM is asleep, + * and the probing can wake up TPM. + */ + uint8_t tmp; + if (!bus) + return -1; + + if (i2c_read(bus, chip, 0, 0, &tmp, sizeof(tmp)) && + i2c_read(bus, chip, 0, 0, &tmp, sizeof(tmp))) + return -1; + + return 0; +} + +int tis_sendrecv(const uint8_t *sendbuf, size_t sbuf_size, + uint8_t *recvbuf, size_t *rbuf_len) +{ + uint8_t buf[TPM_BUFSIZE]; + + if (!opened && tis_open()) + return -1; + + if (sizeof(buf) < sbuf_size) + return -1; + + memcpy(buf, sendbuf, sbuf_size); + + int len = tpm_transmit(buf, sbuf_size); + + if (len < 10) { + *rbuf_len = 0; + return -1; + } + + if (len > *rbuf_len) { + *rbuf_len = len; + return -1; + } + + memcpy(recvbuf, buf, len); + *rbuf_len = len; + + return 0; +} diff --git a/src/drivers/i2c/tpm/tpm.c b/src/drivers/i2c/tpm/tpm.c new file mode 100644 index 0000000000..e099dfea75 --- /dev/null +++ b/src/drivers/i2c/tpm/tpm.c @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2011 Infineon Technologies + * + * Authors: + * Peter Huewe + * + * Description: + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * It is based on the Linux kernel driver tpm.c from Leendert van + * Dorn, Dave Safford, Reiner Sailer, and Kyleen Hall. + * + * Version: 2.1.1 + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include "tis.h" +#include "tpm.h" + +/* global structure for tpm chip data */ +struct tpm_chip g_chip; + +enum tpm_duration { + TPM_SHORT = 0, + TPM_MEDIUM = 1, + TPM_LONG = 2, + TPM_UNDEFINED, +}; + +#define TPM_MAX_ORDINAL 243 +#define TPM_MAX_PROTECTED_ORDINAL 12 +#define TPM_PROTECTED_ORDINAL_MASK 0xFF + +/* + * Array with one entry per ordinal defining the maximum amount + * of time the chip could take to return the result. The ordinal + * designation of short, medium or long is defined in a table in + * TCG Specification TPM Main Part 2 TPM Structures Section 17. The + * values of the SHORT, MEDIUM, and LONG durations are retrieved + * from the chip during initialization with a call to tpm_get_timeouts. + */ +static const uint8_t tpm_prot_ordinal_duration[TPM_MAX_PROTECTED_ORDINAL] = { + TPM_UNDEFINED, /* 0 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 5 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 10 */ + TPM_SHORT, +}; + +static const uint8_t tpm_ordinal_duration[TPM_MAX_ORDINAL] = { + TPM_UNDEFINED, /* 0 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 5 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 10 */ + TPM_SHORT, + TPM_MEDIUM, + TPM_LONG, + TPM_LONG, + TPM_MEDIUM, /* 15 */ + TPM_SHORT, + TPM_SHORT, + TPM_MEDIUM, + TPM_LONG, + TPM_SHORT, /* 20 */ + TPM_SHORT, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_SHORT, /* 25 */ + TPM_SHORT, + TPM_MEDIUM, + TPM_SHORT, + TPM_SHORT, + TPM_MEDIUM, /* 30 */ + TPM_LONG, + TPM_MEDIUM, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, /* 35 */ + TPM_MEDIUM, + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_MEDIUM, /* 40 */ + TPM_LONG, + TPM_MEDIUM, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, /* 45 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_LONG, + TPM_MEDIUM, /* 50 */ + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 55 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_MEDIUM, /* 60 */ + TPM_MEDIUM, + TPM_MEDIUM, + TPM_SHORT, + TPM_SHORT, + TPM_MEDIUM, /* 65 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 70 */ + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 75 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_LONG, /* 80 */ + TPM_UNDEFINED, + TPM_MEDIUM, + TPM_LONG, + TPM_SHORT, + TPM_UNDEFINED, /* 85 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 90 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, /* 95 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_MEDIUM, /* 100 */ + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 105 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 110 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, /* 115 */ + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_LONG, /* 120 */ + TPM_LONG, + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_SHORT, + TPM_SHORT, /* 125 */ + TPM_SHORT, + TPM_LONG, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, /* 130 */ + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_SHORT, + TPM_MEDIUM, + TPM_UNDEFINED, /* 135 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 140 */ + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 145 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 150 */ + TPM_MEDIUM, + TPM_MEDIUM, + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, /* 155 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 160 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 165 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_LONG, /* 170 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 175 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_MEDIUM, /* 180 */ + TPM_SHORT, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_MEDIUM, /* 185 */ + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 190 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 195 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 200 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, + TPM_SHORT, /* 205 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_MEDIUM, /* 210 */ + TPM_UNDEFINED, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_UNDEFINED, /* 215 */ + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, + TPM_SHORT, /* 220 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, /* 225 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 230 */ + TPM_LONG, + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 235 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 240 */ + TPM_UNDEFINED, + TPM_MEDIUM, +}; + +/* + * Returns max number of milliseconds to wait + */ +static unsigned long tpm_calc_ordinal_duration(struct tpm_chip *chip, + uint32_t ordinal) +{ + int duration_idx = TPM_UNDEFINED; + int duration = 0; + + if (ordinal < TPM_MAX_ORDINAL) + duration_idx = tpm_ordinal_duration[ordinal]; + else if ((ordinal & TPM_PROTECTED_ORDINAL_MASK) < + TPM_MAX_PROTECTED_ORDINAL) + duration_idx = + tpm_prot_ordinal_duration[ordinal & + TPM_PROTECTED_ORDINAL_MASK]; + + if (duration_idx != TPM_UNDEFINED) + duration = chip->vendor.duration[duration_idx]; + if (duration <= 0) + return 2 * 60 * 1000; /*two minutes timeout*/ + else + return duration; +} + +#define TPM_CMD_COUNT_BYTE 2 +#define TPM_CMD_ORDINAL_BYTE 6 + +ssize_t tpm_transmit(const uint8_t *buf, size_t bufsiz) +{ + ssize_t rc; + uint32_t count, ordinal; + + struct tpm_chip *chip = &g_chip; + + memcpy(&count, buf + TPM_CMD_COUNT_BYTE, sizeof(count)); + count = be32_to_cpu(count); + memcpy(&ordinal, buf + TPM_CMD_ORDINAL_BYTE, sizeof(ordinal)); + ordinal = be32_to_cpu(ordinal); + + if (count == 0) { + printk(BIOS_DEBUG, "tpm_transmit: no data\n"); + return -1; //ENODATA; + } + if (count > bufsiz) { + printk(BIOS_DEBUG, "tpm_transmit: invalid count value %x %zx\n", + count, bufsiz); + return -1; //E2BIG; + } + + ASSERT(chip->vendor.send); + rc = chip->vendor.send(chip, (uint8_t *) buf, count); + if (rc < 0) { + printk(BIOS_DEBUG, "tpm_transmit: tpm_send: error %zd\n", rc); + goto out; + } + + if (chip->vendor.irq) + goto out_recv; + + int timeout = tpm_calc_ordinal_duration(chip, ordinal) / TPM_TIMEOUT; + while (timeout) { + ASSERT(chip->vendor.status); + uint8_t status = chip->vendor.status(chip); + if ((status & chip->vendor.req_complete_mask) == + chip->vendor.req_complete_val) { + goto out_recv; + } + + if ((status == chip->vendor.req_canceled)) { + printk(BIOS_DEBUG, "tpm_transmit: Operation Canceled\n"); + rc = -1; + goto out; + } + mdelay(TPM_TIMEOUT); + timeout--; + } + + ASSERT(chip->vendor.cancel); + chip->vendor.cancel(chip); + printk(BIOS_DEBUG, "tpm_transmit: Operation Timed out\n"); + rc = -1; //ETIME; + goto out; + +out_recv: + + rc = chip->vendor.recv(chip, (uint8_t *) buf, TPM_BUFSIZE); + if (rc < 0) + printk(BIOS_DEBUG, "tpm_transmit: tpm_recv: error %zd\n", rc); +out: + return rc; +} + +#define TPM_ERROR_SIZE 10 + +struct tpm_chip *tpm_register_hardware(const struct tpm_vendor_specific *entry) +{ + struct tpm_chip *chip; + + /* Driver specific per-device data */ + chip = &g_chip; + memcpy(&chip->vendor, entry, sizeof(struct tpm_vendor_specific)); + chip->is_open = 1; + + return chip; +} + +int tpm_open(unsigned bus, uint32_t dev_addr) +{ + int rc; + if (g_chip.is_open) + return -1; //EBUSY; + rc = tpm_vendor_init(bus, dev_addr); + if (rc < 0) + g_chip.is_open = 0; + return rc; +} + +void tpm_close(void) +{ + if (g_chip.is_open) { + tpm_vendor_cleanup(&g_chip); + g_chip.is_open = 0; + } +} diff --git a/src/drivers/i2c/tpm/tpm.h b/src/drivers/i2c/tpm/tpm.h new file mode 100644 index 0000000000..758f3444d6 --- /dev/null +++ b/src/drivers/i2c/tpm/tpm.h @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2011 Infineon Technologies + * + * Authors: + * Peter Huewe + * + * Version: 2.1.1 + * + * Description: + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * It is based on the Linux kernel driver tpm.c from Leendert van + * Dorn, Dave Safford, Reiner Sailer, and Kyleen Hall. + * + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __DRIVERS_TPM_SLB9635_I2C_TPM_H__ +#define __DRIVERS_TPM_SLB9635_I2C_TPM_H__ + +#include + +enum tpm_timeout { + TPM_TIMEOUT = 1, /* msecs */ +}; + +/* Size of external transmit buffer (used in tpm_transmit)*/ +#define TPM_BUFSIZE 4096 + +/* Index of fields in TPM command buffer */ +#define TPM_CMD_SIZE_BYTE 2 +#define TPM_CMD_ORDINAL_BYTE 6 + +/* Index of Count field in TPM response buffer */ +#define TPM_RSP_SIZE_BYTE 2 +#define TPM_RSP_RC_BYTE 6 + +struct tpm_chip; + +struct tpm_vendor_specific { + const uint8_t req_complete_mask; + const uint8_t req_complete_val; + const uint8_t req_canceled; + int irq; + int (*recv)(struct tpm_chip *, uint8_t *, size_t); + int (*send)(struct tpm_chip *, uint8_t *, size_t); + void (*cancel)(struct tpm_chip *); + uint8_t(*status)(struct tpm_chip *); + int locality; + unsigned long timeout_a, timeout_b, timeout_c, timeout_d; /* msec */ + unsigned long duration[3]; /* msec */ +}; + +struct tpm_chip { + int is_open; + struct tpm_vendor_specific vendor; +}; + +struct tpm_input_header { + uint16_t tag; + uint32_t length; + uint32_t ordinal; +} __attribute__ ((packed)); + +struct tpm_output_header { + uint16_t tag; + uint32_t length; + uint32_t return_code; +} __attribute__ ((packed)); + +struct timeout_t { + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; +} __attribute__ ((packed)); + +struct duration_t { + uint32_t tpm_short; + uint32_t tpm_medium; + uint32_t tpm_long; +} __attribute__ ((packed)); + +typedef union { + struct timeout_t timeout; + struct duration_t duration; +} cap_t; + +struct tpm_getcap_params_in { + uint32_t cap; + uint32_t subcap_size; + uint32_t subcap; +} __attribute__ ((packed)); + +struct tpm_getcap_params_out { + uint32_t cap_size; + cap_t cap; +} __attribute__ ((packed)); + +typedef union { + struct tpm_input_header in; + struct tpm_output_header out; +} tpm_cmd_header; + +typedef union { + struct tpm_getcap_params_out getcap_out; + struct tpm_getcap_params_in getcap_in; +} tpm_cmd_params; + +struct tpm_cmd_t { + tpm_cmd_header header; + tpm_cmd_params params; +} __attribute__ ((packed)); + + +/* ---------- Interface for TPM vendor ------------ */ + +struct tpm_chip *tpm_register_hardware(const struct tpm_vendor_specific *); + +int tpm_vendor_init(unsigned bus, uint32_t dev_addr); + +void tpm_vendor_cleanup(struct tpm_chip *chip); + +/* ---------- Interface for TDDL ------------------- */ + +/* + * if dev_addr != 0 - redefines TPM device address + * Returns < 0 on error, 0 on success. + */ +int tpm_open(unsigned bus, uint32_t dev_addr); + +void tpm_close(void); + +/* + * Transmit bufsiz bytes out of buf to TPM and get results back in buf, too. + * Returns < 0 on error, 0 on success. + */ +ssize_t tpm_transmit(const unsigned char *buf, size_t bufsiz); + +#endif /* __DRIVERS_TPM_SLB9635_I2C_TPM_H__ */ diff --git a/src/drivers/i2c/tpm/tpm_tis_i2c.c b/src/drivers/i2c/tpm/tpm_tis_i2c.c new file mode 100644 index 0000000000..58e94001ae --- /dev/null +++ b/src/drivers/i2c/tpm/tpm_tis_i2c.c @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2011 Infineon Technologies + * + * Authors: + * Peter Huewe + * + * Description: + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * This device driver implements the TPM interface as defined in + * the TCG TPM Interface Spec version 1.2, revision 1.0 and the + * Infineon I2C Protocol Stack Specification v0.20. + * + * It is based on the Linux kernel driver tpm.c from Leendert van + * Dorn, Dave Safford, Reiner Sailer, and Kyleen Hall. + * + * Version: 2.1.1 + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include "tpm.h" + +/* max. buffer size supported by our tpm */ +#ifdef TPM_BUFSIZE +#undef TPM_BUFSIZE +#endif +#define TPM_BUFSIZE 1260 +/* Address of the TPM on the I2C bus */ +#define TPM_I2C_ADDR 0x20 +/* max. number of iterations after i2c NAK */ +#define MAX_COUNT 3 + +#define SLEEP_DURATION 60 /*in usec*/ + +/* max. number of iterations after i2c NAK for 'long' commands + * we need this especially for sending TPM_READY, since the cleanup after the + * transtion to the ready state may take some time, but it is unpredictable + * how long it will take. + */ +#define MAX_COUNT_LONG 50 + +#define SLEEP_DURATION_LONG 210 /* in usec */ + +/* expected value for DIDVID register */ +#define TPM_TIS_I2C_DID_VID_9635 0x000b15d1L +#define TPM_TIS_I2C_DID_VID_9645 0x001a15d1L + +enum i2c_chip_type { + SLB9635, + SLB9645, + UNKNOWN, +}; + +static const char * const chip_name[] = { + [SLB9635] = "slb9635tt", + [SLB9645] = "slb9645tt", + [UNKNOWN] = "unknown/fallback to slb9635", +}; + +/* Structure to store I2C TPM specific stuff */ +struct tpm_inf_dev { + unsigned bus; + unsigned int addr; + uint8_t buf[TPM_BUFSIZE + sizeof(uint8_t)]; // max. buffer size + addr + enum i2c_chip_type chip_type; +}; + +static struct tpm_inf_dev tpm_dev = { + .addr = TPM_I2C_ADDR +}; + +/* + * iic_tpm_read() - read from TPM register + * @addr: register address to read from + * @buffer: provided by caller + * @len: number of bytes to read + * + * Read len bytes from TPM register and put them into + * buffer (little-endian format, i.e. first byte is put into buffer[0]). + * + * NOTE: TPM is big-endian for multi-byte values. Multi-byte + * values have to be swapped. + * + * Return -EIO on error, 0 on success. + */ +static int iic_tpm_read(uint8_t addr, uint8_t *buffer, size_t len) +{ + int rc; + int count; + + if (!tpm_dev.bus) + return -1; + if ((tpm_dev.chip_type == SLB9635) || (tpm_dev.chip_type == UNKNOWN)) { + /* slb9635 protocol should work in both cases */ + for (count = 0; count < MAX_COUNT; count++) { + rc = i2c_write(tpm_dev.bus, tpm_dev.addr, + 0, 0, &addr, 1); + if (rc == 0) + break; /* success, break to skip sleep */ + + udelay(SLEEP_DURATION); + } + + if (rc) + return -rc; + + /* After the TPM has successfully received the register address + * it needs some time, thus we're sleeping here again, before + * retrieving the data + */ + for (count = 0; count < MAX_COUNT; count++) { + udelay(SLEEP_DURATION); + rc = i2c_read(tpm_dev.bus, tpm_dev.addr, + 0, 0, buffer, len); + if (rc == 0) + break; /* success, break to skip sleep */ + + } + } else { + /* use a combined read for newer chips + * unfortunately the smbus functions are not suitable due to + * the 32 byte limit of the smbus. + * retries should usually not be needed, but are kept just to + * be safe on the safe side. + */ + for (count = 0; count < MAX_COUNT; count++) { + rc = i2c_read(tpm_dev.bus, tpm_dev.addr, + addr, 1, buffer, len); + if (rc == 0) + break; /* break here to skip sleep */ + udelay(SLEEP_DURATION); + } + } + + /* take care of 'guard time' */ + udelay(SLEEP_DURATION); + if (rc) + return -rc; + + return 0; +} + +static int iic_tpm_write_generic(uint8_t addr, uint8_t *buffer, size_t len, + unsigned int sleep_time, + uint8_t max_count) +{ + int rc = 0; + int count; + + if (len > TPM_BUFSIZE) { + printk(BIOS_DEBUG, "%s: Length %d is too large\n", __func__, len); + return -1; + } + + /* prepare send buffer */ + tpm_dev.buf[0] = addr; + memcpy(&(tpm_dev.buf[1]), buffer, len); + + if (!tpm_dev.bus) + return -1; + for (count = 0; count < max_count; count++) { + rc = i2c_write(tpm_dev.bus, tpm_dev.addr, 0, 0, + tpm_dev.buf, len + 1); + if (rc == 0) + break; /* success, break to skip sleep */ + + udelay(sleep_time); + } + + /* take care of 'guard time' */ + udelay(SLEEP_DURATION); + if (rc) + return -rc; + + return 0; +} + +/* + * iic_tpm_write() - write to TPM register + * @addr: register address to write to + * @buffer: containing data to be written + * @len: number of bytes to write + * + * Write len bytes from provided buffer to TPM register (little + * endian format, i.e. buffer[0] is written as first byte). + * + * NOTE: TPM is big-endian for multi-byte values. Multi-byte + * values have to be swapped. + * + * NOTE: use this function instead of the iic_tpm_write_generic function. + * + * Return -EIO on error, 0 on success + */ +static int iic_tpm_write(uint8_t addr, uint8_t *buffer, size_t len) +{ + return iic_tpm_write_generic(addr, buffer, len, SLEEP_DURATION, + MAX_COUNT); +} + +/* + * This function is needed especially for the cleanup situation after + * sending TPM_READY + * */ +static int iic_tpm_write_long(uint8_t addr, uint8_t *buffer, size_t len) +{ + return iic_tpm_write_generic(addr, buffer, len, SLEEP_DURATION_LONG, + MAX_COUNT_LONG); +} + +#define TPM_HEADER_SIZE 10 + +enum tis_access { + TPM_ACCESS_VALID = 0x80, + TPM_ACCESS_ACTIVE_LOCALITY = 0x20, + TPM_ACCESS_REQUEST_PENDING = 0x04, + TPM_ACCESS_REQUEST_USE = 0x02, +}; + +enum tis_status { + TPM_STS_VALID = 0x80, + TPM_STS_COMMAND_READY = 0x40, + TPM_STS_GO = 0x20, + TPM_STS_DATA_AVAIL = 0x10, + TPM_STS_DATA_EXPECT = 0x08, +}; + +enum tis_defaults { + TIS_SHORT_TIMEOUT = 750, /* ms */ + TIS_LONG_TIMEOUT = 2000, /* 2 sec */ +}; + +#define TPM_ACCESS(l) (0x0000 | ((l) << 4)) +#define TPM_STS(l) (0x0001 | ((l) << 4)) +#define TPM_DATA_FIFO(l) (0x0005 | ((l) << 4)) +#define TPM_DID_VID(l) (0x0006 | ((l) << 4)) + +static int check_locality(struct tpm_chip *chip, int loc) +{ + uint8_t buf; + int rc; + + rc = iic_tpm_read(TPM_ACCESS(loc), &buf, 1); + if (rc < 0) + return rc; + + if ((buf & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) { + chip->vendor.locality = loc; + return loc; + } + + return -1; +} + +static void release_locality(struct tpm_chip *chip, int loc, int force) +{ + uint8_t buf; + if (iic_tpm_read(TPM_ACCESS(loc), &buf, 1) < 0) + return; + + if (force || (buf & (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) == + (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) { + buf = TPM_ACCESS_ACTIVE_LOCALITY; + iic_tpm_write(TPM_ACCESS(loc), &buf, 1); + } +} + +static int request_locality(struct tpm_chip *chip, int loc) +{ + uint8_t buf = TPM_ACCESS_REQUEST_USE; + + if (check_locality(chip, loc) >= 0) + return loc; /* we already have the locality */ + + iic_tpm_write(TPM_ACCESS(loc), &buf, 1); + + /* wait for burstcount */ + int timeout = chip->vendor.timeout_a / TPM_TIMEOUT; + while (timeout) { + if (check_locality(chip, loc) >= 0) + return loc; + mdelay(TPM_TIMEOUT); + timeout--; + } + + return -1; +} + +static uint8_t tpm_tis_i2c_status(struct tpm_chip *chip) +{ + /* NOTE: since i2c read may fail, return 0 in this case --> time-out */ + uint8_t buf; + if (iic_tpm_read(TPM_STS(chip->vendor.locality), &buf, 1) < 0) + return 0; + else + return buf; +} + +static void tpm_tis_i2c_ready(struct tpm_chip *chip) +{ + /* this causes the current command to be aborted */ + uint8_t buf = TPM_STS_COMMAND_READY; + iic_tpm_write_long(TPM_STS(chip->vendor.locality), &buf, 1); +} + +static ssize_t get_burstcount(struct tpm_chip *chip) +{ + ssize_t burstcnt; + uint8_t buf[3]; + + /* wait for burstcount */ + /* which timeout value, spec has 2 answers (c & d) */ + int timeout = chip->vendor.timeout_d / TPM_TIMEOUT; + while (timeout) { + /* Note: STS is little endian */ + if (iic_tpm_read(TPM_STS(chip->vendor.locality) + 1, buf, 3) < 0) + burstcnt = 0; + else + burstcnt = (buf[2] << 16) + (buf[1] << 8) + buf[0]; + + if (burstcnt) + return burstcnt; + mdelay(TPM_TIMEOUT); + timeout--; + } + return -1; //EBUSY; +} + +static int wait_for_stat(struct tpm_chip *chip, uint8_t mask, + unsigned long timeout, int *status) +{ + /* check current status */ + *status = tpm_tis_i2c_status(chip); + if ((*status & mask) == mask) + return 0; + + timeout /= TPM_TIMEOUT; + while (timeout) { + mdelay(TPM_TIMEOUT); + *status = tpm_tis_i2c_status(chip); + if ((*status & mask) == mask) + return 0; + timeout--; + } + + return -1; //ETIME; +} + +static int recv_data(struct tpm_chip *chip, uint8_t *buf, size_t count) +{ + size_t size = 0; + ssize_t burstcnt; + int rc; + + while (size < count) { + burstcnt = get_burstcount(chip); + + /* burstcount < 0 = tpm is busy */ + if (burstcnt < 0) + return burstcnt; + + /* limit received data to max. left */ + if (burstcnt > (count - size)) + burstcnt = count - size; + + rc = iic_tpm_read(TPM_DATA_FIFO(chip->vendor.locality), + &(buf[size]), + burstcnt); + if (rc == 0) + size += burstcnt; + + } + return size; +} + +static int tpm_tis_i2c_recv(struct tpm_chip *chip, uint8_t *buf, size_t count) +{ + int size = 0; + uint32_t expected; + int status; + + if (count < TPM_HEADER_SIZE) { + size = -1; //EIO; + goto out; + } + + /* read first 10 bytes, including tag, paramsize, and result */ + size = recv_data(chip, buf, TPM_HEADER_SIZE); + if (size < TPM_HEADER_SIZE) { + printk(BIOS_DEBUG, "tpm_tis_i2c_recv: Unable to read header\n"); + goto out; + } + + memcpy(&expected, buf + TPM_RSP_SIZE_BYTE, sizeof(expected)); + expected = be32_to_cpu(expected); + if ((size_t)expected > count) { + size = -1; //EIO; + goto out; + } + + size += recv_data(chip, &buf[TPM_HEADER_SIZE], + expected - TPM_HEADER_SIZE); + if (size < expected) { + printk(BIOS_DEBUG, "tpm_tis_i2c_recv: Unable to " + "read remainder of result\n"); + size = -1; //ETIME; + goto out; + } + + wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, &status); + if (status & TPM_STS_DATA_AVAIL) { /* retry? */ + printk(BIOS_DEBUG, "tpm_tis_i2c_recv: Error left over data\n"); + size = -1; //EIO; + goto out; + } + +out: + tpm_tis_i2c_ready(chip); + release_locality(chip, chip->vendor.locality, 0); + + return size; +} + +static int tpm_tis_i2c_send(struct tpm_chip *chip, uint8_t *buf, size_t len) +{ + int rc, status; + ssize_t burstcnt; + size_t count = 0; + uint8_t sts = TPM_STS_GO; + + if (len > TPM_BUFSIZE) + return -1; //E2BIG; /* command is too long for our tpm, sorry */ + + if (request_locality(chip, 0) < 0) + return -1; //EBUSY; + + status = tpm_tis_i2c_status(chip); + if ((status & TPM_STS_COMMAND_READY) == 0) { + tpm_tis_i2c_ready(chip); + if (wait_for_stat + (chip, TPM_STS_COMMAND_READY, + chip->vendor.timeout_b, &status) < 0) { + rc = -1; //ETIME; + goto out_err; + } + } + + while (count < len - 1) { + burstcnt = get_burstcount(chip); + + /* burstcount < 0 = tpm is busy */ + if (burstcnt < 0) + return burstcnt; + + if (burstcnt > (len-1-count)) + burstcnt = len-1-count; + +#ifdef CONFIG_TPM_I2C_BURST_LIMITATION + if (burstcnt > CONFIG_TPM_I2C_BURST_LIMITATION) + burstcnt = CONFIG_TPM_I2C_BURST_LIMITATION; +#endif /* CONFIG_TPM_I2C_BURST_LIMITATION */ + + rc = iic_tpm_write(TPM_DATA_FIFO(chip->vendor.locality), + &(buf[count]), burstcnt); + if (rc == 0) + count += burstcnt; + + wait_for_stat(chip, TPM_STS_VALID, + chip->vendor.timeout_c, &status); + + if ((status & TPM_STS_DATA_EXPECT) == 0) { + rc = -1; //EIO; + goto out_err; + } + + } + + /* write last byte */ + iic_tpm_write(TPM_DATA_FIFO(chip->vendor.locality), &(buf[count]), 1); + wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, &status); + if ((status & TPM_STS_DATA_EXPECT) != 0) { + rc = -1; //EIO; + goto out_err; + } + + /* go and do it */ + iic_tpm_write(TPM_STS(chip->vendor.locality), &sts, 1); + + return len; +out_err: + tpm_tis_i2c_ready(chip); + release_locality(chip, chip->vendor.locality, 0); + + return rc; +} + +static struct tpm_vendor_specific tpm_tis_i2c = { + .status = tpm_tis_i2c_status, + .recv = tpm_tis_i2c_recv, + .send = tpm_tis_i2c_send, + .cancel = tpm_tis_i2c_ready, + .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID, + .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID, + .req_canceled = TPM_STS_COMMAND_READY, +}; + +/* initialisation of i2c tpm */ + + +int tpm_vendor_init(unsigned bus, uint32_t dev_addr) +{ + uint32_t vendor; + unsigned int old_addr; + int rc = 0; + struct tpm_chip *chip; + + old_addr = tpm_dev.addr; + if (dev_addr != 0) + tpm_dev.addr = dev_addr; + tpm_dev.bus = bus; + + chip = tpm_register_hardware(&tpm_tis_i2c); + if (chip < 0) { + rc = -1; //ENODEV; + goto out_err; + } + + /* Disable interrupts (not supported) */ + chip->vendor.irq = 0; + + /* Default timeouts */ + chip->vendor.timeout_a = TIS_SHORT_TIMEOUT; + chip->vendor.timeout_b = TIS_LONG_TIMEOUT; + chip->vendor.timeout_c = TIS_SHORT_TIMEOUT; + chip->vendor.timeout_d = TIS_SHORT_TIMEOUT; + + if (request_locality(chip, 0) != 0) { + rc = -1; //ENODEV; + goto out_err; + } + + /* read four bytes from DID_VID register */ + if (iic_tpm_read(TPM_DID_VID(0), (uint8_t *)&vendor, 4) < 0) { + rc = -1; //EIO; + goto out_release; + } + + if (vendor == TPM_TIS_I2C_DID_VID_9645) { + tpm_dev.chip_type = SLB9645; + } else if (be32_to_cpu(vendor) == TPM_TIS_I2C_DID_VID_9635) { + tpm_dev.chip_type = SLB9635; + } else { + printk(BIOS_DEBUG, "Vendor ID 0x%08x not recognized.\n", vendor); + rc = -1; //ENODEV; + goto out_release; + } + + printk(BIOS_DEBUG, "1.2 TPM (chip type %s device-id 0x%X)\n", + chip_name[tpm_dev.chip_type], vendor >> 16); + + /* + * A timeout query to TPM can be placed here. + * Standard timeout values are used so far + */ + + return 0; + +out_release: + release_locality(chip, 0, 1); + +out_err: + tpm_dev.addr = old_addr; + return rc; +} + +void tpm_vendor_cleanup(struct tpm_chip *chip) +{ + release_locality(chip, chip->vendor.locality, 1); +} diff --git a/src/drivers/pc80/Kconfig b/src/drivers/pc80/Kconfig index 3c8fd8c6cd..d9ff99151c 100644 --- a/src/drivers/pc80/Kconfig +++ b/src/drivers/pc80/Kconfig @@ -15,7 +15,7 @@ config DRIVERS_PS2_KEYBOARD this option, then you can say N here to speed up boot time. Otherwise say Y. -config TPM +config LPC_TPM bool default n help diff --git a/src/drivers/pc80/Makefile.inc b/src/drivers/pc80/Makefile.inc index 23e986a515..9985433265 100644 --- a/src/drivers/pc80/Makefile.inc +++ b/src/drivers/pc80/Makefile.inc @@ -4,7 +4,7 @@ ramstage-y += i8254.c ramstage-y += i8259.c ramstage-$(CONFIG_UDELAY_IO) += udelay_io.c ramstage-y += keyboard.c -romstage-$(CONFIG_TPM) += tpm.c +romstage-$(CONFIG_LPC_TPM) += tpm.c romstage-$(CONFIG_USE_OPTION_TABLE) += mc146818rtc_early.c subdirs-y += vga