aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHiromitsu Yamasaki <hiromitsu.yamasaki.ym@renesas.com>2019-03-03 10:03:13 -0500
committerWolfram Sang <wsa@the-dreams.de>2019-03-12 09:09:16 -0400
commita35ba2f74df5481cb4db1e9d582c708efeb9880d (patch)
tree43989d1e01d61250328f49c1d82992d187e832e8
parent60f7691c624b41a05bfc3493d9b0519e7951b7ef (diff)
i2c: rcar: fix concurrency issue related to ICDMAER
This patch fixes the problem that an interrupt may set up a new I2C message and the DMA callback overwrites this setup. By disabling the DMA Enable Register(ICDMAER), rcar_i2c_dma_unmap() enables interrupts for register settings (such as Master Control Register(ICMCR)) and advances the I2C transfer sequence. If an interrupt occurs immediately after ICDMAER is disabled, the callback handler later continues and overwrites the previous settings from the interrupt. So, disable ICDMAER at the end of the callback to ensure other interrupts are masked until then. Note that this driver needs to work lock-free because there are IP cores with a HW race condition which prevent us from using a spinlock in the interrupt handler. Reproduction test: 1. Add a delay after disabling ICDMAER. (It is expected to generate an interrupt of rcar_i2c_irq()) void rcar_i2c_dma_unmap(struct rcar_i2c_priv *priv) { ... rcar_i2c_write(priv, ICDMAER, 0); usleep_range(500, 800) ... priv->dma_direction = DMA_NONE; } 2. Execute DMA transfers $ i2ctransfer -y 4 w9@0x6a 1 1+ r16 3. A log message of BUG_ON() will be displayed. Fixes: 73e8b0528346 ("i2c: rcar: add DMA support") Signed-off-by: Hiromitsu Yamasaki <hiromitsu.yamasaki.ym@renesas.com> Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com> [wsa: updated test case to be more reliable, added note to comment] Reviewed-by: Simon Horman <horms+renesas@verge.net.au> Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
-rw-r--r--drivers/i2c/busses/i2c-rcar.c6
1 files changed, 3 insertions, 3 deletions
diff --git a/drivers/i2c/busses/i2c-rcar.c b/drivers/i2c/busses/i2c-rcar.c
index dd52a068b140..ce59e14e34b0 100644
--- a/drivers/i2c/busses/i2c-rcar.c
+++ b/drivers/i2c/busses/i2c-rcar.c
@@ -363,9 +363,6 @@ static void rcar_i2c_dma_unmap(struct rcar_i2c_priv *priv)
363 struct dma_chan *chan = priv->dma_direction == DMA_FROM_DEVICE 363 struct dma_chan *chan = priv->dma_direction == DMA_FROM_DEVICE
364 ? priv->dma_rx : priv->dma_tx; 364 ? priv->dma_rx : priv->dma_tx;
365 365
366 /* Disable DMA Master Received/Transmitted */
367 rcar_i2c_write(priv, ICDMAER, 0);
368
369 dma_unmap_single(chan->device->dev, sg_dma_address(&priv->sg), 366 dma_unmap_single(chan->device->dev, sg_dma_address(&priv->sg),
370 sg_dma_len(&priv->sg), priv->dma_direction); 367 sg_dma_len(&priv->sg), priv->dma_direction);
371 368
@@ -375,6 +372,9 @@ static void rcar_i2c_dma_unmap(struct rcar_i2c_priv *priv)
375 priv->flags |= ID_P_NO_RXDMA; 372 priv->flags |= ID_P_NO_RXDMA;
376 373
377 priv->dma_direction = DMA_NONE; 374 priv->dma_direction = DMA_NONE;
375
376 /* Disable DMA Master Received/Transmitted, must be last! */
377 rcar_i2c_write(priv, ICDMAER, 0);
378} 378}
379 379
380static void rcar_i2c_cleanup_dma(struct rcar_i2c_priv *priv) 380static void rcar_i2c_cleanup_dma(struct rcar_i2c_priv *priv)