Commit 8e54caf407b98efa05409e1fee0e5381abd2b088
Committed by
Peter Huewe
1 parent
3e14d83ef9
Exists in
ti-lsk-linux-4.1.y
and in
10 other branches
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>
Showing 3 changed files with 75 additions and 21 deletions Side-by-side Diff
drivers/char/tpm/tpm-interface.c
... | ... | @@ -491,11 +491,10 @@ |
491 | 491 | int tpm_get_timeouts(struct tpm_chip *chip) |
492 | 492 | { |
493 | 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 | 496 | struct duration_t *duration_cap; |
496 | 497 | ssize_t rc; |
497 | - u32 timeout; | |
498 | - unsigned int scale = 1; | |
499 | 498 | |
500 | 499 | tpm_cmd.header.in = tpm_getcap_header; |
501 | 500 | tpm_cmd.params.getcap_in.cap = TPM_CAP_PROP; |
502 | 501 | |
... | ... | @@ -529,25 +528,46 @@ |
529 | 528 | != sizeof(tpm_cmd.header.out) + sizeof(u32) + 4 * sizeof(u32)) |
530 | 529 | return -EINVAL; |
531 | 530 | |
532 | - timeout_cap = &tpm_cmd.params.getcap_out.cap.timeout; | |
533 | - /* Don't overwrite default if value is 0 */ | |
534 | - timeout = be32_to_cpu(timeout_cap->a); | |
535 | - if (timeout && timeout < 1000) { | |
536 | - /* timeouts in msec rather usec */ | |
537 | - scale = 1000; | |
538 | - chip->vendor.timeout_adjusted = true; | |
531 | + old_timeout[0] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.a); | |
532 | + old_timeout[1] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.b); | |
533 | + old_timeout[2] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.c); | |
534 | + old_timeout[3] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.d); | |
535 | + memcpy(new_timeout, old_timeout, sizeof(new_timeout)); | |
536 | + | |
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) | |
541 | - chip->vendor.timeout_a = usecs_to_jiffies(timeout * scale); | |
542 | - timeout = be32_to_cpu(timeout_cap->b); | |
543 | - if (timeout) | |
544 | - chip->vendor.timeout_b = usecs_to_jiffies(timeout * scale); | |
545 | - timeout = be32_to_cpu(timeout_cap->c); | |
546 | - if (timeout) | |
547 | - chip->vendor.timeout_c = usecs_to_jiffies(timeout * scale); | |
548 | - timeout = be32_to_cpu(timeout_cap->d); | |
549 | - if (timeout) | |
550 | - chip->vendor.timeout_d = usecs_to_jiffies(timeout * scale); | |
556 | + | |
557 | + /* Report adjusted timeouts */ | |
558 | + if (chip->vendor.timeout_adjusted) { | |
559 | + dev_info(chip->dev, | |
560 | + HW_ERR "Adjusting reported timeouts: A %lu->%luus B %lu->%luus C %lu->%luus D %lu->%luus\n", | |
561 | + old_timeout[0], new_timeout[0], | |
562 | + old_timeout[1], new_timeout[1], | |
563 | + old_timeout[2], new_timeout[2], | |
564 | + old_timeout[3], new_timeout[3]); | |
565 | + } | |
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 | 572 | duration: |
553 | 573 | tpm_cmd.header.in = tpm_getcap_header; |
drivers/char/tpm/tpm_tis.c
... | ... | @@ -373,6 +373,36 @@ |
373 | 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 | 407 | * Early probing for iTPM with STS_DATA_EXPECT flaw. |
378 | 408 | * Try sending command without itpm flag set and if that |
... | ... | @@ -437,6 +467,7 @@ |
437 | 467 | .recv = tpm_tis_recv, |
438 | 468 | .send = tpm_tis_send, |
439 | 469 | .cancel = tpm_tis_ready, |
470 | + .update_timeouts = tpm_tis_update_timeouts, | |
440 | 471 | .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID, |
441 | 472 | .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID, |
442 | 473 | .req_canceled = tpm_tis_req_canceled, |
include/linux/tpm.h
... | ... | @@ -39,6 +39,9 @@ |
39 | 39 | int (*send) (struct tpm_chip *chip, u8 *buf, size_t len); |
40 | 40 | void (*cancel) (struct tpm_chip *chip); |
41 | 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 | 47 | #if defined(CONFIG_TCG_TPM) || defined(CONFIG_TCG_TPM_MODULE) |