|
@@ -0,0 +1,794 @@
|
|
|
+/*
|
|
|
+ * intel_powerclamp.c - package c-state idle injection
|
|
|
+ *
|
|
|
+ * Copyright (c) 2012, Intel Corporation.
|
|
|
+ *
|
|
|
+ * Authors:
|
|
|
+ * Arjan van de Ven <arjan@linux.intel.com>
|
|
|
+ * Jacob Pan <jacob.jun.pan@linux.intel.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.
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * TODO:
|
|
|
+ * 1. better handle wakeup from external interrupts, currently a fixed
|
|
|
+ * compensation is added to clamping duration when excessive amount
|
|
|
+ * of wakeups are observed during idle time. the reason is that in
|
|
|
+ * case of external interrupts without need for ack, clamping down
|
|
|
+ * cpu in non-irq context does not reduce irq. for majority of the
|
|
|
+ * cases, clamping down cpu does help reduce irq as well, we should
|
|
|
+ * be able to differenciate the two cases and give a quantitative
|
|
|
+ * solution for the irqs that we can control. perhaps based on
|
|
|
+ * get_cpu_iowait_time_us()
|
|
|
+ *
|
|
|
+ * 2. synchronization with other hw blocks
|
|
|
+ *
|
|
|
+ *
|
|
|
+ */
|
|
|
+
|
|
|
+#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/thermal.h>
|
|
|
+#include <linux/slab.h>
|
|
|
+#include <linux/tick.h>
|
|
|
+#include <linux/debugfs.h>
|
|
|
+#include <linux/seq_file.h>
|
|
|
+
|
|
|
+#include <asm/nmi.h>
|
|
|
+#include <asm/msr.h>
|
|
|
+#include <asm/mwait.h>
|
|
|
+#include <asm/cpu_device_id.h>
|
|
|
+#include <asm/idle.h>
|
|
|
+#include <asm/hardirq.h>
|
|
|
+
|
|
|
+#define MAX_TARGET_RATIO (50U)
|
|
|
+/* For each undisturbed clamping period (no extra wake ups during idle time),
|
|
|
+ * we increment the confidence counter for the given target ratio.
|
|
|
+ * CONFIDENCE_OK defines the level where runtime calibration results are
|
|
|
+ * valid.
|
|
|
+ */
|
|
|
+#define CONFIDENCE_OK (3)
|
|
|
+/* Default idle injection duration, driver adjust sleep time to meet target
|
|
|
+ * idle ratio. Similar to frequency modulation.
|
|
|
+ */
|
|
|
+#define DEFAULT_DURATION_JIFFIES (6)
|
|
|
+
|
|
|
+static unsigned int target_mwait;
|
|
|
+static struct dentry *debug_dir;
|
|
|
+
|
|
|
+/* user selected target */
|
|
|
+static unsigned int set_target_ratio;
|
|
|
+static unsigned int current_ratio;
|
|
|
+static bool should_skip;
|
|
|
+static bool reduce_irq;
|
|
|
+static atomic_t idle_wakeup_counter;
|
|
|
+static unsigned int control_cpu; /* The cpu assigned to collect stat and update
|
|
|
+ * control parameters. default to BSP but BSP
|
|
|
+ * can be offlined.
|
|
|
+ */
|
|
|
+static bool clamping;
|
|
|
+
|
|
|
+
|
|
|
+static struct task_struct * __percpu *powerclamp_thread;
|
|
|
+static struct thermal_cooling_device *cooling_dev;
|
|
|
+static unsigned long *cpu_clamping_mask; /* bit map for tracking per cpu
|
|
|
+ * clamping thread
|
|
|
+ */
|
|
|
+
|
|
|
+static unsigned int duration;
|
|
|
+static unsigned int pkg_cstate_ratio_cur;
|
|
|
+static unsigned int window_size;
|
|
|
+
|
|
|
+static int duration_set(const char *arg, const struct kernel_param *kp)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ unsigned long new_duration;
|
|
|
+
|
|
|
+ ret = kstrtoul(arg, 10, &new_duration);
|
|
|
+ if (ret)
|
|
|
+ goto exit;
|
|
|
+ if (new_duration > 25 || new_duration < 6) {
|
|
|
+ pr_err("Out of recommended range %lu, between 6-25ms\n",
|
|
|
+ new_duration);
|
|
|
+ ret = -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ duration = clamp(new_duration, 6ul, 25ul);
|
|
|
+ smp_mb();
|
|
|
+
|
|
|
+exit:
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static struct kernel_param_ops duration_ops = {
|
|
|
+ .set = duration_set,
|
|
|
+ .get = param_get_int,
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+module_param_cb(duration, &duration_ops, &duration, 0644);
|
|
|
+MODULE_PARM_DESC(duration, "forced idle time for each attempt in msec.");
|
|
|
+
|
|
|
+struct powerclamp_calibration_data {
|
|
|
+ unsigned long confidence; /* used for calibration, basically a counter
|
|
|
+ * gets incremented each time a clamping
|
|
|
+ * period is completed without extra wakeups
|
|
|
+ * once that counter is reached given level,
|
|
|
+ * compensation is deemed usable.
|
|
|
+ */
|
|
|
+ unsigned long steady_comp; /* steady state compensation used when
|
|
|
+ * no extra wakeups occurred.
|
|
|
+ */
|
|
|
+ unsigned long dynamic_comp; /* compensate excessive wakeup from idle
|
|
|
+ * mostly from external interrupts.
|
|
|
+ */
|
|
|
+};
|
|
|
+
|
|
|
+static struct powerclamp_calibration_data cal_data[MAX_TARGET_RATIO];
|
|
|
+
|
|
|
+static int window_size_set(const char *arg, const struct kernel_param *kp)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ unsigned long new_window_size;
|
|
|
+
|
|
|
+ ret = kstrtoul(arg, 10, &new_window_size);
|
|
|
+ if (ret)
|
|
|
+ goto exit_win;
|
|
|
+ if (new_window_size > 10 || new_window_size < 2) {
|
|
|
+ pr_err("Out of recommended window size %lu, between 2-10\n",
|
|
|
+ new_window_size);
|
|
|
+ ret = -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ window_size = clamp(new_window_size, 2ul, 10ul);
|
|
|
+ smp_mb();
|
|
|
+
|
|
|
+exit_win:
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static struct kernel_param_ops window_size_ops = {
|
|
|
+ .set = window_size_set,
|
|
|
+ .get = param_get_int,
|
|
|
+};
|
|
|
+
|
|
|
+module_param_cb(window_size, &window_size_ops, &window_size, 0644);
|
|
|
+MODULE_PARM_DESC(window_size, "sliding window in number of clamping cycles\n"
|
|
|
+ "\tpowerclamp controls idle ratio within this window. larger\n"
|
|
|
+ "\twindow size results in slower response time but more smooth\n"
|
|
|
+ "\tclamping results. default to 2.");
|
|
|
+
|
|
|
+static void find_target_mwait(void)
|
|
|
+{
|
|
|
+ unsigned int eax, ebx, ecx, edx;
|
|
|
+ unsigned int highest_cstate = 0;
|
|
|
+ unsigned int highest_subcstate = 0;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (boot_cpu_data.cpuid_level < CPUID_MWAIT_LEAF)
|
|
|
+ return;
|
|
|
+
|
|
|
+ cpuid(CPUID_MWAIT_LEAF, &eax, &ebx, &ecx, &edx);
|
|
|
+
|
|
|
+ if (!(ecx & CPUID5_ECX_EXTENSIONS_SUPPORTED) ||
|
|
|
+ !(ecx & CPUID5_ECX_INTERRUPT_BREAK))
|
|
|
+ return;
|
|
|
+
|
|
|
+ edx >>= MWAIT_SUBSTATE_SIZE;
|
|
|
+ for (i = 0; i < 7 && edx; i++, edx >>= MWAIT_SUBSTATE_SIZE) {
|
|
|
+ if (edx & MWAIT_SUBSTATE_MASK) {
|
|
|
+ highest_cstate = i;
|
|
|
+ highest_subcstate = edx & MWAIT_SUBSTATE_MASK;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ target_mwait = (highest_cstate << MWAIT_SUBSTATE_SIZE) |
|
|
|
+ (highest_subcstate - 1);
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+static u64 pkg_state_counter(void)
|
|
|
+{
|
|
|
+ u64 val;
|
|
|
+ u64 count = 0;
|
|
|
+
|
|
|
+ static bool skip_c2;
|
|
|
+ static bool skip_c3;
|
|
|
+ static bool skip_c6;
|
|
|
+ static bool skip_c7;
|
|
|
+
|
|
|
+ if (!skip_c2) {
|
|
|
+ if (!rdmsrl_safe(MSR_PKG_C2_RESIDENCY, &val))
|
|
|
+ count += val;
|
|
|
+ else
|
|
|
+ skip_c2 = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!skip_c3) {
|
|
|
+ if (!rdmsrl_safe(MSR_PKG_C3_RESIDENCY, &val))
|
|
|
+ count += val;
|
|
|
+ else
|
|
|
+ skip_c3 = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!skip_c6) {
|
|
|
+ if (!rdmsrl_safe(MSR_PKG_C6_RESIDENCY, &val))
|
|
|
+ count += val;
|
|
|
+ else
|
|
|
+ skip_c6 = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!skip_c7) {
|
|
|
+ if (!rdmsrl_safe(MSR_PKG_C7_RESIDENCY, &val))
|
|
|
+ count += val;
|
|
|
+ else
|
|
|
+ skip_c7 = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return count;
|
|
|
+}
|
|
|
+
|
|
|
+static void noop_timer(unsigned long foo)
|
|
|
+{
|
|
|
+ /* empty... just the fact that we get the interrupt wakes us up */
|
|
|
+}
|
|
|
+
|
|
|
+static unsigned int get_compensation(int ratio)
|
|
|
+{
|
|
|
+ unsigned int comp = 0;
|
|
|
+
|
|
|
+ /* we only use compensation if all adjacent ones are good */
|
|
|
+ if (ratio == 1 &&
|
|
|
+ cal_data[ratio].confidence >= CONFIDENCE_OK &&
|
|
|
+ cal_data[ratio + 1].confidence >= CONFIDENCE_OK &&
|
|
|
+ cal_data[ratio + 2].confidence >= CONFIDENCE_OK) {
|
|
|
+ comp = (cal_data[ratio].steady_comp +
|
|
|
+ cal_data[ratio + 1].steady_comp +
|
|
|
+ cal_data[ratio + 2].steady_comp) / 3;
|
|
|
+ } else if (ratio == MAX_TARGET_RATIO - 1 &&
|
|
|
+ cal_data[ratio].confidence >= CONFIDENCE_OK &&
|
|
|
+ cal_data[ratio - 1].confidence >= CONFIDENCE_OK &&
|
|
|
+ cal_data[ratio - 2].confidence >= CONFIDENCE_OK) {
|
|
|
+ comp = (cal_data[ratio].steady_comp +
|
|
|
+ cal_data[ratio - 1].steady_comp +
|
|
|
+ cal_data[ratio - 2].steady_comp) / 3;
|
|
|
+ } else if (cal_data[ratio].confidence >= CONFIDENCE_OK &&
|
|
|
+ cal_data[ratio - 1].confidence >= CONFIDENCE_OK &&
|
|
|
+ cal_data[ratio + 1].confidence >= CONFIDENCE_OK) {
|
|
|
+ comp = (cal_data[ratio].steady_comp +
|
|
|
+ cal_data[ratio - 1].steady_comp +
|
|
|
+ cal_data[ratio + 1].steady_comp) / 3;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* REVISIT: simple penalty of double idle injection */
|
|
|
+ if (reduce_irq)
|
|
|
+ comp = ratio;
|
|
|
+ /* do not exceed limit */
|
|
|
+ if (comp + ratio >= MAX_TARGET_RATIO)
|
|
|
+ comp = MAX_TARGET_RATIO - ratio - 1;
|
|
|
+
|
|
|
+ return comp;
|
|
|
+}
|
|
|
+
|
|
|
+static void adjust_compensation(int target_ratio, unsigned int win)
|
|
|
+{
|
|
|
+ int delta;
|
|
|
+ struct powerclamp_calibration_data *d = &cal_data[target_ratio];
|
|
|
+
|
|
|
+ /*
|
|
|
+ * adjust compensations if confidence level has not been reached or
|
|
|
+ * there are too many wakeups during the last idle injection period, we
|
|
|
+ * cannot trust the data for compensation.
|
|
|
+ */
|
|
|
+ if (d->confidence >= CONFIDENCE_OK ||
|
|
|
+ atomic_read(&idle_wakeup_counter) >
|
|
|
+ win * num_online_cpus())
|
|
|
+ return;
|
|
|
+
|
|
|
+ delta = set_target_ratio - current_ratio;
|
|
|
+ /* filter out bad data */
|
|
|
+ if (delta >= 0 && delta <= (1+target_ratio/10)) {
|
|
|
+ if (d->steady_comp)
|
|
|
+ d->steady_comp =
|
|
|
+ roundup(delta+d->steady_comp, 2)/2;
|
|
|
+ else
|
|
|
+ d->steady_comp = delta;
|
|
|
+ d->confidence++;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static bool powerclamp_adjust_controls(unsigned int target_ratio,
|
|
|
+ unsigned int guard, unsigned int win)
|
|
|
+{
|
|
|
+ static u64 msr_last, tsc_last;
|
|
|
+ u64 msr_now, tsc_now;
|
|
|
+ u64 val64;
|
|
|
+
|
|
|
+ /* check result for the last window */
|
|
|
+ msr_now = pkg_state_counter();
|
|
|
+ rdtscll(tsc_now);
|
|
|
+
|
|
|
+ /* calculate pkg cstate vs tsc ratio */
|
|
|
+ if (!msr_last || !tsc_last)
|
|
|
+ current_ratio = 1;
|
|
|
+ else if (tsc_now-tsc_last) {
|
|
|
+ val64 = 100*(msr_now-msr_last);
|
|
|
+ do_div(val64, (tsc_now-tsc_last));
|
|
|
+ current_ratio = val64;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* update record */
|
|
|
+ msr_last = msr_now;
|
|
|
+ tsc_last = tsc_now;
|
|
|
+
|
|
|
+ adjust_compensation(target_ratio, win);
|
|
|
+ /*
|
|
|
+ * too many external interrupts, set flag such
|
|
|
+ * that we can take measure later.
|
|
|
+ */
|
|
|
+ reduce_irq = atomic_read(&idle_wakeup_counter) >=
|
|
|
+ 2 * win * num_online_cpus();
|
|
|
+
|
|
|
+ atomic_set(&idle_wakeup_counter, 0);
|
|
|
+ /* if we are above target+guard, skip */
|
|
|
+ return set_target_ratio + guard <= current_ratio;
|
|
|
+}
|
|
|
+
|
|
|
+static int clamp_thread(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,
|
|
|
+ };
|
|
|
+ unsigned int count = 0;
|
|
|
+ unsigned int target_ratio;
|
|
|
+
|
|
|
+ set_bit(cpunr, cpu_clamping_mask);
|
|
|
+ set_freezable();
|
|
|
+ init_timer_on_stack(&wakeup_timer);
|
|
|
+ sched_setscheduler(current, SCHED_FIFO, ¶m);
|
|
|
+
|
|
|
+ while (true == clamping && !kthread_should_stop() &&
|
|
|
+ cpu_online(cpunr)) {
|
|
|
+ int sleeptime;
|
|
|
+ unsigned long target_jiffies;
|
|
|
+ unsigned int guard;
|
|
|
+ unsigned int compensation = 0;
|
|
|
+ int interval; /* jiffies to sleep for each attempt */
|
|
|
+ unsigned int duration_jiffies = msecs_to_jiffies(duration);
|
|
|
+ unsigned int window_size_now;
|
|
|
+
|
|
|
+ try_to_freeze();
|
|
|
+ /*
|
|
|
+ * make sure user selected ratio does not take effect until
|
|
|
+ * the next round. adjust target_ratio if user has changed
|
|
|
+ * target such that we can converge quickly.
|
|
|
+ */
|
|
|
+ target_ratio = set_target_ratio;
|
|
|
+ guard = 1 + target_ratio/20;
|
|
|
+ window_size_now = window_size;
|
|
|
+ count++;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * systems may have different ability to enter package level
|
|
|
+ * c-states, thus we need to compensate the injected idle ratio
|
|
|
+ * to achieve the actual target reported by the HW.
|
|
|
+ */
|
|
|
+ compensation = get_compensation(target_ratio);
|
|
|
+ interval = duration_jiffies*100/(target_ratio+compensation);
|
|
|
+
|
|
|
+ /* align idle time */
|
|
|
+ target_jiffies = roundup(jiffies, interval);
|
|
|
+ sleeptime = target_jiffies - jiffies;
|
|
|
+ if (sleeptime <= 0)
|
|
|
+ sleeptime = 1;
|
|
|
+ schedule_timeout_interruptible(sleeptime);
|
|
|
+ /*
|
|
|
+ * only elected controlling cpu can collect stats and update
|
|
|
+ * control parameters.
|
|
|
+ */
|
|
|
+ if (cpunr == control_cpu && !(count%window_size_now)) {
|
|
|
+ should_skip =
|
|
|
+ powerclamp_adjust_controls(target_ratio,
|
|
|
+ guard, window_size_now);
|
|
|
+ smp_mb();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (should_skip)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ target_jiffies = jiffies + duration_jiffies;
|
|
|
+ mod_timer(&wakeup_timer, target_jiffies);
|
|
|
+ if (unlikely(local_softirq_pending()))
|
|
|
+ continue;
|
|
|
+ /*
|
|
|
+ * stop tick sched during idle time, interrupts are still
|
|
|
+ * allowed. thus jiffies are updated properly.
|
|
|
+ */
|
|
|
+ preempt_disable();
|
|
|
+ tick_nohz_idle_enter();
|
|
|
+ /* mwait until target jiffies is reached */
|
|
|
+ while (time_before(jiffies, target_jiffies)) {
|
|
|
+ unsigned long ecx = 1;
|
|
|
+ unsigned long eax = target_mwait;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * REVISIT: may call enter_idle() to notify drivers who
|
|
|
+ * can save power during cpu idle. same for exit_idle()
|
|
|
+ */
|
|
|
+ local_touch_nmi();
|
|
|
+ stop_critical_timings();
|
|
|
+ __monitor((void *)¤t_thread_info()->flags, 0, 0);
|
|
|
+ cpu_relax(); /* allow HT sibling to run */
|
|
|
+ __mwait(eax, ecx);
|
|
|
+ start_critical_timings();
|
|
|
+ atomic_inc(&idle_wakeup_counter);
|
|
|
+ }
|
|
|
+ tick_nohz_idle_exit();
|
|
|
+ preempt_enable_no_resched();
|
|
|
+ }
|
|
|
+ del_timer_sync(&wakeup_timer);
|
|
|
+ clear_bit(cpunr, cpu_clamping_mask);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * 1 HZ polling while clamping is active, useful for userspace
|
|
|
+ * to monitor actual idle ratio.
|
|
|
+ */
|
|
|
+static void poll_pkg_cstate(struct work_struct *dummy);
|
|
|
+static DECLARE_DELAYED_WORK(poll_pkg_cstate_work, poll_pkg_cstate);
|
|
|
+static void poll_pkg_cstate(struct work_struct *dummy)
|
|
|
+{
|
|
|
+ static u64 msr_last;
|
|
|
+ static u64 tsc_last;
|
|
|
+ static unsigned long jiffies_last;
|
|
|
+
|
|
|
+ u64 msr_now;
|
|
|
+ unsigned long jiffies_now;
|
|
|
+ u64 tsc_now;
|
|
|
+ u64 val64;
|
|
|
+
|
|
|
+ msr_now = pkg_state_counter();
|
|
|
+ rdtscll(tsc_now);
|
|
|
+ jiffies_now = jiffies;
|
|
|
+
|
|
|
+ /* calculate pkg cstate vs tsc ratio */
|
|
|
+ if (!msr_last || !tsc_last)
|
|
|
+ pkg_cstate_ratio_cur = 1;
|
|
|
+ else {
|
|
|
+ if (tsc_now - tsc_last) {
|
|
|
+ val64 = 100 * (msr_now - msr_last);
|
|
|
+ do_div(val64, (tsc_now - tsc_last));
|
|
|
+ pkg_cstate_ratio_cur = val64;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* update record */
|
|
|
+ msr_last = msr_now;
|
|
|
+ jiffies_last = jiffies_now;
|
|
|
+ tsc_last = tsc_now;
|
|
|
+
|
|
|
+ if (true == clamping)
|
|
|
+ schedule_delayed_work(&poll_pkg_cstate_work, HZ);
|
|
|
+}
|
|
|
+
|
|
|
+static int start_power_clamp(void)
|
|
|
+{
|
|
|
+ unsigned long cpu;
|
|
|
+ struct task_struct *thread;
|
|
|
+
|
|
|
+ /* check if pkg cstate counter is completely 0, abort in this case */
|
|
|
+ if (!pkg_state_counter()) {
|
|
|
+ pr_err("pkg cstate counter not functional, abort\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1);
|
|
|
+ /* prevent cpu hotplug */
|
|
|
+ get_online_cpus();
|
|
|
+
|
|
|
+ /* prefer BSP */
|
|
|
+ control_cpu = 0;
|
|
|
+ if (!cpu_online(control_cpu))
|
|
|
+ control_cpu = smp_processor_id();
|
|
|
+
|
|
|
+ clamping = true;
|
|
|
+ schedule_delayed_work(&poll_pkg_cstate_work, 0);
|
|
|
+
|
|
|
+ /* start one thread per online cpu */
|
|
|
+ for_each_online_cpu(cpu) {
|
|
|
+ struct task_struct **p =
|
|
|
+ per_cpu_ptr(powerclamp_thread, cpu);
|
|
|
+
|
|
|
+ thread = kthread_create_on_node(clamp_thread,
|
|
|
+ (void *) cpu,
|
|
|
+ cpu_to_node(cpu),
|
|
|
+ "kidle_inject/%ld", cpu);
|
|
|
+ /* bind to cpu here */
|
|
|
+ if (likely(!IS_ERR(thread))) {
|
|
|
+ kthread_bind(thread, cpu);
|
|
|
+ wake_up_process(thread);
|
|
|
+ *p = thread;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ put_online_cpus();
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void end_power_clamp(void)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ struct task_struct *thread;
|
|
|
+
|
|
|
+ clamping = false;
|
|
|
+ /*
|
|
|
+ * make clamping visible to other cpus and give per cpu clamping threads
|
|
|
+ * sometime to exit, or gets killed later.
|
|
|
+ */
|
|
|
+ smp_mb();
|
|
|
+ msleep(20);
|
|
|
+ if (bitmap_weight(cpu_clamping_mask, num_possible_cpus())) {
|
|
|
+ for_each_set_bit(i, cpu_clamping_mask, num_possible_cpus()) {
|
|
|
+ pr_debug("clamping thread for cpu %d alive, kill\n", i);
|
|
|
+ thread = *per_cpu_ptr(powerclamp_thread, i);
|
|
|
+ kthread_stop(thread);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int powerclamp_cpu_callback(struct notifier_block *nfb,
|
|
|
+ unsigned long action, void *hcpu)
|
|
|
+{
|
|
|
+ unsigned long cpu = (unsigned long)hcpu;
|
|
|
+ struct task_struct *thread;
|
|
|
+ struct task_struct **percpu_thread =
|
|
|
+ per_cpu_ptr(powerclamp_thread, cpu);
|
|
|
+
|
|
|
+ if (false == clamping)
|
|
|
+ goto exit_ok;
|
|
|
+
|
|
|
+ switch (action) {
|
|
|
+ case CPU_ONLINE:
|
|
|
+ thread = kthread_create_on_node(clamp_thread,
|
|
|
+ (void *) cpu,
|
|
|
+ cpu_to_node(cpu),
|
|
|
+ "kidle_inject/%lu", cpu);
|
|
|
+ if (likely(!IS_ERR(thread))) {
|
|
|
+ kthread_bind(thread, cpu);
|
|
|
+ wake_up_process(thread);
|
|
|
+ *percpu_thread = thread;
|
|
|
+ }
|
|
|
+ /* prefer BSP as controlling CPU */
|
|
|
+ if (cpu == 0) {
|
|
|
+ control_cpu = 0;
|
|
|
+ smp_mb();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case CPU_DEAD:
|
|
|
+ if (test_bit(cpu, cpu_clamping_mask)) {
|
|
|
+ pr_err("cpu %lu dead but powerclamping thread is not\n",
|
|
|
+ cpu);
|
|
|
+ kthread_stop(*percpu_thread);
|
|
|
+ }
|
|
|
+ if (cpu == control_cpu) {
|
|
|
+ control_cpu = smp_processor_id();
|
|
|
+ smp_mb();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+exit_ok:
|
|
|
+ return NOTIFY_OK;
|
|
|
+}
|
|
|
+
|
|
|
+static struct notifier_block powerclamp_cpu_notifier = {
|
|
|
+ .notifier_call = powerclamp_cpu_callback,
|
|
|
+};
|
|
|
+
|
|
|
+static int powerclamp_get_max_state(struct thermal_cooling_device *cdev,
|
|
|
+ unsigned long *state)
|
|
|
+{
|
|
|
+ *state = MAX_TARGET_RATIO;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int powerclamp_get_cur_state(struct thermal_cooling_device *cdev,
|
|
|
+ unsigned long *state)
|
|
|
+{
|
|
|
+ if (true == clamping)
|
|
|
+ *state = pkg_cstate_ratio_cur;
|
|
|
+ else
|
|
|
+ /* to save power, do not poll idle ratio while not clamping */
|
|
|
+ *state = -1; /* indicates invalid state */
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int powerclamp_set_cur_state(struct thermal_cooling_device *cdev,
|
|
|
+ unsigned long new_target_ratio)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ new_target_ratio = clamp(new_target_ratio, 0UL,
|
|
|
+ (unsigned long) (MAX_TARGET_RATIO-1));
|
|
|
+ if (set_target_ratio == 0 && new_target_ratio > 0) {
|
|
|
+ pr_info("Start idle injection to reduce power\n");
|
|
|
+ set_target_ratio = new_target_ratio;
|
|
|
+ ret = start_power_clamp();
|
|
|
+ goto exit_set;
|
|
|
+ } else if (set_target_ratio > 0 && new_target_ratio == 0) {
|
|
|
+ pr_info("Stop forced idle injection\n");
|
|
|
+ set_target_ratio = 0;
|
|
|
+ end_power_clamp();
|
|
|
+ } else /* adjust currently running */ {
|
|
|
+ set_target_ratio = new_target_ratio;
|
|
|
+ /* make new set_target_ratio visible to other cpus */
|
|
|
+ smp_mb();
|
|
|
+ }
|
|
|
+
|
|
|
+exit_set:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/* bind to generic thermal layer as cooling device*/
|
|
|
+static struct thermal_cooling_device_ops powerclamp_cooling_ops = {
|
|
|
+ .get_max_state = powerclamp_get_max_state,
|
|
|
+ .get_cur_state = powerclamp_get_cur_state,
|
|
|
+ .set_cur_state = powerclamp_set_cur_state,
|
|
|
+};
|
|
|
+
|
|
|
+/* runs on Nehalem and later */
|
|
|
+static const struct x86_cpu_id intel_powerclamp_ids[] = {
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x1a},
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x1c},
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x1e},
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x1f},
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x25},
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x26},
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x2a},
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x2c},
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x2d},
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x2e},
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x2f},
|
|
|
+ { X86_VENDOR_INTEL, 6, 0x3a},
|
|
|
+ {}
|
|
|
+};
|
|
|
+MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids);
|
|
|
+
|
|
|
+static int powerclamp_probe(void)
|
|
|
+{
|
|
|
+ if (!x86_match_cpu(intel_powerclamp_ids)) {
|
|
|
+ pr_err("Intel powerclamp does not run on family %d model %d\n",
|
|
|
+ boot_cpu_data.x86, boot_cpu_data.x86_model);
|
|
|
+ return -ENODEV;
|
|
|
+ }
|
|
|
+ if (!boot_cpu_has(X86_FEATURE_NONSTOP_TSC) ||
|
|
|
+ !boot_cpu_has(X86_FEATURE_CONSTANT_TSC) ||
|
|
|
+ !boot_cpu_has(X86_FEATURE_MWAIT) ||
|
|
|
+ !boot_cpu_has(X86_FEATURE_ARAT))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ /* find the deepest mwait value */
|
|
|
+ find_target_mwait();
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int powerclamp_debug_show(struct seq_file *m, void *unused)
|
|
|
+{
|
|
|
+ int i = 0;
|
|
|
+
|
|
|
+ seq_printf(m, "controlling cpu: %d\n", control_cpu);
|
|
|
+ seq_printf(m, "pct confidence steady dynamic (compensation)\n");
|
|
|
+ for (i = 0; i < MAX_TARGET_RATIO; i++) {
|
|
|
+ seq_printf(m, "%d\t%lu\t%lu\t%lu\n",
|
|
|
+ i,
|
|
|
+ cal_data[i].confidence,
|
|
|
+ cal_data[i].steady_comp,
|
|
|
+ cal_data[i].dynamic_comp);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int powerclamp_debug_open(struct inode *inode,
|
|
|
+ struct file *file)
|
|
|
+{
|
|
|
+ return single_open(file, powerclamp_debug_show, inode->i_private);
|
|
|
+}
|
|
|
+
|
|
|
+static const struct file_operations powerclamp_debug_fops = {
|
|
|
+ .open = powerclamp_debug_open,
|
|
|
+ .read = seq_read,
|
|
|
+ .llseek = seq_lseek,
|
|
|
+ .release = single_release,
|
|
|
+ .owner = THIS_MODULE,
|
|
|
+};
|
|
|
+
|
|
|
+static inline void powerclamp_create_debug_files(void)
|
|
|
+{
|
|
|
+ debug_dir = debugfs_create_dir("intel_powerclamp", NULL);
|
|
|
+ if (!debug_dir)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (!debugfs_create_file("powerclamp_calib", S_IRUGO, debug_dir,
|
|
|
+ cal_data, &powerclamp_debug_fops))
|
|
|
+ goto file_error;
|
|
|
+
|
|
|
+ return;
|
|
|
+
|
|
|
+file_error:
|
|
|
+ debugfs_remove_recursive(debug_dir);
|
|
|
+}
|
|
|
+
|
|
|
+static int powerclamp_init(void)
|
|
|
+{
|
|
|
+ int retval;
|
|
|
+ int bitmap_size;
|
|
|
+
|
|
|
+ bitmap_size = BITS_TO_LONGS(num_possible_cpus()) * sizeof(long);
|
|
|
+ cpu_clamping_mask = kzalloc(bitmap_size, GFP_KERNEL);
|
|
|
+ if (!cpu_clamping_mask)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ /* probe cpu features and ids here */
|
|
|
+ retval = powerclamp_probe();
|
|
|
+ if (retval)
|
|
|
+ return retval;
|
|
|
+ /* set default limit, maybe adjusted during runtime based on feedback */
|
|
|
+ window_size = 2;
|
|
|
+ register_hotcpu_notifier(&powerclamp_cpu_notifier);
|
|
|
+ powerclamp_thread = alloc_percpu(struct task_struct *);
|
|
|
+ cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL,
|
|
|
+ &powerclamp_cooling_ops);
|
|
|
+ if (IS_ERR(cooling_dev))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ if (!duration)
|
|
|
+ duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES);
|
|
|
+ powerclamp_create_debug_files();
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+module_init(powerclamp_init);
|
|
|
+
|
|
|
+static void powerclamp_exit(void)
|
|
|
+{
|
|
|
+ unregister_hotcpu_notifier(&powerclamp_cpu_notifier);
|
|
|
+ end_power_clamp();
|
|
|
+ free_percpu(powerclamp_thread);
|
|
|
+ thermal_cooling_device_unregister(cooling_dev);
|
|
|
+ kfree(cpu_clamping_mask);
|
|
|
+
|
|
|
+ cancel_delayed_work_sync(&poll_pkg_cstate_work);
|
|
|
+ debugfs_remove_recursive(debug_dir);
|
|
|
+}
|
|
|
+module_exit(powerclamp_exit);
|
|
|
+
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
+MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>");
|
|
|
+MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
|
|
|
+MODULE_DESCRIPTION("Package Level C-state Idle Injection for Intel CPUs");
|