* [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