Commit 481c2d14 authored by Lino Sanfilippo's avatar Lino Sanfilippo Committed by Jarkko Sakkinen
Browse files

tpm,tpm_tis: Disable interrupts after 1000 unhandled IRQs



After activation of interrupts for TPM TIS drivers 0-day reports an
interrupt storm on an Inspur NF5180M6 server.

Fix this by detecting the storm and falling back to polling:
Count the number of unhandled interrupts within a 10 ms time interval. In
case that more than 1000 were unhandled deactivate interrupts entirely,
deregister the handler and use polling instead.

Also print a note to point to the tpm_tis_dmi_table.

Since the interrupt deregistration function devm_free_irq() waits for all
interrupt handlers to finish, only trigger a worker in the interrupt
handler and do the unregistration in the worker to avoid a deadlock.

Note: the storm detection logic equals the implementation in
note_interrupt() which uses timestamps and counters stored in struct
irq_desc. Since this structure is private to the generic interrupt core
the TPM TIS core uses its own timestamps and counters. Furthermore the TPM
interrupt handler always returns IRQ_HANDLED to prevent the generic
interrupt core from processing the interrupt storm.

Cc: stable@vger.kernel.org # v6.4+
Fixes: e644b2f4 ("tpm, tpm_tis: Enable interrupt test")
Reported-by: default avatarkernel test robot <yujie.liu@intel.com>
Closes: https://lore.kernel.org/oe-lkp/202305041325.ae8b0c43-yujie.liu@intel.com/


Suggested-by: default avatarLukas Wunner <lukas@wunner.de>
Signed-off-by: default avatarLino Sanfilippo <l.sanfilippo@kunbus.com>
Reviewed-by: default avatarJarkko Sakkinen <jarkko@kernel.org>
Signed-off-by: default avatarJarkko Sakkinen <jarkko@kernel.org>
parent 393f3623
Loading
Loading
Loading
Loading
+88 −15
Original line number Diff line number Diff line
@@ -24,9 +24,12 @@
#include <linux/wait.h>
#include <linux/acpi.h>
#include <linux/freezer.h>
#include <linux/dmi.h>
#include "tpm.h"
#include "tpm_tis_core.h"

#define TPM_TIS_MAX_UNHANDLED_IRQS	1000

static void tpm_tis_clkrun_enable(struct tpm_chip *chip, bool value);

static bool wait_for_tpm_stat_cond(struct tpm_chip *chip, u8 mask,
@@ -468,25 +471,29 @@ static int tpm_tis_send_data(struct tpm_chip *chip, const u8 *buf, size_t len)
	return rc;
}

static void disable_interrupts(struct tpm_chip *chip)
static void __tpm_tis_disable_interrupts(struct tpm_chip *chip)
{
	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
	u32 int_mask = 0;

	tpm_tis_read32(priv, TPM_INT_ENABLE(priv->locality), &int_mask);
	int_mask &= ~TPM_GLOBAL_INT_ENABLE;
	tpm_tis_write32(priv, TPM_INT_ENABLE(priv->locality), int_mask);

	chip->flags &= ~TPM_CHIP_FLAG_IRQ;
}

static void tpm_tis_disable_interrupts(struct tpm_chip *chip)
{
	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
	u32 intmask;
	int rc;

	if (priv->irq == 0)
		return;

	rc = tpm_tis_read32(priv, TPM_INT_ENABLE(priv->locality), &intmask);
	if (rc < 0)
		intmask = 0;

	intmask &= ~TPM_GLOBAL_INT_ENABLE;
	rc = tpm_tis_write32(priv, TPM_INT_ENABLE(priv->locality), intmask);
	__tpm_tis_disable_interrupts(chip);

	devm_free_irq(chip->dev.parent, priv->irq, chip);
	priv->irq = 0;
	chip->flags &= ~TPM_CHIP_FLAG_IRQ;
}

/*
@@ -552,7 +559,7 @@ static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len)
	if (!test_bit(TPM_TIS_IRQ_TESTED, &priv->flags))
		tpm_msleep(1);
	if (!test_bit(TPM_TIS_IRQ_TESTED, &priv->flags))
		disable_interrupts(chip);
		tpm_tis_disable_interrupts(chip);
	set_bit(TPM_TIS_IRQ_TESTED, &priv->flags);
	return rc;
}
@@ -752,6 +759,57 @@ static bool tpm_tis_req_canceled(struct tpm_chip *chip, u8 status)
	return status == TPM_STS_COMMAND_READY;
}

static irqreturn_t tpm_tis_revert_interrupts(struct tpm_chip *chip)
{
	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
	const char *product;
	const char *vendor;

	dev_warn(&chip->dev, FW_BUG
		 "TPM interrupt storm detected, polling instead\n");

	vendor = dmi_get_system_info(DMI_SYS_VENDOR);
	product = dmi_get_system_info(DMI_PRODUCT_VERSION);

	if (vendor && product) {
		dev_info(&chip->dev,
			"Consider adding the following entry to tpm_tis_dmi_table:\n");
		dev_info(&chip->dev, "\tDMI_SYS_VENDOR: %s\n", vendor);
		dev_info(&chip->dev, "\tDMI_PRODUCT_VERSION: %s\n", product);
	}

	if (tpm_tis_request_locality(chip, 0) != 0)
		return IRQ_NONE;

	__tpm_tis_disable_interrupts(chip);
	tpm_tis_relinquish_locality(chip, 0);

	schedule_work(&priv->free_irq_work);

	return IRQ_HANDLED;
}

static irqreturn_t tpm_tis_update_unhandled_irqs(struct tpm_chip *chip)
{
	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
	irqreturn_t irqret = IRQ_HANDLED;

	if (!(chip->flags & TPM_CHIP_FLAG_IRQ))
		return IRQ_HANDLED;

	if (time_after(jiffies, priv->last_unhandled_irq + HZ/10))
		priv->unhandled_irqs = 1;
	else
		priv->unhandled_irqs++;

	priv->last_unhandled_irq = jiffies;

	if (priv->unhandled_irqs > TPM_TIS_MAX_UNHANDLED_IRQS)
		irqret = tpm_tis_revert_interrupts(chip);

	return irqret;
}

static irqreturn_t tis_int_handler(int dummy, void *dev_id)
{
	struct tpm_chip *chip = dev_id;
@@ -761,10 +819,10 @@ static irqreturn_t tis_int_handler(int dummy, void *dev_id)

	rc = tpm_tis_read32(priv, TPM_INT_STATUS(priv->locality), &interrupt);
	if (rc < 0)
		return IRQ_NONE;
		goto err;

	if (interrupt == 0)
		return IRQ_NONE;
		goto err;

	set_bit(TPM_TIS_IRQ_TESTED, &priv->flags);
	if (interrupt & TPM_INTF_DATA_AVAIL_INT)
@@ -780,10 +838,13 @@ static irqreturn_t tis_int_handler(int dummy, void *dev_id)
	rc = tpm_tis_write32(priv, TPM_INT_STATUS(priv->locality), interrupt);
	tpm_tis_relinquish_locality(chip, 0);
	if (rc < 0)
		return IRQ_NONE;
		goto err;

	tpm_tis_read32(priv, TPM_INT_STATUS(priv->locality), &interrupt);
	return IRQ_HANDLED;

err:
	return tpm_tis_update_unhandled_irqs(chip);
}

static void tpm_tis_gen_interrupt(struct tpm_chip *chip)
@@ -804,6 +865,15 @@ static void tpm_tis_gen_interrupt(struct tpm_chip *chip)
		chip->flags &= ~TPM_CHIP_FLAG_IRQ;
}

static void tpm_tis_free_irq_func(struct work_struct *work)
{
	struct tpm_tis_data *priv = container_of(work, typeof(*priv), free_irq_work);
	struct tpm_chip *chip = priv->chip;

	devm_free_irq(chip->dev.parent, priv->irq, chip);
	priv->irq = 0;
}

/* Register the IRQ and issue a command that will cause an interrupt. If an
 * irq is seen then leave the chip setup for IRQ operation, otherwise reverse
 * everything and leave in polling mode. Returns 0 on success.
@@ -816,6 +886,7 @@ static int tpm_tis_probe_irq_single(struct tpm_chip *chip, u32 intmask,
	int rc;
	u32 int_status;

	INIT_WORK(&priv->free_irq_work, tpm_tis_free_irq_func);

	rc = devm_request_threaded_irq(chip->dev.parent, irq, NULL,
				       tis_int_handler, IRQF_ONESHOT | flags,
@@ -918,6 +989,7 @@ void tpm_tis_remove(struct tpm_chip *chip)
		interrupt = 0;

	tpm_tis_write32(priv, reg, ~TPM_GLOBAL_INT_ENABLE & interrupt);
	flush_work(&priv->free_irq_work);

	tpm_tis_clkrun_enable(chip, false);

@@ -1021,6 +1093,7 @@ int tpm_tis_core_init(struct device *dev, struct tpm_tis_data *priv, int irq,
	chip->timeout_b = msecs_to_jiffies(TIS_TIMEOUT_B_MAX);
	chip->timeout_c = msecs_to_jiffies(TIS_TIMEOUT_C_MAX);
	chip->timeout_d = msecs_to_jiffies(TIS_TIMEOUT_D_MAX);
	priv->chip = chip;
	priv->timeout_min = TPM_TIMEOUT_USECS_MIN;
	priv->timeout_max = TPM_TIMEOUT_USECS_MAX;
	priv->phy_ops = phy_ops;
@@ -1179,7 +1252,7 @@ int tpm_tis_core_init(struct device *dev, struct tpm_tis_data *priv, int irq,
			rc = tpm_tis_request_locality(chip, 0);
			if (rc < 0)
				goto out_err;
			disable_interrupts(chip);
			tpm_tis_disable_interrupts(chip);
			tpm_tis_relinquish_locality(chip, 0);
		}
	}
+4 −0
Original line number Diff line number Diff line
@@ -91,11 +91,15 @@ enum tpm_tis_flags {
};

struct tpm_tis_data {
	struct tpm_chip *chip;
	u16 manufacturer_id;
	struct mutex locality_count_mutex;
	unsigned int locality_count;
	int locality;
	int irq;
	struct work_struct free_irq_work;
	unsigned long last_unhandled_irq;
	unsigned int unhandled_irqs;
	unsigned int int_mask;
	unsigned long flags;
	void __iomem *ilb_base_addr;