/* * cpufreq logging driver * Jungwook Kim * updated: 2021 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../../kernel/sched/sched.h" #include "../../../kernel/sched/ems/ems.h" #include #include #include #include #include #include #include "xperf.h" #if IS_ENABLED(CONFIG_EXYNOS_UFCC) extern unsigned int get_cpufreq_max_limit(void); #endif #if IS_ENABLED(CONFIG_EXYNOS_AFM) extern unsigned int get_afm_clipped_freq(int cpu); #endif static const char *prefix = "xperf"; #define MAX_TNAMES 16 static uint cal_id_mif = 0; static uint cal_id_g3d = 0; static uint devfreq_mif = 0; static int is_running; static void *buf = NULL; static uint polling_ms = 100; static uint bind_cpu = 0; static uint pid = 0; static char tns[MAX_TNAMES][TASK_COMM_LEN + 1] = {"", }; static uint util_thd = 300; static u32 power_coeffs[MAX_CLUSTER]; int cl_cnt; struct my_cluster cls[MAX_CLUSTER]; static int init_cls(void) { struct cpufreq_policy *policy; struct cpufreq_frequency_table *cursor; int next_cpu = 0; int last_cpu; int table_size; int cnt = 0; int i; while (next_cpu < num_possible_cpus()) { policy = cpufreq_cpu_get(next_cpu); if (!policy) { next_cpu++; continue; } cls[cnt].siblings = policy->related_cpus; last_cpu = cpumask_last(cls[cnt].siblings); table_size = 0; cpufreq_for_each_entry(cursor, policy->freq_table) table_size++; cls[cnt].freq_size = table_size; cls[cnt].maxfreq = policy->cpuinfo.max_freq; cls[cnt].minfreq = policy->cpuinfo.min_freq; cls[cnt].first_cpu = next_cpu; cls[cnt].last_cpu = last_cpu; cls[cnt].freqs = kcalloc(cls[cnt].freq_size, sizeof(struct freq), GFP_KERNEL); for (i = 0; i < cls[cnt].freq_size; i++) cls[cnt].freqs[i].freq = policy->freq_table[i].frequency; cnt++; next_cpu = last_cpu + 1; } cl_cnt = cnt; return 0; } int get_cl_idx(int cpu) { int index = 0; int i; for (i = 0; i < cl_cnt; i++) { if (cpu <= cls[i].last_cpu) { index = i; break; } } return index; } int get_f_idx(int cl_idx, int freq) { int index = 0; int i; for (i = 0; i < cls[cl_idx].freq_size; i++) { if (cls[cl_idx].freqs[i].freq == freq) { index = i; break; } } return index; } //--------------------------------------- // hooking function uint cpu_util_avgs[VENDOR_NR_CPUS]; static void update_cpu_util_avg(void *data, struct cfs_rq *cfs_rq) { unsigned int util = READ_ONCE(cfs_rq->avg.util_avg); util = (util > 1024) ? 1024 : util; cpu_util_avgs[cpu_of(rq_of(cfs_rq))] = util; } //--------------------------------------- // thread main static int cpufreq_log_thread(void *data) { int i; int ret = 0; uint buf_size; struct thermal_zone_device *tz; int temp[3], temp_g3d, temp_bat; const char *tz_names[] = { "BIG", "LITTLE", "MID" }; uint siop, ocp; int cpu; uint cpu_cur[MAX_CLUSTER]; uint cpu_max[MAX_CLUSTER]; uint mif, gpu; int gpu_util; int grp_start, grp_num; int cpu_power; int power_total; int cl_idx; int f_idx; int freq; int try_alloc_cnt = 3; struct pid *pgrp; struct task_struct *p; static int task_num_cpus[VENDOR_NR_CPUS] = {0,}; uint util; if (is_running) { pr_info("[%s] already running!!\n", prefix); return 0; } // alloc buf buf_size = KMALLOC_MAX_SIZE; if (buf) vfree(buf); while (try_alloc_cnt-- > 0) { buf = vmalloc(buf_size); if (!buf) { pr_info("[%s] kmalloc failed: try again...%d\n", prefix, try_alloc_cnt); buf_size -= (1024*1024); continue; } else { break; } } if (try_alloc_cnt <= 0) { pr_info("[%s] kmalloc failed: buf_size: %d\n", prefix, buf_size); return 0; } else { memset(buf, 0, buf_size); pr_info("[%s] kmalloc ok. buf=%p, buf_size=%d\n", prefix, buf, buf_size); } // start is_running = 1; pr_info("[%s] cpufreq log start\n", prefix); //--------------------- // header //--------------------- // temperature ret += snprintf(buf + ret, buf_size - ret, "01-temp_BIG "); ret += snprintf(buf + ret, buf_size - ret, "01-temp_LITTLE "); if (cl_cnt > 2) ret += snprintf(buf + ret, buf_size - ret, "01-temp_MID "); ret += snprintf(buf + ret, buf_size - ret, "01-temp_G3D "); ret += snprintf(buf + ret, buf_size - ret, "01-temp_BAT "); // cpufreq grp_start = 2; for (i = cl_cnt-1; i >= 0; i--) { grp_num = grp_start + (cl_cnt-1 - i); cpu = cls[i].first_cpu; ret += snprintf(buf + ret, buf_size - ret, "0%d-cpu%d_max ", grp_num, cpu); ret += snprintf(buf + ret, buf_size - ret, "0%d-cpu%d_cur ", grp_num, cpu); if (i == (cl_cnt-1)) { // if big cluster ret += snprintf(buf + ret, buf_size - ret, "0%d-cpu%d_siop ", grp_num, cpu); ret += snprintf(buf + ret, buf_size - ret, "0%d-cpu%d_ocp ", grp_num, cpu); } } // mif, gpu ret += snprintf(buf + ret, buf_size - ret, "05-mif_cur 06-gpu_util 06-gpu_cur "); // cpu util for_each_possible_cpu(cpu) { ret += snprintf(buf + ret, buf_size - ret, "08-util_cpu%d ", cpu); } // cpu power for_each_possible_cpu(cpu) { ret += snprintf(buf + ret, buf_size - ret, "09-power_cpu%d ", cpu); } ret += snprintf(buf + ret, buf_size - ret, "09-power_total "); // task multi for_each_possible_cpu(cpu) { ret += snprintf(buf + ret, buf_size - ret, "10-task_cpu%d ", cpu); } // tune mode ret += snprintf(buf + ret, buf_size - ret, "11-ems_mode 11-ems_level "); ret -= 1; ret += snprintf(buf + ret, buf_size - ret, "\n"); //--------------------- // body //--------------------- while (is_running) { // cpu temperature for (i = 0; i < cl_cnt; i++) { tz = thermal_zone_get_zone_by_name(tz_names[i]); thermal_zone_get_temp(tz, &temp[i]); temp[i] = (temp[i] < 0)? 0 : temp[i] / 1000; ret += snprintf(buf + ret, buf_size - ret, "%d ", temp[i]); } // gpu temperature tz = thermal_zone_get_zone_by_name("G3D"); thermal_zone_get_temp(tz, &temp_g3d); temp_g3d = (temp_g3d < 0)? 0 : temp_g3d / 1000; ret += snprintf(buf + ret, buf_size - ret, "%d ", temp_g3d); // siop, ocp #if IS_ENABLED(CONFIG_EXYNOS_UFCC) siop = get_cpufreq_max_limit() / 1000; siop = (siop > 4000)? 0 : siop; #else siop = 0; #endif #if IS_ENABLED(CONFIG_EXYNOS_AFM) ocp = get_afm_clipped_freq( cls[cl_cnt-1].last_cpu ) / 1000; ocp = (ocp > 4000)? 0 : ocp; #else ocp = 0; #endif // battery tz = thermal_zone_get_zone_by_name("battery"); thermal_zone_get_temp(tz, &temp_bat); temp_bat = (temp_bat < 0) ? 0 : temp_bat / 1000; ret += snprintf(buf + ret, buf_size - ret, "%d ", temp_bat); // cpufreq for (i = cl_cnt - 1; i >= 0; i--) { cpu = cls[i].first_cpu; cpu_max[i] = cpufreq_quick_get_max(cpu) / 1000; cpu_cur[i] = cpufreq_quick_get(cpu) / 1000; ret += snprintf(buf + ret, buf_size - ret, "%d %d ", cpu_max[i], cpu_cur[i]); if (i == (cl_cnt-1)) { ret += snprintf(buf + ret, buf_size - ret, "%d %d ", siop, ocp); } } // mif mif = (uint)exynos_devfreq_get_domain_freq(devfreq_mif) / 1000; // gpu gpu_util = gpu_dvfs_get_utilization(); //gpu_util = 0; gpu = (uint)cal_dfs_cached_get_rate(cal_id_g3d) / 1000; ret += snprintf(buf + ret, buf_size - ret, "%d %d %d ", mif, gpu_util, gpu); // cpu util for_each_possible_cpu(cpu) ret += snprintf(buf + ret, buf_size - ret, "%d ", cpu_util_avgs[cpu]); // cpu power power_total = 0; for_each_possible_cpu(cpu) { unsigned int util_ratio, power; util_ratio = (cpu_util_avgs[cpu] * 100) / 1024; cl_idx = get_cl_idx(cpu); freq = cpu_cur[cl_idx] * 1000; f_idx = get_f_idx(cl_idx, freq); power = cls[cl_idx].freqs[f_idx].dyn_power + et_freq_to_spower(cpu, freq); cpu_power = util_ratio * power / 100; power_total += cpu_power; ret += snprintf(buf + ret, buf_size - ret, "%d ", cpu_power); } ret += snprintf(buf + ret, buf_size - ret, "%d ", power_total); // task multi for (i = 0; i <= cls[cl_cnt-1].last_cpu; i++) task_num_cpus[i] = 0; pgrp = find_vpid(pid); do_each_pid_thread(pgrp, PIDTYPE_TGID, p) { if (p->state == TASK_RUNNING) { util = p->se.avg.util_avg; if (util > util_thd) { for (i = 0; i < MAX_TNAMES; i++) { if (strlen(tns[i]) > 1) { if (!strncmp(p->comm, tns[i], strlen(tns[i]))) { pr_info("[%s] util_thd=%d util=%d tid=%d cpu=%d tns[%d]=%s p->comm=%s", prefix, util_thd, util, p->pid, p->cpu, i, tns[i], p->comm); task_num_cpus[p->cpu] = i + 1; // tnames index + 1 } } } } } } while_each_pid_thread(pgrp, PIDTYPE_TGID, p); for_each_possible_cpu(cpu) ret += snprintf(buf + ret, buf_size - ret, "%d ", task_num_cpus[cpu]); // tune mode ret += snprintf(buf + ret, buf_size - ret, "%d %d ", emstune_get_cur_mode(), emstune_get_cur_level()); ret -= 1; ret += snprintf(buf + ret, buf_size - ret, "\n"); // check buf size if ( ret + 256 > buf_size ) { pr_info("[%s] buf_size: %d + 256 > %d!!", prefix, ret, buf_size); break; } msleep(polling_ms); } return 0; } static struct task_struct *task; static void cpufreq_log_start(void) { if (is_running) { pr_err("[%s] already running!!\n", prefix); return; } task = kthread_create(cpufreq_log_thread, NULL, "xperf%u", 0); kthread_bind(task, bind_cpu); wake_up_process(task); return; } static void cpufreq_log_stop(void) { is_running = 0; pr_info("[%s] cpufreq profile done\n", prefix); } //=========================================================== // sysfs nodes //=========================================================== #define DEF_NODE(name) \ static ssize_t show_##name(struct kobject *k, struct kobj_attribute *attr, char *buf) { \ int ret = 0; \ ret += sprintf(buf + ret, "%d\n", name); \ return ret; } \ static ssize_t store_##name(struct kobject *k, struct kobj_attribute *attr, const char *buf, size_t count) { \ if (sscanf(buf, "%d", &name) != 1) \ return -EINVAL; \ return count; } \ static struct kobj_attribute name##_attr = __ATTR(name, 0644, show_##name, store_##name); DEF_NODE(polling_ms) DEF_NODE(bind_cpu) DEF_NODE(pid) DEF_NODE(util_thd) static ssize_t show_tnames(struct kobject *k, struct kobj_attribute *attr, char *buf) { int ret = 0, i; for (i = 0; i < MAX_TNAMES; i++) ret += sprintf(buf + ret, "%d:%s\n", i, tns[i]); return ret; } static ssize_t store_tnames(struct kobject *k, struct kobj_attribute *attr, const char *buf, size_t count) { int i; char tbuf[1000], *_tbuf, *_ttmp; if (sscanf(buf, "%999s", tbuf) != 1) return -EINVAL; _tbuf = tbuf; i = 0; while ((_ttmp = strsep(&_tbuf, ",")) != NULL && i < MAX_TNAMES) { strncpy(tns[i], _ttmp, TASK_COMM_LEN); i++; } return count; } static struct kobj_attribute tnames_attr = __ATTR(tnames, 0644, show_tnames, store_tnames); //------------------------------------------------------------------- static int init_cpu_power(void) { u64 f, v, c, p; struct device *dev; unsigned long f_hz; int cnt, i; for (cnt = 0; cnt < cl_cnt; cnt++) { cls[cnt].coeff = power_coeffs[cnt]; dev = get_cpu_device(cls[cnt].first_cpu); for (i = 0; i < cls[cnt].freq_size; i++) { struct dev_pm_opp *opp; f_hz = cls[cnt].freqs[i].freq * 1000; opp = dev_pm_opp_find_freq_ceil(dev, &f_hz); cls[cnt].freqs[i].volt = dev_pm_opp_get_voltage(opp) / 1000; // uV -> mV f = cls[cnt].freqs[i].freq / 1000; // kHz -> MHz v = cls[cnt].freqs[i].volt; c = cls[cnt].coeff; p = c * v * v * f; p = p / 1000000000; cls[cnt].freqs[i].dyn_power = p; // dynamic power p = et_freq_to_spower(cls[cnt].first_cpu, cls[cnt].freqs[i].freq); cls[cnt].freqs[i].sta_power = p; // static power } } return 0; } // power static ssize_t show_power(struct kobject *k, struct kobj_attribute *attr, char *b) { int i, j; int ret = 0; for (i = 0; i < cl_cnt; i++) { for (j = 0; j < cls[i].freq_size; j++) ret += sprintf(b + ret, "%u %u %u\n", cls[i].freqs[j].freq, cls[i].freqs[j].dyn_power, cls[i].freqs[j].sta_power); ret += sprintf(b + ret, "\n"); } b[ret] = '\0'; return ret; } static ssize_t store_power(struct kobject *k, struct kobj_attribute *attr, const char *b, size_t count) { return count; } static struct kobj_attribute power_attr = __ATTR(power, 0644, show_power, store_power); // volt static ssize_t show_volt(struct kobject *k, struct kobj_attribute *attr, char *b) { int i, j; int ret = 0; for (i = 0; i < cl_cnt; i++) { for (j = 0; j < cls[i].freq_size; j++) ret += sprintf(b + ret, "%u %u\n", cls[i].freqs[j].freq, cls[i].freqs[j].volt); ret += sprintf(b + ret, "\n"); } b[ret] = '\0'; return ret; } static ssize_t store_volt(struct kobject *k, struct kobj_attribute *attr, const char *b, size_t count) { return count; } static struct kobj_attribute volt_attr = __ATTR(volt, 0644, show_volt, store_volt); // init static ssize_t show_init(struct kobject *k, struct kobj_attribute *attr, char *b) { return 0; } static ssize_t store_init(struct kobject *k, struct kobj_attribute *attr, const char *b, size_t count) { init_cpu_power(); return count; } static struct kobj_attribute init_attr = __ATTR(init, 0644, show_init, store_init); // run static ssize_t show_run(struct kobject *k, struct kobj_attribute *attr, char *b) { return 0; } static ssize_t store_run(struct kobject *k, struct kobj_attribute *attr, const char *b, size_t count) { int run = 0; if (sscanf(b, "%d", &run) != 1) return -EINVAL; if (run) cpufreq_log_start(); else cpufreq_log_stop(); return count; } static struct kobj_attribute run_attr = __ATTR(run, 0644, show_run, store_run); static struct kobject *prof_kobj; static struct attribute *prof_attrs[] = { &run_attr.attr, &polling_ms_attr.attr, &bind_cpu_attr.attr, &pid_attr.attr, &power_attr.attr, &volt_attr.attr, &init_attr.attr, &tnames_attr.attr, &util_thd_attr.attr, NULL }; static struct attribute_group prof_group = { .attrs = prof_attrs, }; //================================ // debugfs static int result_seq_show(struct seq_file *file, void *iter) { if (is_running) { seq_printf(file, "NO RESULT\n"); } else { seq_printf(file, "%s", (char *)buf); // PRINT RESULT } return 0; } static ssize_t result_seq_write(struct file *file, const char __user *buffer, size_t count, loff_t *off) { return count; } static int result_debugfs_open(struct inode *inode, struct file *file) { return single_open(file, result_seq_show, inode->i_private); } static struct file_operations result_debugfs_fops = { .owner = THIS_MODULE, .open = result_debugfs_open, .write = result_seq_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; /*--------------------------------------*/ // MAIN int xperf_prof_init(struct platform_device *pdev, struct kobject *kobj) { struct device_node *dn = pdev->dev.of_node; struct dentry *root, *d; int ret = 0; WARN_ON(register_trace_pelt_cfs_tp(update_cpu_util_avg, NULL)); of_property_read_u32(dn, "cal-id-cpucl0", &cls[0].cal_id); of_property_read_u32(dn, "cal-id-cpucl1", &cls[1].cal_id); of_property_read_u32(dn, "cal-id-cpucl2", &cls[2].cal_id); of_property_read_u32(dn, "cal-id-mif", &cal_id_mif); of_property_read_u32(dn, "cal-id-g3d", &cal_id_g3d); of_property_read_u32(dn, "devfreq-mif", &devfreq_mif); init_cls(); // get cl_cnt if (of_property_read_u32_array(dn, "power-coeff", power_coeffs, cl_cnt) < 0) return -ENODATA; init_cpu_power(); // sysfs: normal nodes prof_kobj = kobject_create_and_add("prof", kobj); if (!prof_kobj) { pr_info("[%s] create node failed: %s\n", prefix, __FILE__); return -EINVAL; } ret = sysfs_create_group(prof_kobj, &prof_group); if (ret) { pr_info("[%s] create group failed: %s\n", prefix, __FILE__); return -EINVAL; } // debugfs: only for result root = debugfs_create_dir("xperf_prof", NULL); if (!root) { printk("%s: create debugfs\n", __FILE__); return -ENOMEM; } d = debugfs_create_file("result", S_IRUSR, root, (unsigned int *)0, &result_debugfs_fops); if (!d) return -ENOMEM; return 0; }