aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeil Armstrong <narmstrong@baylibre.com>2015-09-16 15:02:02 +0200
committerMichael Turquette <mturquette@baylibre.com>2015-10-16 03:13:55 -0700
commitcaebe33537406d740c8ff109be4b152102c322c1 (patch)
tree2efbb460cead786f165253ab73c3bfe928c98204
parent25cb62b76430a91cc6195f902e61c2cb84ade622 (diff)
downloadlinux-idleforce.tar.gz
introduce idleforce and idleforce_rridleforce
-rw-r--r--drivers/misc/Kconfig11
-rw-r--r--drivers/misc/Makefile2
-rw-r--r--drivers/misc/idleforce.c438
-rw-r--r--drivers/misc/idleforce_rr.c517
-rw-r--r--include/trace/events/idleforce.h164
5 files changed, 1132 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index ccccc2943f2fd..144febd36bfca 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -525,6 +525,17 @@ config VEXPRESS_SYSCFG
bus. System Configuration interface is one of the possible means
of generating transactions on this bus.
+config IDLEFORCE
+ bool "Cyclic Idle Injection driver"
+ help
+ Enable this to enable the cyclic idle injection driver.
+
+config IDLEFORCE_RR
+ bool "Round Robin Idle Injection driver"
+ depends on !IDLEFORCE
+ help
+ Enable this to enable the Round Robin idle injection driver.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 537d7f3b78da9..e3cb7cd67347b 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -56,3 +56,5 @@ obj-$(CONFIG_GENWQE) += genwqe/
obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
+obj-$(CONFIG_IDLEFORCE) += idleforce.o
+obj-$(CONFIG_IDLEFORCE_RR) += idleforce_rr.o
diff --git a/drivers/misc/idleforce.c b/drivers/misc/idleforce.c
new file mode 100644
index 0000000000000..6883f6d7ffb99
--- /dev/null
+++ b/drivers/misc/idleforce.c
@@ -0,0 +1,438 @@
+/*
+ * drivers/cpuidle/idleforce.c - Cyclic idle injection
+ *
+ * Based in thermal/intel_poweclamp.c
+ *
+ * Copyright (c) 2015, Baylibre.
+ * Copyright (c) 2012, Intel Corporation.
+ *
+ * Authors:
+ * Neil Armstrong <narmstrong@baylibre.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/cpu.h>
+#include <linux/slab.h>
+#include <linux/tick.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/sched/rt.h>
+#include <linux/wait.h>
+#include <linux/uaccess.h>
+#include <asm/proc-fns.h>
+#ifdef CONFIG_SMP
+#include <linux/smp.h>
+#endif
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/idleforce.h>
+
+/* Default delay values */
+#define DEFAULT_WAIT_DELAY_MS 100
+#define DEFAULT_IDLE_DELAY_MS 10
+
+/* sysfs entry */
+static struct kobject *sysfs_dir;
+
+/* Startup status */
+static bool idling;
+
+/* Current delay values */
+static unsigned cur_wait_delay = DEFAULT_WAIT_DELAY_MS;
+static unsigned cur_idle_delay = DEFAULT_IDLE_DELAY_MS;
+
+/* Kthread handles */
+static struct task_struct * __percpu *idleforce_thread;
+static unsigned long *cpu_idling_mask; /* bit map for tracking per cpu
+ * idling thread
+ */
+
+/* Kthreads contexts */
+static struct idleforce_task_s {
+ long int prev_cpu;
+ long int next_cpu;
+ unsigned do_sleep;
+ wait_queue_head_t queue;
+} __percpu *idleforce_task;
+
+/* Timer callback */
+static void noop_timer(unsigned long foo)
+{
+ /* empty... just the fact that we get the interrupt wakes us up */
+}
+
+/* Main per-cpu Kthread */
+static int idleforce_kthread(void *arg)
+{
+ int cpunr = (unsigned long)arg;
+ DEFINE_TIMER(wakeup_timer, noop_timer, 0, 0);
+ static const struct sched_param param = {
+ .sched_priority = MAX_USER_RT_PRIO/2,
+ };
+ struct idleforce_task_s *t =
+ per_cpu_ptr((void *)idleforce_task, cpunr);
+ unsigned jiffies_wait, jiffies_idle;
+
+ if (msecs_to_jiffies(cur_wait_delay))
+ jiffies_wait = msecs_to_jiffies(cur_wait_delay);
+ else
+ jiffies_wait = 1;
+
+ if (msecs_to_jiffies(cur_idle_delay))
+ jiffies_idle = msecs_to_jiffies(cur_idle_delay);
+ else
+ jiffies_idle = 1;
+
+ pr_info("live on cpu %d prev=%li next=%li wait=%dms(%dj) idle=%dms(%dj)\n",
+ cpunr, t->prev_cpu, t->next_cpu,
+ cur_wait_delay, jiffies_wait,
+ cur_idle_delay, jiffies_idle);
+
+ trace_idle_thread_start(cpunr);
+
+ set_bit(cpunr, cpu_idling_mask);
+ set_freezable();
+ init_timer_on_stack(&wakeup_timer);
+ sched_setscheduler(current, SCHED_FIFO, &param);
+
+ while (true == idling && !kthread_should_stop()) {
+ unsigned long target_jiffies;
+
+ try_to_freeze();
+
+ trace_idle_wait_start(cpunr);
+
+ if (t->prev_cpu < 0) {
+ /* wait wakeup delay */
+ schedule_timeout_interruptible(jiffies_wait);
+ } else {
+ /* wake up from previous core kthread */
+ wait_event_interruptible(t->queue, t->do_sleep);
+ }
+
+ trace_idle_wait_end(cpunr);
+
+ /* Wake up next core to prepare WFI */
+ if (t->next_cpu >= 0) {
+ struct idleforce_task_s *t_next =
+ per_cpu_ptr((void *)idleforce_task,
+ t->next_cpu);
+
+ t_next->do_sleep = 1;
+ trace_idle_wait_wake_next(cpunr);
+ wake_up_interruptible(&t_next->queue);
+ }
+
+ /* The first Core waits for timer for a wakeup */
+ if (t->prev_cpu < 0) {
+ /* set idle wakeup time */
+ target_jiffies = jiffies + jiffies_idle;
+
+ mod_timer(&wakeup_timer, target_jiffies);
+
+ /*
+ * stop tick sched during idle time,
+ * interrupts are still
+ * allowed. thus jiffies are updated properly.
+ */
+ preempt_disable();
+
+ /* mwait until target jiffies is reached */
+ trace_idle_start(cpunr);
+ while (time_before(jiffies, target_jiffies)) {
+ cpu_do_idle();
+ trace_idle_wakeup(cpunr);
+ }
+ preempt_enable();
+ }
+ /* The other cores waits an IPI Wakeup message to wake up */
+ else {
+ preempt_disable();
+ /* Set in nohz to only have timer on first core */
+ tick_nohz_idle_enter();
+
+ trace_idle_start(cpunr);
+ while (t->do_sleep) {
+ cpu_do_idle();
+ trace_idle_wakeup(cpunr);
+ }
+
+ tick_nohz_idle_exit();
+ preempt_enable();
+ }
+
+ trace_idle_end(cpunr);
+
+#ifdef CONFIG_SMP
+ /* wake up next cpu */
+ if (t->next_cpu >= 0) {
+ struct idleforce_task_s *t_next =
+ per_cpu_ptr((void *)idleforce_task,
+ t->next_cpu);
+
+ t_next->do_sleep = 0;
+
+ trace_idle_wake_next(cpunr);
+
+ /* wake up next core kthread */
+ smp_send_reschedule(t->next_cpu);
+ }
+#endif
+ }
+ del_timer_sync(&wakeup_timer);
+ clear_bit(cpunr, cpu_idling_mask);
+
+ trace_idle_thread_stop(cpunr);
+
+ return 0;
+}
+
+static int start_idleforce(void)
+{
+ unsigned long cpu;
+ struct task_struct *thread;
+
+ if (idling)
+ return -EINVAL;
+ idling = true;
+
+ /* create one thread per online cpu */
+ for_each_online_cpu(cpu) {
+ static int long cpu_prev = -1;
+ struct task_struct **p =
+ per_cpu_ptr(idleforce_thread, cpu);
+ struct idleforce_task_s *t =
+ per_cpu_ptr((void *)idleforce_task, cpu);
+
+ t->prev_cpu = cpu_prev;
+ t->next_cpu = -1;
+
+ /* fill next cpu */
+ if (cpu_prev >= 0) {
+ struct idleforce_task_s *t_prev =
+ per_cpu_ptr(idleforce_task, cpu_prev);
+ t_prev->next_cpu = cpu;
+ }
+
+ init_waitqueue_head(&t->queue);
+ t->do_sleep = 0;
+
+ thread = kthread_create_on_node(idleforce_kthread,
+ (void *) cpu,
+ cpu_to_node(cpu),
+ "idleforce/%ld", cpu);
+ if (unlikely(IS_ERR(thread))) {
+ pr_err("failed to create kthread\n");
+ return -1;
+ }
+ *p = thread;
+ pr_info("kthread created cpu=%ld\n", cpu);
+
+ cpu_prev = cpu;
+ }
+
+ /* start one thread per online cpu */
+ for_each_online_cpu(cpu) {
+ struct task_struct **p =
+ per_cpu_ptr(idleforce_thread, cpu);
+ struct idleforce_task_s *t =
+ per_cpu_ptr((void *)idleforce_task, cpu);
+
+ /* bind to cpu here */
+ kthread_bind(*p, cpu);
+ wake_up_process(*p);
+ pr_info("kthread started cpu=%ld prev=%li next=%li\n",
+ cpu, t->prev_cpu, t->next_cpu);
+ }
+
+ return 0;
+}
+
+static void end_idleforce(void)
+{
+ int i;
+ struct task_struct *thread;
+
+ if (!idling)
+ return;
+ idling = false;
+ /*
+ * make idling visible to other cpus and give per cpu idling threads
+ * sometime to exit, or gets killed later.
+ */
+ smp_mb();
+ msleep(20);
+ if (bitmap_weight(cpu_idling_mask, num_possible_cpus())) {
+ for_each_set_bit(i, cpu_idling_mask, num_possible_cpus()) {
+ pr_info("idling thread for cpu %d alive, kill\n", i);
+ thread = *per_cpu_ptr(idleforce_thread, i);
+ kthread_stop(thread);
+ pr_info("kthread stopped\n");
+ }
+ }
+}
+
+static ssize_t idleforce_delay_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sprintf(buf, "wait: %d\nidle: %d\n",
+ cur_wait_delay, cur_idle_delay);
+}
+
+static ssize_t idleforce_delay_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned wait = 0, idle = 0;
+
+ if (idling) {
+ pr_err("stop idling before changin delays\n");
+ return -EINVAL;
+ }
+
+ if (sscanf(buf, "%u %u", &wait, &idle) != 2) {
+ pr_err("delay format error : '<wait> <idle>'\n");
+ return -EINVAL;
+ }
+
+ if (!wait || !idle) {
+ pr_err("delay value error : > 0\n");
+ return -EINVAL;
+ }
+
+ cur_wait_delay = wait;
+ cur_idle_delay = idle;
+
+ pr_info("new delays wait=%d idle=%d\n",
+ cur_wait_delay, cur_idle_delay);
+
+ return count;
+}
+
+static ssize_t idleforce_ctrl_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sprintf(buf, "state: %d\n", idling);
+}
+
+static ssize_t idleforce_ctrl_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ if (count < 1)
+ return count;
+
+ if (buf[0] == '1') {
+ pr_info("Starting idleforce\n");
+ start_idleforce();
+ } else if (buf[0] == '0') {
+ pr_info("Stopping idleforce\n");
+ end_idleforce();
+ }
+
+ return count;
+}
+
+static struct kobj_attribute idleforce_ctrl_attribute =
+ __ATTR(ctrl, 0664, idleforce_ctrl_show, idleforce_ctrl_store);
+static struct kobj_attribute idleforce_delay_attribute =
+ __ATTR(delay, 0664, idleforce_delay_show, idleforce_delay_store);
+
+static struct attribute *idleforce_attrs[] = {
+ &idleforce_ctrl_attribute.attr,
+ &idleforce_delay_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct attribute_group idleforce_attr_group = {
+ .attrs = idleforce_attrs,
+};
+
+static inline int idleforce_create_sysfs_files(void)
+{
+ int retval = 0;
+
+ sysfs_dir = kobject_create_and_add("idleforce", kernel_kobj);
+ if (!sysfs_dir)
+ return -ENOMEM;
+
+ retval = sysfs_create_group(sysfs_dir, &idleforce_attr_group);
+ if (retval)
+ kobject_put(sysfs_dir);
+
+ return retval;
+}
+
+static int __init idleforce_init(void)
+{
+ int retval = 0;
+ int bitmap_size;
+
+ bitmap_size = BITS_TO_LONGS(num_possible_cpus()) * sizeof(long);
+ cpu_idling_mask = kzalloc(bitmap_size, GFP_KERNEL);
+ if (!cpu_idling_mask)
+ return -ENOMEM;
+
+ idleforce_thread = alloc_percpu(struct task_struct *);
+ if (!idleforce_thread) {
+ retval = -ENOMEM;
+ goto exit_thread;
+ }
+
+ idleforce_task = alloc_percpu(struct idleforce_task_s);
+ if (!idleforce_task) {
+ retval = -ENOMEM;
+ goto exit_free;
+ }
+
+ retval = idleforce_create_sysfs_files();
+ if (retval < 0)
+ goto exit_thread_task;
+
+ return 0;
+
+exit_thread_task:
+ free_percpu(idleforce_task);
+exit_thread:
+ free_percpu(idleforce_thread);
+exit_free:
+ kfree(cpu_idling_mask);
+ return retval;
+}
+module_init(idleforce_init);
+
+static void __exit idleforce_exit(void)
+{
+ if (sysfs_dir)
+ kobject_put(sysfs_dir);
+
+ end_idleforce();
+
+ free_percpu(idleforce_thread);
+ free_percpu(idleforce_task);
+ kfree(cpu_idling_mask);
+}
+module_exit(idleforce_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION("Cycle Idle Core force");
diff --git a/drivers/misc/idleforce_rr.c b/drivers/misc/idleforce_rr.c
new file mode 100644
index 0000000000000..1da3a649d176c
--- /dev/null
+++ b/drivers/misc/idleforce_rr.c
@@ -0,0 +1,517 @@
+/*
+ * drivers/cpuidle/idleforce_rr.c - Cyclic idle injection
+ *
+ * Based in thermal/intel_poweclamp.c
+ *
+ * Copyright (c) 2015, Baylibre.
+ * Copyright (c) 2012, Intel Corporation.
+ *
+ * Authors:
+ * Neil Armstrong <narmstrong@baylibre.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/cpu.h>
+#include <linux/slab.h>
+#include <linux/tick.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/sched/rt.h>
+#include <linux/wait.h>
+#include <linux/uaccess.h>
+#include <asm/proc-fns.h>
+#ifdef CONFIG_SMP
+#include <linux/smp.h>
+#endif
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/idleforce.h>
+
+/* Default delay values */
+#define DEFAULT_WAIT_DELAY_MS 100
+#define DEFAULT_IDLE_DELAY_MS 10
+
+enum idle_mode_e {
+ IDLE_MODE_RR = 0,
+};
+
+enum idle_state_e {
+ T_DO_SLEEP = 0,
+ T_WAIT_PREV,
+ T_WAIT_SLOT
+};
+
+/* sysfs entry */
+static struct kobject *sysfs_dir;
+
+/* Startup status */
+static bool idling;
+
+/* Current Mode */
+static unsigned idle_mode = IDLE_MODE_RR;
+
+/* Current delay values */
+static unsigned cur_wait_delay = DEFAULT_WAIT_DELAY_MS;
+static unsigned cur_idle_delay = DEFAULT_IDLE_DELAY_MS;
+
+/* Kthread handles */
+static struct task_struct * __percpu *idleforce_rr_thread;
+static unsigned long *cpu_idling_mask; /* bit map for tracking per cpu
+ * idling thread
+ */
+
+/* Kthreads contexts */
+static struct idleforce_rr_task_s {
+ long first;
+ long int prev_cpu;
+ long int next_cpu;
+ unsigned do_sleep;
+ wait_queue_head_t queue;
+} __percpu *idleforce_rr_task;
+
+/* Main per-cpu Kthread */
+static int idleforce_rr_kthread(void *arg)
+{
+ int cpunr = (unsigned long)arg;
+ static const struct sched_param param = {
+ .sched_priority = MAX_USER_RT_PRIO/2,
+ };
+ struct idleforce_rr_task_s *t =
+ per_cpu_ptr((void *)idleforce_rr_task, cpunr);
+ struct idleforce_rr_task_s *t_next = NULL,
+ *t_prev = NULL;
+ unsigned jiffies_wait, jiffies_idle;
+ unsigned state;
+
+ if (t->first)
+ state = T_WAIT_SLOT; /* give time to all threads to startup */
+ else
+ state = T_WAIT_PREV;
+
+ if (t->prev_cpu < 0) {
+ pr_err("invalid prev %li\n", t->prev_cpu);
+ return 0;
+ }
+
+ if (t->next_cpu < 0) {
+ pr_err("invalid next %li\n", t->next_cpu);
+ return 0;
+ }
+
+ if (msecs_to_jiffies(cur_wait_delay))
+ jiffies_wait = msecs_to_jiffies(cur_wait_delay);
+ else
+ jiffies_wait = 1;
+
+ if (msecs_to_jiffies(cur_idle_delay))
+ jiffies_idle = msecs_to_jiffies(cur_idle_delay);
+ else
+ jiffies_idle = 1;
+
+ pr_info("live on cpu %d first=%li prev=%li next=%li wait=%dms(%dj) idle=%dms(%dj)\n",
+ cpunr, t->first,
+ t->prev_cpu, t->next_cpu,
+ cur_wait_delay, jiffies_wait,
+ cur_idle_delay, jiffies_idle);
+
+ if (t->prev_cpu >= 0)
+ t_prev = per_cpu_ptr((void *)idleforce_rr_task,
+ t->prev_cpu);
+
+ if (t->next_cpu >= 0)
+ t_next = per_cpu_ptr((void *)idleforce_rr_task,
+ t->next_cpu);
+
+ trace_idle_thread_start(cpunr);
+
+ set_bit(cpunr, cpu_idling_mask);
+ set_freezable();
+ sched_setscheduler(current, SCHED_FIFO, &param);
+
+ while (true == idling && !kthread_should_stop()) {
+
+ try_to_freeze();
+
+ switch (state) {
+ case T_DO_SLEEP:
+ {
+ t->do_sleep = 1;
+
+ trace_idle_wait_wake_next(cpunr);
+
+ wake_up_interruptible(&t_next->queue);
+
+ preempt_disable();
+
+ tick_nohz_idle_enter();
+
+ trace_idle_start(cpunr);
+ while (t->do_sleep && idling &&
+ !kthread_should_stop()) {
+ cpu_do_idle();
+ trace_idle_wakeup(cpunr);
+ }
+
+ tick_nohz_idle_exit();
+ preempt_enable();
+
+ state = T_WAIT_PREV;
+ } break;
+
+ case T_WAIT_PREV:
+ {
+ wait_event_interruptible(t->queue, (t_prev->do_sleep ||
+ !idling ||
+ kthread_should_stop()));
+
+ if (!idling || kthread_should_stop())
+ break;
+
+ trace_idle_wait_start(cpunr);
+
+ schedule_timeout_interruptible(jiffies_idle);
+
+ t_prev->do_sleep = 0;
+
+ trace_idle_wait_end(cpunr);
+
+ smp_send_reschedule(t->prev_cpu);
+
+ if (t->first)
+ state = T_WAIT_SLOT;
+ else
+ state = T_DO_SLEEP;
+ } break;
+
+ case T_WAIT_SLOT:
+ {
+ trace_idle_wait_start(cpunr);
+
+ schedule_timeout_interruptible(jiffies_wait);
+
+ trace_idle_wait_end(cpunr);
+
+ state = T_DO_SLEEP;
+ } break;
+
+ default:
+ goto thread_end;
+ }
+ }
+
+thread_end:
+ clear_bit(cpunr, cpu_idling_mask);
+
+ trace_idle_thread_stop(cpunr);
+
+ return 0;
+}
+
+static int start_idleforce(void)
+{
+ unsigned long cpu;
+ struct task_struct *thread;
+
+ static int long cpu_last = -1;
+ static int long cpu_first = -1;
+
+ if (idling)
+ return -EINVAL;
+
+ if (idle_mode != IDLE_MODE_RR)
+ return -EINVAL;
+
+ idling = true;
+
+ /* create one thread per online cpu */
+ for_each_online_cpu(cpu) {
+ static int long cpu_prev = -1;
+ struct task_struct **p =
+ per_cpu_ptr(idleforce_rr_thread, cpu);
+ struct idleforce_rr_task_s *t =
+ per_cpu_ptr((void *)idleforce_rr_task, cpu);
+
+ t->prev_cpu = cpu_prev;
+ t->next_cpu = -1;
+
+ /* fill next cpu */
+ if (cpu_prev >= 0) {
+ struct idleforce_rr_task_s *t_prev =
+ per_cpu_ptr(idleforce_rr_task, cpu_prev);
+ t_prev->next_cpu = cpu;
+ }
+
+ init_waitqueue_head(&t->queue);
+ t->do_sleep = 0;
+
+ thread = kthread_create_on_node(idleforce_rr_kthread,
+ (void *) cpu,
+ cpu_to_node(cpu),
+ "idleforce/%ld", cpu);
+ if (unlikely(IS_ERR(thread))) {
+ pr_err("failed to create kthread\n");
+ return -1;
+ }
+ *p = thread;
+ pr_info("kthread created cpu=%ld\n", cpu);
+
+ if (cpu_first < 0)
+ cpu_first = cpu;
+
+ cpu_prev = cpu;
+ cpu_last = cpu;
+ }
+
+ if (cpu_first >= 0) {
+ struct idleforce_rr_task_s *t =
+ per_cpu_ptr((void *)idleforce_rr_task, cpu_first);
+ t->prev_cpu = cpu_last;
+ t->first = true;
+ }
+
+ if (cpu_last >= 0) {
+ struct idleforce_rr_task_s *t =
+ per_cpu_ptr((void *)idleforce_rr_task, cpu_last);
+ t->next_cpu = cpu_first;
+ }
+
+ /* start one thread per online cpu */
+ for_each_online_cpu(cpu) {
+ struct task_struct **p =
+ per_cpu_ptr(idleforce_rr_thread, cpu);
+ struct idleforce_rr_task_s *t =
+ per_cpu_ptr((void *)idleforce_rr_task, cpu);
+
+ /* bind to cpu here */
+ kthread_bind(*p, cpu);
+ wake_up_process(*p);
+ pr_info("kthread started cpu=%ld prev=%li next=%li\n",
+ cpu, t->prev_cpu, t->next_cpu);
+ }
+
+ return 0;
+}
+
+static void end_idleforce(void)
+{
+ int i;
+ struct task_struct *thread;
+ struct idleforce_rr_task_s *t;
+
+ if (!idling)
+ return;
+ idling = false;
+ /*
+ * make idling visible to other cpus and give per cpu idling threads
+ * sometime to exit, or gets killed later.
+ */
+ smp_mb();
+ msleep(20);
+ if (bitmap_weight(cpu_idling_mask, num_possible_cpus())) {
+ for_each_set_bit(i, cpu_idling_mask, num_possible_cpus()) {
+ pr_info("idling thread for cpu %d alive, kill\n", i);
+ thread = *per_cpu_ptr(idleforce_rr_thread, i);
+ t = per_cpu_ptr((void *)idleforce_rr_task, i);
+ wake_up_interruptible(&t->queue);
+ smp_send_reschedule(i);
+ kthread_stop(thread);
+ pr_info("kthread stopped\n");
+ }
+ }
+}
+
+static ssize_t idleforce_rr_mode_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sprintf(buf, "mode: %d\n", idle_mode);
+}
+
+static ssize_t idleforce_rr_mode_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ if (count < 1)
+ return count;
+
+ if (idling) {
+ pr_err("stop idling before changing mode\n");
+ return -EINVAL;
+ }
+
+ if (buf[0] == '0') {
+ idle_mode = IDLE_MODE_RR;
+ } else {
+ pr_err("invalid idling mode '%c'\n", buf[0] == '1');
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static ssize_t idleforce_rr_delay_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sprintf(buf, "wait: %d\nidle: %d\n",
+ cur_wait_delay, cur_idle_delay);
+}
+
+static ssize_t idleforce_rr_delay_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned wait = 0, idle = 0;
+
+ if (idling) {
+ pr_err("stop idling before changin delays\n");
+ return -EINVAL;
+ }
+
+ if (sscanf(buf, "%u %u", &wait, &idle) != 2) {
+ pr_err("delay format error : '<wait> <idle>'\n");
+ return -EINVAL;
+ }
+
+ if (!wait || !idle) {
+ pr_err("delay value error : > 0\n");
+ return -EINVAL;
+ }
+
+ cur_wait_delay = wait;
+ cur_idle_delay = idle;
+
+ pr_info("new delays wait=%d idle=%d\n",
+ cur_wait_delay, cur_idle_delay);
+
+ return count;
+}
+
+static ssize_t idleforce_rr_ctrl_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return sprintf(buf, "state: %d\n", idling);
+}
+
+static ssize_t idleforce_rr_ctrl_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ if (count < 1)
+ return count;
+
+ if (buf[0] == '1') {
+ pr_info("Starting idleforce\n");
+ start_idleforce();
+ } else if (buf[0] == '0') {
+ pr_info("Stopping idleforce\n");
+ end_idleforce();
+ }
+
+ return count;
+}
+
+static struct kobj_attribute idleforce_rr_ctrl_attribute =
+ __ATTR(ctrl, 0664, idleforce_rr_ctrl_show, idleforce_rr_ctrl_store);
+static struct kobj_attribute idleforce_rr_delay_attribute =
+ __ATTR(delay, 0664, idleforce_rr_delay_show, idleforce_rr_delay_store);
+static struct kobj_attribute idleforce_rr_mode_attribute =
+ __ATTR(mode, 0664, idleforce_rr_mode_show, idleforce_rr_mode_store);
+
+static struct attribute *idleforce_rr_attrs[] = {
+ &idleforce_rr_ctrl_attribute.attr,
+ &idleforce_rr_delay_attribute.attr,
+ &idleforce_rr_mode_attribute.attr,
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+static struct attribute_group idleforce_rr_attr_group = {
+ .attrs = idleforce_rr_attrs,
+};
+
+static inline int idleforce_rr_create_sysfs_files(void)
+{
+ int retval = 0;
+
+ sysfs_dir = kobject_create_and_add("idleforce_rr", kernel_kobj);
+ if (!sysfs_dir)
+ return -ENOMEM;
+
+ retval = sysfs_create_group(sysfs_dir, &idleforce_rr_attr_group);
+ if (retval)
+ kobject_put(sysfs_dir);
+
+ return retval;
+}
+
+static int __init idleforce_rr_init(void)
+{
+ int retval = 0;
+ int bitmap_size;
+
+ bitmap_size = BITS_TO_LONGS(num_possible_cpus()) * sizeof(long);
+ cpu_idling_mask = kzalloc(bitmap_size, GFP_KERNEL);
+ if (!cpu_idling_mask)
+ return -ENOMEM;
+
+ idleforce_rr_thread = alloc_percpu(struct task_struct *);
+ if (!idleforce_rr_thread) {
+ retval = -ENOMEM;
+ goto exit_thread;
+ }
+
+ idleforce_rr_task = alloc_percpu(struct idleforce_rr_task_s);
+ if (!idleforce_rr_task) {
+ retval = -ENOMEM;
+ goto exit_free;
+ }
+
+ retval = idleforce_rr_create_sysfs_files();
+ if (retval < 0)
+ goto exit_thread_task;
+
+ return 0;
+
+exit_thread_task:
+ free_percpu(idleforce_rr_task);
+exit_thread:
+ free_percpu(idleforce_rr_thread);
+exit_free:
+ kfree(cpu_idling_mask);
+ return retval;
+}
+module_init(idleforce_rr_init);
+
+static void __exit idleforce_rr_exit(void)
+{
+ if (sysfs_dir)
+ kobject_put(sysfs_dir);
+
+ end_idleforce();
+
+ free_percpu(idleforce_rr_thread);
+ free_percpu(idleforce_rr_task);
+ kfree(cpu_idling_mask);
+}
+module_exit(idleforce_rr_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION("Round Robin Cycle Idle Core force");
diff --git a/include/trace/events/idleforce.h b/include/trace/events/idleforce.h
new file mode 100644
index 0000000000000..7bc700b541359
--- /dev/null
+++ b/include/trace/events/idleforce.h
@@ -0,0 +1,164 @@
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM idleforce
+
+#if !defined(_TRACE_IDLEFORCE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_IDLEFORCE_H
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(idle_thread_start,
+
+ TP_PROTO(int cpu),
+
+ TP_ARGS(cpu),
+
+ TP_STRUCT__entry(
+ __field(int, cpu)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu = cpu;
+ ),
+
+ TP_printk("cpu=%d", __entry->cpu)
+);
+
+TRACE_EVENT(idle_thread_stop,
+
+ TP_PROTO(int cpu),
+
+ TP_ARGS(cpu),
+
+ TP_STRUCT__entry(
+ __field(int, cpu)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu = cpu;
+ ),
+
+ TP_printk("cpu=%d", __entry->cpu)
+);
+
+TRACE_EVENT(idle_wait_start,
+
+ TP_PROTO(int cpu),
+
+ TP_ARGS(cpu),
+
+ TP_STRUCT__entry(
+ __field(int, cpu)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu = cpu;
+ ),
+
+ TP_printk("cpu=%d", __entry->cpu)
+);
+
+TRACE_EVENT(idle_wait_end,
+
+ TP_PROTO(int cpu),
+
+ TP_ARGS(cpu),
+
+ TP_STRUCT__entry(
+ __field(int, cpu)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu = cpu;
+ ),
+
+ TP_printk("cpu=%d", __entry->cpu)
+);
+
+TRACE_EVENT(idle_start,
+
+ TP_PROTO(int cpu),
+
+ TP_ARGS(cpu),
+
+ TP_STRUCT__entry(
+ __field(int, cpu)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu = cpu;
+ ),
+
+ TP_printk("cpu=%d", __entry->cpu)
+);
+
+TRACE_EVENT(idle_wakeup,
+
+ TP_PROTO(int cpu),
+
+ TP_ARGS(cpu),
+
+ TP_STRUCT__entry(
+ __field(int, cpu)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu = cpu;
+ ),
+
+ TP_printk("cpu=%d", __entry->cpu)
+);
+
+TRACE_EVENT(idle_end,
+
+ TP_PROTO(int cpu),
+
+ TP_ARGS(cpu),
+
+ TP_STRUCT__entry(
+ __field(int, cpu)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu = cpu;
+ ),
+
+ TP_printk("cpu=%d", __entry->cpu)
+);
+
+TRACE_EVENT(idle_wait_wake_next,
+
+ TP_PROTO(int cpu),
+
+ TP_ARGS(cpu),
+
+ TP_STRUCT__entry(
+ __field(int, cpu)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu = cpu;
+ ),
+
+ TP_printk("cpu=%d", __entry->cpu)
+);
+
+TRACE_EVENT(idle_wake_next,
+
+ TP_PROTO(int cpu),
+
+ TP_ARGS(cpu),
+
+ TP_STRUCT__entry(
+ __field(int, cpu)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu = cpu;
+ ),
+
+ TP_printk("cpu=%d", __entry->cpu)
+);
+
+#endif
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>