linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] i2c: uniphier-f: fix concurrency issues and race conditions
@ 2018-10-16  3:01 Masahiro Yamada
  2018-10-16  3:01 ` [PATCH 1/3] i2c: uniphier-f: make driver robust against concurrency Masahiro Yamada
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Masahiro Yamada @ 2018-10-16  3:01 UTC (permalink / raw)
  To: Wolfram Sang, linux-i2c; +Cc: Masahiro Yamada, linux-arm-kernel, linux-kernel


Currently, this driver has issues that rarely happen.

With this patch series, long-run tests passed.



Masahiro Yamada (3):
  i2c: uniphier-f: make driver robust against concurrency
  i2c: uniphier-f: fix occasional timeout error
  i2c: uniphier-f: fix race condition when IRQ is cleared

 drivers/i2c/busses/i2c-uniphier-f.c | 59 +++++++++++++++++++++++++++++--------
 1 file changed, 46 insertions(+), 13 deletions(-)

-- 
2.7.4


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 1/3] i2c: uniphier-f: make driver robust against concurrency
  2018-10-16  3:01 [PATCH 0/3] i2c: uniphier-f: fix concurrency issues and race conditions Masahiro Yamada
@ 2018-10-16  3:01 ` Masahiro Yamada
  2018-10-28 22:07   ` Wolfram Sang
  2018-10-16  3:01 ` [PATCH 2/3] i2c: uniphier-f: fix occasional timeout error Masahiro Yamada
  2018-10-16  3:01 ` [PATCH 3/3] i2c: uniphier-f: fix race condition when IRQ is cleared Masahiro Yamada
  2 siblings, 1 reply; 7+ messages in thread
From: Masahiro Yamada @ 2018-10-16  3:01 UTC (permalink / raw)
  To: Wolfram Sang, linux-i2c; +Cc: Masahiro Yamada, linux-arm-kernel, linux-kernel

This is unlikely to happen, but it is possible for a CPU to enter
the interrupt handler just after wait_for_completion_timeout() has
expired. If this happens, the hardware is accessed from multiple
contexts concurrently.

Disable the IRQ after wait_for_completion_timeout(), and do nothing
from the handler when the IRQ is disabled.

Fixes: 6a62974b667f ("i2c: uniphier_f: add UniPhier FIFO-builtin I2C driver")
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
---

 drivers/i2c/busses/i2c-uniphier-f.c | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/drivers/i2c/busses/i2c-uniphier-f.c b/drivers/i2c/busses/i2c-uniphier-f.c
index a403e85..200dfd1 100644
--- a/drivers/i2c/busses/i2c-uniphier-f.c
+++ b/drivers/i2c/busses/i2c-uniphier-f.c
@@ -98,6 +98,7 @@ struct uniphier_fi2c_priv {
 	unsigned int flags;
 	unsigned int busy_cnt;
 	unsigned int clk_cycle;
+	spinlock_t lock;	/* IRQ synchronization */
 };
 
 static void uniphier_fi2c_fill_txfifo(struct uniphier_fi2c_priv *priv,
@@ -162,7 +163,10 @@ static irqreturn_t uniphier_fi2c_interrupt(int irq, void *dev_id)
 	struct uniphier_fi2c_priv *priv = dev_id;
 	u32 irq_status;
 
+	spin_lock(&priv->lock);
+
 	irq_status = readl(priv->membase + UNIPHIER_FI2C_INT);
+	irq_status &= priv->enabled_irqs;
 
 	dev_dbg(&priv->adap.dev,
 		"interrupt: enabled_irqs=%04x, irq_status=%04x\n",
@@ -230,6 +234,8 @@ static irqreturn_t uniphier_fi2c_interrupt(int irq, void *dev_id)
 		goto handled;
 	}
 
+	spin_unlock(&priv->lock);
+
 	return IRQ_NONE;
 
 data_done:
@@ -246,6 +252,8 @@ static irqreturn_t uniphier_fi2c_interrupt(int irq, void *dev_id)
 handled:
 	uniphier_fi2c_clear_irqs(priv);
 
+	spin_unlock(&priv->lock);
+
 	return IRQ_HANDLED;
 }
 
@@ -311,7 +319,7 @@ static int uniphier_fi2c_master_xfer_one(struct i2c_adapter *adap,
 {
 	struct uniphier_fi2c_priv *priv = i2c_get_adapdata(adap);
 	bool is_read = msg->flags & I2C_M_RD;
-	unsigned long time_left;
+	unsigned long time_left, flags;
 
 	dev_dbg(&adap->dev, "%s: addr=0x%02x, len=%d, stop=%d\n",
 		is_read ? "receive" : "transmit", msg->addr, msg->len, stop);
@@ -342,6 +350,12 @@ static int uniphier_fi2c_master_xfer_one(struct i2c_adapter *adap,
 	       priv->membase + UNIPHIER_FI2C_CR);
 
 	time_left = wait_for_completion_timeout(&priv->comp, adap->timeout);
+
+	spin_lock_irqsave(&priv->lock, flags);
+	priv->enabled_irqs = 0;
+	uniphier_fi2c_set_irqs(priv);
+	spin_unlock_irqrestore(&priv->lock, flags);
+
 	if (!time_left) {
 		dev_err(&adap->dev, "transaction timeout.\n");
 		uniphier_fi2c_recover(priv);
@@ -529,6 +543,7 @@ static int uniphier_fi2c_probe(struct platform_device *pdev)
 
 	priv->clk_cycle = clk_rate / bus_speed;
 	init_completion(&priv->comp);
+	spin_lock_init(&priv->lock);
 	priv->adap.owner = THIS_MODULE;
 	priv->adap.algo = &uniphier_fi2c_algo;
 	priv->adap.dev.parent = dev;
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 2/3] i2c: uniphier-f: fix occasional timeout error
  2018-10-16  3:01 [PATCH 0/3] i2c: uniphier-f: fix concurrency issues and race conditions Masahiro Yamada
  2018-10-16  3:01 ` [PATCH 1/3] i2c: uniphier-f: make driver robust against concurrency Masahiro Yamada
@ 2018-10-16  3:01 ` Masahiro Yamada
  2018-10-28 22:08   ` Wolfram Sang
  2018-10-16  3:01 ` [PATCH 3/3] i2c: uniphier-f: fix race condition when IRQ is cleared Masahiro Yamada
  2 siblings, 1 reply; 7+ messages in thread
From: Masahiro Yamada @ 2018-10-16  3:01 UTC (permalink / raw)
  To: Wolfram Sang, linux-i2c; +Cc: Masahiro Yamada, linux-arm-kernel, linux-kernel

Currently, a timeout error could happen at a repeated START condition.

For a (non-repeated) START condition, the controller starts sending
data when the UNIPHIER_FI2C_CR_STA bit is set. However, for a repeated
START condition, the hardware starts running when the slave address is
written to the TX FIFO - the write to the UNIPHIER_FI2C_CR register is
actually unneeded.

Because the hardware is already running before the IRQ is enabled for
a repeated START, the driver may miss the IRQ event. In most cases,
this problem does not show up since modern CPUs are much faster than
the I2C transfer. However, it is still possible that a context switch
happens after the controller starts, but before the IRQ register is
set up.

To fix this,

 - Do not write UNIPHIER_FI2C_CR for repeated START conditions.

 - Enable IRQ *before* writing the slave address to the TX FIFO.

 - Disable IRQ for the current CPU while queuing up the TX FIFO;
   If the CPU is interrupted by some task, the interrupt handler
   might be invoked due to the empty TX FIFO before completing the
   setup.

Fixes: 6a62974b667f ("i2c: uniphier_f: add UniPhier FIFO-builtin I2C driver")
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
---

 drivers/i2c/busses/i2c-uniphier-f.c | 33 +++++++++++++++++++++++++--------
 1 file changed, 25 insertions(+), 8 deletions(-)

diff --git a/drivers/i2c/busses/i2c-uniphier-f.c b/drivers/i2c/busses/i2c-uniphier-f.c
index 200dfd1..0677153 100644
--- a/drivers/i2c/busses/i2c-uniphier-f.c
+++ b/drivers/i2c/busses/i2c-uniphier-f.c
@@ -260,6 +260,8 @@ static irqreturn_t uniphier_fi2c_interrupt(int irq, void *dev_id)
 static void uniphier_fi2c_tx_init(struct uniphier_fi2c_priv *priv, u16 addr)
 {
 	priv->enabled_irqs |= UNIPHIER_FI2C_INT_TE;
+	uniphier_fi2c_set_irqs(priv);
+
 	/* do not use TX byte counter */
 	writel(0, priv->membase + UNIPHIER_FI2C_TBC);
 	/* set slave address */
@@ -292,6 +294,8 @@ static void uniphier_fi2c_rx_init(struct uniphier_fi2c_priv *priv, u16 addr)
 		priv->enabled_irqs |= UNIPHIER_FI2C_INT_RF;
 	}
 
+	uniphier_fi2c_set_irqs(priv);
+
 	/* set slave address with RD bit */
 	writel(UNIPHIER_FI2C_DTTX_CMD | UNIPHIER_FI2C_DTTX_RD | addr << 1,
 	       priv->membase + UNIPHIER_FI2C_DTTX);
@@ -315,14 +319,16 @@ static void uniphier_fi2c_recover(struct uniphier_fi2c_priv *priv)
 }
 
 static int uniphier_fi2c_master_xfer_one(struct i2c_adapter *adap,
-					 struct i2c_msg *msg, bool stop)
+					 struct i2c_msg *msg, bool repeat,
+					 bool stop)
 {
 	struct uniphier_fi2c_priv *priv = i2c_get_adapdata(adap);
 	bool is_read = msg->flags & I2C_M_RD;
 	unsigned long time_left, flags;
 
-	dev_dbg(&adap->dev, "%s: addr=0x%02x, len=%d, stop=%d\n",
-		is_read ? "receive" : "transmit", msg->addr, msg->len, stop);
+	dev_dbg(&adap->dev, "%s: addr=0x%02x, len=%d, repeat=%d, stop=%d\n",
+		is_read ? "receive" : "transmit", msg->addr, msg->len,
+		repeat, stop);
 
 	priv->len = msg->len;
 	priv->buf = msg->buf;
@@ -338,16 +344,24 @@ static int uniphier_fi2c_master_xfer_one(struct i2c_adapter *adap,
 	writel(UNIPHIER_FI2C_RST_TBRST | UNIPHIER_FI2C_RST_RBRST,
 	       priv->membase + UNIPHIER_FI2C_RST);	/* reset TX/RX FIFO */
 
+	spin_lock_irqsave(&priv->lock, flags);
+
 	if (is_read)
 		uniphier_fi2c_rx_init(priv, msg->addr);
 	else
 		uniphier_fi2c_tx_init(priv, msg->addr);
 
-	uniphier_fi2c_set_irqs(priv);
-
 	dev_dbg(&adap->dev, "start condition\n");
-	writel(UNIPHIER_FI2C_CR_MST | UNIPHIER_FI2C_CR_STA,
-	       priv->membase + UNIPHIER_FI2C_CR);
+	/*
+	 * For a repeated START condition, writing a slave address to the FIFO
+	 * kicks the controller. So, the UNIPHIER_FI2C_CR register should be
+	 * written only for a non-repeated START condition.
+	 */
+	if (!repeat)
+		writel(UNIPHIER_FI2C_CR_MST | UNIPHIER_FI2C_CR_STA,
+		       priv->membase + UNIPHIER_FI2C_CR);
+
+	spin_unlock_irqrestore(&priv->lock, flags);
 
 	time_left = wait_for_completion_timeout(&priv->comp, adap->timeout);
 
@@ -408,6 +422,7 @@ static int uniphier_fi2c_master_xfer(struct i2c_adapter *adap,
 				     struct i2c_msg *msgs, int num)
 {
 	struct i2c_msg *msg, *emsg = msgs + num;
+	bool repeat = false;
 	int ret;
 
 	ret = uniphier_fi2c_check_bus_busy(adap);
@@ -418,9 +433,11 @@ static int uniphier_fi2c_master_xfer(struct i2c_adapter *adap,
 		/* Emit STOP if it is the last message or I2C_M_STOP is set. */
 		bool stop = (msg + 1 == emsg) || (msg->flags & I2C_M_STOP);
 
-		ret = uniphier_fi2c_master_xfer_one(adap, msg, stop);
+		ret = uniphier_fi2c_master_xfer_one(adap, msg, repeat, stop);
 		if (ret)
 			return ret;
+
+		repeat = !stop;
 	}
 
 	return num;
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 3/3] i2c: uniphier-f: fix race condition when IRQ is cleared
  2018-10-16  3:01 [PATCH 0/3] i2c: uniphier-f: fix concurrency issues and race conditions Masahiro Yamada
  2018-10-16  3:01 ` [PATCH 1/3] i2c: uniphier-f: make driver robust against concurrency Masahiro Yamada
  2018-10-16  3:01 ` [PATCH 2/3] i2c: uniphier-f: fix occasional timeout error Masahiro Yamada
@ 2018-10-16  3:01 ` Masahiro Yamada
  2018-10-28 22:08   ` Wolfram Sang
  2 siblings, 1 reply; 7+ messages in thread
From: Masahiro Yamada @ 2018-10-16  3:01 UTC (permalink / raw)
  To: Wolfram Sang, linux-i2c; +Cc: Masahiro Yamada, linux-arm-kernel, linux-kernel

The current IRQ handler clears all the IRQ status bits when it bails
out. This is dangerous because it might clear away the status bits
that have just been set while processing the current handler. If this
happens, the IRQ event for the latest transfer is lost forever.

The IRQ status bits must be cleared *before* the next transfer is
kicked.

Fixes: 6a62974b667f ("i2c: uniphier_f: add UniPhier FIFO-builtin I2C driver")
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
---

 drivers/i2c/busses/i2c-uniphier-f.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/drivers/i2c/busses/i2c-uniphier-f.c b/drivers/i2c/busses/i2c-uniphier-f.c
index 0677153..dd38474 100644
--- a/drivers/i2c/busses/i2c-uniphier-f.c
+++ b/drivers/i2c/busses/i2c-uniphier-f.c
@@ -143,9 +143,10 @@ static void uniphier_fi2c_set_irqs(struct uniphier_fi2c_priv *priv)
 	writel(priv->enabled_irqs, priv->membase + UNIPHIER_FI2C_IE);
 }
 
-static void uniphier_fi2c_clear_irqs(struct uniphier_fi2c_priv *priv)
+static void uniphier_fi2c_clear_irqs(struct uniphier_fi2c_priv *priv,
+				     u32 mask)
 {
-	writel(-1, priv->membase + UNIPHIER_FI2C_IC);
+	writel(mask, priv->membase + UNIPHIER_FI2C_IC);
 }
 
 static void uniphier_fi2c_stop(struct uniphier_fi2c_priv *priv)
@@ -172,6 +173,8 @@ static irqreturn_t uniphier_fi2c_interrupt(int irq, void *dev_id)
 		"interrupt: enabled_irqs=%04x, irq_status=%04x\n",
 		priv->enabled_irqs, irq_status);
 
+	uniphier_fi2c_clear_irqs(priv, irq_status);
+
 	if (irq_status & UNIPHIER_FI2C_INT_STOP)
 		goto complete;
 
@@ -250,8 +253,6 @@ static irqreturn_t uniphier_fi2c_interrupt(int irq, void *dev_id)
 	}
 
 handled:
-	uniphier_fi2c_clear_irqs(priv);
-
 	spin_unlock(&priv->lock);
 
 	return IRQ_HANDLED;
@@ -340,7 +341,7 @@ static int uniphier_fi2c_master_xfer_one(struct i2c_adapter *adap,
 		priv->flags |= UNIPHIER_FI2C_STOP;
 
 	reinit_completion(&priv->comp);
-	uniphier_fi2c_clear_irqs(priv);
+	uniphier_fi2c_clear_irqs(priv, U32_MAX);
 	writel(UNIPHIER_FI2C_RST_TBRST | UNIPHIER_FI2C_RST_RBRST,
 	       priv->membase + UNIPHIER_FI2C_RST);	/* reset TX/RX FIFO */
 
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH 1/3] i2c: uniphier-f: make driver robust against concurrency
  2018-10-16  3:01 ` [PATCH 1/3] i2c: uniphier-f: make driver robust against concurrency Masahiro Yamada
@ 2018-10-28 22:07   ` Wolfram Sang
  0 siblings, 0 replies; 7+ messages in thread
From: Wolfram Sang @ 2018-10-28 22:07 UTC (permalink / raw)
  To: Masahiro Yamada; +Cc: Wolfram Sang, linux-i2c, linux-arm-kernel, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 605 bytes --]

On Tue, Oct 16, 2018 at 12:01:47PM +0900, Masahiro Yamada wrote:
> This is unlikely to happen, but it is possible for a CPU to enter
> the interrupt handler just after wait_for_completion_timeout() has
> expired. If this happens, the hardware is accessed from multiple
> contexts concurrently.
> 
> Disable the IRQ after wait_for_completion_timeout(), and do nothing
> from the handler when the IRQ is disabled.
> 
> Fixes: 6a62974b667f ("i2c: uniphier_f: add UniPhier FIFO-builtin I2C driver")
> Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>

Applied to for-next, thanks!


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 2/3] i2c: uniphier-f: fix occasional timeout error
  2018-10-16  3:01 ` [PATCH 2/3] i2c: uniphier-f: fix occasional timeout error Masahiro Yamada
@ 2018-10-28 22:08   ` Wolfram Sang
  0 siblings, 0 replies; 7+ messages in thread
From: Wolfram Sang @ 2018-10-28 22:08 UTC (permalink / raw)
  To: Masahiro Yamada; +Cc: Wolfram Sang, linux-i2c, linux-arm-kernel, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1405 bytes --]

On Tue, Oct 16, 2018 at 12:01:48PM +0900, Masahiro Yamada wrote:
> Currently, a timeout error could happen at a repeated START condition.
> 
> For a (non-repeated) START condition, the controller starts sending
> data when the UNIPHIER_FI2C_CR_STA bit is set. However, for a repeated
> START condition, the hardware starts running when the slave address is
> written to the TX FIFO - the write to the UNIPHIER_FI2C_CR register is
> actually unneeded.
> 
> Because the hardware is already running before the IRQ is enabled for
> a repeated START, the driver may miss the IRQ event. In most cases,
> this problem does not show up since modern CPUs are much faster than
> the I2C transfer. However, it is still possible that a context switch
> happens after the controller starts, but before the IRQ register is
> set up.
> 
> To fix this,
> 
>  - Do not write UNIPHIER_FI2C_CR for repeated START conditions.
> 
>  - Enable IRQ *before* writing the slave address to the TX FIFO.
> 
>  - Disable IRQ for the current CPU while queuing up the TX FIFO;
>    If the CPU is interrupted by some task, the interrupt handler
>    might be invoked due to the empty TX FIFO before completing the
>    setup.
> 
> Fixes: 6a62974b667f ("i2c: uniphier_f: add UniPhier FIFO-builtin I2C driver")
> Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>

Applied to for-next, thanks!


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 3/3] i2c: uniphier-f: fix race condition when IRQ is cleared
  2018-10-16  3:01 ` [PATCH 3/3] i2c: uniphier-f: fix race condition when IRQ is cleared Masahiro Yamada
@ 2018-10-28 22:08   ` Wolfram Sang
  0 siblings, 0 replies; 7+ messages in thread
From: Wolfram Sang @ 2018-10-28 22:08 UTC (permalink / raw)
  To: Masahiro Yamada; +Cc: Wolfram Sang, linux-i2c, linux-arm-kernel, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 617 bytes --]

On Tue, Oct 16, 2018 at 12:01:49PM +0900, Masahiro Yamada wrote:
> The current IRQ handler clears all the IRQ status bits when it bails
> out. This is dangerous because it might clear away the status bits
> that have just been set while processing the current handler. If this
> happens, the IRQ event for the latest transfer is lost forever.
> 
> The IRQ status bits must be cleared *before* the next transfer is
> kicked.
> 
> Fixes: 6a62974b667f ("i2c: uniphier_f: add UniPhier FIFO-builtin I2C driver")
> Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>

Applied to for-next, thanks!


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2018-10-28 22:08 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-10-16  3:01 [PATCH 0/3] i2c: uniphier-f: fix concurrency issues and race conditions Masahiro Yamada
2018-10-16  3:01 ` [PATCH 1/3] i2c: uniphier-f: make driver robust against concurrency Masahiro Yamada
2018-10-28 22:07   ` Wolfram Sang
2018-10-16  3:01 ` [PATCH 2/3] i2c: uniphier-f: fix occasional timeout error Masahiro Yamada
2018-10-28 22:08   ` Wolfram Sang
2018-10-16  3:01 ` [PATCH 3/3] i2c: uniphier-f: fix race condition when IRQ is cleared Masahiro Yamada
2018-10-28 22:08   ` Wolfram Sang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).