cpufreq: interactive: allow arbitrary speed / target load mappings
authorTodd Poynor <toddpoynor@google.com>
Wed, 14 Nov 2012 19:41:21 +0000 (11:41 -0800)
committerArve Hjønnevåg <arve@android.com>
Mon, 1 Jul 2013 21:16:15 +0000 (14:16 -0700)
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>
Documentation/cpu-freq/governors.txt
drivers/cpufreq/cpufreq_interactive.c

index a461627e6aea2a3616e26b0e0ad5b251d9af9d49..9ddbef7ea3331b5759bf041831ecf601bc9303d0 100644 (file)
@@ -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
index 17c42cc12dc85315562e3435270ee43fcc7d0f36..6ea77a0d6b805715de2d6b8e6e9b557bcf12294c 100644 (file)
@@ -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,