kernel_samsung_a53x/drivers/soc/samsung/xperf/cpuidle.c
2024-06-15 16:02:09 -03:00

538 lines
13 KiB
C
Executable file

/*
* Copyright (c) 2018 Park Bumgyu, Samsung Electronics Co., Ltd <bumgyu.park@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* CPUIDLE profiler for Exynos
* 2019.7 Expanded to CPUIDLE per cpufreq by Jungwook Kim <jwook1.kim@samsung.com>
*/
#include <linux/device.h>
#include <linux/kobject.h>
#include <linux/cpuidle.h>
#include <linux/slab.h>
#include <linux/cpufreq.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <linux/platform_device.h>
/* whether profiling has started */
static bool profile_started;
static const char *prefix = "exynos_perf";
/*
* Represents statistic of idle state.
* All idle states are mapped 1:1 with cpuidle_stats.
*/
struct cpuidle_stats {
/* time to enter idle state */
ktime_t idle_entry_time;
/* number of times an idle state is entered */
unsigned int entry_count;
/* number of times the entry into idle state is canceled */
unsigned int cancel_count;
/* time in idle state */
unsigned long long time;
};
/* description length of idle state */
#define DESC_LEN 32
/*
* Manages idle state where cpu enters individually. One cpu_idle_state
* structure manages a idle state for each cpu to enter, and the number
* of structure is determined by cpuidle driver.
*/
#define MAX_FREQ 30
#define MAX_CLUSTER 3
struct cpu_idle_state {
/* description of idle state */
char desc[DESC_LEN];
/* idle state statstics for each cpu */
struct cpuidle_stats stats[NR_CPUS][MAX_FREQ];
unsigned int enter_freqs[NR_CPUS];
};
/* cpu idle state list and length of cpu idle state list */
static struct cpu_idle_state *cpu_idle_state;
static int cpu_idle_state_count;
static unsigned int cpufreq_list[MAX_CLUSTER][MAX_FREQ];
static int cluster_last_cpu[MAX_CLUSTER];
static int cluster_first_cpu[MAX_CLUSTER];
/************************************************************************
* Profiling *
************************************************************************/
static void idle_enter(struct cpuidle_stats *stats)
{
stats->idle_entry_time = ktime_get();
stats->entry_count++;
}
static void idle_exit(struct cpuidle_stats *stats, int cancel)
{
s64 diff;
/*
* If profiler is started with cpu already in idle state,
* idle_entry_time is 0 because entry event is not recorded.
* From the start of the profile to cpu wakeup is the idle time,
* but ignore this because it is complex to handle it and the
* time is not large.
*/
if (!stats->idle_entry_time)
return;
if (cancel) {
stats->cancel_count++;
return;
}
diff = ktime_to_us(ktime_sub(ktime_get(), stats->idle_entry_time));
stats->time += diff;
stats->idle_entry_time = 0;
}
static int get_cluster_index(int cpu)
{
int index = 0;
int i;
for (i = 0; i < MAX_CLUSTER; i++) {
if (cpu <= cluster_last_cpu[i]) {
index = i;
break;
}
}
return index;
}
/*
* cpuidle_profile_cpu_idle_enter/cpuidle_profile_cpu_idle_exit
* : profilie for cpu idle state
*/
void exynos_perf_cpu_idle_enter(int cpu, int index)
{
int freq_index = 0;
int cluster_index;
uint cpufreq;
int i;
if (!profile_started)
return;
cpufreq = (uint)cpufreq_quick_get(cpu);
cluster_index = get_cluster_index(cpu);
for (i = 0; i < MAX_FREQ; i++) {
if (cpufreq == cpufreq_list[cluster_index][i]) {
freq_index = i;
break;
}
}
cpu_idle_state[index].enter_freqs[cpu] = cpufreq;
idle_enter(&cpu_idle_state[index].stats[cpu][freq_index]);
}
void exynos_perf_cpu_idle_exit(int cpu, int index, int cancel)
{
int freq_index = 0;
int cluster_index;
uint cpufreq;
int i;
if (!profile_started)
return;
cpufreq = cpu_idle_state[index].enter_freqs[cpu];
cluster_index = get_cluster_index(cpu);
for (i = 0; i < MAX_FREQ; i++) {
if (cpufreq == cpufreq_list[cluster_index][i]) {
freq_index = i;
break;
}
}
idle_exit(&cpu_idle_state[index].stats[cpu][freq_index], cancel);
}
struct cpufreq_stats {
unsigned int total_trans;
unsigned long long last_time;
unsigned int max_state;
unsigned int state_num;
unsigned int last_index;
u64 *time_in_state;
unsigned int *freq_table;
unsigned int *trans_table;
};
static int refill_cpufreq_list(void)
{
struct cpufreq_policy *policy;
struct cpumask *mask;
struct cpufreq_stats *stats;
int cluster_count = 0;
int next_cpu = 0;
int i, cpu, last_cpu;
memset(cpufreq_list, 0, sizeof(unsigned int)*MAX_CLUSTER*MAX_FREQ);
while ( next_cpu < num_possible_cpus() ) {
policy = cpufreq_cpu_get(next_cpu);
if (!policy) {
next_cpu++;
continue;
}
mask = policy->related_cpus;
for_each_cpu(cpu, mask) {
last_cpu = cpu;
}
stats = policy->stats;
for (i = 0; i < stats->state_num; i++)
cpufreq_list[cluster_count][i] = stats->freq_table[i];
cluster_first_cpu[cluster_count] = next_cpu;
cluster_last_cpu[cluster_count] = last_cpu;
cluster_count++;
next_cpu = last_cpu + 1;
}
return 0;
}
/************************************************************************
* Profile start/stop *
************************************************************************/
/* totoal profiling time */
static s64 profile_time;
/* start time of profile */
static ktime_t profile_start_time;
static void clear_stats(struct cpuidle_stats *stats)
{
if (!stats)
return;
stats->idle_entry_time = 0;
stats->entry_count = 0;
stats->cancel_count = 0;
stats->time = 0;
}
static void reset_profile(void)
{
int cpu, i, f;
profile_start_time = 0;
for (i = 0; i < cpu_idle_state_count; i++)
for_each_possible_cpu(cpu)
for (f = 0; f < MAX_FREQ; f++)
clear_stats(&cpu_idle_state[i].stats[cpu][f]);
/* refill cpufreq list */
refill_cpufreq_list();
}
void exynos_perf_cpuidle_start(void)
{
if (profile_started) {
pr_err("[%s] cpuidle profile is ongoing\n", prefix);
return;
}
reset_profile();
profile_start_time = ktime_get();
profile_started = 1;
pr_info("[%s] cpuidle profile start\n", prefix);
}
void exynos_perf_cpuidle_stop(void)
{
if (!profile_started) {
pr_err("[%s] cpuidle profile does not start yet\n", prefix);
return;
}
pr_info("[%s] cpuidle profile stop\n", prefix);
profile_started = 0;
profile_time = ktime_to_us(ktime_sub(ktime_get(), profile_start_time));
}
/************************************************************************
* Show result *
************************************************************************/
static int calculate_percent(s64 residency)
{
if (!residency)
return 0;
residency *= 100;
do_div(residency, profile_time);
return residency;
}
static unsigned long long cpu_idle_time(int cpu)
{
unsigned long long idle_time = 0;
int i, f;
int cluster_index;
cluster_index = get_cluster_index(cpu);
for (i = 0; i < cpu_idle_state_count; i++)
for (f = 0; f < MAX_FREQ; f++) {
if (cpufreq_list[cluster_index][f] == 0)
break;
idle_time += cpu_idle_state[i].stats[cpu][f].time;
}
return idle_time;
}
static int cpu_idle_ratio(int cpu)
{
return calculate_percent(cpu_idle_time(cpu));
}
static ssize_t show_result(char *buf)
{
int ret = 0;
int cpu, i;
int cluster_index = 0;
struct cpuidle_stats *stats;
int freq, freq_value;
if (profile_started) {
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"[%s] cpuilde profile is ongoing\n", prefix);
return ret;
}
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"Profiling Time : %lluus\n", profile_time);
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"\n");
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"[total idle ratio]\n");
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"#cpu #time #ratio\n");
for_each_possible_cpu(cpu)
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"cpu%d %10lluus %3u%%\n",
cpu, cpu_idle_time(cpu), cpu_idle_ratio(cpu));
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"\n");
/*
* Example of cpu idle state profile result.
* Below is an example from the quad core architecture. The number of
* rows depends on the number of cpu.
*
* [state : {desc}]
* #cpu #time
* cpu0 8808916us
*/
for (i = 0; i < cpu_idle_state_count; i++) {
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"[state : %s]\n", cpu_idle_state[i].desc);
for_each_possible_cpu(cpu) {
cluster_index = get_cluster_index(cpu);
if (cpu == cluster_first_cpu[cluster_index]) {
/* header: cpufreq */
ret += snprintf(buf + ret, PAGE_SIZE - ret, "#freq ");
for (freq = 0; freq < MAX_FREQ; freq++) {
freq_value = cpufreq_list[cluster_index][freq];
if (freq_value == 0) {
ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
break;
}
ret += snprintf(buf + ret, PAGE_SIZE - ret, "%u ", freq_value);
}
}
ret += snprintf(buf + ret, PAGE_SIZE - ret, "cpu%d ", cpu);
for (freq = 0; freq < MAX_FREQ; freq++) {
freq_value = cpufreq_list[cluster_index][freq];
if (freq_value == 0) {
ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
break;
}
stats = &cpu_idle_state[i].stats[cpu][freq];
ret += snprintf(buf + ret, PAGE_SIZE - ret, "%llu ", stats->time);
}
}
ret += snprintf(buf + ret, PAGE_SIZE - ret,
"\n");
}
return ret;
}
/*********************************************************************
* Sysfs interface *
*********************************************************************/
static int run_seq_show(struct seq_file *file, void *iter)
{
int ret = 0;
static char buf[PAGE_SIZE];
if (profile_started)
ret += snprintf(buf + ret, PAGE_SIZE - ret, "Not finished!");
else
ret += show_result(buf);
buf[ret] = '\0';
seq_printf(file, "%s", buf);
return 0;
}
static ssize_t run_seq_write(struct file *file, const char __user *buffer, size_t count, loff_t *off)
{
int run;
char buf[10];
count = (count > 10)? 10 : count;
if (copy_from_user(buf, buffer, count) != 0)
return -EFAULT;
if (!sscanf(buf, "%1d", &run))
return -EINVAL;
if (run)
exynos_perf_cpuidle_start();
else
exynos_perf_cpuidle_stop();
return count;
}
static int run_debugfs_open(struct inode *inode, struct file *file)
{
return single_open(file, run_seq_show, inode->i_private);
}
static struct file_operations run_debugfs_fops;
/* freq */
static int freq_seq_show(struct seq_file *file, void *iter)
{
int i, j;
int ret = 0;
static char buf[PAGE_SIZE];
refill_cpufreq_list();
for (i = 0; i < MAX_CLUSTER; i++) {
ret += snprintf(buf + ret, PAGE_SIZE - ret, "cluster[%d] = ", i);
for (j = 0; j < MAX_FREQ; j++)
ret += snprintf(buf + ret, PAGE_SIZE - ret, "%u ", cpufreq_list[i][j]);
ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
}
buf[ret] = '\0';
seq_printf(file, "%s", buf);
return 0;
}
static ssize_t freq_seq_write(struct file *file, const char __user *buffer, size_t count, loff_t *off)
{
return count;
}
static int freq_debugfs_open(struct inode *inode, struct file *file)
{
return single_open(file, freq_seq_show, inode->i_private);
}
/*********************************************************************
* Initialize cpuidle profiler *
*********************************************************************/
void __init
exynos_perf_cpu_idle_register(struct cpuidle_driver *drv)
{
struct cpu_idle_state *state;
int state_count = drv->state_count;
int i;
state = kzalloc(sizeof(struct cpu_idle_state) * state_count,
GFP_KERNEL);
if (!state) {
pr_err("%s: Failed to allocate memory\n", __func__);
return;
}
for (i = 0; i < state_count; i++)
strncpy(state[i].desc, drv->states[i].desc, DESC_LEN - 1);
cpu_idle_state = state;
cpu_idle_state_count = state_count;
}
// MAIN
static struct file_operations run_debugfs_fops = {
.owner = THIS_MODULE,
.open = run_debugfs_open,
.write = run_seq_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static struct file_operations freq_debugfs_fops = {
.owner = THIS_MODULE,
.open = freq_debugfs_open,
.write = freq_seq_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
int exynos_perf_cpuidle_profile_init(struct platform_device *pdev)
{
struct dentry *root, *d;
root = debugfs_create_dir("exynos_perf_cpuidle", NULL);
if (!root) {
printk("%s: create debugfs\n", __FILE__);
return -ENOMEM;
}
d = debugfs_create_file("run", S_IRUSR, root,
(unsigned int *)0,
&run_debugfs_fops);
if (!d)
return -ENOMEM;
d = debugfs_create_file("freq", S_IRUSR, root,
(unsigned int *)0,
&freq_debugfs_fops);
if (!d)
return -ENOMEM;
return 0;
}