blob: 1895bb988c9fcb4559920fe214647dc72e2a8d10 [file] [log] [blame]
/**
* Copyright (C) ARM Limited 2011-2014. 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 as
* published by the Free Software Foundation.
*
*/
#include <linux/cpufreq.h>
#include <trace/events/power.h>
#if defined(__arm__)
#include <asm/mach-types.h>
#define implements_wfi() (!machine_is_omap3_beagle())
#else
#define implements_wfi() false
#endif
// cpu_frequency and cpu_idle trace points were introduced in Linux kernel v2.6.38
// the now deprecated power_frequency trace point was available prior to 2.6.38, but only for x86
#if GATOR_CPU_FREQ_SUPPORT
enum {
POWER_CPU_FREQ,
POWER_CPU_IDLE,
POWER_TOTAL
};
static DEFINE_PER_CPU(ulong, idle_prev_state);
static ulong power_cpu_enabled[POWER_TOTAL];
static ulong power_cpu_key[POWER_TOTAL];
static int gator_trace_power_create_files(struct super_block *sb, struct dentry *root)
{
struct dentry *dir;
int cpu;
bool found_nonzero_freq = false;
// Even if CONFIG_CPU_FREQ is defined, it still may not be used. Check
// for non-zero values from cpufreq_quick_get
for_each_online_cpu(cpu) {
if (cpufreq_quick_get(cpu) > 0) {
found_nonzero_freq = true;
break;
}
}
if (found_nonzero_freq) {
// cpu_frequency
dir = gatorfs_mkdir(sb, root, "Linux_power_cpu_freq");
if (!dir) {
return -1;
}
gatorfs_create_ulong(sb, dir, "enabled", &power_cpu_enabled[POWER_CPU_FREQ]);
gatorfs_create_ro_ulong(sb, dir, "key", &power_cpu_key[POWER_CPU_FREQ]);
}
// cpu_idle
dir = gatorfs_mkdir(sb, root, "Linux_power_cpu_idle");
if (!dir) {
return -1;
}
gatorfs_create_ulong(sb, dir, "enabled", &power_cpu_enabled[POWER_CPU_IDLE]);
gatorfs_create_ro_ulong(sb, dir, "key", &power_cpu_key[POWER_CPU_IDLE]);
return 0;
}
// 'cpu' may not equal smp_processor_id(), i.e. may not be running on the core that is having the freq/idle state change
GATOR_DEFINE_PROBE(cpu_frequency, TP_PROTO(unsigned int frequency, unsigned int cpu))
{
cpu = lcpu_to_pcpu(cpu);
marshal_event_single(cpu, power_cpu_key[POWER_CPU_FREQ], frequency * 1000);
}
GATOR_DEFINE_PROBE(cpu_idle, TP_PROTO(unsigned int state, unsigned int cpu))
{
cpu = lcpu_to_pcpu(cpu);
if (state == per_cpu(idle_prev_state, cpu)) {
return;
}
if (implements_wfi()) {
if (state == PWR_EVENT_EXIT) {
// transition from wfi to non-wfi
marshal_idle(cpu, MESSAGE_IDLE_EXIT);
} else {
// transition from non-wfi to wfi
marshal_idle(cpu, MESSAGE_IDLE_ENTER);
}
}
per_cpu(idle_prev_state, cpu) = state;
if (power_cpu_enabled[POWER_CPU_IDLE]) {
// Increment state so that no negative numbers are sent
marshal_event_single(cpu, power_cpu_key[POWER_CPU_IDLE], state + 1);
}
}
static void gator_trace_power_online(void)
{
int pcpu = get_physical_cpu();
int lcpu = get_logical_cpu();
if (power_cpu_enabled[POWER_CPU_FREQ]) {
marshal_event_single(pcpu, power_cpu_key[POWER_CPU_FREQ], cpufreq_quick_get(lcpu) * 1000);
}
}
static void gator_trace_power_offline(void)
{
// Set frequency to zero on an offline
int cpu = get_physical_cpu();
if (power_cpu_enabled[POWER_CPU_FREQ]) {
marshal_event_single(cpu, power_cpu_key[POWER_CPU_FREQ], 0);
}
}
static int gator_trace_power_start(void)
{
int cpu;
// register tracepoints
if (power_cpu_enabled[POWER_CPU_FREQ])
if (GATOR_REGISTER_TRACE(cpu_frequency))
goto fail_cpu_frequency_exit;
// Always register for cpu:idle for detecting WFI, independent of power_cpu_enabled[POWER_CPU_IDLE]
if (GATOR_REGISTER_TRACE(cpu_idle))
goto fail_cpu_idle_exit;
pr_debug("gator: registered power event tracepoints\n");
for_each_present_cpu(cpu) {
per_cpu(idle_prev_state, cpu) = 0;
}
return 0;
// unregister tracepoints on error
fail_cpu_idle_exit:
if (power_cpu_enabled[POWER_CPU_FREQ])
GATOR_UNREGISTER_TRACE(cpu_frequency);
fail_cpu_frequency_exit:
pr_err("gator: power event tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n");
return -1;
}
static void gator_trace_power_stop(void)
{
int i;
if (power_cpu_enabled[POWER_CPU_FREQ])
GATOR_UNREGISTER_TRACE(cpu_frequency);
GATOR_UNREGISTER_TRACE(cpu_idle);
pr_debug("gator: unregistered power event tracepoints\n");
for (i = 0; i < POWER_TOTAL; i++) {
power_cpu_enabled[i] = 0;
}
}
static void gator_trace_power_init(void)
{
int i;
for (i = 0; i < POWER_TOTAL; i++) {
power_cpu_enabled[i] = 0;
power_cpu_key[i] = gator_events_get_key();
}
}
#else
static int gator_trace_power_create_files(struct super_block *sb, struct dentry *root)
{
return 0;
}
static void gator_trace_power_online(void)
{
}
static void gator_trace_power_offline(void)
{
}
static int gator_trace_power_start(void)
{
return 0;
}
static void gator_trace_power_stop(void)
{
}
static void gator_trace_power_init(void)
{
}
#endif