/* * Multi-purpose Load tracker * * Copyright (C) 2018 Samsung Electronics Co., Ltd * Park Bumgyu */ #include "../sched.h" #include "../sched-pelt.h" #include "ems.h" #include #include static void (*dsufreq_callback)(int ratio, int period_ns); static void (*miffreq_callback)(int ratio); static void (*cpufreq_callback)(int *ratio); void register_mhdvfs_dsufreq_callback(void (*callback)(int ratio, int period_ns)) { dsufreq_callback = callback; } EXPORT_SYMBOL(register_mhdvfs_dsufreq_callback); void register_mhdvfs_miffreq_callback(void (*callback)(int ratio)) { miffreq_callback = callback; } EXPORT_SYMBOL(register_mhdvfs_miffreq_callback); void register_mhdvfs_cpufreq_callback(void (*callback)(int *ratio)) { cpufreq_callback = callback; } EXPORT_SYMBOL(register_mhdvfs_cpufreq_callback); static int mhdvfs_interval_jiffies = 5; static u64 last_mhdvfs_time; static int target_mspc = 1500; static int memstall_duration_jiffies = 10; static u64 memstall_start_time; static int low_perf_ipc = 1000; struct { struct work_struct work; int dsu_ratio; int mif_ratio; int *cpu_ratio; } mhdvfs_update; static void mhdvfs_update_work(struct work_struct *work) { /* call dsufreq callback */ if (!dsufreq_callback) return; dsufreq_callback(mhdvfs_update.dsu_ratio, mhdvfs_interval_jiffies * TICK_NSEC); /* call miffreq callback */ if (!miffreq_callback) return; miffreq_callback(mhdvfs_update.mif_ratio); /* call cpufreq callback */ if (!cpufreq_callback) return; cpufreq_callback(mhdvfs_update.cpu_ratio); } static void kick_mhdvfs_update(int dsu_ratio, int mif_ratio, int *cpu_ratio) { if (work_busy(&mhdvfs_update.work)) return; mhdvfs_update.dsu_ratio = dsu_ratio; mhdvfs_update.mif_ratio = mif_ratio; mhdvfs_update.cpu_ratio = cpu_ratio; schedule_work_on(smp_processor_id(), &mhdvfs_update.work); } struct uarch_info { u64 core_cycles; u64 inst_ret; u64 mem_stall; u64 last_core_cycles; u64 last_inst_ret; u64 last_mem_stall; }; static struct uarch_info __percpu *pcpu_uinfo; static void this_cpu_uarch_info_update(void) { /* Uarch information can be accessed by only this cpu */ struct uarch_info *uinfo = per_cpu_ptr(pcpu_uinfo, smp_processor_id()); uinfo->core_cycles = amu_core_cycles(); uinfo->inst_ret = amu_inst_ret(); uinfo->mem_stall = amu_mem_stall(); } static DEFINE_SPINLOCK(mhdvfs_lock); void mhdvfs(void) { unsigned long now = jiffies; u64 ipc[VENDOR_NR_CPUS], mspc[VENDOR_NR_CPUS]; u64 ipc_sum = 0, mspc_sum = 0; int dsu_ratio = 0, mif_ratio = 0; int cpu_ratio[VENDOR_NR_CPUS] = { 0, }; int cpu; this_cpu_uarch_info_update(); if (!spin_trylock(&mhdvfs_lock)) return; if (now < last_mhdvfs_time + mhdvfs_interval_jiffies) { spin_unlock(&mhdvfs_lock); return; } last_mhdvfs_time = now; /* Gather uarch infomration */ for_each_cpu(cpu, cpu_active_mask) { struct uarch_info *uinfo = per_cpu_ptr(pcpu_uinfo, cpu); ipc[cpu] = div64_u64((uinfo->inst_ret - uinfo->last_inst_ret) << 10, uinfo->core_cycles - uinfo->last_core_cycles); mspc[cpu] = div64_u64((uinfo->mem_stall - uinfo->last_mem_stall) << 10, uinfo->core_cycles - uinfo->last_core_cycles); ipc_sum += ipc[cpu]; mspc_sum += mspc[cpu]; uinfo->last_core_cycles = uinfo->core_cycles; uinfo->last_inst_ret = uinfo->inst_ret; uinfo->last_mem_stall = uinfo->mem_stall; } trace_mhdvfs_profile(ipc_sum, mspc_sum); spin_unlock(&mhdvfs_lock); return; if (mspc_sum > target_mspc) { struct cpumask cpus_to_visit; /* * MSPC and DSU frequency are inverse proportion. * * f = 1 / mspc * * f and mspc are the current frequency and mspc, f' is * the frequency to reach the target_mspc. Through relation * of f, f', mspc and target_mspc, we can get f'. * * f : 1 / mspc = f' : 1 / target_mspc * f' / mspc = f / target_mspc * f' = f * mspc / target_mspc * * That is, if the current frequency is raised by * (mspc / target_mspc), mspc is lowered to target_mspc. * We therefore calculate dsu_ratio for DSU frequency * as below: * * dsu_ratio = mspc / target_mspc */ dsu_ratio = div64_u64(mspc_sum << 10, target_mspc); trace_mhdvfs_kick_dsufreq(mspc_sum, dsu_ratio); /* * Raises the MIF frequency to break the bottleneck of the * memory when MSPC continues to be high for longer than * memstall_duration_jiffies. */ if (!memstall_start_time) memstall_start_time = now; else if (now >= memstall_start_time + memstall_duration_jiffies) { /* * It is difficult to predict the exact required * performance of memory since memory is not used only * by the CPU. Therefore, we set MIF frequency to * maximum to relieve memstall as quickly as possible. */ mif_ratio = UINT_MAX; /* max freq */ trace_mhdvfs_kick_miffreq(mspc_sum, mif_ratio, div64_u64((now - memstall_start_time) * TICK_NSEC, NSEC_PER_MSEC)); } /* * Lowers CPU frequency to reduce unnecessary CPU power when * IPC is lower than low_perf_ipc and MSPC is higher than * target_mspc. */ cpumask_copy(&cpus_to_visit, cpu_active_mask); while (!cpumask_empty(&cpus_to_visit)) { struct cpufreq_policy *policy; u64 ipc_sum_cpus = 0; int i; cpu = cpumask_first(&cpus_to_visit); policy = cpufreq_cpu_get(cpu); if (!policy) break; for_each_cpu(i, policy->cpus) ipc_sum_cpus += ipc[i]; if (ipc_sum_cpus < low_perf_ipc) { cpu_ratio[policy->cpu] = 768; /* 75% */ trace_mhdvfs_kick_cpufreq(cpu, ipc_sum_cpus, cpu_ratio[policy->cpu]); } cpumask_andnot(&cpus_to_visit, &cpus_to_visit, policy->cpus); cpufreq_cpu_put(policy); } } else memstall_start_time = 0; kick_mhdvfs_update(dsu_ratio, mif_ratio, cpu_ratio); spin_unlock(&mhdvfs_lock); } static ssize_t show_amu_count(struct device *dev, struct device_attribute *attr, char *buf) { struct uarch_info *uinfo; int cpu; int ret = 0; ret += snprintf(buf + ret, PAGE_SIZE - ret, "cpu core_cycle inst_ret_cnt mem_stall_cnt\n"); for_each_possible_cpu(cpu) { uinfo = per_cpu_ptr(pcpu_uinfo, cpu); ret += snprintf(buf + ret, PAGE_SIZE - ret, "%2d %16llu %16llu %16llu\n", cpu, uinfo->core_cycles, uinfo->inst_ret, uinfo->mem_stall); } return ret; } static DEVICE_ATTR(amu_count, S_IRUGO, show_amu_count, NULL); void mhdvfs_init(struct kobject *ems_kobj) { pcpu_uinfo = alloc_percpu(struct uarch_info); INIT_WORK(&mhdvfs_update.work, mhdvfs_update_work); if (sysfs_create_file(ems_kobj, &dev_attr_amu_count.attr)) pr_warn("Failed to create amu_count node\n"); }