/* * Exynos Mobile Scheduler Group Scheduling * * Copyright (C) 2020 Samsung Electronics Co., Ltd */ #include #include "../sched.h" #include "../pelt.h" #include "ems.h" #include #include /****************************************************************************** * GSC (Group Scheduling based on Cgroup) * ******************************************************************************/ #define GSC_DEACTIVATED (0) #define GSC_ACTIVATED (1) #define GSC_DISABLED (1024) #define GSC_BOOSTED (0) #define DEFAULT_UP_THRESHOLD (GSC_DISABLED) #define DEFAULT_DOWN_THRESHOLD (GSC_DISABLED) #define DEFAULT_MIN_INTERVAL_NS (16 * NSEC_PER_MSEC) #define DEFAULT_MIN_RETAIN_NS (16 * NSEC_PER_MSEC) struct gsc_group { int id; int enabled; int status; u64 last_update_time; u64 deactivated_t; u64 activated_t; raw_spinlock_t lock; } *gsc_groups[CGROUP_COUNT]; struct gsc_stat { unsigned int up_threshold; unsigned int down_threshold; unsigned int min_interval_ns; unsigned int min_retain_ns; } gsc; static DEFINE_SPINLOCK(gsc_update_lock); static struct gsc_group *get_gsc_group(struct task_struct *p) { int index = cpuctl_task_group_idx(p); return gsc_groups[index]; } bool is_gsc_task(struct task_struct *p) { struct gsc_group *grp = get_gsc_group(p); if (gsc.up_threshold == GSC_DISABLED) return false; if (!grp->enabled) return false; if (gsc.up_threshold == GSC_BOOSTED) return true; return grp->status == GSC_ACTIVATED; } void gsc_update_status(struct gsc_group *grp, u64 group_load, u64 now) { s64 delta; if (grp->status == GSC_DEACTIVATED) { if (group_load > gsc.up_threshold) grp->status = GSC_ACTIVATED; goto out; } /* GSC is activated here */ if (group_load < gsc.down_threshold) { if (!grp->deactivated_t) { grp->deactivated_t = grp->last_update_time; goto out; } delta = grp->last_update_time - grp->deactivated_t; if (delta > gsc.min_retain_ns) { grp->deactivated_t = 0; grp->status = GSC_DEACTIVATED; } } else if (grp->deactivated_t) grp->deactivated_t = 0; out: grp->last_update_time = now; } static u64 lookup_table[MLT_PERIOD_COUNT] = { 1024, 922, 829, 746, 672, 605, 544, 490 }; static int _gsc_get_cpu_group_load(struct gsc_group *grp, int cpu) { int i, curr = mlt_cur_period(cpu); u64 group_load = 0, load; /* calc average group load */ for (i = 0; i < MLT_PERIOD_COUNT; i++) { load = mult_frac(mlt_art_cgroup_value(cpu, curr, grp->id), lookup_table[i], SCHED_CAPACITY_SCALE); group_load += load; curr--; if (curr < 0) curr = MLT_PERIOD_COUNT -1; } group_load = div64_u64(group_load, MLT_PERIOD_COUNT); return group_load; } static void _gsc_decision_activate(struct gsc_group *grp, u64 now) { int cpu, prev_status = grp->status; u64 group_load = 0, nr_cpus = 0, load; if (now < grp->last_update_time + gsc.min_interval_ns) return; if (gsc.up_threshold == GSC_BOOSTED) { grp->status = GSC_ACTIVATED; grp->deactivated_t = 0; grp->activated_t = now; grp->last_update_time = now; trace_gsc_decision_activate(grp->id, group_load, now, grp->last_update_time, "boosted"); return; } /* calc cpu's group load */ for_each_cpu_and(cpu, cpu_active_mask, ecs_cpus_allowed(NULL)) { load = mult_frac(_gsc_get_cpu_group_load(grp, cpu), capacity_cpu_orig(cpu), SCHED_CAPACITY_SCALE); group_load += load; nr_cpus++; trace_gsc_cpu_group_load(cpu, grp->id, load); } if (unlikely(!nr_cpus)) return; group_load = div64_u64(group_load, nr_cpus); gsc_update_status(grp, group_load, now); if (grp->status != prev_status) grp->activated_t = (grp->status == GSC_ACTIVATED) ? now : 0; trace_gsc_decision_activate(grp->id, group_load, now, grp->last_update_time, grp->status ? "activated" : "deactivated"); } static void gsc_decision_activate(struct gsc_group *grp, u64 now) { raw_spin_lock(&grp->lock); _gsc_decision_activate(grp, now); raw_spin_unlock(&grp->lock); } void gsc_update_tick(void) { int i; u64 now; if (!spin_trylock(&gsc_update_lock)) return; now = sched_clock(); if (gsc.up_threshold == GSC_DISABLED) goto unlock; for (i = 0; i < CGROUP_COUNT; i++) { struct gsc_group *grp = gsc_groups[i]; if (!grp->enabled) continue; if (now < grp->last_update_time + gsc.min_interval_ns) continue; gsc_decision_activate(grp, now); } unlock: spin_unlock(&gsc_update_lock); } /********************************************************************** * SYSFS support * **********************************************************************/ static struct kobject *gsc_kobj; /* up_threshold */ static ssize_t show_up_threshold(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return snprintf(buf, 10, "%d\n", gsc.up_threshold); } static ssize_t store_up_threshold(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int input; if (sscanf(buf, "%d", &input) != 1) return -EINVAL; gsc.up_threshold = input; return count; } static struct kobj_attribute up_threshold_attr = __ATTR(up_threshold, 0644, show_up_threshold, store_up_threshold); /* down_threshold */ static ssize_t show_down_threshold(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return snprintf(buf, 10, "%d\n", gsc.down_threshold); } static ssize_t store_down_threshold(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int input; if (sscanf(buf, "%d", &input) != 1) return -EINVAL; gsc.down_threshold = input; return count; } static struct kobj_attribute down_threshold_attr = __ATTR(down_threshold, 0644, show_down_threshold, store_down_threshold); /* min_interval_ns */ static ssize_t show_min_interval_ns(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return snprintf(buf, 20, "%d\n", gsc.min_interval_ns); } static ssize_t store_min_interval_ns(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int input; if (sscanf(buf, "%d", &input) != 1) return -EINVAL; gsc.min_interval_ns = input; return count; } static struct kobj_attribute min_interval_ns_attr = __ATTR(min_interval_ns, 0644, show_min_interval_ns, store_min_interval_ns); /* min_retain_ns */ static ssize_t show_min_retain_us(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return snprintf(buf, 20, "%d\n", gsc.min_retain_ns); } static ssize_t store_min_retain_us(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int input; if (sscanf(buf, "%d", &input) != 1) return -EINVAL; gsc.min_retain_ns = input; return count; } static struct kobj_attribute min_retain_us_attr = __ATTR(min_retain_ns, 0644, show_min_retain_us, store_min_retain_us); static int gsc_sysfs_init(struct kobject *ems_kobj) { int ret; gsc_kobj = kobject_create_and_add("gsc", ems_kobj); if (!gsc_kobj) { pr_info("%s: fail to create node\n", __func__); return -EINVAL; } ret = sysfs_create_file(gsc_kobj, &min_interval_ns_attr.attr); if (ret) pr_warn("%s: failed to create gsc sysfs, line %u\n", __func__, __LINE__); ret = sysfs_create_file(gsc_kobj, &up_threshold_attr.attr); if (ret) pr_warn("%s: failed to create gsc sysfs, line %u\n", __func__, __LINE__); ret = sysfs_create_file(gsc_kobj, &down_threshold_attr.attr); if (ret) pr_warn("%s: failed to create gsc sysfs, line %u\n", __func__, __LINE__); ret = sysfs_create_file(gsc_kobj, &min_retain_us_attr.attr); if (ret) pr_warn("%s: failed to create gsc sysfs, line %u\n", __func__, __LINE__); return ret; } static void init_gsc_stat(void) { gsc.up_threshold = DEFAULT_UP_THRESHOLD; gsc.down_threshold = DEFAULT_DOWN_THRESHOLD; gsc.min_interval_ns = DEFAULT_MIN_INTERVAL_NS; gsc.min_retain_ns = DEFAULT_MIN_RETAIN_NS; } static void init_gsc_group(void) { struct gsc_group *grp; int i; for (i = 0; i < CGROUP_COUNT; i++) { grp = kzalloc(sizeof(struct gsc_group), GFP_ATOMIC | GFP_NOWAIT); if (!grp) { pr_err("%s: Fail to allocate memory for gsc. grp=%p\n", __func__, grp); WARN_ON(!grp); return; } grp->id = i; grp->enabled = (i == CGROUP_TOPAPP) ? 1 : 0; grp->status = GSC_DEACTIVATED; grp->last_update_time = 0; grp->deactivated_t = 0; grp->activated_t = 0; raw_spin_lock_init(&grp->lock); gsc_groups[i] = grp; } } static int gsc_emstune_notifier_call(struct notifier_block *nb, unsigned long val, void *v) { struct emstune_set *cur_set = (struct emstune_set *)v; int i; for (i = 0; i < CGROUP_COUNT; i++) gsc_groups[i]->enabled = cur_set->gsc.enabled[i]; gsc.up_threshold = cur_set->gsc.up_threshold; gsc.down_threshold = cur_set->gsc.down_threshold; return NOTIFY_OK; } static struct notifier_block gsc_emstune_notifier = { .notifier_call = gsc_emstune_notifier_call, }; void gsc_init(struct kobject *ems_kobj) { gsc_sysfs_init(ems_kobj); init_gsc_stat(); init_gsc_group(); emstune_register_notifier(&gsc_emstune_notifier); pr_info("EMS: Initialized GSC\n"); }