/* * Copyright (c) 2018 Park Bumgyu, Samsung Electronics Co., Ltd * * 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 */ #include #include #include #include /* whether profiling has started */ static bool profile_started; /* * 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. */ struct cpu_idle_state { /* description of idle state */ char desc[DESC_LEN]; /* idle state statstics for each cpu */ struct cpuidle_stats stats[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; /* * Manages idle state in which multiple cpus unit enter. Each idle state * has one group_idle_state structure. */ struct group_idle_state { /* idle state id, it must be unique */ int id; /* description of idle state */ char desc[DESC_LEN]; /* idle state statstics */ struct cpuidle_stats stats; }; /* * To easily manage group_idle_state dynamically, manage the list as an * list. Currently, the maximum number of group idle states supported is 5, * which is unlikely to exceed the number of states empirically. */ #define MAX_GROUP_IDLE_STATE 5 /* group idle state list and length of group idle state list */ static struct group_idle_state * group_idle_state[MAX_GROUP_IDLE_STATE]; static int group_idle_state_count; /************************************************************************ * 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; } /* * cpuidle_profile_cpu_idle_enter/cpuidle_profile_cpu_idle_exit * : profilie for cpu idle state */ void cpuidle_profile_cpu_idle_enter(int cpu, int index) { if (!profile_started) return; idle_enter(&cpu_idle_state[index].stats[cpu]); } void cpuidle_profile_cpu_idle_exit(int cpu, int index, int cancel) { if (!profile_started) return; idle_exit(&cpu_idle_state[index].stats[cpu], cancel); } /* * cpuidle_profile_group_idle_enter/cpuidle_profile_group_idle_exit * : profilie for group idle state */ void cpuidle_profile_group_idle_enter(int id) { int i; if (!profile_started) return; for (i = 0; i < group_idle_state_count; i++) if (group_idle_state[i]->id == id) break; idle_enter(&group_idle_state[i]->stats); } void cpuidle_profile_group_idle_exit(int id, int cancel) { int i; if (!profile_started) return; for (i = 0; i < group_idle_state_count; i++) if (group_idle_state[i]->id == id) break; idle_exit(&group_idle_state[i]->stats, cancel); } /************************************************************************ * Profile start/stop * ************************************************************************/ /* totoal profiling time */ static s64 profile_time; /* start time of profile */ static ktime_t profile_start_time; /* idle ip */ static int idle_ip_stats[4][32]; extern char *idle_ip_names[4][32]; 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; profile_start_time = 0; for (i = 0; i < cpu_idle_state_count; i++) for_each_possible_cpu(cpu) clear_stats(&cpu_idle_state[i].stats[cpu]); for (i = 0; i < group_idle_state_count; i++) clear_stats(&group_idle_state[i]->stats); memset(idle_ip_stats, 0, sizeof(idle_ip_stats)); } static void do_nothing(void *unused) { } static void cpuidle_profile_start(void) { if (profile_started) { pr_err("cpuidle profile is ongoing\n"); return; } reset_profile(); profile_start_time = ktime_get(); profile_started = 1; preempt_disable(); /* wakeup all cpus to start profile */ smp_call_function(do_nothing, NULL, 1); preempt_enable(); pr_info("cpuidle profile start\n"); } static void cpuidle_profile_stop(void) { if (!profile_started) { pr_err("CPUIDLE profile does not start yet\n"); return; } pr_info("cpuidle profile stop\n"); preempt_disable(); /* wakeup all cpus to stop profile */ smp_call_function(do_nothing, NULL, 1); preempt_enable(); profile_started = 0; profile_time = ktime_to_us(ktime_sub(ktime_get(), profile_start_time)); } /************************************************************************ * IDLE IP * ************************************************************************/ void cpuidle_profile_idle_ip(int index, unsigned int idle_ip) { int i; /* * Return if profile is not started */ if (!profile_started) return; for (i = 0; i < 32; i++) { /* * If bit of idle_ip has 1, IP corresponding to its bit * is not idle. */ if (idle_ip & (1 << i)) idle_ip_stats[index][i]++; } } /************************************************************************ * 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; for (i = 0; i < cpu_idle_state_count; i++) idle_time += cpu_idle_state[i].stats[cpu].time; return idle_time; } static int cpu_idle_ratio(int cpu) { return calculate_percent(cpu_idle_time(cpu)); } static ssize_t show_result(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int ret = 0; int cpu, i, bit; if (profile_started) { ret += snprintf(buf + ret, PAGE_SIZE - ret, "CPUIDLE profile is ongoing\n"); return ret; } ret += snprintf(buf + ret, PAGE_SIZE - ret, "#############################################################\n"); 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 #entry #cancel #time #ratio * cpu0 985 8 8808916us 87% * cpu1 340 2 8311318us 82% * cpu2 270 7 8744801us 87% * cpu3 330 2 9001329us 89% */ for (i = 0; i < cpu_idle_state_count; i++) { ret += snprintf(buf + ret, PAGE_SIZE - ret, "[state : %s]\n", cpu_idle_state[i].desc); ret += snprintf(buf + ret, PAGE_SIZE - ret, "#cpu #entry #cancel #time #ratio\n"); for_each_possible_cpu(cpu) { struct cpuidle_stats *stats = &cpu_idle_state[i].stats[cpu]; ret += snprintf(buf + ret, PAGE_SIZE - ret, "cpu%d %5u %5u %10lluus %3u%%\n", cpu, stats->entry_count, stats->cancel_count, stats->time, calculate_percent(stats->time)); } ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n"); } /* * Example of group idle state profile result. * The number of results depends on the number of group idle state. * * [state : {desc}] * #entry #cancel #time #ratio * 52 1 4296397us 42% * * [state : {desc}] * #entry #cancel #time #ratio * 20 0 2230528us 22% */ for (i = 0; i < group_idle_state_count; i++) { struct cpuidle_stats *stats = &group_idle_state[i]->stats; ret += snprintf(buf + ret, PAGE_SIZE - ret, "[state : %s]\n", group_idle_state[i]->desc); ret += snprintf(buf + ret, PAGE_SIZE - ret, "#entry #cancel #time #ratio\n"); ret += snprintf(buf + ret, PAGE_SIZE - ret, "%5u %5u %10lluus %3u%%\n", stats->entry_count, stats->cancel_count, stats->time, calculate_percent(stats->time)); ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n"); } ret += snprintf(buf + ret, PAGE_SIZE - ret, "[IDLE-IP statistics]\n"); for (i = 0; i < 4; i++) { for (bit = 0; bit < 32; bit++) { if (!idle_ip_stats[i][bit]) continue; ret += snprintf(buf + ret, PAGE_SIZE - ret, "busy IP : %s(count = %d)\n", idle_ip_names[i][bit], idle_ip_stats[i][bit]); } } ret += snprintf(buf + ret, PAGE_SIZE - ret, "#############################################################\n"); return ret; } /********************************************************************* * Sysfs interface * *********************************************************************/ static ssize_t show_cpuidle_profile(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int ret = 0; if (profile_started) ret += snprintf(buf + ret, PAGE_SIZE - ret, "CPUIDLE profile is ongoing\n"); else ret = show_result(kobj, attr, buf); return ret; } static ssize_t store_cpuidle_profile(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int input; if (!sscanf(buf, "%1d", &input)) return -EINVAL; if (!!input) cpuidle_profile_start(); else cpuidle_profile_stop(); return count; } static struct kobj_attribute cpuidle_profile_attr = __ATTR(profile, 0644, show_cpuidle_profile, store_cpuidle_profile); static struct attribute *cpuidle_profile_attrs[] = { &cpuidle_profile_attr.attr, NULL, }; static const struct attribute_group cpuidle_profile_group = { .attrs = cpuidle_profile_attrs, }; /********************************************************************* * Initialize cpuidle profiler * *********************************************************************/ void __init cpuidle_profile_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; } void __init cpuidle_profile_group_idle_register(int id, const char *name) { struct group_idle_state *state; state = kzalloc(sizeof(struct group_idle_state), GFP_KERNEL); if (!state) { pr_err("%s: Failed to allocate memory\n", __func__); return; } state->id = id; strncpy(state->desc, name, DESC_LEN - 1); group_idle_state[group_idle_state_count] = state; group_idle_state_count++; } static int __init cpuidle_profile_init(void) { struct class *class; struct device *dev; int ret = 0; class = class_create(THIS_MODULE, "cpuidle"); dev = device_create(class, NULL, 0, NULL, "cpuidle_profiler"); ret = sysfs_create_group(&dev->kobj, &cpuidle_profile_group); if (ret) pr_err("%s: failed to create sysfs group", __func__); return ret; } late_initcall(cpuidle_profile_init);