diff mbox series

[v3,1/6] regulator: core: validate selector against linear_min_sel

Message ID 1605280870-32432-2-git-send-email-claudiu.beznea@microchip.com (mailing list archive)
State New, archived
Headers show
Series regulator: mcp16502: add support for ramp delay | expand

Commit Message

Claudiu Beznea Nov. 13, 2020, 3:21 p.m. UTC
There are regulators who's min selector is not zero. Selectors loops
(looping b/w zero and regulator::desc::n_voltages) might throw errors
because invalid selectors are used (lower than
regulator::desc::linear_min_sel). For this situations validate selectors
against regulator::desc::linear_min_sel.

Fixes: 3a40cfc36bb3d ("regulator: core: create unlocked version of regulator_list_voltage")
Fixes: 04eca28cde52c ("regulator: Add helpers for low-level register access")
Fixes: 88cd222b259d6 ("regulator: provide consumer interface for fall/rise time")
Fixes: d295f7670127e ("regulator: core: Move list_voltage_{linear,linear_range,table} to helpers.c")
Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/regulator/core.c    | 9 +++++++--
 drivers/regulator/helpers.c | 3 ++-
 2 files changed, 9 insertions(+), 3 deletions(-)

Comments

Jon Hunter Nov. 24, 2020, 9:36 a.m. UTC | #1
On 13/11/2020 15:21, Claudiu Beznea wrote:
> There are regulators who's min selector is not zero. Selectors loops
> (looping b/w zero and regulator::desc::n_voltages) might throw errors
> because invalid selectors are used (lower than
> regulator::desc::linear_min_sel). For this situations validate selectors
> against regulator::desc::linear_min_sel.


After this commit was merged, I noticed a regression in the DFLL (CPU
clock source) on Tegra124. The DFLL driver
(drivers/clk/tegra/clk-dfll.c) calls regulator_list_voltage() in a loop
to determine the selector for a given voltage (see function
find_vdd_map_entry_exact()).

Currently, the DFLL driver queries the number of voltages provided by
the regulator by calling regulator_count_voltages() and then starting
from 0, iterates through the number of voltages to find the selector
value for the voltage it is looking for by calling
regulator_list_voltage(). It assumes that any negative value returned by
calling regulator_list_voltage() is an error and this will cause the
loop up to terminate.

In this case the regulator in question is the as3722 and the
linear_min_sel for this regulator is 1 and so when the DFLL driver calls
regulator_list_voltage() with a selector value of 0 it now returns a
negative error code, as expected by this change, and this terminates the
loop up in the DFLL driver. So I can clearly see why this is happening
and I could fix up the DFLL driver to avoid this.

Before doing so, I wanted to ask if that is the correct fix here,
because it seems a bit odd that regulator_count_voltages() returns N
voltages, but if the min selector value is greater than 0, then actually
there are less than N. However, changing the number of voltages
supported by the regulator to be N - linear_min_sel does not make sense
either because then we need to know the linear_min_sel in order to
determine the first valid voltage.

In case of the as3722, the value 0 means that the regulator is powered
down. So it is a valid setting and equates to 0 volts at the output AFAICT.

Please let me know your thoughts are the correct way to fix this up.

Thanks
Jon
Claudiu Beznea Nov. 24, 2020, 11:14 a.m. UTC | #2
Hi Jon,

On 24.11.2020 11:36, Jon Hunter wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
> On 13/11/2020 15:21, Claudiu Beznea wrote:
>> There are regulators who's min selector is not zero. Selectors loops
>> (looping b/w zero and regulator::desc::n_voltages) might throw errors
>> because invalid selectors are used (lower than
>> regulator::desc::linear_min_sel). For this situations validate selectors
>> against regulator::desc::linear_min_sel.
> 
> 
> After this commit was merged, I noticed a regression in the DFLL (CPU
> clock source) on Tegra124. The DFLL driver
> (drivers/clk/tegra/clk-dfll.c) calls regulator_list_voltage() in a loop
> to determine the selector for a given voltage (see function
> find_vdd_map_entry_exact()).
> 
> Currently, the DFLL driver queries the number of voltages provided by
> the regulator by calling regulator_count_voltages() and then starting
> from 0, iterates through the number of voltages to find the selector
> value for the voltage it is looking for by calling
> regulator_list_voltage(). It assumes that any negative value returned by
> calling regulator_list_voltage() is an error and this will cause the
> loop up to terminate.
> 
> In this case the regulator in question is the as3722 and the
> linear_min_sel for this regulator is 1 and so when the DFLL driver calls
> regulator_list_voltage() with a selector value of 0 it now returns a
> negative error code, as expected by this change, and this terminates the
> loop up in the DFLL driver. So I can clearly see why this is happening
> and I could fix up the DFLL driver to avoid this.
> 
> Before doing so, I wanted to ask if that is the correct fix here,
> because it seems a bit odd that regulator_count_voltages() returns N
> voltages, but if the min selector value is greater than 0, then actually
> there are less than N. However, changing the number of voltages
> supported by the regulator to be N - linear_min_sel does not make sense
> either because then we need to know the linear_min_sel in order to
> determine the first valid voltage.
> 
> In case of the as3722, the value 0 means that the regulator is powered
> down. So it is a valid setting and equates to 0 volts at the output AFAICT.
> 
> Please let me know your thoughts are the correct way to fix this up.

I would say that a solution would be to have a new helper to retrieve the
linear_min_sel (e.g. regulator_min_sel()) and use this for all the
consumers of regulator_list_voltage() and the other APIs that patch
"regulator: core: validate selector against linear_min_sel" has changed
(regulator_list_voltage_table(), regulator_set_voltage_time()). With this
change the loop in find_vdd_map_entry_exact() should be b/w
regulator_min_sel() and regulator_count_voltages().

Maybe Mark has a better solution for this.

Thank you,
Claudiu

> 
> Thanks
> Jon
> 
> --
> nvpublic
>
Jon Hunter Nov. 24, 2020, 1:41 p.m. UTC | #3
On 24/11/2020 11:14, Claudiu.Beznea@microchip.com wrote:

...

> I would say that a solution would be to have a new helper to retrieve the
> linear_min_sel (e.g. regulator_min_sel()) and use this for all the
> consumers of regulator_list_voltage() and the other APIs that patch
> "regulator: core: validate selector against linear_min_sel" has changed
> (regulator_list_voltage_table(), regulator_set_voltage_time()). With this
> change the loop in find_vdd_map_entry_exact() should be b/w
> regulator_min_sel() and regulator_count_voltages().
> 
> Maybe Mark has a better solution for this.


By the way, I don't think that Tegra is alone here. I see some other
drivers doing some similar things [0][1][2] and so I am wondering if
this is going to be a problem for a few drivers.

Jon

[0]
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/mmc/core/regulator.c#n61
[1]
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/cpufreq/s3c2416-cpufreq.c#n263
[2]
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/leds/leds-regulator.c#n29
Mark Brown Nov. 24, 2020, 2:11 p.m. UTC | #4
On Tue, Nov 24, 2020 at 11:14:54AM +0000, Claudiu.Beznea@microchip.com wrote:
> On 24.11.2020 11:36, Jon Hunter wrote:

> > Before doing so, I wanted to ask if that is the correct fix here,
> > because it seems a bit odd that regulator_count_voltages() returns N
> > voltages, but if the min selector value is greater than 0, then actually
> > there are less than N. However, changing the number of voltages
> > supported by the regulator to be N - linear_min_sel does not make sense
> > either because then we need to know the linear_min_sel in order to
> > determine the first valid voltage.

> I would say that a solution would be to have a new helper to retrieve the
> linear_min_sel (e.g. regulator_min_sel()) and use this for all the
> consumers of regulator_list_voltage() and the other APIs that patch
> "regulator: core: validate selector against linear_min_sel" has changed
> (regulator_list_voltage_table(), regulator_set_voltage_time()). With this
> change the loop in find_vdd_map_entry_exact() should be b/w
> regulator_min_sel() and regulator_count_voltages().

We need an incremental fix to return 0 rather than an error for things
below the minimum selector, it's not invalid for there to be holes in
the range of selectors and this is just an example of that.  Consumers
need to be able to cope with skipping over values that can't be mapped.
Claudiu Beznea Nov. 25, 2020, 10:46 a.m. UTC | #5
On 24.11.2020 15:41, Jon Hunter wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
> On 24/11/2020 11:14, Claudiu.Beznea@microchip.com wrote:
> 
> ...
> 
>> I would say that a solution would be to have a new helper to retrieve the
>> linear_min_sel (e.g. regulator_min_sel()) and use this for all the
>> consumers of regulator_list_voltage() and the other APIs that patch
>> "regulator: core: validate selector against linear_min_sel" has changed
>> (regulator_list_voltage_table(), regulator_set_voltage_time()). With this
>> change the loop in find_vdd_map_entry_exact() should be b/w
>> regulator_min_sel() and regulator_count_voltages().
>>
>> Maybe Mark has a better solution for this.
> 
> 
> By the way, I don't think that Tegra is alone here. I see some other
> drivers doing some similar things [0][1][2] and so I am wondering if
> this is going to be a problem for a few drivers.
> 

As far as I can tell most of the regulator_list_voltage() consumers are
checking the return value against a [min_uV, max_uV] range or if the return
value is a negative error code or zero. The consumers are looking a
selector that respect the above rule for the entire [0,
regulator_count_voltages()] range (I have to double check for the rest of
functions modified by my patch). In case of clk-dfll.c the
find_vdd_map_entry_exact() returns if it finds the 1st invalid selector:

	n_voltages = regulator_count_voltages(td->vdd_reg);
	for (i = 0; i < n_voltages; i++) {
		reg_uV = regulator_list_voltage(td->vdd_reg, i);
		if (reg_uV < 0)
			break;

		reg_volt_id = reg_uV / td->soc->alignment.step_uv;

		if (align_step == reg_volt_id)
			return i;
	}

Maybe it would be better if the loop continues in case reg_uV is negative
or zero? (the zero case is good for this function as it will make the
(align_step == reg_volt_id) to be false). But as Mark said in the previous
email, there could be regulators with gaps in between min_sel and n_voltages.

With the previous code it seems it worked because the
regulator_list_voltage() calls the as3722's list_voltage which is
regulator_list_voltage_linear() which checks the selector against
min_selector and returns zero in case the selector is lower than the
min_selector. Please correct me if I'm wrong.

Anyway, I will prepare a fix for my previous patch to return zero in case
the regulator_count_voltages() receives an invalid selector. That should
also fix the case with this driver.

Thank you,
Claudiu

> Jon
> 
> [0]
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/mmc/core/regulator.c#n61
> [1]
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/cpufreq/s3c2416-cpufreq.c#n263
> [2]
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/leds/leds-regulator.c#n29
> 
> --
> nvpublic
>
diff mbox series

Patch

diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index 2e1ea18221ef..1d0d35f14f37 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -2958,7 +2958,8 @@  static int _regulator_list_voltage(struct regulator_dev *rdev,
 		return rdev->desc->fixed_uV;
 
 	if (ops->list_voltage) {
-		if (selector >= rdev->desc->n_voltages)
+		if (selector >= rdev->desc->n_voltages ||
+		    selector < rdev->desc->linear_min_sel)
 			return -EINVAL;
 		if (lock)
 			regulator_lock(rdev);
@@ -3109,7 +3110,8 @@  int regulator_list_hardware_vsel(struct regulator *regulator,
 	struct regulator_dev *rdev = regulator->rdev;
 	const struct regulator_ops *ops = rdev->desc->ops;
 
-	if (selector >= rdev->desc->n_voltages)
+	if (selector >= rdev->desc->n_voltages ||
+	    selector < rdev->desc->linear_min_sel)
 		return -EINVAL;
 	if (ops->set_voltage_sel != regulator_set_voltage_sel_regmap)
 		return -EOPNOTSUPP;
@@ -4032,6 +4034,9 @@  int regulator_set_voltage_time(struct regulator *regulator,
 
 	for (i = 0; i < rdev->desc->n_voltages; i++) {
 		/* We only look for exact voltage matches here */
+		if (i < rdev->desc->linear_min_sel)
+			continue;
+
 		voltage = regulator_list_voltage(regulator, i);
 		if (voltage < 0)
 			return -EINVAL;
diff --git a/drivers/regulator/helpers.c b/drivers/regulator/helpers.c
index e4bb09bbd3fa..974f1a63993d 100644
--- a/drivers/regulator/helpers.c
+++ b/drivers/regulator/helpers.c
@@ -647,7 +647,8 @@  int regulator_list_voltage_table(struct regulator_dev *rdev,
 		return -EINVAL;
 	}
 
-	if (selector >= rdev->desc->n_voltages)
+	if (selector >= rdev->desc->n_voltages ||
+	    selector < rdev->desc->linear_min_sel)
 		return -EINVAL;
 
 	return rdev->desc->volt_table[selector];