mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-11-27 01:11:31 +00:00
i2c: uniphier-f: make driver robust against concurrency
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: 6a62974b66 ("i2c: uniphier_f: add UniPhier FIFO-builtin I2C driver")
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
This commit is contained in:
parent
a560ae55dc
commit
f1fdcbbdf4
1 changed files with 16 additions and 1 deletions
|
|
@ -98,6 +98,7 @@ struct uniphier_fi2c_priv {
|
||||||
unsigned int flags;
|
unsigned int flags;
|
||||||
unsigned int busy_cnt;
|
unsigned int busy_cnt;
|
||||||
unsigned int clk_cycle;
|
unsigned int clk_cycle;
|
||||||
|
spinlock_t lock; /* IRQ synchronization */
|
||||||
};
|
};
|
||||||
|
|
||||||
static void uniphier_fi2c_fill_txfifo(struct uniphier_fi2c_priv *priv,
|
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;
|
struct uniphier_fi2c_priv *priv = dev_id;
|
||||||
u32 irq_status;
|
u32 irq_status;
|
||||||
|
|
||||||
|
spin_lock(&priv->lock);
|
||||||
|
|
||||||
irq_status = readl(priv->membase + UNIPHIER_FI2C_INT);
|
irq_status = readl(priv->membase + UNIPHIER_FI2C_INT);
|
||||||
|
irq_status &= priv->enabled_irqs;
|
||||||
|
|
||||||
dev_dbg(&priv->adap.dev,
|
dev_dbg(&priv->adap.dev,
|
||||||
"interrupt: enabled_irqs=%04x, irq_status=%04x\n",
|
"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;
|
goto handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spin_unlock(&priv->lock);
|
||||||
|
|
||||||
return IRQ_NONE;
|
return IRQ_NONE;
|
||||||
|
|
||||||
data_done:
|
data_done:
|
||||||
|
|
@ -246,6 +252,8 @@ complete:
|
||||||
handled:
|
handled:
|
||||||
uniphier_fi2c_clear_irqs(priv);
|
uniphier_fi2c_clear_irqs(priv);
|
||||||
|
|
||||||
|
spin_unlock(&priv->lock);
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
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);
|
struct uniphier_fi2c_priv *priv = i2c_get_adapdata(adap);
|
||||||
bool is_read = msg->flags & I2C_M_RD;
|
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",
|
dev_dbg(&adap->dev, "%s: addr=0x%02x, len=%d, stop=%d\n",
|
||||||
is_read ? "receive" : "transmit", msg->addr, msg->len, stop);
|
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);
|
priv->membase + UNIPHIER_FI2C_CR);
|
||||||
|
|
||||||
time_left = wait_for_completion_timeout(&priv->comp, adap->timeout);
|
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) {
|
if (!time_left) {
|
||||||
dev_err(&adap->dev, "transaction timeout.\n");
|
dev_err(&adap->dev, "transaction timeout.\n");
|
||||||
uniphier_fi2c_recover(priv);
|
uniphier_fi2c_recover(priv);
|
||||||
|
|
@ -529,6 +543,7 @@ static int uniphier_fi2c_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
priv->clk_cycle = clk_rate / bus_speed;
|
priv->clk_cycle = clk_rate / bus_speed;
|
||||||
init_completion(&priv->comp);
|
init_completion(&priv->comp);
|
||||||
|
spin_lock_init(&priv->lock);
|
||||||
priv->adap.owner = THIS_MODULE;
|
priv->adap.owner = THIS_MODULE;
|
||||||
priv->adap.algo = &uniphier_fi2c_algo;
|
priv->adap.algo = &uniphier_fi2c_algo;
|
||||||
priv->adap.dev.parent = dev;
|
priv->adap.dev.parent = dev;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue