Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

95
drivers/cpuquiet/Kconfig Normal file
View File

@@ -0,0 +1,95 @@
menu "CPUQuiet Framework"
config CPU_QUIET
bool "CPUQuiet Framework"
help
Cpuquiet implements pluggable policies for forcing cpu cores into a
quiescent state. Appropriate policies will save power without hurting
performance.
if CPU_QUIET
config CPU_QUIET_STATS
bool "per CPU statistics"
depends on SYSFS
default n
help
Enable up/down count and total time plugged statistics per CPU. These
depend on correct driver input for reliability. The statistic value
can be checked under "/sys/devices/system/cpu/cpux/cpuquiet/stats".
If in doubt say N.
config CPU_QUIET_GOVERNOR_USERSPACE
bool "'userspace' governor"
default y
help
Manual control of the number of CPUs online.
This governor allows userspace to control the number of online CPUs.
That means this governor allow user to implement the real governor
in user space.
If in doubt say Y.
config CPU_QUIET_GOVERNOR_RUNNABLE
bool "'runnable threads' governor"
default y
help
Scale the number of CPUs online depending on the number of runnable
threads. This governor will scale the number of CPUs online depending
on the number of runnable threads.
If in doubt say Y.
choice
prompt "Default CPUQuiet governor"
default CPU_QUIET_DEFAULT_GOV_USERSPACE
help
This option sets which CPUQuiet governor shall be loaded at
startup. If in doubt, select 'userspace'.
config CPU_QUIET_DEFAULT_GOV_USERSPACE
bool "userspace"
select CPU_QUIET_GOVERNOR_USERSPACE
help
Use the CPUQuiet governor 'userspace' as default.
config CPU_QUIET_DEFAULT_GOV_RUNNABLE
bool "runnable threads"
select CPU_QUIET_GOVERNOR_RUNNABLE
help
Use the CPUQuiet governor 'runnable threads' as default.
endchoice
menu "Tegra CPUquiet Driver"
depends on ARCH_TEGRA
config TEGRA_CPUQUIET
bool "Tegra SoCs"
depends on HOTPLUG_CPU && ARM
default n
help
This is the CPUquiet driver for NVIDIA Tegra SoCs. It supports
auto hotplug the secondary CPU cores by the CPUquiet governor.
And the auto clusterswitch would be supported in this driver
too.
If unsure, say N.
endmenu
menu "X86 CPUquiet Driver"
depends on X86
config X86_CPUQUIET
bool "X86"
depends on HOTPLUG_CPU && X86
default n
help
This is the CPUquiet driver for X86 CPUs and SoCs. It supports
auto hotplug of CPU cores using the CPUquiet governor.
If unsure, say N.
endmenu
endif
endmenu

View File

@@ -0,0 +1,6 @@
GCOV_PROFILE := y
obj-$(CONFIG_CPU_QUIET) += driver.o governor.o governors/
obj-$(CONFIG_CPU_QUIET_STATS) += sysfs.o attribute.o
obj-$(CONFIG_TEGRA_CPUQUIET) += cpuquiet-common.o cpuquiet-tegra.o
obj-$(CONFIG_X86_CPUQUIET) += cpuquiet-common.o cpuquiet-x86.o

View File

@@ -0,0 +1,99 @@
/*
* 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/cpuquiet.h>
ssize_t store_int_attribute(struct cpuquiet_attribute *cattr, const char *buf,
size_t count)
{
int err, val;
err = kstrtoint(buf, 0, &val);
if (err < 0)
return err;
*((int *)(cattr->param)) = val;
return count;
}
ssize_t show_int_attribute(struct cpuquiet_attribute *cattr, char *buf)
{
return sprintf(buf, "%d\n", *((int *)cattr->param));
}
ssize_t store_bool_attribute(struct cpuquiet_attribute *cattr, const char *buf,
size_t count)
{
int err, val;
err = kstrtoint(buf, 0, &val);
if (err < 0)
return err;
if (val < 0 || val > 1)
return -EINVAL;
*((bool *)(cattr->param)) = val;
return count;
}
ssize_t show_bool_attribute(struct cpuquiet_attribute *cattr, char *buf)
{
return sprintf(buf, "%d\n", *((bool *)cattr->param));
}
ssize_t store_uint_attribute(struct cpuquiet_attribute *cattr, const char *buf,
size_t count)
{
int err;
unsigned int val;
err = kstrtouint(buf, 0, &val);
if (err < 0)
return err;
*((unsigned int *)(cattr->param)) = val;
return count;
}
ssize_t show_uint_attribute(struct cpuquiet_attribute *cattr, char *buf)
{
return sprintf(buf, "%u\n", *((unsigned int *)cattr->param));
}
ssize_t store_ulong_attribute(struct cpuquiet_attribute *cattr, const char *buf,
size_t count)
{
int err;
unsigned long val;
err = kstrtoul(buf, 0, &val);
if (err < 0)
return err;
*((unsigned long *)(cattr->param)) = val;
return count;
}
ssize_t show_ulong_attribute(struct cpuquiet_attribute *cattr, char *buf)
{
return sprintf(buf, "%lu\n", *((unsigned long *)cattr->param));
}

View File

@@ -0,0 +1,240 @@
/*
* Derived from drivers/cpuquiet/cpuquiet-tegra.c
*
* 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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/cpu.h>
#include <linux/cpuquiet.h>
#include <linux/pm_qos.h>
#include <linux/sched.h>
#define DEFAULT_HOTPLUG_DELAY_MS 100
static DEFINE_MUTEX(cpuquiet_cpu_lock);
static struct workqueue_struct *cpuquiet_wq;
static struct work_struct cpuquiet_work;
static wait_queue_head_t wait_cpu;
static unsigned long hotplug_timeout;
static struct cpumask cr_online_requests;
static struct cpumask cr_offline_requests;
static void cpuquiet_work_func(struct work_struct *work);
static int update_core_config(unsigned int cpunumber, bool up)
{
int ret = 0;
mutex_lock(&cpuquiet_cpu_lock);
if (up) {
cpumask_set_cpu(cpunumber, &cr_online_requests);
cpumask_clear_cpu(cpunumber, &cr_offline_requests);
queue_work(cpuquiet_wq, &cpuquiet_work);
} else {
cpumask_set_cpu(cpunumber, &cr_offline_requests);
cpumask_clear_cpu(cpunumber, &cr_online_requests);
queue_work(cpuquiet_wq, &cpuquiet_work);
}
mutex_unlock(&cpuquiet_cpu_lock);
return ret;
}
int cpuquiet_cpu_down(unsigned int cpunumber, bool sync)
{
unsigned long timeout = msecs_to_jiffies(hotplug_timeout);
int err = 0;
err = update_core_config(cpunumber, false);
if (err || !sync)
return err;
err = wait_event_interruptible_timeout(wait_cpu, !cpu_online(cpunumber),
timeout);
if (err < 0)
return err;
if (err > 0)
return 0;
else
return -ETIMEDOUT;
}
EXPORT_SYMBOL(cpuquiet_cpu_down);
int cpuquiet_cpu_up(unsigned int cpunumber, bool sync)
{
unsigned long timeout = msecs_to_jiffies(hotplug_timeout);
int err = 0;
err = update_core_config(cpunumber, true);
if (err || !sync)
return err;
err = wait_event_interruptible_timeout(wait_cpu, cpu_online(cpunumber),
timeout);
if (err < 0)
return err;
if (err > 0)
return 0;
else
return -ETIMEDOUT;
}
EXPORT_SYMBOL(cpuquiet_cpu_up);
static void __cpuinit cpuquiet_work_func(struct work_struct *work)
{
int count = -1;
unsigned int cpu;
int nr_cpus;
struct cpumask online, offline, cpu_online;
int max_cpus = pm_qos_request(PM_QOS_MAX_ONLINE_CPUS) ? :
num_present_cpus();
int min_cpus = pm_qos_request(PM_QOS_MIN_ONLINE_CPUS);
mutex_lock(&cpuquiet_cpu_lock);
online = cr_online_requests;
offline = cr_offline_requests;
mutex_unlock(&cpuquiet_cpu_lock);
/* always keep CPU0 online */
cpumask_set_cpu(0, &online);
cpu_online = *cpu_online_mask;
if (max_cpus < min_cpus)
max_cpus = min_cpus;
nr_cpus = cpumask_weight(&online);
if (nr_cpus < min_cpus) {
cpu = 0;
count = min_cpus - nr_cpus;
for (; count > 0; count--) {
cpu = cpumask_next_zero(cpu, &online);
cpumask_set_cpu(cpu, &online);
cpumask_clear_cpu(cpu, &offline);
}
} else if (nr_cpus > max_cpus) {
count = nr_cpus - max_cpus;
cpu = 1;
for (; count > 0; count--) {
/* CPU0 should always be online */
cpu = cpumask_next(cpu, &online);
cpumask_set_cpu(cpu, &offline);
cpumask_clear_cpu(cpu, &online);
}
}
cpumask_andnot(&online, &online, &cpu_online);
for_each_cpu(cpu, &online)
cpu_up(cpu);
cpumask_and(&offline, &offline, &cpu_online);
for_each_cpu(cpu, &offline)
cpu_down(cpu);
wake_up_interruptible(&wait_cpu);
}
static int minmax_cpus_notify(struct notifier_block *nb, unsigned long n,
void *p)
{
mutex_lock(&cpuquiet_cpu_lock);
queue_work(cpuquiet_wq, &cpuquiet_work);
mutex_unlock(&cpuquiet_cpu_lock);
return NOTIFY_OK;
}
static struct notifier_block minmax_cpus_notifier = {
.notifier_call = minmax_cpus_notify,
};
#ifdef CONFIG_CPU_QUIET_STATS
CPQ_SIMPLE_ATTRIBUTE(hotplug_timeout, 0644, ulong);
static struct attribute *cpuquiet_attrs[] = {
&hotplug_timeout_attr.attr,
NULL,
};
static struct attribute_group cpuquiet_attrs_group = {
.attrs = cpuquiet_attrs,
};
#endif /* CONFIG_CPU_QUIET_STATS */
int cpuquiet_probe_common(struct platform_device *pdev)
{
init_waitqueue_head(&wait_cpu);
/*
* Not bound to the issuer CPU (=> high-priority), has rescue worker
* task, single-threaded, freezable.
*/
cpuquiet_wq = alloc_workqueue(
"cpuquiet", WQ_NON_REENTRANT | WQ_FREEZABLE, 1);
if (!cpuquiet_wq)
return -ENOMEM;
INIT_WORK(&cpuquiet_work, cpuquiet_work_func);
hotplug_timeout = DEFAULT_HOTPLUG_DELAY_MS;
cpumask_clear(&cr_online_requests);
cpumask_clear(&cr_offline_requests);
if (pm_qos_add_notifier(PM_QOS_MIN_ONLINE_CPUS, &minmax_cpus_notifier))
pr_err("Failed to register min cpus PM QoS notifier\n");
if (pm_qos_add_notifier(PM_QOS_MAX_ONLINE_CPUS, &minmax_cpus_notifier))
pr_err("Failed to register max cpus PM QoS notifier\n");
return 0;
}
EXPORT_SYMBOL(cpuquiet_probe_common);
#ifdef CONFIG_CPU_QUIET_STATS
int cpuquiet_probe_common_post(struct platform_device *pdev)
{
int err;
err = cpuquiet_register_attrs(&cpuquiet_attrs_group);
if (err)
cpuquiet_remove_common(pdev);
return err;
}
EXPORT_SYMBOL(cpuquiet_probe_common_post);
#endif
int cpuquiet_remove_common(struct platform_device *pdev)
{
destroy_workqueue(cpuquiet_wq);
#ifdef CONFIG_CPU_QUIET_STATS
cpuquiet_unregister_attrs(&cpuquiet_attrs_group);
#endif
return 0;
}
EXPORT_SYMBOL(cpuquiet_remove_common);

View File

@@ -0,0 +1,88 @@
/*
* arch/arm/mach-tegra/cpuquiet.c
*
* Cpuquiet driver for Tegra CPUs
*
* 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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/cpu.h>
#include <linux/cpuquiet.h>
#include <linux/pm_qos.h>
#include <linux/sched.h>
#define TEGRA_AVG_HOTPLUG_LATENCY_MS 2
static struct cpuquiet_driver tegra_cpuquiet_driver = {
.name = "tegra",
.quiesce_cpu = cpuquiet_cpu_down,
.wake_cpu = cpuquiet_cpu_up,
.avg_hotplug_latency_ms = TEGRA_AVG_HOTPLUG_LATENCY_MS,
};
static int __init tegra_cpuquiet_probe(struct platform_device *pdev)
{
int err;
err = cpuquiet_probe_common(pdev);
if (err)
return err;
err = cpuquiet_register_driver(&tegra_cpuquiet_driver);
if (err) {
cpuquiet_remove_common(pdev);
return err;
}
#ifdef CONFIG_CPU_QUIET_STATS
err = cpuquiet_probe_common_post(pdev);
if (err)
return err;
#endif
return err;
}
static int tegra_cpuquiet_remove(struct platform_device *pdev)
{
cpuquiet_remove_common(pdev);
cpuquiet_unregister_driver(&tegra_cpuquiet_driver);
return 0;
}
static struct platform_driver tegra_cpuquiet_platdrv __refdata = {
.driver = {
.name = "tegra-cpuquiet",
.owner = THIS_MODULE,
},
.probe = tegra_cpuquiet_probe,
.remove = tegra_cpuquiet_remove,
};
module_platform_driver(tegra_cpuquiet_platdrv);
int __init tegra_cpuquiet_init(void)
{
struct platform_device_info devinfo = { .name = "tegra-cpuquiet", };
platform_device_register_full(&devinfo);
return 0;
}
EXPORT_SYMBOL(tegra_cpuquiet_init);

View File

@@ -0,0 +1,87 @@
/*
* Cpuquiet driver for X86 CPUs
*
* Derived from drivers/cpuquiet/cpuquiet-tegra.c
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/cpu.h>
#include <linux/cpuquiet.h>
#include <linux/pm_qos.h>
#include <linux/sched.h>
#define X86_AVG_HOTPLUG_LATENCY_MS 20
static struct cpuquiet_driver x86_cpuquiet_driver = {
.name = "x86",
.quiesce_cpu = cpuquiet_cpu_down,
.wake_cpu = cpuquiet_cpu_up,
.avg_hotplug_latency_ms = X86_AVG_HOTPLUG_LATENCY_MS,
};
static int __init x86_cpuquiet_probe(struct platform_device *pdev)
{
int err;
err = cpuquiet_probe_common(pdev);
if (err)
return err;
err = cpuquiet_register_driver(&x86_cpuquiet_driver);
if (err) {
cpuquiet_remove_common(pdev);
return err;
}
#ifdef CONFIG_CPU_QUIET_STATS
err = cpuquiet_probe_common_post(pdev);
if (err)
return err;
#endif
return err;
}
static int x86_cpuquiet_remove(struct platform_device *pdev)
{
cpuquiet_remove_common(pdev);
cpuquiet_unregister_driver(&x86_cpuquiet_driver);
return 0;
}
static struct platform_driver x86_cpuquiet_platdrv __refdata = {
.driver = {
.name = "x86-cpuquiet",
.owner = THIS_MODULE,
},
.probe = x86_cpuquiet_probe,
.remove = x86_cpuquiet_remove,
};
module_platform_driver(x86_cpuquiet_platdrv);
int __init x86_cpuquiet_init(void)
{
struct platform_device_info devinfo = { .name = "x86-cpuquiet", };
platform_device_register_full(&devinfo);
return 0;
}
EXPORT_SYMBOL(x86_cpuquiet_init);
late_initcall(x86_cpuquiet_init);

View File

@@ -0,0 +1,57 @@
/*
* 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.
*
*/
#ifndef __DRIVER_CPUQUIET_H
#define __DRIVER_CPUQUIET_H
#include <linux/device.h>
extern struct mutex cpuquiet_lock;
extern struct cpuquiet_governor *cpuquiet_curr_governor;
extern struct list_head cpuquiet_governors;
extern struct cpuquiet_governor *cpuquiet_find_governor(const char *str);
extern int cpuquiet_switch_governor(struct cpuquiet_governor *gov);
extern struct cpuquiet_governor *cpuquiet_get_first_governor(void);
extern struct cpuquiet_driver *cpuquiet_get_driver(void);
#ifdef CONFIG_CPU_QUIET_STATS
extern int cpuquiet_sysfs_init(void);
extern void cpuquiet_sysfs_exit(void);
extern int cpuquiet_add_dev(struct device *dev, unsigned int cpu);
extern void cpuquiet_remove_dev(unsigned int cpu);
#else
static inline int cpuquiet_sysfs_init(void)
{
return 0;
}
static inline void cpuquiet_sysfs_exit(void)
{
}
static inline int cpuquiet_add_dev(struct device *dev, unsigned int cpu)
{
return 0;
}
static inline void cpuquiet_remove_dev(unsigned int cpu)
{
}
#endif
#endif

315
drivers/cpuquiet/driver.c Normal file
View File

@@ -0,0 +1,315 @@
/*
* 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 <linux/hrtimer.h>
#include <asm/cputime.h>
#include "cpuquiet.h"
DEFINE_MUTEX(cpuquiet_lock);
static struct cpuquiet_driver *cpuquiet_curr_driver;
#ifdef CONFIG_CPU_QUIET_STATS
struct cpuquiet_cpu_stat {
u64 time_up;
u64 time_down;
u64 last_update;
u64 hotplug_up_overhead_us;
u64 hotplug_down_overhead_us;
unsigned int transitions;
bool up;
};
static DEFINE_SPINLOCK(stats_lock);
static struct cpuquiet_cpu_stat *stats;
static void __stats_update(unsigned int cpu, bool up, u64 trans_overhead_us)
{
struct cpuquiet_cpu_stat *stat = &stats[cpu];
u64 cur_jiffies = get_jiffies_64();
if (stat->up)
stat->time_up += cur_jiffies - stat->last_update;
else
stat->time_down += cur_jiffies - stat->last_update;
if (stat->up != up) {
stat->transitions++;
stat->up = up;
if (up)
stat->hotplug_up_overhead_us += trans_overhead_us;
else
stat->hotplug_down_overhead_us += trans_overhead_us;
}
stat->last_update = cur_jiffies;
}
static ssize_t show_transitions(unsigned int cpu, char *buf)
{
struct cpuquiet_cpu_stat *stat = &stats[cpu];
return sprintf(buf, "%u\n", stat->transitions);
}
static ssize_t show_hp_up(unsigned int cpu, char *buf)
{
struct cpuquiet_cpu_stat *stat = &stats[cpu];
return sprintf(buf, "%llu\n", stat->hotplug_up_overhead_us);
}
static ssize_t show_hp_down(unsigned int cpu, char *buf)
{
struct cpuquiet_cpu_stat *stat = &stats[cpu];
return sprintf(buf, "%llu\n", stat->hotplug_down_overhead_us);
}
static ssize_t show_overhead_us_in_state(unsigned int cpu, char *buf)
{
struct cpuquiet_cpu_stat *stat = &stats[cpu];
u64 up, down;
ssize_t len = 0;
unsigned long flags;
spin_lock_irqsave(&stats_lock, flags);
__stats_update(cpu, stat->up, 0);
up = stat->time_up;
down = stat->time_down;
spin_unlock_irqrestore(&stats_lock, flags);
len = sprintf(buf, "up %llu\ndown %llu\n", stat->time_up,
stat->time_down);
return len;
}
CPQ_CPU_ATTRIBUTE(transitions, 0444, show_transitions, NULL);
CPQ_CPU_ATTRIBUTE(time_in_state, 0444, show_overhead_us_in_state, NULL);
CPQ_CPU_ATTRIBUTE(hotplug_up_us, 0444, show_hp_up, NULL);
CPQ_CPU_ATTRIBUTE(hotplug_down_us, 0444, show_hp_down, NULL);
static struct attribute *stats_attrs[] = {
&transitions_attr.attr,
&time_in_state_attr.attr,
&hotplug_up_us_attr.attr,
&hotplug_down_us_attr.attr,
NULL,
};
static struct attribute_group stats_group = {
.name = "stats",
.attrs = stats_attrs,
};
static int cpuquiet_stats_init(void)
{
unsigned int cpu;
int ret = 0;
stats = kzalloc(sizeof(*stats) * CONFIG_NR_CPUS, GFP_KERNEL);
if (!stats)
return -ENOMEM;
for_each_possible_cpu(cpu) {
if (cpu_online(cpu)) {
stats[cpu].last_update = get_jiffies_64();
stats[cpu].up = true;
stats[cpu].hotplug_up_overhead_us = 0;
stats[cpu].hotplug_down_overhead_us = 0;
}
}
ret = cpuquiet_register_cpu_attrs(&stats_group);
if (ret)
kfree(stats);
return ret;
}
static void cpuquiet_stats_exit(void)
{
cpuquiet_unregister_cpu_attrs(&stats_group);
kfree(stats);
}
static void stats_update(unsigned int cpu, bool up, u64 trans_overhead_us)
{
unsigned long flags;
spin_lock_irqsave(&stats_lock, flags);
__stats_update(cpu, up, trans_overhead_us);
spin_unlock_irqrestore(&stats_lock, flags);
}
#else
static inline int cpuquiet_stats_init(void)
{
return 0;
}
static inline void cpuquiet_stats_exit(void)
{
}
static inline void stats_update(unsigned int cpu, bool up,
u64 trans_overhead_us)
{
}
#endif
int cpuquiet_quiesce_cpu(unsigned int cpunumber, bool sync)
{
int err = -EPERM;
ktime_t before, after;
u64 delta;
mutex_lock(&cpuquiet_lock);
if (cpuquiet_curr_driver && cpuquiet_curr_driver->quiesce_cpu) {
/*
* If sync is false, we will not be collecting hotplug overhead
* and this value should be ignored.
*/
before = ktime_get();
err = cpuquiet_curr_driver->quiesce_cpu(cpunumber, sync);
after = ktime_get();
delta = (u64) ktime_to_us(ktime_sub(after, before));
}
mutex_unlock(&cpuquiet_lock);
if (!err)
stats_update(cpunumber, false, delta);
return err;
}
EXPORT_SYMBOL(cpuquiet_quiesce_cpu);
int cpuquiet_wake_cpu(unsigned int cpunumber, bool sync)
{
int err = -EPERM;
ktime_t before, after;
u64 delta;
mutex_lock(&cpuquiet_lock);
if (cpuquiet_curr_driver && cpuquiet_curr_driver->wake_cpu) {
/*
* If sync is false, we will not be collecting hotplug overhead
* and this value should be ignored.
*/
before = ktime_get();
err = cpuquiet_curr_driver->wake_cpu(cpunumber, sync);
after = ktime_get();
delta = (u64) ktime_to_us(ktime_sub(after, before));
}
mutex_unlock(&cpuquiet_lock);
if (!err)
stats_update(cpunumber, true, delta);
return err;
}
EXPORT_SYMBOL(cpuquiet_wake_cpu);
struct cpuquiet_driver *cpuquiet_get_driver(void)
{
return cpuquiet_curr_driver;
}
int cpuquiet_get_avg_hotplug_latency(void)
{
if (cpuquiet_curr_driver)
return cpuquiet_curr_driver->avg_hotplug_latency_ms;
return 0;
}
int cpuquiet_register_driver(struct cpuquiet_driver *drv)
{
int err = 0;
unsigned int cpu;
struct device *dev;
if (!drv)
return -EINVAL;
mutex_lock(&cpuquiet_lock);
if (cpuquiet_curr_driver) {
err = -EBUSY;
goto out_busy;
}
err = cpuquiet_sysfs_init();
if (err)
goto out_busy;
for_each_possible_cpu(cpu) {
dev = get_cpu_device(cpu);
if (dev) {
err = cpuquiet_add_dev(dev, cpu);
if (err)
goto out_add_dev;
}
}
err = cpuquiet_stats_init();
if (err)
goto out_add_dev;
cpuquiet_curr_driver = drv;
cpuquiet_switch_governor(cpuquiet_get_first_governor());
mutex_unlock(&cpuquiet_lock);
return 0;
out_add_dev:
for_each_possible_cpu(cpu)
cpuquiet_remove_dev(cpu);
out_busy:
mutex_unlock(&cpuquiet_lock);
return err;
}
EXPORT_SYMBOL(cpuquiet_register_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)
cpuquiet_remove_dev(cpu);
cpuquiet_stats_exit();
cpuquiet_sysfs_exit();
mutex_unlock(&cpuquiet_lock);
}
EXPORT_SYMBOL(cpuquiet_unregister_driver);

104
drivers/cpuquiet/governor.c Normal file
View File

@@ -0,0 +1,104 @@
/*
* 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 "cpuquiet.h"
LIST_HEAD(cpuquiet_governors);
struct cpuquiet_governor *cpuquiet_curr_governor;
struct cpuquiet_governor *cpuquiet_get_first_governor(void)
{
if (!list_empty(&cpuquiet_governors))
return list_entry(cpuquiet_governors.next,
struct cpuquiet_governor,
governor_list);
else
return NULL;
}
struct cpuquiet_governor *cpuquiet_find_governor(const char *str)
{
struct cpuquiet_governor *gov;
list_for_each_entry(gov, &cpuquiet_governors, governor_list)
if (!strnicmp(str, gov->name, CPUQUIET_NAME_LEN))
return gov;
return NULL;
}
int cpuquiet_switch_governor(struct cpuquiet_governor *gov)
{
int err = 0;
if (cpuquiet_curr_governor) {
if (cpuquiet_curr_governor->stop)
cpuquiet_curr_governor->stop();
module_put(cpuquiet_curr_governor->owner);
}
cpuquiet_curr_governor = gov;
if (gov) {
if (!try_module_get(cpuquiet_curr_governor->owner))
return -EINVAL;
if (gov->start)
err = gov->start();
if (!err)
cpuquiet_curr_governor = gov;
}
return err;
}
int cpuquiet_register_governor(struct cpuquiet_governor *gov)
{
int ret = -EEXIST;
if (!gov)
return -EINVAL;
mutex_lock(&cpuquiet_lock);
if (cpuquiet_find_governor(gov->name) == NULL) {
ret = 0;
list_add_tail(&gov->governor_list, &cpuquiet_governors);
if (!cpuquiet_curr_governor && cpuquiet_get_driver())
cpuquiet_switch_governor(gov);
}
mutex_unlock(&cpuquiet_lock);
return ret;
}
EXPORT_SYMBOL(cpuquiet_register_governor);
void cpuquiet_unregister_governor(struct cpuquiet_governor *gov)
{
if (!gov)
return;
mutex_lock(&cpuquiet_lock);
if (cpuquiet_curr_governor == gov)
cpuquiet_switch_governor(NULL);
list_del(&gov->governor_list);
mutex_unlock(&cpuquiet_lock);
}
EXPORT_SYMBOL(cpuquiet_unregister_governor);

View File

@@ -0,0 +1,4 @@
GCOV_PROFILE := y
obj-$(CONFIG_CPU_QUIET_GOVERNOR_USERSPACE) += userspace.o
obj-$(CONFIG_CPU_QUIET_GOVERNOR_RUNNABLE) += runnable_threads.o

View File

@@ -0,0 +1,305 @@
/*
* 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 <linux/kernel.h>
#include <linux/cpuquiet.h>
#include <linux/cpumask.h>
#include <linux/module.h>
#include <linux/pm_qos.h>
#include <linux/jiffies.h>
#include <linux/cpu.h>
#include <linux/sched.h>
static struct work_struct runnables_work;
static struct timer_list runnables_timer;
static bool runnables_enabled;
/* configurable parameters */
static unsigned int sample_rate = 200; /* msec */
#define NR_FSHIFT_EXP 3
#define NR_FSHIFT (1 << NR_FSHIFT_EXP)
/* avg run threads * 8 (e.g., 11 = 1.375 threads) */
static unsigned int default_thresholds[] = {
4, 18, 20, UINT_MAX
};
static unsigned int nr_run_last;
static unsigned int nr_run_hysteresis = 2; /* 1 / 2 thread */
static unsigned int default_threshold_level = 4; /* 1 / 4 thread */
static unsigned int nr_run_thresholds[NR_CPUS];
static DEFINE_MUTEX(runnables_lock);
struct runnables_avg_sample {
u64 previous_integral;
unsigned int avg;
bool integral_sampled;
u64 prev_timestamp;
};
static DEFINE_PER_CPU(struct runnables_avg_sample, avg_nr_sample);
/*
* EXP = alpha in the exponential moving average.
* Alpha = e ^ (-sample_rate / window_size) * FIXED_1
* Calculated for sample_rate of 20ms, window size of 100ms
*/
#define EXP 1677
static unsigned int get_avg_nr_runnables(void)
{
unsigned int i, sum = 0;
static unsigned int avg;
struct runnables_avg_sample *sample;
u64 integral, old_integral, delta_integral, delta_time, cur_time;
for_each_online_cpu(i) {
sample = &per_cpu(avg_nr_sample, i);
integral = nr_running_integral(i);
old_integral = sample->previous_integral;
sample->previous_integral = integral;
cur_time = ktime_to_ns(ktime_get());
delta_time = cur_time - sample->prev_timestamp;
sample->prev_timestamp = cur_time;
if (!sample->integral_sampled) {
sample->integral_sampled = true;
/*
* First sample to initialize prev_integral, skip
* avg calculation
*/
continue;
}
if (integral < old_integral) {
/* Overflow */
delta_integral = (ULLONG_MAX - old_integral) + integral;
} else {
delta_integral = integral - old_integral;
}
/* Calculate average for the previous sample window */
do_div(delta_integral, delta_time);
sample->avg = delta_integral;
sum += sample->avg;
}
/*
* Exponential moving average
* Avgn = Avgn-1 * alpha + new_avg * (1 - alpha)
*/
avg *= EXP;
avg += sum * (FIXED_1 - EXP);
avg >>= FSHIFT;
return avg;
}
static int get_action(unsigned int nr_run)
{
unsigned int nr_cpus = num_online_cpus();
int max_cpus = pm_qos_request(PM_QOS_MAX_ONLINE_CPUS) ? : 4;
int min_cpus = pm_qos_request(PM_QOS_MIN_ONLINE_CPUS);
if ((nr_cpus > max_cpus || nr_run < nr_cpus) && nr_cpus > min_cpus)
return -1;
if ((nr_cpus < min_cpus || nr_run > nr_cpus) && nr_cpus < max_cpus)
return 1;
return 0;
}
static void runnables_avg_sampler(unsigned long data)
{
unsigned int nr_run, avg_nr_run;
int action;
if (!runnables_enabled)
return;
avg_nr_run = get_avg_nr_runnables();
mod_timer(&runnables_timer, jiffies + msecs_to_jiffies(sample_rate));
for (nr_run = 1; nr_run < ARRAY_SIZE(nr_run_thresholds); nr_run++) {
unsigned int nr_threshold = nr_run_thresholds[nr_run - 1];
if (nr_run_last <= nr_run)
nr_threshold += NR_FSHIFT / nr_run_hysteresis;
if (avg_nr_run <= (nr_threshold << (FSHIFT - NR_FSHIFT_EXP)))
break;
}
nr_run_last = nr_run;
action = get_action(nr_run);
if (action != 0)
schedule_work(&runnables_work);
}
static unsigned int get_lightest_loaded_cpu(void)
{
unsigned long min_avg_runnables = ULONG_MAX;
unsigned int cpu = nr_cpu_ids;
int i;
for_each_online_cpu(i) {
struct runnables_avg_sample *s = &per_cpu(avg_nr_sample, i);
unsigned int nr_runnables = s->avg;
if (i > 0 && min_avg_runnables > nr_runnables) {
cpu = i;
min_avg_runnables = nr_runnables;
}
}
return cpu;
}
static void runnables_work_func(struct work_struct *work)
{
unsigned int cpu = nr_cpu_ids;
int action;
if (!runnables_enabled)
return;
action = get_action(nr_run_last);
if (action > 0)
cpu = cpumask_next_zero(0, cpu_online_mask);
else if (action < 0)
cpu = get_lightest_loaded_cpu();
if (cpu > nr_cpu_ids)
return;
if (action > 0)
cpuquiet_wake_cpu(cpu, false);
if (action < 0)
cpuquiet_quiesce_cpu(cpu, false);
}
#ifdef CONFIG_CPU_QUIET_STATS
CPQ_SIMPLE_ATTRIBUTE(sample_rate, 0644, uint);
CPQ_SIMPLE_ATTRIBUTE(nr_run_hysteresis, 0644, uint);
static struct attribute *runnables_attrs[] = {
&sample_rate_attr.attr,
&nr_run_hysteresis_attr.attr,
NULL,
};
static struct attribute_group runnables_group = {
.name = "runnable_threads",
.attrs = runnables_attrs,
};
static int runnables_sysfs_init(void)
{
return cpuquiet_register_attrs(&runnables_group);
}
static void runnables_sysfs_exit(void)
{
cpuquiet_unregister_attrs(&runnables_group);
}
#else
static inline int runnables_sysfs_init(void)
{
return 0;
}
static inline void runnables_sysfs_exit(void)
{
return;
}
#endif
static void runnables_stop(void)
{
mutex_lock(&runnables_lock);
runnables_enabled = false;
del_timer_sync(&runnables_timer);
cancel_work_sync(&runnables_work);
runnables_sysfs_exit();
mutex_unlock(&runnables_lock);
}
static int runnables_start(void)
{
int i, err, arch_specific_sample_rate;
err = runnables_sysfs_init();
if (err)
return err;
INIT_WORK(&runnables_work, runnables_work_func);
init_timer(&runnables_timer);
runnables_timer.function = runnables_avg_sampler;
arch_specific_sample_rate = cpuquiet_get_avg_hotplug_latency();
if (arch_specific_sample_rate)
/*
* Sample at least 10 times as slowly as overhead for one
* hotplug event.
*/
sample_rate = arch_specific_sample_rate * 10;
for (i = 0; i < ARRAY_SIZE(nr_run_thresholds); ++i) {
if (i < ARRAY_SIZE(default_thresholds))
nr_run_thresholds[i] = default_thresholds[i];
else if (i == (ARRAY_SIZE(nr_run_thresholds) - 1))
nr_run_thresholds[i] = UINT_MAX;
else
nr_run_thresholds[i] = i + 1 +
NR_FSHIFT / default_threshold_level;
}
runnables_enabled = true;
runnables_avg_sampler(0);
return 0;
}
static struct cpuquiet_governor runnables_governor = {
.name = "runnable",
.start = runnables_start,
.stop = runnables_stop,
.owner = THIS_MODULE,
};
static int __init init_runnables(void)
{
return cpuquiet_register_governor(&runnables_governor);
}
static void __exit exit_runnables(void)
{
cpuquiet_unregister_governor(&runnables_governor);
}
MODULE_LICENSE("GPL");
#ifdef CONFIG_CPU_QUIET_DEFAULT_GOV_RUNNABLE
fs_initcall(init_runnables);
#else
module_init(init_runnables);
#endif
module_exit(exit_runnables);

View File

@@ -0,0 +1,57 @@
/*
* 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>
static int governor_set(unsigned int cpu, bool active)
{
int err;
if (active)
err = cpuquiet_wake_cpu(cpu, true);
else
err = cpuquiet_quiesce_cpu(cpu, true);
return err;
}
static struct cpuquiet_governor userspace_governor = {
.name = "userspace",
.store_active = governor_set,
.owner = THIS_MODULE,
};
static int __init init_usermode(void)
{
return cpuquiet_register_governor(&userspace_governor);
}
static void __exit exit_usermode(void)
{
cpuquiet_unregister_governor(&userspace_governor);
}
MODULE_LICENSE("GPL");
#ifdef CONFIG_CPU_QUIET_DEFAULT_GOV_USERSPACE
fs_initcall(init_usermode);
#else
module_init(init_usermode);
#endif
module_exit(exit_usermode);

304
drivers/cpuquiet/sysfs.c Normal file
View File

@@ -0,0 +1,304 @@
/*
* 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/kernel.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/cpuquiet.h>
#include "cpuquiet.h"
struct cpuquiet_dev {
unsigned int cpu;
struct kobject kobj;
};
static struct kobject *cpuquiet_global_kobject;
static struct cpuquiet_dev *cpuquiet_cpu_devices[CONFIG_NR_CPUS];
static ssize_t show_current_governor(struct cpuquiet_attribute *cattr,
char *buf)
{
ssize_t ret;
mutex_lock(&cpuquiet_lock);
if (cpuquiet_curr_governor)
ret = sprintf(buf, "%s\n", cpuquiet_curr_governor->name);
else
ret = sprintf(buf, "none\n");
mutex_unlock(&cpuquiet_lock);
return ret;
}
static ssize_t store_current_governor(struct cpuquiet_attribute *cattr,
const char *buf, size_t count)
{
char name[CPUQUIET_NAME_LEN];
struct cpuquiet_governor *gov;
int len = count, ret = -EINVAL;
if (!len || len >= sizeof(name))
return -EINVAL;
memcpy(name, buf, count);
name[len] = '\0';
if (name[len - 1] == '\n')
name[--len] = '\0';
mutex_lock(&cpuquiet_lock);
gov = cpuquiet_find_governor(name);
if (gov)
ret = cpuquiet_switch_governor(gov);
mutex_unlock(&cpuquiet_lock);
if (ret)
return ret;
else
return count;
}
static ssize_t show_available_governors(struct cpuquiet_attribute *cattr,
char *buf)
{
ssize_t ret = 0, len;
struct cpuquiet_governor *gov;
mutex_lock(&cpuquiet_lock);
if (!list_empty(&cpuquiet_governors)) {
list_for_each_entry(gov, &cpuquiet_governors, governor_list) {
len = sprintf(buf, "%s ", gov->name);
buf += len;
ret += len;
}
buf--;
*buf = '\n';
} else
ret = sprintf(buf, "none\n");
mutex_unlock(&cpuquiet_lock);
return ret;
}
CPQ_ATTRIBUTE(current_governor, 0644, show_current_governor,
store_current_governor);
CPQ_ATTRIBUTE(available_governors, 0444, show_available_governors, NULL);
static struct attribute *cpuquiet_default_attrs[] = {
&current_governor_attr.attr,
&available_governors_attr.attr,
NULL,
};
static ssize_t cpuquiet_sysfs_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct cpuquiet_attribute *cattr =
container_of(attr, struct cpuquiet_attribute, attr);
return cattr->show(cattr, buf);
}
static ssize_t cpuquiet_sysfs_store(struct kobject *kobj,
struct attribute *attr, const char *buf, size_t count)
{
struct cpuquiet_attribute *cattr =
container_of(attr, struct cpuquiet_attribute, attr);
if (cattr->store)
return cattr->store(cattr, buf, count);
return -EINVAL;
}
const struct sysfs_ops cpuquiet_sysfs_ops = {
.show = cpuquiet_sysfs_show,
.store = cpuquiet_sysfs_store,
};
static struct kobj_type ktype_cpuquiet = {
.sysfs_ops = &cpuquiet_sysfs_ops,
.default_attrs = cpuquiet_default_attrs,
};
static ssize_t show_active(unsigned int cpu, char *buf)
{
return sprintf(buf, "%u\n", cpu_online(cpu));
}
static ssize_t store_active(unsigned int cpu, const char *value, size_t count)
{
unsigned int active;
int ret;
if (!cpuquiet_curr_governor->store_active)
return -EINVAL;
ret = sscanf(value, "%u", &active);
if (ret != 1)
return -EINVAL;
cpuquiet_curr_governor->store_active(cpu, active);
return count;
}
CPQ_CPU_ATTRIBUTE(active, 0644, show_active, store_active);
static struct attribute *cpuquiet_cpu_default_attrs[] = {
&active_attr.attr,
NULL,
};
static ssize_t cpuquiet_cpu_sysfs_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct cpuquiet_cpu_attribute *cattr =
container_of(attr, struct cpuquiet_cpu_attribute, attr);
struct cpuquiet_dev *dev =
container_of(kobj, struct cpuquiet_dev, kobj);
return cattr->show(dev->cpu, buf);
}
static ssize_t cpuquiet_cpu_sysfs_store(struct kobject *kobj,
struct attribute *attr, const char *buf, size_t count)
{
struct cpuquiet_cpu_attribute *cattr =
container_of(attr, struct cpuquiet_cpu_attribute, attr);
struct cpuquiet_dev *dev =
container_of(kobj, struct cpuquiet_dev, kobj);
if (cattr->store)
return cattr->store(dev->cpu, buf, count);
return -EINVAL;
}
static const struct sysfs_ops cpuquiet_cpu_sysfs_ops = {
.show = cpuquiet_cpu_sysfs_show,
.store = cpuquiet_cpu_sysfs_store,
};
static struct kobj_type ktype_cpuquiet_cpu = {
.sysfs_ops = &cpuquiet_cpu_sysfs_ops,
.default_attrs = cpuquiet_cpu_default_attrs,
};
int cpuquiet_add_dev(struct device *device, unsigned int cpu)
{
struct cpuquiet_dev *dev;
int err;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
device = get_cpu_device(cpu);
dev_info(device, "cpuquiet_add_dev\n");
cpuquiet_cpu_devices[cpu] = dev;
dev->cpu = cpu;
err = kobject_init_and_add(&dev->kobj, &ktype_cpuquiet_cpu,
&device->kobj, "cpuquiet");
if (err) {
kfree(dev);
return err;
}
kobject_uevent(&dev->kobj, KOBJ_ADD);
return 0;
}
void cpuquiet_remove_dev(unsigned int cpu)
{
if (cpuquiet_cpu_devices[cpu])
kobject_put(&cpuquiet_cpu_devices[cpu]->kobj);
}
int cpuquiet_sysfs_init(void)
{
struct device *dev = cpu_subsys.dev_root;
int err;
cpuquiet_global_kobject = kzalloc(sizeof(*cpuquiet_global_kobject),
GFP_KERNEL);
if (!cpuquiet_global_kobject)
return -ENOMEM;
err = kobject_init_and_add(cpuquiet_global_kobject,
&ktype_cpuquiet, &dev->kobj, "cpuquiet");
if (err) {
kfree(cpuquiet_global_kobject);
return err;
}
kobject_uevent(cpuquiet_global_kobject, KOBJ_ADD);
return 0;
}
void cpuquiet_sysfs_exit(void)
{
kobject_put(cpuquiet_global_kobject);
}
int cpuquiet_register_attrs(struct attribute_group *attrs)
{
return sysfs_create_group(cpuquiet_global_kobject, attrs);
}
EXPORT_SYMBOL(cpuquiet_register_attrs);
void cpuquiet_unregister_attrs(struct attribute_group *attrs)
{
sysfs_remove_group(cpuquiet_global_kobject, attrs);
}
EXPORT_SYMBOL(cpuquiet_unregister_attrs);
int cpuquiet_register_cpu_attrs(struct attribute_group *attrs)
{
int cpu, ret = 0;
for_each_possible_cpu(cpu) {
if (cpuquiet_cpu_devices[cpu]) {
ret = sysfs_create_group(
&cpuquiet_cpu_devices[cpu]->kobj,
attrs);
if (ret) {
cpuquiet_unregister_cpu_attrs(attrs);
return ret;
}
}
}
return ret;
}
EXPORT_SYMBOL(cpuquiet_register_cpu_attrs);
void cpuquiet_unregister_cpu_attrs(struct attribute_group *attrs)
{
int cpu;
for_each_possible_cpu(cpu) {
if (cpuquiet_cpu_devices[cpu])
sysfs_remove_group(&cpuquiet_cpu_devices[cpu]->kobj,
attrs);
}
}
EXPORT_SYMBOL(cpuquiet_unregister_cpu_attrs);