blob: fb9912b86e5e6e622019196ce3cfefd9ac91ad21 [file] [log] [blame]
/*
* Copyright (c) 2012 NVIDIA CORPORATION. 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 as published by
* the Free Software Foundation; version 2 of the License.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/cpuquiet.h>
#include <linux/cpu.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <asm/cputime.h>
#include "cpuquiet.h"
static struct cpuquiet_driver *cpuquiet_curr_driver;
#ifdef CONFIG_CPUQUIET_STATS
struct cpuquiet_cpu_stat {
cputime64_t time_up_total;
u64 last_update;
unsigned int up_down_count;
struct kobject cpu_kobject;
};
struct cpuquiet_cpu_stat *stats;
struct cpu_attribute {
struct attribute attr;
enum { up_down_count, time_up_total } type;
};
#define CPU_ATTRIBUTE(_name) \
static struct cpu_attribute _name ## _attr = { \
.attr = {.name = __stringify(_name), .mode = 0444 }, \
.type = _name, \
}
CPU_ATTRIBUTE(up_down_count);
CPU_ATTRIBUTE(time_up_total);
static struct attribute *cpu_attributes[] = {
&up_down_count_attr.attr,
&time_up_total_attr.attr,
NULL,
};
static void stats_update(struct cpuquiet_cpu_stat *stat, bool up)
{
u64 cur_jiffies = get_jiffies_64();
bool was_up = stat->up_down_count & 0x1;
if (was_up)
stat->time_up_total += cur_jiffies - stat->last_update;
if (was_up != up)
stat->up_down_count++;
stat->last_update = cur_jiffies;
}
static ssize_t stats_sysfs_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct cpu_attribute *cattr =
container_of(attr, struct cpu_attribute, attr);
struct cpuquiet_cpu_stat *stat =
container_of(kobj, struct cpuquiet_cpu_stat, cpu_kobject);
ssize_t len = 0;
bool was_up = stat->up_down_count & 0x1;
stats_update(stat, was_up);
switch (cattr->type) {
case up_down_count:
len = sprintf(buf, "%u\n", stat->up_down_count);
break;
case time_up_total:
len = sprintf(buf, "%llu\n", stat->time_up_total);
break;
}
return len;
}
static const struct sysfs_ops stats_sysfs_ops = {
.show = stats_sysfs_show,
};
static struct kobj_type ktype_cpu_stats = {
.sysfs_ops = &stats_sysfs_ops,
.default_attrs = cpu_attributes,
};
#endif
int cpuquiet_quiesence_cpu(unsigned int cpunumber, bool sync)
{
int err = -EPERM;
if (cpuquiet_curr_driver && cpuquiet_curr_driver->quiesence_cpu)
err = cpuquiet_curr_driver->quiesence_cpu(cpunumber, sync);
#ifdef CONFIG_CPUQUIET_STATS
if (!err)
stats_update(stats + cpunumber, 0);
#endif
return err;
}
EXPORT_SYMBOL(cpuquiet_quiesence_cpu);
int cpuquiet_wake_cpu(unsigned int cpunumber, bool sync)
{
int err = -EPERM;
if (cpuquiet_curr_driver && cpuquiet_curr_driver->wake_cpu)
err = cpuquiet_curr_driver->wake_cpu(cpunumber, sync);
#ifdef CONFIG_CPUQUIET_STATS
if (!err)
stats_update(stats + cpunumber, 1);
#endif
return err;
}
EXPORT_SYMBOL(cpuquiet_wake_cpu);
int cpuquiet_register_driver(struct cpuquiet_driver *drv)
{
int err = -EBUSY;
unsigned int cpu;
struct device *dev;
if (!drv)
return -EINVAL;
#ifdef CONFIG_CPUQUIET_STATS
stats = kzalloc(nr_cpu_ids * sizeof(*stats), GFP_KERNEL);
if (!stats)
return -ENOMEM;
#endif
for_each_possible_cpu(cpu) {
#ifdef CONFIG_CPUQUIET_STATS
u64 cur_jiffies = get_jiffies_64();
stats[cpu].last_update = cur_jiffies;
if (cpu_online(cpu))
stats[cpu].up_down_count = 1;
#endif
dev = get_cpu_device(cpu);
if (dev) {
cpuquiet_add_dev(dev, cpu);
#ifdef CONFIG_CPUQUIET_STATS
cpuquiet_cpu_kobject_init(&stats[cpu].cpu_kobject,
&ktype_cpu_stats, "stats", cpu);
#endif
}
}
mutex_lock(&cpuquiet_lock);
if (!cpuquiet_curr_driver) {
err = 0;
cpuquiet_curr_driver = drv;
cpuquiet_switch_governor(cpuquiet_get_first_governor());
}
mutex_unlock(&cpuquiet_lock);
return err;
}
EXPORT_SYMBOL(cpuquiet_register_driver);
struct cpuquiet_driver *cpuquiet_get_driver(void)
{
return cpuquiet_curr_driver;
}
void cpuquiet_unregister_driver(struct cpuquiet_driver *drv)
{
unsigned int cpu;
if (drv != cpuquiet_curr_driver) {
WARN(1, "invalid cpuquiet_unregister_driver(%s)\n",
drv->name);
return;
}
/* stop current governor first */
cpuquiet_switch_governor(NULL);
mutex_lock(&cpuquiet_lock);
cpuquiet_curr_driver = NULL;
for_each_possible_cpu(cpu) {
#ifdef CONFIG_CPUQUIET_STATS
kobject_put(&stats[cpu].cpu_kobject);
#endif
cpuquiet_remove_dev(cpu);
}
mutex_unlock(&cpuquiet_lock);
}
EXPORT_SYMBOL(cpuquiet_unregister_driver);