diff mbox series

i2c: imx: Make sure to unregister adapter on remove()

Message ID 20220720150933.239956-1-u.kleine-koenig@pengutronix.de
State Accepted
Commit d98bdd3a5b50446d8e010be5b04ce81c4eabf728
Headers show
Series i2c: imx: Make sure to unregister adapter on remove() | expand

Commit Message

Uwe Kleine-König July 20, 2022, 3:09 p.m. UTC
If for whatever reasons pm_runtime_resume_and_get() fails and .remove() is
exited early, the i2c adapter stays around and the irq still calls its
handler, while the driver data and the register mapping go away. So if
later the i2c adapter is accessed or the irq triggers this results in
havoc accessing freed memory and unmapped registers.

So unregister the software resources even if resume failed, and only skip
the hardware access in that case.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
---
 drivers/i2c/busses/i2c-imx.c | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)


base-commit: 6014cfa5bf32cf8c5c58b3cfd5ee0e1542c8a825

Comments

Oleksij Rempel July 29, 2022, 4:29 a.m. UTC | #1
Hi Uwe,

thank you for your work.

On Wed, Jul 20, 2022 at 05:09:33PM +0200, Uwe Kleine-König wrote:
> If for whatever reasons pm_runtime_resume_and_get() fails and .remove() is
> exited early, the i2c adapter stays around and the irq still calls its
> handler, while the driver data and the register mapping go away. So if
> later the i2c adapter is accessed or the irq triggers this results in
> havoc accessing freed memory and unmapped registers.
> 
> So unregister the software resources even if resume failed, and only skip
> the hardware access in that case.
> 
> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>

Can you please add Fixes tag. I assume this patch can got to stable
kernel version too.

Otherwise:
Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>

> ---
>  drivers/i2c/busses/i2c-imx.c | 20 +++++++++++---------
>  1 file changed, 11 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
> index e9e2db68b9fb..7395560c13d0 100644
> --- a/drivers/i2c/busses/i2c-imx.c
> +++ b/drivers/i2c/busses/i2c-imx.c
> @@ -1572,9 +1572,7 @@ static int i2c_imx_remove(struct platform_device *pdev)
>  	struct imx_i2c_struct *i2c_imx = platform_get_drvdata(pdev);
>  	int irq, ret;
>  
> -	ret = pm_runtime_resume_and_get(&pdev->dev);
> -	if (ret < 0)
> -		return ret;
> +	ret = pm_runtime_get_sync(&pdev->dev);
>  
>  	hrtimer_cancel(&i2c_imx->slave_timer);
>  
> @@ -1585,17 +1583,21 @@ static int i2c_imx_remove(struct platform_device *pdev)
>  	if (i2c_imx->dma)
>  		i2c_imx_dma_free(i2c_imx);
>  
> -	/* setup chip registers to defaults */
> -	imx_i2c_write_reg(0, i2c_imx, IMX_I2C_IADR);
> -	imx_i2c_write_reg(0, i2c_imx, IMX_I2C_IFDR);
> -	imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2CR);
> -	imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2SR);
> +	if (ret == 0) {
> +		/* setup chip registers to defaults */
> +		imx_i2c_write_reg(0, i2c_imx, IMX_I2C_IADR);
> +		imx_i2c_write_reg(0, i2c_imx, IMX_I2C_IFDR);
> +		imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2CR);
> +		imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2SR);
> +		clk_disable(i2c_imx->clk);
> +	}
>  
>  	clk_notifier_unregister(i2c_imx->clk, &i2c_imx->clk_change_nb);
>  	irq = platform_get_irq(pdev, 0);
>  	if (irq >= 0)
>  		free_irq(irq, i2c_imx);
> -	clk_disable_unprepare(i2c_imx->clk);
> +
> +	clk_unprepare(i2c_imx->clk);
>  
>  	pm_runtime_put_noidle(&pdev->dev);
>  	pm_runtime_disable(&pdev->dev);
> 
> base-commit: 6014cfa5bf32cf8c5c58b3cfd5ee0e1542c8a825
> -- 
> 2.36.1
> 
> 
>
Uwe Kleine-König July 30, 2022, 11:13 p.m. UTC | #2
On Fri, Jul 29, 2022 at 06:29:22AM +0200, Oleksij Rempel wrote:
> Hi Uwe,
> 
> thank you for your work.
> 
> On Wed, Jul 20, 2022 at 05:09:33PM +0200, Uwe Kleine-König wrote:
> > If for whatever reasons pm_runtime_resume_and_get() fails and .remove() is
> > exited early, the i2c adapter stays around and the irq still calls its
> > handler, while the driver data and the register mapping go away. So if
> > later the i2c adapter is accessed or the irq triggers this results in
> > havoc accessing freed memory and unmapped registers.
> > 
> > So unregister the software resources even if resume failed, and only skip
> > the hardware access in that case.
> > 
> > Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
> 
> Can you please add Fixes tag. I assume this patch can got to stable
> kernel version too.

That would be:

Fixes: 588eb93ea49f ("i2c: imx: add runtime pm support to improve the performance")

and that was merged around v4.4. I'm not sure it's sensible to backport
that given the bug is that old and didn't even pop up in a user story
but by a cleanup effort. *shrug* Up to you and Wolfram to decide.

Best regards
Uwe
diff mbox series

Patch

diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
index e9e2db68b9fb..7395560c13d0 100644
--- a/drivers/i2c/busses/i2c-imx.c
+++ b/drivers/i2c/busses/i2c-imx.c
@@ -1572,9 +1572,7 @@  static int i2c_imx_remove(struct platform_device *pdev)
 	struct imx_i2c_struct *i2c_imx = platform_get_drvdata(pdev);
 	int irq, ret;
 
-	ret = pm_runtime_resume_and_get(&pdev->dev);
-	if (ret < 0)
-		return ret;
+	ret = pm_runtime_get_sync(&pdev->dev);
 
 	hrtimer_cancel(&i2c_imx->slave_timer);
 
@@ -1585,17 +1583,21 @@  static int i2c_imx_remove(struct platform_device *pdev)
 	if (i2c_imx->dma)
 		i2c_imx_dma_free(i2c_imx);
 
-	/* setup chip registers to defaults */
-	imx_i2c_write_reg(0, i2c_imx, IMX_I2C_IADR);
-	imx_i2c_write_reg(0, i2c_imx, IMX_I2C_IFDR);
-	imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2CR);
-	imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2SR);
+	if (ret == 0) {
+		/* setup chip registers to defaults */
+		imx_i2c_write_reg(0, i2c_imx, IMX_I2C_IADR);
+		imx_i2c_write_reg(0, i2c_imx, IMX_I2C_IFDR);
+		imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2CR);
+		imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2SR);
+		clk_disable(i2c_imx->clk);
+	}
 
 	clk_notifier_unregister(i2c_imx->clk, &i2c_imx->clk_change_nb);
 	irq = platform_get_irq(pdev, 0);
 	if (irq >= 0)
 		free_irq(irq, i2c_imx);
-	clk_disable_unprepare(i2c_imx->clk);
+
+	clk_unprepare(i2c_imx->clk);
 
 	pm_runtime_put_noidle(&pdev->dev);
 	pm_runtime_disable(&pdev->dev);