summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRohit Gupta <rohgup@codeaurora.org>2014-11-03 19:42:34 -0800
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-23 21:22:19 -0700
commit5c8690eb75f10be6fd8a6b1cfff38eb9043da3bf (patch)
tree5a1b22250c61653547bbe12b0448e0fb62d0854f
parentc4d4ef627d935f0fdcf68769d92fd16c80912f64 (diff)
soc: qcom: Add cpufreq limits voting mechanism to msm_perf module
Userspace might need to set scaling_min/scaling_max for different usecases based on perf/power requirement. Currently available userspace nodes exposed by cpufreq framework might not be appropriate in situations where userspace wants to revert back its vote and the CPU in question is offline. Add support to msm_performance module to set min/max limits on cpufreq for different CPUs such that the module maintains the userspace vote across hotplugs and applies it whenever CPU policy gets updated (after it comes online). Change-Id: I1fab2a8b63d4ba13465ecd36adecddcb7c63b43a Signed-off-by: Rohit Gupta <rohgup@codeaurora.org>
-rw-r--r--drivers/soc/qcom/msm_performance.c199
1 files changed, 198 insertions, 1 deletions
diff --git a/drivers/soc/qcom/msm_performance.c b/drivers/soc/qcom/msm_performance.c
index f307c664615e..56de8c59fb7f 100644
--- a/drivers/soc/qcom/msm_performance.c
+++ b/drivers/soc/qcom/msm_performance.c
@@ -16,6 +16,7 @@
#include <linux/cpu.h>
#include <linux/moduleparam.h>
#include <linux/cpumask.h>
+#include <linux/cpufreq.h>
#include <trace/events/power.h>
@@ -37,6 +38,13 @@ struct delayed_work try_hotplug_work;
static unsigned int num_online_managed(void);
+/* To handle cpufreq min/max request */
+struct cpu_status {
+ unsigned int min;
+ unsigned int max;
+};
+static DEFINE_PER_CPU(struct cpu_status, cpu_stats);
+
static int set_max_cpus(const char *buf, const struct kernel_param *kp)
{
unsigned int val;
@@ -119,6 +127,192 @@ static unsigned int num_online_managed(void)
}
/*
+ * Userspace sends cpu#:min_freq_value to vote for min_freq_value as the new
+ * scaling_min. To withdraw its vote it needs to enter cpu#:0
+ */
+static int set_cpu_min_freq(const char *buf, const struct kernel_param *kp)
+{
+ int i, j, ntokens = 0;
+ unsigned int val, cpu;
+ const char *cp = buf;
+ struct cpu_status *i_cpu_stats;
+ struct cpufreq_policy policy;
+ cpumask_var_t limit_mask;
+ int ret;
+
+ while ((cp = strpbrk(cp + 1, " :")))
+ ntokens++;
+
+ /* CPU:value pair */
+ if (!(ntokens % 2))
+ return -EINVAL;
+
+ cp = buf;
+ cpumask_clear(limit_mask);
+ for (i = 0; i < ntokens; i += 2) {
+ if (sscanf(cp, "%u:%u", &cpu, &val) != 2)
+ return -EINVAL;
+ if (cpu > num_present_cpus())
+ return -EINVAL;
+
+ i_cpu_stats = &per_cpu(cpu_stats, cpu);
+
+ i_cpu_stats->min = val;
+ cpumask_set_cpu(cpu, limit_mask);
+
+ cp = strnchr(cp, strlen(cp), ' ');
+ cp++;
+ }
+
+ /*
+ * Since on synchronous systems policy is shared amongst multiple
+ * CPUs only one CPU needs to be updated for the limit to be
+ * reflected for the entire cluster. We can avoid updating the policy
+ * of other CPUs in the cluster once it is done for at least one CPU
+ * in the cluster
+ */
+ get_online_cpus();
+ for_each_cpu(i, limit_mask) {
+ i_cpu_stats = &per_cpu(cpu_stats, i);
+
+ if (cpufreq_get_policy(&policy, i))
+ continue;
+
+ if (cpu_online(i) && (policy.min != i_cpu_stats->min)) {
+ ret = cpufreq_update_policy(i);
+ if (ret)
+ continue;
+ }
+ for_each_cpu(j, policy.related_cpus)
+ cpumask_clear_cpu(j, limit_mask);
+ }
+ put_online_cpus();
+
+ return 0;
+}
+
+static int get_cpu_min_freq(char *buf, const struct kernel_param *kp)
+{
+ int cnt = 0, cpu;
+
+ for_each_present_cpu(cpu) {
+ cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
+ "%d:%u ", cpu, per_cpu(cpu_stats, cpu).min);
+ }
+ cnt += snprintf(buf + cnt, PAGE_SIZE - cnt, "\n");
+ return cnt;
+}
+
+static const struct kernel_param_ops param_ops_cpu_min_freq = {
+ .set = set_cpu_min_freq,
+ .get = get_cpu_min_freq,
+};
+module_param_cb(cpu_min_freq, &param_ops_cpu_min_freq, NULL, 0644);
+
+/*
+ * Userspace sends cpu#:max_freq_value to vote for max_freq_value as the new
+ * scaling_max. To withdraw its vote it needs to enter cpu#:UINT_MAX
+ */
+static int set_cpu_max_freq(const char *buf, const struct kernel_param *kp)
+{
+ int i, j, ntokens = 0;
+ unsigned int val, cpu;
+ const char *cp = buf;
+ struct cpu_status *i_cpu_stats;
+ struct cpufreq_policy policy;
+ cpumask_var_t limit_mask;
+ int ret;
+
+ while ((cp = strpbrk(cp + 1, " :")))
+ ntokens++;
+
+ /* CPU:value pair */
+ if (!(ntokens % 2))
+ return -EINVAL;
+
+ cp = buf;
+ cpumask_clear(limit_mask);
+ for (i = 0; i < ntokens; i += 2) {
+ if (sscanf(cp, "%u:%u", &cpu, &val) != 2)
+ return -EINVAL;
+ if (cpu > num_present_cpus())
+ return -EINVAL;
+
+ i_cpu_stats = &per_cpu(cpu_stats, cpu);
+
+ i_cpu_stats->max = val;
+ cpumask_set_cpu(cpu, limit_mask);
+
+ cp = strnchr(cp, strlen(cp), ' ');
+ cp++;
+ }
+
+ get_online_cpus();
+ for_each_cpu(i, limit_mask) {
+ i_cpu_stats = &per_cpu(cpu_stats, i);
+ if (cpufreq_get_policy(&policy, i))
+ continue;
+
+ if (cpu_online(i) && (policy.max != i_cpu_stats->max)) {
+ ret = cpufreq_update_policy(i);
+ if (ret)
+ continue;
+ }
+ for_each_cpu(j, policy.related_cpus)
+ cpumask_clear_cpu(j, limit_mask);
+ }
+ put_online_cpus();
+
+ return 0;
+}
+
+static int get_cpu_max_freq(char *buf, const struct kernel_param *kp)
+{
+ int cnt = 0, cpu;
+
+ for_each_present_cpu(cpu) {
+ cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
+ "%d:%u ", cpu, per_cpu(cpu_stats, cpu).max);
+ }
+ cnt += snprintf(buf + cnt, PAGE_SIZE - cnt, "\n");
+ return cnt;
+}
+
+static const struct kernel_param_ops param_ops_cpu_max_freq = {
+ .set = set_cpu_max_freq,
+ .get = get_cpu_max_freq,
+};
+module_param_cb(cpu_max_freq, &param_ops_cpu_max_freq, NULL, 0644);
+
+static int perf_adjust_notify(struct notifier_block *nb, unsigned long val,
+ void *data)
+{
+ struct cpufreq_policy *policy = data;
+ unsigned int cpu = policy->cpu;
+ struct cpu_status *cpu_st = &per_cpu(cpu_stats, cpu);
+ unsigned int min = cpu_st->min, max = cpu_st->max;
+
+
+ if (val != CPUFREQ_ADJUST)
+ return NOTIFY_OK;
+
+ pr_debug("msm_perf: CPU%u policy before: %u:%u kHz\n", cpu,
+ policy->min, policy->max);
+ pr_debug("msm_perf: CPU%u seting min:max %u:%u kHz\n", cpu, min, max);
+
+ cpufreq_verify_within_limits(policy, min, max);
+
+ pr_debug("msm_perf: CPU%u policy after: %u:%u kHz\n", cpu,
+ policy->min, policy->max);
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block perf_cpufreq_nb = {
+ .notifier_call = perf_adjust_notify,
+};
+
+/*
* try_hotplug tries to online/offline cores based on the current requirement.
* It loops through the currently managed CPUs and tries to online/offline
* them until the max_cpus criteria is met.
@@ -213,13 +407,16 @@ static struct notifier_block __refdata msm_performance_cpu_notifier = {
static int __init msm_performance_init(void)
{
+ int cpu;
INIT_DELAYED_WORK(&try_hotplug_work, try_hotplug);
mutex_init(&managed_cpus_lock);
cpumask_clear(&managed_offline_cpus);
+ cpufreq_register_notifier(&perf_cpufreq_nb, CPUFREQ_POLICY_NOTIFIER);
+ for_each_present_cpu(cpu)
+ per_cpu(cpu_stats, cpu).max = UINT_MAX;
register_cpu_notifier(&msm_performance_cpu_notifier);
return 0;
}
late_initcall(msm_performance_init);
-