/* * Frequency variant cpufreq driver * * Copyright (C) 2017 Samsung Electronics Co., Ltd * Park Bumgyu */ #include #include #include #include #include #include #include #include "../sched.h" #include "ems.h" #include #include #include /****************************************************************************** * emstune mode/level/set management * ******************************************************************************/ struct { struct emstune_mode *modes; struct emstune_mode *modes_backup; int cur_mode; int cur_level; int mode_count; int level_count; int boost_level; struct { int ongoing; struct emstune_set set; struct delayed_work work; } boot; struct device *ems_dev; struct notifier_block nb; struct emstune_mode_request level_req; } emstune; static inline struct emstune_set *emstune_cur_set(void) { if (!emstune.modes) return NULL; if (emstune.boot.ongoing) return &emstune.boot.set; return &emstune.modes[emstune.cur_mode].sets[emstune.cur_level]; } static int emstune_notify(void); static DEFINE_SPINLOCK(emstune_lock); static void emstune_mode_change(int next_mode) { unsigned long flags; char msg[32]; spin_lock_irqsave(&emstune_lock, flags); if (emstune.cur_mode == next_mode) { spin_unlock_irqrestore(&emstune_lock, flags); return; } trace_emstune_mode(emstune.cur_mode, emstune.cur_level); emstune.cur_mode = next_mode; emstune_notify(); spin_unlock_irqrestore(&emstune_lock, flags); snprintf(msg, sizeof(msg), "NOTI_MODE=%d", emstune.cur_mode); send_uevent(msg); } static void emstune_level_change(int next_level) { unsigned long flags; spin_lock_irqsave(&emstune_lock, flags); if (emstune.cur_level == next_level) { spin_unlock_irqrestore(&emstune_lock, flags); return; } trace_emstune_mode(emstune.cur_mode, emstune.cur_level); emstune.cur_level = next_level; emstune_notify(); spin_unlock_irqrestore(&emstune_lock, flags); } static void emstune_reset(int mode, int level) { memcpy(&emstune.modes[mode].sets[level], &emstune.modes_backup[mode].sets[level], sizeof(struct emstune_set)); } /****************************************************************************** * emstune level request * ******************************************************************************/ static int emstune_pm_qos_notifier(struct notifier_block *nb, unsigned long level, void *data) { emstune_level_change(level); return 0; } #define DEFAULT_LEVEL (0) void __emstune_add_request(struct emstune_mode_request *req, char *func, unsigned int line) { if (dev_pm_qos_add_request(emstune.ems_dev, &req->dev_req, DEV_PM_QOS_MIN_FREQUENCY, DEFAULT_LEVEL)) return; req->func = func; req->line = line; } EXPORT_SYMBOL_GPL(__emstune_add_request); void emstune_remove_request(struct emstune_mode_request *req) { dev_pm_qos_remove_request(&req->dev_req); } EXPORT_SYMBOL_GPL(emstune_remove_request); void emstune_update_request(struct emstune_mode_request *req, s32 new_level) { /* ignore if the value is out of range except boost level */ if ((new_level < 0 || new_level >= emstune.level_count)) return; dev_pm_qos_update_request(&req->dev_req, new_level); } EXPORT_SYMBOL_GPL(emstune_update_request); void emstune_boost(struct emstune_mode_request *req, int enable) { if (enable) emstune_update_request(req, emstune.boost_level); else emstune_update_request(req, 0); } EXPORT_SYMBOL_GPL(emstune_boost); int emstune_get_cur_mode(void) { return emstune.cur_mode; } EXPORT_SYMBOL_GPL(emstune_get_cur_mode); int emstune_get_cur_level(void) { return emstune.cur_level; } EXPORT_SYMBOL_GPL(emstune_get_cur_level); /****************************************************************************** * emstune notify * ******************************************************************************/ static RAW_NOTIFIER_HEAD(emstune_chain); /* emstune_lock *MUST* be held before notifying */ static int emstune_notify(void) { struct emstune_set *cur_set = emstune_cur_set(); if (!cur_set) return 0; return raw_notifier_call_chain(&emstune_chain, 0, cur_set); } int emstune_register_notifier(struct notifier_block *nb) { return raw_notifier_chain_register(&emstune_chain, nb); } EXPORT_SYMBOL_GPL(emstune_register_notifier); int emstune_unregister_notifier(struct notifier_block *nb) { return raw_notifier_chain_unregister(&emstune_chain, nb); } EXPORT_SYMBOL_GPL(emstune_unregister_notifier); /****************************************************************************** * initializing and parsing parameter * ******************************************************************************/ static char *task_cgroup_name[] = { "root", "foreground", "background", "top-app", "rt", "system", "system-background", "nnapi-hal", "camera-daemon", "midground", }; static inline void parse_coregroup_field(struct device_node *dn, const char *name, int (field)[VENDOR_NR_CPUS], int default_value) { int count, cpu, cursor; u32 val[VENDOR_NR_CPUS]; for (count = 0; count < VENDOR_NR_CPUS; count++) val[count] = default_value; count = of_property_count_u32_elems(dn, name); if (count < 0) goto skip_parse_value; if (of_property_read_u32_array(dn, name, (u32 *)&val, count)) goto skip_parse_value; skip_parse_value: count = 0; for_each_possible_cpu(cpu) { if (cpu != cpumask_first(cpu_coregroup_mask(cpu))) continue; for_each_cpu(cursor, cpu_coregroup_mask(cpu)) field[cursor] = val[count]; count++; } } static inline void parse_cgroup_field(struct device_node *dn, int *field, int default_value) { int group, val; for (group = 0; group < CGROUP_COUNT; group++) { if (!of_property_read_u32(dn, task_cgroup_name[group], &val)) field[group] = val; else field[group] = default_value; } } static inline int parse_cgroup_field_mask(struct device_node *dn, struct cpumask *mask) { int group; const char *buf; for (group = 0; group < CGROUP_COUNT; group++) { if (!of_property_read_string(dn, task_cgroup_name[group], &buf)) cpulist_parse(buf, &mask[group]); else { /* if it failed to parse mask, set mask all */ cpumask_copy(&mask[group], cpu_possible_mask); } } return 0; } static inline void parse_cgroup_coregroup_field(struct device_node *dn, int (field)[CGROUP_COUNT][VENDOR_NR_CPUS], int default_value) { int group; for (group = 0; group < CGROUP_COUNT; group++) parse_coregroup_field(dn, task_cgroup_name[group], field[group], default_value); } /* energy table */ static void parse_specific_energy_table(struct device_node *dn, struct emstune_specific_energy_table *specific_et) { parse_coregroup_field(dn, "capacity-mips-mhzs", specific_et->mips_mhz, 0); parse_coregroup_field(dn, "dynamic-power-coeffs", specific_et->dynamic_coeff, 0); } /* sched policy */ static void parse_sched_policy(struct device_node *dn, struct emstune_sched_policy *sched_policy) { parse_cgroup_field(dn, sched_policy->policy, 0); } /* active weight */ static void parse_active_weight(struct device_node *dn, struct emstune_active_weight *active_weight) { parse_cgroup_coregroup_field(dn, active_weight->ratio, 100); } /* idle weight */ static void parse_idle_weight(struct device_node *dn, struct emstune_idle_weight *idle_weight) { parse_cgroup_coregroup_field(dn, idle_weight->ratio, 100); } /* freqboost */ static void parse_freqboost(struct device_node *dn, struct emstune_freqboost *freqboost) { parse_cgroup_coregroup_field(dn, freqboost->ratio, 0); } /* cpufreq governor parameters */ static void parse_cpufreq_gov(struct device_node *dn, struct emstune_cpufreq_gov *cpufreq_gov) { parse_coregroup_field(dn, "htask-boost", cpufreq_gov->htask_boost, 100); parse_coregroup_field(dn, "pelt-boost", cpufreq_gov->pelt_boost, 0); parse_coregroup_field(dn, "pelt-margin", cpufreq_gov->split_pelt_margin, 25); parse_coregroup_field(dn, "pelt-margin-freq", cpufreq_gov->split_pelt_margin_freq, INT_MAX); parse_coregroup_field(dn, "up-rate-limit-ms", cpufreq_gov->split_up_rate_limit, 4); parse_coregroup_field(dn, "up-rate-limit-freq", cpufreq_gov->split_up_rate_limit_freq, INT_MAX); if (of_property_read_u32(dn, "down-rate-limit-ms", &cpufreq_gov->down_rate_limit)) cpufreq_gov->down_rate_limit = 4; /* 4ms */ of_property_read_u32(dn, "rapid-scale-up", &cpufreq_gov->rapid_scale_up); of_property_read_u32(dn, "rapid-scale-down", &cpufreq_gov->rapid_scale_down); parse_coregroup_field(dn, "dis-buck-share", cpufreq_gov->dis_buck_share, 0); } static void parse_cpuidle_gov(struct device_node *dn, struct emstune_cpuidle_gov *cpuidle_gov) { parse_coregroup_field(dn, "expired-ratio", cpuidle_gov->expired_ratio, 100); } /* ontime migration */ #define UPPER_BOUNDARY 0 #define LOWER_BOUNDARY 1 static void __update_ontime_domain(struct ontime_dom *dom, struct list_head *dom_list) { /* * At EMS initializing time, capacity_cpu_orig() cannot be used * because it returns capacity that does not reflect frequency. * So, use et_max_cap() instead of capacity_cpu_orig(). */ unsigned long max_cap = et_max_cap(cpumask_any(&dom->cpus)); if (dom->upper_ratio < 0) dom->upper_boundary = 1024; else if (dom == list_last_entry(dom_list, struct ontime_dom, node)) { /* Upper boundary of last domain is meaningless */ dom->upper_boundary = 1024; } else dom->upper_boundary = max_cap * dom->upper_ratio / 100; if (dom->lower_ratio < 0) dom->lower_boundary = 0; else if (dom == list_first_entry(dom_list, struct ontime_dom, node)) { /* Lower boundary of first domain is meaningless */ dom->lower_boundary = 0; } else dom->lower_boundary = max_cap * dom->lower_ratio / 100; } static void update_ontime_domain(struct list_head *dom_list, int cpu, unsigned long ratio, int type) { struct ontime_dom *dom; list_for_each_entry(dom, dom_list, node) { if (cpumask_test_cpu(cpu, &dom->cpus)) { if (type == UPPER_BOUNDARY) dom->upper_ratio = ratio; else dom->lower_ratio = ratio; __update_ontime_domain(dom, dom_list); } } } void emstune_ontime_init(void) { int mode, level; for (mode = 0; mode < emstune.mode_count; mode++) { for (level = 0; level < emstune.level_count; level++) { struct emstune_set *set; struct ontime_dom *dom; set = &emstune.modes[mode].sets[level]; list_for_each_entry(dom, &set->ontime.dom_list, node) __update_ontime_domain(dom, &set->ontime.dom_list); } } } static int init_dom_list(struct device_node *dn, struct list_head *dom_list) { struct device_node *dom_dn; for_each_child_of_node(dn, dom_dn) { struct ontime_dom *dom; const char *buf; dom = kzalloc(sizeof(struct ontime_dom), GFP_KERNEL); if (!dom) return -ENOMEM; if (of_property_read_string(dom_dn, "cpus", &buf)) { kfree(dom); return -EINVAL; } else cpulist_parse(buf, &dom->cpus); if (of_property_read_u32(dom_dn, "upper-boundary", &dom->upper_ratio)) dom->upper_ratio = -1; if (of_property_read_u32(dom_dn, "lower-boundary", &dom->lower_ratio)) dom->lower_ratio = -1; /* * Init boundary as default. * Actual boundary value is set in cpufreq policy notifier. */ dom->upper_boundary = 1024; dom->lower_boundary = 0; list_add_tail(&dom->node, dom_list); } return 0; } static void parse_ontime(struct device_node *dn, struct emstune_ontime *ontime) { parse_cgroup_field(dn, ontime->enabled, 0); INIT_LIST_HEAD(&ontime->dom_list); init_dom_list(dn, &ontime->dom_list); ontime->p_dom_list = &ontime->dom_list; } /* cpus binding */ static void parse_cpus_binding(struct device_node *dn, struct emstune_cpus_binding *cpus_binding) { if (of_property_read_u32(dn, "target-sched-class", (unsigned int *)&cpus_binding->target_sched_class)) cpus_binding->target_sched_class = 0; parse_cgroup_field_mask(dn, cpus_binding->mask); } /* task express */ static void parse_tex(struct device_node *dn, struct emstune_tex *tex) { parse_cgroup_field(dn, tex->enabled, 0); of_property_read_u32(dn, "prio", &tex->prio); } /* Fluid RT scheduling */ static void parse_frt(struct device_node *dn, struct emstune_frt *frt) { parse_coregroup_field(dn, "active-ratio", frt->active_ratio, 100); } /* CPU Sparing */ static void update_ecs_threshold_ratio(struct list_head *domain_list, int id, int ratio) { struct ecs_domain *domain; list_for_each_entry(domain, domain_list, node) if (domain->id == id) domain->busy_threshold_ratio = ratio; } static int init_ecs_domain_list(struct device_node *ecs_dn, struct list_head *domain_list) { struct device_node *domain_dn; int id = 0; INIT_LIST_HEAD(domain_list); for_each_child_of_node(ecs_dn, domain_dn) { struct ecs_domain *domain; domain = kzalloc(sizeof(struct ecs_domain), GFP_KERNEL); if (!domain) return -ENOMEM; if (of_property_read_u32(domain_dn, "busy-threshold-ratio", (u32 *)&domain->busy_threshold_ratio)) domain->busy_threshold_ratio = 100; domain->id = id++; list_add_tail(&domain->node, domain_list); } return 0; } static void parse_ecs(struct device_node *dn, struct emstune_ecs *ecs) { INIT_LIST_HEAD(&ecs->domain_list); init_ecs_domain_list(dn, &ecs->domain_list); } static void parse_ecs_dynamic(struct device_node *dn, struct emstune_ecs_dynamic *dynamic) { parse_coregroup_field(dn, "dynamic-busy-ratio", dynamic->dynamic_busy_ratio, 0); } /* Margin applied to newly created task's utilization */ static void parse_ntu(struct device_node *dn, struct emstune_ntu *ntu) { parse_cgroup_field(dn, ntu->ratio, 100); } /* fclamp */ static void parse_fclamp(struct device_node *dn, struct emstune_fclamp *fclamp) { parse_coregroup_field(dn, "min-freq", fclamp->min_freq, 0); parse_coregroup_field(dn, "min-target-period", fclamp->min_target_period, 0); parse_coregroup_field(dn, "min-target-ratio", fclamp->min_target_ratio, 0); parse_coregroup_field(dn, "max-freq", fclamp->max_freq, 0); parse_coregroup_field(dn, "max-target-period", fclamp->max_target_period, 0); parse_coregroup_field(dn, "max-target-ratio", fclamp->max_target_ratio, 0); parse_cgroup_field(of_get_child_by_name(dn, "monitor-group"), fclamp->monitor_group, 0); } /* support uclamp */ static void parse_support_uclamp(struct device_node *dn, struct emstune_support_uclamp *su) { if (of_property_read_u32(dn, "enabled", &su->enabled)) su->enabled = 1; } /* gsc */ static void parse_gsc(struct device_node *dn, struct emstune_gsc *gsc) { if (of_property_read_u32(dn, "up_threshold", &gsc->up_threshold)) gsc->up_threshold = 1024; if (of_property_read_u32(dn, "down_threshold", &gsc->down_threshold)) gsc->down_threshold = 1024; parse_cgroup_field(of_get_child_by_name(dn, "monitor-group"), gsc->enabled, 0); } /* should spread */ static void parse_should_spread(struct device_node *dn, struct emstune_should_spread *ss) { if (of_property_read_u32(dn, "enabled", &ss->enabled)) ss->enabled = 0; } /****************************************************************************** * emstune sched boost * ******************************************************************************/ static int sched_boost; int emstune_sched_boost(void) { return sched_boost; } int emstune_support_uclamp(void) { struct emstune_set *cur_set = emstune_cur_set(); return cur_set->support_uclamp.enabled; } int emstune_should_spread(void) { struct emstune_set *cur_set = emstune_cur_set(); return cur_set->should_spread.enabled; } /****************************************************************************** * sysfs for emstune * ******************************************************************************/ static ssize_t sched_boost_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", sched_boost); } static ssize_t sched_boost_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int boost; if (sscanf(buf, "%d", &boost) != 1) return -EINVAL; /* ignore if requested mode is out of range */ if (boost < 0 || boost >= 100) return -EINVAL; sched_boost = boost; return count; } DEVICE_ATTR_RW(sched_boost); static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = 0, mode, level; unsigned long flags; for (mode = 0; mode < emstune.mode_count; mode++) ret += sprintf(buf + ret, "mode%d : %s\n", mode, emstune.modes[mode].desc); ret += sprintf(buf + ret, "\n"); spin_lock_irqsave(&emstune_lock, flags); ret += sprintf(buf + ret, "┌─────"); for (mode = 0; mode < emstune.mode_count; mode++) ret += sprintf(buf + ret, "┬──────"); ret += sprintf(buf + ret, "┐\n"); ret += sprintf(buf + ret, "│ "); for (mode = 0; mode < emstune.mode_count; mode++) ret += sprintf(buf + ret, "│ mode%d", mode); ret += sprintf(buf + ret, "│\n"); ret += sprintf(buf + ret, "├─────"); for (mode = 0; mode < emstune.mode_count; mode++) ret += sprintf(buf + ret, "┼──────"); ret += sprintf(buf + ret, "┤\n"); for (level = 0; level < emstune.level_count; level++) { ret += sprintf(buf + ret, "│ lv%d ", level); for (mode = 0; mode < emstune.mode_count; mode++) { if (mode == emstune.cur_mode && level == emstune.cur_level) ret += sprintf(buf + ret, "│ curr"); else ret += sprintf(buf + ret, "│ "); } ret += sprintf(buf + ret, "│\n"); if (level == emstune.level_count - 1) break; ret += sprintf(buf + ret, "├-----"); for (mode = 0; mode < emstune.mode_count; mode++) ret += sprintf(buf + ret, "┼------"); ret += sprintf(buf + ret, "┤\n"); } ret += sprintf(buf + ret, "└─────"); for (mode = 0; mode < emstune.mode_count; mode++) ret += sprintf(buf + ret, "┴──────"); ret += sprintf(buf + ret, "┘\n"); ret += sprintf(buf + ret, "\n"); spin_unlock_irqrestore(&emstune_lock, flags); return ret; } DEVICE_ATTR_RO(status); static ssize_t req_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; unsigned long flags; spin_lock_irqsave(&emstune_lock, flags); ret = sprintf(buf, "%d\n", emstune.cur_mode); spin_unlock_irqrestore(&emstune_lock, flags); return ret; } static ssize_t req_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int mode; if (sscanf(buf, "%d", &mode) != 1) return -EINVAL; /* ignore if requested mode is out of range */ if (mode < 0 || mode >= emstune.mode_count) return -EINVAL; emstune_mode_change(mode); return count; } DEVICE_ATTR_RW(req_mode); static ssize_t req_level_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; unsigned long flags; spin_lock_irqsave(&emstune_lock, flags); ret = sprintf(buf, "%d\n", emstune.cur_level); spin_unlock_irqrestore(&emstune_lock, flags); return ret; } static ssize_t req_level_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int level; if (sscanf(buf, "%d", &level) != 1) return -EINVAL; if (level < 0 || level >= emstune.level_count) return -EINVAL; emstune_update_request(&emstune.level_req, level); return count; } DEVICE_ATTR_RW(req_level); static ssize_t req_level_debug_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = 0, count = 0; struct plist_node *pos; plist_for_each(pos, &dev->power.qos->freq.min_freq.list) { struct freq_qos_request *f_req; struct dev_pm_qos_request *d_req; struct emstune_mode_request *req; f_req = container_of(pos, struct freq_qos_request, pnode); d_req = container_of(f_req, struct dev_pm_qos_request, data.freq); req = container_of(d_req, struct emstune_mode_request, dev_req); count++; ret += snprintf(buf + ret, PAGE_SIZE, "%d: %d (%s:%d)\n", count, f_req->pnode.prio, req->func, req->line); } return ret; } DEVICE_ATTR_RO(req_level_debug); static ssize_t reset_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "usage\n# > reset\n"); } static ssize_t reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int mode, level; if (sscanf(buf, "%d %d", &mode, &level) != 2) return -EINVAL; if (mode < 0 || mode >= emstune.mode_count || level < 0 || level >= emstune.level_count) return -EINVAL; emstune_reset(mode, level); return count; } DEVICE_ATTR_RW(reset); enum aio_tuner_item_type { TYPE1, /* cpu */ TYPE2, /* group */ TYPE3, /* cpu,group */ TYPE4, /* cpu,option */ TYPE5, /* option */ TYPE6, /* nop */ }; #define I(x, type, comm) x, #define ITEM_LIST \ I(sched_policy, TYPE2, "") \ I(active_weight, TYPE3, "") \ I(idle_weight, TYPE3, "") \ I(freqboost, TYPE3, "") \ I(cpufreq_gov_pelt_boost, TYPE1, "") \ I(cpufreq_gov_htask_boost, TYPE1, "") \ I(cpufreq_gov_dis_buck_share, TYPE1, "") \ I(cpufreq_gov_pelt_margin, TYPE1, "") \ I(cpufreq_gov_pelt_margin_freq, TYPE1, "") \ I(cpufreq_gov_rate_limit, TYPE1, "") \ I(cpufreq_gov_rate_limit_freq, TYPE1, "") \ I(fclamp_freq, TYPE4, "option:min=0,max=1") \ I(fclamp_period, TYPE4, "option:min=0,max=1") \ I(fclamp_ratio, TYPE4, "option:min=0,max=1") \ I(fclamp_monitor_group, TYPE2, "") \ I(ontime_en, TYPE2, "") \ I(ontime_boundary, TYPE4, "option:up=0,lo=1") \ I(cpus_binding_tsc, TYPE6, "val:mask") \ I(cpus_binding_cpumask, TYPE2, "val:mask") \ I(tex_en, TYPE2, "") \ I(tex_prio, TYPE6, "") \ I(ecs_threshold_ratio, TYPE5, "option:stage_id") \ I(ecs_dynamic_busy_ratio, TYPE1, "") \ I(ntu_ratio, TYPE2, "") \ I(frt_active_ratio, TYPE1, "") \ I(cpuidle_gov_expired_ratio, TYPE1, "") \ I(support_uclamp, TYPE6, "") \ I(gsc_en, TYPE2, "") \ I(gsc_up_threshold, TYPE6, "") \ I(gsc_down_threshold, TYPE6, "") \ I(should_spread, TYPE6, "") enum { ITEM_LIST field_count }; struct aio_tuner_item { int id; const char *name; int param_type; const char *comment; } aio_tuner_items[field_count]; static void aio_tuner_items_init(void) { int i; #undef I #define I(x, type, comm) #x, const char * const item_name[] = { ITEM_LIST }; #undef I #define I(x, type, comm) type, const int item_type[] = { ITEM_LIST }; #undef I #define I(x, type, comm) comm, const char * const item_comm[] = { ITEM_LIST }; for (i = 0; i < field_count; i++) { aio_tuner_items[i].id = i; aio_tuner_items[i].name = item_name[i]; aio_tuner_items[i].param_type = item_type[i]; aio_tuner_items[i].comment = item_comm[i]; } } static ssize_t func_list_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = 0, i; for (i = 0; i < field_count; i++) { ret += sprintf(buf + ret, "%d:%s\n", aio_tuner_items[i].id, aio_tuner_items[i].name); } return ret; } DEVICE_ATTR_RO(func_list); static ssize_t aio_tuner_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = 0, i; ret = sprintf(buf + ret, "\n"); ret += sprintf(buf + ret, "echo > aio_tuner\n"); ret += sprintf(buf + ret, "\n"); for (i = 0; i < field_count; i++) { struct aio_tuner_item *item = &aio_tuner_items[i]; char key[20] = { 0 }; switch (item->param_type) { case TYPE1: strcpy(key, ",cpu"); break; case TYPE2: strcpy(key, ",group"); break; case TYPE3: strcpy(key, ",cpu,group"); break; case TYPE4: strcpy(key, ",cpu,option"); break; case TYPE5: strcpy(key, ",option"); break; case TYPE6: break; }; ret += sprintf(buf + ret, "[%d] %s\n", item->id, item->name); ret += sprintf(buf + ret, " # echo {%d,mode,level%s} {val} > aio_tuner\n", item->id, key); if (strlen(item->comment)) ret += sprintf(buf + ret, " (%s)\n", item->comment); ret += sprintf(buf + ret, "\n"); } return ret; } #define STR_LEN 10 static int cut_hexa_prefix(char *str) { int i; if (!(str[0] == '0' && str[1] == 'x')) return -EINVAL; for (i = 0; i + 2 < STR_LEN; i++) { str[i] = str[i + 2]; str[i + 2] = '\n'; } return 0; } static int sanity_check_default(int field, int mode, int level) { if (field < 0 || field >= field_count) return -EINVAL; if (mode < 0 || mode >= emstune.mode_count) return -EINVAL; if (level < 0 || level >= emstune.level_count) return -EINVAL; return 0; } static int sanity_check_option(int cpu, int group, int type) { if (cpu < 0 || cpu > NR_CPUS) return -EINVAL; if (group < 0) return -EINVAL; if (!(type == 0 || type == 1)) return -EINVAL; return 0; } enum val_type { VAL_TYPE_ONOFF, VAL_TYPE_RATIO, VAL_TYPE_RATIO_NEGATIVE, VAL_TYPE_LEVEL, VAL_TYPE_CPU, VAL_TYPE_MASK, }; static int sanity_check_convert_value(char *arg, enum val_type type, int limit, void *v) { int ret = 0; long value; switch (type) { case VAL_TYPE_ONOFF: if (kstrtol(arg, 10, &value)) return -EINVAL; *(int *)v = !!(int)value; break; case VAL_TYPE_RATIO_NEGATIVE: if (kstrtol(arg, 10, &value)) return -EINVAL; if (value < -limit || value > limit) return -EINVAL; *(int *)v = (int)value; break; case VAL_TYPE_RATIO: case VAL_TYPE_LEVEL: case VAL_TYPE_CPU: if (kstrtol(arg, 10, &value)) return -EINVAL; if (value < 0 || value > limit) return -EINVAL; *(int *)v = (int)value; break; case VAL_TYPE_MASK: if (cut_hexa_prefix(arg)) return -EINVAL; if (cpumask_parse(arg, (struct cpumask *)v) || cpumask_empty((struct cpumask *)v)) return -EINVAL; break; } return ret; } static void set_value_single(int *field, char *v, enum val_type type, int limit) { int value; if (sanity_check_convert_value(v, VAL_TYPE_LEVEL, limit, &value)) return; *field = value; } static void set_value_coregroup(int (field)[VENDOR_NR_CPUS], int cpu, char *v, enum val_type type, int limit) { int i, value; if (sanity_check_option(cpu, 0, 0)) return; if (sanity_check_convert_value(v, type, limit, &value)) return; for_each_cpu(i, cpu_coregroup_mask(cpu)) field[i] = value; } static void set_value_cgroup(int (field)[CGROUP_COUNT], int group, char *v, enum val_type type, int limit) { int value; if (sanity_check_option(0, group, 0)) return; if (sanity_check_convert_value(v, type, limit, &value)) return; if (group >= CGROUP_COUNT) { for (group = 0; group < CGROUP_COUNT; group++) field[group] = value; return; } field[group] = value; } static void set_value_cgroup_coregroup(int (field)[CGROUP_COUNT][VENDOR_NR_CPUS], int cpu, int group, char *v, enum val_type type, int limit) { int i, value; if (sanity_check_option(cpu, group, 0)) return; if (sanity_check_convert_value(v, type, limit, &value)) return; if (group >= CGROUP_COUNT) { for (group = 0; group < CGROUP_COUNT; group++) for_each_cpu(i, cpu_coregroup_mask(cpu)) field[group][i] = value; return; } for_each_cpu(i, cpu_coregroup_mask(cpu)) field[group][i] = value; } #define NUM_OF_KEY (10) #define MAX_RATIO 10000 /* 10000% */ #define MAX_ESG_STEP 20 #define MAX_PATIENT_TICK 100 #define MAX_RATE_LIMIT 1000 /* 1000ms */ static ssize_t aio_tuner_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct emstune_set *set; char arg0[100], arg1[100], *_arg, *ptr; int keys[NUM_OF_KEY], i, ret, v; long val; int mode, level, field, cpu, group, type; struct cpumask mask; if (sscanf(buf, "%99s %99s", &arg0, &arg1) != 2) return -EINVAL; /* fill keys with default value(-1) */ for (i = 0; i < NUM_OF_KEY; i++) keys[i] = -1; /* classify key input value by comma(,) */ _arg = arg0; ptr = strsep(&_arg, ","); i = 0; while (ptr != NULL && (i < NUM_OF_KEY)) { ret = kstrtol(ptr, 10, &val); if (ret) return ret; keys[i] = (int)val; i++; ptr = strsep(&_arg, ","); }; field = keys[0]; mode = keys[1]; level = keys[2]; if (sanity_check_default(field, mode, level)) return -EINVAL; set = &emstune.modes[mode].sets[level]; switch (field) { case sched_policy: set_value_cgroup(set->sched_policy.policy, keys[3], arg1, VAL_TYPE_LEVEL, NUM_OF_SCHED_POLICY); break; case active_weight: set_value_cgroup_coregroup(set->active_weight.ratio, keys[3], keys[4], arg1, VAL_TYPE_RATIO, MAX_RATIO); break; case idle_weight: set_value_cgroup_coregroup(set->idle_weight.ratio, keys[3], keys[4], arg1, VAL_TYPE_RATIO, MAX_RATIO); break; case freqboost: set_value_cgroup_coregroup(set->freqboost.ratio, keys[3], keys[4], arg1, VAL_TYPE_RATIO_NEGATIVE, MAX_RATIO); break; case cpufreq_gov_pelt_margin: set_value_coregroup(set->cpufreq_gov.split_pelt_margin, keys[3], arg1, VAL_TYPE_RATIO_NEGATIVE, MAX_RATIO); break; case cpufreq_gov_htask_boost: set_value_coregroup(set->cpufreq_gov.htask_boost, keys[3], arg1, VAL_TYPE_RATIO_NEGATIVE, MAX_RATIO); break; case cpufreq_gov_pelt_boost: set_value_coregroup(set->cpufreq_gov.pelt_boost, keys[3], arg1, VAL_TYPE_RATIO_NEGATIVE, MAX_RATIO); break; case cpufreq_gov_rate_limit: set_value_coregroup(set->cpufreq_gov.split_up_rate_limit, keys[3], arg1, VAL_TYPE_LEVEL, INT_MAX); break; case cpuidle_gov_expired_ratio: set_value_coregroup(set->cpuidle_gov.expired_ratio, keys[3], arg1, VAL_TYPE_RATIO, MAX_RATIO); break; case ontime_en: set_value_cgroup(set->ontime.enabled, keys[3], arg1, VAL_TYPE_ONOFF, 0); break; case ontime_boundary: cpu = keys[3]; type = !!keys[4]; if (sanity_check_option(cpu, 0, 0)) return -EINVAL; if (sanity_check_convert_value(arg1, VAL_TYPE_LEVEL, 1024, &v)) return -EINVAL; update_ontime_domain(&set->ontime.dom_list, cpu, v, type); break; case cpus_binding_tsc: if (kstrtol(arg1, 0, &val)) return -EINVAL; val &= EMS_SCHED_CLASS_MASK; set->cpus_binding.target_sched_class = 0; for (i = 0; i < NUM_OF_SCHED_CLASS; i++) if (test_bit(i, &val)) set_bit(i, &set->cpus_binding.target_sched_class); break; case cpus_binding_cpumask: group = keys[3]; if (sanity_check_option(0, group, 0)) return -EINVAL; if (sanity_check_convert_value(arg1, VAL_TYPE_MASK, 0, &mask)) return -EINVAL; cpumask_copy(&set->cpus_binding.mask[group], &mask); break; case tex_en: set_value_cgroup(set->tex.enabled, keys[3], arg1, VAL_TYPE_ONOFF, 0); break; case tex_prio: set_value_single(&set->tex.prio, arg1, VAL_TYPE_LEVEL, MAX_PRIO); break; case frt_active_ratio: set_value_coregroup(set->frt.active_ratio, keys[3], arg1, VAL_TYPE_RATIO, MAX_RATIO); break; case ecs_threshold_ratio: i = keys[3]; if (sanity_check_convert_value(arg1, VAL_TYPE_LEVEL, 10000, &v)) return -EINVAL; update_ecs_threshold_ratio(&set->ecs.domain_list, i, v); break; case ecs_dynamic_busy_ratio: set_value_coregroup(set->ecs_dynamic.dynamic_busy_ratio, keys[3], arg1, VAL_TYPE_RATIO, MAX_RATIO); break; case ntu_ratio: set_value_cgroup(set->ntu.ratio, keys[3], arg1, VAL_TYPE_RATIO_NEGATIVE, 100); break; case fclamp_freq: type = !!keys[4]; if (type) set_value_coregroup(set->fclamp.max_freq, keys[3], arg1, VAL_TYPE_LEVEL, 10000000); else set_value_coregroup(set->fclamp.min_freq, keys[3], arg1, VAL_TYPE_LEVEL, 10000000); break; case fclamp_period: type = !!keys[4]; if (type) set_value_coregroup(set->fclamp.max_target_period, keys[3], arg1, VAL_TYPE_LEVEL, 100); else set_value_coregroup(set->fclamp.min_target_period, keys[3], arg1, VAL_TYPE_LEVEL, 100); break; case fclamp_ratio: type = !!keys[4]; if (type) set_value_coregroup(set->fclamp.max_target_ratio, keys[3], arg1, VAL_TYPE_LEVEL, 1000); else set_value_coregroup(set->fclamp.min_target_ratio, keys[3], arg1, VAL_TYPE_LEVEL, 1000); break; case fclamp_monitor_group: set_value_cgroup(set->fclamp.monitor_group, keys[3], arg1, VAL_TYPE_ONOFF, 0); break; case cpufreq_gov_pelt_margin_freq: set_value_coregroup(set->cpufreq_gov.split_pelt_margin_freq, keys[3], arg1, VAL_TYPE_LEVEL, INT_MAX); break; case cpufreq_gov_rate_limit_freq: set_value_coregroup(set->cpufreq_gov.split_up_rate_limit_freq, keys[3], arg1, VAL_TYPE_LEVEL, INT_MAX); break; case cpufreq_gov_dis_buck_share: set_value_coregroup(set->cpufreq_gov.dis_buck_share, keys[3], arg1, VAL_TYPE_LEVEL, 100); break; case support_uclamp: set_value_single(&set->support_uclamp.enabled, arg1, VAL_TYPE_ONOFF, 1); break; case gsc_en: set_value_cgroup(set->gsc.enabled, keys[3], arg1, VAL_TYPE_ONOFF, 0); break; case gsc_up_threshold: set_value_single(&set->gsc.up_threshold, arg1, VAL_TYPE_LEVEL, 1024); break; case gsc_down_threshold: set_value_single(&set->gsc.down_threshold, arg1, VAL_TYPE_LEVEL, 1024); break; case should_spread: set_value_single(&set->should_spread.enabled, arg1, VAL_TYPE_ONOFF, 1); break; } emstune_notify(); return count; } DEVICE_ATTR_RW(aio_tuner); static char *task_cgroup_simple_name[] = { "root", "fg", "bg", "ta", "rt", "sy", "syb", "n-h", "c-d", "mg", }; static const char *get_sched_class_str(int class) { if (class & EMS_SCHED_STOP) return "STOP"; if (class & EMS_SCHED_DL) return "DL"; if (class & EMS_SCHED_RT) return "RT"; if (class & EMS_SCHED_FAIR) return "FAIR"; if (class & EMS_SCHED_IDLE) return "IDLE"; return NULL; } static int get_type_pending_bit(int type) { int bit = 0; type >>= 1; while (type) { type >>= 1; bit++; } return bit; } #define MSG_SIZE 8192 static ssize_t cur_set_read(struct file *file, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t offset, size_t size) { struct emstune_set *cur_set = emstune_cur_set(); int cpu, group, class; char *msg = kcalloc(MSG_SIZE, sizeof(char), GFP_KERNEL); ssize_t count = 0, msg_size; /* mode/level info */ if (emstune.boot.ongoing) count += sprintf(msg + count, "Booting exclusive mode\n"); else count += sprintf(msg + count, "%s mode : level%d [mode=%d level=%d]\n", emstune.modes[emstune.cur_mode].desc, emstune.cur_level, get_type_pending_bit(EMSTUNE_MODE_TYPE(cur_set->type)), get_type_pending_bit(EMSTUNE_BOOST_TYPE(cur_set->type))); count += sprintf(msg + count, "\n"); /* sched policy */ count += sprintf(msg + count, "[sched-policy]\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4s", task_cgroup_simple_name[group]); count += sprintf(msg + count, "\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4d", cur_set->sched_policy.policy[group]); count += sprintf(msg + count, "\n\n"); /* active weight */ count += sprintf(msg + count, "[active-weight]\n"); count += sprintf(msg + count, " "); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4s", task_cgroup_simple_name[group]); count += sprintf(msg + count, "\n"); for_each_possible_cpu(cpu) { count += sprintf(msg + count, "cpu%d:", cpu); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4d", cur_set->active_weight.ratio[group][cpu]); count += sprintf(msg + count, "\n"); } count += sprintf(msg + count, "\n"); /* idle weight */ count += sprintf(msg + count, "[idle-weight]\n"); count += sprintf(msg + count, " "); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4s", task_cgroup_simple_name[group]); count += sprintf(msg + count, "\n"); for_each_possible_cpu(cpu) { count += sprintf(msg + count, "cpu%d:", cpu); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4d", cur_set->idle_weight.ratio[group][cpu]); count += sprintf(msg + count, "\n"); } count += sprintf(msg + count, "\n"); /* freq boost */ count += sprintf(msg + count, "[freq-boost]\n"); count += sprintf(msg + count, " "); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4s", task_cgroup_simple_name[group]); count += sprintf(msg + count, "\n"); for_each_possible_cpu(cpu) { count += sprintf(msg + count, "cpu%d:", cpu); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4d", cur_set->freqboost.ratio[group][cpu]); count += sprintf(msg + count, "\n"); } count += sprintf(msg + count, "\n"); /* cpufreq gov */ count += sprintf(msg + count, "[cpufreq gov]\n"); count += sprintf(msg + count, " |-pelt margin--||-rate limit--|\n"); count += sprintf(msg + count, " pb hb dbs margin freq rate freq\n"); for_each_possible_cpu(cpu) { count += sprintf(msg + count, "cpu%d: %3d %3d %4d", cpu, cur_set->cpufreq_gov.pelt_boost[cpu], cur_set->cpufreq_gov.htask_boost[cpu], cur_set->cpufreq_gov.dis_buck_share[cpu]); if (cur_set->cpufreq_gov.split_pelt_margin_freq[cpu] == INT_MAX) count += sprintf(msg + count, " %4d %7s", cur_set->cpufreq_gov.split_pelt_margin[cpu], "INF"); else count += sprintf(msg + count, " %4d %7u", cur_set->cpufreq_gov.split_pelt_margin[cpu], cur_set->cpufreq_gov.split_pelt_margin_freq[cpu]); if (cur_set->cpufreq_gov.split_up_rate_limit_freq[cpu] == INT_MAX) count += sprintf(msg + count, " %4d %7s\n", cur_set->cpufreq_gov.split_up_rate_limit[cpu], "INF"); else count += sprintf(msg + count, " %4d %7u\n", cur_set->cpufreq_gov.split_up_rate_limit[cpu], cur_set->cpufreq_gov.split_up_rate_limit_freq[cpu]); } count += sprintf(msg + count, "\n"); /* cpuidle gov */ count += sprintf(msg + count, "[cpuidle gov]\n"); count += sprintf(msg + count, "expired ratio\n"); for_each_possible_cpu(cpu) { if (cpu != cpumask_first(cpu_coregroup_mask(cpu))) continue; count += sprintf(msg + count, "cpu%d: %4d\n", cpu, cur_set->cpuidle_gov.expired_ratio[cpu]); } count += sprintf(msg + count, "\n"); /* ontime */ count += sprintf(msg + count, "[ontime]\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4s", task_cgroup_simple_name[group]); count += sprintf(msg + count, "\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4d", cur_set->ontime.enabled[group]); count += sprintf(msg + count, "\n\n"); { struct ontime_dom *dom; count += sprintf(msg + count, "-----------------------------\n"); if (!list_empty(cur_set->ontime.p_dom_list)) { list_for_each_entry(dom, cur_set->ontime.p_dom_list, node) { count += sprintf(msg + count, " cpus : %*pbl\n", cpumask_pr_args(&dom->cpus)); count += sprintf(msg + count, " upper boundary : %4lu (%3d%%)\n", dom->upper_boundary, dom->upper_ratio); count += sprintf(msg + count, " lower boundary : %4lu (%3d%%)\n", dom->lower_boundary, dom->lower_ratio); count += sprintf(msg + count, "-----------------------------\n"); } } else { count += sprintf(msg + count, "list empty!\n"); count += sprintf(msg + count, "-----------------------------\n"); } } count += sprintf(msg + count, "\n"); /* cpus binding */ count += sprintf(msg + count, "[cpus binding]\n"); for (class = 0; class < NUM_OF_SCHED_CLASS; class++) count += sprintf(msg + count, "%5s", get_sched_class_str(1 << class)); count += sprintf(msg + count, "\n"); for (class = 0; class < NUM_OF_SCHED_CLASS; class++) count += sprintf(msg + count, "%5d", (cur_set->cpus_binding.target_sched_class & (1 << class) ? 1 : 0)); count += sprintf(msg + count, "\n\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4s", task_cgroup_simple_name[group]); count += sprintf(msg + count, "\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4x", *(unsigned int *)cpumask_bits(&cur_set->cpus_binding.mask[group])); count += sprintf(msg + count, "\n\n"); /* tex */ count += sprintf(msg + count, "[tex]\n"); count += sprintf(msg + count, "prio : %d\n", cur_set->tex.prio); count += sprintf(msg + count, "\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4s", task_cgroup_simple_name[group]); count += sprintf(msg + count, "\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4d", cur_set->tex.enabled[group]); count += sprintf(msg + count, "\n\n"); /* frt */ count += sprintf(msg + count, "[frt]\n"); count += sprintf(msg + count, " active\n"); for_each_possible_cpu(cpu) count += sprintf(msg + count, "cpu%d: %4d%%\n", cpu, cur_set->frt.active_ratio[cpu]); count += sprintf(msg + count, "\n"); /* ecs */ count += sprintf(msg + count, "[ecs]\n"); { struct ecs_domain *domain; count += sprintf(msg + count, "-------------------------\n"); if (!list_empty(&cur_set->ecs.domain_list)) { list_for_each_entry(domain, &cur_set->ecs.domain_list, node) { count += sprintf(msg + count, " domain id : %d\n", domain->id); count += sprintf(msg + count, " threshold ratio : %u\n", domain->busy_threshold_ratio); count += sprintf(msg + count, "-------------------------\n"); } } else { count += sprintf(msg + count, "list empty!\n"); count += sprintf(msg + count, "-------------------------\n"); } } count += sprintf(msg + count, " busy_ratio\n"); for_each_possible_cpu(cpu) count += sprintf(msg + count, "cpu%d: %4d%%\n", cpu, cur_set->ecs_dynamic.dynamic_busy_ratio[cpu]); count += sprintf(msg + count, "\n"); /* new task util ratio */ count += sprintf(msg + count, "[new task util ratio]\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4s", task_cgroup_simple_name[group]); count += sprintf(msg + count, "\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4d", cur_set->ntu.ratio[group]); count += sprintf(msg + count, "\n\n"); /* fclamp */ count += sprintf(msg + count, "[fclamp]\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4s", task_cgroup_simple_name[group]); count += sprintf(msg + count, "\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4d", cur_set->fclamp.monitor_group[group]); count += sprintf(msg + count, "\n\n"); count += sprintf(msg + count, " |-------- min -------||-------- max -------|\n"); count += sprintf(msg + count, " freq period ratio freq period ratio\n"); for_each_possible_cpu(cpu) { count += sprintf(msg + count, "cpu%d: %7d %6d %5d %7d %6d %5d\n", cpu, cur_set->fclamp.min_freq[cpu], cur_set->fclamp.min_target_period[cpu], cur_set->fclamp.min_target_ratio[cpu], cur_set->fclamp.max_freq[cpu], cur_set->fclamp.max_target_period[cpu], cur_set->fclamp.max_target_ratio[cpu]); } count += sprintf(msg + count, "\n"); /* support uclamp */ count += sprintf(msg + count, "[support uclamp]\n"); count += sprintf(msg + count, "enabled : %d\n", cur_set->support_uclamp.enabled); count += sprintf(msg + count, "\n"); /* gsc */ count += sprintf(msg + count, "[gsc]\n"); count += sprintf(msg + count, "up_threshold : %d\n", cur_set->gsc.up_threshold); count += sprintf(msg + count, "down_threshold : %d\n", cur_set->gsc.down_threshold); count += sprintf(msg + count, "\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4s", task_cgroup_simple_name[group]); count += sprintf(msg + count, "\n"); for (group = 0; group < CGROUP_COUNT; group++) count += sprintf(msg + count, "%4d", cur_set->gsc.enabled[group]); count += sprintf(msg + count, "\n\n"); /* should spread */ count += sprintf(msg + count, "[should spread]\n"); count += sprintf(msg + count, "enabled : %d\n", cur_set->should_spread.enabled); count += sprintf(msg + count, "\n"); msg_size = min_t(ssize_t, count, MSG_SIZE); msg_size = memory_read_from_buffer(buf, size, &offset, msg, msg_size); kfree(msg); return msg_size; } static BIN_ATTR(cur_set, 0440, cur_set_read, NULL, 0); static DEFINE_MUTEX(task_boost_mutex); static int task_boost = 0; static ssize_t task_boost_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = 0; mutex_lock(&task_boost_mutex); ret = sprintf(buf, "%d\n", task_boost); mutex_unlock(&task_boost_mutex); return ret; } static ssize_t task_boost_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int tid; struct task_struct *task; mutex_lock(&task_boost_mutex); if (sscanf(buf, "%d", &tid) != 1) { mutex_unlock(&task_boost_mutex); return -EINVAL; } /* clear prev task_boost */ if (task_boost != 0) { task = get_pid_task(find_vpid(task_boost), PIDTYPE_PID); if (task) { ems_boosted_tex(task) = 0; put_task_struct(task); } } /* set task_boost */ task = get_pid_task(find_vpid(tid), PIDTYPE_PID); if (task) { ems_boosted_tex(task) = 1; put_task_struct(task); } task_boost = tid; mutex_unlock(&task_boost_mutex); return count; } DEVICE_ATTR_RW(task_boost); static ssize_t task_boost_del_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = 0; return ret; } static ssize_t task_boost_del_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int tid; struct task_struct *task; mutex_lock(&task_boost_mutex); if (sscanf(buf, "%d", &tid) != 1) { mutex_unlock(&task_boost_mutex); return -EINVAL; } task = get_pid_task(find_vpid(tid), PIDTYPE_PID); if (task) { ems_boosted_tex(task) = 0; put_task_struct(task); } /* clear task_boost at last task_boost_del */ if (task_boost == tid) task_boost = 0; mutex_unlock(&task_boost_mutex); return count; } DEVICE_ATTR_RW(task_boost_del); static struct attribute *emstune_attrs[] = { &dev_attr_sched_boost.attr, &dev_attr_status.attr, &dev_attr_req_mode.attr, &dev_attr_req_level.attr, &dev_attr_req_level_debug.attr, &dev_attr_reset.attr, &dev_attr_aio_tuner.attr, &dev_attr_func_list.attr, &dev_attr_task_boost.attr, &dev_attr_task_boost_del.attr, NULL, }; static struct bin_attribute *emstune_bin_attrs[] = { &bin_attr_cur_set, NULL, }; static struct attribute_group emstune_attr_group = { .name = "emstune", .attrs = emstune_attrs, .bin_attrs = emstune_bin_attrs, }; /****************************************************************************** * initialization * ******************************************************************************/ static int emstune_mode_backup(void) { int i; emstune.modes_backup = kzalloc(sizeof(struct emstune_mode) * emstune.mode_count, GFP_KERNEL); if (!emstune.modes_backup) return -ENOMEM; /* backup modes */ memcpy(emstune.modes_backup, emstune.modes, sizeof(struct emstune_mode) * emstune.mode_count); for (i = 0; i < emstune.mode_count; i++) { emstune.modes_backup[i].sets = kzalloc(sizeof(struct emstune_set) * emstune.level_count, GFP_KERNEL); if (!emstune.modes_backup[i].sets) return -ENOMEM; /* backup sets */ memcpy(emstune.modes_backup[i].sets, emstune.modes[i].sets, sizeof(struct emstune_set) * emstune.level_count); } return 0; } static void emstune_set_type_init(struct emstune_set *set, int mode, int level) { /* init set type */ set->type = 0; set->type |= (1 << level); set->type |= (1 << (BEGIN_OF_MODE_TYPE + mode)); } int emstune_cpu_dsu_table_index(void *_set) { struct emstune_set *set = (struct emstune_set *)_set; return set->cpu_dsu_table_index; } EXPORT_SYMBOL_GPL(emstune_cpu_dsu_table_index); static void emstune_perf_table_init(struct emstune_set *set, struct device_node *base_node, struct device_node *set_node) { if (!of_property_read_u32(set_node, "cpu-dsu-table-index", &set->cpu_dsu_table_index)) return; if (!of_property_read_u32(base_node, "cpu-dsu-table-index", &set->cpu_dsu_table_index)) return; set->cpu_dsu_table_index = 0; /* 0 = default index */ } #define parse(_name) \ child = of_get_child_by_name(set_node, #_name); \ if (!child) \ child = of_get_child_by_name(base_node, #_name); \ parse_##_name(child, &set->_name); \ of_node_put(child) static int emstune_set_init(struct device_node *base_node, struct device_node *set_node, int mode, int level, struct emstune_set *set) { struct device_node *child; emstune_set_type_init(set, mode, level); emstune_perf_table_init(set, base_node, set_node); parse(specific_energy_table); parse(sched_policy); parse(active_weight); parse(idle_weight); parse(freqboost); parse(cpufreq_gov); parse(cpuidle_gov); parse(ontime); parse(cpus_binding); parse(tex); parse(frt); parse(ecs); parse(ecs_dynamic); parse(ntu); parse(fclamp); parse(support_uclamp); parse(gsc); parse(should_spread); return 0; } static void emstune_boot_done(struct work_struct *work) { unsigned long flags; spin_lock_irqsave(&emstune_lock, flags); emstune.boot.ongoing = 0; emstune_notify(); spin_unlock_irqrestore(&emstune_lock, flags); } static void emstune_boot(void) { unsigned long flags; spin_lock_irqsave(&emstune_lock, flags); emstune.boot.ongoing = 1; emstune_notify(); spin_unlock_irqrestore(&emstune_lock, flags); INIT_DELAYED_WORK(&emstune.boot.work, emstune_boot_done); schedule_delayed_work(&emstune.boot.work, msecs_to_jiffies(40000)); } static int emstune_mode_init(struct device_node *dn) { struct device_node *emstune_node, *mode_node, *node; int mode_id, level, ret; emstune_node = of_get_child_by_name(dn, "emstune"); if (!emstune_node) return -EINVAL; /* If boost_level does not exist in dt, emstune.boost_level is 0 */ of_property_read_u32(emstune_node, "boost-level", &emstune.boost_level); emstune.mode_count = of_get_child_count(emstune_node); if (!emstune.mode_count) return -ENODATA; emstune.modes = kzalloc(sizeof(struct emstune_mode) * emstune.mode_count, GFP_KERNEL); if (!emstune.modes) return -ENOMEM; mode_id = 0; for_each_child_of_node(emstune_node, mode_node) { struct device_node *level_node; struct emstune_mode *mode = &emstune.modes[mode_id]; struct emstune_set *sets; int level_count; if (of_property_read_string(mode_node, "desc", &mode->desc)) { ret = -ENODATA; goto fail; } level_count = of_get_child_count(mode_node); if (!level_count) { ret = -ENODATA; goto fail; } if (!emstune.level_count) emstune.level_count = level_count; else if (emstune.level_count != level_count) { /* the level count of each mode must be same */ ret = -EINVAL; goto fail; } sets = kzalloc(sizeof(struct emstune_set) * emstune.level_count, GFP_KERNEL); if (!sets) { ret = -ENOMEM; goto fail; } level = 0; for_each_child_of_node(mode_node, level_node) { struct device_node *base_node = of_parse_phandle(level_node, "base", 0); struct device_node *set_node = of_parse_phandle(level_node, "set", 0); if (!base_node || !set_node) { ret = -ENODATA; goto fail; } ret = emstune_set_init(base_node, set_node, mode_id, level, &sets[level]); of_node_put(base_node); of_node_put(set_node); if (ret) goto fail; level++; } mode->sets = sets; mode_id++; } /* backup origin emstune */ if (emstune_mode_backup()) goto fail; node = of_parse_phandle(emstune_node, "boot-set", 0); if (node) { emstune_set_init(node, node, 0, 0, &emstune.boot.set); emstune_boot(); of_node_put(node); } else emstune_mode_change(0); return 0; fail: for (mode_id = 0; mode_id < emstune.mode_count; mode_id++) { struct emstune_mode *mode = &emstune.modes[mode_id]; kfree(mode->sets); mode->sets = NULL; } kfree(emstune.modes); emstune.modes = NULL; return ret; } static int emstune_pm_qos_init(struct device *dev) { int ret; emstune.nb.notifier_call = emstune_pm_qos_notifier; ret = dev_pm_qos_add_notifier(dev, &emstune.nb, DEV_PM_QOS_MIN_FREQUENCY); if (ret) { pr_err("Failed to register emstume qos notifier\n"); return ret; } emstune.ems_dev = dev; emstune_add_request(&emstune.level_req); return 0; } int emstune_init(struct kobject *ems_kobj, struct device_node *dn, struct device *dev) { int ret; ret = emstune_mode_init(dn); if (ret) { WARN(1, "failed to initialize emstune with err=%d\n", ret); return ret; } ret = emstune_pm_qos_init(dev); if (ret) { pr_err("Failed to initialize emstune PM QoS\n"); return ret; } if (sysfs_create_group(ems_kobj, &emstune_attr_group)) pr_err("failed to initialize emstune sysfs\n"); aio_tuner_items_init(); return 0; }