diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 71f1e2f843aa..5a563f402de3 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -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 diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index fc945ed20bce..7a85fb419909 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -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 diff --git a/drivers/soc/qcom/msm_performance.c b/drivers/soc/qcom/msm_performance.c new file mode 100644 index 000000000000..ac0d4e480b54 --- /dev/null +++ b/drivers/soc/qcom/msm_performance.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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, ¶m_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, ¶m_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, ¬ify_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, ¶m_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);