tpm: Provide a generic means to override the chip returned timeouts
authorJason Gunthorpe <jgunthorpe@obsidianresearch.com>
Thu, 22 May 2014 00:26:44 +0000 (18:26 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 17 Sep 2014 16:04:02 +0000 (09:04 -0700)
commit 8e54caf407b98efa05409e1fee0e5381abd2b088 upstream.

Some Atmel TPMs provide completely wrong timeouts from their
TPM_CAP_PROP_TIS_TIMEOUT query. This patch detects that and returns
new correct values via a DID/VID table in the TIS driver.

Tested on ARM using an AT97SC3204T FW version 37.16

[PHuewe: without this fix these 'broken' Atmel TPMs won't function on
older kernels]
Signed-off-by: "Berg, Christopher" <Christopher.Berg@atmel.com>
Signed-off-by: Jason Gunthorpe <jgunthorpe@obsidianresearch.com>
Signed-off-by: Peter Huewe <peterhuewe@gmx.de>
[bwh: Backported to 3.10:
 - Adjust filename, context
 - s/chip->ops->/chip->vendor./]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/char/tpm/tpm.c
drivers/char/tpm/tpm.h
drivers/char/tpm/tpm_tis.c

index 01d6968a9e479c4d28d81c736d0896bdbb350228..f659a571ad23e3b2d10a03a02449126cebd4190f 100644 (file)
@@ -533,11 +533,10 @@ static int tpm_startup(struct tpm_chip *chip, __be16 startup_type)
 int tpm_get_timeouts(struct tpm_chip *chip)
 {
        struct tpm_cmd_t tpm_cmd;
-       struct timeout_t *timeout_cap;
+       unsigned long new_timeout[4];
+       unsigned long old_timeout[4];
        struct duration_t *duration_cap;
        ssize_t rc;
-       u32 timeout;
-       unsigned int scale = 1;
 
        tpm_cmd.header.in = tpm_getcap_header;
        tpm_cmd.params.getcap_in.cap = TPM_CAP_PROP;
@@ -571,25 +570,46 @@ int tpm_get_timeouts(struct tpm_chip *chip)
            != sizeof(tpm_cmd.header.out) + sizeof(u32) + 4 * sizeof(u32))
                return -EINVAL;
 
-       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 && timeout < 1000) {
-               /* timeouts in msec rather usec */
-               scale = 1000;
-               chip->vendor.timeout_adjusted = true;
+       old_timeout[0] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.a);
+       old_timeout[1] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.b);
+       old_timeout[2] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.c);
+       old_timeout[3] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.d);
+       memcpy(new_timeout, old_timeout, sizeof(new_timeout));
+
+       /*
+        * Provide ability for vendor overrides of timeout values in case
+        * of misreporting.
+        */
+       if (chip->vendor.update_timeouts != NULL)
+               chip->vendor.timeout_adjusted =
+                       chip->vendor.update_timeouts(chip, new_timeout);
+
+       if (!chip->vendor.timeout_adjusted) {
+               /* Don't overwrite default if value is 0 */
+               if (new_timeout[0] != 0 && new_timeout[0] < 1000) {
+                       int i;
+
+                       /* timeouts in msec rather usec */
+                       for (i = 0; i != ARRAY_SIZE(new_timeout); i++)
+                               new_timeout[i] *= 1000;
+                       chip->vendor.timeout_adjusted = true;
+               }
        }
-       if (timeout)
-               chip->vendor.timeout_a = usecs_to_jiffies(timeout * scale);
-       timeout = be32_to_cpu(timeout_cap->b);
-       if (timeout)
-               chip->vendor.timeout_b = usecs_to_jiffies(timeout * scale);
-       timeout = be32_to_cpu(timeout_cap->c);
-       if (timeout)
-               chip->vendor.timeout_c = usecs_to_jiffies(timeout * scale);
-       timeout = be32_to_cpu(timeout_cap->d);
-       if (timeout)
-               chip->vendor.timeout_d = usecs_to_jiffies(timeout * scale);
+
+       /* Report adjusted timeouts */
+       if (chip->vendor.timeout_adjusted) {
+               dev_info(chip->dev,
+                        HW_ERR "Adjusting reported timeouts: A %lu->%luus B %lu->%luus C %lu->%luus D %lu->%luus\n",
+                        old_timeout[0], new_timeout[0],
+                        old_timeout[1], new_timeout[1],
+                        old_timeout[2], new_timeout[2],
+                        old_timeout[3], new_timeout[3]);
+       }
+
+       chip->vendor.timeout_a = usecs_to_jiffies(new_timeout[0]);
+       chip->vendor.timeout_b = usecs_to_jiffies(new_timeout[1]);
+       chip->vendor.timeout_c = usecs_to_jiffies(new_timeout[2]);
+       chip->vendor.timeout_d = usecs_to_jiffies(new_timeout[3]);
 
 duration:
        tpm_cmd.header.in = tpm_getcap_header;
index 0770d1d79366d31d4832dd2186d677a6ad3a565c..deffda7678a0b2ec154d5ed99323f2a76d694731 100644 (file)
@@ -95,6 +95,9 @@ struct tpm_vendor_specific {
        int (*send) (struct tpm_chip *, u8 *, size_t);
        void (*cancel) (struct tpm_chip *);
        u8 (*status) (struct tpm_chip *);
+       bool (*update_timeouts)(struct tpm_chip *chip,
+                               unsigned long *timeout_cap);
+
        void (*release) (struct device *);
        struct miscdevice miscdev;
        struct attribute_group *attr_group;
index 8a41b6be23a057bd5ff033f30d7de52ac7085a38..72f21377fa02f975af9a88e16a9a697dc897bf60 100644 (file)
@@ -373,6 +373,36 @@ out_err:
        return rc;
 }
 
+struct tis_vendor_timeout_override {
+       u32 did_vid;
+       unsigned long timeout_us[4];
+};
+
+static const struct tis_vendor_timeout_override vendor_timeout_overrides[] = {
+       /* Atmel 3204 */
+       { 0x32041114, { (TIS_SHORT_TIMEOUT*1000), (TIS_LONG_TIMEOUT*1000),
+                       (TIS_SHORT_TIMEOUT*1000), (TIS_SHORT_TIMEOUT*1000) } },
+};
+
+static bool tpm_tis_update_timeouts(struct tpm_chip *chip,
+                                   unsigned long *timeout_cap)
+{
+       int i;
+       u32 did_vid;
+
+       did_vid = ioread32(chip->vendor.iobase + TPM_DID_VID(0));
+
+       for (i = 0; i != ARRAY_SIZE(vendor_timeout_overrides); i++) {
+               if (vendor_timeout_overrides[i].did_vid != did_vid)
+                       continue;
+               memcpy(timeout_cap, vendor_timeout_overrides[i].timeout_us,
+                      sizeof(vendor_timeout_overrides[i].timeout_us));
+               return true;
+       }
+
+       return false;
+}
+
 /*
  * Early probing for iTPM with STS_DATA_EXPECT flaw.
  * Try sending command without itpm flag set and if that
@@ -475,6 +505,7 @@ static struct tpm_vendor_specific tpm_tis = {
        .recv = tpm_tis_recv,
        .send = tpm_tis_send,
        .cancel = tpm_tis_ready,
+       .update_timeouts = tpm_tis_update_timeouts,
        .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
        .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
        .req_canceled = tpm_tis_req_canceled,