cpufreq: interactive: handle errors from cpufreq_frequency_table_target
[firefly-linux-kernel-4.4.55.git] / drivers / cpufreq / cpufreq_interactive.c
index c8358a3b5454837f7f515132a6a0c6db7e0e4076..1b5d9301e2d7812b41e4b85ab535a9faa2e3ad14 100644 (file)
 #include <linux/workqueue.h>
 #include <linux/kthread.h>
 #include <linux/slab.h>
+#include <linux/kernel_stat.h>
 #include <asm/cputime.h>
 
 #define CREATE_TRACE_POINTS
 #include <trace/events/cpufreq_interactive.h>
 
-static atomic_t active_count = ATOMIC_INIT(0);
+static int active_count;
 
 struct cpufreq_interactive_cpuinfo {
        struct timer_list cpu_timer;
@@ -61,6 +62,7 @@ static DEFINE_PER_CPU(struct cpufreq_interactive_cpuinfo, cpuinfo);
 static struct task_struct *speedchange_task;
 static cpumask_t speedchange_cpumask;
 static spinlock_t speedchange_cpumask_lock;
+static struct mutex gov_lock;
 
 /* Hi speed to bump to from lo speed when load burst (default max) */
 static unsigned int hispeed_freq;
@@ -93,7 +95,11 @@ static unsigned long timer_rate = DEFAULT_TIMER_RATE;
  * timer interval.
  */
 #define DEFAULT_ABOVE_HISPEED_DELAY DEFAULT_TIMER_RATE
-static unsigned long above_hispeed_delay_val = DEFAULT_ABOVE_HISPEED_DELAY;
+static unsigned int default_above_hispeed_delay[] = {
+       DEFAULT_ABOVE_HISPEED_DELAY };
+static spinlock_t above_hispeed_delay_lock;
+static unsigned int *above_hispeed_delay = default_above_hispeed_delay;
+static int nabove_hispeed_delay = ARRAY_SIZE(default_above_hispeed_delay);
 
 /* Non-zero means indefinite speed boost active */
 static int boost_val;
@@ -109,6 +115,8 @@ static u64 boostpulse_endtime;
 #define DEFAULT_TIMER_SLACK (4 * DEFAULT_TIMER_RATE)
 static int timer_slack_val = DEFAULT_TIMER_SLACK;
 
+static bool io_is_busy;
+
 static int cpufreq_governor_interactive(struct cpufreq_policy *policy,
                unsigned int event);
 
@@ -122,10 +130,47 @@ struct cpufreq_governor cpufreq_gov_interactive = {
        .owner = THIS_MODULE,
 };
 
+static inline cputime64_t get_cpu_idle_time_jiffy(unsigned int cpu,
+                                                 cputime64_t *wall)
+{
+       u64 idle_time;
+       u64 cur_wall_time;
+       u64 busy_time;
+
+       cur_wall_time = jiffies64_to_cputime64(get_jiffies_64());
+
+       busy_time  = kcpustat_cpu(cpu).cpustat[CPUTIME_USER];
+       busy_time += kcpustat_cpu(cpu).cpustat[CPUTIME_SYSTEM];
+       busy_time += kcpustat_cpu(cpu).cpustat[CPUTIME_IRQ];
+       busy_time += kcpustat_cpu(cpu).cpustat[CPUTIME_SOFTIRQ];
+       busy_time += kcpustat_cpu(cpu).cpustat[CPUTIME_STEAL];
+       busy_time += kcpustat_cpu(cpu).cpustat[CPUTIME_NICE];
+
+       idle_time = cur_wall_time - busy_time;
+       if (wall)
+               *wall = jiffies_to_usecs(cur_wall_time);
+
+       return jiffies_to_usecs(idle_time);
+}
+
+static inline cputime64_t get_cpu_idle_time(unsigned int cpu,
+                                           cputime64_t *wall)
+{
+       u64 idle_time = get_cpu_idle_time_us(cpu, wall);
+
+       if (idle_time == -1ULL)
+               idle_time = get_cpu_idle_time_jiffy(cpu, wall);
+       else if (!io_is_busy)
+               idle_time += get_cpu_iowait_time_us(cpu, wall);
+
+       return idle_time;
+}
+
 static void cpufreq_interactive_timer_resched(
        struct cpufreq_interactive_cpuinfo *pcpu)
 {
        unsigned long expires = jiffies + usecs_to_jiffies(timer_rate);
+       unsigned long flags;
 
        mod_timer_pinned(&pcpu->cpu_timer, expires);
        if (timer_slack_val >= 0 && pcpu->target_freq > pcpu->policy->min) {
@@ -133,27 +178,45 @@ static void cpufreq_interactive_timer_resched(
                mod_timer_pinned(&pcpu->cpu_slack_timer, expires);
        }
 
-       spin_lock(&pcpu->load_lock);
+       spin_lock_irqsave(&pcpu->load_lock, flags);
        pcpu->time_in_idle =
-               get_cpu_idle_time_us(smp_processor_id(),
+               get_cpu_idle_time(smp_processor_id(),
                                     &pcpu->time_in_idle_timestamp);
        pcpu->cputime_speedadj = 0;
        pcpu->cputime_speedadj_timestamp = pcpu->time_in_idle_timestamp;
-       spin_unlock(&pcpu->load_lock);
+       spin_unlock_irqrestore(&pcpu->load_lock, flags);
+}
+
+static unsigned int freq_to_above_hispeed_delay(unsigned int freq)
+{
+       int i;
+       unsigned int ret;
+       unsigned long flags;
+
+       spin_lock_irqsave(&above_hispeed_delay_lock, flags);
+
+       for (i = 0; i < nabove_hispeed_delay - 1 &&
+                       freq >= above_hispeed_delay[i+1]; i += 2)
+               ;
+
+       ret = above_hispeed_delay[i];
+       spin_unlock_irqrestore(&above_hispeed_delay_lock, flags);
+       return ret;
 }
 
 static unsigned int freq_to_targetload(unsigned int freq)
 {
        int i;
        unsigned int ret;
+       unsigned long flags;
 
-       spin_lock(&target_loads_lock);
+       spin_lock_irqsave(&target_loads_lock, flags);
 
        for (i = 0; i < ntarget_loads - 1 && freq >= target_loads[i+1]; i += 2)
                ;
 
        ret = target_loads[i];
-       spin_unlock(&target_loads_lock);
+       spin_unlock_irqrestore(&target_loads_lock, flags);
        return ret;
 }
 
@@ -183,9 +246,10 @@ static unsigned int choose_freq(
                 * than or equal to the target load.
                 */
 
-               cpufreq_frequency_table_target(
-                       pcpu->policy, pcpu->freq_table, loadadjfreq / tl,
-                       CPUFREQ_RELATION_L, &index);
+               if (cpufreq_frequency_table_target(
+                           pcpu->policy, pcpu->freq_table, loadadjfreq / tl,
+                           CPUFREQ_RELATION_L, &index))
+                       break;
                freq = pcpu->freq_table[index].frequency;
 
                if (freq > prevfreq) {
@@ -197,10 +261,11 @@ static unsigned int choose_freq(
                                 * Find the highest frequency that is less
                                 * than freqmax.
                                 */
-                               cpufreq_frequency_table_target(
-                                       pcpu->policy, pcpu->freq_table,
-                                       freqmax - 1, CPUFREQ_RELATION_H,
-                                       &index);
+                               if (cpufreq_frequency_table_target(
+                                           pcpu->policy, pcpu->freq_table,
+                                           freqmax - 1, CPUFREQ_RELATION_H,
+                                           &index))
+                                       break;
                                freq = pcpu->freq_table[index].frequency;
 
                                if (freq == freqmin) {
@@ -223,10 +288,11 @@ static unsigned int choose_freq(
                                 * Find the lowest frequency that is higher
                                 * than freqmin.
                                 */
-                               cpufreq_frequency_table_target(
-                                       pcpu->policy, pcpu->freq_table,
-                                       freqmin + 1, CPUFREQ_RELATION_L,
-                                       &index);
+                               if (cpufreq_frequency_table_target(
+                                           pcpu->policy, pcpu->freq_table,
+                                           freqmin + 1, CPUFREQ_RELATION_L,
+                                           &index))
+                                       break;
                                freq = pcpu->freq_table[index].frequency;
 
                                /*
@@ -254,7 +320,7 @@ static u64 update_load(int cpu)
        unsigned int delta_time;
        u64 active_time;
 
-       now_idle = get_cpu_idle_time_us(cpu, &now);
+       now_idle = get_cpu_idle_time(cpu, &now);
        delta_idle = (unsigned int)(now_idle - pcpu->time_in_idle);
        delta_time = (unsigned int)(now - pcpu->time_in_idle_timestamp);
        active_time = delta_time - delta_idle;
@@ -284,11 +350,11 @@ static void cpufreq_interactive_timer(unsigned long data)
        if (!pcpu->governor_enabled)
                goto exit;
 
-       spin_lock(&pcpu->load_lock);
+       spin_lock_irqsave(&pcpu->load_lock, flags);
        now = update_load(data);
        delta_time = (unsigned int)(now - pcpu->cputime_speedadj_timestamp);
        cputime_speedadj = pcpu->cputime_speedadj;
-       spin_unlock(&pcpu->load_lock);
+       spin_unlock_irqrestore(&pcpu->load_lock, flags);
 
        if (WARN_ON_ONCE(!delta_time))
                goto rearm;
@@ -313,7 +379,8 @@ static void cpufreq_interactive_timer(unsigned long data)
 
        if (pcpu->target_freq >= hispeed_freq &&
            new_freq > pcpu->target_freq &&
-           now - pcpu->hispeed_validate_time < above_hispeed_delay_val) {
+           now - pcpu->hispeed_validate_time <
+           freq_to_above_hispeed_delay(pcpu->target_freq)) {
                trace_cpufreq_interactive_notyet(
                        data, cpu_load, pcpu->target_freq,
                        pcpu->policy->cur, new_freq);
@@ -324,11 +391,8 @@ static void cpufreq_interactive_timer(unsigned long data)
 
        if (cpufreq_frequency_table_target(pcpu->policy, pcpu->freq_table,
                                           new_freq, CPUFREQ_RELATION_L,
-                                          &index)) {
-               pr_warn_once("timer %d: cpufreq_frequency_table_target error\n",
-                            (int) data);
+                                          &index))
                goto rearm;
-       }
 
        new_freq = pcpu->freq_table[index].frequency;
 
@@ -549,19 +613,27 @@ static int cpufreq_interactive_notifier(
        struct cpufreq_freqs *freq = data;
        struct cpufreq_interactive_cpuinfo *pcpu;
        int cpu;
+       unsigned long flags;
 
        if (val == CPUFREQ_POSTCHANGE) {
                pcpu = &per_cpu(cpuinfo, freq->cpu);
+               if (!down_read_trylock(&pcpu->enable_sem))
+                       return 0;
+               if (!pcpu->governor_enabled) {
+                       up_read(&pcpu->enable_sem);
+                       return 0;
+               }
 
                for_each_cpu(cpu, pcpu->policy->cpus) {
                        struct cpufreq_interactive_cpuinfo *pjcpu =
                                &per_cpu(cpuinfo, cpu);
-                       spin_lock(&pjcpu->load_lock);
+                       spin_lock_irqsave(&pjcpu->load_lock, flags);
                        update_load(cpu);
-                       spin_unlock(&pjcpu->load_lock);
+                       spin_unlock_irqrestore(&pjcpu->load_lock, flags);
                }
-       }
 
+               up_read(&pcpu->enable_sem);
+       }
        return 0;
 }
 
@@ -569,51 +641,32 @@ static struct notifier_block cpufreq_notifier_block = {
        .notifier_call = cpufreq_interactive_notifier,
 };
 
-static ssize_t show_target_loads(
-       struct kobject *kobj, struct attribute *attr, char *buf)
+static unsigned int *get_tokenized_data(const char *buf, int *num_tokens)
 {
-       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_loads(
-       struct kobject *kobj, struct attribute *attr, const char *buf,
-       size_t count)
-{
-       int ret;
        const char *cp;
-       unsigned int *new_target_loads = NULL;
-       int ntokens = 1;
        int i;
+       int ntokens = 1;
+       unsigned int *tokenized_data;
+       int err = -EINVAL;
 
        cp = buf;
        while ((cp = strpbrk(cp + 1, " :")))
                ntokens++;
 
        if (!(ntokens & 0x1))
-               goto err_inval;
+               goto err;
 
-       new_target_loads = kmalloc(ntokens * sizeof(unsigned int), GFP_KERNEL);
-       if (!new_target_loads) {
-               ret = -ENOMEM;
+       tokenized_data = kmalloc(ntokens * sizeof(unsigned int), GFP_KERNEL);
+       if (!tokenized_data) {
+               err = -ENOMEM;
                goto err;
        }
 
        cp = buf;
        i = 0;
        while (i < ntokens) {
-               if (sscanf(cp, "%u", &new_target_loads[i++]) != 1)
-                       goto err_inval;
+               if (sscanf(cp, "%u", &tokenized_data[i++]) != 1)
+                       goto err_kfree;
 
                cp = strpbrk(cp, " :");
                if (!cp)
@@ -622,27 +675,104 @@ static ssize_t store_target_loads(
        }
 
        if (i != ntokens)
-               goto err_inval;
+               goto err_kfree;
+
+       *num_tokens = ntokens;
+       return tokenized_data;
+
+err_kfree:
+       kfree(tokenized_data);
+err:
+       return ERR_PTR(err);
+}
+
+static ssize_t show_target_loads(
+       struct kobject *kobj, struct attribute *attr, char *buf)
+{
+       int i;
+       ssize_t ret = 0;
+       unsigned long flags;
+
+       spin_lock_irqsave(&target_loads_lock, flags);
+
+       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_irqrestore(&target_loads_lock, flags);
+       return ret;
+}
 
-       spin_lock(&target_loads_lock);
+static ssize_t store_target_loads(
+       struct kobject *kobj, struct attribute *attr, const char *buf,
+       size_t count)
+{
+       int ntokens;
+       unsigned int *new_target_loads = NULL;
+       unsigned long flags;
+
+       new_target_loads = get_tokenized_data(buf, &ntokens);
+       if (IS_ERR(new_target_loads))
+               return PTR_RET(new_target_loads);
+
+       spin_lock_irqsave(&target_loads_lock, flags);
        if (target_loads != default_target_loads)
                kfree(target_loads);
        target_loads = new_target_loads;
        ntarget_loads = ntokens;
-       spin_unlock(&target_loads_lock);
+       spin_unlock_irqrestore(&target_loads_lock, flags);
        return count;
-
-err_inval:
-       ret = -EINVAL;
-err:
-       kfree(new_target_loads);
-       return ret;
 }
 
 static struct global_attr target_loads_attr =
        __ATTR(target_loads, S_IRUGO | S_IWUSR,
                show_target_loads, store_target_loads);
 
+static ssize_t show_above_hispeed_delay(
+       struct kobject *kobj, struct attribute *attr, char *buf)
+{
+       int i;
+       ssize_t ret = 0;
+       unsigned long flags;
+
+       spin_lock_irqsave(&above_hispeed_delay_lock, flags);
+
+       for (i = 0; i < nabove_hispeed_delay; i++)
+               ret += sprintf(buf + ret, "%u%s", above_hispeed_delay[i],
+                              i & 0x1 ? ":" : " ");
+
+       ret += sprintf(buf + ret, "\n");
+       spin_unlock_irqrestore(&above_hispeed_delay_lock, flags);
+       return ret;
+}
+
+static ssize_t store_above_hispeed_delay(
+       struct kobject *kobj, struct attribute *attr, const char *buf,
+       size_t count)
+{
+       int ntokens;
+       unsigned int *new_above_hispeed_delay = NULL;
+       unsigned long flags;
+
+       new_above_hispeed_delay = get_tokenized_data(buf, &ntokens);
+       if (IS_ERR(new_above_hispeed_delay))
+               return PTR_RET(new_above_hispeed_delay);
+
+       spin_lock_irqsave(&above_hispeed_delay_lock, flags);
+       if (above_hispeed_delay != default_above_hispeed_delay)
+               kfree(above_hispeed_delay);
+       above_hispeed_delay = new_above_hispeed_delay;
+       nabove_hispeed_delay = ntokens;
+       spin_unlock_irqrestore(&above_hispeed_delay_lock, flags);
+       return count;
+
+}
+
+static struct global_attr above_hispeed_delay_attr =
+       __ATTR(above_hispeed_delay, S_IRUGO | S_IWUSR,
+               show_above_hispeed_delay, store_above_hispeed_delay);
+
 static ssize_t show_hispeed_freq(struct kobject *kobj,
                                 struct attribute *attr, char *buf)
 {
@@ -711,28 +841,6 @@ static ssize_t store_min_sample_time(struct kobject *kobj,
 static struct global_attr min_sample_time_attr = __ATTR(min_sample_time, 0644,
                show_min_sample_time, store_min_sample_time);
 
-static ssize_t show_above_hispeed_delay(struct kobject *kobj,
-                                       struct attribute *attr, char *buf)
-{
-       return sprintf(buf, "%lu\n", above_hispeed_delay_val);
-}
-
-static ssize_t store_above_hispeed_delay(struct kobject *kobj,
-                                        struct attribute *attr,
-                                        const char *buf, size_t count)
-{
-       int ret;
-       unsigned long val;
-
-       ret = strict_strtoul(buf, 0, &val);
-       if (ret < 0)
-               return ret;
-       above_hispeed_delay_val = val;
-       return count;
-}
-
-define_one_global_rw(above_hispeed_delay);
-
 static ssize_t show_timer_rate(struct kobject *kobj,
                        struct attribute *attr, char *buf)
 {
@@ -850,17 +958,40 @@ static ssize_t store_boostpulse_duration(
 
 define_one_global_rw(boostpulse_duration);
 
+static ssize_t show_io_is_busy(struct kobject *kobj,
+                       struct attribute *attr, char *buf)
+{
+       return sprintf(buf, "%u\n", io_is_busy);
+}
+
+static ssize_t store_io_is_busy(struct kobject *kobj,
+                       struct attribute *attr, const char *buf, size_t count)
+{
+       int ret;
+       unsigned long val;
+
+       ret = kstrtoul(buf, 0, &val);
+       if (ret < 0)
+               return ret;
+       io_is_busy = val;
+       return count;
+}
+
+static struct global_attr io_is_busy_attr = __ATTR(io_is_busy, 0644,
+               show_io_is_busy, store_io_is_busy);
+
 static struct attribute *interactive_attributes[] = {
        &target_loads_attr.attr,
+       &above_hispeed_delay_attr.attr,
        &hispeed_freq_attr.attr,
        &go_hispeed_load_attr.attr,
-       &above_hispeed_delay.attr,
        &min_sample_time_attr.attr,
        &timer_rate_attr.attr,
        &timer_slack.attr,
        &boost.attr,
        &boostpulse.attr,
        &boostpulse_duration.attr,
+       &io_is_busy_attr.attr,
        NULL,
 };
 
@@ -902,6 +1033,8 @@ static int cpufreq_governor_interactive(struct cpufreq_policy *policy,
                if (!cpu_online(policy->cpu))
                        return -EINVAL;
 
+               mutex_lock(&gov_lock);
+
                freq_table =
                        cpufreq_frequency_get_table(policy->cpu);
                if (!hispeed_freq)
@@ -936,20 +1069,26 @@ static int cpufreq_governor_interactive(struct cpufreq_policy *policy,
                 * Do not register the idle hook and create sysfs
                 * entries if we have already done so.
                 */
-               if (atomic_inc_return(&active_count) > 1)
+               if (++active_count > 1) {
+                       mutex_unlock(&gov_lock);
                        return 0;
+               }
 
                rc = sysfs_create_group(cpufreq_global_kobject,
                                &interactive_attr_group);
-               if (rc)
+               if (rc) {
+                       mutex_unlock(&gov_lock);
                        return rc;
+               }
 
                idle_notifier_register(&cpufreq_interactive_idle_nb);
                cpufreq_register_notifier(
                        &cpufreq_notifier_block, CPUFREQ_TRANSITION_NOTIFIER);
+               mutex_unlock(&gov_lock);
                break;
 
        case CPUFREQ_GOV_STOP:
+               mutex_lock(&gov_lock);
                for_each_cpu(j, policy->cpus) {
                        pcpu = &per_cpu(cpuinfo, j);
                        down_write(&pcpu->enable_sem);
@@ -959,14 +1098,17 @@ static int cpufreq_governor_interactive(struct cpufreq_policy *policy,
                        up_write(&pcpu->enable_sem);
                }
 
-               if (atomic_dec_return(&active_count) > 0)
+               if (--active_count > 0) {
+                       mutex_unlock(&gov_lock);
                        return 0;
+               }
 
                cpufreq_unregister_notifier(
                        &cpufreq_notifier_block, CPUFREQ_TRANSITION_NOTIFIER);
                idle_notifier_unregister(&cpufreq_interactive_idle_nb);
                sysfs_remove_group(cpufreq_global_kobject,
                                &interactive_attr_group);
+               mutex_unlock(&gov_lock);
 
                break;
 
@@ -1006,6 +1148,8 @@ static int __init cpufreq_interactive_init(void)
 
        spin_lock_init(&target_loads_lock);
        spin_lock_init(&speedchange_cpumask_lock);
+       spin_lock_init(&above_hispeed_delay_lock);
+       mutex_init(&gov_lock);
        speedchange_task =
                kthread_create(cpufreq_interactive_speedchange_task, NULL,
                               "cfinteractive");