From: Nicolas Pitre <nicolas.pi...@linaro.org>

Relying on pcpu->governor_enabled alone to ensure proper shutdown
of the governor activities is flawed. The following race is perfectly
possible:

CPU0                                    |CPU2
----                                    +----
                                        |cpufreq_interactive_timer()
cpufreq_governor_interactive(GOV_STOP)  |if (!pcpu->governor_enabled)
pcpu->governor_enabled = 0;             |        return; [untaken]
smp_wmb();                              |[...]
del_timer_sync(&pcpu->cpu_timer);       |cpufreq_interactive_timer_resched()
                                        |mod_timer_pinned(&pcpu->cpu_timer)

Result: timer is pending again.  It is also possible for
del_timer_sync() to race against the invocation of the idle notifier
callback which also 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.

Signed-off-by: Nicolas Pitre <nicolas.pi...@linaro.org>
---
 drivers/cpufreq/cpufreq_interactive.c | 38 ++++++++++++++++++++++++++---------
 1 file changed, 28 insertions(+), 10 deletions(-)

diff --git a/drivers/cpufreq/cpufreq_interactive.c 
b/drivers/cpufreq/cpufreq_interactive.c
index c82d9fe..061af7f 100644
--- a/drivers/cpufreq/cpufreq_interactive.c
+++ b/drivers/cpufreq/cpufreq_interactive.c
@@ -21,14 +21,13 @@
 #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/tick.h>
 #include <linux/time.h>
 #include <linux/timer.h>
 #include <linux/workqueue.h>
 #include <linux/kthread.h>
-#include <linux/mutex.h>
 #include <linux/slab.h>
 #include <asm/cputime.h>
 
@@ -50,6 +49,7 @@ struct cpufreq_interactive_cpuinfo {
        unsigned int floor_freq;
        u64 floor_validate_time;
        u64 hispeed_validate_time;
+       struct rw_semaphore mutex;
        int governor_enabled;
 };
 
@@ -138,8 +138,8 @@ static void cpufreq_interactive_timer(unsigned long data)
        unsigned int index;
        unsigned long flags;
 
-       smp_rmb();
-
+       if (!down_read_trylock(&pcpu->mutex))
+               return;
        if (!pcpu->governor_enabled)
                goto exit;
 
@@ -258,6 +258,7 @@ rearm:
        }
 
 exit:
+       up_read(&pcpu->mutex);
        return;
 }
 
@@ -267,8 +268,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->mutex))
+               return;
+       if (!pcpu->governor_enabled) {
+               up_read(&pcpu->mutex);
                return;
+       }
 
        pending = timer_pending(&pcpu->cpu_timer);
 
@@ -298,6 +303,7 @@ static void cpufreq_interactive_idle_start(void)
                }
        }
 
+       up_read(&pcpu->mutex);
 }
 
 static void cpufreq_interactive_idle_end(void)
@@ -305,8 +311,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->mutex))
+               return;
+       if (!pcpu->governor_enabled) {
+               up_read(&pcpu->mutex);
                return;
+       }
 
        /* Arm the timer for 1-2 ticks later if not already. */
        if (!timer_pending(&pcpu->cpu_timer)) {
@@ -317,6 +327,8 @@ static void cpufreq_interactive_idle_end(void)
                del_timer(&pcpu->cpu_timer);
                cpufreq_interactive_timer(smp_processor_id());
        }
+
+       up_read(&pcpu->mutex);
 }
 
 static int cpufreq_interactive_speedchange_task(void *data)
@@ -351,10 +363,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->mutex))
+                               continue;
+                       if (!pcpu->governor_enabled) {
+                               up_read(&pcpu->mutex);
                                continue;
+                       }
 
                        for_each_cpu(j, pcpu->policy->cpus) {
                                struct cpufreq_interactive_cpuinfo *pjcpu =
@@ -371,6 +385,8 @@ static int cpufreq_interactive_speedchange_task(void *data)
                        trace_cpufreq_interactive_setspeed(cpu,
                                                     pcpu->target_freq,
                                                     pcpu->policy->cur);
+
+                       up_read(&pcpu->mutex);
                }
        }
 
@@ -690,9 +706,10 @@ 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->mutex);
                        pcpu->governor_enabled = 0;
-                       smp_wmb();
                        del_timer_sync(&pcpu->cpu_timer);
+                       up_write(&pcpu->mutex);
                }
 
                if (atomic_dec_return(&active_count) > 0)
@@ -736,6 +753,7 @@ static int __init cpufreq_interactive_init(void)
                        init_timer_deferrable(&pcpu->cpu_timer);
                pcpu->cpu_timer.function = cpufreq_interactive_timer;
                pcpu->cpu_timer.data = i;
+               init_rwsem(&pcpu->mutex);
        }
 
        spin_lock_init(&speedchange_cpumask_lock);
-- 
1.7.12.rc2.18.g61b472e


_______________________________________________
linaro-dev mailing list
linaro-dev@lists.linaro.org
http://lists.linaro.org/mailman/listinfo/linaro-dev

Reply via email to