aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorViresh Kumar <viresh.kumar@linaro.org>2014-01-06 20:40:10 -0500
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2014-01-16 20:00:43 -0500
commitfcd7af917abba798cd954419030142e95139359f (patch)
tree8af5f8b05aeb36470b0af642f8a007a54ab77816
parent5650cef2ea4c660a3d38bb1ac44e9169d310f366 (diff)
cpufreq: stats: handle cpufreq_unregister_driver() and suspend/resume properly
There are several problems with cpufreq stats in the way it handles cpufreq_unregister_driver() and suspend/resume.. - We must not lose data collected so far when suspend/resume happens and so stats directories must not be removed/allocated during these operations, which is done currently. - cpufreq_stat has registered notifiers with both cpufreq and hotplug. It adds sysfs stats directory with a cpufreq notifier: CPUFREQ_NOTIFY and removes this directory with a notifier from hotplug core. In case cpufreq_unregister_driver() is called (on rmmod cpufreq driver), stats directories per cpu aren't removed as CPUs are still online. The only call cpufreq_stats gets is cpufreq_stats_update_policy_cpu() for all CPUs except the last of each policy. And pointer to stat information is stored in the entry for last CPU in the per-cpu cpufreq_stats_table. But policy structure would be freed inside cpufreq core and so that will result in memory leak inside cpufreq stats (as we are never freeing memory for stats). Now if we again insert the module cpufreq_register_driver() will be called and we will again allocate stats data and put it on for first CPU of every policy. In case we only have a single CPU per policy, we will return with a error from cpufreq_stats_create_table() due to this code: if (per_cpu(cpufreq_stats_table, cpu)) return -EBUSY; And so probably cpufreq stats directory would not show up anymore (as it was added inside last policies->kobj which doesn't exist anymore). I haven't tested it, though. Also the values in stats files wouldn't be refreshed as we are using the earlier stats structure. - CPUFREQ_NOTIFY is called from cpufreq_set_policy() which is called for scenarios where we don't really want cpufreq_stat_notifier_policy() to get called. For example whenever we are changing anything related to a policy: min/max/current freq, etc. cpufreq_set_policy() is called and so cpufreq stats is notified. Where we don't do any useful stuff other than simply returning with -EBUSY from cpufreq_stats_create_table(). And so this isn't the right notifier that cpufreq stats.. Due to all above reasons this patch does following changes: - Add new notifiers CPUFREQ_CREATE_POLICY and CPUFREQ_REMOVE_POLICY, which are only called when policy is created/destroyed. They aren't called for suspend/resume paths.. - Use these notifiers in cpufreq_stat_notifier_policy() to create/destory stats sysfs entries. And so cpufreq_unregister_driver() or suspend/resume shouldn't be a problem for cpufreq_stats. - Return early from cpufreq_stat_cpu_callback() for suspend/resume sequence, so that we don't free stats structure. Acked-by: Nicolas Pitre <nico@linaro.org> Tested-by: Nicolas Pitre <nico@linaro.org> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
-rw-r--r--drivers/cpufreq/cpufreq.c5
-rw-r--r--drivers/cpufreq/cpufreq_stats.c24
-rw-r--r--include/linux/cpufreq.h2
3 files changed, 24 insertions, 7 deletions
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index 3509ca04b5bb..1afbe52d6782 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -943,6 +943,9 @@ static void cpufreq_policy_put_kobj(struct cpufreq_policy *policy)
943 struct kobject *kobj; 943 struct kobject *kobj;
944 struct completion *cmp; 944 struct completion *cmp;
945 945
946 blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
947 CPUFREQ_REMOVE_POLICY, policy);
948
946 down_read(&policy->rwsem); 949 down_read(&policy->rwsem);
947 kobj = &policy->kobj; 950 kobj = &policy->kobj;
948 cmp = &policy->kobj_unregister; 951 cmp = &policy->kobj_unregister;
@@ -1148,6 +1151,8 @@ static int __cpufreq_add_dev(struct device *dev, struct subsys_interface *sif,
1148 ret = cpufreq_add_dev_interface(policy, dev); 1151 ret = cpufreq_add_dev_interface(policy, dev);
1149 if (ret) 1152 if (ret)
1150 goto err_out_unregister; 1153 goto err_out_unregister;
1154 blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
1155 CPUFREQ_CREATE_POLICY, policy);
1151 } 1156 }
1152 1157
1153 write_lock_irqsave(&cpufreq_driver_lock, flags); 1158 write_lock_irqsave(&cpufreq_driver_lock, flags);
diff --git a/drivers/cpufreq/cpufreq_stats.c b/drivers/cpufreq/cpufreq_stats.c
index 4cf0d2805cb2..0f7156252453 100644
--- a/drivers/cpufreq/cpufreq_stats.c
+++ b/drivers/cpufreq/cpufreq_stats.c
@@ -277,7 +277,7 @@ static void cpufreq_stats_update_policy_cpu(struct cpufreq_policy *policy)
277static int cpufreq_stat_notifier_policy(struct notifier_block *nb, 277static int cpufreq_stat_notifier_policy(struct notifier_block *nb,
278 unsigned long val, void *data) 278 unsigned long val, void *data)
279{ 279{
280 int ret; 280 int ret = 0;
281 struct cpufreq_policy *policy = data; 281 struct cpufreq_policy *policy = data;
282 struct cpufreq_frequency_table *table; 282 struct cpufreq_frequency_table *table;
283 unsigned int cpu = policy->cpu; 283 unsigned int cpu = policy->cpu;
@@ -287,15 +287,21 @@ static int cpufreq_stat_notifier_policy(struct notifier_block *nb,
287 return 0; 287 return 0;
288 } 288 }
289 289
290 if (val != CPUFREQ_NOTIFY)
291 return 0;
292 table = cpufreq_frequency_get_table(cpu); 290 table = cpufreq_frequency_get_table(cpu);
293 if (!table) 291 if (!table)
294 return 0; 292 return 0;
295 ret = cpufreq_stats_create_table(policy, table); 293
296 if (ret) 294 if (val == CPUFREQ_CREATE_POLICY)
297 return ret; 295 ret = cpufreq_stats_create_table(policy, table);
298 return 0; 296 else if (val == CPUFREQ_REMOVE_POLICY) {
297 /* This might already be freed by cpu hotplug notifier */
298 if (per_cpu(cpufreq_stats_table, cpu)) {
299 cpufreq_stats_free_sysfs(cpu);
300 cpufreq_stats_free_table(cpu);
301 }
302 }
303
304 return ret;
299} 305}
300 306
301static int cpufreq_stat_notifier_trans(struct notifier_block *nb, 307static int cpufreq_stat_notifier_trans(struct notifier_block *nb,
@@ -340,6 +346,10 @@ static int cpufreq_stat_cpu_callback(struct notifier_block *nfb,
340{ 346{
341 unsigned int cpu = (unsigned long)hcpu; 347 unsigned int cpu = (unsigned long)hcpu;
342 348
349 /* Don't free/allocate stats during suspend/resume */
350 if (action & CPU_TASKS_FROZEN)
351 return 0;
352
343 switch (action) { 353 switch (action) {
344 case CPU_DOWN_PREPARE: 354 case CPU_DOWN_PREPARE:
345 cpufreq_stats_free_sysfs(cpu); 355 cpufreq_stats_free_sysfs(cpu);
diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h
index aaf800eb9dd2..bb727eb98ed5 100644
--- a/include/linux/cpufreq.h
+++ b/include/linux/cpufreq.h
@@ -308,6 +308,8 @@ cpufreq_verify_within_cpu_limits(struct cpufreq_policy *policy)
308#define CPUFREQ_NOTIFY (2) 308#define CPUFREQ_NOTIFY (2)
309#define CPUFREQ_START (3) 309#define CPUFREQ_START (3)
310#define CPUFREQ_UPDATE_POLICY_CPU (4) 310#define CPUFREQ_UPDATE_POLICY_CPU (4)
311#define CPUFREQ_CREATE_POLICY (5)
312#define CPUFREQ_REMOVE_POLICY (6)
311 313
312#ifdef CONFIG_CPU_FREQ 314#ifdef CONFIG_CPU_FREQ
313int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list); 315int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list);