/* * Copyright (c) 2012, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include "trace_stat.h" #include "trace.h" struct trans { struct rb_node node; unsigned int cpu; unsigned int start_freq; unsigned int end_freq; unsigned int min_us; unsigned int max_us; ktime_t total_t; unsigned int count; }; static struct rb_root freq_trans_tree = RB_ROOT; static struct trans *tr_search(struct rb_root *root, unsigned int cpu, unsigned int start_freq, unsigned int end_freq) { struct rb_node *node = root->rb_node; while (node) { struct trans *tr = container_of(node, struct trans, node); if (cpu < tr->cpu) node = node->rb_left; else if (cpu > tr->cpu) node = node->rb_right; else if (start_freq < tr->start_freq) node = node->rb_left; else if (start_freq > tr->start_freq) node = node->rb_right; else if (end_freq < tr->end_freq) node = node->rb_left; else if (end_freq > tr->end_freq) node = node->rb_right; else return tr; } return NULL; } static int tr_insert(struct rb_root *root, struct trans *tr) { struct rb_node **new = &(root->rb_node), *parent = NULL; while (*new) { struct trans *this = container_of(*new, struct trans, node); parent = *new; if (tr->cpu < this->cpu) new = &((*new)->rb_left); else if (tr->cpu > this->cpu) new = &((*new)->rb_right); else if (tr->start_freq < this->start_freq) new = &((*new)->rb_left); else if (tr->start_freq > this->start_freq) new = &((*new)->rb_right); else if (tr->end_freq < this->end_freq) new = &((*new)->rb_left); else if (tr->end_freq > this->end_freq) new = &((*new)->rb_right); else return -EINVAL; } rb_link_node(&tr->node, parent, new); rb_insert_color(&tr->node, root); return 0; } struct trans_state { spinlock_t lock; unsigned int start_freq; unsigned int end_freq; ktime_t start_t; bool started; }; static DEFINE_PER_CPU(struct trans_state, freq_trans_state); static DEFINE_SPINLOCK(state_lock); static void probe_start(void *ignore, unsigned int start_freq, unsigned int end_freq, unsigned int cpu) { unsigned long flags; spin_lock_irqsave(&state_lock, flags); per_cpu(freq_trans_state, cpu).start_freq = start_freq; per_cpu(freq_trans_state, cpu).end_freq = end_freq; per_cpu(freq_trans_state, cpu).start_t = ktime_get(); per_cpu(freq_trans_state, cpu).started = true; spin_unlock_irqrestore(&state_lock, flags); } static void probe_end(void *ignore, unsigned int cpu) { unsigned long flags; struct trans *tr; s64 dur_us; ktime_t dur_t, end_t = ktime_get(); spin_lock_irqsave(&state_lock, flags); if (!per_cpu(freq_trans_state, cpu).started) goto out; dur_t = ktime_sub(end_t, per_cpu(freq_trans_state, cpu).start_t); dur_us = ktime_to_us(dur_t); tr = tr_search(&freq_trans_tree, cpu, per_cpu(freq_trans_state, cpu).start_freq, per_cpu(freq_trans_state, cpu).end_freq); if (!tr) { tr = kzalloc(sizeof(*tr), GFP_ATOMIC); if (!tr) { WARN_ONCE(1, "CPU frequency trace is now invalid!\n"); goto out; } tr->start_freq = per_cpu(freq_trans_state, cpu).start_freq; tr->end_freq = per_cpu(freq_trans_state, cpu).end_freq; tr->cpu = cpu; tr->min_us = UINT_MAX; tr_insert(&freq_trans_tree, tr); } tr->total_t = ktime_add(tr->total_t, dur_t); tr->count++; if (dur_us > tr->max_us) tr->max_us = dur_us; if (dur_us < tr->min_us) tr->min_us = dur_us; per_cpu(freq_trans_state, cpu).started = false; out: spin_unlock_irqrestore(&state_lock, flags); } static void *freq_switch_stat_start(struct tracer_stat *trace) { struct rb_node *n; unsigned long flags; spin_lock_irqsave(&state_lock, flags); n = rb_first(&freq_trans_tree); spin_unlock_irqrestore(&state_lock, flags); return n; } static void *freq_switch_stat_next(void *prev, int idx) { struct rb_node *n; unsigned long flags; spin_lock_irqsave(&state_lock, flags); n = rb_next(prev); spin_unlock_irqrestore(&state_lock, flags); return n; } static int freq_switch_stat_show(struct seq_file *s, void *p) { unsigned long flags; struct trans *tr = p; spin_lock_irqsave(&state_lock, flags); seq_printf(s, "%3d %9d %8d %5d %6lld %6d %6d\n", tr->cpu, tr->start_freq, tr->end_freq, tr->count, div_s64(ktime_to_us(tr->total_t), tr->count), tr->min_us, tr->max_us); spin_unlock_irqrestore(&state_lock, flags); return 0; } static void freq_switch_stat_release(void *stat) { struct trans *tr = stat; unsigned long flags; spin_lock_irqsave(&state_lock, flags); rb_erase(&tr->node, &freq_trans_tree); spin_unlock_irqrestore(&state_lock, flags); kfree(tr); } static int freq_switch_stat_headers(struct seq_file *s) { seq_puts(s, "CPU START_KHZ END_KHZ COUNT AVG_US MIN_US MAX_US\n"); seq_puts(s, " | | | | | | |\n"); return 0; } struct tracer_stat freq_switch_stats __read_mostly = { .name = "cpu_freq_switch", .stat_start = freq_switch_stat_start, .stat_next = freq_switch_stat_next, .stat_show = freq_switch_stat_show, .stat_release = freq_switch_stat_release, .stat_headers = freq_switch_stat_headers }; static void trace_freq_switch_disable(void) { unregister_stat_tracer(&freq_switch_stats); unregister_trace_cpu_frequency_switch_end(probe_end, NULL); unregister_trace_cpu_frequency_switch_start(probe_start, NULL); pr_info("disabled cpu frequency switch time profiling\n"); } static int trace_freq_switch_enable(void) { int ret; ret = register_trace_cpu_frequency_switch_start(probe_start, NULL); if (ret) goto out; ret = register_trace_cpu_frequency_switch_end(probe_end, NULL); if (ret) goto err_register_switch_end; ret = register_stat_tracer(&freq_switch_stats); if (ret) goto err_register_stat_tracer; pr_info("enabled cpu frequency switch time profiling\n"); return 0; err_register_stat_tracer: unregister_trace_cpu_frequency_switch_end(probe_end, NULL); err_register_switch_end: register_trace_cpu_frequency_switch_start(probe_start, NULL); out: pr_err("failed to enable cpu frequency switch time profiling\n"); return ret; } static DEFINE_MUTEX(debugfs_lock); static bool trace_freq_switch_enabled; static int debug_toggle_tracing(void *data, u64 val) { int ret = 0; mutex_lock(&debugfs_lock); if (val == 1 && !trace_freq_switch_enabled) ret = trace_freq_switch_enable(); else if (val == 0 && trace_freq_switch_enabled) trace_freq_switch_disable(); else if (val > 1) ret = -EINVAL; if (!ret) trace_freq_switch_enabled = val; mutex_unlock(&debugfs_lock); return ret; } static int debug_tracing_state_get(void *data, u64 *val) { mutex_lock(&debugfs_lock); *val = trace_freq_switch_enabled; mutex_unlock(&debugfs_lock); return 0; } DEFINE_SIMPLE_ATTRIBUTE(debug_tracing_state_fops, debug_tracing_state_get, debug_toggle_tracing, "%llu\n"); static int __init trace_freq_switch_init(void) { struct dentry *d_tracer = tracing_init_dentry(); if (!d_tracer) return 0; debugfs_create_file("cpu_freq_switch_profile_enabled", S_IRUGO | S_IWUSR, d_tracer, NULL, &debug_tracing_state_fops); return 0; } late_initcall(trace_freq_switch_init);