If the hardware is still accessing memory after SMMU translation
is disabled (as part of smmu shutdown callback), then the
IOVAs (I/O virtual address) which it was using will go on the bus
as the physical addresses which will result in unknown crashes
like NoC/interconnect errors.

So, implement shutdown callback to i2c driver to stop on-going transfer
and unmap DMA mappings during system "reboot" or "shutdown".

Fixes: 37692de5d523 ("i2c: i2c-qcom-geni: Add bus driver for the Qualcomm GENI 
I2C controller")
Signed-off-by: Roja Rani Yarubandi <ro...@codeaurora.org>
---
Changes in V2:
 - As per Stephen's comments added seperate function for stop transfer,
   fixed minor nitpicks.
 - As per Stephen's comments, changed commit text.

Changes in V3:
 - As per Stephen's comments, squashed patch 1 into patch 2, added Fixes tag.
 - As per Akash's comments, included FIFO case in stop_xfer, fixed minor 
nitpicks.

Changes in V4:
 - As per Stephen's comments cleaned up geni_i2c_stop_xfer function,
   added dma_buf in geni_i2c_dev struct to call i2c_put_dma_safe_msg_buf()
   from other functions, removed "iova" check in geni_se_rx_dma_unprep()
   and geni_se_tx_dma_unprep() functions.
 - Added two helper functions geni_i2c_rx_one_msg_done() and
   geni_i2c_tx_one_msg_done() to unwrap the things after rx/tx FIFO/DMA
   transfers, so that the same can be used in geni_i2c_stop_xfer() function
   during shutdown callback. Updated commit text accordingly.
 - Checking whether it is tx/rx transfer using I2C_M_RD which is valid for both
   FIFO and DMA cases, so dropped DMA_RX_ACTIVE and DMA_TX_ACTIVE bit checking

Changes in V5:
 - As per Stephen's comments, added spin_lock_irqsave & spin_unlock_irqsave in
   geni_i2c_stop_xfer() function.

Changes in V6:
 - As per Stephen's comments, taken care of unsafe lock order in
   geni_i2c_stop_xfer().
 - Moved spin_lock/unlock to geni_i2c_rx_msg_cleanup() and
   geni_i2c_tx_msg_cleanup() functions.

Changes in V7:
 - No changes

Changes in V8:
 - As per Wolfram Sang comment, removed goto and modified geni_i2c_stop_xfer()
   accordingly.

Changes in V9:
 - Fixed possbile race by protecting gi2c->cur and calling geni_i2c_abort_xfer()
   with adding another parameter to differentiate from which sequence is the
   geni_i2c_abort_xfer() called and handle the spin_lock/spin_unlock accordingly
   inside geni_i2c_abort_xfer(). For this added two macros ABORT_XFER and
   STOP_AND_ABORT_XFER.
 - Added a bool variable "stop_xfer" in geni_i2c_dev struct, used to put stop
   to upcoming geni_i2c_rx_one_msg() and geni_i2c_tx_one_msg() calls once we
   recieve the shutdown call.
 - Added gi2c->cur == NULL check in geni_i2c_irq() to not to process the irq
   even if any transfer is queued and shutdown to HW received.

 drivers/i2c/busses/i2c-qcom-geni.c | 71 +++++++++++++++++++++++++++---
 1 file changed, 64 insertions(+), 7 deletions(-)

diff --git a/drivers/i2c/busses/i2c-qcom-geni.c 
b/drivers/i2c/busses/i2c-qcom-geni.c
index 214b4c913a13..8ae17ccad99e 100644
--- a/drivers/i2c/busses/i2c-qcom-geni.c
+++ b/drivers/i2c/busses/i2c-qcom-geni.c
@@ -71,6 +71,8 @@ enum geni_i2c_err_code {
 #define ABORT_TIMEOUT          HZ
 #define XFER_TIMEOUT           HZ
 #define RST_TIMEOUT            HZ
+#define ABORT_XFER             0
+#define STOP_AND_ABORT_XFER    1
 
 struct geni_i2c_dev {
        struct geni_se se;
@@ -89,6 +91,7 @@ struct geni_i2c_dev {
        void *dma_buf;
        size_t xfer_len;
        dma_addr_t dma_addr;
+       bool stop_xfer;
 };
 
 struct geni_i2c_err_log {
@@ -215,6 +218,11 @@ static irqreturn_t geni_i2c_irq(int irq, void *dev)
        struct i2c_msg *cur;
 
        spin_lock(&gi2c->lock);
+       if (!gi2c->cur) {
+               dev_err(gi2c->se.dev, "Can't process irq, gi2c->cur is NULL\n");
+               spin_unlock(&gi2c->lock);
+               return IRQ_HANDLED;
+       }
        m_stat = readl_relaxed(base + SE_GENI_M_IRQ_STATUS);
        rx_st = readl_relaxed(base + SE_GENI_RX_FIFO_STATUS);
        dm_tx_st = readl_relaxed(base + SE_DMA_TX_IRQ_STAT);
@@ -222,8 +230,7 @@ static irqreturn_t geni_i2c_irq(int irq, void *dev)
        dma = readl_relaxed(base + SE_GENI_DMA_MODE_EN);
        cur = gi2c->cur;
 
-       if (!cur ||
-           m_stat & (M_CMD_FAILURE_EN | M_CMD_ABORT_EN) ||
+       if (m_stat & (M_CMD_FAILURE_EN | M_CMD_ABORT_EN) ||
            dm_rx_st & (DM_I2C_CB_ERR)) {
                if (m_stat & M_GP_IRQ_1_EN)
                        geni_i2c_err(gi2c, NACK);
@@ -301,17 +308,19 @@ static irqreturn_t geni_i2c_irq(int irq, void *dev)
        return IRQ_HANDLED;
 }
 
-static void geni_i2c_abort_xfer(struct geni_i2c_dev *gi2c)
+static void geni_i2c_abort_xfer(struct geni_i2c_dev *gi2c, bool is_stop_xfer)
 {
        u32 val;
        unsigned long time_left = ABORT_TIMEOUT;
        unsigned long flags;
 
-       spin_lock_irqsave(&gi2c->lock, flags);
+       if (!is_stop_xfer)
+               spin_lock_irqsave(&gi2c->lock, flags);
        geni_i2c_err(gi2c, GENI_TIMEOUT);
        gi2c->cur = NULL;
        geni_se_abort_m_cmd(&gi2c->se);
-       spin_unlock_irqrestore(&gi2c->lock, flags);
+       if (!is_stop_xfer)
+               spin_unlock_irqrestore(&gi2c->lock, flags);
        do {
                time_left = wait_for_completion_timeout(&gi2c->done, time_left);
                val = readl_relaxed(gi2c->se.base + SE_GENI_M_IRQ_STATUS);
@@ -375,6 +384,38 @@ static void geni_i2c_tx_msg_cleanup(struct geni_i2c_dev 
*gi2c,
        }
 }
 
+static void geni_i2c_stop_xfer(struct geni_i2c_dev *gi2c)
+{
+       int ret;
+       u32 geni_status;
+       struct i2c_msg *cur;
+       unsigned long flags;
+
+       /* Resume device, as runtime suspend can happen anytime during transfer 
*/
+       ret = pm_runtime_get_sync(gi2c->se.dev);
+       if (ret < 0) {
+               dev_err(gi2c->se.dev, "Failed to resume device: %d\n", ret);
+               return;
+       }
+
+       spin_lock_irqsave(&gi2c->lock, flags);
+       gi2c->stop_xfer = 1;
+       geni_status = readl_relaxed(gi2c->se.base + SE_GENI_STATUS);
+       if (geni_status & M_GENI_CMD_ACTIVE) {
+               cur = gi2c->cur;
+               geni_i2c_abort_xfer(gi2c, STOP_AND_ABORT_XFER);
+               spin_unlock_irqrestore(&gi2c->lock, flags);
+               if (cur->flags & I2C_M_RD)
+                       geni_i2c_rx_msg_cleanup(gi2c, cur);
+               else
+                       geni_i2c_tx_msg_cleanup(gi2c, cur);
+       } else {
+               spin_unlock_irqrestore(&gi2c->lock, flags);
+       }
+
+       pm_runtime_put_sync_suspend(gi2c->se.dev);
+}
+
 static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
                                u32 m_param)
 {
@@ -407,7 +448,7 @@ static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, 
struct i2c_msg *msg,
        cur = gi2c->cur;
        time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
        if (!time_left)
-               geni_i2c_abort_xfer(gi2c);
+               geni_i2c_abort_xfer(gi2c, ABORT_XFER);
 
        geni_i2c_rx_msg_cleanup(gi2c, cur);
 
@@ -449,7 +490,7 @@ static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, 
struct i2c_msg *msg,
        cur = gi2c->cur;
        time_left = wait_for_completion_timeout(&gi2c->done, XFER_TIMEOUT);
        if (!time_left)
-               geni_i2c_abort_xfer(gi2c);
+               geni_i2c_abort_xfer(gi2c, ABORT_XFER);
 
        geni_i2c_tx_msg_cleanup(gi2c, cur);
 
@@ -462,6 +503,7 @@ static int geni_i2c_xfer(struct i2c_adapter *adap,
 {
        struct geni_i2c_dev *gi2c = i2c_get_adapdata(adap);
        int i, ret;
+       unsigned long flags;
 
        gi2c->err = 0;
        reinit_completion(&gi2c->done);
@@ -480,7 +522,13 @@ static int geni_i2c_xfer(struct i2c_adapter *adap,
 
                m_param |= ((msgs[i].addr << SLV_ADDR_SHFT) & SLV_ADDR_MSK);
 
+               spin_lock_irqsave(&gi2c->lock, flags);
+               if (gi2c->stop_xfer) {
+                       spin_unlock_irqrestore(&gi2c->lock, flags);
+                       break;
+               }
                gi2c->cur = &msgs[i];
+               spin_unlock_irqrestore(&gi2c->lock, flags);
                if (msgs[i].flags & I2C_M_RD)
                        ret = geni_i2c_rx_one_msg(gi2c, &msgs[i], m_param);
                else
@@ -624,6 +672,7 @@ static int geni_i2c_probe(struct platform_device *pdev)
        dev_dbg(dev, "i2c fifo/se-dma mode. fifo depth:%d\n", tx_depth);
 
        gi2c->suspended = 1;
+       gi2c->stop_xfer = 0;
        pm_runtime_set_suspended(gi2c->se.dev);
        pm_runtime_set_autosuspend_delay(gi2c->se.dev, I2C_AUTO_SUSPEND_DELAY);
        pm_runtime_use_autosuspend(gi2c->se.dev);
@@ -650,6 +699,13 @@ static int geni_i2c_remove(struct platform_device *pdev)
        return 0;
 }
 
+static void  geni_i2c_shutdown(struct platform_device *pdev)
+{
+       struct geni_i2c_dev *gi2c = platform_get_drvdata(pdev);
+
+       geni_i2c_stop_xfer(gi2c);
+}
+
 static int __maybe_unused geni_i2c_runtime_suspend(struct device *dev)
 {
        int ret;
@@ -714,6 +770,7 @@ MODULE_DEVICE_TABLE(of, geni_i2c_dt_match);
 static struct platform_driver geni_i2c_driver = {
        .probe  = geni_i2c_probe,
        .remove = geni_i2c_remove,
+       .shutdown = geni_i2c_shutdown,
        .driver = {
                .name = "geni_i2c",
                .pm = &geni_i2c_pm_ops,
-- 
QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member 
of Code Aurora Forum, hosted by The Linux Foundation

Reply via email to