regulator: core: Only increment use_count when enable_count changes
authorRui Zhang <zr.zhang@vivo.com>
Fri, 3 Nov 2023 07:42:31 +0000 (15:42 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 5 Feb 2024 20:14:14 +0000 (20:14 +0000)
[ Upstream commit 7993d3a9c34f609c02171e115fd12c10e2105ff4 ]

The use_count of a regulator should only be incremented when the
enable_count changes from 0 to 1. Similarly, the use_count should
only be decremented when the enable_count changes from 1 to 0.

In the previous implementation, use_count was sometimes decremented
to 0 when some consumer called unbalanced disable,
leading to unexpected disable even the regulator is enabled by
other consumers. With this change, the use_count accurately reflects
the number of users which the regulator is enabled.

This should make things more robust in the case where a consumer does
leak references.

Signed-off-by: Rui Zhang <zr.zhang@vivo.com>
Link: https://lore.kernel.org/r/20231103074231.8031-1-zr.zhang@vivo.com
Signed-off-by: Mark Brown <broonie@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/regulator/core.c

index 3137e40..a7b3e54 100644 (file)
@@ -2918,7 +2918,8 @@ static int _regulator_enable(struct regulator *regulator)
                /* Fallthrough on positive return values - already enabled */
        }
 
-       rdev->use_count++;
+       if (regulator->enable_count == 1)
+               rdev->use_count++;
 
        return 0;
 
@@ -2993,37 +2994,40 @@ static int _regulator_disable(struct regulator *regulator)
 
        lockdep_assert_held_once(&rdev->mutex.base);
 
-       if (WARN(rdev->use_count <= 0,
+       if (WARN(regulator->enable_count == 0,
                 "unbalanced disables for %s\n", rdev_get_name(rdev)))
                return -EIO;
 
-       /* are we the last user and permitted to disable ? */
-       if (rdev->use_count == 1 &&
-           (rdev->constraints && !rdev->constraints->always_on)) {
-
-               /* we are last user */
-               if (regulator_ops_is_valid(rdev, REGULATOR_CHANGE_STATUS)) {
-                       ret = _notifier_call_chain(rdev,
-                                                  REGULATOR_EVENT_PRE_DISABLE,
-                                                  NULL);
-                       if (ret & NOTIFY_STOP_MASK)
-                               return -EINVAL;
-
-                       ret = _regulator_do_disable(rdev);
-                       if (ret < 0) {
-                               rdev_err(rdev, "failed to disable: %pe\n", ERR_PTR(ret));
-                               _notifier_call_chain(rdev,
-                                               REGULATOR_EVENT_ABORT_DISABLE,
+       if (regulator->enable_count == 1) {
+       /* disabling last enable_count from this regulator */
+               /* are we the last user and permitted to disable ? */
+               if (rdev->use_count == 1 &&
+                   (rdev->constraints && !rdev->constraints->always_on)) {
+
+                       /* we are last user */
+                       if (regulator_ops_is_valid(rdev, REGULATOR_CHANGE_STATUS)) {
+                               ret = _notifier_call_chain(rdev,
+                                                          REGULATOR_EVENT_PRE_DISABLE,
+                                                          NULL);
+                               if (ret & NOTIFY_STOP_MASK)
+                                       return -EINVAL;
+
+                               ret = _regulator_do_disable(rdev);
+                               if (ret < 0) {
+                                       rdev_err(rdev, "failed to disable: %pe\n", ERR_PTR(ret));
+                                       _notifier_call_chain(rdev,
+                                                       REGULATOR_EVENT_ABORT_DISABLE,
+                                                       NULL);
+                                       return ret;
+                               }
+                               _notifier_call_chain(rdev, REGULATOR_EVENT_DISABLE,
                                                NULL);
-                               return ret;
                        }
-                       _notifier_call_chain(rdev, REGULATOR_EVENT_DISABLE,
-                                       NULL);
-               }
 
-               rdev->use_count = 0;
-       } else if (rdev->use_count > 1) {
-               rdev->use_count--;
+                       rdev->use_count = 0;
+               } else if (rdev->use_count > 1) {
+                       rdev->use_count--;
+               }
        }
 
        if (ret == 0)