[PATCH 5.15.y] i2c: imx: Fix slave registration race and error handling
Sasha Levin <[email protected]>
| Newsgroups | org.kernel.vger.stable |
|---|---|
| Message-ID | <[email protected]> |
From: Liem <[email protected]> [ Upstream commit d64ec362c369bbc33833f7936d5f3a706b0d5c45 ] In i2c_imx_reg_slave(), the slave pointer was assigned before pm_runtime_resume_and_get(). If pm_runtime_resume_and_get() failed, the error path returned without clearing i2c_imx->slave, leaving it non-NULL and causing all subsequent registration attempts to fail with -EBUSY. Additionally, because this driver uses a shared IRQ, the interrupt handler i2c_imx_isr() can execute concurrently and, after acquiring slave_lock, dereference i2c_imx->slave. The previous fix attempt added a lockless i2c_imx->slave = NULL on the error path, but that could race with the ISR under the lock and still cause a NULL pointer dereference. Fix both issues by deferring the assignment of i2c_imx->slave and i2c_imx->last_slave_event to after a successful resume, and by performing the assignment inside the slave_lock critical section. This guarantees that the slave pointer is never left stale on the error path and is always valid when observed by the interrupt handler. Fixes: f7414cd6923f ("i2c: imx: support slave mode for imx I2C driver") Signed-off-by: Liem <[email protected]> Cc: <[email protected]> # v5.11+ Reviewed-by: Frank Li <[email protected]> Acked-by: Carlos Song <[email protected]> Signed-off-by: Andi Shyti <[email protected]> Link: https://lore.kernel.org/r/[email protected] [ open-coded scoped_guard(spinlock_irqsave) into explicit spin_lock_irqsave/spin_unlock_irqrestore since 5.15 builds with -std=gnu89 ] Signed-off-by: Sasha Levin <[email protected]> --- drivers/i2c/busses/i2c-imx.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index fae674969628b..9badd5b773bd6 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -832,14 +832,12 @@ static void i2c_imx_slave_init(struct imx_i2c_struct *i2c_imx) static int i2c_imx_reg_slave(struct i2c_client *client) { struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(client->adapter); + unsigned long flags; int ret; if (i2c_imx->slave) return -EBUSY; - i2c_imx->slave = client; - i2c_imx->last_slave_event = I2C_SLAVE_STOP; - /* Resume */ ret = pm_runtime_resume_and_get(i2c_imx->adapter.dev.parent); if (ret < 0) { @@ -847,6 +845,11 @@ static int i2c_imx_reg_slave(struct i2c_client *client) return ret; } + spin_lock_irqsave(&i2c_imx->slave_lock, flags); + i2c_imx->slave = client; + i2c_imx->last_slave_event = I2C_SLAVE_STOP; + spin_unlock_irqrestore(&i2c_imx->slave_lock, flags); + i2c_imx_slave_init(i2c_imx); return 0; -- 2.53.0