[PATCH v3] hwmon: (gpio-fan) fix pm_runtime imbalance for alarm-only fans

From: Cong Nguyen

Date: Tue Sep 01 2026 - 07:25:09 EST


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@xxxxxxxxxxxx>
Link: https://lore.kernel.org/r/20260830152150.27F5F1F000E9@xxxxxxxxxxxxxxx
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Claude:claude-opus-4
Signed-off-by: Cong Nguyen <congnt264@xxxxxxxxx>
---
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