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@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@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@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
Panto,
This looks interesting. cc'ing Rob and Charles who were interested in this at Connect.
/Amit
On Thu, Jun 21, 2012 at 3:15 AM, Pantelis Antoniou panto@antoniou-consulting.com wrote:
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@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@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@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
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
Hi
This is exactly the sort of technique I was proposing, but I was wondering whether there is a generic timer API that could be used. I was thinking hrtimer but profess no knowledge here. Such an approach would make it usable on all boards.
Cheers
Charles
-----Original Message----- From: Amit Kucheria [mailto:amit.kucheria@linaro.org] Sent: 20 June 2012 06:30 To: Pantelis Antoniou Cc: santosh.shilimkar@ti.com; linaro-dev@lists.linaro.org; vbarshak@dev.rtsoft.ru; Rob Lee; Charles Garcia-Tobin Subject: Re: [PATCH] [CPUFREQ] VCPUfreq: Virtual CPU frequency driver.
Panto,
This looks interesting. cc'ing Rob and Charles who were interested in this at Connect.
/Amit
On Thu, Jun 21, 2012 at 3:15 AM, Pantelis Antoniou panto@antoniou-consulting.com wrote:
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@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@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@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
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
-- IMPORTANT NOTICE: The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.
Hi Charles,
At first I did looked in the hrtimers API to do so, but it seems that significant hacking would be required to provide what is needed for vcpufreq to work; That is:
1) Steering of the timer interrupt to a given CPU 2) Making sure that the timer callback is called in an interrupt context.
I am specifically interested in getting vcpufreq to work on TI SoCs anyway, and using dm timers was painless comparatively.
However if someone wants to take stab at implementing this in a portable manner shouldn't be too hard, since the platform glue is nicely abstracted with just 3 functions to implement.
Regards
-- Pantelis
On Jun 20, 2012, at 10:56 AM, Charles Garcia-Tobin wrote:
Hi
This is exactly the sort of technique I was proposing, but I was wondering whether there is a generic timer API that could be used. I was thinking hrtimer but profess no knowledge here. Such an approach would make it usable on all boards.
Cheers
Charles
-----Original Message----- From: Amit Kucheria [mailto:amit.kucheria@linaro.org] Sent: 20 June 2012 06:30 To: Pantelis Antoniou Cc: santosh.shilimkar@ti.com; linaro-dev@lists.linaro.org; vbarshak@dev.rtsoft.ru; Rob Lee; Charles Garcia-Tobin Subject: Re: [PATCH] [CPUFREQ] VCPUfreq: Virtual CPU frequency driver.
Panto,
This looks interesting. cc'ing Rob and Charles who were interested in this at Connect.
/Amit
On Thu, Jun 21, 2012 at 3:15 AM, Pantelis Antoniou panto@antoniou-consulting.com wrote:
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@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@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@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
linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev
-- IMPORTANT NOTICE: The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.