Index: drivers/tpm/slb9635_i2c/tpm.c |
diff --git a/drivers/tpm/slb9635_i2c/tpm.c b/drivers/tpm/slb9635_i2c/tpm.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..bb0467448ac8239742ebd7d4f9052782156c9b64 |
--- /dev/null |
+++ b/drivers/tpm/slb9635_i2c/tpm.c |
@@ -0,0 +1,562 @@ |
+/* |
+ * Copyright (C) 2011 Infineon Technologies |
+ * |
+ * Authors: |
+ * |
+ * |
+ * 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. |
+ * |
+ * 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. |
+ * |
+ */ |
+ |
+#include <malloc.h> |
+ |
+#include "tpm.h" |
+#include "tpm_tis_i2c.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 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 jiffies to wait |
+ */ |
+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; |
+ else |
+ return duration; |
+} |
+EXPORT_SYMBOL_GPL(tpm_calc_ordinal_duration); |
+ |
+#define TPM_CMD_COUNT_BYTE 2 |
+#define TPM_CMD_ORDINAL_BYTE 6 |
+ |
+ssize_t tpm_transmit(const unsigned char *buf, size_t bufsiz) |
+{ |
+ ssize_t rc; |
+ u32 count, ordinal; |
+ unsigned long stop; |
+ |
+ struct tpm_chip *chip = &g_chip; |
+ |
+ /* switch endianess: big->little */ |
+ count = switch_endian32(&buf[TPM_CMD_COUNT_BYTE]); |
+ ordinal = switch_endian32(&buf[TPM_CMD_ORDINAL_BYTE]); |
+ |
+ if (count == 0) { |
+ dev_err(chip->dev, "no data\n"); |
+ return -ENODATA; |
+ } |
+ if (count > bufsiz) { |
+ dev_err(chip->dev, |
+ "invalid count value %x %zx\n", count, bufsiz); |
+ return -E2BIG; |
+ } |
+ |
+ rc = chip->vendor.send(chip, (u8 *) buf, count); |
+ if (rc < 0) { |
+ dev_err(chip->dev, "tpm_transmit: tpm_send: error %zd\n", rc); |
+ goto out; |
+ } |
+ |
+ if (chip->vendor.irq) |
+ goto out_recv; |
+ |
+ jiffies = 0; |
+ stop = jiffies + tpm_calc_ordinal_duration(chip, ordinal); |
+ do { |
+ dbg_printf("waiting for status...\n"); |
+ u8 status = chip->vendor.status(chip); |
+ if ((status & chip->vendor.req_complete_mask) == |
+ chip->vendor.req_complete_val) { |
+ dbg_printf("...got it;\n"); |
+ goto out_recv; |
+ } |
+ |
+ if ((status == chip->vendor.req_canceled)) { |
+ dev_err(chip->dev, "Operation Canceled\n"); |
+ rc = -ECANCELED; |
+ goto out; |
+ } |
+ |
+ msleep2(TPM_TIMEOUT); /* CHECK */ |
+ } while (time_before(jiffies, stop)); |
+ |
+ chip->vendor.cancel(chip); |
+ dev_err(chip->dev, "Operation Timed out\n"); |
+ rc = -ETIME; |
+ goto out; |
+ |
+out_recv: |
+ |
+ dbg_printf("out_recv: reading response...\n"); |
+ rc = chip->vendor.recv(chip, (u8 *) buf, bufsiz); |
+ if (rc < 0) |
+ dev_err(chip->dev, "tpm_transmit: tpm_recv: error %zd\n", rc); |
+out: |
+ return rc; |
+} |
+ |
+#define TPM_ERROR_SIZE 10 |
+ |
+enum tpm_capabilities { |
+ TPM_CAP_PROP = cpu_to_be32(5), |
+}; |
+ |
+enum tpm_sub_capabilities { |
+ TPM_CAP_PROP_TIS_TIMEOUT = cpu_to_be32(0x115), |
+ TPM_CAP_PROP_TIS_DURATION = cpu_to_be32(0x120), |
+}; |
+ |
+static ssize_t transmit_cmd(struct tpm_chip *chip, struct tpm_cmd_t *cmd, |
+ int len, const char *desc) |
+{ |
+ int err; |
+ |
+ len = tpm_transmit((u8 *) cmd, len); |
+ if (len < 0) |
+ return len; |
+ if (len == TPM_ERROR_SIZE) { |
+ err = be32_to_cpu(cmd->header.out.return_code); |
+ dev_dbg(chip->dev, "A TPM error (%d) occurred %s\n", err, desc); |
+ return err; |
+ } |
+ return 0; |
+} |
+ |
+#define TPM_INTERNAL_RESULT_SIZE 200 |
+#define TPM_TAG_RQU_COMMAND cpu_to_be16(193) |
+#define TPM_ORD_GET_CAP cpu_to_be32(101) |
+ |
+static const struct tpm_input_header tpm_getcap_header = { |
+ .tag = TPM_TAG_RQU_COMMAND, |
+ .length = cpu_to_be32(22), |
+ .ordinal = TPM_ORD_GET_CAP |
+}; |
+ |
+void tpm_get_timeouts(struct tpm_chip *chip) |
+{ |
+ struct tpm_cmd_t tpm_cmd; |
+ struct timeout_t *timeout_cap; |
+ struct duration_t *duration_cap; |
+ ssize_t rc; |
+ u32 timeout; |
+ |
+ tpm_cmd.header.in = tpm_getcap_header; |
+ tpm_cmd.params.getcap_in.cap = TPM_CAP_PROP; |
+ tpm_cmd.params.getcap_in.subcap_size = cpu_to_be32(4); |
+ tpm_cmd.params.getcap_in.subcap = TPM_CAP_PROP_TIS_TIMEOUT; |
+ |
+ rc = transmit_cmd(chip, &tpm_cmd, TPM_INTERNAL_RESULT_SIZE, |
+ "attempting to determine the timeouts"); |
+ if (rc) |
+ goto duration; |
+ |
+ if (be32_to_cpu(tpm_cmd.header.out.length) |
+ != 4 * sizeof(u32)) |
+ goto duration; |
+ |
+ timeout_cap = &tpm_cmd.params.getcap_out.cap.timeout; |
+ /* Don't overwrite default if value is 0 */ |
+ timeout = be32_to_cpu(timeout_cap->a); |
+ if (timeout) |
+ chip->vendor.timeout_a = usecs_to_jiffies(timeout); |
+ timeout = be32_to_cpu(timeout_cap->b); |
+ if (timeout) |
+ chip->vendor.timeout_b = usecs_to_jiffies(timeout); |
+ timeout = be32_to_cpu(timeout_cap->c); |
+ if (timeout) |
+ chip->vendor.timeout_c = usecs_to_jiffies(timeout); |
+ timeout = be32_to_cpu(timeout_cap->d); |
+ if (timeout) |
+ chip->vendor.timeout_d = usecs_to_jiffies(timeout); |
+ |
+duration: |
+ tpm_cmd.header.in = tpm_getcap_header; |
+ tpm_cmd.params.getcap_in.cap = TPM_CAP_PROP; |
+ tpm_cmd.params.getcap_in.subcap_size = cpu_to_be32(4); |
+ tpm_cmd.params.getcap_in.subcap = TPM_CAP_PROP_TIS_DURATION; |
+ |
+ rc = transmit_cmd(chip, &tpm_cmd, TPM_INTERNAL_RESULT_SIZE, |
+ "attempting to determine the durations"); |
+ if (rc) |
+ return; |
+ |
+ if (be32_to_cpu(tpm_cmd.header.out.return_code) |
+ != 3 * sizeof(u32)) |
+ return; |
+ duration_cap = &tpm_cmd.params.getcap_out.cap.duration; |
+ chip->vendor.duration[TPM_SHORT] = |
+ usecs_to_jiffies(be32_to_cpu(duration_cap->tpm_short)); |
+ /* The Broadcom BCM0102 chipset in a Dell Latitude D820 gets the above |
+ * value wrong and apparently reports msecs rather than usecs. So we |
+ * fix up the resulting too-small TPM_SHORT value to make things work. |
+ */ |
+ if (chip->vendor.duration[TPM_SHORT] < (HZ / 100)) |
+ chip->vendor.duration[TPM_SHORT] = HZ; |
+ |
+ chip->vendor.duration[TPM_MEDIUM] = |
+ usecs_to_jiffies(be32_to_cpu(duration_cap->tpm_medium)); |
+ chip->vendor.duration[TPM_LONG] = |
+ usecs_to_jiffies(be32_to_cpu(duration_cap->tpm_long)); |
+} |
+EXPORT_SYMBOL_GPL(tpm_get_timeouts); |
+ |
+void tpm_continue_selftest(struct tpm_chip *chip) |
+{ |
+ u8 data[] = { |
+ 0, 193, /* TPM_TAG_RQU_COMMAND */ |
+ 0, 0, 0, 10, /* length */ |
+ 0, 0, 0, 83, /* TPM_ORD_ContinueSelfTest */ |
+ }; |
+ |
+ tpm_transmit(data, sizeof(data)); |
+} |
+EXPORT_SYMBOL_GPL(tpm_continue_selftest); |
+ |
+/* |
+void tpm_startup(struct tpm_chip *chip) |
+{ |
+ u8 data[] = { |
+ 0, 193, *//* TPM_TAG_RQU_COMMAND *//* |
+ 0, 0, 0, 12, *//* length *//* |
+ 0, 0, 0, 153, *//* TPM_ORD_Startup *//* |
+ 0, 0 |
+ }; |
+ |
+ tpm_transmit(data, sizeof(data)); |
+} |
+*/ |
+EXPORT_SYMBOL_GPL(tpm_startup); |
+ |
+struct tpm_chip *tpm_register_hardware(struct device *dev, |
+ 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; |
+} |
+EXPORT_SYMBOL_GPL(tpm_register_hardware); |
+ |
+int tpm_open(void) |
+{ |
+ if (g_chip.is_open) |
+ return 0; |
+ else |
+ return tpm_tis_i2c_init(NULL); |
+} |
+ |
+void tpm_close(void) |
+{ |
+ if (g_chip.is_open) { |
+ tpm_tis_i2c_cleanup(&g_chip); |
+ g_chip.is_open = 0; |
+ } |
+} |