regulator: core: Only increment use_count when enable_count changes

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>
This commit is contained in:
Rui Zhang 2023-11-03 15:42:31 +08:00 committed by Mark Brown
parent c986968fe9
commit 7993d3a9c3
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
1 changed files with 28 additions and 24 deletions

View 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)) {
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;
/* 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,
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)