diff mbox series

[1/3] pwm: lpc18xx-sct: Initialize driver data and hardware before pwmchip_add()

Message ID 20211110084950.1053426-1-u.kleine-koenig@pengutronix.de
State Accepted
Headers show
Series [1/3] pwm: lpc18xx-sct: Initialize driver data and hardware before pwmchip_add() | expand

Commit Message

Uwe Kleine-König Nov. 10, 2021, 8:49 a.m. UTC
When a driver calls pwmchip_add() it has to be prepared to immediately
get its callbacks called. So move allocation of driver data and hardware
initialization before the call to pwmchip_add().

This fixes a potential NULL pointer exception and a race condition on
register writes.

Fixes: 841e6f90bb78 ("pwm: NXP LPC18xx PWM/SCT driver")
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
---
 drivers/pwm/pwm-lpc18xx-sct.c | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)


base-commit: d2f38a3c6507b2520101f9a3807ed98f1bdc545a

Comments

Thierry Reding Feb. 1, 2022, 7:47 a.m. UTC | #1
On Wed, Nov 10, 2021 at 09:49:48AM +0100, Uwe Kleine-König wrote:
> When a driver calls pwmchip_add() it has to be prepared to immediately
> get its callbacks called. So move allocation of driver data and hardware
> initialization before the call to pwmchip_add().
> 
> This fixes a potential NULL pointer exception and a race condition on
> register writes.
> 
> Fixes: 841e6f90bb78 ("pwm: NXP LPC18xx PWM/SCT driver")
> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
> ---
>  drivers/pwm/pwm-lpc18xx-sct.c | 20 +++++++++-----------
>  1 file changed, 9 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/pwm/pwm-lpc18xx-sct.c b/drivers/pwm/pwm-lpc18xx-sct.c
> index 8e461f3baa05..8cc8ae16553c 100644
> --- a/drivers/pwm/pwm-lpc18xx-sct.c
> +++ b/drivers/pwm/pwm-lpc18xx-sct.c
> @@ -395,12 +395,6 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
>  	lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_LIMIT,
>  			   BIT(lpc18xx_pwm->period_event));
>  
> -	ret = pwmchip_add(&lpc18xx_pwm->chip);
> -	if (ret < 0) {
> -		dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret);
> -		goto disable_pwmclk;
> -	}
> -
>  	for (i = 0; i < lpc18xx_pwm->chip.npwm; i++) {
>  		struct lpc18xx_pwm_data *data;
>  
> @@ -410,14 +404,12 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
>  				    GFP_KERNEL);
>  		if (!data) {
>  			ret = -ENOMEM;
> -			goto remove_pwmchip;
> +			goto disable_pwmclk;
>  		}
>  
>  		pwm_set_chip_data(pwm, data);
>  	}
>  
> -	platform_set_drvdata(pdev, lpc18xx_pwm);
> -
>  	val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_CTRL);
>  	val &= ~LPC18XX_PWM_BIDIR;
>  	val &= ~LPC18XX_PWM_CTRL_HALT;
> @@ -425,10 +417,16 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
>  	val |= LPC18XX_PWM_PRE(0);
>  	lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CTRL, val);
>  
> +	ret = pwmchip_add(&lpc18xx_pwm->chip);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret);
> +		goto disable_pwmclk;
> +	}
> +
> +	platform_set_drvdata(pdev, lpc18xx_pwm);

Is there any particular reason why this needs to move? The driver only
call platform_get_drvdata() from the ->remove() implementation at this
point, so this should be fine, but it's possible that it would at some
future point try to access this data from some code path that may get
called as part of pwmchip_add(), so doing this prior to chip addition
may be a better option.

No need to resend, I can fix that up as I apply if there are no strong
reasons to keep this after pwmchip_add().

Thierry
Uwe Kleine-König Feb. 1, 2022, 8:19 a.m. UTC | #2
On Tue, Feb 01, 2022 at 08:47:26AM +0100, Thierry Reding wrote:
> On Wed, Nov 10, 2021 at 09:49:48AM +0100, Uwe Kleine-König wrote:
> > When a driver calls pwmchip_add() it has to be prepared to immediately
> > get its callbacks called. So move allocation of driver data and hardware
> > initialization before the call to pwmchip_add().
> > 
> > This fixes a potential NULL pointer exception and a race condition on
> > register writes.
> > 
> > Fixes: 841e6f90bb78 ("pwm: NXP LPC18xx PWM/SCT driver")
> > Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
> > ---
> >  drivers/pwm/pwm-lpc18xx-sct.c | 20 +++++++++-----------
> >  1 file changed, 9 insertions(+), 11 deletions(-)
> > 
> > diff --git a/drivers/pwm/pwm-lpc18xx-sct.c b/drivers/pwm/pwm-lpc18xx-sct.c
> > index 8e461f3baa05..8cc8ae16553c 100644
> > --- a/drivers/pwm/pwm-lpc18xx-sct.c
> > +++ b/drivers/pwm/pwm-lpc18xx-sct.c
> > @@ -395,12 +395,6 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
> >  	lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_LIMIT,
> >  			   BIT(lpc18xx_pwm->period_event));
> >  
> > -	ret = pwmchip_add(&lpc18xx_pwm->chip);
> > -	if (ret < 0) {
> > -		dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret);
> > -		goto disable_pwmclk;
> > -	}
> > -
> >  	for (i = 0; i < lpc18xx_pwm->chip.npwm; i++) {
> >  		struct lpc18xx_pwm_data *data;
> >  
> > @@ -410,14 +404,12 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
> >  				    GFP_KERNEL);
> >  		if (!data) {
> >  			ret = -ENOMEM;
> > -			goto remove_pwmchip;
> > +			goto disable_pwmclk;
> >  		}
> >  
> >  		pwm_set_chip_data(pwm, data);
> >  	}
> >  
> > -	platform_set_drvdata(pdev, lpc18xx_pwm);
> > -
> >  	val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_CTRL);
> >  	val &= ~LPC18XX_PWM_BIDIR;
> >  	val &= ~LPC18XX_PWM_CTRL_HALT;
> > @@ -425,10 +417,16 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
> >  	val |= LPC18XX_PWM_PRE(0);
> >  	lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CTRL, val);
> >  
> > +	ret = pwmchip_add(&lpc18xx_pwm->chip);
> > +	if (ret < 0) {
> > +		dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret);
> > +		goto disable_pwmclk;
> > +	}
> > +
> > +	platform_set_drvdata(pdev, lpc18xx_pwm);
> 
> Is there any particular reason why this needs to move? The driver only
> call platform_get_drvdata() from the ->remove() implementation at this
> point, so this should be fine, but it's possible that it would at some
> future point try to access this data from some code path that may get
> called as part of pwmchip_add(), so doing this prior to chip addition
> may be a better option.
> 
> No need to resend, I can fix that up as I apply if there are no strong
> reasons to keep this after pwmchip_add().

What I actually did was:

 	lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_LIMIT,
 			   BIT(lpc18xx_pwm->period_event));
 
+	for (i = 0; i < lpc18xx_pwm->chip.npwm; i++) {
+		struct lpc18xx_pwm_data *data;
+
+		pwm = &lpc18xx_pwm->chip.pwms[i];
+
+		data = devm_kzalloc(lpc18xx_pwm->dev, sizeof(*data),
+				    GFP_KERNEL);
+		if (!data) {
+			ret = -ENOMEM;
+			goto disable_pwmclk;
+		}
+
+		pwm_set_chip_data(pwm, data);
+	}
+
+	val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_CTRL);
+	val &= ~LPC18XX_PWM_BIDIR;
+	val &= ~LPC18XX_PWM_CTRL_HALT;
+	val &= ~LPC18XX_PWM_PRE_MASK;
+	val |= LPC18XX_PWM_PRE(0);
+	lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CTRL, val);
+
 	ret = pwmchip_add(&lpc18xx_pwm->chip);
 	if (ret < 0) {
 		dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret);
 		goto disable_pwmclk;
 	}
 
-	for (i = 0; i < lpc18xx_pwm->chip.npwm; i++) {
-		struct lpc18xx_pwm_data *data;
-
-		pwm = &lpc18xx_pwm->chip.pwms[i];
-
-		data = devm_kzalloc(lpc18xx_pwm->dev, sizeof(*data),
-				    GFP_KERNEL);
-		if (!data) {
-			ret = -ENOMEM;
-			goto remove_pwmchip;
-		}
-
-		pwm_set_chip_data(pwm, data);
-	}
-
 	platform_set_drvdata(pdev, lpc18xx_pwm);
 
-	val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_CTRL);
-	val &= ~LPC18XX_PWM_BIDIR;
-	val &= ~LPC18XX_PWM_CTRL_HALT;
-	val &= ~LPC18XX_PWM_PRE_MASK;
-	val |= LPC18XX_PWM_PRE(0);
-	lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CTRL, val);
-
 	return 0;
 
-remove_pwmchip:
-	pwmchip_remove(&lpc18xx_pwm->chip);
 disable_pwmclk:
 	clk_disable_unprepare(lpc18xx_pwm->pwm_clk);
 	return ret;
 }


and git simplified that making it look as if platform_set_drvdata was
moved.

Best regards
Uwe
diff mbox series

Patch

diff --git a/drivers/pwm/pwm-lpc18xx-sct.c b/drivers/pwm/pwm-lpc18xx-sct.c
index 8e461f3baa05..8cc8ae16553c 100644
--- a/drivers/pwm/pwm-lpc18xx-sct.c
+++ b/drivers/pwm/pwm-lpc18xx-sct.c
@@ -395,12 +395,6 @@  static int lpc18xx_pwm_probe(struct platform_device *pdev)
 	lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_LIMIT,
 			   BIT(lpc18xx_pwm->period_event));
 
-	ret = pwmchip_add(&lpc18xx_pwm->chip);
-	if (ret < 0) {
-		dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret);
-		goto disable_pwmclk;
-	}
-
 	for (i = 0; i < lpc18xx_pwm->chip.npwm; i++) {
 		struct lpc18xx_pwm_data *data;
 
@@ -410,14 +404,12 @@  static int lpc18xx_pwm_probe(struct platform_device *pdev)
 				    GFP_KERNEL);
 		if (!data) {
 			ret = -ENOMEM;
-			goto remove_pwmchip;
+			goto disable_pwmclk;
 		}
 
 		pwm_set_chip_data(pwm, data);
 	}
 
-	platform_set_drvdata(pdev, lpc18xx_pwm);
-
 	val = lpc18xx_pwm_readl(lpc18xx_pwm, LPC18XX_PWM_CTRL);
 	val &= ~LPC18XX_PWM_BIDIR;
 	val &= ~LPC18XX_PWM_CTRL_HALT;
@@ -425,10 +417,16 @@  static int lpc18xx_pwm_probe(struct platform_device *pdev)
 	val |= LPC18XX_PWM_PRE(0);
 	lpc18xx_pwm_writel(lpc18xx_pwm, LPC18XX_PWM_CTRL, val);
 
+	ret = pwmchip_add(&lpc18xx_pwm->chip);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret);
+		goto disable_pwmclk;
+	}
+
+	platform_set_drvdata(pdev, lpc18xx_pwm);
+
 	return 0;
 
-remove_pwmchip:
-	pwmchip_remove(&lpc18xx_pwm->chip);
 disable_pwmclk:
 	clk_disable_unprepare(lpc18xx_pwm->pwm_clk);
 	return ret;