tpm_i2c_infineon: Add reboot handler
On gfsc100 the I2C bus sometimes does not work at all after a reboot.
The problem happens when the reboot occurs in midst of an I2C read
transfer from the TPM. The TPM is not physically reset on reboots and
apparently remains in a state where it keeps holding the bus to complete
the pending read transfer.
To avoid this situation a reboot handler is added to the TPM driver. The
handler sets a flag indicating that a reboot is underway and flushes the
work queue of the I2C controller. Subsequent attempts to read or write
are aborted.
Google-Bug-Id: 23816953
Change-Id: I58c5c6cd4485315b2850f731e1fb28a53d13cac4
diff --git a/drivers/char/tpm/tpm_i2c_infineon.c b/drivers/char/tpm/tpm_i2c_infineon.c
index ee41671..8aaa4f5 100644
--- a/drivers/char/tpm/tpm_i2c_infineon.c
+++ b/drivers/char/tpm/tpm_i2c_infineon.c
@@ -25,6 +25,7 @@
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
+#include <linux/reboot.h>
#include <linux/wait.h>
#include "tpm.h"
@@ -71,6 +72,8 @@
u8 buf[TPM_BUFSIZE + sizeof(u8)]; /* max. buffer size + addr */
struct tpm_chip *chip;
enum i2c_chip_type chip_type;
+ bool reboot_in_progress;
+ struct mutex reboot_lock;
};
static struct tpm_inf_dev tpm_dev;
@@ -117,6 +120,16 @@
/* Lock the adapter for the duration of the whole sequence. */
if (!tpm_dev.client->adapter->algo->master_xfer)
return -EOPNOTSUPP;
+
+ mutex_lock(&tpm_dev.reboot_lock);
+
+ if (tpm_dev.reboot_in_progress) {
+ dev_warn(tpm_dev.chip->dev, "reboot in progress, aborting %s\n",
+ __func__);
+ mutex_unlock(&tpm_dev.reboot_lock);
+ return -EIO;
+ }
+
i2c_lock_adapter(tpm_dev.client->adapter);
if (tpm_dev.chip_type == SLB9645) {
@@ -159,6 +172,9 @@
out:
i2c_unlock_adapter(tpm_dev.client->adapter);
+
+ mutex_unlock(&tpm_dev.reboot_lock);
+
/* take care of 'guard time' */
usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI);
@@ -190,6 +206,13 @@
if (!tpm_dev.client->adapter->algo->master_xfer)
return -EOPNOTSUPP;
+
+ if (tpm_dev.reboot_in_progress) {
+ dev_warn(tpm_dev.chip->dev, "reboot in progress, aborting %s\n",
+ __func__);
+ return -EIO;
+ }
+
i2c_lock_adapter(tpm_dev.client->adapter);
/* prepend the 'register address' to the buffer */
@@ -726,6 +749,22 @@
MODULE_DEVICE_TABLE(of, tpm_tis_i2c_of_match);
#endif
+static int tpm_reboot_handler(struct notifier_block *nb,unsigned long action, void *data)
+{
+ mutex_lock(&tpm_dev.reboot_lock);
+
+ tpm_dev.reboot_in_progress = true;
+ flush_work_sync(&tpm_dev.chip->work);
+
+ mutex_unlock(&tpm_dev.reboot_lock);
+
+ return 0;
+}
+
+static struct notifier_block tpm_reboot_notifier = {
+ .notifier_call = tpm_reboot_handler
+};
+
static SIMPLE_DEV_PM_OPS(tpm_tis_i2c_ops, tpm_pm_suspend, tpm_pm_resume);
static int tpm_tis_i2c_probe(struct i2c_client *client,
@@ -744,6 +783,8 @@
return -ENODEV;
}
+ mutex_init(&tpm_dev.reboot_lock);
+
client->driver = &tpm_tis_i2c_driver;
tpm_dev.client = client;
rc = tpm_tis_i2c_init(&client->dev);
@@ -752,6 +793,10 @@
tpm_dev.client = NULL;
rc = -ENODEV;
}
+
+ tpm_dev.reboot_in_progress = false;
+ register_reboot_notifier(&tpm_reboot_notifier);
+
return rc;
}