cpufreq: interactive: fix racy timer stopping
authorTodd Poynor <toddpoynor@google.com>
Wed, 19 Dec 2012 01:50:44 +0000 (17:50 -0800)
committerArve Hjønnevåg <arve@android.com>
Mon, 1 Jul 2013 21:16:18 +0000 (14:16 -0700)
When stopping the governor, del_timer_sync() can race against an
invocation of the idle notifier callback, which has the potential
to reactivate the timer.

To fix this issue, a read-write semaphore is used. Multiple readers are
allowed as long as pcpu->governor_enabled is true.  However it can be
moved to false only after taking a write semaphore which would wait for
any on-going asynchronous activities to complete and prevent any more of
those activities to be initiated.

[toddpoynor@google.com: cosmetic and commit text changes]
Change-Id: Ib51165a735d73dcf964a06754c48bdc1913e13d0
Signed-off-by: Nicolas Pitre <nicolas.pitre@linaro.org>
drivers/cpufreq/cpufreq_interactive.c

index f8e9ee9f7137b7518c355dcb874551f15916aa5e..74f56093d2f3b6bc3fda777e7f815a6ee21f66d4 100644 (file)
@@ -21,7 +21,7 @@
 #include <linux/cpufreq.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
-#include <linux/mutex.h>
+#include <linux/rwsem.h>
 #include <linux/sched.h>
 #include <linux/sched/rt.h>
 #include <linux/tick.h>
@@ -29,7 +29,6 @@
 #include <linux/timer.h>
 #include <linux/workqueue.h>
 #include <linux/kthread.h>
-#include <linux/mutex.h>
 #include <linux/slab.h>
 #include <asm/cputime.h>
 
@@ -52,6 +51,7 @@ struct cpufreq_interactive_cpuinfo {
        unsigned int floor_freq;
        u64 floor_validate_time;
        u64 hispeed_validate_time;
+       struct rw_semaphore enable_sem;
        int governor_enabled;
 };
 
@@ -279,8 +279,8 @@ static void cpufreq_interactive_timer(unsigned long data)
        unsigned long flags;
        bool boosted;
 
-       smp_rmb();
-
+       if (!down_read_trylock(&pcpu->enable_sem))
+               return;
        if (!pcpu->governor_enabled)
                goto exit;
 
@@ -387,6 +387,7 @@ rearm:
                cpufreq_interactive_timer_resched(pcpu);
 
 exit:
+       up_read(&pcpu->enable_sem);
        return;
 }
 
@@ -396,8 +397,12 @@ static void cpufreq_interactive_idle_start(void)
                &per_cpu(cpuinfo, smp_processor_id());
        int pending;
 
-       if (!pcpu->governor_enabled)
+       if (!down_read_trylock(&pcpu->enable_sem))
+               return;
+       if (!pcpu->governor_enabled) {
+               up_read(&pcpu->enable_sem);
                return;
+       }
 
        pending = timer_pending(&pcpu->cpu_timer);
 
@@ -414,6 +419,7 @@ static void cpufreq_interactive_idle_start(void)
                        cpufreq_interactive_timer_resched(pcpu);
        }
 
+       up_read(&pcpu->enable_sem);
 }
 
 static void cpufreq_interactive_idle_end(void)
@@ -421,8 +427,12 @@ static void cpufreq_interactive_idle_end(void)
        struct cpufreq_interactive_cpuinfo *pcpu =
                &per_cpu(cpuinfo, smp_processor_id());
 
-       if (!pcpu->governor_enabled)
+       if (!down_read_trylock(&pcpu->enable_sem))
+               return;
+       if (!pcpu->governor_enabled) {
+               up_read(&pcpu->enable_sem);
                return;
+       }
 
        /* Arm the timer for 1-2 ticks later if not already. */
        if (!timer_pending(&pcpu->cpu_timer)) {
@@ -432,6 +442,8 @@ static void cpufreq_interactive_idle_end(void)
                del_timer(&pcpu->cpu_slack_timer);
                cpufreq_interactive_timer(smp_processor_id());
        }
+
+       up_read(&pcpu->enable_sem);
 }
 
 static int cpufreq_interactive_speedchange_task(void *data)
@@ -466,10 +478,12 @@ static int cpufreq_interactive_speedchange_task(void *data)
                        unsigned int max_freq = 0;
 
                        pcpu = &per_cpu(cpuinfo, cpu);
-                       smp_rmb();
-
-                       if (!pcpu->governor_enabled)
+                       if (!down_read_trylock(&pcpu->enable_sem))
+                               continue;
+                       if (!pcpu->governor_enabled) {
+                               up_read(&pcpu->enable_sem);
                                continue;
+                       }
 
                        for_each_cpu(j, pcpu->policy->cpus) {
                                struct cpufreq_interactive_cpuinfo *pjcpu =
@@ -486,6 +500,8 @@ static int cpufreq_interactive_speedchange_task(void *data)
                        trace_cpufreq_interactive_setspeed(cpu,
                                                     pcpu->target_freq,
                                                     pcpu->policy->cur);
+
+                       up_read(&pcpu->enable_sem);
                }
        }
 
@@ -936,10 +952,11 @@ static int cpufreq_governor_interactive(struct cpufreq_policy *policy,
        case CPUFREQ_GOV_STOP:
                for_each_cpu(j, policy->cpus) {
                        pcpu = &per_cpu(cpuinfo, j);
+                       down_write(&pcpu->enable_sem);
                        pcpu->governor_enabled = 0;
-                       smp_wmb();
                        del_timer_sync(&pcpu->cpu_timer);
                        del_timer_sync(&pcpu->cpu_slack_timer);
+                       up_write(&pcpu->enable_sem);
                }
 
                if (atomic_dec_return(&active_count) > 0)
@@ -989,6 +1006,7 @@ static int __init cpufreq_interactive_init(void)
                init_timer(&pcpu->cpu_slack_timer);
                pcpu->cpu_slack_timer.function = cpufreq_interactive_nop_timer;
                spin_lock_init(&pcpu->load_lock);
+               init_rwsem(&pcpu->enable_sem);
        }
 
        spin_lock_init(&target_loads_lock);