diff options
author | Bryan Freed <bfreed@chromium.org> | 2017-05-22 11:20:11 +0200 |
---|---|---|
committer | Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> | 2017-06-13 22:02:08 +0300 |
commit | d8c3eab5cb92f37ca8576fc641fa4bfd8a0c8b00 (patch) | |
tree | 18c9216bda1e1697579ef111f68c5bad8f008316 | |
parent | 4cb586a188d468e05649575f0689dd2bf8c122e6 (diff) | |
download | linux-d8c3eab5cb92f37ca8576fc641fa4bfd8a0c8b00.tar.bz2 |
tpm: Apply a sane minimum adapterlimit value for retransmission.
When the I2C Infineon part is attached to an I2C adapter that imposes
a size limitation, large requests will fail with -EOPNOTSUPP. Retry
them with a sane minimum size without re-issuing the 0x05 command
as this appears to occasionally put the TPM in a bad state.
Signed-off-by: Bryan Freed <bfreed@chromium.org>
[rework the patch to adapt to the feedback received]
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Acked-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
-rw-r--r-- | drivers/char/tpm/tpm_i2c_infineon.c | 76 |
1 files changed, 56 insertions, 20 deletions
diff --git a/drivers/char/tpm/tpm_i2c_infineon.c b/drivers/char/tpm/tpm_i2c_infineon.c index dc47fa222a26..79d6bbb58e39 100644 --- a/drivers/char/tpm/tpm_i2c_infineon.c +++ b/drivers/char/tpm/tpm_i2c_infineon.c @@ -70,6 +70,7 @@ struct tpm_inf_dev { u8 buf[TPM_BUFSIZE + sizeof(u8)]; /* max. buffer size + addr */ struct tpm_chip *chip; enum i2c_chip_type chip_type; + unsigned int adapterlimit; }; static struct tpm_inf_dev tpm_dev; @@ -111,6 +112,7 @@ static int iic_tpm_read(u8 addr, u8 *buffer, size_t len) int rc = 0; int count; + unsigned int msglen = len; /* Lock the adapter for the duration of the whole sequence. */ if (!tpm_dev.client->adapter->algo->master_xfer) @@ -131,27 +133,61 @@ static int iic_tpm_read(u8 addr, u8 *buffer, size_t len) usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); } } else { - /* slb9635 protocol should work in all cases */ - for (count = 0; count < MAX_COUNT; count++) { - rc = __i2c_transfer(tpm_dev.client->adapter, &msg1, 1); - if (rc > 0) - break; /* break here to skip sleep */ - - usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); - } - - if (rc <= 0) - goto out; - - /* After the TPM has successfully received the register address - * it needs some time, thus we're sleeping here again, before - * retrieving the data + /* Expect to send one command message and one data message, but + * support looping over each or both if necessary. */ - for (count = 0; count < MAX_COUNT; count++) { - usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); - rc = __i2c_transfer(tpm_dev.client->adapter, &msg2, 1); - if (rc > 0) - break; + while (len > 0) { + /* slb9635 protocol should work in all cases */ + for (count = 0; count < MAX_COUNT; count++) { + rc = __i2c_transfer(tpm_dev.client->adapter, + &msg1, 1); + if (rc > 0) + break; /* break here to skip sleep */ + + usleep_range(SLEEP_DURATION_LOW, + SLEEP_DURATION_HI); + } + + if (rc <= 0) + goto out; + + /* After the TPM has successfully received the register + * address it needs some time, thus we're sleeping here + * again, before retrieving the data + */ + for (count = 0; count < MAX_COUNT; count++) { + if (tpm_dev.adapterlimit) { + msglen = min_t(unsigned int, + tpm_dev.adapterlimit, + len); + msg2.len = msglen; + } + usleep_range(SLEEP_DURATION_LOW, + SLEEP_DURATION_HI); + rc = __i2c_transfer(tpm_dev.client->adapter, + &msg2, 1); + if (rc > 0) { + /* Since len is unsigned, make doubly + * sure we do not underflow it. + */ + if (msglen > len) + len = 0; + else + len -= msglen; + msg2.buf += msglen; + break; + } + /* If the I2C adapter rejected the request (e.g + * when the quirk read_max_len < len) fall back + * to a sane minimum value and try again. + */ + if (rc == -EOPNOTSUPP) + tpm_dev.adapterlimit = + I2C_SMBUS_BLOCK_MAX; + } + + if (rc <= 0) + goto out; } } |