/* * Copyright (c) 2012-2013 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 #include #include #include #include #include #include #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; } mutex_lock(&cpuquiet_lock); /* Stop current governor first */ cpuquiet_switch_governor(NULL); 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);