cpufreq: interactive: allow arbitrary speed / target load mappings
Accept a string of target loads and speeds at which to apply the target loads, per the documentation update in this patch. For example, "85 1000000:90 1700000:99" targets CPU load 85% below speed 1GHz, 90% at or above 1GHz, until 1.7GHz and above, at which load 99% is targeted. Attempt to avoid oscillations by evaluating the current speed weighted by current load against each new choice of speed, choosing a higher speed if the current load requires a higher speed. Change-Id: Ie3300206047c84eca5a26b0b63ea512e5207550e Signed-off-by: Todd Poynor <toddpoynor@google.com>
This commit is contained in:
@@ -244,6 +244,23 @@ short-term load since idle exit to determine the cpu speed to ramp to.
|
||||
|
||||
The tuneable values for this governor are:
|
||||
|
||||
target_loads: CPU load values used to adjust speed to influence the
|
||||
current CPU load toward that value. In general, the lower the target
|
||||
load, the more often the governor will raise CPU speeds to bring load
|
||||
below the target. The format is a single target load, optionally
|
||||
followed by pairs of CPU speeds and CPU loads to target at or above
|
||||
those speeds. Colons can be used between the speeds and associated
|
||||
target loads for readability. For example:
|
||||
|
||||
85 1000000:90 1700000:99
|
||||
|
||||
targets CPU load 85% below speed 1GHz, 90% at or above 1GHz, until
|
||||
1.7GHz and above, at which load 99% is targeted. If speeds are
|
||||
specified these must appear in ascending order. Higher target load
|
||||
values are typically specified for higher speeds, that is, target load
|
||||
values also usually appear in an ascending order. The default is
|
||||
target load 90% for all speeds.
|
||||
|
||||
min_sample_time: The minimum amount of time to spend at the current
|
||||
frequency before ramping down. This is to ensure that the governor has
|
||||
seen enough historic cpu load data to determine the appropriate
|
||||
|
||||
@@ -70,7 +70,10 @@ static unsigned long go_hispeed_load;
|
||||
|
||||
/* Target load. Lower values result in higher CPU speeds. */
|
||||
#define DEFAULT_TARGET_LOAD 90
|
||||
static unsigned long target_load = DEFAULT_TARGET_LOAD;
|
||||
static unsigned int default_target_loads[] = {DEFAULT_TARGET_LOAD};
|
||||
static spinlock_t target_loads_lock;
|
||||
static unsigned int *target_loads = default_target_loads;
|
||||
static int ntarget_loads = ARRAY_SIZE(default_target_loads);
|
||||
|
||||
/*
|
||||
* The minimum amount of time to spend at a frequency before we can ramp down.
|
||||
@@ -125,6 +128,110 @@ static void cpufreq_interactive_timer_resched(
|
||||
&pcpu->time_in_idle_timestamp);
|
||||
}
|
||||
|
||||
static unsigned int freq_to_targetload(unsigned int freq)
|
||||
{
|
||||
int i;
|
||||
unsigned int ret;
|
||||
|
||||
spin_lock(&target_loads_lock);
|
||||
|
||||
for (i = 0; i < ntarget_loads - 1 && freq >= target_loads[i+1]; i += 2)
|
||||
;
|
||||
|
||||
ret = target_loads[i];
|
||||
spin_unlock(&target_loads_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* If increasing frequencies never map to a lower target load then
|
||||
* choose_freq() will find the minimum frequency that does not exceed its
|
||||
* target load given the current load.
|
||||
*/
|
||||
|
||||
static unsigned int choose_freq(
|
||||
struct cpufreq_interactive_cpuinfo *pcpu, unsigned int curload)
|
||||
{
|
||||
unsigned int freq = pcpu->policy->cur;
|
||||
unsigned int loadadjfreq = freq * curload;
|
||||
unsigned int prevfreq, freqmin, freqmax;
|
||||
unsigned int tl;
|
||||
int index;
|
||||
|
||||
freqmin = 0;
|
||||
freqmax = UINT_MAX;
|
||||
|
||||
do {
|
||||
prevfreq = freq;
|
||||
tl = freq_to_targetload(freq);
|
||||
|
||||
/*
|
||||
* Find the lowest frequency where the computed load is less
|
||||
* than or equal to the target load.
|
||||
*/
|
||||
|
||||
cpufreq_frequency_table_target(
|
||||
pcpu->policy, pcpu->freq_table, loadadjfreq / tl,
|
||||
CPUFREQ_RELATION_L, &index);
|
||||
freq = pcpu->freq_table[index].frequency;
|
||||
|
||||
if (freq > prevfreq) {
|
||||
/* The previous frequency is too low. */
|
||||
freqmin = prevfreq;
|
||||
|
||||
if (freq >= freqmax) {
|
||||
/*
|
||||
* Find the highest frequency that is less
|
||||
* than freqmax.
|
||||
*/
|
||||
cpufreq_frequency_table_target(
|
||||
pcpu->policy, pcpu->freq_table,
|
||||
freqmax - 1, CPUFREQ_RELATION_H,
|
||||
&index);
|
||||
freq = pcpu->freq_table[index].frequency;
|
||||
|
||||
if (freq == freqmin) {
|
||||
/*
|
||||
* The first frequency below freqmax
|
||||
* has already been found to be too
|
||||
* low. freqmax is the lowest speed
|
||||
* we found that is fast enough.
|
||||
*/
|
||||
freq = freqmax;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (freq < prevfreq) {
|
||||
/* The previous frequency is high enough. */
|
||||
freqmax = prevfreq;
|
||||
|
||||
if (freq <= freqmin) {
|
||||
/*
|
||||
* Find the lowest frequency that is higher
|
||||
* than freqmin.
|
||||
*/
|
||||
cpufreq_frequency_table_target(
|
||||
pcpu->policy, pcpu->freq_table,
|
||||
freqmin + 1, CPUFREQ_RELATION_L,
|
||||
&index);
|
||||
freq = pcpu->freq_table[index].frequency;
|
||||
|
||||
/*
|
||||
* If freqmax is the first frequency above
|
||||
* freqmin then we have already found that
|
||||
* this speed is fast enough.
|
||||
*/
|
||||
if (freq == freqmax)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If same frequency chosen as previous then done. */
|
||||
} while (freq != prevfreq);
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
static void cpufreq_interactive_timer(unsigned long data)
|
||||
{
|
||||
u64 now;
|
||||
@@ -180,7 +287,7 @@ static void cpufreq_interactive_timer(unsigned long data)
|
||||
pcpu->target_freq < hispeed_freq)
|
||||
new_freq = hispeed_freq;
|
||||
else
|
||||
new_freq = pcpu->policy->cur * cpu_load / target_load;
|
||||
new_freq = choose_freq(pcpu, cpu_load);
|
||||
|
||||
if (pcpu->target_freq >= hispeed_freq &&
|
||||
new_freq > pcpu->target_freq &&
|
||||
@@ -414,29 +521,79 @@ static void cpufreq_interactive_boost(void)
|
||||
wake_up_process(speedchange_task);
|
||||
}
|
||||
|
||||
static ssize_t show_target_load(
|
||||
static ssize_t show_target_loads(
|
||||
struct kobject *kobj, struct attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%lu\n", target_load);
|
||||
int i;
|
||||
ssize_t ret = 0;
|
||||
|
||||
spin_lock(&target_loads_lock);
|
||||
|
||||
for (i = 0; i < ntarget_loads; i++)
|
||||
ret += sprintf(buf + ret, "%u%s", target_loads[i],
|
||||
i & 0x1 ? ":" : " ");
|
||||
|
||||
ret += sprintf(buf + ret, "\n");
|
||||
spin_unlock(&target_loads_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t store_target_load(
|
||||
static ssize_t store_target_loads(
|
||||
struct kobject *kobj, struct attribute *attr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
int ret;
|
||||
unsigned long val;
|
||||
const char *cp;
|
||||
unsigned int *new_target_loads = NULL;
|
||||
int ntokens = 1;
|
||||
int i;
|
||||
|
||||
ret = strict_strtoul(buf, 0, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
target_load = val;
|
||||
cp = buf;
|
||||
while ((cp = strpbrk(cp + 1, " :")))
|
||||
ntokens++;
|
||||
|
||||
if (!(ntokens & 0x1))
|
||||
goto err_inval;
|
||||
|
||||
new_target_loads = kmalloc(ntokens * sizeof(unsigned int), GFP_KERNEL);
|
||||
if (!new_target_loads) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
cp = buf;
|
||||
i = 0;
|
||||
while (i < ntokens) {
|
||||
if (sscanf(cp, "%u", &new_target_loads[i++]) != 1)
|
||||
goto err_inval;
|
||||
|
||||
cp = strpbrk(cp, " :");
|
||||
if (!cp)
|
||||
break;
|
||||
cp++;
|
||||
}
|
||||
|
||||
if (i != ntokens)
|
||||
goto err_inval;
|
||||
|
||||
spin_lock(&target_loads_lock);
|
||||
if (target_loads != default_target_loads)
|
||||
kfree(target_loads);
|
||||
target_loads = new_target_loads;
|
||||
ntarget_loads = ntokens;
|
||||
spin_unlock(&target_loads_lock);
|
||||
return count;
|
||||
|
||||
err_inval:
|
||||
ret = -EINVAL;
|
||||
err:
|
||||
kfree(new_target_loads);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct global_attr target_load_attr =
|
||||
__ATTR(target_load, S_IRUGO | S_IWUSR,
|
||||
show_target_load, store_target_load);
|
||||
static struct global_attr target_loads_attr =
|
||||
__ATTR(target_loads, S_IRUGO | S_IWUSR,
|
||||
show_target_loads, store_target_loads);
|
||||
|
||||
static ssize_t show_hispeed_freq(struct kobject *kobj,
|
||||
struct attribute *attr, char *buf)
|
||||
@@ -599,7 +756,7 @@ static struct global_attr boostpulse =
|
||||
__ATTR(boostpulse, 0200, NULL, store_boostpulse);
|
||||
|
||||
static struct attribute *interactive_attributes[] = {
|
||||
&target_load_attr.attr,
|
||||
&target_loads_attr.attr,
|
||||
&hispeed_freq_attr.attr,
|
||||
&go_hispeed_load_attr.attr,
|
||||
&above_hispeed_delay.attr,
|
||||
@@ -739,6 +896,7 @@ static int __init cpufreq_interactive_init(void)
|
||||
pcpu->cpu_timer.data = i;
|
||||
}
|
||||
|
||||
spin_lock_init(&target_loads_lock);
|
||||
spin_lock_init(&speedchange_cpumask_lock);
|
||||
speedchange_task =
|
||||
kthread_create(cpufreq_interactive_speedchange_task, NULL,
|
||||
|
||||
Reference in New Issue
Block a user