mirror of
https://github.com/team-infusion-developers/android_kernel_samsung_msm8976.git
synced 2024-09-21 11:53:01 +00:00
87fb7c5002
The cpufreq_stats_create_table function does not fully protect cpu_stats' members with a spinlock. This change fixes that. It also performs a null-check on freq_table in freq_table_get_index and returns -ENOENT appropriately. Change-Id: I301d1b23ef766a77161bab1472dc810d53b6c5fe Signed-off-by: Anurag Singh <anursing@codeaurora.org>
595 lines
14 KiB
C
595 lines
14 KiB
C
/* 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(¬ifier_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(¬ifier_trans_block,
|
|
CPUFREQ_TRANSITION_NOTIFIER);
|
|
return ret;
|
|
}
|
|
|
|
static void __exit cpufreq_persistent_stats_exit(void)
|
|
{
|
|
unsigned int cpu;
|
|
|
|
cpufreq_unregister_notifier(¬ifier_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);
|