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