[PATCH V9] i2c: i2c-qcom-geni: Add shutdown callback for i2c

From: Roja Rani Yarubandi
Date: Tue Apr 20 2021 - 07:14:49 EST


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 <rojay@xxxxxxxxxxxxxx>
---
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