You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1587 lines
37 KiB

/*
* Copyright (C) 2016 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.
*/
#include <linux/stop_machine.h>
static inline unsigned long task_util(struct task_struct *p);
static int select_max_spare_capacity(struct task_struct *p, int target);
int cpu_eff_tp = 1024;
unsigned long long big_cpu_eff_tp = 1024;
#ifndef cpu_isolated
#define cpu_isolated(cpu) 0
#endif
#if defined(CONFIG_MACH_MT6763) || defined(CONFIG_MACH_MT6758)
#ifdef CONFIG_MTK_SCHED_EAS_POWER_SUPPORT
/* MT6763: 2 gears. cluster 0 & 1 is buck shared. */
static int share_buck[3] = {1, 0, 2};
/* cpu7 is L+ */
#endif
int l_plus_cpu = 7;
#elif defined(CONFIG_MACH_MT6799)
#ifdef CONFIG_MTK_SCHED_EAS_POWER_SUPPORT
/* MT6799: 3 gears. cluster 0 & 2 is buck shared. */
static int share_buck[3] = {2, 1, 0};
#endif
/* No L+ */
int l_plus_cpu = -1;
#elif defined(CONFIG_MACH_MT6765) || defined(CONFIG_MACH_MT6762)
#ifdef CONFIG_MTK_SCHED_EAS_POWER_SUPPORT
static int share_buck[3] = {1, 0, 2};
#endif
int l_plus_cpu = -1;
#elif defined(CONFIG_MACH_MT6779)
#ifdef CONFIG_MTK_SCHED_EAS_POWER_SUPPORT
static int share_buck[2] = {2, 1};
#define ARM_V8_2
#endif
int l_plus_cpu = -1;
#else
#ifdef CONFIG_MTK_SCHED_EAS_POWER_SUPPORT
/* no buck shared */
static int share_buck[3] = {0, 1, 2};
#endif
int l_plus_cpu = -1;
#endif
#define CCI_ID (arch_get_nr_clusters())
static void
update_system_overutilized(struct lb_env *env)
{
unsigned long group_util;
bool intra_overutil = false;
unsigned long max_capacity;
struct sched_group *group = env->sd->groups;
int this_cpu;
int max_cap_orig_cpu;
bool overutilized = sd_overutilized(env->sd);
int i;
if (!sched_feat(SCHED_MTK_EAS))
return;
this_cpu = smp_processor_id();
max_cap_orig_cpu = cpu_rq(this_cpu)->rd->max_cap_orig_cpu;
if (max_cap_orig_cpu > -1)
max_capacity = capacity_orig_of(max_cap_orig_cpu);
else
max_capacity = cpu_rq(this_cpu)->rd->max_cpu_capacity.val;
do {
group_util = 0;
for_each_cpu_and(i, sched_group_span(group), env->cpus) {
if (cpu_isolated(i))
continue;
group_util += cpu_util(i);
if (cpu_overutilized(i)) {
if (capacity_orig_of(i) < max_capacity) {
intra_overutil = true;
break;
}
}
}
/*
* A capacity base hint for over-utilization.
* Not to trigger system overutiled if heavy tasks
* in Big.cluster, so
* add the free room(20%) of Big.cluster is impacted which means
* system-wide over-utilization,
* that considers whole cluster not single cpu
*/
if (group->group_weight > 1 && (group->sgc->capacity * 1024 <
group_util * 1280)) {
intra_overutil = true;
break;
}
group = group->next;
} while (group != env->sd->groups && !intra_overutil);
if (overutilized != intra_overutil) {
if (intra_overutil == true)
set_sd_overutilized(env->sd);
else
clear_sd_overutilized(env->sd);
}
}
bool is_intra_domain(int prev, int target)
{
#ifdef CONFIG_ARM64
return (cpu_topology[prev].cluster_id ==
cpu_topology[target].cluster_id);
#else
return (cpu_topology[prev].socket_id ==
cpu_topology[target].socket_id);
#endif
}
static int
___select_idle_sibling(struct task_struct *p, int prev_cpu, int new_cpu)
{
if (sched_feat(SCHED_MTK_EAS)) {
#ifdef CONFIG_SCHED_TUNE
bool prefer_idle = schedtune_prefer_idle(p) > 0;
#else
bool prefer_idle = true;
#endif
int idle_cpu;
idle_cpu = find_best_idle_cpu(p, prefer_idle);
if (idle_cpu >= 0)
new_cpu = idle_cpu;
else
new_cpu = select_max_spare_capacity(p, new_cpu);
} else
new_cpu = select_idle_sibling(p, prev_cpu, new_cpu);
return new_cpu;
}
/* To find a CPU with max spare capacity in the same cluster with target */
static
int select_max_spare_capacity(struct task_struct *p, int target)
{
unsigned long int max_spare_capacity = 0;
int max_spare_cpu = -1;
struct cpumask cls_cpus;
int cid = arch_get_cluster_id(target); /* cid of target CPU */
int cpu = task_cpu(p);
struct cpumask *tsk_cpus_allow = &p->cpus_allowed;
/* If the prevous cpu is cache affine and idle, choose it first. */
if (cpu != l_plus_cpu && cpu != target &&
cpus_share_cache(cpu, target) &&
idle_cpu(cpu) && !cpu_isolated(cpu))
return cpu;
arch_get_cluster_cpus(&cls_cpus, cid);
/* Otherwise, find a CPU with max spare-capacity in cluster */
for_each_cpu_and(cpu, tsk_cpus_allow, &cls_cpus) {
unsigned long int new_usage;
unsigned long int spare_cap;
if (!cpu_online(cpu))
continue;
if (cpu_isolated(cpu))
continue;
#ifdef CONFIG_MTK_SCHED_INTEROP
if (cpu_rq(cpu)->rt.rt_nr_running &&
likely(!is_rt_throttle(cpu)))
continue;
#endif
#ifdef CONFIG_SCHED_WALT
if (walt_cpu_high_irqload(cpu))
continue;
#endif
if (idle_cpu(cpu))
return cpu;
new_usage = cpu_util(cpu) + task_util(p);
if (new_usage >= capacity_of(cpu))
spare_cap = 0;
else /* consider RT/IRQ capacity reduction */
spare_cap = (capacity_of(cpu) - new_usage);
/* update CPU with max spare capacity */
if ((long int)spare_cap > (long int)max_spare_capacity) {
max_spare_cpu = cpu;
max_spare_capacity = spare_cap;
}
}
/* if max_spare_cpu exist, choose it. */
if (max_spare_cpu > -1)
return max_spare_cpu;
else
return task_cpu(p);
}
/*
* @p: the task want to be located at.
*
* Return:
*
* cpu id or
* -1 if target CPU is not found
*/
int find_best_idle_cpu(struct task_struct *p, bool prefer_idle)
{
int iter_cpu;
int best_idle_cpu = -1;
struct cpumask *tsk_cpus_allow = &p->cpus_allowed;
struct hmp_domain *domain;
for_each_hmp_domain_L_first(domain) {
for_each_cpu(iter_cpu, &domain->possible_cpus) {
/* tsk with prefer idle to find bigger idle cpu */
int i = ((prefer_idle &&
(task_util(p) > stune_task_threshold)))
? nr_cpu_ids-iter_cpu-1 : iter_cpu;
if (!cpu_online(i) || cpu_isolated(i) ||
!cpumask_test_cpu(i, tsk_cpus_allow))
continue;
#ifdef CONFIG_MTK_SCHED_INTEROP
if (cpu_rq(i)->rt.rt_nr_running &&
likely(!is_rt_throttle(i)))
continue;
#endif
/* favoring tasks that prefer idle cpus
* to improve latency.
*/
if (idle_cpu(i)) {
best_idle_cpu = i;
break;
}
}
}
return best_idle_cpu;
}
static int init_cpu_info(void)
{
int i;
for (i = 0; i < nr_cpu_ids; i++) {
unsigned long capacity = SCHED_CAPACITY_SCALE;
if (cpu_core_energy(i)) {
int idx = cpu_core_energy(i)->nr_cap_states - 1;
capacity = cpu_core_energy(i)->cap_states[idx].cap;
}
}
return 0;
}
late_initcall_sync(init_cpu_info)
#ifdef CONFIG_MTK_UNIFY_POWER
void set_sched_turn_point_cap(void)
{
int turn_point_idx;
struct hmp_domain *domain;
int cpu;
const struct sched_group_energy *sge_core;
domain = list_entry(hmp_domains.prev, struct hmp_domain, hmp_domains);
cpu = cpumask_first(&domain->possible_cpus);
sge_core = cpu_core_energy(cpu);
turn_point_idx = max(upower_get_turn_point() - 1, 0);
cpu_eff_tp = sge_core->cap_states[turn_point_idx].cap;
}
#else
void set_sched_turn_point_cap(void)
{
return;
}
#endif
#if defined(CONFIG_SCHED_HMP) || defined(CONFIG_MTK_IDLE_BALANCE_ENHANCEMENT)
/*
* Heterogenous Multi-Processor (HMP) Global Load Balance
*/
static DEFINE_SPINLOCK(hmp_force_migration);
/*
* For debugging purpose,
* to depart functions of cpu_stop to make call_stack clear.
*/
static int hmp_idle_pull_cpu_stop(void *data)
{
int ret;
struct task_struct *p = ((struct rq *)data)->migrate_task;
ret = active_load_balance_cpu_stop(data);
put_task_struct(p);
return ret;
}
static int
migrate_running_task(int this_cpu, struct task_struct *p, struct rq *target)
{
unsigned long flags;
unsigned int force = 0;
/* now we have a candidate */
raw_spin_lock_irqsave(&target->lock, flags);
if (!target->active_balance &&
(task_rq(p) == target) && !cpu_park(cpu_of(target)) &&
p->state != TASK_DEAD) {
get_task_struct(p);
target->push_cpu = this_cpu;
target->migrate_task = p;
trace_sched_hmp_migrate(p, target->push_cpu, MIGR_IDLE_RUNNING);
#ifdef CONFIG_SCHED_HMP
hmp_next_up_delay(&p->se, target->push_cpu);
#endif
target->active_balance = MIGR_IDLE_RUNNING; /* idle pull */
force = 1;
}
raw_spin_unlock_irqrestore(&target->lock, flags);
if (force) {
if (!stop_one_cpu_nowait(cpu_of(target),
hmp_idle_pull_cpu_stop,
target, &target->active_balance_work)) {
put_task_struct(p); /* out of rq->lock */
raw_spin_lock_irqsave(&target->lock, flags);
target->active_balance = 0;
target->migrate_task = NULL;
force = 0;
raw_spin_unlock_irqrestore(&target->lock, flags);
}
}
return force;
}
#endif
inline unsigned long cluster_max_capacity(void)
{
struct hmp_domain *domain;
unsigned int max_capacity = 0;
for_each_hmp_domain_L_first(domain) {
int cpu;
unsigned long capacity;
cpu = cpumask_first(&domain->possible_cpus);
capacity = capacity_of(cpu);
if (capacity > max_capacity)
max_capacity = capacity;
}
return max_capacity;
}
inline unsigned long task_uclamped_min_w_ceiling(struct task_struct *p)
{
unsigned long max_capacity = cluster_max_capacity();
return min_t(unsigned int, uclamp_task_effective_util(p, UCLAMP_MIN),
max_capacity);
}
/* Calculte util with DVFS margin */
inline unsigned int freq_util(unsigned long util)
{
return util * 10 >> 3;
}
#ifdef CONFIG_MTK_IDLE_BALANCE_ENHANCEMENT
bool idle_lb_enhance(struct task_struct *p, int cpu)
{
int target_capacity = capacity_orig_of(cpu);
if (schedtune_prefer_idle(p))
return 1;
if (uclamp_task_effective_util(p, UCLAMP_MIN) > target_capacity)
return 1;
return 0;
}
/* must hold runqueue lock for queue se is currently on */
static const int idle_prefer_max_tasks = 5;
static struct sched_entity
*get_idle_prefer_task(int cpu, int target_cpu, int check_min_cap,
struct task_struct **backup_task, int *backup_cpu)
{
int num_tasks = idle_prefer_max_tasks;
const struct cpumask *hmp_target_mask = NULL;
int src_capacity;
unsigned int util_min;
struct cfs_rq *cfs_rq;
struct sched_entity *se;
if (target_cpu >= 0)
hmp_target_mask = cpumask_of(target_cpu);
else
return NULL;
/* The currently running task is not on the runqueue
* a. idle prefer
* b. task_capacity > belonged CPU
*/
src_capacity = capacity_orig_of(cpu);
cfs_rq = &cpu_rq(cpu)->cfs;
se = __pick_first_entity(cfs_rq);
while (num_tasks && se) {
if (entity_is_task(se) &&
cpumask_intersects(hmp_target_mask,
&(task_of(se)->cpus_allowed))) {
struct task_struct *p;
p = task_of(se);
util_min = uclamp_task_effective_util(p, UCLAMP_MIN);
#ifdef CONFIG_MTK_SCHED_BOOST
if (!task_prefer_match_on_cpu(p, cpu, target_cpu))
return se;
#endif
if (check_min_cap && util_min >= src_capacity)
return se;
if (schedtune_prefer_idle(task_of(se)) &&
cpu_rq(cpu)->nr_running > 1) {
if (!check_min_cap)
return se;
if (backup_task && !*backup_task) {
*backup_cpu = cpu;
/* get task and selection inside
* rq lock
*/
*backup_task = task_of(se);
get_task_struct(*backup_task);
}
}
}
se = __pick_next_entity(se);
num_tasks--;
}
return NULL;
}
static void
hmp_slowest_idle_prefer_pull(int this_cpu, struct task_struct **p,
struct rq **target)
{
int cpu, backup_cpu;
struct sched_entity *se = NULL;
struct task_struct *backup_task = NULL;
struct hmp_domain *domain;
struct list_head *pos;
int selected = 0;
struct rq *rq;
unsigned long flags;
int check_min_cap;
/* 1. select a runnable task
* idle prefer
*
* order: fast to slow hmp domain
*/
check_min_cap = 0;
list_for_each(pos, &hmp_domains) {
domain = list_entry(pos, struct hmp_domain, hmp_domains);
for_each_cpu(cpu, &domain->cpus) {
if (cpu == this_cpu)
continue;
rq = cpu_rq(cpu);
raw_spin_lock_irqsave(&rq->lock, flags);
se = get_idle_prefer_task(cpu, this_cpu,
check_min_cap, &backup_task, &backup_cpu);
if (se && entity_is_task(se) &&
cpumask_test_cpu(this_cpu,
&(task_of(se))->cpus_allowed)) {
selected = 1;
/* get task and selection inside rq lock */
*p = task_of(se);
get_task_struct(*p);
*target = rq;
}
raw_spin_unlock_irqrestore(&rq->lock, flags);
if (selected) {
/* To put task out of rq lock */
if (backup_task)
put_task_struct(backup_task);
return;
}
}
}
if (backup_task) {
*target = cpu_rq(backup_cpu);
return;
}
}
DECLARE_PER_CPU(struct hmp_domain *, hmp_cpu_domain);
static void
hmp_fastest_idle_prefer_pull(int this_cpu, struct task_struct **p,
struct rq **target)
{
int cpu, backup_cpu;
struct sched_entity *se = NULL;
struct task_struct *backup_task = NULL;
struct hmp_domain *hmp_domain = NULL, *domain;
struct list_head *pos;
int selected = 0;
struct rq *rq;
unsigned long flags;
int target_capacity;
int check_min_cap;
int turning;
hmp_domain = per_cpu(hmp_cpu_domain, this_cpu);
/* 1. select a runnable task
*
* first candidate:
* capacity_min in slow domain
*
* order: target->next to slow hmp domain
*/
check_min_cap = 1;
list_for_each(pos, &hmp_domain->hmp_domains) {
domain = list_entry(pos, struct hmp_domain, hmp_domains);
for_each_cpu(cpu, &domain->cpus) {
if (cpu == this_cpu)
continue;
rq = cpu_rq(cpu);
raw_spin_lock_irqsave(&rq->lock, flags);
se = get_idle_prefer_task(cpu, this_cpu,
check_min_cap, &backup_task, &backup_cpu);
if (se && entity_is_task(se) &&
cpumask_test_cpu(this_cpu,
&(task_of(se))->cpus_allowed)) {
selected = 1;
/* get task and selection inside rq lock */
*p = task_of(se);
get_task_struct(*p);
*target = rq;
}
raw_spin_unlock_irqrestore(&rq->lock, flags);
if (selected) {
/* To put task out of rq lock */
if (backup_task)
put_task_struct(backup_task);
return;
}
}
if (list_is_last(pos, &hmp_domains))
break;
}
/* backup candidate:
* idle prefer
*
* order: fastest to target hmp domain
*/
check_min_cap = 0;
list_for_each(pos, &hmp_domains) {
domain = list_entry(pos, struct hmp_domain, hmp_domains);
for_each_cpu(cpu, &domain->cpus) {
if (cpu == this_cpu)
continue;
rq = cpu_rq(cpu);
raw_spin_lock_irqsave(&rq->lock, flags);
se = get_idle_prefer_task(cpu, this_cpu,
check_min_cap, &backup_task, &backup_cpu);
if (se && entity_is_task(se) &&
cpumask_test_cpu(this_cpu,
&(task_of(se)->cpus_allowed))) {
selected = 1;
/* get task and selection inside rq lock */
*p = task_of(se);
get_task_struct(*p);
*target = rq;
}
raw_spin_unlock_irqrestore(&rq->lock, flags);
if (selected) {
/* To put task out of rq lock */
if (backup_task)
put_task_struct(backup_task);
return;
}
}
if (cpumask_test_cpu(this_cpu, &domain->cpus))
break;
}
if (backup_task) {
*p = backup_task;
*target = cpu_rq(backup_cpu);
return;
}
/* 2. select a running task
* order: target->next to slow hmp domain
* 3. turning = true, pick a runnable task from slower domain
*/
turning = check_freq_turning();
list_for_each(pos, &hmp_domain->hmp_domains) {
domain = list_entry(pos, struct hmp_domain, hmp_domains);
for_each_cpu(cpu, &domain->cpus) {
if (cpu == this_cpu)
continue;
rq = cpu_rq(cpu);
raw_spin_lock_irqsave(&rq->lock, flags);
se = rq->cfs.curr;
if (!se) {
raw_spin_unlock_irqrestore(&rq->lock, flags);
continue;
}
if (!entity_is_task(se)) {
struct cfs_rq *cfs_rq;
cfs_rq = group_cfs_rq(se);
while (cfs_rq) {
se = cfs_rq->curr;
if (!entity_is_task(se))
cfs_rq = group_cfs_rq(se);
else
cfs_rq = NULL;
}
}
target_capacity = capacity_orig_of(cpu);
if (se && entity_is_task(se) &&
(uclamp_task_effective_util(task_of(se),
UCLAMP_MIN) >= target_capacity) &&
cpumask_test_cpu(this_cpu,
&((task_of(se))->cpus_allowed))) {
selected = 1;
/* get task and selection inside rq lock */
*p = task_of(se);
get_task_struct(*p);
*target = rq;
}
raw_spin_unlock_irqrestore(&rq->lock, flags);
if (selected) {
/* To put task out of rq lock */
if (backup_task)
put_task_struct(backup_task);
return;
}
if (turning && !backup_task) {
const struct cpumask *hmp_target_mask = NULL;
struct cfs_rq *cfs_rq;
struct sched_entity *se;
raw_spin_lock_irqsave(&rq->lock, flags);
hmp_target_mask = cpumask_of(this_cpu);
cfs_rq = &rq->cfs;
se = __pick_first_entity(cfs_rq);
if (se && entity_is_task(se) &&
cpumask_intersects(hmp_target_mask,
&(task_of(se)->cpus_allowed))) {
backup_cpu = cpu;
/* get task and selection inside
* rq lock
*/
backup_task = task_of(se);
get_task_struct(backup_task);
}
raw_spin_unlock_irqrestore(&rq->lock, flags);
}
}
if (list_is_last(pos, &hmp_domains))
break;
}
if (backup_task) {
*p = backup_task;
*target = cpu_rq(backup_cpu);
return;
}
}
/*
* rq: src rq
*/
static int
migrate_runnable_task(struct task_struct *p, int dst_cpu,
struct rq *rq)
{
struct rq_flags rf;
int moved = 0;
int src_cpu = cpu_of(rq);
raw_spin_lock(&p->pi_lock);
rq_lock(rq, &rf);
/* Are both target and busiest cpu online */
if (!cpu_online(src_cpu) || !cpu_online(dst_cpu) ||
cpu_isolated(src_cpu) || cpu_isolated(dst_cpu))
goto out_unlock;
/* Task has migrated meanwhile, abort forced migration */
/* can't migrate running task */
if (task_running(rq, p))
goto out_unlock;
/*
* If task_rq(p) != rq, it cannot be migrated here, because we're
* holding rq->lock, if p->on_rq == 0 it cannot get enqueued because
* we're holding p->pi_lock.
*/
if (task_rq(p) == rq) {
if (task_on_rq_queued(p)) {
rq = __migrate_task(rq, &rf, p, dst_cpu);
moved = 1;
}
}
out_unlock:
rq_unlock(rq, &rf);
raw_spin_unlock(&p->pi_lock);
return moved;
}
static unsigned int aggressive_idle_pull(int this_cpu)
{
int moved = 0;
struct rq *target = NULL;
struct task_struct *p = NULL;
if (!sched_smp_initialized)
return 0;
if (!spin_trylock(&hmp_force_migration))
return 0;
/*
* aggressive idle balance for min_cap/idle_prefer
*/
if (hmp_cpu_is_slowest(this_cpu)) {
hmp_slowest_idle_prefer_pull(this_cpu, &p, &target);
if (p) {
trace_sched_hmp_migrate(p, this_cpu, 0x10);
moved = migrate_runnable_task(p, this_cpu, target);
if (moved)
goto done;
}
} else {
hmp_fastest_idle_prefer_pull(this_cpu, &p, &target);
if (p) {
trace_sched_hmp_migrate(p, this_cpu, 0x10);
moved = migrate_runnable_task(p, this_cpu, target);
if (moved)
goto done;
moved = migrate_running_task(this_cpu, p, target);
}
}
done:
spin_unlock(&hmp_force_migration);
if (p)
put_task_struct(p);
return moved;
}
#else
bool idle_lb_enhance(struct task_struct *p, int cpu)
{
return 0;
}
static unsigned int aggressive_idle_pull(int this_cpu)
{
return 0;
}
#endif
#ifdef CONFIG_MTK_SCHED_EAS_POWER_SUPPORT
/*
* group_norm_util() returns the approximated group util relative to it's
* current capacity (busy ratio) in the range [0..SCHED_CAPACITY_SCALE] for use
* in energy calculations. Since task executions may or may not overlap in time
* in the group the true normalized util is between max(cpu_norm_util(i)) and
* sum(cpu_norm_util(i)) when iterating over all cpus in the group, i. The
* latter is used as the estimate as it leads to a more pessimistic energy
* estimate (more busy).
*/
static unsigned
long group_norm_util(struct energy_env *eenv, int cpu_idx)
{
struct sched_group *sg = eenv->sg;
int cpu_id = group_first_cpu(sg);
#ifdef CONFIG_ARM64
int cid = cpu_topology[cpu_id].cluster_id;
#else
int cid = cpu_topology[cpu_id].socket_id;
#endif
unsigned long capacity = eenv->cpu[cpu_idx].cap[cid];
unsigned long util, util_sum = 0;
int cpu;
for_each_cpu(cpu, sched_group_span(eenv->sg)) {
util = cpu_util_without(cpu, eenv->p);
/*
* If we are looking at the target CPU specified by the eenv,
* then we should add the (estimated) utilization of the task
* assuming we will wake it up on that CPU.
*/
if (unlikely(cpu == eenv->cpu[cpu_idx].cpu_id))
util += eenv->util_delta;
util_sum += __cpu_norm_util(util, capacity);
trace_group_norm_util(cpu_idx, cpu, cid, util_sum,
__cpu_norm_util(util, capacity), eenv->util_delta,
util, capacity);
}
if (util_sum > SCHED_CAPACITY_SCALE)
return SCHED_CAPACITY_SCALE;
return util_sum;
}
#endif
#ifdef CONFIG_MTK_SCHED_EAS_POWER_SUPPORT
static unsigned long
mtk_cluster_max_usage(int cid, struct energy_env *eenv, int cpu_idx,
int *max_cpu)
{
unsigned long util, max_util = 0;
int cpu = -1;
struct cpumask cls_cpus;
*max_cpu = -1;
arch_get_cluster_cpus(&cls_cpus, cid);
for_each_cpu(cpu, &cls_cpus) {
if (!cpu_online(cpu))
continue;
util = cpu_util_without(cpu, eenv->p);
/*
* If we are looking at the target CPU specified by the eenv,
* then we should add the (estimated) utilization of the task
* assuming we will wake it up on that CPU.
*/
if (unlikely(cpu == eenv->cpu[cpu_idx].cpu_id))
util += eenv->util_delta;
if (util >= max_util) {
max_util = util;
*max_cpu = cpu;
}
}
return max_util;
}
unsigned int capacity_margin_dvfs = 1280;
void mtk_cluster_capacity_idx(int cid, struct energy_env *eenv, int cpu_idx)
{
int cpu;
unsigned long util = mtk_cluster_max_usage(cid, eenv, cpu_idx, &cpu);
unsigned long new_capacity = util;
struct sched_domain *sd;
struct sched_group *sg;
const struct sched_group_energy *sge;
int idx, max_idx;
if (cpu == -1) { /* maybe no online CPU */
printk_deferred("sched: %s no online CPU", __func__);
return;
}
sd = rcu_dereference_check_sched_domain(cpu_rq(cpu)->sd);
if (sd) {
sg = sd->groups;
sge = sg->sge;
} else{
printk_deferred("sched: %s no sd", __func__);
return;
}
max_idx = sge->nr_cap_states - 1;
/* default is max_cap if we don't find a match */
eenv->cpu[cpu_idx].cap_idx[cid] = max_idx;
eenv->cpu[cpu_idx].cap[cid] = sge->cap_states[max_idx].cap;
/* OPP idx to refer capacity margin */
new_capacity = util * capacity_margin_dvfs >> SCHED_CAPACITY_SHIFT;
new_capacity = min(new_capacity,
(unsigned long) sge->cap_states[sge->nr_cap_states-1].cap);
for (idx = 0; idx < sge->nr_cap_states; idx++) {
if (sge->cap_states[idx].cap >= new_capacity) {
/* Keep track of SG's capacity */
eenv->cpu[cpu_idx].cap_idx[cid] = idx;
eenv->cpu[cpu_idx].cap[cid] = sge->cap_states[idx].cap;
break;
}
}
mt_sched_printf(sched_eas_energy_calc,
"cpu_idx=%d cid=%d max_cpu=%d (util=%ld new=%ld) max_opp=%d (cap=%d)",
cpu_idx, cid, cpu, util, new_capacity,
eenv->cpu[cpu_idx].cap_idx[cid],
eenv->cpu[cpu_idx].cap[cid]);
}
#define VOLT_SCALE 10
bool is_share_buck(int cid, int *co_buck_cid)
{
bool ret = false;
if (share_buck[cid] != cid) {
*co_buck_cid = share_buck[cid];
ret = true;
}
return ret;
}
#ifdef ARM_V8_2
const struct sched_group_energy * const cci_energy(void)
{
struct sched_group_energy *sge = &cci_tbl;
struct upower_tbl_info **addr_ptr_tbl_info;
struct upower_tbl_info *ptr_tbl_info;
struct upower_tbl *ptr_tbl;
addr_ptr_tbl_info = upower_get_tbl();
ptr_tbl_info = *addr_ptr_tbl_info;
ptr_tbl = ptr_tbl_info[UPOWER_BANK_CCI].p_upower_tbl;
sge->nr_cap_states = ptr_tbl->row_num;
sge->cap_states = ptr_tbl->row;
sge->lkg_idx = ptr_tbl->lkg_idx;
return sge;
}
extern unsigned int mt_cpufreq_get_cur_cci_freq_idx(void);
int get_cci_cap_idx(void)
{
const struct sched_group_energy *_sge;
static int CCI_nr_cap_stats;
if (CCI_nr_cap_stats == 0) {
#ifdef CONFIG_MTK_UNIFY_POWER
_sge = cci_energy();
CCI_nr_cap_stats = _sge->nr_cap_states;
#endif
}
return CCI_nr_cap_stats - mt_cpufreq_get_cur_cci_freq_idx();
}
#endif
int share_buck_cap_idx(struct energy_env *eenv, int cpu_idx,
int cid, int *co_buck_cid)
{
int cap_idx = eenv->cpu[cpu_idx].cap_idx[cid];
int co_buck_cap_idx = -1;
if (is_share_buck(cid, co_buck_cid)) {
int num_cluster = arch_get_nr_clusters();
if (*co_buck_cid < num_cluster)
co_buck_cap_idx =
eenv->cpu[cpu_idx].cap_idx[*co_buck_cid];
#ifdef ARM_V8_2
else if (*co_buck_cid == CCI_ID) /* CCI + DSU */
co_buck_cap_idx = get_cci_cap_idx();
#endif
trace_sched_share_buck(cpu_idx, cid, cap_idx, *co_buck_cid,
co_buck_cap_idx);
}
return co_buck_cap_idx;
}
int
mtk_idle_power(int cpu_idx, int idle_state, int cpu, void *argu, int sd_level)
{
int energy_cost = 0;
struct sched_domain *sd;
const struct sched_group_energy *_sge, *sge_core, *sge_clus;
#ifdef CONFIG_ARM64
int cid = cpu_topology[cpu].cluster_id;
#else
int cid = cpu_topology[cpu].socket_id;
#endif
struct energy_env *eenv = (struct energy_env *)argu;
int cap_idx = eenv->cpu[cpu_idx].cap_idx[cid];
int co_buck_cid = -1, co_buck_cap_idx;
int only_lv1 = 0;
sd = rcu_dereference_check_sched_domain(cpu_rq(cpu)->sd);
/* [FIXME] racing with hotplug */
if (!sd)
return 0;
/* [FIXME] racing with hotplug */
if (cap_idx == -1)
return 0;
co_buck_cap_idx = share_buck_cap_idx(eenv, cpu_idx, cid, &co_buck_cid);
cap_idx = max(cap_idx, co_buck_cap_idx);
_sge = sge_core = sge_clus = NULL;
/* To handle only 1 CPU in cluster by HPS */
if (unlikely(!sd->child &&
(rcu_dereference(per_cpu(sd_scs, cpu)) == NULL))) {
sge_core = cpu_core_energy(cpu);
sge_clus = cpu_cluster_energy(cpu);
only_lv1 = 1;
} else {
if (sd_level == 0)
_sge = cpu_core_energy(cpu); /* for cpu */
else
_sge = cpu_cluster_energy(cpu); /* for cluster */
}
idle_state = 0;
/* active idle: WFI */
if (only_lv1) {
struct upower_tbl_row *cpu_pwr_tbl, *clu_pwr_tbl;
cpu_pwr_tbl = &sge_core->cap_states[cap_idx];
clu_pwr_tbl = &sge_clus->cap_states[cap_idx];
/* idle: core->leask_power + cluster->lkg_pwr */
energy_cost = cpu_pwr_tbl->lkg_pwr[sge_core->lkg_idx] +
clu_pwr_tbl->lkg_pwr[sge_clus->lkg_idx];
mt_sched_printf(sched_eas_energy_calc,
"%s: %s lv=%d tlb_cpu[%d].leak=%d tlb_clu[%d].leak=%d total=%d",
__func__, "WFI", sd_level,
cap_idx,
cpu_pwr_tbl->lkg_pwr[sge_core->lkg_idx],
cap_idx,
clu_pwr_tbl->lkg_pwr[sge_clus->lkg_idx],
energy_cost);
} else {
struct upower_tbl_row *pwr_tbl;
unsigned long lkg_pwr;
pwr_tbl = &_sge->cap_states[cap_idx];
lkg_pwr = pwr_tbl->lkg_pwr[_sge->lkg_idx];
energy_cost = lkg_pwr;
trace_sched_idle_power(sd_level, cap_idx, lkg_pwr, energy_cost);
}
#ifdef ARM_V8_2
if ((sd_level != 0) && (co_buck_cid == CCI_ID)) {
struct upower_tbl_row *CCI_pwr_tbl;
unsigned long lkg_pwr;
_sge = cci_energy();
CCI_pwr_tbl = &_sge->cap_states[cap_idx];
lkg_pwr = CCI_pwr_tbl->lkg_pwr[_sge->lkg_idx];
energy_cost += lkg_pwr;
trace_sched_idle_power(sd_level, cap_idx, lkg_pwr, energy_cost);
}
#endif
return energy_cost;
}
int calc_busy_power(const struct sched_group_energy *_sge, int cap_idx,
int co_buck_cap_idx, int sd_level)
{
int energy_cost;
unsigned long int volt_factor = 1;
if (co_buck_cap_idx > cap_idx) {
/*
* calculated power with share-buck impact
*
* dynamic power = F*V^2
*
* dyn_pwr = current_power * (v_max/v_min)^2
* lkg_pwr = tlb[idx of v_max].leak;
*/
unsigned long v_max = _sge->cap_states[co_buck_cap_idx].volt;
unsigned long v_min = _sge->cap_states[cap_idx].volt;
unsigned long dyn_pwr;
unsigned long lkg_pwr;
int lkg_idx = _sge->lkg_idx;
volt_factor = ((v_max*v_max) << VOLT_SCALE) /
(v_min*v_min);
dyn_pwr = (_sge->cap_states[cap_idx].dyn_pwr *
volt_factor) >> VOLT_SCALE;
lkg_pwr = _sge->cap_states[co_buck_cap_idx].lkg_pwr[lkg_idx];
energy_cost = dyn_pwr + lkg_pwr;
trace_sched_busy_power(sd_level, cap_idx,
_sge->cap_states[cap_idx].dyn_pwr, volt_factor,
dyn_pwr, co_buck_cap_idx, lkg_pwr,
energy_cost);
} else {
/* No share buck impact */
unsigned long dyn_pwr;
unsigned long lkg_pwr;
int lkg_idx = _sge->lkg_idx;
dyn_pwr = _sge->cap_states[cap_idx].dyn_pwr;
lkg_pwr = _sge->cap_states[cap_idx].lkg_pwr[lkg_idx];
energy_cost = dyn_pwr + lkg_pwr;
trace_sched_busy_power(sd_level, cap_idx, dyn_pwr,
volt_factor, dyn_pwr, cap_idx, lkg_pwr,
energy_cost);
}
return energy_cost;
}
int mtk_busy_power(int cpu_idx, int cpu, void *argu, int sd_level)
{
struct energy_env *eenv = (struct energy_env *)argu;
struct sched_domain *sd;
int energy_cost = 0;
#ifdef CONFIG_ARM64
int cid = cpu_topology[cpu].cluster_id;
#else
int cid = cpu_topology[cpu].socket_id;
#endif
int cap_idx = eenv->cpu[cpu_idx].cap_idx[cid];
int co_cap_idx = -1;
int co_buck_cid = -1;
unsigned long int volt_factor = 1;
sd = rcu_dereference_check_sched_domain(cpu_rq(cpu)->sd);
/* [FIXME] racing with hotplug */
if (!sd)
return 0;
/* [FIXME] racing with hotplug */
if (cap_idx == -1)
return 0;
co_cap_idx = share_buck_cap_idx(eenv, cpu_idx, cid, &co_buck_cid);
/* To handle only 1 CPU in cluster by HPS */
if (unlikely(!sd->child &&
(rcu_dereference(per_cpu(sd_scs, cpu)) == NULL))) {
/* fix HPS defeats: only one CPU in this cluster */
const struct sched_group_energy *sge_core;
const struct sched_group_energy *sge_clus;
sge_core = cpu_core_energy(cpu);
sge_clus = cpu_cluster_energy(cpu);
if (co_cap_idx > cap_idx) {
unsigned long v_max;
unsigned long v_min;
unsigned long clu_dyn_pwr, cpu_dyn_pwr;
unsigned long clu_lkg_pwr, cpu_lkg_pwr;
struct upower_tbl_row *cpu_pwr_tbl, *clu_pwr_tbl;
v_max = sge_core->cap_states[co_cap_idx].volt;
v_min = sge_core->cap_states[cap_idx].volt;
/*
* dynamic power = F*V^2
*
* dyn_pwr = current_power * (v_max/v_min)^2
* lkg_pwr = tlb[idx of v_max].leak;
*
*/
volt_factor = ((v_max*v_max) << VOLT_SCALE)
/ (v_min*v_min);
cpu_dyn_pwr = sge_core->cap_states[cap_idx].dyn_pwr;
clu_dyn_pwr = sge_clus->cap_states[cap_idx].dyn_pwr;
energy_cost = ((cpu_dyn_pwr+clu_dyn_pwr)*volt_factor)
>> VOLT_SCALE;
/* + leak power of co_buck_cid's opp */
cpu_pwr_tbl = &sge_core->cap_states[co_cap_idx];
clu_pwr_tbl = &sge_clus->cap_states[co_cap_idx];
cpu_lkg_pwr = cpu_pwr_tbl->lkg_pwr[sge_core->lkg_idx];
clu_lkg_pwr = clu_pwr_tbl->lkg_pwr[sge_clus->lkg_idx];
energy_cost += (cpu_lkg_pwr + clu_lkg_pwr);
mt_sched_printf(sched_eas_energy_calc,
"%s: %s lv=%d tlb[%d].dyn_pwr=(cpu:%d,clu:%d) tlb[%d].leak=(cpu:%d,clu:%d) vlt_f=%ld",
__func__, "share_buck/only1CPU", sd_level,
cap_idx,
sge_core->cap_states[cap_idx].dyn_pwr,
sge_clus->cap_states[cap_idx].dyn_pwr,
co_cap_idx,
cpu_pwr_tbl->lkg_pwr[sge_core->lkg_idx],
clu_pwr_tbl->lkg_pwr[sge_clus->lkg_idx],
volt_factor);
mt_sched_printf(sched_eas_energy_calc,
"%s: %s total=%d",
__func__, "share_buck/only1CPU", energy_cost);
} else {
struct upower_tbl_row *cpu_pwr_tbl, *clu_pwr_tbl;
cpu_pwr_tbl = &sge_core->cap_states[cap_idx];
clu_pwr_tbl = &sge_clus->cap_states[cap_idx];
energy_cost = cpu_pwr_tbl->dyn_pwr +
cpu_pwr_tbl->lkg_pwr[sge_core->lkg_idx];
energy_cost += clu_pwr_tbl->dyn_pwr +
clu_pwr_tbl->lkg_pwr[sge_clus->lkg_idx];
mt_sched_printf(sched_eas_energy_calc,
"%s: %s lv=%d tlb_core[%d].dyn_pwr=(%d,%d) tlb_clu[%d]=(%d,%d) total=%d",
__func__, "only1CPU", sd_level,
cap_idx,
cpu_pwr_tbl->dyn_pwr,
cpu_pwr_tbl->lkg_pwr[sge_core->lkg_idx],
cap_idx,
clu_pwr_tbl->dyn_pwr,
clu_pwr_tbl->lkg_pwr[sge_clus->lkg_idx],
energy_cost);
}
} else {
const struct sched_group_energy *_sge;
if (sd_level == 0)
_sge = cpu_core_energy(cpu); /* for CPU */
else
_sge = cpu_cluster_energy(cpu); /* for cluster */
energy_cost = calc_busy_power(_sge, cap_idx, co_cap_idx,
sd_level);
}
#ifdef ARM_V8_2
if ((sd_level != 0) && (co_buck_cid == CCI_ID)) {
/* CCI + DSU */
const struct sched_group_energy *_sge;
_sge = cci_energy();
energy_cost += calc_busy_power(_sge, co_cap_idx, cap_idx,
sd_level);
}
#endif
return energy_cost;
}
#endif
#ifdef CONFIG_MTK_SCHED_EAS_POWER_SUPPORT
void mtk_update_new_capacity(struct energy_env *eenv)
{
int i, cpu_idx;
/* To get max opp index of every cluster for power estimation of
* share buck
*/
for (cpu_idx = EAS_CPU_PRV; cpu_idx < eenv->max_cpu_count ; ++cpu_idx) {
if (eenv->cpu[cpu_idx].cpu_id == -1)
continue;
for (i = 0; i < arch_get_nr_clusters(); i++)
mtk_cluster_capacity_idx(i, eenv, cpu_idx);
}
}
#else
void mtk_update_new_capacity(struct energy_env *eenv)
{
}
#endif
#ifdef CONFIG_MTK_SCHED_BOOST
static void select_task_prefer_cpu_fair(struct task_struct *p, int *result)
{
int task_prefer;
int cpu, new_cpu;
task_prefer = cpu_prefer(p);
cpu = (*result & LB_CPU_MASK);
if (task_prefer_match(p, cpu))
return;
new_cpu = select_task_prefer_cpu(p, cpu);
if ((new_cpu >= 0) && (new_cpu != cpu))
*result = new_cpu | LB_HINT;
}
#else
static void select_task_prefer_cpu_fair(struct task_struct *p, int *result)
{
}
#endif
inline int
task_match_on_dst_cpu(struct task_struct *p, int src_cpu, int target_cpu)
{
struct task_struct *target_tsk;
struct rq *rq = cpu_rq(target_cpu);
if (task_prefer_match(p, src_cpu))
return 0;
target_tsk = rq->curr;
if (task_prefer_fit(target_tsk, target_cpu))
return 0;
return 1;
}
static int check_freq_turning(void)
{
struct root_domain *rd = cpu_rq(smp_processor_id())->rd;
unsigned long capacity_curr_little, capacity_curr_big;
if (rd->min_cap_orig_cpu < 0 || rd->max_cap_orig_cpu < 0)
return false;
capacity_curr_little = capacity_curr_of(rd->min_cap_orig_cpu);
capacity_curr_big = capacity_curr_of(rd->max_cap_orig_cpu);
if ((capacity_curr_little > cpu_eff_tp) &&
(capacity_curr_big <= big_cpu_eff_tp))
return true;
return false;
}
static int collect_cluster_info(int cpu, int *total_nr_running, int *cpu_count)
{
struct sched_domain *sd;
struct sched_group *sg;
int i;
/* Find SD for the start CPU */
sd = rcu_dereference(per_cpu(sd_ea, cpu));
if (!sd)
return 0;
*total_nr_running = 0;
/* Scan CPUs in all SDs */
sg = sd->groups;
*cpu_count = cpumask_weight(sched_group_span(sg));
for_each_cpu(i, sched_group_span(sg)) {
struct rq *rq = cpu_rq(i);
*total_nr_running += rq->nr_running;
}
return 1;
}
struct task_rotate_work {
struct work_struct w;
struct task_struct *src_task;
struct task_struct *dst_task;
int src_cpu;
int dst_cpu;
};
static DEFINE_PER_CPU(struct task_rotate_work, task_rotate_works);
unsigned int sysctl_sched_rotation_enable;
void set_sched_rotation_enable(bool enable)
{
sysctl_sched_rotation_enable = enable;
}
static void task_rotate_work_func(struct work_struct *work)
{
struct task_rotate_work *wr = container_of(work,
struct task_rotate_work, w);
migrate_swap(wr->src_task, wr->dst_task);
put_task_struct(wr->src_task);
put_task_struct(wr->dst_task);
clear_reserved(wr->src_cpu);
clear_reserved(wr->dst_cpu);
}
void task_rotate_work_init(void)
{
int i;
for_each_possible_cpu(i) {
struct task_rotate_work *wr = &per_cpu(task_rotate_works, i);
INIT_WORK(&wr->w, task_rotate_work_func);
}
}
void task_check_for_rotation(struct rq *src_rq)
{
u64 wc, wait, max_wait = 0, run, max_run = 0;
int deserved_cpu = nr_cpu_ids, dst_cpu = nr_cpu_ids;
int i, src_cpu = cpu_of(src_rq);
struct rq *dst_rq;
struct task_rotate_work *wr = NULL;
int heavy_task = 0;
if (!sysctl_sched_rotation_enable)
return;
if (is_max_capacity_cpu(src_cpu))
return;
for_each_possible_cpu(i) {
struct rq *rq = cpu_rq(i);
struct task_struct *curr_task = rq->curr;
if (curr_task &&
!task_fits_capacity(curr_task, capacity_of(i)))
heavy_task += 1;
}
if (heavy_task < HEAVY_TASK_NUM)
return;
wc = ktime_get_ns();
for_each_possible_cpu(i) {
struct rq *rq = cpu_rq(i);
if (is_max_capacity_cpu(i))
break;
if (is_reserved(i))
continue;
if (!rq->misfit_task_load || rq->curr->sched_class !=
&fair_sched_class)
continue;
wait = wc - rq->curr->last_enqueued_ts;
if (wait > max_wait) {
max_wait = wait;
deserved_cpu = i;
}
}
if (deserved_cpu != src_cpu)
return;
for_each_possible_cpu(i) {
struct rq *rq = cpu_rq(i);
if (!is_max_capacity_cpu(i))
continue;
if (is_reserved(i))
continue;
if (rq->curr->sched_class != &fair_sched_class)
continue;
if (rq->nr_running > 1)
continue;
run = wc - rq->curr->last_enqueued_ts;
if (run < TASK_ROTATION_THRESHOLD_NS)
continue;
if (run > max_run) {
max_run = run;
dst_cpu = i;
}
}
if (dst_cpu == nr_cpu_ids)
return;
dst_rq = cpu_rq(dst_cpu);
double_rq_lock(src_rq, dst_rq);
if (dst_rq->curr->sched_class == &fair_sched_class) {
get_task_struct(src_rq->curr);
get_task_struct(dst_rq->curr);
mark_reserved(src_cpu);
mark_reserved(dst_cpu);
wr = &per_cpu(task_rotate_works, src_cpu);
wr->src_task = src_rq->curr;
wr->dst_task = dst_rq->curr;
wr->src_cpu = src_cpu;
wr->dst_cpu = dst_cpu;
}
double_rq_unlock(src_rq, dst_rq);
if (wr) {
queue_work_on(src_cpu, system_highpri_wq, &wr->w);
trace_sched_big_task_rotation(src_cpu, dst_cpu,
src_rq->curr->pid, dst_rq->curr->pid);
}
}