/* * 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 #include #include #include #include #include #include #include #include /* * Heterogenous multiprocessor (HMP) optimizations * * The cpu types are distinguished using a list of hmp_domains * which each represent one cpu type using a cpumask. * The list is assumed ordered by compute capacity with the * fastest domain first. */ DEFINE_PER_CPU(struct hmp_domain *, hmp_cpu_domain); /* Setup hmp_domains */ void hmp_cpu_mask_setup(void) { struct hmp_domain *domain; struct list_head *pos; int cpu; pr_info("Initializing HMP scheduler:\n"); /* Initialize hmp_domains using platform code */ if (list_empty(&hmp_domains)) { pr_info("HMP domain list is empty!\n"); return; } /* Print hmp_domains */ list_for_each(pos, &hmp_domains) { domain = list_entry(pos, struct hmp_domain, hmp_domains); for_each_cpu(cpu, &domain->possible_cpus) per_cpu(hmp_cpu_domain, cpu) = domain; } pr_info("Initializing HMP scheduler done\n"); } /* * Heterogenous CPU capacity compare function * Only inspect lowest id of cpus in same domain. * Assume CPUs in same domain has same capacity. */ struct cluster_info { struct hmp_domain *hmpd; unsigned long cpu_perf; int cpu; }; static inline void fillin_cluster(struct cluster_info *cinfo, struct hmp_domain *hmpd) { int cpu; unsigned long cpu_perf; cinfo->hmpd = hmpd; cinfo->cpu = cpumask_any(&cinfo->hmpd->possible_cpus); for_each_cpu(cpu, &hmpd->possible_cpus) { cpu_perf = arch_scale_cpu_capacity(NULL, cpu); if (cpu_perf > 0) break; } cinfo->cpu_perf = cpu_perf; if (cpu_perf == 0) pr_info("Uninitialized CPU performance (CPU mask: %lx)", cpumask_bits(&hmpd->possible_cpus)[0]); } /* * Negative, if @a should sort before @b * Positive, if @a should sort after @b. * Return 0, if ordering is to be preserved */ int hmp_compare(void *priv, struct list_head *a, struct list_head *b) { struct cluster_info ca; struct cluster_info cb; fillin_cluster(&ca, list_entry(a, struct hmp_domain, hmp_domains)); fillin_cluster(&cb, list_entry(b, struct hmp_domain, hmp_domains)); return (ca.cpu_perf > cb.cpu_perf) ? -1 : 1; } void init_hmp_domains(void) { struct hmp_domain *domain; struct cpumask cpu_mask; int id, maxid; cpumask_clear(&cpu_mask); maxid = arch_get_nr_clusters(); /* * Initialize hmp_domains * Must be ordered with respect to compute capacity. * Fastest domain at head of list. */ for (id = 0; id < maxid; id++) { arch_get_cluster_cpus(&cpu_mask, id); domain = (struct hmp_domain *) kmalloc(sizeof(struct hmp_domain), GFP_KERNEL); if (domain) { cpumask_copy(&domain->possible_cpus, &cpu_mask); cpumask_and(&domain->cpus, cpu_online_mask, &domain->possible_cpus); list_add(&domain->hmp_domains, &hmp_domains); } } /* * Sorting HMP domain by CPU capacity */ list_sort(NULL, &hmp_domains, &hmp_compare); pr_info("Sort hmp_domains from little to big:\n"); for_each_hmp_domain_L_first(domain) { pr_info(" cpumask: 0x%02lx\n", *cpumask_bits(&domain->possible_cpus)); } hmp_cpu_mask_setup(); } #ifdef CONFIG_SCHED_HMP static int is_heavy_task(struct task_struct *p) { return p->se.avg.loadwop_avg >= 650 ? 1 : 0; } struct clb_env { struct clb_stats bstats; struct clb_stats lstats; int btarget, ltarget; struct cpumask bcpus; struct cpumask lcpus; unsigned int flags; struct mcheck { /* Details of this migration check */ int status; /* Indicate whether we should perform this task migration */ int result; } mcheck; }; static void collect_cluster_stats(struct clb_stats *clbs, struct cpumask *cluster_cpus, int target) { /* Update cluster informatics */ int cpu; int loadwop; for_each_cpu(cpu, cluster_cpus) { if (cpu_online(cpu)) { clbs->ncpu++; clbs->ntask += cpu_rq(cpu)->cfs.h_nr_running; clbs->load_avg += cpu_rq(cpu)->cfs.avg.loadwop_avg; #ifdef CONFIG_SCHED_HMP_PRIO_FILTER clbs->nr_normal_prio_task += cfs_nr_normal_prio(cpu); clbs->nr_dequeuing_low_prio += cfs_nr_dequeuing_low_prio(cpu); #endif } } if (!clbs->ncpu || target >= num_possible_cpus() || !cpumask_test_cpu(target, cluster_cpus)) return; /* * Calculate available CPU capacity * Calculate available task space * * Why load ratio should be multiplied by the number of task ? * The task is the entity of scheduling unit so that we should consider * it in scheduler. Only considering task load is not enough. * Thus, multiplying the number of tasks can adjust load ratio to a more * reasonable value. */ loadwop = cpu_rq(target)->cfs.avg.loadwop_avg; clbs->load_avg /= clbs->ncpu; clbs->acap = (clbs->cpu_capacity > loadwop) ? (clbs->cpu_capacity - loadwop) : 0; clbs->scaled_atask = (clbs->cpu_capacity > loadwop) ? (clbs->cpu_capacity - loadwop) : 0; trace_sched_cluster_stats(target, cpu_rq(target)->cfs.avg.loadwop_avg, cpu_rq(target)->cfs.h_nr_running, *cpumask_bits(cluster_cpus), clbs->ntask, clbs->load_avg, clbs->cpu_capacity, clbs->acap, clbs->scaled_atask, clbs->threshold); } /* * Task Dynamic Migration Threshold Adjustment. * * If the workload between clusters is not balanced, adjust migration * threshold in an attempt to move task precisely. * * Diff. = Max Threshold - Min Threshold * * Dynamic UP-Threshold = * B_nacap B_natask * Max Threshold - Diff. x ----------------- x ------------------- * B_nacap + L_nacap B_natask + L_natask * * * Dynamic Down-Threshold = * L_nacap L_natask * Min Threshold + Diff. x ----------------- x ------------------- * B_nacap + L_nacap B_natask + L_natask */ static void adj_threshold(struct clb_env *clbenv) { #define HMP_RESOLUTION_SCALING (4) #define hmp_scale_down(w) ((w) >> HMP_RESOLUTION_SCALING) unsigned long b_cap = 0, l_cap = 0; int b_nacap, l_nacap; const int hmp_max_weight = scale_load_down(HMP_MAX_LOAD); b_cap = clbenv->bstats.cpu_power; l_cap = clbenv->lstats.cpu_power; b_nacap = clbenv->bstats.acap; l_nacap = clbenv->lstats.acap * l_cap / (b_cap+1); b_nacap = hmp_scale_down(b_nacap); l_nacap = hmp_scale_down(l_nacap); if ((b_nacap + l_nacap) == 0) { clbenv->bstats.threshold = hmp_max_weight; clbenv->lstats.threshold = 0; } else { clbenv->bstats.threshold = hmp_max_weight - (hmp_max_weight * b_nacap * b_nacap) / ((b_nacap + l_nacap) * (b_nacap + l_nacap)); clbenv->lstats.threshold = hmp_max_weight * l_nacap * l_nacap / ((b_nacap + l_nacap) * (b_nacap + l_nacap)); } trace_sched_adj_threshold(clbenv->bstats.threshold, clbenv->lstats.threshold, clbenv->ltarget, l_cap, clbenv->btarget, b_cap); } static void sched_update_clbstats(struct clb_env *clbenv) { /* init cpu power and capacity */ clbenv->bstats.cpu_power = (int) arch_scale_cpu_capacity(NULL, clbenv->btarget); clbenv->lstats.cpu_power = (int) arch_scale_cpu_capacity(NULL, clbenv->ltarget); clbenv->lstats.cpu_capacity = SCHED_CAPACITY_SCALE * clbenv->lstats.cpu_power / (clbenv->bstats.cpu_power+1); clbenv->bstats.cpu_capacity = SCHED_CAPACITY_SCALE; collect_cluster_stats(&clbenv->bstats, &clbenv->bcpus, clbenv->btarget); collect_cluster_stats(&clbenv->lstats, &clbenv->lcpus, clbenv->ltarget); adj_threshold(clbenv); } static struct hmp_domain *hmp_get_hmp_domain_for_cpu(int cpu) { struct hmp_domain *domain; struct list_head *pos; list_for_each(pos, &hmp_domains) { domain = list_entry(pos, struct hmp_domain, hmp_domains); if (cpumask_test_cpu(cpu, &domain->possible_cpus)) return domain; } return NULL; } static void hmp_online_cpu(int cpu) { struct hmp_domain *domain = hmp_get_hmp_domain_for_cpu(cpu); if (domain) cpumask_set_cpu(cpu, &domain->cpus); } static void hmp_offline_cpu(int cpu) { struct hmp_domain *domain = hmp_get_hmp_domain_for_cpu(cpu); if (domain) cpumask_clear_cpu(cpu, &domain->cpus); } unsigned int hmp_next_up_threshold = 4096; unsigned int hmp_next_down_threshold = 4096; #define hmp_last_up_migration(cpu) \ cpu_rq(cpu)->cfs.avg.hmp_last_up_migration #define hmp_last_down_migration(cpu) \ cpu_rq(cpu)->cfs.avg.hmp_last_down_migration static inline unsigned int hmp_domain_min_load(struct hmp_domain *hmpd, int *min_cpu); /* Check if cpu is in fastest hmp_domain */ inline unsigned int hmp_cpu_is_fastest(int cpu) { struct list_head *pos; pos = &hmp_cpu_domain(cpu)->hmp_domains; return pos == hmp_domains.next; } /* Check if cpu is in slowest hmp_domain */ inline unsigned int hmp_cpu_is_slowest(int cpu) { struct list_head *pos; pos = &hmp_cpu_domain(cpu)->hmp_domains; return list_is_last(pos, &hmp_domains); } /* Next (slower) hmp_domain relative to cpu */ static inline struct hmp_domain *hmp_slower_domain(int cpu) { struct list_head *pos; pos = &hmp_cpu_domain(cpu)->hmp_domains; if (list_is_last(pos, &hmp_domains)) return list_entry(pos, struct hmp_domain, hmp_domains); return list_entry(pos->next, struct hmp_domain, hmp_domains); } /* Previous (faster) hmp_domain relative to cpu */ static inline struct hmp_domain *hmp_faster_domain(int cpu) { struct list_head *pos; pos = &hmp_cpu_domain(cpu)->hmp_domains; if (pos->prev == &hmp_domains) return list_entry(pos, struct hmp_domain, hmp_domains); return list_entry(pos->prev, struct hmp_domain, hmp_domains); } /* * Selects a cpu in previous (faster) hmp_domain * Note that cpumask_any_and() returns the first cpu in the cpumask */ static inline unsigned int hmp_select_faster_cpu(struct task_struct *tsk, int cpu) { int lowest_cpu = num_possible_cpus(); __always_unused int lowest_ratio = hmp_domain_min_load(hmp_faster_domain(cpu), &lowest_cpu); struct cpumask act_faster_mask; int act_faster_cpu = cpu; /* * If the lowest-loaded CPU in the domain is allowed by * the task affinity. * Select that one, otherwise select one which is allowed */ if (lowest_cpu < nr_cpu_ids && cpumask_test_cpu(lowest_cpu, &tsk->cpus_allowed) && !cpu_isolated(lowest_cpu)) return lowest_cpu; else { cpumask_andnot(&act_faster_mask, &hmp_faster_domain(cpu)->cpus, cpu_isolated_mask); act_faster_cpu = cpumask_any_and(&act_faster_mask, &tsk->cpus_allowed); return act_faster_cpu; } } static inline void hmp_next_up_delay(struct sched_entity *se, int cpu) { hmp_last_up_migration(cpu) = sched_clock(); hmp_last_down_migration(cpu) = 0; } static inline void hmp_next_down_delay(struct sched_entity *se, int cpu) { hmp_last_down_migration(cpu) = sched_clock(); hmp_last_up_migration(cpu) = 0; } static inline unsigned int hmp_domain_min_load(struct hmp_domain *hmpd, int *min_cpu) { int cpu; int min_cpu_runnable_temp = num_possible_cpus(); unsigned long min_runnable_load = INT_MAX; unsigned long contrib; for_each_cpu(cpu, &hmpd->cpus) { struct cfs_rq *cfs_rq = &cpu_rq(cpu)->cfs; /* don't use the divisor in the loop, just at the end */ contrib = cfs_rq->runnable_load_avg * scale_load_down(1024); if (contrib < min_runnable_load) { min_runnable_load = contrib; min_cpu_runnable_temp = cpu; } } if (min_cpu) *min_cpu = min_cpu_runnable_temp; /* domain will often have at least one empty CPU */ return min_runnable_load ? min_runnable_load / (__LOAD_AVG_MAX + 1) : 0; } /* Function Declaration */ static int hmp_up_stable(int cpu); static int hmp_down_stable(int cpu); static unsigned int hmp_up_migration(int cpu, int *target_cpu, struct sched_entity *se, struct clb_env *clbenv); static unsigned int hmp_down_migration(int cpu, int *target_cpu, struct sched_entity *se, struct clb_env *clbenv); #ifdef CONFIG_SCHED_HMP_PLUS static struct sched_entity *hmp_get_heaviest_task( struct sched_entity *se, int target_cpu); static struct sched_entity *hmp_get_lightest_task( struct sched_entity *se, int migrate_down); #endif #define hmp_caller_is_gb(caller) ((caller == HMP_GB)?1:0) #define hmp_cpu_stable(cpu, up) (up ? \ hmp_up_stable(cpu) : hmp_down_stable(cpu)) #define hmp_inc(v) ((v) + 1) #define task_created(f) ((SD_BALANCE_EXEC == f || SD_BALANCE_FORK == f)?1:0) /* * Heterogenous Multi-Processor (HMP) - Utility Function */ /* * These functions add next up/down migration delay that prevents the task from * doing another migration in the same direction until the delay has expired. */ static int hmp_up_stable(int cpu) { u64 now = sched_clock(); if (((now - hmp_last_up_migration(cpu)) >> 10) < hmp_next_up_threshold) return 0; return 1; } static int hmp_down_stable(int cpu) { u64 now = sched_clock(); u64 duration = now - hmp_last_down_migration(cpu); if ((duration >> 10) < hmp_next_down_threshold) return 0; return 1; } /* Select the most appropriate CPU from hmp cluster */ static unsigned int hmp_select_cpu(unsigned int caller, struct task_struct *p, struct cpumask *mask, int prev, int up) { int curr = 0; int target = num_possible_cpus(); unsigned long curr_wload = 0; unsigned long target_wload = 0; struct cpumask srcp; struct cpumask *tsk_cpus_allow = &p->cpus_allowed; cpumask_andnot(&srcp, cpu_online_mask, cpu_isolated_mask); cpumask_and(&srcp, &srcp, mask); target = cpumask_any_and(&srcp, tsk_cpus_allow); if (target >= num_possible_cpus()) goto out; /* * RT class is taken into account because CPU load is multiplied * by the total number of CPU runnable tasks that includes RT tasks. */ target_wload = hmp_inc(cfs_load(target)); target_wload *= rq_length(target); for_each_cpu(curr, mask) { /* Check CPU status and task affinity */ if (!cpu_online(curr) || !cpumask_test_cpu(curr, tsk_cpus_allow) || cpu_isolated(curr)) continue; /* For global load balancing, unstable CPU will be bypassed */ if (hmp_caller_is_gb(caller) && !hmp_cpu_stable(curr, up)) continue; curr_wload = hmp_inc(cfs_load(curr)); curr_wload *= rq_length(curr); if (curr_wload < target_wload) { target_wload = curr_wload; target = curr; } else if (curr_wload == target_wload && curr == prev) { target = curr; } } out: return target; } static int hmp_select_task_migration(int sd_flag, struct task_struct *p, int prev_cpu, int new_cpu, struct cpumask *fast_cpu_mask, struct cpumask *slow_cpu_mask) { int step = 0; struct sched_entity *se = &p->se; int B_target = num_possible_cpus(); int L_target = num_possible_cpus(); struct clb_env clbenv; B_target = hmp_select_cpu(HMP_SELECT_RQ, p, fast_cpu_mask, prev_cpu, 0); L_target = hmp_select_cpu(HMP_SELECT_RQ, p, slow_cpu_mask, prev_cpu, 1); /* * Only one cluster exists or only one cluster is allowed for this task * Case 1: return the runqueue whose load is minimum * Case 2: return original CFS runqueue selection result */ if (B_target >= num_possible_cpus() && L_target >= num_possible_cpus()) goto out; if (B_target >= num_possible_cpus()) goto select_slow; if (L_target >= num_possible_cpus()) goto select_fast; /* * Two clusters exist and both clusters are allowed for this task * Step 1: Move newly created task to the cpu where no tasks are running * Step 2: Migrate heavy-load task to big * Step 3: Migrate light-load task to LITTLE * Step 4: Make sure the task stays in its previous hmp domain */ step = 1; if (task_created(sd_flag) && !task_low_priority(p->prio)) { if (!rq_length(B_target)) goto select_fast; if (!rq_length(L_target)) goto select_slow; } memset(&clbenv, 0, sizeof(clbenv)); clbenv.flags |= HMP_SELECT_RQ; cpumask_copy(&clbenv.lcpus, slow_cpu_mask); cpumask_copy(&clbenv.bcpus, fast_cpu_mask); clbenv.ltarget = L_target; clbenv.btarget = B_target; step = 2; sched_update_clbstats(&clbenv); if (hmp_up_migration(L_target, &B_target, se, &clbenv)) goto select_fast; step = 3; if (hmp_down_migration(B_target, &L_target, se, &clbenv)) goto select_slow; step = 4; if (hmp_cpu_is_slowest(prev_cpu)) goto select_slow; goto select_fast; select_fast: new_cpu = B_target; cpumask_clear(slow_cpu_mask); goto out; select_slow: new_cpu = L_target; cpumask_copy(fast_cpu_mask, slow_cpu_mask); cpumask_clear(slow_cpu_mask); goto out; out: /* * Value of clbenb..load_avg only ready after step 2. * Dump value after this step to avoid invalid stack value */ if (step > 1) trace_sched_hmp_load(step, clbenv.bstats.load_avg, clbenv.lstats.load_avg); return new_cpu; } /* * Heterogenous Multi-Processor (HMP) - Task Runqueue Selection */ /* This function enhances the original task selection function */ static int hmp_select_task_rq_fair(int sd_flag, struct task_struct *p, int prev_cpu, int new_cpu) { struct list_head *pos; struct cpumask fast_cpu_mask, slow_cpu_mask; if (idle_cpu(new_cpu) && hmp_cpu_is_fastest(new_cpu) && !cpu_isolated(new_cpu)) return new_cpu; /* error handling */ if (prev_cpu >= num_possible_cpus()) return new_cpu; /* * Skip all the checks if only one CPU is online. * Otherwise, select the most appropriate CPU from cluster. */ if (num_online_cpus() == 1) goto out; cpumask_clear(&fast_cpu_mask); cpumask_clear(&slow_cpu_mask); /* order: fast to slow hmp domain */ list_for_each(pos, &hmp_domains) { struct hmp_domain *domain; domain = list_entry(pos, struct hmp_domain, hmp_domains); if (cpumask_empty(&domain->cpus) || cpumask_subset(&domain->possible_cpus, cpu_isolated_mask)) continue; if (cpumask_empty(&fast_cpu_mask)) { cpumask_copy(&fast_cpu_mask, &domain->possible_cpus); } else { cpumask_copy(&slow_cpu_mask, &domain->possible_cpus); new_cpu = hmp_select_task_migration(sd_flag, p, prev_cpu, new_cpu, &fast_cpu_mask, &slow_cpu_mask); } } out: /* it happens when num_online_cpus=1 */ if (new_cpu >= nr_cpu_ids) { /* BUG_ON(1); */ new_cpu = prev_cpu; } return new_cpu; } #define hmp_fast_cpu_has_spare_cycles(B, cpu_load) \ ((cpu_load * capacity_margin) < (SCHED_CAPACITY_SCALE * 1024)) #define hmp_task_fast_cpu_afford(B, se, cpu) \ ((B->acap > 0) && hmp_fast_cpu_has_spare_cycles(B, \ (se_load(se) + cfs_load(cpu)))) #define hmp_fast_cpu_oversubscribed(caller, B, se, cpu) \ (hmp_caller_is_gb(caller) ? \ !hmp_fast_cpu_has_spare_cycles(B, cfs_load(cpu)) : \ !hmp_task_fast_cpu_afford(B, se, cpu)) #define hmp_task_slow_cpu_afford(L, se) \ (L->acap > 0 && L->acap >= se_load(se)) /* Macro used by low-priority task filter */ #define hmp_low_prio_task_up_rejected(p, B, L) \ (task_low_priority(p->prio) && \ (B->ntask >= B->ncpu || 0 != L->nr_normal_prio_task) && \ (p->se.avg.loadwop_avg < 800)) #define hmp_low_prio_task_down_allowed(p, B, L) \ (task_low_priority(p->prio) && !B->nr_dequeuing_low_prio && \ B->ntask >= B->ncpu && 0 != L->nr_normal_prio_task && \ (p->se.avg.loadwop_avg < 800)) /* Migration check result */ #define HMP_BIG_NOT_OVERSUBSCRIBED (0x01) #define HMP_BIG_CAPACITY_INSUFFICIENT (0x02) #define HMP_LITTLE_CAPACITY_INSUFFICIENT (0x04) #define HMP_LOW_PRIORITY_FILTER (0x08) #define HMP_BIG_BUSY_LITTLE_IDLE (0x10) #define HMP_BIG_IDLE (0x20) #define HMP_MIGRATION_APPROVED (0x100) #define HMP_TASK_UP_MIGRATION (0x200) #define HMP_TASK_DOWN_MIGRATION (0x400) /* Migration statistics */ struct hmp_statisic hmp_stats; /* * Check whether this task should be migrated to big * Briefly summarize the flow as below; * 1) Migration stabilizing * 2) Filter low-priority task * 2.5) Keep all cpu busy * 3) Check CPU capacity * 4) Check dynamic migration threshold */ static unsigned int hmp_up_migration(int cpu, int *target_cpu, struct sched_entity *se, struct clb_env *clbenv) { struct task_struct *p = task_of(se); struct clb_stats *L, *B; struct mcheck *check; int curr_cpu = cpu; unsigned int caller = clbenv->flags; cpumask_t act_mask; L = &clbenv->lstats; B = &clbenv->bstats; check = &clbenv->mcheck; check->status = clbenv->flags; check->status |= HMP_TASK_UP_MIGRATION; check->result = 0; cpumask_andnot(&act_mask, cpu_active_mask, cpu_isolated_mask); /* * No migration is needed if * 1) There is only one cluster * 2) Task is already in big cluster * 3) It violates task affinity */ if (!L->ncpu || !B->ncpu || cpumask_test_cpu(curr_cpu, &clbenv->bcpus) || !cpumask_intersects(&clbenv->bcpus, &p->cpus_allowed) || !cpumask_intersects(&clbenv->bcpus, &act_mask)) goto out; /* * [1] Migration stabilizing * Let the task load settle before doing another up migration. * It can prevent a bunch of tasks from migrating to a unstable CPU. */ if (!hmp_up_stable(*target_cpu)) goto out; /* [2] Filter low-priority task */ #ifdef CONFIG_SCHED_HMP_PRIO_FILTER if (hmp_low_prio_task_up_rejected(p, B, L)) { check->status |= HMP_LOW_PRIORITY_FILTER; goto trace; } #endif /* [2.5]if big is idle, just go to big */ if (rq_length(*target_cpu) == 0) { check->status |= HMP_BIG_IDLE; check->status |= HMP_MIGRATION_APPROVED; check->result = 1; goto trace; } /* * [3] Check CPU capacity * Forbid up-migration if big CPU can't handle this task */ if (!hmp_task_fast_cpu_afford(B, se, *target_cpu)) { check->status |= HMP_BIG_CAPACITY_INSUFFICIENT; goto trace; } /* * [4] Check dynamic migration threshold * Migrate task from LITTLE to big if load is greater than up-threshold */ if (se_load(se) >= B->threshold) { check->status |= HMP_MIGRATION_APPROVED; check->result = 1; } trace: if (check->result && hmp_caller_is_gb(caller)) hmp_stats.nr_force_up++; trace_sched_hmp_stats(&hmp_stats); trace_sched_dynamic_threshold(task_of(se), B->threshold, check->status, curr_cpu, *target_cpu, se_load(se), B, L); trace_sched_dynamic_threshold_draw(B->threshold, L->threshold); out: return check->result; } /* * Check whether this task should be migrated to LITTLE * Briefly summarize the flow as below; * 1) Migration stabilizing * 1.5) Keep all cpu busy * 2) Filter low-priority task * 3) Check CPU capacity * 4) Check dynamic migration threshold */ static unsigned int hmp_down_migration(int cpu, int *target_cpu, struct sched_entity *se, struct clb_env *clbenv) { struct task_struct *p = task_of(se); struct clb_stats *L, *B; struct mcheck *check; int curr_cpu = cpu; unsigned int caller = clbenv->flags; cpumask_t act_mask; L = &clbenv->lstats; B = &clbenv->bstats; check = &clbenv->mcheck; check->status = caller; check->status |= HMP_TASK_DOWN_MIGRATION; check->result = 0; cpumask_andnot(&act_mask, cpu_active_mask, cpu_isolated_mask); /* * No migration is needed if * 1) There is only one cluster * 2) Task is already in LITTLE cluster * 3) It violates task affinity */ if (!L->ncpu || !B->ncpu || cpumask_test_cpu(curr_cpu, &clbenv->lcpus) || !cpumask_intersects(&clbenv->lcpus, &p->cpus_allowed) || !cpumask_intersects(&clbenv->lcpus, &act_mask)) goto out; /* * [1] Migration stabilizing * Let the task load settle before doing another down migration. * It can prevent a bunch of tasks from migrating to a unstable CPU. */ if (!hmp_down_stable(*target_cpu)) goto out; /* [1.5]if big is busy and little is idle, just go to little */ if (rq_length(*target_cpu) == 0 && caller == HMP_SELECT_RQ && rq_length(curr_cpu) > 0) { struct rq *curr_rq = cpu_rq(curr_cpu); /* * If current big core is not heavy task, * and wake up task is heavy task. * * Dont go to little. */ if (!(!is_heavy_task(curr_rq->curr) && is_heavy_task(p))) { check->status |= HMP_BIG_BUSY_LITTLE_IDLE; check->status |= HMP_MIGRATION_APPROVED; check->result = 1; goto trace; } } /* [2] Filter low-priority task */ #ifdef CONFIG_SCHED_HMP_PRIO_FILTER if (hmp_low_prio_task_down_allowed(p, B, L)) { cfs_nr_dequeuing_low_prio(curr_cpu)++; check->status |= HMP_LOW_PRIORITY_FILTER; check->status |= HMP_MIGRATION_APPROVED; check->result = 1; goto trace; } #endif /* * [3] Check CPU capacity * Forbid down-migration if either of the following conditions is true * 1) big cpu is not oversubscribed (if big CPU seems to have spare * cycles, do not force this task to run on LITTLE CPU, but * keep it staying in its previous cluster instead) * 2) LITTLE cpu doesn't have available capacity for this new task */ if (cpu_rq(curr_cpu)->cfs.h_nr_running > 1 && !hmp_fast_cpu_oversubscribed(caller, B, se, curr_cpu)) { check->status |= HMP_BIG_NOT_OVERSUBSCRIBED; goto trace; } /* * [4] Check dynamic migration threshold * Migrate task from big to LITTLE if load ratio is less than * or equal to down-threshold */ if (L->threshold >= se_load(se)) { check->status |= HMP_MIGRATION_APPROVED; check->result = 1; } trace: if (check->result && hmp_caller_is_gb(caller)) hmp_stats.nr_force_down++; trace_sched_hmp_stats(&hmp_stats); trace_sched_dynamic_threshold(task_of(se), L->threshold, check->status, curr_cpu, *target_cpu, se_load(se), B, L); trace_sched_dynamic_threshold_draw(B->threshold, L->threshold); out: return check->result; } static int hmp_active_load_balance_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; } /* * According to Linaro's comment, we should only check the currently running * tasks because selecting other tasks for migration will require extensive * book keeping. */ static void hmp_force_down_migration(int this_cpu) { int target_cpu; struct sched_entity *se; struct rq *target; unsigned long flags; unsigned int force = 0; struct task_struct *p; struct clb_env clbenv; #ifdef CONFIG_SCHED_HMP_PLUS struct sched_entity *orig; int B_cpu; #endif struct hmp_domain *hmp_domain = NULL; struct cpumask fast_cpu_mask, slow_cpu_mask; cpumask_clear(&fast_cpu_mask); cpumask_clear(&slow_cpu_mask); /* Migrate light task from big to LITTLE */ if (!hmp_cpu_is_slowest(this_cpu)) { hmp_domain = hmp_cpu_domain(this_cpu); cpumask_copy(&fast_cpu_mask, &hmp_domain->possible_cpus); while (!list_is_last(&hmp_domain->hmp_domains, &hmp_domains)) { struct list_head *pos = &hmp_domain->hmp_domains; hmp_domain = list_entry(pos->next, struct hmp_domain, hmp_domains); if (!cpumask_empty(&hmp_domain->cpus)) { cpumask_copy(&slow_cpu_mask, &hmp_domain->possible_cpus); break; } } } if (!hmp_domain || hmp_domain == hmp_cpu_domain(this_cpu)) return; if (cpumask_empty(&fast_cpu_mask) || cpumask_empty(&slow_cpu_mask)) return; force = 0; target = cpu_rq(this_cpu); raw_spin_lock_irqsave(&target->lock, flags); se = target->cfs.curr; if (!se) { raw_spin_unlock_irqrestore(&target->lock, flags); return; } /* Find task entity */ if (!entity_is_task(se)) { struct cfs_rq *cfs_rq; cfs_rq = group_cfs_rq(se); while (cfs_rq) { se = cfs_rq->curr; cfs_rq = group_cfs_rq(se); } } #ifdef CONFIG_SCHED_HMP_PLUS orig = se; se = hmp_get_lightest_task(orig, 1); if (!se) { raw_spin_unlock_irqrestore(&target->lock, flags); return; } if (!entity_is_task(se)) p = task_of(orig); else #endif p = task_of(se); #ifdef CONFIG_SCHED_HMP_PLUS /* * Don't offload to little if there is one idle big, * let load balance to do it's work. * Also, to prevent idle_balance from leading to potential ping-pong */ B_cpu = hmp_select_cpu(HMP_GB, p, &fast_cpu_mask, this_cpu, 0); if (B_cpu < nr_cpu_ids && !rq_length(B_cpu)) { raw_spin_unlock_irqrestore(&target->lock, flags); return; } #endif target_cpu = hmp_select_cpu(HMP_GB, p, &slow_cpu_mask, -1, 1); if (target_cpu >= num_possible_cpus()) { raw_spin_unlock_irqrestore(&target->lock, flags); return; } /* Collect cluster information */ memset(&clbenv, 0, sizeof(clbenv)); clbenv.flags |= HMP_GB; clbenv.btarget = this_cpu; clbenv.ltarget = target_cpu; cpumask_copy(&clbenv.lcpus, &slow_cpu_mask); cpumask_copy(&clbenv.bcpus, &fast_cpu_mask); sched_update_clbstats(&clbenv); #ifdef CONFIG_SCHED_HMP_PLUS if (cpu_rq(this_cpu)->cfs.h_nr_running < 2) { raw_spin_unlock_irqrestore(&target->lock, flags); return; } #endif /* Check migration threshold */ if (!target->active_balance && hmp_down_migration(this_cpu, &target_cpu, se, &clbenv) && !cpu_park(cpu_of(target))) { if (p->state != TASK_DEAD) { get_task_struct(p); target->active_balance = MIGR_DOWN_MIGRATE; target->push_cpu = target_cpu; target->migrate_task = p; force = 1; trace_sched_hmp_migrate(p, target->push_cpu, MIGR_DOWN_MIGRATE); hmp_next_down_delay(&p->se, target->push_cpu); } } raw_spin_unlock_irqrestore(&target->lock, flags); if (force) { if (!stop_one_cpu_nowait(cpu_of(target), hmp_active_load_balance_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); } } } /* * hmp_force_up_migration checks runqueues for tasks that need to * be actively migrated to a faster cpu. */ static void hmp_force_up_migration(int this_cpu) { int curr_cpu, target_cpu; struct sched_entity *se; struct rq *target; unsigned long flags; unsigned int force = 0; struct task_struct *p; struct clb_env clbenv; #ifdef CONFIG_SCHED_HMP_PLUS struct sched_entity *orig; #endif if (!spin_trylock(&hmp_force_migration)) return; /* Migrate heavy task from LITTLE to big */ for_each_online_cpu(curr_cpu) { struct hmp_domain *hmp_domain = NULL; struct cpumask fast_cpu_mask, slow_cpu_mask; cpumask_clear(&fast_cpu_mask); cpumask_clear(&slow_cpu_mask); if (!hmp_cpu_is_fastest(curr_cpu)) { /* current cpu is slow_cpu_mask*/ hmp_domain = hmp_cpu_domain(curr_cpu); cpumask_copy(&slow_cpu_mask, &hmp_domain->possible_cpus); while (&hmp_domain->hmp_domains != hmp_domains.next) { struct list_head *pos; pos = &hmp_domain->hmp_domains; hmp_domain = list_entry(pos->prev, struct hmp_domain, hmp_domains); if (cpumask_empty(&hmp_domain->cpus)) continue; cpumask_copy(&fast_cpu_mask, &hmp_domain->possible_cpus); break; } } else { hmp_force_down_migration(curr_cpu); continue; } if (!hmp_domain || hmp_domain == hmp_cpu_domain(curr_cpu)) continue; if (cpumask_empty(&fast_cpu_mask) || cpumask_empty(&slow_cpu_mask)) continue; force = 0; target = cpu_rq(curr_cpu); raw_spin_lock_irqsave(&target->lock, flags); se = target->cfs.curr; if (!se) { raw_spin_unlock_irqrestore(&target->lock, flags); continue; } /* Find task entity */ if (!entity_is_task(se)) { struct cfs_rq *cfs_rq; cfs_rq = group_cfs_rq(se); while (cfs_rq) { se = cfs_rq->curr; cfs_rq = group_cfs_rq(se); } } #ifdef CONFIG_SCHED_HMP_PLUS orig = se; se = hmp_get_heaviest_task(se, -1); if (!se) { raw_spin_unlock_irqrestore(&target->lock, flags); continue; } if (!entity_is_task(se)) p = task_of(orig); else #endif p = task_of(se); target_cpu = hmp_select_cpu(HMP_GB, p, &fast_cpu_mask, -1, 0); if (target_cpu >= num_possible_cpus()) { raw_spin_unlock_irqrestore(&target->lock, flags); continue; } /* Collect cluster information */ memset(&clbenv, 0, sizeof(clbenv)); clbenv.flags |= HMP_GB; clbenv.ltarget = curr_cpu; clbenv.btarget = target_cpu; cpumask_copy(&clbenv.lcpus, &slow_cpu_mask); cpumask_copy(&clbenv.bcpus, &fast_cpu_mask); sched_update_clbstats(&clbenv); /* Check migration threshold */ if (!target->active_balance && hmp_up_migration(curr_cpu, &target_cpu, se, &clbenv) && !cpu_park(cpu_of(target))) { if (p->state != TASK_DEAD) { get_task_struct(p); target->active_balance = MIGR_UP_MIGRATE; target->push_cpu = target_cpu; target->migrate_task = p; force = 1; trace_sched_hmp_migrate(p, target->push_cpu, MIGR_UP_MIGRATE); hmp_next_up_delay(&p->se, target->push_cpu); } } raw_spin_unlock_irqrestore(&target->lock, flags); if (force) { if (!stop_one_cpu_nowait(cpu_of(target), hmp_active_load_balance_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); } } else hmp_force_down_migration(this_cpu); } trace_sched_hmp_load(100, clbenv.bstats.load_avg, clbenv.lstats.load_avg); spin_unlock(&hmp_force_migration); } static inline void hmp_enqueue_entity_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se) { int cpu = cfs_rq->rq->cpu; cfs_rq->avg.loadwop_avg += se->avg.loadwop_avg; cfs_rq->avg.loadwop_sum += se->avg.loadwop_sum; #ifdef CONFIG_SCHED_HMP_PRIO_FILTER if (!task_low_priority(task_of(se)->prio)) cfs_nr_normal_prio(cpu)++; #endif trace_sched_cfs_enqueue_task(task_of(se), se_load(se), cpu); } static inline void hmp_dequeue_entity_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se) { int cpu = cfs_rq->rq->cpu; cfs_reset_nr_dequeuing_low_prio(cpu); if (!task_low_priority(task_of(se)->prio)) cfs_nr_normal_prio(cpu)--; cfs_rq->avg.loadwop_avg = max_t(long, cfs_rq->avg.loadwop_avg - se->avg.loadwop_avg, 0); cfs_rq->avg.loadwop_sum = max_t(s64, cfs_rq->avg.loadwop_sum - se->avg.loadwop_sum, 0); trace_sched_cfs_dequeue_task(task_of(se), se_load(se), cfs_rq->rq->cpu); } /* * hmp_idle_pull looks at little domain runqueues to see * if a task should be pulled. * * Reuses hmp_force_migration spinlock. * */ static unsigned int hmp_idle_pull(int this_cpu) { int cpu; struct sched_entity *curr, *orig; struct hmp_domain *hmp_domain = NULL; struct rq *target = NULL, *rq; unsigned long flags, ratio = 0; unsigned int moved = 0; struct task_struct *p = NULL; struct clb_env clbenv; struct task_struct *prev_selected = NULL; int selected = 0; if (!hmp_cpu_is_slowest(this_cpu)) hmp_domain = hmp_slower_domain(this_cpu); if (!hmp_domain) return 0; if (!spin_trylock(&hmp_force_migration)) return 0; memset(&clbenv, 0, sizeof(clbenv)); clbenv.flags |= HMP_GB; clbenv.btarget = this_cpu; cpumask_copy(&clbenv.lcpus, &hmp_domain->possible_cpus); cpumask_copy(&clbenv.bcpus, &hmp_cpu_domain(this_cpu)->possible_cpus); /* first select a task */ for_each_cpu(cpu, &hmp_domain->cpus) { rq = cpu_rq(cpu); raw_spin_lock_irqsave(&rq->lock, flags); curr = rq->cfs.curr; if (!curr) { raw_spin_unlock_irqrestore(&rq->lock, flags); continue; } if (!entity_is_task(curr)) { struct cfs_rq *cfs_rq; cfs_rq = group_cfs_rq(curr); while (cfs_rq) { curr = cfs_rq->curr; if (!entity_is_task(curr)) cfs_rq = group_cfs_rq(curr); else cfs_rq = NULL; } } orig = curr; curr = hmp_get_heaviest_task(curr, this_cpu); /* check if heaviest eligible task on this * CPU is heavier than previous task */ clbenv.ltarget = cpu; sched_update_clbstats(&clbenv); if (curr && entity_is_task(curr) && (se_load(curr) > clbenv.bstats.threshold) && (se_load(curr) > ratio) && cpumask_test_cpu(this_cpu, &task_of(curr)->cpus_allowed)) { selected = 1; /* get task and selection inside rq lock */ p = task_of(curr); get_task_struct(p); target = rq; ratio = curr->avg.loadwop_avg; } raw_spin_unlock_irqrestore(&rq->lock, flags); if (selected) { if (prev_selected) /* To put task out of rq lock */ put_task_struct(prev_selected); prev_selected = p; selected = 0; } } if (!p) goto done; moved = migrate_running_task(this_cpu, p, target); done: spin_unlock(&hmp_force_migration); if (p) put_task_struct(p); return moved; } /* must hold runqueue lock for queue se is currently on */ static const int hmp_max_tasks = 5; static struct sched_entity *hmp_get_heaviest_task( struct sched_entity *se, int target_cpu) { int num_tasks = hmp_max_tasks; struct sched_entity *max_se = se; long int max_ratio = se->avg.loadwop_avg; const struct cpumask *hmp_target_mask = NULL; struct hmp_domain *hmp; if (hmp_cpu_is_fastest(cpu_of(se->cfs_rq->rq))) return max_se; if (!task_prefer_little(task_of(se))) { max_se = se; max_ratio = se->avg.loadwop_avg; } hmp = hmp_faster_domain(cpu_of(se->cfs_rq->rq)); hmp_target_mask = &hmp->cpus; if (target_cpu >= 0) { /* idle_balance gets run on a CPU while * it is in the middle of being hotplugged * out. Bail early in that case. */ if (!cpumask_test_cpu(target_cpu, hmp_target_mask)) return NULL; hmp_target_mask = cpumask_of(target_cpu); } /* The currently running task is not on the runqueue */ se = __pick_first_entity(cfs_rq_of(se)); while (num_tasks && se) { if (entity_is_task(se) && se->avg.loadwop_avg > max_ratio && cpumask_intersects(hmp_target_mask, &task_of(se)->cpus_allowed)) { max_se = se; max_ratio = se->avg.loadwop_avg; } se = __pick_next_entity(se); num_tasks--; } return max_se; } static struct sched_entity *hmp_get_lightest_task( struct sched_entity *se, int migrate_down) { int num_tasks = hmp_max_tasks; struct sched_entity *min_se = 0; unsigned long int min_ratio = INT_MAX; const struct cpumask *hmp_target_mask = NULL; if (migrate_down) { struct hmp_domain *hmp; if (hmp_cpu_is_slowest(cpu_of(se->cfs_rq->rq))) return min_se; hmp = hmp_slower_domain(cpu_of(se->cfs_rq->rq)); hmp_target_mask = &hmp->cpus; } if (!task_prefer_big(task_of(se))) { min_se = se; min_ratio = se->avg.loadwop_avg; } /* The currently running task is not on the runqueue */ se = __pick_first_entity(cfs_rq_of(se)); while (num_tasks && se) { if (entity_is_task(se) && (se->avg.loadwop_avg < min_ratio && hmp_target_mask && cpumask_intersects(hmp_target_mask, &task_of(se)->cpus_allowed))) { min_se = se; min_ratio = se->avg.loadwop_avg; } se = __pick_next_entity(se); num_tasks--; } return min_se; } inline int hmp_fork_balance(struct task_struct *p, int prev_cpu) { int new_cpu = prev_cpu; int cpu = smp_processor_id(); if (hmp_cpu_is_fastest(prev_cpu)) { /* prev_cpu is fastest domain */ struct hmp_domain *hmpdom; __always_unused int lowest_ratio; hmpdom = list_entry( &hmp_cpu_domain(prev_cpu)->hmp_domains, struct hmp_domain, hmp_domains); lowest_ratio = hmp_domain_min_load(hmpdom, &new_cpu); if (new_cpu < nr_cpu_ids && cpumask_test_cpu(new_cpu, &p->cpus_allowed) && !cpu_isolated(new_cpu)) return new_cpu; new_cpu = cpumask_any_and(&hmp_faster_domain(cpu)->cpus, &p->cpus_allowed); if (new_cpu < nr_cpu_ids) return new_cpu; } else { /* prev_cpu is not fastest domain */ new_cpu = hmp_select_faster_cpu(p, prev_cpu); if (new_cpu < nr_cpu_ids) return new_cpu; } return new_cpu; } #endif #ifdef CONFIG_HMP_FREQUENCY_INVARIANT_SCALE #include static int cpufreq_callback(struct notifier_block *nb, unsigned long val, void *data) { struct cpufreq_freqs *freq = data; int cpu = freq->cpu; struct cpumask cls_cpus; int id; if (freq->flags & CPUFREQ_CONST_LOOPS) return NOTIFY_OK; if (val == CPUFREQ_PRECHANGE) { arch_get_cluster_cpus(&cls_cpus, arch_get_cluster_id(cpu)); for_each_cpu(id, &cls_cpus) arch_scale_set_curr_freq(id, freq->new); } return NOTIFY_OK; } static struct notifier_block cpufreq_notifier = { .notifier_call = cpufreq_callback, }; static int cpufreq_policy_callback(struct notifier_block *nb, unsigned long val, void *data) { struct cpufreq_policy *policy = data; int i; if (val != CPUFREQ_NOTIFY) return NOTIFY_OK; for_each_cpu(i, policy->cpus) { arch_scale_set_curr_freq(i, policy->cur); arch_scale_set_max_freq(i, policy->max); arch_scale_set_min_freq(i, policy->min); } return NOTIFY_OK; } static struct notifier_block cpufreq_policy_notifier = { .notifier_call = cpufreq_policy_callback, }; static int __init register_cpufreq_notifier(void) { int ret; ret = cpufreq_register_notifier(&cpufreq_notifier, CPUFREQ_TRANSITION_NOTIFIER); if (ret) return ret; return cpufreq_register_notifier(&cpufreq_policy_notifier, CPUFREQ_POLICY_NOTIFIER); } core_initcall(register_cpufreq_notifier); #endif /* CONFIG_HMP_FREQUENCY_INVARIANT_SCALE */