[PATCH] [CPUFREQ] VCPUfreq: Virtual CPU frequency driver.

Pantelis Antoniou panto at antoniou-consulting.com
Wed Jun 20 21:45:12 UTC 2012


Many current interesting systems have no ability to simulate the upcoming
bigLITTLE machines, since their cores have to be clocked at the same speeds.

Using this driver it is possible to simulate a bigLITTLE system by means
of a standard (virtual) cpufreq driver.

By using a timer per core & irq affinity it is possible to do something
like this:

	$ cpucycle cpu0
	90403235
	$ cpucycle cpu1
	89810456
	$ cd /sys/devices/system/cpu/cpu0/cpufreq
	$ cat scaling_available_frequencies
	233325 466651 699977
	$ echo 466651 > scaling_setspeed
	$ cpucycle cpu0
	58936083

Note that the ratios are about the same so it is somewhat accurate.
	4666651  /  699977 =~ 0.666
	58936083 / 90403235 =~ 0.652

The available tunables available as module parameters are:

freq:
	Normal maximum CPU frequency in kHz
	When 0, then the platform glue layer should probe for it.
	default 0

hogtime:
	Amount of time in usecs that the timer interrupt handler will hog
	the CPU. Note this is time spend spinning in an IRQ handler, so
	it should be as low as possible. A higher value result in more
	accurate simulation.
	Default 100

latency:
	Simulated latency in usecs of cpu freq change.
	Default 500

splits:
	Number of splits in the frequency value. For example when freq is
	1000000 and splits is 2 then two frequency OPPs will be generated,
	one in 500000 and one in 1000000.

Only one glue layer for omap2plus is provided, but it should be trivial to
add more for other platforms.
---
 drivers/cpufreq/Kconfig         |   26 ++++
 drivers/cpufreq/Makefile        |    5 +
 drivers/cpufreq/vcpufreq-omap.c |  251 +++++++++++++++++++++++++++++++++++++++
 drivers/cpufreq/vcpufreq.c      |  216 +++++++++++++++++++++++++++++++++
 drivers/cpufreq/vcpufreq.h      |   25 ++++
 5 files changed, 523 insertions(+), 0 deletions(-)
 create mode 100644 drivers/cpufreq/vcpufreq-omap.c
 create mode 100644 drivers/cpufreq/vcpufreq.c
 create mode 100644 drivers/cpufreq/vcpufreq.h

diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig
index e24a2a1..1fef0ad 100644
--- a/drivers/cpufreq/Kconfig
+++ b/drivers/cpufreq/Kconfig
@@ -194,5 +194,31 @@ depends on PPC32 || PPC64
 source "drivers/cpufreq/Kconfig.powerpc"
 endmenu
 
+config VCPUFREQ
+	bool "Virtual CPU freq driver"
+	depends on CPU_FREQ
+	select CPU_FREQ_TABLE
+	help
+	  This driver implements a cycle-soaker cpufreq driver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called vcpufreq.
+
+	  If in doubt, say N.
+
+if VCPUFREQ
+
+choice
+	prompt "VCPUFREQ Platform glue Layer"
+
+config VCPUFREQ_OMAP2PLUS
+	bool "OMAP VCPUFREQ driver"
+	depends on ARCH_OMAP2PLUS
+
+endchoice
+
+endif
+
 endif
+
 endmenu
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 9531fc2..97d3011 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -52,3 +52,8 @@ obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ)     += omap-cpufreq.o
 ##################################################################################
 # PowerPC platform drivers
 obj-$(CONFIG_CPU_FREQ_MAPLE)		+= maple-cpufreq.o
+
+##################################################################################
+# Virtual driver
+obj-$(CONFIG_VCPUFREQ)     		+= vcpufreq.o
+obj-$(CONFIG_VCPUFREQ_OMAP2PLUS) 	+= vcpufreq-omap.o
diff --git a/drivers/cpufreq/vcpufreq-omap.c b/drivers/cpufreq/vcpufreq-omap.c
new file mode 100644
index 0000000..fd789c4
--- /dev/null
+++ b/drivers/cpufreq/vcpufreq-omap.c
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2012 Pantelis Antoniou <panto at antoniou-consulting.com>
+ *
+ * Virtual CPUFreq glue driver for OMAP2PLUS
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) "cpufreq: " fmt
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/cpufreq.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/math64.h>
+#include <linux/delay.h>
+
+#include <asm/smp_plat.h>
+#include <asm/cpu.h>
+#include <plat/cpu.h>
+#include <plat/dmtimer.h>	
+
+#include "vcpufreq.h"
+
+struct omap_timer_info {
+	unsigned int cpu;
+	struct omap_dm_timer *dm_timer;
+	unsigned int irq;
+	uint64_t counter;
+	char irqname[16];	/* vcpufreq%d */
+	unsigned int hwtimer_rate;
+	unsigned int hog_delta;
+};
+
+static DEFINE_PER_CPU(struct omap_timer_info, omap_timer);
+
+static irqreturn_t dm_timer_handler(int irq, void *dev_id)
+{
+	struct omap_timer_info *oti = dev_id;
+	unsigned int status;
+	unsigned int start;
+
+	BUG_ON(oti == NULL);
+	BUG_ON(oti->dm_timer == NULL);
+
+	status = omap_dm_timer_read_status(oti->dm_timer);
+	if (status & OMAP_TIMER_INT_OVERFLOW) {
+		omap_dm_timer_write_status(oti->dm_timer,
+				OMAP_TIMER_INT_OVERFLOW);
+		omap_dm_timer_read_status(oti->dm_timer);
+		oti->counter++;
+
+		/*
+		 * udelay is really crap for this; no accuracy whatsoever
+		 * so use the nice hardware counter and be happy
+		 */
+		start = omap_dm_timer_read_counter(oti->dm_timer);
+		while ((omap_dm_timer_read_counter(oti->dm_timer) - start)
+				< oti->hog_delta)
+			;	/* do nothing */
+
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+int vcpufreq_glue_set_freq(unsigned int cpu, unsigned int new_freq,
+		unsigned int old_freq)
+{
+	struct omap_timer_info __percpu *oti = &per_cpu(omap_timer, cpu);
+	int ret = 0;
+	uint32_t rate;
+	unsigned int hog_timer_rate;
+	unsigned int freq = vcpufreq_get_maxspeed();
+	unsigned int hogtime = vcpufreq_get_hogtime();
+
+	/* should never happen; checked before */
+	BUG_ON(new_freq == old_freq);
+
+	/* max freq; stop the timer */
+	if (new_freq == freq) {
+		pr_debug("#%d: shut down timer\n", cpu);
+		/* no error */
+		ret = 0;
+		goto omap_stop_timer;
+
+	}
+	
+	/* timer was stopped, we should start it */
+	if (old_freq == freq) {
+
+		oti->cpu = cpu;
+
+		/* get any omap timer */
+		oti->dm_timer = omap_dm_timer_request();
+		if (oti->dm_timer == NULL) {
+			pr_err("#%d: No available omap timers\n", cpu);
+			ret = -ENODEV;
+			goto omap_stop_timer;
+		}
+
+		pr_debug("#%d: got omap timer with id %d\n", cpu, oti->dm_timer->id);
+
+		/* source it from SYS_CLK */
+		ret = omap_dm_timer_set_source(oti->dm_timer, OMAP_TIMER_SRC_SYS_CLK);
+		if (ret != 0) {
+			pr_err("#%d: omap_dm_timer_set_source() failed\n", cpu);
+			goto omap_stop_timer;
+		}
+
+		/* set the prescaler to 0 (need a fast timer) */
+		ret = omap_dm_timer_set_prescaler(oti->dm_timer, 0);
+		if (ret != 0) {
+			pr_err("#%d: omap_dm_timer_set_prescaler() failed\n", cpu);
+			goto omap_stop_timer;
+		}
+
+		/* get the irq */
+		ret = omap_dm_timer_get_irq(oti->dm_timer);
+		if (ret < 0) {
+			pr_err("#%d: omap_dm_timer_get_irq() failed\n", cpu);
+			goto omap_stop_timer;
+		}
+		oti->irq = ret;
+
+		snprintf(oti->irqname, sizeof(oti->irqname), "vcpufreq%u", cpu);
+		ret = request_irq(oti->irq, dm_timer_handler,
+				IRQF_DISABLED | IRQF_TIMER, oti->irqname, oti);
+		if (ret < 0) {
+			pr_err("#%d: failed to request percpu irq %d (%d)\n", cpu,
+					oti->irq, ret);
+			goto omap_stop_timer;
+		}
+	} else
+		omap_dm_timer_stop(oti->dm_timer);
+	
+	/* common in either case */
+	oti->hwtimer_rate = clk_get_rate(omap_dm_timer_get_fclk(oti->dm_timer));
+	if (oti->hwtimer_rate == 0) {
+		pr_err("#%d: illegal timer fclk rate\n", cpu);
+		goto omap_stop_timer;
+	}
+	pr_debug("#%d: hwtimer_rate=%u/sec (period %uns)", cpu,
+			oti->hwtimer_rate, 1000000000 / oti->hwtimer_rate);
+
+	oti->hog_delta = div_u64((u64)oti->hwtimer_rate * (u64)hogtime, 1000000);
+	pr_debug("#%d: hog_delta = %u\n", cpu, oti->hog_delta);
+
+	/* rate of hog timer */
+	hog_timer_rate = div_u64((u64)(freq - new_freq) * 1000000, freq * hogtime);
+	pr_debug("#%d: hog timer rate = %u\n", cpu, hog_timer_rate);
+
+	rate = (oti->hwtimer_rate + (hog_timer_rate / 2)) / hog_timer_rate;
+	pr_debug("#%d: hw timer rate = %u\n", cpu, rate);
+
+	omap_dm_timer_set_load(oti->dm_timer, 1, 0xFFFFFFFF - rate);
+
+	/* first start */
+	if (old_freq == freq) {
+		/* enable the interrupt on overflow */
+		omap_dm_timer_set_int_enable(oti->dm_timer,
+				OMAP_TIMER_INT_OVERFLOW);
+		/* route the interrupt to a given cpu */
+		irq_set_affinity(oti->irq, cpumask_of(cpu));
+	}
+
+	omap_dm_timer_start(oti->dm_timer);
+
+	vcpufreq_set_speed(cpu, new_freq);
+	return 0;
+
+omap_stop_timer:
+	/* clear everything */
+	if (oti->dm_timer) {
+
+		omap_dm_timer_stop(oti->dm_timer);
+		if (oti->irq != (unsigned int)-1) {
+			free_irq(oti->irq, oti);
+			oti->irq = -1;
+		}
+		omap_dm_timer_free(oti->dm_timer);
+
+		/* clean up */
+		memset(oti, 0, sizeof(*oti));
+		oti->irq = (unsigned int)-1;
+	}
+
+	/* always return to max speed here */
+	vcpufreq_set_speed(cpu, freq);
+	return ret;
+}
+
+int vcpufreq_glue_init(struct cpufreq_policy *policy, int *freq)
+{
+	struct omap_timer_info __percpu *oti;
+	int ret = 0;
+	struct clk *mpu_clk;
+	const char *mpu_clk_name = NULL;
+
+	BUG_ON(freq == NULL);
+
+	/* if no freq was provided, probe */
+	if (*freq == 0) {
+		if (cpu_is_omap24xx())
+			mpu_clk_name = "virt_prcm_set";
+		else if (cpu_is_omap34xx())
+			mpu_clk_name = "dpll1_ck";
+		else if (cpu_is_omap44xx())
+			mpu_clk_name = "dpll_mpu_ck";
+
+		if (mpu_clk_name == NULL) {
+			pr_err("%s: Unknown mpu_clk_name (unsupported)\n",
+					__func__);
+			ret = -EINVAL;
+			goto error_out;
+		}
+		mpu_clk = clk_get(NULL, mpu_clk_name);
+		if (IS_ERR(mpu_clk)) {
+			ret = PTR_ERR(mpu_clk);
+			pr_err("%s: clk_get for '%s' failed\n", __func__,
+					mpu_clk_name);
+			goto error_out;
+		}
+		/* update freq */
+		*freq = clk_get_rate(mpu_clk) / 1000;
+	}
+
+	/* initialize per cpu structure */
+	oti = &per_cpu(omap_timer, policy->cpu);
+	memset(oti, 0, sizeof(*oti));
+	oti->irq = (unsigned int)-1;
+
+	ret = 0;
+
+error_out:
+	return ret;
+}
+
+int vcpufreq_glue_exit(struct cpufreq_policy *policy)
+{
+	return 0;
+}
diff --git a/drivers/cpufreq/vcpufreq.c b/drivers/cpufreq/vcpufreq.c
new file mode 100644
index 0000000..b5ded3f
--- /dev/null
+++ b/drivers/cpufreq/vcpufreq.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2012 Pantelis Antoniou <panto at antoniou-consulting.com>
+ *
+ * Virtual CPUFreq driver; allows usage of normal SMP systems for
+ * asymmetric processing evaluation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) "cpufreq: " fmt
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/cpufreq.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/math64.h>
+#include <linux/delay.h>
+
+#include "vcpufreq.h"
+
+static struct cpufreq_frequency_table *vfreq_table = NULL;
+
+static unsigned int latency = 500;
+static unsigned int splits = 3;
+static unsigned int freq = 0;	/* default 1GHz */
+static unsigned int hogtime = 100;
+
+static DEFINE_PER_CPU(unsigned int, curfreq);
+
+static int vcpufreq_verify_speed(struct cpufreq_policy *policy)
+{
+	BUG_ON(vfreq_table == NULL);
+	return cpufreq_frequency_table_verify(policy, vfreq_table);
+}
+
+unsigned int vcpufreq_get_speed(unsigned int cpu)
+{
+	return per_cpu(curfreq, cpu);
+}
+
+void vcpufreq_set_speed(unsigned int cpu, unsigned int new_freq)
+{
+	per_cpu(curfreq, cpu) = new_freq;
+}
+
+unsigned int vcpufreq_get_maxspeed(void)
+{
+	return freq;
+}
+
+unsigned int vcpufreq_get_hogtime(void)
+{
+	return hogtime;
+}
+
+static int vcpufreq_set_target(struct cpufreq_policy *policy,
+				      unsigned int target_freq,
+				      unsigned int relation)
+{
+	int ret;
+	unsigned int i;
+	struct cpufreq_freqs freqs;
+
+	BUG_ON(vfreq_table == NULL);
+
+	ret = cpufreq_frequency_table_target(policy, vfreq_table,
+					     target_freq, relation, &i);
+	if (ret != 0)
+		return ret;
+
+	memset(&freqs, 0, sizeof(freqs));
+	freqs.cpu = policy->cpu;
+	freqs.old = vcpufreq_get_speed(policy->cpu);
+	freqs.new = vfreq_table[i].frequency;
+
+	if (freqs.old == freqs.new && policy->cur == freqs.new)
+		return 0;
+
+	/* the CPUs are free-clocked */
+	freqs.cpu = policy->cpu;
+	cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
+
+	pr_debug("Transition %d-%dkHz\n", freqs.old, freqs.new);
+
+	/* nothing */
+	if (freqs.new == freqs.old) {
+		pr_err("#%d: same freq %u\n", policy->cpu, freqs.new);
+		ret = -EAGAIN;
+		goto error_out;
+	}
+
+	ret = vcpufreq_glue_set_freq(policy->cpu, freqs.new, freqs.old);
+
+error_out:
+	cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+
+	return ret;
+}
+
+static int __cpuinit vcpufreq_driver_init(struct cpufreq_policy *policy)
+{
+	int ret;
+	unsigned int i;
+
+	ret = vcpufreq_glue_init(policy, &freq); 
+	if (ret != 0) {
+		pr_err("%s: vcpufreq_glue_init() failed\n", __func__);
+		goto error_out;
+	}
+
+	if (splits < 1) {
+		pr_err("%s: Illegal splits value (%u)\n", __func__, splits);
+		ret = -EINVAL;
+		goto error_out;
+	}
+
+	vfreq_table = kmalloc(sizeof(*vfreq_table) * (splits + 1), GFP_KERNEL);
+	if (vfreq_table == NULL) {
+		pr_err("Failed to allocate frequency table: %d\n",
+		       ret);
+		ret = -ENOMEM;
+		goto error_out;
+	}
+
+	/* 0 .. splits-1 */
+	for (i = 0; i < splits; i++) {
+		vfreq_table[i].index = i;
+		vfreq_table[i].frequency = (freq * (i + 1)) / splits;
+	}
+	/* splits-1 */
+	vfreq_table[i].index = i;
+	vfreq_table[i].frequency = freq;
+
+	/* ends */
+	vfreq_table[i].index = i;
+	vfreq_table[i].frequency = CPUFREQ_TABLE_END;
+
+	ret = cpufreq_frequency_table_cpuinfo(policy, vfreq_table);
+	if (ret != 0) {
+		pr_err("Failed to configure frequency table: %d\n",
+		       ret);
+		goto error_out;
+	}
+
+	cpufreq_frequency_table_get_attr(vfreq_table, policy->cpu);
+
+	policy->min = policy->cpuinfo.min_freq;
+	policy->max = policy->cpuinfo.max_freq;
+
+	/* always start at the max */
+	per_cpu(curfreq, policy->cpu) = freq;
+
+	policy->cur = per_cpu(curfreq, policy->cpu);
+	policy->cpuinfo.transition_latency = latency;
+
+	pr_info("#%d: Virtual CPU frequency driver initialized\n", policy->cpu);
+
+	return 0;
+
+error_out:
+	kfree(vfreq_table);
+	vfreq_table = NULL;
+	return ret;
+}
+
+static int __cpuexit vcpufreq_driver_exit(struct cpufreq_policy *policy)
+{
+	kfree(vfreq_table);
+	vfreq_table = NULL;
+	vcpufreq_glue_exit(policy);
+
+	return 0;
+}
+
+static struct freq_attr *vcpufreq_attr[] = {
+	&cpufreq_freq_attr_scaling_available_freqs,
+	NULL,
+};
+
+static struct cpufreq_driver vcpufreq_driver = {
+	.owner		= THIS_MODULE,
+	.flags          = CPUFREQ_CONST_LOOPS,
+	.verify		= vcpufreq_verify_speed,
+	.target		= vcpufreq_set_target,
+	.get		= vcpufreq_get_speed,
+	.init		= vcpufreq_driver_init,
+	.exit		= vcpufreq_driver_exit,
+	.name		= "vcpufreq",
+	.attr		= vcpufreq_attr,
+};
+
+static int __init vcpufreq_init(void)
+{
+	return cpufreq_register_driver(&vcpufreq_driver);
+}
+module_init(vcpufreq_init);
+
+module_param(latency, uint, 0644);
+MODULE_PARM_DESC(latency, "Transition latency in usecs (default 500)");
+
+module_param(splits, uint, 0644);
+MODULE_PARM_DESC(splits, "Number of frequency splits (default 2)");
+
+module_param(freq, uint, 0644);
+MODULE_PARM_DESC(freq, "Maximum frequency in kHz (0 means platform detect)");
+
+module_param(hogtime, uint, 0644);
+MODULE_PARM_DESC(hogtime, "Time spend hogging the CPU in the IRQ handle in usec (default 10)");
diff --git a/drivers/cpufreq/vcpufreq.h b/drivers/cpufreq/vcpufreq.h
new file mode 100644
index 0000000..6135b23
--- /dev/null
+++ b/drivers/cpufreq/vcpufreq.h
@@ -0,0 +1,25 @@
+#ifndef __VCPUFREQ_H
+#define __VCPUFREQ_H
+
+/*
+ * Copyright 2012 Pantelis Antoniou <panto at antoniou-consulting.com>
+ *
+ * Virtual CPUFreq driver header.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* provided by the glue layer */
+int vcpufreq_glue_set_freq(unsigned int cpu, unsigned int new_freq,
+		unsigned int old_freq);
+int vcpufreq_glue_init(struct cpufreq_policy *policy, int *freq);
+int vcpufreq_glue_exit(struct cpufreq_policy *policy);
+
+/* provided by the core */
+unsigned int vcpufreq_get_maxspeed(void);
+unsigned int vcpufreq_get_hogtime(void);
+void vcpufreq_set_speed(unsigned int cpu, unsigned int new_freq);
+
+#endif
-- 
1.7.1




More information about the linaro-dev mailing list