[PATCH] [CPUFREQ] VCPUFREQ: HR timer based generic cycle stealer

Pantelis Antoniou panto at antoniou-consulting.com
Tue Jun 26 21:03:52 UTC 2012


It turns out that hr timers have a nice propery always been executed
on the cpu started on. So it is possible to have a generic bigLITTLE
simulator based on generic code.

Many thanks to Paul E. McKenney that pointed this out.
---
 drivers/cpufreq/Kconfig            |    4 +
 drivers/cpufreq/Makefile           |    1 +
 drivers/cpufreq/vcpufreq-hrtimer.c |  211 ++++++++++++++++++++++++++++++++++++
 3 files changed, 216 insertions(+), 0 deletions(-)
 create mode 100644 drivers/cpufreq/vcpufreq-hrtimer.c

diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig
index 1fef0ad..3fa3523 100644
--- a/drivers/cpufreq/Kconfig
+++ b/drivers/cpufreq/Kconfig
@@ -211,6 +211,10 @@ if VCPUFREQ
 choice
 	prompt "VCPUFREQ Platform glue Layer"
 
+config VCPUFREQ_HRTIMER
+	bool "HRTIMER based VCPUFREQ driver"
+	depends on HIGH_RES_TIMERS
+
 config VCPUFREQ_OMAP2PLUS
 	bool "OMAP VCPUFREQ driver"
 	depends on ARCH_OMAP2PLUS
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 97d3011..128ea25 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -57,3 +57,4 @@ obj-$(CONFIG_CPU_FREQ_MAPLE)		+= maple-cpufreq.o
 # Virtual driver
 obj-$(CONFIG_VCPUFREQ)     		+= vcpufreq.o
 obj-$(CONFIG_VCPUFREQ_OMAP2PLUS) 	+= vcpufreq-omap.o
+obj-$(CONFIG_VCPUFREQ_HRTIMER) 		+= vcpufreq-hrtimer.o
diff --git a/drivers/cpufreq/vcpufreq-hrtimer.c b/drivers/cpufreq/vcpufreq-hrtimer.c
new file mode 100644
index 0000000..98616a9
--- /dev/null
+++ b/drivers/cpufreq/vcpufreq-hrtimer.c
@@ -0,0 +1,211 @@
+/*
+ * 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 <linux/hrtimer.h>
+
+#include "vcpufreq.h"
+
+struct hrtimer_info {
+	unsigned int cpu;
+	struct hrtimer hrtimer;
+	ktime_t interval;
+	unsigned int irq;
+	uint64_t counter;
+	unsigned int timer_rate;
+	unsigned int hogtime;
+};
+
+static DEFINE_PER_CPU(struct hrtimer_info, hr_timer);
+
+static enum hrtimer_restart vcpufreq_hrtimer_handler(struct hrtimer *hrtimer)
+{
+	struct hrtimer_info __percpu *hti = container_of(hrtimer, struct hrtimer_info, hrtimer);
+
+	BUG_ON(hti == NULL);
+
+	/* we rely on being executed on the proper CPU */
+	BUG_ON(hti->cpu != smp_processor_id());
+
+	hti->counter++;
+
+	/*
+	 * udelay is not always accurate for this;
+	 * unfortunately that is all we have
+	 */
+	udelay(hti->hogtime);
+
+	hrtimer_forward_now(hrtimer, hti->interval);
+
+	return HRTIMER_RESTART;
+}
+
+struct vpcufreq_hr_timer_set_freq_info {
+	unsigned int cpu;
+	unsigned int new_freq;
+	unsigned int old_freq;
+	int ret;
+};
+
+static void hr_timer_glue_set_freq(void *data)
+{
+	struct vpcufreq_hr_timer_set_freq_info *info = data;
+	struct hrtimer_info __percpu *hti;
+	int ret = 0;
+	unsigned int hog_timer_rate;
+	unsigned int freq = vcpufreq_get_maxspeed();
+	unsigned int hogtime = vcpufreq_get_hogtime();
+	u32 div, rem;
+
+	BUG_ON(smp_processor_id() != info->cpu);
+
+	hti = &per_cpu(hr_timer, info->cpu);
+
+	/* should never happen; checked before */
+	BUG_ON(info->new_freq == info->old_freq);
+
+	/* max freq; stop the timer */
+	if (info->new_freq == freq) {
+		pr_debug("#%d: shut down timer\n", info->cpu);
+		/* no error */
+		ret = 0;
+		goto hr_stop_timer;
+
+	}
+	
+	/* timer was stopped, we should start it */
+	if (info->old_freq != freq)
+		hrtimer_cancel(&hti->hrtimer);
+	
+	hog_timer_rate = div_u64((u64)(freq - info->new_freq) * 1000000, freq * hogtime);
+	pr_debug("#%d: hog timer rate = %u\n", info->cpu, hog_timer_rate);
+
+	div = div_u64_rem(1000000000 / hog_timer_rate, 1000000000, &rem);
+	hti->interval = ktime_set(div, rem);
+	hti->hogtime = hogtime;
+
+	pr_debug("#%d: internal set to %usec %unsec\n", info->cpu, div, rem);
+
+	/* timer bound to this CPU please */
+	ret = hrtimer_start(&hti->hrtimer, hti->interval, HRTIMER_MODE_PINNED);
+	if (ret != 0) {
+		pr_err("#%d: failed to start hrtimer (%d)\n", info->cpu, ret);
+		goto hr_stop_timer;
+	}
+
+	pr_debug("#%d: setting freq to %u\n", info->cpu, info->new_freq);
+
+	vcpufreq_set_speed(info->cpu, info->new_freq);
+	ret = 0;
+	goto out;
+
+hr_stop_timer:
+	/* clear everything */
+
+	hrtimer_cancel(&hti->hrtimer);
+
+	/* always return to max speed here */
+	vcpufreq_set_speed(info->cpu, freq);
+out:
+	info->ret = ret;
+}
+
+int vcpufreq_glue_set_freq(unsigned int cpu, unsigned int new_freq,
+		unsigned int old_freq)
+{
+	struct vpcufreq_hr_timer_set_freq_info info;
+	int ret;
+
+	memset(&info, 0, sizeof(info));
+
+	info.cpu = cpu;
+	info.new_freq = new_freq;
+	info.old_freq = old_freq;
+	info.ret = -EINVAL;
+
+	ret = smp_call_function_single(cpu, hr_timer_glue_set_freq, &info, 1);
+	if (ret != 0) {
+		pr_err("#%d: failed to call per CPU glue set freq\n", cpu);
+		return ret;
+	}
+
+	if (info.ret != 0) {
+		pr_err("#%d: failed to set freq\n", cpu);
+		return info.ret;
+	}
+
+	return 0;
+}
+
+static void hr_timer_glue_init_cpu(void *info)
+{
+	struct hrtimer_info __percpu *hti;
+	struct cpufreq_policy *policy = info;
+
+	BUG_ON(policy == NULL);
+
+	BUG_ON(smp_processor_id() != policy->cpu);
+
+	hti = &per_cpu(hr_timer, policy->cpu);
+
+	hrtimer_init(&hti->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_PINNED);
+	hti->hrtimer.function = vcpufreq_hrtimer_handler;
+
+	pr_info("#%d: %s\n", policy->cpu, __func__);
+}
+
+int vcpufreq_glue_init(struct cpufreq_policy *policy, int *freq)
+{
+	struct hrtimer_info __percpu *hti;
+	int ret = 0;
+
+	BUG_ON(freq == NULL);
+
+	if (*freq == 0) {
+		*freq = 1000000;	/* simulated 1GHz */
+	}
+
+	/* initialize per cpu structure */
+	hti = &per_cpu(hr_timer, policy->cpu);
+	memset(hti, 0, sizeof(*hti));
+	hti->cpu = policy->cpu;
+
+	ret = smp_call_function_single(policy->cpu, hr_timer_glue_init_cpu, policy, 1);
+	if (ret != 0) {
+		pr_err("#%d: failed to call per CPU glue init\n", policy->cpu);
+		goto error_out;
+	}
+
+	ret = 0;
+
+error_out:
+	return ret;
+}
+
+int vcpufreq_glue_exit(struct cpufreq_policy *policy)
+{
+	return 0;
+}
-- 
1.7.1




More information about the linaro-dev mailing list