i2c: tegra: Better handle case where CPU0 is busy for a long time
authorDmitry Osipenko <digetx@gmail.com>
Tue, 24 Mar 2020 19:12:16 +0000 (22:12 +0300)
committerWolfram Sang <wsa@the-dreams.de>
Wed, 15 Apr 2020 16:27:25 +0000 (18:27 +0200)
Boot CPU0 always handle I2C interrupt and under some rare circumstances
(like running KASAN + NFS root) it may stuck in uninterruptible state for
a significant time. In this case we will get timeout if I2C transfer is
running on a sibling CPU, despite of IRQ being raised. In order to handle
this rare condition, the IRQ status needs to be checked after completion
timeout.

Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
drivers/i2c/busses/i2c-tegra.c

index 4c4d17ddc96b964125f89b49c13dd1f44f8bc457..a795b4e278b1fcd9b964b9bd556a31eadfcf2fd2 100644 (file)
@@ -996,14 +996,13 @@ tegra_i2c_poll_completion_timeout(struct tegra_i2c_dev *i2c_dev,
        do {
                u32 status = i2c_readl(i2c_dev, I2C_INT_STATUS);
 
-               if (status) {
+               if (status)
                        tegra_i2c_isr(i2c_dev->irq, i2c_dev);
 
-                       if (completion_done(complete)) {
-                               s64 delta = ktime_ms_delta(ktimeout, ktime);
+               if (completion_done(complete)) {
+                       s64 delta = ktime_ms_delta(ktimeout, ktime);
 
-                               return msecs_to_jiffies(delta) ?: 1;
-                       }
+                       return msecs_to_jiffies(delta) ?: 1;
                }
 
                ktime = ktime_get();
@@ -1030,14 +1029,18 @@ tegra_i2c_wait_completion_timeout(struct tegra_i2c_dev *i2c_dev,
                disable_irq(i2c_dev->irq);
 
                /*
-                * There is a chance that completion may happen after IRQ
-                * synchronization, which is done by disable_irq().
+                * Under some rare circumstances (like running KASAN +
+                * NFS root) CPU, which handles interrupt, may stuck in
+                * uninterruptible state for a significant time.  In this
+                * case we will get timeout if I2C transfer is running on
+                * a sibling CPU, despite of IRQ being raised.
+                *
+                * In order to handle this rare condition, the IRQ status
+                * needs to be checked after timeout.
                 */
-               if (ret == 0 && completion_done(complete)) {
-                       dev_warn(i2c_dev->dev,
-                                "completion done after timeout\n");
-                       ret = 1;
-               }
+               if (ret == 0)
+                       ret = tegra_i2c_poll_completion_timeout(i2c_dev,
+                                                               complete, 0);
        }
 
        return ret;