Merge "soc: qcom: msm_perf: Add msm_performance module"

This commit is contained in:
qctecmdr 2020-02-21 23:21:34 -08:00 • committed by Gerrit - the friendly Code Review server
commit e447076b15
3 changed files with 521 additions and 0 deletions

View file

@ -483,6 +483,14 @@ config QCOM_GLINK_PKT
This enable the userspace clients to read and write to
some glink packets channel.
config MSM_PERFORMANCE
tristate "msm performance driver to support userspace fmin/fmax request"
help
This driver can restrict max freq or min freq of cpu cluster
when requested by the userspace by changing the cpufreq policy
fmin and fmax. The user space can request the cpu freq change by
writing cpu#:freq values
config QMP_DEBUGFS_CLIENT
bool "Debugfs Client to communicate with AOP using QMP protocol"
depends on DEBUG_FS

View file

@ -64,6 +64,7 @@ obj-$(CONFIG_QTI_SYSTEM_PM) += system_pm.o
obj-$(CONFIG_MSM_REMOTEQDSS) += remoteqdss.o
obj-$(CONFIG_MSM_SPSS_UTILS) += spss_utils.o
obj-$(CONFIG_MSM_IDLE_STATS) += lpm-stats.o
obj-$(CONFIG_MSM_PERFORMANCE) += msm_performance.o
obj-$(CONFIG_QTI_RPM_STATS_LOG) += rpmh_master_stat.o
obj-$(CONFIG_QPNP_PBS) += qpnp-pbs.o
obj-$(CONFIG_QCOM_WATCHDOG) += qcom_watchdog.o

View file

@ -0,0 +1,512 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
*/
#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 25
#define NODE_NAME_MAX_CHARS 16
enum cpu_clusters {
MIN = 0,
MID = 1,
MAX = 2,
CLUSTER_MAX
};
/* To handle cpufreq min/max request */
struct cpu_status {
unsigned int min;
unsigned int max;
};
static DEFINE_PER_CPU(struct cpu_status, msm_perf_cpu_stats);
static DEFINE_PER_CPU(struct freq_qos_request, qos_req_min);
static DEFINE_PER_CPU(struct freq_qos_request, qos_req_max);
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;
static unsigned int top_load[CLUSTER_MAX];
static unsigned int curr_cap[CLUSTER_MAX];
/*******************************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;
struct freq_qos_request *req;
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(msm_perf_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(msm_perf_cpu_stats, i);
if (cpufreq_get_policy(&policy, i))
continue;
if (cpu_online(i) && (policy.min != i_cpu_stats->min)) {
req = &per_cpu(qos_req_min, cpu);
if (freq_qos_update_request(req, i_cpu_stats->min) < 0)
break;
}
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 += scnprintf(buf + cnt, PAGE_SIZE - cnt,
"%d:%u ", cpu,
per_cpu(msm_perf_cpu_stats, cpu).min);
}
cnt += scnprintf(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;
struct freq_qos_request *req;
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(msm_perf_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(msm_perf_cpu_stats, i);
if (cpufreq_get_policy(&policy, i))
continue;
if (cpu_online(i) && (policy.max != i_cpu_stats->max)) {
req = &per_cpu(qos_req_max, cpu);
if (freq_qos_update_request(req, i_cpu_stats->max) < 0)
break;
}
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 += scnprintf(buf + cnt, PAGE_SIZE - cnt,
"%d:%u ", cpu,
per_cpu(msm_perf_cpu_stats, cpu).max);
}
cnt += scnprintf(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 scnprintf(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 scnprintf(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 scnprintf(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 ssize_t show_top_load_cluster(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%u %u %u\n",
top_load[MIN], top_load[MID],
top_load[MAX]);
}
static struct kobj_attribute cluster_top_load_attr =
__ATTR(top_load_cluster, 0444, show_top_load_cluster, NULL);
static ssize_t show_curr_cap_cluster(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%u %u %u\n",
curr_cap[MIN], curr_cap[MID],
curr_cap[MAX]);
}
static struct kobj_attribute cluster_curr_cap_attr =
__ATTR(curr_cap_cluster, 0444, show_curr_cap_cluster, NULL);
static struct attribute *notify_attrs[] = {
&big_nr_attr.attr,
&top_load_attr.attr,
&cluster_top_load_attr.attr,
&cluster_curr_cap_attr.attr,
NULL,
};
static struct attribute_group notify_attr_group = {
.attrs = notify_attrs,
};
static struct kobject *notify_kobj;
/*******************************sysfs ends************************************/
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");
sysfs_notify(notify_kobj, NULL, "top_load_cluster");
sysfs_notify(notify_kobj, NULL, "curr_cap_cluster");
}
static int msm_perf_core_ctl_notify(struct notifier_block *nb,
unsigned long unused,
void *data)
{
static unsigned int tld, nrb, i;
static unsigned int top_ld[CLUSTER_MAX], curr_cp[CLUSTER_MAX];
static DECLARE_WORK(sysfs_notify_work, nr_notify_userspace);
struct core_ctl_notif_data *d = data;
int cluster = 0;
nrb += d->nr_big;
tld += d->coloc_load_pct;
for (cluster = 0; cluster < CLUSTER_MAX; cluster++) {
top_ld[cluster] += d->ta_util_pct[cluster];
curr_cp[cluster] += d->cur_cap_pct[cluster];
}
i++;
if (i == POLL_INT) {
aggr_big_nr = ((nrb%POLL_INT) ? 1 : 0) + nrb/POLL_INT;
aggr_top_load = tld/POLL_INT;
for (cluster = 0; cluster < CLUSTER_MAX; cluster++) {
top_load[cluster] = top_ld[cluster]/POLL_INT;
curr_cap[cluster] = curr_cp[cluster]/POLL_INT;
top_ld[cluster] = 0;
curr_cp[cluster] = 0;
}
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 ret;
struct cpufreq_policy *policy;
struct freq_qos_request *req;
for_each_present_cpu(cpu) {
per_cpu(msm_perf_cpu_stats, cpu).max = UINT_MAX;
req = &per_cpu(qos_req_min, cpu);
policy = cpufreq_cpu_get(cpu);
ret = freq_qos_add_request(&policy->constraints, req,
FREQ_QOS_MIN, policy->min);
if (ret < 0) {
pr_err("%s: Failed to add min freq constraint (%d)\n",
__func__, ret);
return ret;
}
req = &per_cpu(qos_req_max, cpu);
ret = freq_qos_add_request(&policy->constraints, req,
FREQ_QOS_MAX, policy->max);
if (ret < 0) {
pr_err("%s: Failed to add max freq constraint (%d)\n",
__func__, ret);
return ret;
}
}
ret = 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);