cpufreq: stats: Make the stats code non-modular
The modularity of cpufreq_stats is quite problematic. First off, the usage of policy notifiers for the initialization and cleanup in the cpufreq_stats module is inherently racy with respect to CPU offline/online and the initialization and cleanup of the cpufreq driver. Second, fast frequency switching (used by the schedutil governor) cannot be enabled if any transition notifiers are registered, so if the cpufreq_stats module (that registers a transition notifier for updating transition statistics) is loaded, the schedutil governor cannot use fast frequency switching. On the other hand, allowing cpufreq_stats to be built as a module doesn't really add much value. Arguably, there's not much reason for that code to be modular at all. For the above reasons, make the cpufreq stats code non-modular, modify the core to invoke functions provided by that code directly and drop the notifiers from it. Make the stats sysfs attributes appear empty if fast frequency switching is enabled as the statistics will not be updated in that case anyway (and returning -EBUSY from those attributes breaks powertop). While at it, clean up Kconfig help for the CPU_FREQ_STAT and CPU_FREQ_STAT_DETAILS options. Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
This commit is contained in:
parent
910c6e881c
commit
1aefc75b24
|
@ -31,23 +31,18 @@ config CPU_FREQ_BOOST_SW
|
||||||
depends on THERMAL
|
depends on THERMAL
|
||||||
|
|
||||||
config CPU_FREQ_STAT
|
config CPU_FREQ_STAT
|
||||||
tristate "CPU frequency translation statistics"
|
bool "CPU frequency transition statistics"
|
||||||
default y
|
default y
|
||||||
help
|
help
|
||||||
This driver exports CPU frequency statistics information through sysfs
|
Export CPU frequency statistics information through sysfs.
|
||||||
file system.
|
|
||||||
|
|
||||||
To compile this driver as a module, choose M here: the
|
|
||||||
module will be called cpufreq_stats.
|
|
||||||
|
|
||||||
If in doubt, say N.
|
If in doubt, say N.
|
||||||
|
|
||||||
config CPU_FREQ_STAT_DETAILS
|
config CPU_FREQ_STAT_DETAILS
|
||||||
bool "CPU frequency translation statistics details"
|
bool "CPU frequency transition statistics details"
|
||||||
depends on CPU_FREQ_STAT
|
depends on CPU_FREQ_STAT
|
||||||
help
|
help
|
||||||
This will show detail CPU frequency translation table in sysfs file
|
Show detailed CPU frequency transition table in sysfs.
|
||||||
system.
|
|
||||||
|
|
||||||
If in doubt, say N.
|
If in doubt, say N.
|
||||||
|
|
||||||
|
|
|
@ -347,6 +347,7 @@ static void __cpufreq_notify_transition(struct cpufreq_policy *policy,
|
||||||
pr_debug("FREQ: %lu - CPU: %lu\n",
|
pr_debug("FREQ: %lu - CPU: %lu\n",
|
||||||
(unsigned long)freqs->new, (unsigned long)freqs->cpu);
|
(unsigned long)freqs->new, (unsigned long)freqs->cpu);
|
||||||
trace_cpu_frequency(freqs->new, freqs->cpu);
|
trace_cpu_frequency(freqs->new, freqs->cpu);
|
||||||
|
cpufreq_stats_record_transition(policy, freqs->new);
|
||||||
srcu_notifier_call_chain(&cpufreq_transition_notifier_list,
|
srcu_notifier_call_chain(&cpufreq_transition_notifier_list,
|
||||||
CPUFREQ_POSTCHANGE, freqs);
|
CPUFREQ_POSTCHANGE, freqs);
|
||||||
if (likely(policy) && likely(policy->cpu == freqs->cpu))
|
if (likely(policy) && likely(policy->cpu == freqs->cpu))
|
||||||
|
@ -1108,6 +1109,7 @@ static void cpufreq_policy_put_kobj(struct cpufreq_policy *policy, bool notify)
|
||||||
CPUFREQ_REMOVE_POLICY, policy);
|
CPUFREQ_REMOVE_POLICY, policy);
|
||||||
|
|
||||||
down_write(&policy->rwsem);
|
down_write(&policy->rwsem);
|
||||||
|
cpufreq_stats_free_table(policy);
|
||||||
cpufreq_remove_dev_symlink(policy);
|
cpufreq_remove_dev_symlink(policy);
|
||||||
kobj = &policy->kobj;
|
kobj = &policy->kobj;
|
||||||
cmp = &policy->kobj_unregister;
|
cmp = &policy->kobj_unregister;
|
||||||
|
@ -1262,6 +1264,8 @@ static int cpufreq_online(unsigned int cpu)
|
||||||
ret = cpufreq_add_dev_interface(policy);
|
ret = cpufreq_add_dev_interface(policy);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out_exit_policy;
|
goto out_exit_policy;
|
||||||
|
|
||||||
|
cpufreq_stats_create_table(policy);
|
||||||
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
|
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
|
||||||
CPUFREQ_CREATE_POLICY, policy);
|
CPUFREQ_CREATE_POLICY, policy);
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/cputime.h>
|
#include <linux/cputime.h>
|
||||||
|
|
||||||
static spinlock_t cpufreq_stats_lock;
|
static DEFINE_SPINLOCK(cpufreq_stats_lock);
|
||||||
|
|
||||||
struct cpufreq_stats {
|
struct cpufreq_stats {
|
||||||
unsigned int total_trans;
|
unsigned int total_trans;
|
||||||
|
@ -52,6 +52,9 @@ static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf)
|
||||||
ssize_t len = 0;
|
ssize_t len = 0;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if (policy->fast_switch_enabled)
|
||||||
|
return 0;
|
||||||
|
|
||||||
cpufreq_stats_update(stats);
|
cpufreq_stats_update(stats);
|
||||||
for (i = 0; i < stats->state_num; i++) {
|
for (i = 0; i < stats->state_num; i++) {
|
||||||
len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i],
|
len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i],
|
||||||
|
@ -68,6 +71,9 @@ static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf)
|
||||||
ssize_t len = 0;
|
ssize_t len = 0;
|
||||||
int i, j;
|
int i, j;
|
||||||
|
|
||||||
|
if (policy->fast_switch_enabled)
|
||||||
|
return 0;
|
||||||
|
|
||||||
len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n");
|
len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n");
|
||||||
len += snprintf(buf + len, PAGE_SIZE - len, " : ");
|
len += snprintf(buf + len, PAGE_SIZE - len, " : ");
|
||||||
for (i = 0; i < stats->state_num; i++) {
|
for (i = 0; i < stats->state_num; i++) {
|
||||||
|
@ -130,7 +136,7 @@ static int freq_table_get_index(struct cpufreq_stats *stats, unsigned int freq)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __cpufreq_stats_free_table(struct cpufreq_policy *policy)
|
void cpufreq_stats_free_table(struct cpufreq_policy *policy)
|
||||||
{
|
{
|
||||||
struct cpufreq_stats *stats = policy->stats;
|
struct cpufreq_stats *stats = policy->stats;
|
||||||
|
|
||||||
|
@ -146,20 +152,7 @@ static void __cpufreq_stats_free_table(struct cpufreq_policy *policy)
|
||||||
policy->stats = NULL;
|
policy->stats = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cpufreq_stats_free_table(unsigned int cpu)
|
void cpufreq_stats_create_table(struct cpufreq_policy *policy)
|
||||||
{
|
|
||||||
struct cpufreq_policy *policy;
|
|
||||||
|
|
||||||
policy = cpufreq_cpu_get(cpu);
|
|
||||||
if (!policy)
|
|
||||||
return;
|
|
||||||
|
|
||||||
__cpufreq_stats_free_table(policy);
|
|
||||||
|
|
||||||
cpufreq_cpu_put(policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int __cpufreq_stats_create_table(struct cpufreq_policy *policy)
|
|
||||||
{
|
{
|
||||||
unsigned int i = 0, count = 0, ret = -ENOMEM;
|
unsigned int i = 0, count = 0, ret = -ENOMEM;
|
||||||
struct cpufreq_stats *stats;
|
struct cpufreq_stats *stats;
|
||||||
|
@ -170,15 +163,15 @@ static int __cpufreq_stats_create_table(struct cpufreq_policy *policy)
|
||||||
/* We need cpufreq table for creating stats table */
|
/* We need cpufreq table for creating stats table */
|
||||||
table = cpufreq_frequency_get_table(cpu);
|
table = cpufreq_frequency_get_table(cpu);
|
||||||
if (unlikely(!table))
|
if (unlikely(!table))
|
||||||
return 0;
|
return;
|
||||||
|
|
||||||
/* stats already initialized */
|
/* stats already initialized */
|
||||||
if (policy->stats)
|
if (policy->stats)
|
||||||
return -EEXIST;
|
return;
|
||||||
|
|
||||||
stats = kzalloc(sizeof(*stats), GFP_KERNEL);
|
stats = kzalloc(sizeof(*stats), GFP_KERNEL);
|
||||||
if (!stats)
|
if (!stats)
|
||||||
return -ENOMEM;
|
return;
|
||||||
|
|
||||||
/* Find total allocation size */
|
/* Find total allocation size */
|
||||||
cpufreq_for_each_valid_entry(pos, table)
|
cpufreq_for_each_valid_entry(pos, table)
|
||||||
|
@ -215,80 +208,32 @@ static int __cpufreq_stats_create_table(struct cpufreq_policy *policy)
|
||||||
policy->stats = stats;
|
policy->stats = stats;
|
||||||
ret = sysfs_create_group(&policy->kobj, &stats_attr_group);
|
ret = sysfs_create_group(&policy->kobj, &stats_attr_group);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
return 0;
|
return;
|
||||||
|
|
||||||
/* We failed, release resources */
|
/* We failed, release resources */
|
||||||
policy->stats = NULL;
|
policy->stats = NULL;
|
||||||
kfree(stats->time_in_state);
|
kfree(stats->time_in_state);
|
||||||
free_stat:
|
free_stat:
|
||||||
kfree(stats);
|
kfree(stats);
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cpufreq_stats_create_table(unsigned int cpu)
|
void cpufreq_stats_record_transition(struct cpufreq_policy *policy,
|
||||||
|
unsigned int new_freq)
|
||||||
{
|
{
|
||||||
struct cpufreq_policy *policy;
|
struct cpufreq_stats *stats = policy->stats;
|
||||||
|
|
||||||
/*
|
|
||||||
* "likely(!policy)" because normally cpufreq_stats will be registered
|
|
||||||
* before cpufreq driver
|
|
||||||
*/
|
|
||||||
policy = cpufreq_cpu_get(cpu);
|
|
||||||
if (likely(!policy))
|
|
||||||
return;
|
|
||||||
|
|
||||||
__cpufreq_stats_create_table(policy);
|
|
||||||
|
|
||||||
cpufreq_cpu_put(policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cpufreq_stat_notifier_policy(struct notifier_block *nb,
|
|
||||||
unsigned long val, void *data)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
struct cpufreq_policy *policy = data;
|
|
||||||
|
|
||||||
if (val == CPUFREQ_CREATE_POLICY)
|
|
||||||
ret = __cpufreq_stats_create_table(policy);
|
|
||||||
else if (val == CPUFREQ_REMOVE_POLICY)
|
|
||||||
__cpufreq_stats_free_table(policy);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cpufreq_stat_notifier_trans(struct notifier_block *nb,
|
|
||||||
unsigned long val, void *data)
|
|
||||||
{
|
|
||||||
struct cpufreq_freqs *freq = data;
|
|
||||||
struct cpufreq_policy *policy = cpufreq_cpu_get(freq->cpu);
|
|
||||||
struct cpufreq_stats *stats;
|
|
||||||
int old_index, new_index;
|
int old_index, new_index;
|
||||||
|
|
||||||
if (!policy) {
|
if (!stats) {
|
||||||
pr_err("%s: No policy found\n", __func__);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (val != CPUFREQ_POSTCHANGE)
|
|
||||||
goto put_policy;
|
|
||||||
|
|
||||||
if (!policy->stats) {
|
|
||||||
pr_debug("%s: No stats found\n", __func__);
|
pr_debug("%s: No stats found\n", __func__);
|
||||||
goto put_policy;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
stats = policy->stats;
|
|
||||||
|
|
||||||
old_index = stats->last_index;
|
old_index = stats->last_index;
|
||||||
new_index = freq_table_get_index(stats, freq->new);
|
new_index = freq_table_get_index(stats, new_freq);
|
||||||
|
|
||||||
/* We can't do stats->time_in_state[-1]= .. */
|
/* We can't do stats->time_in_state[-1]= .. */
|
||||||
if (old_index == -1 || new_index == -1)
|
if (old_index == -1 || new_index == -1 || old_index == new_index)
|
||||||
goto put_policy;
|
return;
|
||||||
|
|
||||||
if (old_index == new_index)
|
|
||||||
goto put_policy;
|
|
||||||
|
|
||||||
cpufreq_stats_update(stats);
|
cpufreq_stats_update(stats);
|
||||||
|
|
||||||
|
@ -297,61 +242,4 @@ static int cpufreq_stat_notifier_trans(struct notifier_block *nb,
|
||||||
stats->trans_table[old_index * stats->max_state + new_index]++;
|
stats->trans_table[old_index * stats->max_state + new_index]++;
|
||||||
#endif
|
#endif
|
||||||
stats->total_trans++;
|
stats->total_trans++;
|
||||||
|
|
||||||
put_policy:
|
|
||||||
cpufreq_cpu_put(policy);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct notifier_block notifier_policy_block = {
|
|
||||||
.notifier_call = cpufreq_stat_notifier_policy
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct notifier_block notifier_trans_block = {
|
|
||||||
.notifier_call = cpufreq_stat_notifier_trans
|
|
||||||
};
|
|
||||||
|
|
||||||
static int __init cpufreq_stats_init(void)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
unsigned int cpu;
|
|
||||||
|
|
||||||
spin_lock_init(&cpufreq_stats_lock);
|
|
||||||
ret = cpufreq_register_notifier(¬ifier_policy_block,
|
|
||||||
CPUFREQ_POLICY_NOTIFIER);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
for_each_online_cpu(cpu)
|
|
||||||
cpufreq_stats_create_table(cpu);
|
|
||||||
|
|
||||||
ret = cpufreq_register_notifier(¬ifier_trans_block,
|
|
||||||
CPUFREQ_TRANSITION_NOTIFIER);
|
|
||||||
if (ret) {
|
|
||||||
cpufreq_unregister_notifier(¬ifier_policy_block,
|
|
||||||
CPUFREQ_POLICY_NOTIFIER);
|
|
||||||
for_each_online_cpu(cpu)
|
|
||||||
cpufreq_stats_free_table(cpu);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
static void __exit cpufreq_stats_exit(void)
|
|
||||||
{
|
|
||||||
unsigned int cpu;
|
|
||||||
|
|
||||||
cpufreq_unregister_notifier(¬ifier_policy_block,
|
|
||||||
CPUFREQ_POLICY_NOTIFIER);
|
|
||||||
cpufreq_unregister_notifier(¬ifier_trans_block,
|
|
||||||
CPUFREQ_TRANSITION_NOTIFIER);
|
|
||||||
for_each_online_cpu(cpu)
|
|
||||||
cpufreq_stats_free_table(cpu);
|
|
||||||
}
|
|
||||||
|
|
||||||
MODULE_AUTHOR("Zou Nan hai <nanhai.zou@intel.com>");
|
|
||||||
MODULE_DESCRIPTION("Export cpufreq stats via sysfs");
|
|
||||||
MODULE_LICENSE("GPL");
|
|
||||||
|
|
||||||
module_init(cpufreq_stats_init);
|
|
||||||
module_exit(cpufreq_stats_exit);
|
|
||||||
|
|
|
@ -185,6 +185,18 @@ static inline unsigned int cpufreq_quick_get_max(unsigned int cpu)
|
||||||
static inline void disable_cpufreq(void) { }
|
static inline void disable_cpufreq(void) { }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_CPU_FREQ_STAT
|
||||||
|
void cpufreq_stats_create_table(struct cpufreq_policy *policy);
|
||||||
|
void cpufreq_stats_free_table(struct cpufreq_policy *policy);
|
||||||
|
void cpufreq_stats_record_transition(struct cpufreq_policy *policy,
|
||||||
|
unsigned int new_freq);
|
||||||
|
#else
|
||||||
|
static inline void cpufreq_stats_create_table(struct cpufreq_policy *policy) { }
|
||||||
|
static inline void cpufreq_stats_free_table(struct cpufreq_policy *policy) { }
|
||||||
|
static inline void cpufreq_stats_record_transition(struct cpufreq_policy *policy,
|
||||||
|
unsigned int new_freq) { }
|
||||||
|
#endif /* CONFIG_CPU_FREQ_STAT */
|
||||||
|
|
||||||
/*********************************************************************
|
/*********************************************************************
|
||||||
* CPUFREQ DRIVER INTERFACE *
|
* CPUFREQ DRIVER INTERFACE *
|
||||||
*********************************************************************/
|
*********************************************************************/
|
||||||
|
|
Loading…
Reference in a new issue