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 <pa...@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 <pa...@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 <pa...@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 <pa...@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 _______________________________________________ linaro-dev mailing list linaro-dev@lists.linaro.org http://lists.linaro.org/mailman/listinfo/linaro-dev