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

Reply via email to