/* * 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 #include #include #include #include "tpm_private.h" DECLARE_GLOBAL_DATA_PTR; /* TPM configuration */ struct tpm { #ifdef CONFIG_DM_I2C struct udevice *dev; #else int i2c_bus; int slave_addr; int old_bus; #endif char inited; } tpm; /* Global structure for tpm chip data */ static struct tpm_chip g_chip; enum tpm_duration { TPM_SHORT = 0, TPM_MEDIUM = 1, TPM_LONG = 2, TPM_UNDEFINED, }; /* Extended error numbers from linux (see errno.h) */ #define ECANCELED 125 /* Operation Canceled */ /* Timer frequency. Corresponds to msec timer resolution*/ #define HZ 1000 #define TPM_MAX_ORDINAL 243 #define TPM_MAX_PROTECTED_ORDINAL 12 #define TPM_PROTECTED_ORDINAL_MASK 0xFF #define TPM_CMD_COUNT_BYTE 2 #define TPM_CMD_ORDINAL_BYTE 6 /* * 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 u8 tpm_protected_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 u8 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, u32 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_protected_ordinal_duration[ ordinal & TPM_PROTECTED_ORDINAL_MASK]; } if (duration_idx != TPM_UNDEFINED) duration = chip->vendor.duration[duration_idx]; if (duration <= 0) return 2 * 60 * HZ; /* Two minutes timeout */ else return duration; } static ssize_t tpm_transmit(const unsigned char *buf, size_t bufsiz) { int rc; u32 count, ordinal; unsigned long start, stop; struct tpm_chip *chip = &g_chip; /* switch endianess: big->little */ count = get_unaligned_be32(buf + TPM_CMD_COUNT_BYTE); ordinal = get_unaligned_be32(buf + TPM_CMD_ORDINAL_BYTE); if (count == 0) { error("no data\n"); return -ENODATA; } if (count > bufsiz) { error("invalid count value %x %zx\n", count, bufsiz); return -E2BIG; } debug("Calling send\n"); rc = chip->vendor.send(chip, (u8 *)buf, count); debug(" ... done calling send\n"); if (rc < 0) { error("tpm_transmit: tpm_send: error %d\n", rc); goto out; } if (chip->vendor.irq) goto out_recv; start = get_timer(0); stop = tpm_calc_ordinal_duration(chip, ordinal); do { debug("waiting for status... %ld %ld\n", start, stop); u8 status = chip->vendor.status(chip); if ((status & chip->vendor.req_complete_mask) == chip->vendor.req_complete_val) { debug("...got it;\n"); goto out_recv; } if (status == chip->vendor.req_canceled) { error("Operation Canceled\n"); rc = -ECANCELED; goto out; } udelay(TPM_TIMEOUT * 1000); } while (get_timer(start) < stop); chip->vendor.cancel(chip); error("Operation Timed out\n"); rc = -ETIME; goto out; out_recv: debug("out_recv: reading response...\n"); rc = chip->vendor.recv(chip, (u8 *)buf, TPM_BUFSIZE); if (rc < 0) error("tpm_transmit: tpm_recv: error %d\n", rc); out: return rc; } #ifdef CONFIG_DM_I2C static int tpm_open_dev(struct udevice *dev) { int rc; debug("%s: start\n", __func__); if (g_chip.is_open) return -EBUSY; rc = tpm_vendor_init_dev(dev); if (rc < 0) g_chip.is_open = 0; return rc; } #else static int tpm_open(uint32_t dev_addr) { int rc; if (g_chip.is_open) return -EBUSY; rc = tpm_vendor_init(dev_addr); if (rc < 0) g_chip.is_open = 0; return rc; } #endif static void tpm_close(void) { if (g_chip.is_open) { tpm_vendor_cleanup(&g_chip); g_chip.is_open = 0; } } static int tpm_select(void) { #ifndef CONFIG_DM_I2C int ret; tpm.old_bus = i2c_get_bus_num(); if (tpm.old_bus != tpm.i2c_bus) { ret = i2c_set_bus_num(tpm.i2c_bus); if (ret) { debug("%s: Fail to set i2c bus %d\n", __func__, tpm.i2c_bus); return -1; } } #endif return 0; } static int tpm_deselect(void) { #ifndef CONFIG_DM_I2C int ret; if (tpm.old_bus != i2c_get_bus_num()) { ret = i2c_set_bus_num(tpm.old_bus); if (ret) { debug("%s: Fail to restore i2c bus %d\n", __func__, tpm.old_bus); return -1; } } tpm.old_bus = -1; #endif return 0; } /** * Decode TPM configuration. * * @param dev Returns a configuration of TPM device * @return 0 if ok, -1 on error */ static int tpm_decode_config(struct tpm *dev) { const void *blob = gd->fdt_blob; int parent; int node; node = fdtdec_next_compatible(blob, 0, COMPAT_INFINEON_SLB9635_TPM); if (node < 0) { node = fdtdec_next_compatible(blob, 0, COMPAT_INFINEON_SLB9645_TPM); } if (node < 0) { debug("%s: Node not found\n", __func__); return -1; } parent = fdt_parent_offset(blob, node); if (parent < 0) { debug("%s: Cannot find node parent\n", __func__); return -1; } #ifdef CONFIG_DM_I2C struct udevice *bus; int chip_addr; int ret; /* * TODO(sjg@chromium.org): Remove this when driver model supports * TPMs */ ret = uclass_get_device_by_of_offset(UCLASS_I2C, parent, &bus); if (ret) { debug("Cannot find bus for node '%s: ret=%d'\n", fdt_get_name(blob, parent, NULL), ret); return ret; } chip_addr = fdtdec_get_int(blob, node, "reg", -1); if (chip_addr == -1) { debug("Cannot find reg property for node '%s: ret=%d'\n", fdt_get_name(blob, node, NULL), ret); return ret; } /* * TODO(sjg@chromium.org): Older TPMs will need to use the older method * in iic_tpm_read() so the offset length needs to be 0 here. */ ret = i2c_get_chip(bus, chip_addr, 1, &dev->dev); if (ret) { debug("Cannot find device for node '%s: ret=%d'\n", fdt_get_name(blob, node, NULL), ret); return ret; } #else int i2c_bus; i2c_bus = i2c_get_bus_num_fdt(parent); if (i2c_bus < 0) return -1; dev->i2c_bus = i2c_bus; dev->slave_addr = fdtdec_get_addr(blob, node, "reg"); #endif return 0; } 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 tis_init(void) { if (tpm.inited) return 0; if (tpm_decode_config(&tpm)) return -1; if (tpm_select()) return -1; #ifndef CONFIG_DM_I2C /* * Probe TPM twice; the first probing might fail because TPM is asleep, * and the probing can wake up TPM. */ if (i2c_probe(tpm.slave_addr) && i2c_probe(tpm.slave_addr)) { debug("%s: fail to probe i2c addr 0x%x\n", __func__, tpm.slave_addr); return -1; } #endif tpm_deselect(); debug("%s: done\n", __func__); tpm.inited = 1; return 0; } int tis_open(void) { int rc; if (!tpm.inited) return -1; if (tpm_select()) return -1; #ifdef CONFIG_DM_I2C rc = tpm_open_dev(tpm.dev); #else rc = tpm_open(tpm.slave_addr); #endif tpm_deselect(); return rc; } int tis_close(void) { if (!tpm.inited) return -1; if (tpm_select()) return -1; tpm_close(); tpm_deselect(); return 0; } int tis_sendrecv(const uint8_t *sendbuf, size_t sbuf_size, uint8_t *recvbuf, size_t *rbuf_len) { int len; uint8_t buf[4096]; if (!tpm.inited) return -1; if (sizeof(buf) < sbuf_size) return -1; memcpy(buf, sendbuf, sbuf_size); if (tpm_select()) return -1; len = tpm_transmit(buf, sbuf_size); tpm_deselect(); if (len < 10) { *rbuf_len = 0; return -1; } memcpy(recvbuf, buf, len); *rbuf_len = len; return 0; }