diff options
author | Jason Gunthorpe <jgunthorpe@obsidianresearch.com> | 2014-05-21 20:26:44 -0400 |
---|---|---|
committer | Peter Huewe <peterhuewe@gmx.de> | 2014-07-29 17:10:56 -0400 |
commit | 8e54caf407b98efa05409e1fee0e5381abd2b088 (patch) | |
tree | 690ed020c643ca869e5fc27686d518cb3a9f5f1e | |
parent | 3e14d83ef94a5806a865b85b513b4e891923c19b (diff) |
tpm: Provide a generic means to override the chip returned timeouts
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
Cc: <stable@vger.kernel.org>
[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>
-rw-r--r-- | drivers/char/tpm/tpm-interface.c | 62 | ||||
-rw-r--r-- | drivers/char/tpm/tpm_tis.c | 31 | ||||
-rw-r--r-- | include/linux/tpm.h | 3 |
3 files changed, 75 insertions, 21 deletions
diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index bef6cceffc3a..6af17002a115 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c | |||
@@ -491,11 +491,10 @@ static int tpm_startup(struct tpm_chip *chip, __be16 startup_type) | |||
491 | int tpm_get_timeouts(struct tpm_chip *chip) | 491 | int tpm_get_timeouts(struct tpm_chip *chip) |
492 | { | 492 | { |
493 | struct tpm_cmd_t tpm_cmd; | 493 | struct tpm_cmd_t tpm_cmd; |
494 | struct timeout_t *timeout_cap; | 494 | unsigned long new_timeout[4]; |
495 | unsigned long old_timeout[4]; | ||
495 | struct duration_t *duration_cap; | 496 | struct duration_t *duration_cap; |
496 | ssize_t rc; | 497 | ssize_t rc; |
497 | u32 timeout; | ||
498 | unsigned int scale = 1; | ||
499 | 498 | ||
500 | tpm_cmd.header.in = tpm_getcap_header; | 499 | tpm_cmd.header.in = tpm_getcap_header; |
501 | tpm_cmd.params.getcap_in.cap = TPM_CAP_PROP; | 500 | tpm_cmd.params.getcap_in.cap = TPM_CAP_PROP; |
@@ -529,25 +528,46 @@ int tpm_get_timeouts(struct tpm_chip *chip) | |||
529 | != sizeof(tpm_cmd.header.out) + sizeof(u32) + 4 * sizeof(u32)) | 528 | != sizeof(tpm_cmd.header.out) + sizeof(u32) + 4 * sizeof(u32)) |
530 | return -EINVAL; | 529 | return -EINVAL; |
531 | 530 | ||
532 | timeout_cap = &tpm_cmd.params.getcap_out.cap.timeout; | 531 | old_timeout[0] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.a); |
533 | /* Don't overwrite default if value is 0 */ | 532 | old_timeout[1] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.b); |
534 | timeout = be32_to_cpu(timeout_cap->a); | 533 | old_timeout[2] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.c); |
535 | if (timeout && timeout < 1000) { | 534 | old_timeout[3] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.d); |
536 | /* timeouts in msec rather usec */ | 535 | memcpy(new_timeout, old_timeout, sizeof(new_timeout)); |
537 | scale = 1000; | 536 | |
538 | chip->vendor.timeout_adjusted = true; | 537 | /* |
538 | * Provide ability for vendor overrides of timeout values in case | ||
539 | * of misreporting. | ||
540 | */ | ||
541 | if (chip->ops->update_timeouts != NULL) | ||
542 | chip->vendor.timeout_adjusted = | ||
543 | chip->ops->update_timeouts(chip, new_timeout); | ||
544 | |||
545 | if (!chip->vendor.timeout_adjusted) { | ||
546 | /* Don't overwrite default if value is 0 */ | ||
547 | if (new_timeout[0] != 0 && new_timeout[0] < 1000) { | ||
548 | int i; | ||
549 | |||
550 | /* timeouts in msec rather usec */ | ||
551 | for (i = 0; i != ARRAY_SIZE(new_timeout); i++) | ||
552 | new_timeout[i] *= 1000; | ||
553 | chip->vendor.timeout_adjusted = true; | ||
554 | } | ||
539 | } | 555 | } |
540 | if (timeout) | 556 | |
541 | chip->vendor.timeout_a = usecs_to_jiffies(timeout * scale); | 557 | /* Report adjusted timeouts */ |
542 | timeout = be32_to_cpu(timeout_cap->b); | 558 | if (chip->vendor.timeout_adjusted) { |
543 | if (timeout) | 559 | dev_info(chip->dev, |
544 | chip->vendor.timeout_b = usecs_to_jiffies(timeout * scale); | 560 | HW_ERR "Adjusting reported timeouts: A %lu->%luus B %lu->%luus C %lu->%luus D %lu->%luus\n", |
545 | timeout = be32_to_cpu(timeout_cap->c); | 561 | old_timeout[0], new_timeout[0], |
546 | if (timeout) | 562 | old_timeout[1], new_timeout[1], |
547 | chip->vendor.timeout_c = usecs_to_jiffies(timeout * scale); | 563 | old_timeout[2], new_timeout[2], |
548 | timeout = be32_to_cpu(timeout_cap->d); | 564 | old_timeout[3], new_timeout[3]); |
549 | if (timeout) | 565 | } |
550 | chip->vendor.timeout_d = usecs_to_jiffies(timeout * scale); | 566 | |
567 | chip->vendor.timeout_a = usecs_to_jiffies(new_timeout[0]); | ||
568 | chip->vendor.timeout_b = usecs_to_jiffies(new_timeout[1]); | ||
569 | chip->vendor.timeout_c = usecs_to_jiffies(new_timeout[2]); | ||
570 | chip->vendor.timeout_d = usecs_to_jiffies(new_timeout[3]); | ||
551 | 571 | ||
552 | duration: | 572 | duration: |
553 | tpm_cmd.header.in = tpm_getcap_header; | 573 | tpm_cmd.header.in = tpm_getcap_header; |
diff --git a/drivers/char/tpm/tpm_tis.c b/drivers/char/tpm/tpm_tis.c index a9ed2270c25d..2c46734b266d 100644 --- a/drivers/char/tpm/tpm_tis.c +++ b/drivers/char/tpm/tpm_tis.c | |||
@@ -373,6 +373,36 @@ out_err: | |||
373 | return rc; | 373 | return rc; |
374 | } | 374 | } |
375 | 375 | ||
376 | struct tis_vendor_timeout_override { | ||
377 | u32 did_vid; | ||
378 | unsigned long timeout_us[4]; | ||
379 | }; | ||
380 | |||
381 | static const struct tis_vendor_timeout_override vendor_timeout_overrides[] = { | ||
382 | /* Atmel 3204 */ | ||
383 | { 0x32041114, { (TIS_SHORT_TIMEOUT*1000), (TIS_LONG_TIMEOUT*1000), | ||
384 | (TIS_SHORT_TIMEOUT*1000), (TIS_SHORT_TIMEOUT*1000) } }, | ||
385 | }; | ||
386 | |||
387 | static bool tpm_tis_update_timeouts(struct tpm_chip *chip, | ||
388 | unsigned long *timeout_cap) | ||
389 | { | ||
390 | int i; | ||
391 | u32 did_vid; | ||
392 | |||
393 | did_vid = ioread32(chip->vendor.iobase + TPM_DID_VID(0)); | ||
394 | |||
395 | for (i = 0; i != ARRAY_SIZE(vendor_timeout_overrides); i++) { | ||
396 | if (vendor_timeout_overrides[i].did_vid != did_vid) | ||
397 | continue; | ||
398 | memcpy(timeout_cap, vendor_timeout_overrides[i].timeout_us, | ||
399 | sizeof(vendor_timeout_overrides[i].timeout_us)); | ||
400 | return true; | ||
401 | } | ||
402 | |||
403 | return false; | ||
404 | } | ||
405 | |||
376 | /* | 406 | /* |
377 | * Early probing for iTPM with STS_DATA_EXPECT flaw. | 407 | * Early probing for iTPM with STS_DATA_EXPECT flaw. |
378 | * Try sending command without itpm flag set and if that | 408 | * Try sending command without itpm flag set and if that |
@@ -437,6 +467,7 @@ static const struct tpm_class_ops tpm_tis = { | |||
437 | .recv = tpm_tis_recv, | 467 | .recv = tpm_tis_recv, |
438 | .send = tpm_tis_send, | 468 | .send = tpm_tis_send, |
439 | .cancel = tpm_tis_ready, | 469 | .cancel = tpm_tis_ready, |
470 | .update_timeouts = tpm_tis_update_timeouts, | ||
440 | .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID, | 471 | .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID, |
441 | .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID, | 472 | .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID, |
442 | .req_canceled = tpm_tis_req_canceled, | 473 | .req_canceled = tpm_tis_req_canceled, |
diff --git a/include/linux/tpm.h b/include/linux/tpm.h index fff1d0976f80..8350c538b486 100644 --- a/include/linux/tpm.h +++ b/include/linux/tpm.h | |||
@@ -39,6 +39,9 @@ struct tpm_class_ops { | |||
39 | int (*send) (struct tpm_chip *chip, u8 *buf, size_t len); | 39 | int (*send) (struct tpm_chip *chip, u8 *buf, size_t len); |
40 | void (*cancel) (struct tpm_chip *chip); | 40 | void (*cancel) (struct tpm_chip *chip); |
41 | u8 (*status) (struct tpm_chip *chip); | 41 | u8 (*status) (struct tpm_chip *chip); |
42 | bool (*update_timeouts)(struct tpm_chip *chip, | ||
43 | unsigned long *timeout_cap); | ||
44 | |||
42 | }; | 45 | }; |
43 | 46 | ||
44 | #if defined(CONFIG_TCG_TPM) || defined(CONFIG_TCG_TPM_MODULE) | 47 | #if defined(CONFIG_TCG_TPM) || defined(CONFIG_TCG_TPM_MODULE) |