Commit de9e33df authored by Jonathan McDowell's avatar Jonathan McDowell Committed by Jarkko Sakkinen
Browse files

tpm, tpm_tis: Workaround failed command reception on Infineon devices

Some Infineon devices have a issue where the status register will get
stuck with a quick REQUEST_USE / COMMAND_READY sequence. This is not
simply a matter of requiring a longer timeout; the work around is to
retry the command submission. Add appropriate logic to do this in the
send path.

This is fixed in later firmware revisions, but those are not always
available, and cannot generally be easily updated from outside a
firmware environment.

Testing has been performed with a simple repeated loop of doing a
TPM2_CC_GET_CAPABILITY for TPM_CAP_PROP_MANUFACTURER using the Go code
at:

  https://the.earth.li/~noodles/tpm-stuff/timeout-reproducer-simple.go



It can take several hours to reproduce, and several million operations.

Signed-off-by: default avatarJonathan McDowell <noodles@meta.com>
Reviewed-by: default avatarJarkko Sakkinen <jarkko@kernel.org>
Signed-off-by: default avatarJarkko Sakkinen <jarkko@kernel.org>
parent 7146dffa
Loading
Loading
Loading
Loading
+14 −3
Original line number Diff line number Diff line
@@ -463,6 +463,9 @@ static int tpm_tis_send_data(struct tpm_chip *chip, const u8 *buf, size_t len)

		if (wait_for_tpm_stat(chip, TPM_STS_VALID, chip->timeout_c,
					&priv->int_queue, false) < 0) {
			if (test_bit(TPM_TIS_STATUS_VALID_RETRY, &priv->flags))
				rc = -EAGAIN;
			else
				rc = -ETIME;
			goto out_err;
		}
@@ -480,6 +483,9 @@ static int tpm_tis_send_data(struct tpm_chip *chip, const u8 *buf, size_t len)

	if (wait_for_tpm_stat(chip, TPM_STS_VALID, chip->timeout_c,
				&priv->int_queue, false) < 0) {
		if (test_bit(TPM_TIS_STATUS_VALID_RETRY, &priv->flags))
			rc = -EAGAIN;
		else
			rc = -ETIME;
		goto out_err;
	}
@@ -545,9 +551,11 @@ static int tpm_tis_send_main(struct tpm_chip *chip, const u8 *buf, size_t len)
		if (rc >= 0)
			/* Data transfer done successfully */
			break;
		else if (rc != -EIO)
		else if (rc != -EAGAIN && rc != -EIO)
			/* Data transfer failed, not recoverable */
			return rc;

		usleep_range(priv->timeout_min, priv->timeout_max);
	}

	/* go and do it */
@@ -1143,6 +1151,9 @@ int tpm_tis_core_init(struct device *dev, struct tpm_tis_data *priv, int irq,
		priv->timeout_max = TIS_TIMEOUT_MAX_ATML;
	}

	if (priv->manufacturer_id == TPM_VID_IFX)
		set_bit(TPM_TIS_STATUS_VALID_RETRY, &priv->flags);

	if (is_bsw()) {
		priv->ilb_base_addr = ioremap(INTEL_LEGACY_BLK_BASE_ADDR,
					ILB_REMAP_SIZE);
+1 −0
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ enum tpm_tis_flags {
	TPM_TIS_INVALID_STATUS		= 1,
	TPM_TIS_DEFAULT_CANCELLATION	= 2,
	TPM_TIS_IRQ_TESTED		= 3,
	TPM_TIS_STATUS_VALID_RETRY	= 4,
};

struct tpm_tis_data {
+1 −0
Original line number Diff line number Diff line
@@ -335,6 +335,7 @@ enum tpm2_cc_attrs {
#define TPM_VID_WINBOND  0x1050
#define TPM_VID_STM      0x104A
#define TPM_VID_ATML     0x1114
#define TPM_VID_IFX      0x15D1

enum tpm_chip_flags {
	TPM_CHIP_FLAG_BOOTSTRAPPED		= BIT(0),