Linux Hardware Monitor development
 help / color / mirror / Atom feed
* [PATCH v3] hwmon: (gpio-fan) fix pm_runtime imbalance for alarm-only fans
@ 2026-09-01 11:19 Cong Nguyen
  2026-09-01 11:32 ` sashiko-bot
  0 siblings, 1 reply; 3+ messages in thread
From: Cong Nguyen @ 2026-09-01 11:19 UTC (permalink / raw)
  To: Guenter Roeck, Alexander Stein; +Cc: linux-hwmon, linux-kernel

pm_runtime_enable() runs unconditionally in probe, but the devm cleanup
that calls pm_runtime_disable() is only registered when control GPIOs
are present. Alarm-only fans never get it, so unbind warns about the
missing disable.

v2 fixed this by switching to devm_pm_runtime_enable() and moving it
before gpio_fan_stop()'s devm registration, so LIFO teardown runs
gpio_fan_stop() first. Sashiko/Guenter correctly flagged that moving
the actual pm_runtime_enable() call earlier opens a new race: hwmon
sysfs (pwm1, fan1_target) is now exposed while PM is already enabled,
so a concurrent sysfs write transitioning speed 0->nonzero can call
pm_runtime_resume_and_get() successfully, and probe's own final
speed_index check does it again -- a double-increment that permanently
blocks runtime suspend. In the original code this same race just fails
cleanly with -EACCES, since PM isn't enabled yet at that point.

Fix this properly: reserve the devm cleanup slot early (a small
gpio_fan_pm_runtime_disable() wrapper, registered right after the
regulator is acquired, before gpio_fan_stop()'s registration) without
touching when pm_runtime_enable() itself actually runs. The enable call
stays at its original position, after hwmon registration and alarm
init, so the race window is identical to the pristine driver -- only
the devm teardown order changes, restoring gpio_fan_stop() running
before the PM disable on unbind, exactly as v2 intended.

Fixes: 0d01110e6356 ("hwmon: (gpio-fan) Add regulator support")
Reported-by: Guenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/20260830152150.27F5F1F000E9@smtp.kernel.org
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4
Signed-off-by: Cong Nguyen <congnt264@gmail.com>
---
Changes in v3:
- v2 moved the actual pm_runtime_enable() call earlier, which fixed the
  LIFO ordering but opened a new race (Sashiko/Guenter): sysfs exposed
  while PM already enabled. v3 only moves the devm cleanup registration
  early, leaving pm_runtime_enable() at its original position -- no
  behavior change to the race window, only teardown order changes.

 drivers/hwmon/gpio-fan.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c
index 084828e1e281..8b28f0d58c63 100644
--- a/drivers/hwmon/gpio-fan.c
+++ b/drivers/hwmon/gpio-fan.c
@@ -524,8 +524,11 @@ static void gpio_fan_stop(void *data)
 	mutex_lock(&fan_data->lock);
 	set_fan_speed(data, 0);
 	mutex_unlock(&fan_data->lock);
+}
 
-	pm_runtime_disable(fan_data->dev);
+static void gpio_fan_pm_runtime_disable(void *data)
+{
+	pm_runtime_disable(data);
 }
 
 static int gpio_fan_probe(struct platform_device *pdev)
@@ -553,6 +556,17 @@ static int gpio_fan_probe(struct platform_device *pdev)
 		return dev_err_probe(dev, PTR_ERR(fan_data->supply),
 				     "Failed to get fan-supply");
 
+	/*
+	 * Reserve this devm slot before gpio_fan_stop()'s so LIFO teardown
+	 * runs gpio_fan_stop() (needs PM enabled to disable the regulator)
+	 * first. pm_runtime_enable() itself still happens at its original
+	 * position below, so this doesn't change when PM actually becomes
+	 * enabled -- only where its eventual disable is queued.
+	 */
+	err = devm_add_action_or_reset(dev, gpio_fan_pm_runtime_disable, dev);
+	if (err)
+		return err;
+
 	/* Configure control GPIOs if available. */
 	if (fan_data->gpios && fan_data->num_gpios > 0) {
 		if (!fan_data->speed || fan_data->num_speed <= 1)
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH v3] hwmon: (gpio-fan) fix pm_runtime imbalance for alarm-only fans
  2026-09-01 11:19 [PATCH v3] hwmon: (gpio-fan) fix pm_runtime imbalance for alarm-only fans Cong Nguyen
@ 2026-09-01 11:32 ` sashiko-bot
  2026-09-01 15:57   ` Guenter Roeck
  0 siblings, 1 reply; 3+ messages in thread
From: sashiko-bot @ 2026-09-01 11:32 UTC (permalink / raw)
  To: Cong Nguyen; +Cc: linux-hwmon

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:

New issues:
- [High] Unbalanced pm_runtime_disable() on probe error paths permanently disables runtime PM.

Pre-existing issues:
- [High] Failing to resume the device at the end of probe causes a usage count underflow during devm cleanup.
- [High] A race condition between probe and sysfs writes can cause a double-increment of the runtime PM usage count, permanently blocking runtime suspend.
- [High] Missing synchronize_irq() in runtime suspend callback for shared IRQ.
- [High] Missing cancel_work_sync() for alarm_work leads to a Use-After-Free on driver unbind or probe failure.
- [High] Unconditional IRQ_NONE return in a shared interrupt handler.
--

commit c1b61438a4a40096fe54d7b86037b9d5eebc1cae
Author: Cong Nguyen <congnt264@gmail.com>

hwmon: (gpio-fan) fix pm_runtime imbalance for alarm-only fans

pm_runtime_enable() runs unconditionally in probe, but the devm cleanup
that calls pm_runtime_disable() is only registered when control GPIOs
are present. Alarm-only fans never get it, so unbind warns about the
missing disable.

Link: https://lore.kernel.org/r/20260830152150.27F5F1F000E9@smtp.kernel.org

> diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c
> index 084828e1e2817..8b28f0d58c638 100644
> --- a/drivers/hwmon/gpio-fan.c
> +++ b/drivers/hwmon/gpio-fan.c
[ ... ]
> @@ -553,6 +556,17 @@ static int gpio_fan_probe(struct platform_device *pdev)
>  		return dev_err_probe(dev, PTR_ERR(fan_data->supply),
>  				     "Failed to get fan-supply");
>  
> +	/*
> +	 * Reserve this devm slot before gpio_fan_stop()'s so LIFO teardown
> +	 * runs gpio_fan_stop() (needs PM enabled to disable the regulator)
> +	 * first. pm_runtime_enable() itself still happens at its original
> +	 * position below, so this doesn't change when PM actually becomes
> +	 * enabled -- only where its eventual disable is queued.
> +	 */
> +	err = devm_add_action_or_reset(dev, gpio_fan_pm_runtime_disable, dev);
> +	if (err)
> +		return err;

[Severity: High]
Does unconditionally registering devm_add_action_or_reset() early in the
probe path cause an unbalanced pm_runtime_disable() on probe error paths?

If probe fails at any intermediate point (e.g., in fan_ctrl_init(),
hwmon_device_register_with_groups(), or fan_alarm_init()), devm teardown will
execute gpio_fan_pm_runtime_disable(). Since pm_runtime_enable() hasn't been
reached yet, this incorrectly increments dev->power.disable_depth. On a
subsequent probe attempt, pm_runtime_enable() will only decrement it to 1,
leaving runtime PM permanently disabled.

[Severity: High]
This isn't a bug introduced by this patch, but does failing to resume the device
at the end of probe cause a usage count underflow during devm cleanup?

fan_ctrl_init() initializes fan_data->speed_index based on the hardware state.
If the fan is active, speed_index > 0. gpio_fan_stop() is then queued as a devm
action. If probe subsequently fails before the final pm_runtime_resume_and_get()
succeeds, the devm cleanup runs gpio_fan_stop(), which calls set_fan_speed():

drivers/hwmon/gpio-fan.c:set_fan_speed() {
	...
	if (fan_data->speed_index > 0 && speed_index == 0) {
		int ret;

		ret = pm_runtime_put_sync(fan_data->dev);
		if (ret < 0 && ret != -ENOSYS)
			return ret;
	}
	...
}

Because speed_index > 0, set_fan_speed() drops a PM reference that was never
actually acquired, pushing the usage count below zero.

[Severity: High]
This is a pre-existing issue, but does a race condition between probe and
sysfs writes cause a double-increment of the runtime PM usage count?

Sysfs attributes are exposed early by devm_hwmon_device_register_with_groups().
Userspace can write to pwm1 right after probe calls pm_runtime_enable(). The
sysfs handler acquires a PM reference and sets speed_index > 0.

The probe thread then checks the state:

drivers/hwmon/gpio-fan.c:gpio_fan_probe() {
	...
	/* If current GPIO state is active, mark RPM as active as well */
	if (fan_data->speed_index > 0) {
		int ret;

		ret = pm_runtime_resume_and_get(&pdev->dev);
		if (ret)
			return ret;
	}
	...
}

Since this check doesn't hold fan_data->lock, probe sees the new value and takes
a second PM reference. When userspace later stops the fan, only one reference
is dropped, permanently blocking runtime suspend.

[Severity: High]
This is a pre-existing issue, but is synchronize_irq() missing in the runtime
suspend callback for a shared IRQ?

The driver requests a shared interrupt for the alarm GPIO:

drivers/hwmon/gpio-fan.c:fan_alarm_init() {
	...
	return devm_request_irq(dev, alarm_irq, fan_alarm_irq_handler,
				IRQF_SHARED, "GPIO fan alarm", fan_data);
}

The PM subsystem guide mandates that drivers using IRQF_SHARED MUST call
synchronize_irq() in their runtime suspend callback before powering down
hardware.

Since this is missing, the IRQ handler could execute mid-flight while the
regulator is being disabled in gpio_fan_runtime_suspend():

drivers/hwmon/gpio-fan.c:gpio_fan_runtime_suspend() {
	...
	if (fan_data->supply)
		ret = regulator_disable(fan_data->supply);
	...
}

[Severity: High]
This is a pre-existing issue, but does missing cancel_work_sync() for alarm_work
lead to a use-after-free on driver unbind or probe failure?

The driver initializes fan_data->alarm_work and requests an interrupt to
schedule it:

drivers/hwmon/gpio-fan.c:fan_alarm_init() {
	...
	INIT_WORK(&fan_data->alarm_work, fan_alarm_notify);
	...
	return devm_request_irq(dev, alarm_irq, ...);
}

It fails to register a cleanup action to call cancel_work_sync(). During driver
unbind or probe failure, devm_request_irq is unwound, which synchronizes with
running handlers but does not cancel pending work. fan_data is then freed by
devm. If an interrupt fired right before cleanup, the work executes
asynchronously and dereferences the freed fan_data pointer.

[Severity: High]
This is a pre-existing issue, but does the shared interrupt handler return an
unconditional IRQ_NONE?

drivers/hwmon/gpio-fan.c:fan_alarm_irq_handler() {
	...
	schedule_work(&fan_data->alarm_work);

	return IRQ_NONE;
}

The driver requests an IRQF_SHARED interrupt. In its handler, it processes the
event but unconditionally returns IRQ_NONE. When the kernel sees IRQ_NONE from
all handlers on a shared line, it registers an unhandled interrupt. After enough
unhandled interrupts, the kernel disables the IRQ line permanently, breaking all
devices sharing it.

> +
>  	/* Configure control GPIOs if available. */
>  	if (fan_data->gpios && fan_data->num_gpios > 0) {
>  		if (!fan_data->speed || fan_data->num_speed <= 1)

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260901111903.660681-1-congnt264@gmail.com?part=1

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH v3] hwmon: (gpio-fan) fix pm_runtime imbalance for alarm-only fans
  2026-09-01 11:32 ` sashiko-bot
@ 2026-09-01 15:57   ` Guenter Roeck
  0 siblings, 0 replies; 3+ messages in thread
From: Guenter Roeck @ 2026-09-01 15:57 UTC (permalink / raw)
  To: sashiko-reviews, Cong Nguyen; +Cc: linux-hwmon

On 9/1/26 04:32, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
> 
> New issues:
> - [High] Unbalanced pm_runtime_disable() on probe error paths permanently disables runtime PM.
> 
> Pre-existing issues:
> - [High] Failing to resume the device at the end of probe causes a usage count underflow during devm cleanup.
> - [High] A race condition between probe and sysfs writes can cause a double-increment of the runtime PM usage count, permanently blocking runtime suspend.
> - [High] Missing synchronize_irq() in runtime suspend callback for shared IRQ.
> - [High] Missing cancel_work_sync() for alarm_work leads to a Use-After-Free on driver unbind or probe failure.
> - [High] Unconditional IRQ_NONE return in a shared interrupt handler.

PM seems to be almost impossible to get right :-(.
I don't expect the other issues to be fixed as part of this patch,
but it should fix all the PM issues.

Thanks,
Guenter

> --
> 
> commit c1b61438a4a40096fe54d7b86037b9d5eebc1cae
> Author: Cong Nguyen <congnt264@gmail.com>
> 
> hwmon: (gpio-fan) fix pm_runtime imbalance for alarm-only fans
> 
> pm_runtime_enable() runs unconditionally in probe, but the devm cleanup
> that calls pm_runtime_disable() is only registered when control GPIOs
> are present. Alarm-only fans never get it, so unbind warns about the
> missing disable.
> 
> Link: https://lore.kernel.org/r/20260830152150.27F5F1F000E9@smtp.kernel.org
> 
>> diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c
>> index 084828e1e2817..8b28f0d58c638 100644
>> --- a/drivers/hwmon/gpio-fan.c
>> +++ b/drivers/hwmon/gpio-fan.c
> [ ... ]
>> @@ -553,6 +556,17 @@ static int gpio_fan_probe(struct platform_device *pdev)
>>   		return dev_err_probe(dev, PTR_ERR(fan_data->supply),
>>   				     "Failed to get fan-supply");
>>   
>> +	/*
>> +	 * Reserve this devm slot before gpio_fan_stop()'s so LIFO teardown
>> +	 * runs gpio_fan_stop() (needs PM enabled to disable the regulator)
>> +	 * first. pm_runtime_enable() itself still happens at its original
>> +	 * position below, so this doesn't change when PM actually becomes
>> +	 * enabled -- only where its eventual disable is queued.
>> +	 */
>> +	err = devm_add_action_or_reset(dev, gpio_fan_pm_runtime_disable, dev);
>> +	if (err)
>> +		return err;
> 
> [Severity: High]
> Does unconditionally registering devm_add_action_or_reset() early in the
> probe path cause an unbalanced pm_runtime_disable() on probe error paths?
> 
> If probe fails at any intermediate point (e.g., in fan_ctrl_init(),
> hwmon_device_register_with_groups(), or fan_alarm_init()), devm teardown will
> execute gpio_fan_pm_runtime_disable(). Since pm_runtime_enable() hasn't been
> reached yet, this incorrectly increments dev->power.disable_depth. On a
> subsequent probe attempt, pm_runtime_enable() will only decrement it to 1,
> leaving runtime PM permanently disabled.
> 
> [Severity: High]
> This isn't a bug introduced by this patch, but does failing to resume the device
> at the end of probe cause a usage count underflow during devm cleanup?
> 
> fan_ctrl_init() initializes fan_data->speed_index based on the hardware state.
> If the fan is active, speed_index > 0. gpio_fan_stop() is then queued as a devm
> action. If probe subsequently fails before the final pm_runtime_resume_and_get()
> succeeds, the devm cleanup runs gpio_fan_stop(), which calls set_fan_speed():
> 
> drivers/hwmon/gpio-fan.c:set_fan_speed() {
> 	...
> 	if (fan_data->speed_index > 0 && speed_index == 0) {
> 		int ret;
> 
> 		ret = pm_runtime_put_sync(fan_data->dev);
> 		if (ret < 0 && ret != -ENOSYS)
> 			return ret;
> 	}
> 	...
> }
> 
> Because speed_index > 0, set_fan_speed() drops a PM reference that was never
> actually acquired, pushing the usage count below zero.
> 
> [Severity: High]
> This is a pre-existing issue, but does a race condition between probe and
> sysfs writes cause a double-increment of the runtime PM usage count?
> 
> Sysfs attributes are exposed early by devm_hwmon_device_register_with_groups().
> Userspace can write to pwm1 right after probe calls pm_runtime_enable(). The
> sysfs handler acquires a PM reference and sets speed_index > 0.
> 
> The probe thread then checks the state:
> 
> drivers/hwmon/gpio-fan.c:gpio_fan_probe() {
> 	...
> 	/* If current GPIO state is active, mark RPM as active as well */
> 	if (fan_data->speed_index > 0) {
> 		int ret;
> 
> 		ret = pm_runtime_resume_and_get(&pdev->dev);
> 		if (ret)
> 			return ret;
> 	}
> 	...
> }
> 
> Since this check doesn't hold fan_data->lock, probe sees the new value and takes
> a second PM reference. When userspace later stops the fan, only one reference
> is dropped, permanently blocking runtime suspend.
> 
> [Severity: High]
> This is a pre-existing issue, but is synchronize_irq() missing in the runtime
> suspend callback for a shared IRQ?
> 
> The driver requests a shared interrupt for the alarm GPIO:
> 
> drivers/hwmon/gpio-fan.c:fan_alarm_init() {
> 	...
> 	return devm_request_irq(dev, alarm_irq, fan_alarm_irq_handler,
> 				IRQF_SHARED, "GPIO fan alarm", fan_data);
> }
> 
> The PM subsystem guide mandates that drivers using IRQF_SHARED MUST call
> synchronize_irq() in their runtime suspend callback before powering down
> hardware.
> 
> Since this is missing, the IRQ handler could execute mid-flight while the
> regulator is being disabled in gpio_fan_runtime_suspend():
> 
> drivers/hwmon/gpio-fan.c:gpio_fan_runtime_suspend() {
> 	...
> 	if (fan_data->supply)
> 		ret = regulator_disable(fan_data->supply);
> 	...
> }
> 
> [Severity: High]
> This is a pre-existing issue, but does missing cancel_work_sync() for alarm_work
> lead to a use-after-free on driver unbind or probe failure?
> 
> The driver initializes fan_data->alarm_work and requests an interrupt to
> schedule it:
> 
> drivers/hwmon/gpio-fan.c:fan_alarm_init() {
> 	...
> 	INIT_WORK(&fan_data->alarm_work, fan_alarm_notify);
> 	...
> 	return devm_request_irq(dev, alarm_irq, ...);
> }
> 
> It fails to register a cleanup action to call cancel_work_sync(). During driver
> unbind or probe failure, devm_request_irq is unwound, which synchronizes with
> running handlers but does not cancel pending work. fan_data is then freed by
> devm. If an interrupt fired right before cleanup, the work executes
> asynchronously and dereferences the freed fan_data pointer.
> 
> [Severity: High]
> This is a pre-existing issue, but does the shared interrupt handler return an
> unconditional IRQ_NONE?
> 
> drivers/hwmon/gpio-fan.c:fan_alarm_irq_handler() {
> 	...
> 	schedule_work(&fan_data->alarm_work);
> 
> 	return IRQ_NONE;
> }
> 
> The driver requests an IRQF_SHARED interrupt. In its handler, it processes the
> event but unconditionally returns IRQ_NONE. When the kernel sees IRQ_NONE from
> all handlers on a shared line, it registers an unhandled interrupt. After enough
> unhandled interrupts, the kernel disables the IRQ line permanently, breaking all
> devices sharing it.
> 
>> +
>>   	/* Configure control GPIOs if available. */
>>   	if (fan_data->gpios && fan_data->num_gpios > 0) {
>>   		if (!fan_data->speed || fan_data->num_speed <= 1)
> 


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-09-01 15:57 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-01 11:19 [PATCH v3] hwmon: (gpio-fan) fix pm_runtime imbalance for alarm-only fans Cong Nguyen
2026-09-01 11:32 ` sashiko-bot
2026-09-01 15:57   ` Guenter Roeck

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox