Files
android_kernel_eebbk_sm6150/drivers/soc/qcom/msm_performance.c
T

468 lines
11 KiB
C

/*
* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/init.h>
#include <linux/notifier.h>
#include <linux/cpu.h>
#include <linux/moduleparam.h>
#include <linux/cpumask.h>
#include <linux/cpufreq.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/tick.h>
#include <trace/events/power.h>
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/kthread.h>
#include <linux/sched/core_ctl.h>
/*
* Sched will provide the data for every 20ms window,
* will collect the data for 15 windows(300ms) and then update
* sysfs nodes with aggregated data
*/
#define POLL_INT 15
/* To handle cpufreq min/max request */
struct cpu_status {
unsigned int min;
unsigned int max;
};
static DEFINE_PER_CPU(struct cpu_status, cpu_stats);
struct events {
spinlock_t cpu_hotplug_lock;
bool cpu_hotplug;
bool init_success;
};
static struct events events_group;
static struct task_struct *events_notify_thread;
static unsigned int aggr_big_nr;
static unsigned int aggr_top_load;
/*******************************sysfs start************************************/
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;
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() - 1))
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))
cpufreq_update_policy(i);
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);
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;
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() - 1))
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))
cpufreq_update_policy(i);
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 struct kobject *events_kobj;
static ssize_t show_cpu_hotplug(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "\n");
}
static struct kobj_attribute cpu_hotplug_attr =
__ATTR(cpu_hotplug, 0444, show_cpu_hotplug, NULL);
static struct attribute *events_attrs[] = {
&cpu_hotplug_attr.attr,
NULL,
};
static struct attribute_group events_attr_group = {
.attrs = events_attrs,
};
static ssize_t show_big_nr(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", aggr_big_nr);
}
static struct kobj_attribute big_nr_attr =
__ATTR(aggr_big_nr, 0444, show_big_nr, NULL);
static ssize_t show_top_load(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", aggr_top_load);
}
static struct kobj_attribute top_load_attr =
__ATTR(aggr_top_load, 0444, show_top_load, NULL);
static struct attribute *notify_attrs[] = {
&big_nr_attr.attr,
&top_load_attr.attr,
NULL,
};
static struct attribute_group notify_attr_group = {
.attrs = notify_attrs,
};
static struct kobject *notify_kobj;
/*******************************sysfs ends************************************/
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,
};
static int hotplug_notify(unsigned int cpu)
{
unsigned long flags;
if (events_group.init_success) {
spin_lock_irqsave(&(events_group.cpu_hotplug_lock), flags);
events_group.cpu_hotplug = true;
spin_unlock_irqrestore(&(events_group.cpu_hotplug_lock), flags);
wake_up_process(events_notify_thread);
}
return 0;
}
static int events_notify_userspace(void *data)
{
unsigned long flags;
bool notify_change;
while (1) {
set_current_state(TASK_INTERRUPTIBLE);
spin_lock_irqsave(&(events_group.cpu_hotplug_lock), flags);
if (!events_group.cpu_hotplug) {
spin_unlock_irqrestore(&(events_group.cpu_hotplug_lock),
flags);
schedule();
if (kthread_should_stop())
break;
spin_lock_irqsave(&(events_group.cpu_hotplug_lock),
flags);
}
set_current_state(TASK_RUNNING);
notify_change = events_group.cpu_hotplug;
events_group.cpu_hotplug = false;
spin_unlock_irqrestore(&(events_group.cpu_hotplug_lock), flags);
if (notify_change)
sysfs_notify(events_kobj, NULL, "cpu_hotplug");
}
return 0;
}
static int init_notify_group(void)
{
int ret;
struct kobject *module_kobj;
module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME);
if (!module_kobj) {
pr_err("msm_perf: Couldn't find module kobject\n");
return -ENOENT;
}
notify_kobj = kobject_create_and_add("notify", module_kobj);
if (!notify_kobj) {
pr_err("msm_perf: Failed to add notify_kobj\n");
return -ENOMEM;
}
ret = sysfs_create_group(notify_kobj, &notify_attr_group);
if (ret) {
kobject_put(notify_kobj);
pr_err("msm_perf: Failed to create sysfs\n");
return ret;
}
return 0;
}
static int init_events_group(void)
{
int ret;
struct kobject *module_kobj;
module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME);
if (!module_kobj) {
pr_err("msm_perf: Couldn't find module kobject\n");
return -ENOENT;
}
events_kobj = kobject_create_and_add("events", module_kobj);
if (!events_kobj) {
pr_err("msm_perf: Failed to add events_kobj\n");
return -ENOMEM;
}
ret = sysfs_create_group(events_kobj, &events_attr_group);
if (ret) {
pr_err("msm_perf: Failed to create sysfs\n");
return ret;
}
spin_lock_init(&(events_group.cpu_hotplug_lock));
events_notify_thread = kthread_run(events_notify_userspace,
NULL, "msm_perf:events_notify");
if (IS_ERR(events_notify_thread))
return PTR_ERR(events_notify_thread);
events_group.init_success = true;
return 0;
}
static void nr_notify_userspace(struct work_struct *work)
{
sysfs_notify(notify_kobj, NULL, "aggr_top_load");
sysfs_notify(notify_kobj, NULL, "aggr_big_nr");
}
static int msm_perf_core_ctl_notify(struct notifier_block *nb,
unsigned long unused,
void *data)
{
static unsigned int tld, nrb, i;
static DECLARE_WORK(sysfs_notify_work, nr_notify_userspace);
struct core_ctl_notif_data *d = data;
nrb += d->nr_big;
tld += d->coloc_load_pct;
i++;
if (i == POLL_INT) {
aggr_big_nr = ((nrb%POLL_INT) ? 1 : 0) + nrb/POLL_INT;
aggr_top_load = tld/POLL_INT;
tld = 0;
nrb = 0;
i = 0;
schedule_work(&sysfs_notify_work);
}
return NOTIFY_OK;
}
static struct notifier_block msm_perf_nb = {
.notifier_call = msm_perf_core_ctl_notify
};
static bool core_ctl_register;
static int set_core_ctl_register(const char *buf, const struct kernel_param *kp)
{
int ret;
bool old_val = core_ctl_register;
ret = param_set_bool(buf, kp);
if (ret < 0)
return ret;
if (core_ctl_register == old_val)
return 0;
if (core_ctl_register)
core_ctl_notifier_register(&msm_perf_nb);
else
core_ctl_notifier_unregister(&msm_perf_nb);
return 0;
}
static const struct kernel_param_ops param_ops_cc_register = {
.set = set_core_ctl_register,
.get = param_get_bool,
};
module_param_cb(core_ctl_register, &param_ops_cc_register,
&core_ctl_register, 0644);
static int __init msm_performance_init(void)
{
unsigned int cpu;
int rc;
cpufreq_register_notifier(&perf_cpufreq_nb, CPUFREQ_POLICY_NOTIFIER);
for_each_present_cpu(cpu)
per_cpu(cpu_stats, cpu).max = UINT_MAX;
rc = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE,
"msm_performance_cpu_hotplug",
hotplug_notify,
NULL);
init_events_group();
init_notify_group();
return 0;
}
late_initcall(msm_performance_init);