Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Rui Zhang and committed by
Mark Brown
7993d3a9 c986968f

+28 -24
+28 -24
drivers/regulator/core.c
··· 2918 2918 /* Fallthrough on positive return values - already enabled */ 2919 2919 } 2920 2920 2921 - rdev->use_count++; 2921 + if (regulator->enable_count == 1) 2922 + rdev->use_count++; 2922 2923 2923 2924 return 0; 2924 2925 ··· 2994 2993 2995 2994 lockdep_assert_held_once(&rdev->mutex.base); 2996 2995 2997 - if (WARN(rdev->use_count <= 0, 2996 + if (WARN(regulator->enable_count == 0, 2998 2997 "unbalanced disables for %s\n", rdev_get_name(rdev))) 2999 2998 return -EIO; 3000 2999 3001 - /* are we the last user and permitted to disable ? */ 3002 - if (rdev->use_count == 1 && 3003 - (rdev->constraints && !rdev->constraints->always_on)) { 3000 + if (regulator->enable_count == 1) { 3001 + /* disabling last enable_count from this regulator */ 3002 + /* are we the last user and permitted to disable ? */ 3003 + if (rdev->use_count == 1 && 3004 + (rdev->constraints && !rdev->constraints->always_on)) { 3004 3005 3005 - /* we are last user */ 3006 - if (regulator_ops_is_valid(rdev, REGULATOR_CHANGE_STATUS)) { 3007 - ret = _notifier_call_chain(rdev, 3008 - REGULATOR_EVENT_PRE_DISABLE, 3009 - NULL); 3010 - if (ret & NOTIFY_STOP_MASK) 3011 - return -EINVAL; 3006 + /* we are last user */ 3007 + if (regulator_ops_is_valid(rdev, REGULATOR_CHANGE_STATUS)) { 3008 + ret = _notifier_call_chain(rdev, 3009 + REGULATOR_EVENT_PRE_DISABLE, 3010 + NULL); 3011 + if (ret & NOTIFY_STOP_MASK) 3012 + return -EINVAL; 3012 3013 3013 - ret = _regulator_do_disable(rdev); 3014 - if (ret < 0) { 3015 - rdev_err(rdev, "failed to disable: %pe\n", ERR_PTR(ret)); 3016 - _notifier_call_chain(rdev, 3017 - REGULATOR_EVENT_ABORT_DISABLE, 3014 + ret = _regulator_do_disable(rdev); 3015 + if (ret < 0) { 3016 + rdev_err(rdev, "failed to disable: %pe\n", ERR_PTR(ret)); 3017 + _notifier_call_chain(rdev, 3018 + REGULATOR_EVENT_ABORT_DISABLE, 3019 + NULL); 3020 + return ret; 3021 + } 3022 + _notifier_call_chain(rdev, REGULATOR_EVENT_DISABLE, 3018 3023 NULL); 3019 - return ret; 3020 3024 } 3021 - _notifier_call_chain(rdev, REGULATOR_EVENT_DISABLE, 3022 - NULL); 3023 - } 3024 3025 3025 - rdev->use_count = 0; 3026 - } else if (rdev->use_count > 1) { 3027 - rdev->use_count--; 3026 + rdev->use_count = 0; 3027 + } else if (rdev->use_count > 1) { 3028 + rdev->use_count--; 3029 + } 3028 3030 } 3029 3031 3030 3032 if (ret == 0)