/* * Exynos Core Sparing Governor - Exynos Mobile Scheduler * * Copyright (C) 2022 Samsung Electronics Co., Ltd * Youngtae Lee */ #include "../sched.h" #include "ems.h" #include #include static struct { struct cpumask cpus; struct ecs_governor *gov; struct list_head gov_list; /* ecs request */ struct list_head requests; struct cpumask requested_cpus; /* ecs user request */ struct cpumask user_cpus; struct kobject *ecs_kobj; struct kobject *governor_kobj; } ecs; static DEFINE_RAW_SPINLOCK(ecs_lock); static DEFINE_RAW_SPINLOCK(ecs_req_lock); /****************************************************************************** * HELP Function * ******************************************************************************/ static inline const struct cpumask *get_governor_cpus(void) { if (likely(ecs.gov)) return ecs.gov->get_target_cpus(); return cpu_possible_mask; } /****************************************************************************** * core sparing * ******************************************************************************/ static struct cpu_stop_work __percpu *ecs_migration_work; static void detach_any_task(struct rq *src_rq, struct rq *dst_rq, struct task_struct *p, struct list_head *tasks) { if (!can_migrate(p, dst_rq->cpu)) return; detach_task(src_rq, dst_rq, p); list_add(&p->se.group_node, tasks); } static void detach_all_tasks(struct rq *src_rq, struct rq *dst_rq, struct list_head *tasks) { struct task_struct *p, *n; update_rq_clock(src_rq); /* detach cfs tasks */ list_for_each_entry_safe(p, n, &src_rq->cfs_tasks, se.group_node) detach_any_task(src_rq, dst_rq, p, tasks); /* detach rt tasks */ plist_for_each_entry_safe(p, n, &src_rq->rt.pushable_tasks, pushable_tasks) detach_any_task(src_rq, dst_rq, p, tasks); } static void attach_all_tasks(struct rq *dst_rq, struct list_head *tasks) { struct task_struct *p; struct rq_flags rf; rq_lock(dst_rq, &rf); update_rq_clock(dst_rq); while (!list_empty(tasks)) { p = list_first_entry(tasks, struct task_struct, se.group_node); list_del_init(&p->se.group_node); attach_task(dst_rq, p); } rq_unlock(dst_rq, &rf); } static int ecs_migration_cpu_stop(void *data) { struct rq *src_rq = this_rq(), *dst_rq = data; struct list_head tasks = LIST_HEAD_INIT(tasks); struct rq_flags rf; rq_lock_irq(src_rq, &rf); detach_all_tasks(src_rq, dst_rq, &tasks); src_rq->active_balance = 0; rq_unlock(src_rq, &rf); if (!list_empty(&tasks)) attach_all_tasks(dst_rq, &tasks); local_irq_enable(); return 0; } static void __move_from_spared_cpus(int src_cpu, int dst_cpu) { struct rq *rq = cpu_rq(src_cpu); unsigned long flags; if (unlikely(src_cpu == dst_cpu)) return; raw_spin_rq_lock_irqsave(rq, flags); if (rq->active_balance) { raw_spin_rq_unlock_irqrestore(rq, flags); return; } rq->active_balance = 1; raw_spin_rq_unlock_irqrestore(rq, flags); /* Migrate all tasks from src to dst through stopper */ stop_one_cpu_nowait(src_cpu, ecs_migration_cpu_stop, cpu_rq(dst_cpu), per_cpu_ptr(ecs_migration_work, src_cpu)); } static void move_from_spared_cpus(struct cpumask *spared_cpus) { int fcpu, idlest_cpu = 0; for_each_cpu(fcpu, cpu_active_mask) { int cpu, min_util = INT_MAX; /* To loop each domain */ if (fcpu != cpumask_first(cpu_coregroup_mask(fcpu))) continue; /* To find idlest cpu in this domain */ for_each_cpu_and(cpu, get_governor_cpus(), cpu_coregroup_mask(fcpu)) { struct rq *rq = cpu_rq(cpu); int cpu_util; cpu_util = ml_cpu_util(cpu) + cpu_util_rt(rq); if (min_util < cpu_util) continue; min_util = cpu_util; idlest_cpu = cpu; } /* Move task to idlest cpu */ for_each_cpu_and(cpu, spared_cpus, cpu_coregroup_mask(fcpu)) { if (available_idle_cpu(cpu)) continue; __move_from_spared_cpus(cpu, idlest_cpu); } } } void update_ecs_cpus(void) { struct cpumask spared_cpus, prev_cpus; unsigned long flags; raw_spin_lock_irqsave(&ecs_lock, flags); cpumask_copy(&prev_cpus, &ecs.cpus); cpumask_and(&ecs.cpus, get_governor_cpus(), cpu_possible_mask); cpumask_and(&ecs.cpus, &ecs.cpus, &ecs.requested_cpus); cpumask_and(&ecs.cpus, &ecs.cpus, &ecs.user_cpus); if (cpumask_subset(&prev_cpus, &ecs.cpus)) { raw_spin_unlock_irqrestore(&ecs_lock, flags); return; } cpumask_andnot(&spared_cpus, cpu_active_mask, &ecs.cpus); cpumask_and(&spared_cpus, &spared_cpus, &prev_cpus); if (!cpumask_empty(&spared_cpus)) move_from_spared_cpus(&spared_cpus); raw_spin_unlock_irqrestore(&ecs_lock, flags); } int ecs_cpu_available(int cpu, struct task_struct *p) { if (p && is_per_cpu_kthread(p)) return true; return cpumask_test_cpu(cpu, &ecs.cpus); } const struct cpumask *ecs_available_cpus(void) { return &ecs.cpus; } const struct cpumask *ecs_cpus_allowed(struct task_struct *p) { if (p && is_per_cpu_kthread(p)) return cpu_active_mask; return &ecs.cpus; } void ecs_enqueue_update(int prev_cpu, struct task_struct *p) { if (ecs.gov && ecs.gov->enqueue_update) ecs.gov->enqueue_update(prev_cpu, p); } void ecs_update(void) { if (likely(ecs.gov)) ecs.gov->update(); } static void ecs_governor_set(struct ecs_governor *gov) { if (ecs.gov == gov) return; /* stop current working governor */ if (ecs.gov) ecs.gov->stop(); /* change governor pointer and start new governor */ gov->start(&ecs.cpus); ecs.gov = gov; } void ecs_governor_register(struct ecs_governor *gov, bool default_gov) { list_add(&gov->list, &ecs.gov_list); if (default_gov) ecs_governor_set(gov); } /****************************************************************************** * ECS requestes * ******************************************************************************/ struct ecs_request { char name[ECS_USER_NAME_LEN]; struct cpumask cpus; struct list_head list; }; static struct ecs_request *ecs_find_request(char *name) { struct ecs_request *req; list_for_each_entry(req, &ecs.requests, list) if (!strcmp(req->name, name)) return req; return NULL; } static void ecs_request_combine_and_apply(void) { struct cpumask mask; struct ecs_request *req; char buf[10]; cpumask_setall(&mask); list_for_each_entry(req, &ecs.requests, list) cpumask_and(&mask, &mask, &req->cpus); if (cpumask_empty(&mask) || !cpumask_test_cpu(0, &mask)) { scnprintf(buf, sizeof(buf), "%*pbl", cpumask_pr_args(&mask)); panic("ECS cpumask(%s) is wrong\n", buf); } cpumask_copy(&ecs.requested_cpus, &mask); update_ecs_cpus(); } int ecs_request_register(char *name, const struct cpumask *mask) { struct ecs_request *req; char buf[ECS_USER_NAME_LEN]; unsigned long flags; /* allocate memory for new request */ req = kzalloc(sizeof(struct ecs_request), GFP_KERNEL); if (!req) return -ENOMEM; raw_spin_lock_irqsave(&ecs_req_lock, flags); /* check whether name is already register or not */ if (ecs_find_request(name)) panic("Failed to register ecs request! already existed\n"); /* init new request information */ cpumask_copy(&req->cpus, mask); strcpy(req->name, name); /* register request list */ list_add(&req->list, &ecs.requests); scnprintf(buf, sizeof(buf), "%*pbl", cpumask_pr_args(&req->cpus)); pr_info("Register new ECS request [name:%s, mask:%s]\n", req->name, buf); /* applying new request */ ecs_request_combine_and_apply(); raw_spin_unlock_irqrestore(&ecs_req_lock, flags); return 0; } EXPORT_SYMBOL_GPL(ecs_request_register); /* remove request on the request list of exynos_core_sparing request */ int ecs_request_unregister(char *name) { struct ecs_request *req; unsigned long flags; raw_spin_lock_irqsave(&ecs_req_lock, flags); req = ecs_find_request(name); if (!req) { raw_spin_unlock_irqrestore(&ecs_req_lock, flags); return -ENODEV; } /* remove request from list and free */ list_del(&req->list); kfree(req); ecs_request_combine_and_apply(); raw_spin_unlock_irqrestore(&ecs_req_lock, flags); return 0; } EXPORT_SYMBOL_GPL(ecs_request_unregister); int ecs_request(char *name, const struct cpumask *mask) { struct ecs_request *req; unsigned long flags; raw_spin_lock_irqsave(&ecs_req_lock, flags); req = ecs_find_request(name); if (!req) { raw_spin_unlock_irqrestore(&ecs_req_lock, flags); return -EINVAL; } cpumask_copy(&req->cpus, mask); ecs_request_combine_and_apply(); raw_spin_unlock_irqrestore(&ecs_req_lock, flags); return 0; } EXPORT_SYMBOL_GPL(ecs_request); struct kobject *ecs_get_governor_object(void) { return ecs.governor_kobj; } /****************************************************************************** * sysfs * ******************************************************************************/ #define STR_LEN (6) static ssize_t store_ecs_cpus(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int ret; char str[STR_LEN]; struct cpumask mask; if (strlen(buf) >= STR_LEN) return -EINVAL; if (!sscanf(buf, "%s", str)) return -EINVAL; if (str[0] == '0' && str[1] =='x') ret = cpumask_parse(str + 2, &mask); else ret = cpumask_parse(str, &mask); if (ret){ pr_err("input of req_cpus(%s) is not correct\n", buf); return -EINVAL; } cpumask_copy(&ecs.user_cpus, &mask); update_ecs_cpus(); return count; } static ssize_t show_ecs_cpus(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int ret = 0; /* All combined CPUs */ ret += snprintf(buf + ret, PAGE_SIZE - ret, "cpus : %#x\n", *(unsigned int *)cpumask_bits(&ecs.cpus)); /* Governor CPUs */ ret += snprintf(buf + ret, PAGE_SIZE - ret, "* gov cpus : %#x\n", *(unsigned int *)cpumask_bits(get_governor_cpus())); /* Requested from kernel CPUs */ ret += snprintf(buf + ret, PAGE_SIZE - ret, "* kernel requsted cpus : %#x\n", *(unsigned int *)cpumask_bits(&ecs.requested_cpus)); /* Requested from user CPUs */ ret += snprintf(buf + ret, PAGE_SIZE - ret, "* user requested cpus : %#x\n", *(unsigned int *)cpumask_bits(&ecs.user_cpus)); return ret; } static struct kobj_attribute ecs_cpus_attr = __ATTR(cpus, 0644, show_ecs_cpus, store_ecs_cpus); static ssize_t store_ecs_scaling_governor(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { char str[ECS_USER_NAME_LEN]; struct ecs_governor *gov = NULL; if (strlen(buf) >= ECS_USER_NAME_LEN) return -EINVAL; if (!sscanf(buf, "%s", str)) return -EINVAL; list_for_each_entry(gov, &ecs.gov_list, list) { if (!strncasecmp(str, gov->name, ECS_USER_NAME_LEN)) { ecs_governor_set(gov); return count; } } pr_err("input of govenor name(%s) is not correct\n", buf); return -EINVAL; } static ssize_t show_ecs_scaling_governor(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { if (!ecs.gov) return snprintf(buf, PAGE_SIZE, "there is no working governor\n"); return scnprintf(buf, ECS_USER_NAME_LEN, "%s\n", ecs.gov->name); } static struct kobj_attribute ecs_scaling_governor_attr = __ATTR(scaling_governor, 0644, show_ecs_scaling_governor, store_ecs_scaling_governor); static int ecs_sysfs_init(struct kobject *ems_kobj) { int ret; ecs.ecs_kobj = kobject_create_and_add("ecs", ems_kobj); if (!ecs.ecs_kobj) { pr_info("%s: fail to create node\n", __func__); return -EINVAL; } ecs.governor_kobj = kobject_create_and_add("governos", ecs.ecs_kobj); if (!ecs.governor_kobj) { pr_info("%s: fail to create node for governos\n", __func__); return -EINVAL; } ret = sysfs_create_file(ecs.ecs_kobj, &ecs_cpus_attr.attr); if (ret) pr_warn("%s: failed to create ecs sysfs\n", __func__); ret = sysfs_create_file(ecs.governor_kobj, &ecs_scaling_governor_attr.attr); if (ret) pr_warn("%s: failed to create ecs sysfs\n", __func__); return 0; } int ecs_init(struct kobject *ems_kobj) { ecs_migration_work = alloc_percpu(struct cpu_stop_work); if (!ecs_migration_work) { pr_err("falied to allocate ecs_migration_work\n"); return -ENOMEM; } ecs.gov = NULL; cpumask_copy(&ecs.cpus, cpu_possible_mask); cpumask_copy(&ecs.requested_cpus, cpu_possible_mask); cpumask_copy(&ecs.user_cpus, cpu_possible_mask); INIT_LIST_HEAD(&ecs.requests); INIT_LIST_HEAD(&ecs.gov_list); ecs_sysfs_init(ems_kobj); return 0; }