diff mbox series

[v9,6/8] PM / devfreq: Introduce get_freq_range helper

Message ID c453bb60a74b41a5192e270f286dfc81c1088449.1570044052.git.leonard.crestez@nxp.com (mailing list archive)
State Superseded
Headers show
Series PM / devfreq: Add dev_pm_qos support | expand

Commit Message

Leonard Crestez Oct. 2, 2019, 7:25 p.m. UTC
Moving handling of min/max freq to a single function and call it from
update_devfreq and for printing min/max freq values in sysfs.

This changes the behavior of out-of-range min_freq/max_freq: clamping
is now done at evaluation time. This means that if an out-of-range
constraint is imposed by sysfs and it later becomes valid then it will
be enforced.

Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
---
 drivers/devfreq/devfreq.c | 110 +++++++++++++++++++++-----------------
 1 file changed, 62 insertions(+), 48 deletions(-)

Comments

Matthias Kaehlcke Oct. 3, 2019, 6:19 p.m. UTC | #1
On Wed, Oct 02, 2019 at 10:25:09PM +0300, Leonard Crestez wrote:
> Moving handling of min/max freq to a single function and call it from
> update_devfreq and for printing min/max freq values in sysfs.
> 
> This changes the behavior of out-of-range min_freq/max_freq: clamping
> is now done at evaluation time. This means that if an out-of-range
> constraint is imposed by sysfs and it later becomes valid then it will
> be enforced.
> 
> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
> ---
>  drivers/devfreq/devfreq.c | 110 +++++++++++++++++++++-----------------
>  1 file changed, 62 insertions(+), 48 deletions(-)
> 
> diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
> index 87eff789ce24..2d63692903ff 100644
> --- a/drivers/devfreq/devfreq.c
> +++ b/drivers/devfreq/devfreq.c
>
> ...
>
>  static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
>  			     char *buf)
>  {
>  	struct devfreq *df = to_devfreq(dev);
> +	unsigned long min_freq, max_freq;
>  
> -	return sprintf(buf, "%lu\n", max(df->scaling_min_freq, df->min_freq));
> +	mutex_lock(&df->lock);
> +	get_freq_range(df, &min_freq, &max_freq);

With this min/max_freq shown aren't necessarily those set through sysfs,
but the aggregated PM QoS values (plus OPP constraints).

I did some testing with a WIP patch that converts devfreq_cooling.c to
PM QoS. When reading sysfs min/max values to double check the limits
set earlier I found it utterly confusing to see the sysfs min/max values
fluctuating due to thermal throttling, and not being able to see the
configured values.

Looks like cpufreq does the same, but I'm not convinced this is a good
idea. I think we want to display the values set by userspace, as done
before managing the limits through PM QoS. Viresh, was this change of
userspace visible behavior done intentionally for cpufreq?
Leonard Crestez Oct. 3, 2019, 7:16 p.m. UTC | #2
On 03.10.2019 21:19, Matthias Kaehlcke wrote:
> On Wed, Oct 02, 2019 at 10:25:09PM +0300, Leonard Crestez wrote:
>> Moving handling of min/max freq to a single function and call it from
>> update_devfreq and for printing min/max freq values in sysfs.
>>
>> This changes the behavior of out-of-range min_freq/max_freq: clamping
>> is now done at evaluation time. This means that if an out-of-range
>> constraint is imposed by sysfs and it later becomes valid then it will
>> be enforced.
>>
>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>> Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
>> ---
>>   drivers/devfreq/devfreq.c | 110 +++++++++++++++++++++-----------------
>>   1 file changed, 62 insertions(+), 48 deletions(-)
>>
>> diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
>> index 87eff789ce24..2d63692903ff 100644
>> --- a/drivers/devfreq/devfreq.c
>> +++ b/drivers/devfreq/devfreq.c
>>
>> ...
>>
>>   static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
>>   			     char *buf)
>>   {
>>   	struct devfreq *df = to_devfreq(dev);
>> +	unsigned long min_freq, max_freq;
>>   
>> -	return sprintf(buf, "%lu\n", max(df->scaling_min_freq, df->min_freq));
>> +	mutex_lock(&df->lock);
>> +	get_freq_range(df, &min_freq, &max_freq);
> 
> With this min/max_freq shown aren't necessarily those set through sysfs,
> but the aggregated PM QoS values (plus OPP constraints).
> 
> I did some testing with a WIP patch that converts devfreq_cooling.c to
> PM QoS. When reading sysfs min/max values to double check the limits
> set earlier I found it utterly confusing to see the sysfs min/max values
> fluctuating due to thermal throttling, and not being able to see the
> configured values.

Isn't current devfreq_cooling based on OPP disabling which modifies 
scaling_max_freq? This is not a behavior change: reading back always 
showed the "effective maximum" rather than the value explicitly written 
to max_freq.

This behavior is indeed confusing but can be fixed by adding two new 
files: user_min/max_freq and user_max_freq. These would act like current 
min/max_freq on write but on read would only show the value explicitly 
configured by the user.

> Looks like cpufreq does the same, but I'm not convinced this is a good
> idea. I think we want to display the values set by userspace, as done
> before managing the limits through PM QoS. Viresh, was this change of
> userspace visible behavior done intentionally for cpufreq?
Matthias Kaehlcke Oct. 3, 2019, 7:36 p.m. UTC | #3
On Thu, Oct 03, 2019 at 07:16:03PM +0000, Leonard Crestez wrote:
> On 03.10.2019 21:19, Matthias Kaehlcke wrote:
> > On Wed, Oct 02, 2019 at 10:25:09PM +0300, Leonard Crestez wrote:
> >> Moving handling of min/max freq to a single function and call it from
> >> update_devfreq and for printing min/max freq values in sysfs.
> >>
> >> This changes the behavior of out-of-range min_freq/max_freq: clamping
> >> is now done at evaluation time. This means that if an out-of-range
> >> constraint is imposed by sysfs and it later becomes valid then it will
> >> be enforced.
> >>
> >> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> >> Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
> >> ---
> >>   drivers/devfreq/devfreq.c | 110 +++++++++++++++++++++-----------------
> >>   1 file changed, 62 insertions(+), 48 deletions(-)
> >>
> >> diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
> >> index 87eff789ce24..2d63692903ff 100644
> >> --- a/drivers/devfreq/devfreq.c
> >> +++ b/drivers/devfreq/devfreq.c
> >>
> >> ...
> >>
> >>   static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
> >>   			     char *buf)
> >>   {
> >>   	struct devfreq *df = to_devfreq(dev);
> >> +	unsigned long min_freq, max_freq;
> >>   
> >> -	return sprintf(buf, "%lu\n", max(df->scaling_min_freq, df->min_freq));
> >> +	mutex_lock(&df->lock);
> >> +	get_freq_range(df, &min_freq, &max_freq);
> > 
> > With this min/max_freq shown aren't necessarily those set through sysfs,
> > but the aggregated PM QoS values (plus OPP constraints).
> > 
> > I did some testing with a WIP patch that converts devfreq_cooling.c to
> > PM QoS. When reading sysfs min/max values to double check the limits
> > set earlier I found it utterly confusing to see the sysfs min/max values
> > fluctuating due to thermal throttling, and not being able to see the
> > configured values.
> 
> Isn't current devfreq_cooling based on OPP disabling which modifies 
> scaling_max_freq? This is not a behavior change: reading back always 
> showed the "effective maximum" rather than the value explicitly written 
> to max_freq.

I stand corrected, for devfreq indeed this isn't a behavioral change, and
looking at the diff would have told me. I just expected it to do the
reasonable thing and what cpufreq did in the past.

> This behavior is indeed confusing but can be fixed by adding two new 
> files: user_min/max_freq and user_max_freq. These would act like current 
> min/max_freq on write but on read would only show the value explicitly 
> configured by the user.

It seems the reasonable thing to do, though it's not great to alter
userspace visible behavior :( I doubt though that userspace would really
depend on it, since any min/max value read might change inmediately
afterwards. If there's really value in exposing the aggregate limits it
would probably make sense to do this through separate attributes. Let's
see what devfreq maintainers think.

In any case the patch should be fine as is, since it doesn't introduce the
(IMO) odd behavior.
Matthias Kaehlcke Oct. 11, 2019, 6:29 p.m. UTC | #4
On Thu, Oct 03, 2019 at 11:19:38AM -0700, Matthias Kaehlcke wrote:
> On Wed, Oct 02, 2019 at 10:25:09PM +0300, Leonard Crestez wrote:
> > Moving handling of min/max freq to a single function and call it from
> > update_devfreq and for printing min/max freq values in sysfs.
> > 
> > This changes the behavior of out-of-range min_freq/max_freq: clamping
> > is now done at evaluation time. This means that if an out-of-range
> > constraint is imposed by sysfs and it later becomes valid then it will
> > be enforced.
> > 
> > Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> > Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
> > ---
> >  drivers/devfreq/devfreq.c | 110 +++++++++++++++++++++-----------------
> >  1 file changed, 62 insertions(+), 48 deletions(-)
> > 
> > diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
> > index 87eff789ce24..2d63692903ff 100644
> > --- a/drivers/devfreq/devfreq.c
> > +++ b/drivers/devfreq/devfreq.c
> >
> > ...
> >
> >  static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
> >  			     char *buf)
> >  {
> >  	struct devfreq *df = to_devfreq(dev);
> > +	unsigned long min_freq, max_freq;
> >  
> > -	return sprintf(buf, "%lu\n", max(df->scaling_min_freq, df->min_freq));
> > +	mutex_lock(&df->lock);
> > +	get_freq_range(df, &min_freq, &max_freq);
> 
> With this min/max_freq shown aren't necessarily those set through sysfs,
> but the aggregated PM QoS values (plus OPP constraints).
> 
> I did some testing with a WIP patch that converts devfreq_cooling.c to
> PM QoS. When reading sysfs min/max values to double check the limits
> set earlier I found it utterly confusing to see the sysfs min/max values
> fluctuating due to thermal throttling, and not being able to see the
> configured values.
> 
> Looks like cpufreq does the same, but I'm not convinced this is a good
> idea. I think we want to display the values set by userspace, as done
> before managing the limits through PM QoS. Viresh, was this change of
> userspace visible behavior done intentionally for cpufreq?

ping

Viresh / devfreq maintainers:

Do we really want to expose through sysfs the potentially constantly
changing aggregate min/max values, instead of those set by userspace?
At least for cpufreq that's a divergence from previous behavior, and
from a userspace perspective it seems odd that you can't reliably read
back the limit set by userspace.
Chanwoo Choi Oct. 31, 2019, 2:42 a.m. UTC | #5
On 19. 10. 3. 오전 4:25, Leonard Crestez wrote:
> Moving handling of min/max freq to a single function and call it from
> update_devfreq and for printing min/max freq values in sysfs.
> 
> This changes the behavior of out-of-range min_freq/max_freq: clamping
> is now done at evaluation time. This means that if an out-of-range
> constraint is imposed by sysfs and it later becomes valid then it will
> be enforced.
> 
> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
> Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
> ---
>  drivers/devfreq/devfreq.c | 110 +++++++++++++++++++++-----------------
>  1 file changed, 62 insertions(+), 48 deletions(-)
> 
> diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
> index 87eff789ce24..2d63692903ff 100644
> --- a/drivers/devfreq/devfreq.c
> +++ b/drivers/devfreq/devfreq.c
> @@ -96,10 +96,53 @@ static unsigned long find_available_max_freq(struct devfreq *devfreq)
>  		dev_pm_opp_put(opp);
>  
>  	return max_freq;
>  }
>  
> +/**
> + * get_freq_range() - Get the current freq range
> + * @devfreq:	the devfreq instance
> + * @min_freq:	the min frequency
> + * @max_freq:	the max frequency
> + *
> + * This takes into consideration all constraints.
> + */
> +static void get_freq_range(struct devfreq *devfreq,
> +			   unsigned long *min_freq,
> +			   unsigned long *max_freq)
> +{
> +	unsigned long *freq_table = devfreq->profile->freq_table;
> +
> +	lockdep_assert_held(&devfreq->lock);
> +
> +	/*
> +	 * Initialize minimum/maximum frequency from freq table.
> +	 * The devfreq drivers can initialize this in either ascending or
> +	 * descending order and devfreq core supports both.
> +	 */
> +	if (freq_table[0] < freq_table[devfreq->profile->max_state - 1]) {
> +		*min_freq = freq_table[0];
> +		*max_freq = freq_table[devfreq->profile->max_state - 1];
> +	} else {
> +		*min_freq = freq_table[devfreq->profile->max_state - 1];
> +		*max_freq = freq_table[0];
> +	}
> +
> +	/* Apply constraints from sysfs */
> +	*min_freq = max(*min_freq, devfreq->min_freq);
> +	*max_freq = min(*max_freq, devfreq->max_freq);
> +
> +	/* Apply constraints from OPP interface */
> +	*min_freq = max(*min_freq, devfreq->scaling_min_freq);
> +	/* scaling_max_freq can be zero on error */
> +	if (devfreq->scaling_max_freq)

nitpick:
This if statement is necessary?

The patch3 in this series initializes the 'devfreq->scaling_max_freq'
is ULONG_MAX if failed to get the available frequency from find_available_max_freq.

> +		*max_freq = min(*max_freq, devfreq->scaling_max_freq);
> +
> +	if (*min_freq > *max_freq)
> +		*min_freq = *max_freq;
> +}
> +
>  /**
>   * devfreq_get_freq_level() - Lookup freq_table for the frequency
>   * @devfreq:	the devfreq instance
>   * @freq:	the target frequency
>   */
> @@ -348,20 +391,11 @@ int update_devfreq(struct devfreq *devfreq)
>  
>  	/* Reevaluate the proper frequency */
>  	err = devfreq->governor->get_target_freq(devfreq, &freq);
>  	if (err)
>  		return err;
> -
> -	/*
> -	 * Adjust the frequency with user freq, QoS and available freq.
> -	 *
> -	 * List from the highest priority
> -	 * max_freq
> -	 * min_freq
> -	 */
> -	max_freq = min(devfreq->scaling_max_freq, devfreq->max_freq);
> -	min_freq = max(devfreq->scaling_min_freq, devfreq->min_freq);
> +	get_freq_range(devfreq, &min_freq, &max_freq);
>  
>  	if (freq < min_freq) {
>  		freq = min_freq;
>  		flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use GLB */
>  	}
> @@ -1281,40 +1315,28 @@ static ssize_t min_freq_store(struct device *dev, struct device_attribute *attr,
>  	ret = sscanf(buf, "%lu", &value);
>  	if (ret != 1)
>  		return -EINVAL;
>  
>  	mutex_lock(&df->lock);
> -
> -	if (value) {
> -		if (value > df->max_freq) {
> -			ret = -EINVAL;
> -			goto unlock;
> -		}
> -	} else {
> -		unsigned long *freq_table = df->profile->freq_table;
> -
> -		/* Get minimum frequency according to sorting order */
> -		if (freq_table[0] < freq_table[df->profile->max_state - 1])
> -			value = freq_table[0];
> -		else
> -			value = freq_table[df->profile->max_state - 1];
> -	}
> -
>  	df->min_freq = value;
>  	update_devfreq(df);
> -	ret = count;
> -unlock:
>  	mutex_unlock(&df->lock);
> -	return ret;
> +
> +	return count;
>  }
>  
>  static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
>  			     char *buf)
>  {
>  	struct devfreq *df = to_devfreq(dev);
> +	unsigned long min_freq, max_freq;
>  
> -	return sprintf(buf, "%lu\n", max(df->scaling_min_freq, df->min_freq));
> +	mutex_lock(&df->lock);
> +	get_freq_range(df, &min_freq, &max_freq);
> +	mutex_unlock(&df->lock);
> +
> +	return sprintf(buf, "%lu\n", min_freq);
>  }
>  
>  static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
>  			      const char *buf, size_t count)
>  {
> @@ -1326,40 +1348,32 @@ static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
>  	if (ret != 1)
>  		return -EINVAL;
>  
>  	mutex_lock(&df->lock);
>  
> -	if (value) {
> -		if (value < df->min_freq) {
> -			ret = -EINVAL;
> -			goto unlock;
> -		}
> -	} else {
> -		unsigned long *freq_table = df->profile->freq_table;
> -
> -		/* Get maximum frequency according to sorting order */
> -		if (freq_table[0] < freq_table[df->profile->max_state - 1])
> -			value = freq_table[df->profile->max_state - 1];
> -		else
> -			value = freq_table[0];
> -	}
> +	if (!value)
> +		value = ULONG_MAX;
>  
>  	df->max_freq = value;
>  	update_devfreq(df);
> -	ret = count;
> -unlock:
>  	mutex_unlock(&df->lock);
> -	return ret;
> +
> +	return count;
>  }
>  static DEVICE_ATTR_RW(min_freq);
>  
>  static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr,
>  			     char *buf)
>  {
>  	struct devfreq *df = to_devfreq(dev);
> +	unsigned long min_freq, max_freq;
> +
> +	mutex_lock(&df->lock);
> +	get_freq_range(df, &min_freq, &max_freq);
> +	mutex_unlock(&df->lock);
>  
> -	return sprintf(buf, "%lu\n", min(df->scaling_max_freq, df->max_freq));
> +	return sprintf(buf, "%lu\n", max_freq);
>  }
>  static DEVICE_ATTR_RW(max_freq);
>  
>  static ssize_t available_frequencies_show(struct device *d,
>  					  struct device_attribute *attr,
> 

Reviewed-by: Chanwoo Choi <cw00.choi@samsung.com>
Chanwoo Choi Oct. 31, 2019, 2:44 a.m. UTC | #6
On 19. 10. 12. 오전 3:29, Matthias Kaehlcke wrote:
> On Thu, Oct 03, 2019 at 11:19:38AM -0700, Matthias Kaehlcke wrote:
>> On Wed, Oct 02, 2019 at 10:25:09PM +0300, Leonard Crestez wrote:
>>> Moving handling of min/max freq to a single function and call it from
>>> update_devfreq and for printing min/max freq values in sysfs.
>>>
>>> This changes the behavior of out-of-range min_freq/max_freq: clamping
>>> is now done at evaluation time. This means that if an out-of-range
>>> constraint is imposed by sysfs and it later becomes valid then it will
>>> be enforced.
>>>
>>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>>> Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
>>> ---
>>>  drivers/devfreq/devfreq.c | 110 +++++++++++++++++++++-----------------
>>>  1 file changed, 62 insertions(+), 48 deletions(-)
>>>
>>> diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
>>> index 87eff789ce24..2d63692903ff 100644
>>> --- a/drivers/devfreq/devfreq.c
>>> +++ b/drivers/devfreq/devfreq.c
>>>
>>> ...
>>>
>>>  static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
>>>  			     char *buf)
>>>  {
>>>  	struct devfreq *df = to_devfreq(dev);
>>> +	unsigned long min_freq, max_freq;
>>>  
>>> -	return sprintf(buf, "%lu\n", max(df->scaling_min_freq, df->min_freq));
>>> +	mutex_lock(&df->lock);
>>> +	get_freq_range(df, &min_freq, &max_freq);
>>
>> With this min/max_freq shown aren't necessarily those set through sysfs,
>> but the aggregated PM QoS values (plus OPP constraints).
>>
>> I did some testing with a WIP patch that converts devfreq_cooling.c to
>> PM QoS. When reading sysfs min/max values to double check the limits
>> set earlier I found it utterly confusing to see the sysfs min/max values
>> fluctuating due to thermal throttling, and not being able to see the
>> configured values.
>>
>> Looks like cpufreq does the same, but I'm not convinced this is a good
>> idea. I think we want to display the values set by userspace, as done
>> before managing the limits through PM QoS. Viresh, was this change of
>> userspace visible behavior done intentionally for cpufreq?
> 
> ping
> 
> Viresh / devfreq maintainers:
> 
> Do we really want to expose through sysfs the potentially constantly
> changing aggregate min/max values, instead of those set by userspace?

Until now, the devfreq core show the available min/max frequency
through sysfs interface. I think the available min/max frequency is proper
for sysfs.

> At least for cpufreq that's a divergence from previous behavior, and
> from a userspace perspective it seems odd that you can't reliably read
> back the limit set by userspace.
> 
>
Leonard Crestez Oct. 31, 2019, 1:12 p.m. UTC | #7
On 31.10.2019 04:37, Chanwoo Choi wrote:
> On 19. 10. 3. 오전 4:25, Leonard Crestez wrote:
>> Moving handling of min/max freq to a single function and call it from
>> update_devfreq and for printing min/max freq values in sysfs.
>>
>> This changes the behavior of out-of-range min_freq/max_freq: clamping
>> is now done at evaluation time. This means that if an out-of-range
>> constraint is imposed by sysfs and it later becomes valid then it will
>> be enforced.
>>
>> Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
>> Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
>> ---
>>   drivers/devfreq/devfreq.c | 110 +++++++++++++++++++++-----------------
>>   1 file changed, 62 insertions(+), 48 deletions(-)
>>
>> diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
>> index 87eff789ce24..2d63692903ff 100644
>> --- a/drivers/devfreq/devfreq.c
>> +++ b/drivers/devfreq/devfreq.c
>> @@ -96,10 +96,53 @@ static unsigned long find_available_max_freq(struct devfreq *devfreq)
>>   		dev_pm_opp_put(opp);
>>   
>>   	return max_freq;
>>   }
>>   
>> +/**
>> + * get_freq_range() - Get the current freq range
>> + * @devfreq:	the devfreq instance
>> + * @min_freq:	the min frequency
>> + * @max_freq:	the max frequency
>> + *
>> + * This takes into consideration all constraints.
>> + */
>> +static void get_freq_range(struct devfreq *devfreq,
>> +			   unsigned long *min_freq,
>> +			   unsigned long *max_freq)
>> +{
>> +	unsigned long *freq_table = devfreq->profile->freq_table;
>> +
>> +	lockdep_assert_held(&devfreq->lock);
>> +
>> +	/*
>> +	 * Initialize minimum/maximum frequency from freq table.
>> +	 * The devfreq drivers can initialize this in either ascending or
>> +	 * descending order and devfreq core supports both.
>> +	 */
>> +	if (freq_table[0] < freq_table[devfreq->profile->max_state - 1]) {
>> +		*min_freq = freq_table[0];
>> +		*max_freq = freq_table[devfreq->profile->max_state - 1];
>> +	} else {
>> +		*min_freq = freq_table[devfreq->profile->max_state - 1];
>> +		*max_freq = freq_table[0];
>> +	}
>> +
>> +	/* Apply constraints from sysfs */
>> +	*min_freq = max(*min_freq, devfreq->min_freq);
>> +	*max_freq = min(*max_freq, devfreq->max_freq);
>> +
>> +	/* Apply constraints from OPP interface */
>> +	*min_freq = max(*min_freq, devfreq->scaling_min_freq);
>> +	/* scaling_max_freq can be zero on error */
>> +	if (devfreq->scaling_max_freq)
> 
> nitpick:
> This if statement is necessary?
> 
> The patch3 in this series initializes the 'devfreq->scaling_max_freq'
> is ULONG_MAX if failed to get the available frequency from find_available_max_freq.

Ok, seem I forgot to drop the if statement.

> 
>> +		*max_freq = min(*max_freq, devfreq->scaling_max_freq);
>> +
>> +	if (*min_freq > *max_freq)
>> +		*min_freq = *max_freq;
>> +}
>> +
>>   /**
>>    * devfreq_get_freq_level() - Lookup freq_table for the frequency
>>    * @devfreq:	the devfreq instance
>>    * @freq:	the target frequency
>>    */
>> @@ -348,20 +391,11 @@ int update_devfreq(struct devfreq *devfreq)
>>   
>>   	/* Reevaluate the proper frequency */
>>   	err = devfreq->governor->get_target_freq(devfreq, &freq);
>>   	if (err)
>>   		return err;
>> -
>> -	/*
>> -	 * Adjust the frequency with user freq, QoS and available freq.
>> -	 *
>> -	 * List from the highest priority
>> -	 * max_freq
>> -	 * min_freq
>> -	 */
>> -	max_freq = min(devfreq->scaling_max_freq, devfreq->max_freq);
>> -	min_freq = max(devfreq->scaling_min_freq, devfreq->min_freq);
>> +	get_freq_range(devfreq, &min_freq, &max_freq);
>>   
>>   	if (freq < min_freq) {
>>   		freq = min_freq;
>>   		flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use GLB */
>>   	}
>> @@ -1281,40 +1315,28 @@ static ssize_t min_freq_store(struct device *dev, struct device_attribute *attr,
>>   	ret = sscanf(buf, "%lu", &value);
>>   	if (ret != 1)
>>   		return -EINVAL;
>>   
>>   	mutex_lock(&df->lock);
>> -
>> -	if (value) {
>> -		if (value > df->max_freq) {
>> -			ret = -EINVAL;
>> -			goto unlock;
>> -		}
>> -	} else {
>> -		unsigned long *freq_table = df->profile->freq_table;
>> -
>> -		/* Get minimum frequency according to sorting order */
>> -		if (freq_table[0] < freq_table[df->profile->max_state - 1])
>> -			value = freq_table[0];
>> -		else
>> -			value = freq_table[df->profile->max_state - 1];
>> -	}
>> -
>>   	df->min_freq = value;
>>   	update_devfreq(df);
>> -	ret = count;
>> -unlock:
>>   	mutex_unlock(&df->lock);
>> -	return ret;
>> +
>> +	return count;
>>   }
>>   
>>   static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
>>   			     char *buf)
>>   {
>>   	struct devfreq *df = to_devfreq(dev);
>> +	unsigned long min_freq, max_freq;
>>   
>> -	return sprintf(buf, "%lu\n", max(df->scaling_min_freq, df->min_freq));
>> +	mutex_lock(&df->lock);
>> +	get_freq_range(df, &min_freq, &max_freq);
>> +	mutex_unlock(&df->lock);
>> +
>> +	return sprintf(buf, "%lu\n", min_freq);
>>   }
>>   
>>   static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
>>   			      const char *buf, size_t count)
>>   {
>> @@ -1326,40 +1348,32 @@ static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
>>   	if (ret != 1)
>>   		return -EINVAL;
>>   
>>   	mutex_lock(&df->lock);
>>   
>> -	if (value) {
>> -		if (value < df->min_freq) {
>> -			ret = -EINVAL;
>> -			goto unlock;
>> -		}
>> -	} else {
>> -		unsigned long *freq_table = df->profile->freq_table;
>> -
>> -		/* Get maximum frequency according to sorting order */
>> -		if (freq_table[0] < freq_table[df->profile->max_state - 1])
>> -			value = freq_table[df->profile->max_state - 1];
>> -		else
>> -			value = freq_table[0];
>> -	}
>> +	if (!value)
>> +		value = ULONG_MAX;
>>   
>>   	df->max_freq = value;
>>   	update_devfreq(df);
>> -	ret = count;
>> -unlock:
>>   	mutex_unlock(&df->lock);
>> -	return ret;
>> +
>> +	return count;
>>   }
>>   static DEVICE_ATTR_RW(min_freq);
>>   
>>   static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr,
>>   			     char *buf)
>>   {
>>   	struct devfreq *df = to_devfreq(dev);
>> +	unsigned long min_freq, max_freq;
>> +
>> +	mutex_lock(&df->lock);
>> +	get_freq_range(df, &min_freq, &max_freq);
>> +	mutex_unlock(&df->lock);
>>   
>> -	return sprintf(buf, "%lu\n", min(df->scaling_max_freq, df->max_freq));
>> +	return sprintf(buf, "%lu\n", max_freq);
>>   }
>>   static DEVICE_ATTR_RW(max_freq);
>>   
>>   static ssize_t available_frequencies_show(struct device *d,
>>   					  struct device_attribute *attr,
>>
> 
> Reviewed-by: Chanwoo Choi <cw00.choi@samsung.com>
>
diff mbox series

Patch

diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
index 87eff789ce24..2d63692903ff 100644
--- a/drivers/devfreq/devfreq.c
+++ b/drivers/devfreq/devfreq.c
@@ -96,10 +96,53 @@  static unsigned long find_available_max_freq(struct devfreq *devfreq)
 		dev_pm_opp_put(opp);
 
 	return max_freq;
 }
 
+/**
+ * get_freq_range() - Get the current freq range
+ * @devfreq:	the devfreq instance
+ * @min_freq:	the min frequency
+ * @max_freq:	the max frequency
+ *
+ * This takes into consideration all constraints.
+ */
+static void get_freq_range(struct devfreq *devfreq,
+			   unsigned long *min_freq,
+			   unsigned long *max_freq)
+{
+	unsigned long *freq_table = devfreq->profile->freq_table;
+
+	lockdep_assert_held(&devfreq->lock);
+
+	/*
+	 * Initialize minimum/maximum frequency from freq table.
+	 * The devfreq drivers can initialize this in either ascending or
+	 * descending order and devfreq core supports both.
+	 */
+	if (freq_table[0] < freq_table[devfreq->profile->max_state - 1]) {
+		*min_freq = freq_table[0];
+		*max_freq = freq_table[devfreq->profile->max_state - 1];
+	} else {
+		*min_freq = freq_table[devfreq->profile->max_state - 1];
+		*max_freq = freq_table[0];
+	}
+
+	/* Apply constraints from sysfs */
+	*min_freq = max(*min_freq, devfreq->min_freq);
+	*max_freq = min(*max_freq, devfreq->max_freq);
+
+	/* Apply constraints from OPP interface */
+	*min_freq = max(*min_freq, devfreq->scaling_min_freq);
+	/* scaling_max_freq can be zero on error */
+	if (devfreq->scaling_max_freq)
+		*max_freq = min(*max_freq, devfreq->scaling_max_freq);
+
+	if (*min_freq > *max_freq)
+		*min_freq = *max_freq;
+}
+
 /**
  * devfreq_get_freq_level() - Lookup freq_table for the frequency
  * @devfreq:	the devfreq instance
  * @freq:	the target frequency
  */
@@ -348,20 +391,11 @@  int update_devfreq(struct devfreq *devfreq)
 
 	/* Reevaluate the proper frequency */
 	err = devfreq->governor->get_target_freq(devfreq, &freq);
 	if (err)
 		return err;
-
-	/*
-	 * Adjust the frequency with user freq, QoS and available freq.
-	 *
-	 * List from the highest priority
-	 * max_freq
-	 * min_freq
-	 */
-	max_freq = min(devfreq->scaling_max_freq, devfreq->max_freq);
-	min_freq = max(devfreq->scaling_min_freq, devfreq->min_freq);
+	get_freq_range(devfreq, &min_freq, &max_freq);
 
 	if (freq < min_freq) {
 		freq = min_freq;
 		flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use GLB */
 	}
@@ -1281,40 +1315,28 @@  static ssize_t min_freq_store(struct device *dev, struct device_attribute *attr,
 	ret = sscanf(buf, "%lu", &value);
 	if (ret != 1)
 		return -EINVAL;
 
 	mutex_lock(&df->lock);
-
-	if (value) {
-		if (value > df->max_freq) {
-			ret = -EINVAL;
-			goto unlock;
-		}
-	} else {
-		unsigned long *freq_table = df->profile->freq_table;
-
-		/* Get minimum frequency according to sorting order */
-		if (freq_table[0] < freq_table[df->profile->max_state - 1])
-			value = freq_table[0];
-		else
-			value = freq_table[df->profile->max_state - 1];
-	}
-
 	df->min_freq = value;
 	update_devfreq(df);
-	ret = count;
-unlock:
 	mutex_unlock(&df->lock);
-	return ret;
+
+	return count;
 }
 
 static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
 			     char *buf)
 {
 	struct devfreq *df = to_devfreq(dev);
+	unsigned long min_freq, max_freq;
 
-	return sprintf(buf, "%lu\n", max(df->scaling_min_freq, df->min_freq));
+	mutex_lock(&df->lock);
+	get_freq_range(df, &min_freq, &max_freq);
+	mutex_unlock(&df->lock);
+
+	return sprintf(buf, "%lu\n", min_freq);
 }
 
 static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
 			      const char *buf, size_t count)
 {
@@ -1326,40 +1348,32 @@  static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
 	if (ret != 1)
 		return -EINVAL;
 
 	mutex_lock(&df->lock);
 
-	if (value) {
-		if (value < df->min_freq) {
-			ret = -EINVAL;
-			goto unlock;
-		}
-	} else {
-		unsigned long *freq_table = df->profile->freq_table;
-
-		/* Get maximum frequency according to sorting order */
-		if (freq_table[0] < freq_table[df->profile->max_state - 1])
-			value = freq_table[df->profile->max_state - 1];
-		else
-			value = freq_table[0];
-	}
+	if (!value)
+		value = ULONG_MAX;
 
 	df->max_freq = value;
 	update_devfreq(df);
-	ret = count;
-unlock:
 	mutex_unlock(&df->lock);
-	return ret;
+
+	return count;
 }
 static DEVICE_ATTR_RW(min_freq);
 
 static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr,
 			     char *buf)
 {
 	struct devfreq *df = to_devfreq(dev);
+	unsigned long min_freq, max_freq;
+
+	mutex_lock(&df->lock);
+	get_freq_range(df, &min_freq, &max_freq);
+	mutex_unlock(&df->lock);
 
-	return sprintf(buf, "%lu\n", min(df->scaling_max_freq, df->max_freq));
+	return sprintf(buf, "%lu\n", max_freq);
 }
 static DEVICE_ATTR_RW(max_freq);
 
 static ssize_t available_frequencies_show(struct device *d,
 					  struct device_attribute *attr,