summaryrefslogtreecommitdiff
path: root/drivers/cpuquiet
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/cpuquiet')
-rw-r--r--drivers/cpuquiet/governors/Makefile2
-rw-r--r--drivers/cpuquiet/governors/runnable_threads.c247
2 files changed, 248 insertions, 1 deletions
diff --git a/drivers/cpuquiet/governors/Makefile b/drivers/cpuquiet/governors/Makefile
index c70803127082..e199d73008f1 100644
--- a/drivers/cpuquiet/governors/Makefile
+++ b/drivers/cpuquiet/governors/Makefile
@@ -1 +1 @@
-obj-y += userspace.o balanced.o
+obj-y += userspace.o balanced.o runnable_threads.o
diff --git a/drivers/cpuquiet/governors/runnable_threads.c b/drivers/cpuquiet/governors/runnable_threads.c
new file mode 100644
index 000000000000..a44abe9b25f2
--- /dev/null
+++ b/drivers/cpuquiet/governors/runnable_threads.c
@@ -0,0 +1,247 @@
+/*
+ * 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/cpuquiet.h>
+#include <linux/cpumask.h>
+#include <linux/module.h>
+#include <linux/pm_qos.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/sched.h>
+
+typedef enum {
+ DISABLED,
+ IDLE,
+ DOWN,
+ UP,
+} RUNNABLES_STATE;
+
+static struct delayed_work runnables_work;
+static struct kobject *runnables_kobject;
+
+/* configurable parameters */
+static unsigned int sample_rate = 20; /* msec */
+
+static RUNNABLES_STATE runnables_state;
+static struct workqueue_struct *runnables_wq;
+
+#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[] = {
+ 9, 17, 25, UINT_MAX
+};
+
+static unsigned int nr_run_last;
+static unsigned int nr_run_hysteresis = 4; /* 1 / 4 thread */
+static unsigned int default_threshold_level = 4; /* 1 / 4 thread */
+static unsigned int nr_run_thresholds[NR_CPUS];
+
+DEFINE_MUTEX(runnables_work_lock);
+
+static void update_runnables_state(void)
+{
+ 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);
+ unsigned int avg_nr_run = avg_nr_running();
+ unsigned int nr_run;
+
+ if (runnables_state == DISABLED)
+ return;
+
+ 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;
+
+ if ((nr_cpus > max_cpus || nr_run < nr_cpus) && nr_cpus >= min_cpus) {
+ runnables_state = DOWN;
+ } else if (nr_cpus < min_cpus || nr_run > nr_cpus) {
+ runnables_state = UP;
+ } else {
+ runnables_state = IDLE;
+ }
+}
+
+static unsigned int get_lightest_loaded_cpu_n(void)
+{
+ unsigned long min_avg_runnables = ULONG_MAX;
+ unsigned int cpu = nr_cpu_ids;
+ int i;
+
+ for_each_online_cpu(i) {
+ unsigned int nr_runnables = get_avg_nr_running(i);
+
+ 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)
+{
+ bool up = false;
+ bool sample = false;
+ unsigned int cpu = nr_cpu_ids;
+
+ mutex_lock(&runnables_work_lock);
+
+ update_runnables_state();
+
+ switch (runnables_state) {
+ case DISABLED:
+ break;
+ case IDLE:
+ sample = true;
+ break;
+ case UP:
+ cpu = cpumask_next_zero(0, cpu_online_mask);
+ up = true;
+ sample = true;
+ break;
+ case DOWN:
+ cpu = get_lightest_loaded_cpu_n();
+ sample = true;
+ break;
+ default:
+ pr_err("%s: invalid cpuquiet runnable governor state %d\n",
+ __func__, runnables_state);
+ break;
+ }
+
+ if (sample)
+ queue_delayed_work(runnables_wq, &runnables_work,
+ msecs_to_jiffies(sample_rate));
+
+ if (cpu < nr_cpu_ids) {
+ if (up)
+ cpuquiet_wake_cpu(cpu);
+ else
+ cpuquiet_quiesence_cpu(cpu);
+ }
+
+ mutex_unlock(&runnables_work_lock);
+}
+
+CPQ_BASIC_ATTRIBUTE(sample_rate, 0644, uint);
+CPQ_BASIC_ATTRIBUTE(nr_run_hysteresis, 0644, uint);
+
+static struct attribute *runnables_attributes[] = {
+ &sample_rate_attr.attr,
+ &nr_run_hysteresis_attr.attr,
+ NULL,
+};
+
+static const struct sysfs_ops runnables_sysfs_ops = {
+ .show = cpuquiet_auto_sysfs_show,
+ .store = cpuquiet_auto_sysfs_store,
+};
+
+static struct kobj_type ktype_runnables = {
+ .sysfs_ops = &runnables_sysfs_ops,
+ .default_attrs = runnables_attributes,
+};
+
+static int runnables_sysfs(void)
+{
+ int err;
+
+ runnables_kobject = kzalloc(sizeof(*runnables_kobject),
+ GFP_KERNEL);
+
+ if (!runnables_kobject)
+ return -ENOMEM;
+
+ err = cpuquiet_kobject_init(runnables_kobject, &ktype_runnables,
+ "runnable_threads");
+
+ if (err)
+ kfree(runnables_kobject);
+
+ return err;
+}
+
+static void runnables_stop(void)
+{
+ runnables_state = DISABLED;
+ cancel_delayed_work_sync(&runnables_work);
+ destroy_workqueue(runnables_wq);
+ kobject_put(runnables_kobject);
+}
+
+static int runnables_start(void)
+{
+ int err, i;
+
+ err = runnables_sysfs();
+ if (err)
+ return err;
+
+ runnables_wq = alloc_workqueue("cpuquiet-runnables",
+ WQ_UNBOUND | WQ_RESCUER | WQ_FREEZABLE, 1);
+ if (!runnables_wq)
+ return -ENOMEM;
+
+ INIT_DELAYED_WORK(&runnables_work, runnables_work_func);
+
+ 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_state = IDLE;
+ runnables_work_func(NULL);
+
+ return 0;
+}
+
+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");
+module_init(init_runnables);
+module_exit(exit_runnables);