| /* |
| * Copyright (C) 2018 MediaTek Inc. |
| * |
| * 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. |
| * |
| * 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 http://www.gnu.org/licenses/gpl-2.0.html for more details. |
| */ |
| |
| #define MET_STUNE_DEBUG 1 |
| |
| #if MET_STUNE_DEBUG |
| #include <mt-plat/met_drv.h> |
| #endif |
| |
| int stune_task_threshold; |
| static int default_stune_threshold; |
| |
| /* A lock for set stune_task_threshold */ |
| static raw_spinlock_t stune_lock; |
| |
| int set_stune_task_threshold(int threshold) |
| { |
| if (threshold > 1024 || threshold < -1) |
| return -EINVAL; |
| |
| raw_spin_lock(&stune_lock); |
| |
| if (threshold < 0) |
| stune_task_threshold = default_stune_threshold; |
| else |
| stune_task_threshold = threshold; |
| |
| raw_spin_unlock(&stune_lock); |
| |
| #if MET_STUNE_DEBUG |
| met_tag_oneshot(0, "sched_stune_threshold", stune_task_threshold); |
| #endif |
| |
| return 0; |
| } |
| |
| int sched_stune_task_threshold_handler(struct ctl_table *table, |
| int write, void __user *buffer, |
| size_t *lenp, loff_t *ppos) |
| { |
| int ret; |
| int old_threshold; |
| |
| old_threshold = stune_task_threshold; |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| if (!ret && write) { |
| ret = set_stune_task_threshold(stune_task_threshold); |
| if (ret) |
| stune_task_threshold = old_threshold; |
| } |
| |
| return ret; |
| } |
| |
| void calculate_default_stune_threshold(void) |
| { |
| const struct sched_group_energy *sge_core; |
| struct hmp_domain *domain; |
| int cluster_first_cpu = 0; |
| |
| raw_spin_lock_init(&stune_lock); |
| rcu_read_lock(); |
| for_each_hmp_domain_L_first(domain) { |
| cluster_first_cpu = cpumask_first(&domain->possible_cpus); |
| /* lowest capacity CPU in system */ |
| sge_core = cpu_core_energy(cluster_first_cpu); |
| if (!sge_core) { |
| pr_info("schedtune: no energy model data\n"); |
| } else { |
| default_stune_threshold = sge_core->cap_states[0].cap; |
| if (default_stune_threshold) { |
| set_stune_task_threshold(-1); |
| break; |
| } |
| } |
| } |
| rcu_read_unlock(); |
| } |
| |
| #if defined(CONFIG_UCLAMP_TASK_GROUP) && defined(CONFIG_SCHED_TUNE) |
| int uclamp_min_for_perf_idx(int idx, int min_value) |
| { |
| struct schedtune *st; |
| struct cgroup_subsys_state *css; |
| int ret = 0; |
| |
| if (min_value > SCHED_CAPACITY_SCALE) |
| return -ERANGE; |
| |
| if (!is_group_idx_valid(idx)) |
| return -ERANGE; |
| |
| st = allocated_group[idx]; |
| if (!st) |
| return -EINVAL; |
| |
| min_value = find_fit_capacity(min_value); |
| |
| css = &st->css; |
| |
| mutex_lock(&uclamp_mutex); |
| rcu_read_lock(); |
| |
| if (st->uclamp[UCLAMP_MIN].value == min_value) |
| goto out; |
| if (st->uclamp[UCLAMP_MAX].value < min_value) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| /* Update ST's reference count */ |
| uclamp_group_get(NULL, css, &st->uclamp[UCLAMP_MIN], |
| UCLAMP_MIN, min_value); |
| |
| /* Update effective clamps to track the most restrictive value */ |
| cpu_util_update(css, UCLAMP_MIN, st->uclamp[UCLAMP_MIN].group_id, |
| min_value); |
| out: |
| rcu_read_unlock(); |
| mutex_unlock(&uclamp_mutex); |
| |
| return ret; |
| } |
| |
| int uclamp_min_pct_for_perf_idx(int idx, int pct) |
| { |
| unsigned int min_value; |
| |
| if (pct < 0 || pct > 100) |
| return -ERANGE; |
| |
| min_value = scale_from_percent(pct); |
| return uclamp_min_for_perf_idx(idx, min_value); |
| } |
| #endif |
| |
| int boost_write_for_perf_idx(int idx, int boost_value) |
| { |
| struct schedtune *ct; |
| |
| if (boost_value < 0 || boost_value > 100) |
| printk_deferred("warn: boost value should be 0~100\n"); |
| |
| if (boost_value >= 100) |
| boost_value = 100; |
| else if (boost_value <= 0) |
| boost_value = 0; |
| |
| if (!is_group_idx_valid(idx)) |
| return -ERANGE; |
| |
| ct = allocated_group[idx]; |
| if (ct) { |
| rcu_read_lock(); |
| ct->boost = boost_value; |
| |
| /* Update CPU boost */ |
| schedtune_boostgroup_update(ct->idx, ct->boost); |
| rcu_read_unlock(); |
| |
| #if MET_STUNE_DEBUG |
| /* foreground */ |
| if (ct->idx == 1) |
| met_tag_oneshot(0, "sched_boost_fg", ct->boost); |
| /* top-app */ |
| if (ct->idx == 3) |
| met_tag_oneshot(0, "sched_boost_ta", ct->boost); |
| #endif |
| } else { |
| printk_deferred("error: stune group idx=%d is nonexistent\n", |
| idx); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int prefer_idle_for_perf_idx(int idx, int prefer_idle) |
| { |
| struct schedtune *ct = NULL; |
| |
| if (!is_group_idx_valid(idx)) |
| return -ERANGE; |
| |
| ct = allocated_group[idx]; |
| |
| if (!ct) |
| return -EINVAL; |
| |
| rcu_read_lock(); |
| ct->prefer_idle = prefer_idle; |
| rcu_read_unlock(); |
| |
| #if MET_STUNE_DEBUG |
| /* foreground */ |
| if (ct->idx == 1) |
| met_tag_oneshot(0, "sched_prefer_idle_fg", |
| ct->prefer_idle); |
| /* top-app */ |
| if (ct->idx == 3) |
| met_tag_oneshot(0, "sched_prefer_idle_ta", |
| ct->prefer_idle); |
| #endif |
| |
| return 0; |
| } |
| |
| int group_boost_read(int group_idx) |
| { |
| struct schedtune *ct; |
| int boost = 0; |
| |
| if (!is_group_idx_valid(group_idx)) |
| return -ERANGE; |
| |
| ct = allocated_group[group_idx]; |
| if (ct) { |
| rcu_read_lock(); |
| boost = ct->boost; |
| rcu_read_unlock(); |
| } |
| |
| return boost; |
| } |
| EXPORT_SYMBOL(group_boost_read); |
| |
| int group_prefer_idle_read(int group_idx) |
| { |
| struct schedtune *ct; |
| int prefer_idle = 0; |
| |
| if (!is_group_idx_valid(group_idx)) |
| return -ERANGE; |
| |
| ct = allocated_group[group_idx]; |
| if (ct) { |
| rcu_read_lock(); |
| prefer_idle = ct->prefer_idle; |
| rcu_read_unlock(); |
| } |
| |
| return prefer_idle; |
| } |
| EXPORT_SYMBOL(group_prefer_idle_read); |
| |
| #ifdef CONFIG_MTK_SCHED_RQAVG_KS |
| /* mtk: a linear boost value for tuning */ |
| int linear_real_boost(int linear_boost) |
| { |
| int target_cpu, usage; |
| int boost; |
| int ta_org_cap; |
| |
| sched_max_util_task(&target_cpu, NULL, &usage, NULL); |
| |
| ta_org_cap = capacity_orig_of(target_cpu); |
| |
| if (usage >= SCHED_CAPACITY_SCALE) |
| usage = SCHED_CAPACITY_SCALE; |
| |
| /* |
| * Conversion Formula of Linear Boost: |
| * |
| * margin = (usage * linear_boost)/100; |
| * margin = (original_cap - usage) * boost/100; |
| * so |
| * boost = (usage * linear_boost) / (original_cap - usage) |
| */ |
| if (ta_org_cap <= usage) { |
| /* If target cpu is saturated, consider bigger one */ |
| boost = (SCHED_CAPACITY_SCALE - usage) ? |
| (usage * linear_boost)/(SCHED_CAPACITY_SCALE - usage) : 0; |
| } else |
| boost = (usage * linear_boost)/(ta_org_cap - usage); |
| |
| return boost; |
| } |
| EXPORT_SYMBOL(linear_real_boost); |
| #endif |