blob: 0b2ded84e17b84b06b2e0f2aac2df841a290d857 [file] [log] [blame]
/* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/sysfs.h>
#include <linux/cpufreq.h>
#include <linux/jiffies.h>
#include <linux/percpu.h>
#include <linux/kobject.h>
#include <linux/spinlock.h>
#include <linux/notifier.h>
#include <linux/bitops.h>
#include <linux/stat.h>
#include <asm/cputime.h>
#include <linux/module.h>
#define MAX_CPUATTR_NAME_LEN (16)
#define MAX_CPU_STATS_LINE_LEN (64)
static spinlock_t cpufreq_stats_lock;
static unsigned int stats_collection_enabled = 1;
static const char *time_in_state_attr_name = "time_in_state";
struct cpu_persistent_stats {
/* CPU name, e.g. cpu0 */
char name[MAX_CPUATTR_NAME_LEN];
/* the number identifying this CPU */
unsigned int cpu_id;
/*
* the last time stats were
* updated for this CPU
*/
unsigned long long last_time;
/*
* the number of frequencies available
* to this CPU
*/
unsigned int max_state;
/*
* the index corresponding to
* the frequency (from freq_table) that this CPU
* last ran at
*/
unsigned int last_index;
/*
* the kobject corresponding to
* this CPU
*/
struct kobject cpu_persistent_stat_kobj;
/*
* a table holding the cumulative time
* (in jiffies) spent by this CPU at
* frequency freq_table[i]
*/
cputime64_t *time_in_state;
/*
* a table holding the frequencies this CPU
* can run at
*/
unsigned int *freq_table;
};
struct cpufreq_persistent_stats {
char name[MAX_CPUATTR_NAME_LEN];
struct kobject *persistent_stats_kobj;
} persistent_stats = {
.name = "stats",
};
static DEFINE_PER_CPU(struct cpu_persistent_stats, pcpu_stats);
static struct attribute cpu_time_in_state_attr[NR_CPUS];
static int cpufreq_stats_update(unsigned int cpu)
{
unsigned long long cur_time;
struct cpu_persistent_stats *cpu_stats = &per_cpu(pcpu_stats, cpu);
if (!stats_collection_enabled)
return 0;
if (cpu_stats->last_time) {
cur_time = get_jiffies_64();
if (likely(cpu_stats->time_in_state))
cpu_stats->time_in_state[cpu_stats->last_index] =
cpu_stats->time_in_state[cpu_stats->last_index]
+ (cur_time - cpu_stats->last_time);
cpu_stats->last_time = cur_time;
}
return 0;
}
static void reset_stats(void)
{
unsigned int cpu;
unsigned long irq_flags;
struct cpu_persistent_stats *cpux_persistent_stats;
spin_lock_irqsave(&cpufreq_stats_lock, irq_flags);
for_each_possible_cpu(cpu) {
cpux_persistent_stats = &per_cpu(pcpu_stats, cpu);
if (cpux_persistent_stats->freq_table &&
cpux_persistent_stats->time_in_state) {
int i;
for (i = 0; i < cpux_persistent_stats->max_state; i++)
cpux_persistent_stats->time_in_state[i] = 0ULL;
}
}
spin_unlock_irqrestore(&cpufreq_stats_lock, irq_flags);
}
static int freq_table_get_index(
struct cpu_persistent_stats *stats,
unsigned int freq)
{
int index;
if (stats->freq_table) {
for (index = 0; index < stats->max_state; index++)
if (stats->freq_table[index] == freq)
return index;
}
return -ENOENT;
}
static void cpufreq_stats_free_table(unsigned int cpu)
{
unsigned long irq_flags;
struct cpu_persistent_stats *cpu_stats = &per_cpu(pcpu_stats, cpu);
spin_lock_irqsave(&cpufreq_stats_lock, irq_flags);
kfree(cpu_stats->time_in_state);
cpu_stats->time_in_state = NULL;
cpu_stats->freq_table = NULL;
spin_unlock_irqrestore(&cpufreq_stats_lock, irq_flags);
}
static int cpufreq_stats_create_table(unsigned int cpu,
struct cpufreq_policy *policy)
{
unsigned int i, k, count = 0;
unsigned int alloc_size;
struct cpu_persistent_stats *cpu_stats = &per_cpu(pcpu_stats, cpu);
unsigned long irq_flags;
struct cpufreq_frequency_table *table =
cpufreq_frequency_get_table(policy->cpu);
void *temp_time_in_state;
int next_freq_index;
if (unlikely(!table))
return -EINVAL;
if (likely(cpu_stats->time_in_state))
return -EBUSY;
for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) {
unsigned int freq = table[i].frequency;
if (freq == CPUFREQ_ENTRY_INVALID)
continue;
count++;
}
alloc_size = count * sizeof(int) + count * sizeof(cputime64_t);
temp_time_in_state = kzalloc(alloc_size, GFP_KERNEL);
if (!temp_time_in_state)
return -ENOMEM;
spin_lock_irqsave(&cpufreq_stats_lock, irq_flags);
if (!cpu_stats->time_in_state) {
cpu_stats->time_in_state = temp_time_in_state;
cpu_stats->max_state = count;
cpu_stats->freq_table =
(unsigned int *)(cpu_stats->time_in_state + count);
k = 0;
for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) {
unsigned int freq = table[i].frequency;
if (freq == CPUFREQ_ENTRY_INVALID)
continue;
cpu_stats->freq_table[k++] = freq;
}
} else
kfree(temp_time_in_state);
next_freq_index = freq_table_get_index(
cpu_stats, policy->cur);
if (next_freq_index != -ENOENT) {
cpu_stats->last_time = get_jiffies_64();
cpu_stats->last_index = next_freq_index;
}
spin_unlock_irqrestore(&cpufreq_stats_lock, irq_flags);
return 0;
}
static int cpufreq_stat_notifier_trans(struct notifier_block *nb,
unsigned long val, void *data)
{
struct cpufreq_freqs *freq = data;
struct cpufreq_policy *policy;
static unsigned int table_created_count;
struct cpu_persistent_stats *cpu_stats = &per_cpu(pcpu_stats,
freq->cpu);
unsigned long irq_flags;
unsigned int cpu;
int next_freq_index;
if (val != CPUFREQ_POSTCHANGE)
return 0;
policy = cpufreq_cpu_get(freq->cpu);
if (!policy)
return 0;
if (table_created_count < num_possible_cpus()) {
for_each_cpu(cpu, policy->cpus) {
if (cpufreq_stats_create_table(cpu, policy) == 0)
table_created_count++;
}
}
spin_lock_irqsave(&cpufreq_stats_lock, irq_flags);
for_each_cpu(cpu, policy->cpus) {
cpu_stats = &per_cpu(pcpu_stats, cpu);
next_freq_index = freq_table_get_index(cpu_stats,
freq->new);
if (next_freq_index != -ENOENT) {
if (cpu_online(cpu)) {
cpufreq_stats_update(cpu);
cpu_stats->last_time = get_jiffies_64();
}
cpu_stats->last_index = next_freq_index;
}
}
spin_unlock_irqrestore(&cpufreq_stats_lock, irq_flags);
cpufreq_cpu_put(policy);
return 0;
}
static int __cpuinit cpufreq_stat_cpu_callback(struct notifier_block *nfb,
unsigned long action,
void *hcpu)
{
unsigned int cpu = (unsigned long)hcpu;
struct cpu_persistent_stats *cpu_stats = &per_cpu(pcpu_stats, cpu);
unsigned long flags;
spin_lock_irqsave(&cpufreq_stats_lock, flags);
switch (action) {
case CPU_ONLINE:
case CPU_ONLINE_FROZEN:
cpu_stats->last_time = get_jiffies_64();
break;
case CPU_DEAD:
case CPU_DEAD_FROZEN:
cpufreq_stats_update(cpu);
cpu_stats->last_time = 0;
break;
}
spin_unlock_irqrestore(&cpufreq_stats_lock, flags);
return NOTIFY_OK;
}
/************************** sysfs interface ************************/
static ssize_t show_cpu_time_in_state(struct kobject *kobj,
struct attribute *attr, char *buf)
{
/*
* The container of kobj is of type cpu_persistent_stats
* so we can use it to get the cpu ID corresponding to this attribute.
*/
int len = 0, i;
unsigned long irq_flags;
struct cpu_persistent_stats *cpux_persistent_stats =
container_of(kobj, struct cpu_persistent_stats,
cpu_persistent_stat_kobj);
spin_lock_irqsave(&cpufreq_stats_lock, irq_flags);
cpufreq_stats_update(cpux_persistent_stats->cpu_id);
if (cpux_persistent_stats->time_in_state) {
for (i = 0; i < cpux_persistent_stats->max_state; i++) {
cputime64_t time_in_state =
cpux_persistent_stats->time_in_state[i];
s64 clocktime_in_state =
cputime64_to_clock_t(time_in_state);
len += snprintf(buf + len, MAX_CPU_STATS_LINE_LEN,
"%u %lld\n",
cpux_persistent_stats->freq_table[i],
clocktime_in_state);
}
}
spin_unlock_irqrestore(&cpufreq_stats_lock, irq_flags);
return len;
}
static ssize_t store_reset(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
int ret, input;
ret = sscanf(buf, "%d", &input);
if (ret < 0)
return -EINVAL;
if (input)
reset_stats();
return count;
}
static ssize_t show_enabled(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, 2, "%u\n", !!stats_collection_enabled);
}
static ssize_t store_enable(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
int ret, input;
unsigned int cpu;
unsigned long irq_flags;
ret = sscanf(buf, "%d", &input);
if (ret < 0)
return -EINVAL;
if (!!input == stats_collection_enabled)
return count;
if (input && !stats_collection_enabled) {
spin_lock_irqsave(&cpufreq_stats_lock, irq_flags);
for_each_possible_cpu(cpu) {
if (per_cpu(pcpu_stats, cpu).last_time)
per_cpu(pcpu_stats, cpu).last_time =
get_jiffies_64();
}
spin_unlock_irqrestore(&cpufreq_stats_lock, irq_flags);
}
stats_collection_enabled = !!input;
return count;
}
static const struct sysfs_ops cpu_time_in_state_ops = {
.show = show_cpu_time_in_state,
};
static struct kobj_attribute reset_attr =
__ATTR(reset, 0220, NULL, store_reset);
static struct kobj_attribute enable_attr =
__ATTR(enable, S_IRUGO|S_IWUSR, show_enabled, store_enable);
static int create_persistent_stats_groups(void)
{
unsigned int cpu_id;
int ret;
struct cpu_persistent_stats *cpu_stats;
/* Create toplevel persistent stats kobject. */
ret = cpufreq_get_global_kobject();
if (ret)
return ret;
persistent_stats.persistent_stats_kobj =
kobject_create_and_add(persistent_stats.name,
cpufreq_global_kobject);
if (!persistent_stats.persistent_stats_kobj) {
pr_err("%s: Unable to create persistent_stats_kobj.\n",
__func__);
ret = -EINVAL;
goto abort_stats_kobj_create_failed;
}
ret = sysfs_create_file(
persistent_stats.persistent_stats_kobj,
&enable_attr.attr);
if (ret) {
pr_err("%s: Unable to create enable_attr\n", __func__);
goto abort_enable_attr_create_failed;
}
ret = sysfs_create_file(
persistent_stats.persistent_stats_kobj,
&reset_attr.attr);
if (ret) {
pr_err("%s: Unable to create reset_attr\n", __func__);
goto abort_reset_attr_create_failed;
}
/* Create kobjects and add them to persistent_stats_kobj */
for_each_possible_cpu(cpu_id) {
memset(&cpu_time_in_state_attr[cpu_id],
0, sizeof(struct attribute));
cpu_stats = &per_cpu(pcpu_stats, cpu_id);
snprintf(cpu_stats->name, MAX_CPUATTR_NAME_LEN,
"cpu%u", cpu_id);
cpu_time_in_state_attr[cpu_id].name =
time_in_state_attr_name;
cpu_time_in_state_attr[cpu_id].mode = S_IRUGO;
cpu_stats->cpu_id = cpu_id;
cpu_stats->cpu_persistent_stat_kobj.ktype =
kzalloc(sizeof(struct kobj_type), GFP_KERNEL);
if (!cpu_stats->cpu_persistent_stat_kobj.ktype) {
ret = -ENOMEM;
goto abort_cpu_persistent_stats_create_failed;
}
ret = kobject_init_and_add(
&cpu_stats->cpu_persistent_stat_kobj,
cpu_stats->cpu_persistent_stat_kobj.ktype,
persistent_stats.persistent_stats_kobj,
cpu_stats->name);
if (ret) {
pr_err("%s: Failed to create persistent stats node for cpu%u\n",
__func__, cpu_id);
goto abort_cpu_persistent_stats_create_failed;
}
cpu_stats->cpu_persistent_stat_kobj.ktype->sysfs_ops =
&cpu_time_in_state_ops;
ret = sysfs_create_file(
&cpu_stats->cpu_persistent_stat_kobj,
&cpu_time_in_state_attr[cpu_id]);
if (ret) {
pr_err("%s: sys_create_file failed.\n", __func__);
goto abort_cpu_persistent_stats_create_failed;
}
}
return 0;
abort_cpu_persistent_stats_create_failed:
/* Remove all CPU entries and the root persistent_stats entry */
for_each_possible_cpu(cpu_id) {
cpu_stats = &per_cpu(pcpu_stats, cpu_id);
sysfs_remove_file(
&cpu_stats->cpu_persistent_stat_kobj,
&cpu_time_in_state_attr[cpu_id]);
kfree(cpu_stats->cpu_persistent_stat_kobj.ktype);
kobject_put(&cpu_stats->cpu_persistent_stat_kobj);
}
abort_reset_attr_create_failed:
sysfs_remove_file(persistent_stats.persistent_stats_kobj,
&enable_attr.attr);
abort_enable_attr_create_failed:
kobject_put(persistent_stats.persistent_stats_kobj);
abort_stats_kobj_create_failed:
cpufreq_put_global_kobject();
return ret;
}
/*
* Remove the persistent stats groups created at this driver's init time.
*/
static void remove_persistent_stats_groups(void)
{
int cpu_id = 0;
struct cpu_persistent_stats *cpu_stats;
for (cpu_id = 0; cpu_id < num_possible_cpus(); cpu_id++) {
cpu_stats = &per_cpu(pcpu_stats, cpu_id);
sysfs_remove_file(
&cpu_stats->cpu_persistent_stat_kobj,
&cpu_time_in_state_attr[cpu_id]);
kfree(cpu_stats->cpu_persistent_stat_kobj.ktype);
kobject_put(&cpu_stats->cpu_persistent_stat_kobj);
}
/* Remove the root persistent stats kobject. */
kobject_put(persistent_stats.persistent_stats_kobj);
cpufreq_put_global_kobject();
}
static struct notifier_block cpufreq_stat_cpu_notifier __refdata = {
.notifier_call = cpufreq_stat_cpu_callback,
};
static struct notifier_block notifier_trans_block = {
.notifier_call = cpufreq_stat_notifier_trans
};
static int __init cpufreq_persistent_stats_init(void)
{
int ret = 0;
spin_lock_init(&cpufreq_stats_lock);
ret = cpufreq_register_notifier(&notifier_trans_block,
CPUFREQ_TRANSITION_NOTIFIER);
if (ret)
return ret;
ret = register_hotcpu_notifier(&cpufreq_stat_cpu_notifier);
if (ret)
goto abort_register_hotcpu_failed;
ret = create_persistent_stats_groups();
if (ret)
goto abort_create_stats_group_failed;
return 0;
abort_create_stats_group_failed:
unregister_hotcpu_notifier(&cpufreq_stat_cpu_notifier);
abort_register_hotcpu_failed:
cpufreq_unregister_notifier(&notifier_trans_block,
CPUFREQ_TRANSITION_NOTIFIER);
return ret;
}
static void __exit cpufreq_persistent_stats_exit(void)
{
unsigned int cpu;
cpufreq_unregister_notifier(&notifier_trans_block,
CPUFREQ_TRANSITION_NOTIFIER);
unregister_hotcpu_notifier(&cpufreq_stat_cpu_notifier);
for_each_possible_cpu(cpu)
cpufreq_stats_free_table(cpu);
remove_persistent_stats_groups();
}
MODULE_DESCRIPTION("Persists CPUFreq stats across CPU hotplugs.");
MODULE_LICENSE("GPL");
module_init(cpufreq_persistent_stats_init);
module_exit(cpufreq_persistent_stats_exit);