blob: 57450d9d938e057f1d5b9da0cf5188a835f97b5b [file] [log] [blame]
/* Copyright (c) 2010, 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.
*/
/*
per-process_perf
DESCRIPTION
Capture the processor performances registers when the process context
switches. The /proc file system is used to control and access the results
of the performance counters.
Each time a process is context switched, the performance counters for
the Snoop Control Unit and the standard ARM counters are set according
to the values stored for that process.
The events to capture per process are set in the /proc/ppPerf/settings
directory.
EXTERNALIZED FUNCTIONS
INITIALIZATION AND SEQUENCING REQUIREMENTS
Detail how to initialize and use this service. The sequencing aspect
is only needed if the order of operations is important.
*/
/*
INCLUDE FILES FOR MODULE
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/sysrq.h>
#include <linux/time.h>
#include "linux/proc_fs.h"
#include "linux/kernel_stat.h"
#include <asm/thread_notify.h>
#include "asm/uaccess.h"
#include "cp15_registers.h"
#include "l2_cp15_registers.h"
#include <asm/perftypes.h>
#include "per-axi.h"
#include "perf.h"
#define DEBUG_SWAPIO
#ifdef DEBUG_SWAPIO
#define MR_SIZE 1024
#define PM_PP_ERR -1
struct mark_data_s {
long c;
long cpu;
unsigned long pid_old;
unsigned long pid_new;
};
struct mark_data_s markRay[MR_SIZE] __attribute__((aligned(16)));
int mrcnt;
DEFINE_SPINLOCK(_mark_lock);
static inline void MARKPIDS(char a, int opid, int npid)
{
int cpu = smp_processor_id();
if (opid == 0)
return;
spin_lock(&_mark_lock);
if (++mrcnt >= MR_SIZE)
mrcnt = 0;
spin_unlock(&_mark_lock);
markRay[mrcnt].pid_old = opid;
markRay[mrcnt].pid_new = npid;
markRay[mrcnt].cpu = cpu;
markRay[mrcnt].c = a;
}
static inline void MARK(char a) { MARKPIDS(a, 0xFFFF, 0xFFFF); }
static inline void MARKPID(char a, int pid) { MARKPIDS(a, pid, 0xFFFF); }
#else
#define MARK(a)
#define MARKPID(a, b)
#define MARKPIDS(a, b, c)
#endif /* DEBUG_SWAPIO */
/*
DEFINITIONS AND DECLARATIONS FOR MODULE
This section contains definitions for constants, macros, types, variables
and other items needed by this module.
*/
/*
Constant / Define Declarations
*/
#define PERF_MON_PROCESS_NUM 0x400
#define PERF_MON_PROCESS_MASK (PERF_MON_PROCESS_NUM-1)
#define PP_MAX_PROC_ENTRIES 32
/*
* The entry is locked and is not to be replaced.
*/
#define PERF_ENTRY_LOCKED (1<<0)
#define PERF_NOT_FIRST_TIME (1<<1)
#define PERF_EXITED (1<<2)
#define PERF_AUTOLOCK (1<<3)
#define IS_LOCKED(p) (p->flags & PERF_ENTRY_LOCKED)
#define PERF_NUM_MONITORS 4
#define L1_EVENTS_0 0
#define L1_EVENTS_1 1
#define L2_EVENTS_0 2
#define L2_EVENTS_1 3
#define PM_CYCLE_OVERFLOW_MASK 0x80000000
#define L2_PM_CYCLE_OVERFLOW_MASK 0x80000000
#define PM_START_ALL() do {\
if (pm_global) \
pmStartAll();\
} while (0);
#define PM_STOP_ALL() do {\
if (pm_global)\
pmStopAll();\
} while (0);
#define PM_RESET_ALL() do {\
if (pm_global)\
pmResetAll();\
} while (0);
/*
* Accessors for SMP based variables.
*/
#define _SWAPS(p) ((p)->cnts[smp_processor_id()].swaps)
#define _CYCLES(p) ((p)->cnts[smp_processor_id()].cycles)
#define _COUNTS(p, i) ((p)->cnts[smp_processor_id()].counts[i])
#define _L2COUNTS(p, i) ((p)->cnts[smp_processor_id()].l2_counts[i])
#define _L2CYCLES(p) ((p)->cnts[smp_processor_id()].l2_cycles)
/*
Type Declarations
*/
/*
* Counts are on a per core basis.
*/
struct pm_counters_s {
unsigned long long cycles;
unsigned long long l2_cycles;
unsigned long long counts[PERF_NUM_MONITORS];
unsigned long long l2_counts[PERF_NUM_MONITORS];
unsigned long swaps;
};
struct per_process_perf_mon_type{
struct pm_counters_s cnts[NR_CPUS];
unsigned long control;
unsigned long index[PERF_NUM_MONITORS];
unsigned long l2_index[PERF_NUM_MONITORS];
unsigned long pid;
struct proc_dir_entry *proc;
struct proc_dir_entry *l2_proc;
unsigned short flags;
unsigned short running_cpu;
char *pidName;
unsigned long lpm0evtyper;
unsigned long lpm1evtyper;
unsigned long lpm2evtyper;
unsigned long l2lpmevtyper;
unsigned long vlpmevtyper;
unsigned long l2pmevtyper0;
unsigned long l2pmevtyper1;
unsigned long l2pmevtyper2;
unsigned long l2pmevtyper3;
unsigned long l2pmevtyper4;
};
unsigned long last_in_pid[NR_CPUS];
unsigned long fake_swap_out[NR_CPUS] = {0};
/*
Local Object Definitions
*/
struct per_process_perf_mon_type perf_mons[PERF_MON_PROCESS_NUM];
struct proc_dir_entry *proc_dir;
struct proc_dir_entry *settings_dir;
struct proc_dir_entry *values_dir;
struct proc_dir_entry *axi_dir;
struct proc_dir_entry *l2_dir;
struct proc_dir_entry *axi_settings_dir;
struct proc_dir_entry *axi_results_dir;
struct proc_dir_entry *l2_results_dir;
unsigned long pp_enabled;
unsigned long pp_settings_valid = -1;
unsigned long pp_auto_lock;
unsigned long pp_set_pid;
signed long pp_clear_pid = -1;
unsigned long per_proc_event[PERF_NUM_MONITORS];
unsigned long l2_per_proc_event[PERF_NUM_MONITORS];
unsigned long dbg_flags;
unsigned long pp_lpm0evtyper;
unsigned long pp_lpm1evtyper;
unsigned long pp_lpm2evtyper;
unsigned long pp_l2lpmevtyper;
unsigned long pp_vlpmevtyper;
unsigned long pm_stop_for_interrupts;
unsigned long pm_global; /* track all, not process based */
unsigned long pm_global_enable;
unsigned long pm_remove_pid;
unsigned long pp_l2pmevtyper0;
unsigned long pp_l2pmevtyper1;
unsigned long pp_l2pmevtyper2;
unsigned long pp_l2pmevtyper3;
unsigned long pp_l2pmevtyper4;
unsigned long pp_proc_entry_index;
char *per_process_proc_names[PP_MAX_PROC_ENTRIES];
unsigned int axi_swaps;
#define MAX_AXI_SWAPS 10
int first_switch = 1;
/*
Forward Declarations
*/
/*
Function Definitions
*/
/*
FUNCTION per_process_find
DESCRIPTION
Find the per process information based on the process id (pid) passed.
This is a simple mask based on the number of entries stored in the
static array
DEPENDENCIES
RETURN VALUE
Pointer to the per process data
SIDE EFFECTS
*/
struct per_process_perf_mon_type *per_process_find(unsigned long pid)
{
return &perf_mons[pid & PERF_MON_PROCESS_MASK];
}
/*
FUNCTION per_process_get_name
DESCRIPTION
Retreive the name of the performance counter based on the table and
index passed. We have two different sets of performance counters so
different table need to be used.
DEPENDENCIES
RETURN VALUE
Pointer to char string with the name of the event or "BAD"
Never returns NULL or a bad pointer.
SIDE EFFECTS
*/
char *per_process_get_name(unsigned long index)
{
return pm_find_event_name(index);
}
/*
FUNCTION per_process_results_read
DESCRIPTION
Print out the formatted results from the process id read. Event names
and counts are printed.
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
*/
int per_process_results_read(char *page, char **start, off_t off, int count,
int *eof, void *data)
{
struct per_process_perf_mon_type *p =
(struct per_process_perf_mon_type *)data;
struct pm_counters_s cnts;
int i, j;
/*
* Total across all CPUS
*/
memset(&cnts, 0, sizeof(cnts));
for (i = 0; i < num_possible_cpus(); i++) {
cnts.swaps += p->cnts[i].swaps;
cnts.cycles += p->cnts[i].cycles;
for (j = 0; j < PERF_NUM_MONITORS; j++)
cnts.counts[j] += p->cnts[i].counts[j];
}
/*
* Display as single results of the totals calculated above.
* Do we want to display or have option to display individula cores?
*/
return sprintf(page, "pid:%lu one:%s:%llu two:%s:%llu three:%s:%llu \
four:%s:%llu cycles:%llu swaps:%lu\n",
p->pid,
per_process_get_name(p->index[0]), cnts.counts[0],
per_process_get_name(p->index[1]), cnts.counts[1],
per_process_get_name(p->index[2]), cnts.counts[2],
per_process_get_name(p->index[3]), cnts.counts[3],
cnts.cycles, cnts.swaps);
}
int per_process_l2_results_read(char *page, char **start, off_t off, int count,
int *eof, void *data)
{
struct per_process_perf_mon_type *p =
(struct per_process_perf_mon_type *)data;
struct pm_counters_s cnts;
int i, j;
/*
* Total across all CPUS
*/
memset(&cnts, 0, sizeof(cnts));
for (i = 0; i < num_possible_cpus(); i++) {
cnts.l2_cycles += p->cnts[i].l2_cycles;
for (j = 0; j < PERF_NUM_MONITORS; j++)
cnts.l2_counts[j] += p->cnts[i].l2_counts[j];
}
/*
* Display as single results of the totals calculated above.
* Do we want to display or have option to display individula cores?
*/
return sprintf(page, "pid:%lu l2_one:%s:%llu l2_two:%s:%llu \
l2_three:%s:%llu \
l2_four:%s:%llu l2_cycles:%llu\n",
p->pid,
per_process_get_name(p->l2_index[0]), cnts.l2_counts[0],
per_process_get_name(p->l2_index[1]), cnts.l2_counts[1],
per_process_get_name(p->l2_index[2]), cnts.l2_counts[2],
per_process_get_name(p->l2_index[3]), cnts.l2_counts[3],
cnts.l2_cycles);
}
/*
FUNCTION per_process_results_write
DESCRIPTION
Allow some control over the results. If the user forgets to autolock or
wants to unlock the results so they will be deleted, then this is
where it is processed.
For example, to unlock process 23
echo "unlock" > 23
DEPENDENCIES
RETURN VALUE
Number of characters used (all of them!)
SIDE EFFECTS
*/
int per_process_results_write(struct file *file, const char *buff,
unsigned long cnt, void *data)
{
char *newbuf;
struct per_process_perf_mon_type *p =
(struct per_process_perf_mon_type *)data;
if (p == 0)
return cnt;
/*
* Alloc the user data in kernel space. and then copy user to kernel
*/
newbuf = kmalloc(cnt + 1, GFP_KERNEL);
if (0 == newbuf)
return cnt;
if (copy_from_user(newbuf, buff, cnt) != 0) {
printk(KERN_INFO "%s copy_from_user failed\n", __func__);
return cnt;
}
if (0 == strcmp("lock", newbuf))
p->flags |= PERF_ENTRY_LOCKED;
else if (0 == strcmp("unlock", newbuf))
p->flags &= ~PERF_ENTRY_LOCKED;
else if (0 == strcmp("auto", newbuf))
p->flags |= PERF_AUTOLOCK;
else if (0 == strcmp("autoun", newbuf))
p->flags &= ~PERF_AUTOLOCK;
return cnt;
}
/*
FUNCTION perProcessCreateResults
DESCRIPTION
Create the results /proc file if the system parameters allow it...
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
*/
void per_process_create_results_proc(struct per_process_perf_mon_type *p)
{
if (0 == p->pidName)
p->pidName = kmalloc(12, GFP_KERNEL);
if (0 == p->pidName)
return;
sprintf(p->pidName, "%ld", p->pid);
if (0 == p->proc) {
p->proc = create_proc_entry(p->pidName, 0777, values_dir);
if (0 == p->proc)
return;
} else {
p->proc->name = p->pidName;
}
p->proc->read_proc = per_process_results_read;
p->proc->write_proc = per_process_results_write;
p->proc->data = (void *)p;
}
void per_process_create_l2_results_proc(struct per_process_perf_mon_type *p)
{
if (0 == p->pidName)
p->pidName = kmalloc(12, GFP_KERNEL);
if (0 == p->pidName)
return;
sprintf(p->pidName, "%ld", p->pid);
if (0 == p->l2_proc) {
p->l2_proc = create_proc_entry(p->pidName, 0777,
l2_results_dir);
if (0 == p->l2_proc)
return;
} else {
p->l2_proc->name = p->pidName;
}
p->l2_proc->read_proc = per_process_l2_results_read;
p->l2_proc->write_proc = per_process_results_write;
p->l2_proc->data = (void *)p;
}
/*
FUNCTION per_process_swap_out
DESCRIPTION
Store the counters from the process that is about to swap out. We take
the old counts and add them to the current counts in the perf registers.
Before the new process is swapped in, the counters are reset.
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
*/
typedef void (*vfun)(void *);
void per_process_swap_out(struct per_process_perf_mon_type *data)
{
int i;
unsigned long overflow;
#ifdef CONFIG_ARCH_MSM8X60
unsigned long l2_overflow;
#endif
struct per_process_perf_mon_type *p = data;
MARKPIDS('O', p->pid, 0);
RCP15_PMOVSR(overflow);
#ifdef CONFIG_ARCH_MSM8X60
RCP15_L2PMOVSR(l2_overflow);
#endif
if (!pp_enabled)
return;
/*
* The kernel for some reason (2.6.32.9) starts a process context on
* one core and ends on another. So the swap in and swap out can be
* on different cores. If this happens, we need to stop the
* counters and collect the data on the core that started the counters
* ....otherwise we receive invalid data. So we mark the the core with
* the process as deferred. The next time a process is swapped on
* the core that the process was running on, the counters will be
* updated.
*/
if ((smp_processor_id() != p->running_cpu) && (p->pid != 0)) {
fake_swap_out[p->running_cpu] = 1;
return;
}
_SWAPS(p)++;
_CYCLES(p) += pm_get_cycle_count();
if (overflow & PM_CYCLE_OVERFLOW_MASK)
_CYCLES(p) += 0xFFFFFFFF;
for (i = 0; i < PERF_NUM_MONITORS; i++) {
_COUNTS(p, i) += pm_get_count(i);
if (overflow & (1 << i))
_COUNTS(p, i) += 0xFFFFFFFF;
}
#ifdef CONFIG_ARCH_MSM8X60
_L2CYCLES(p) += l2_pm_get_cycle_count();
if (l2_overflow & L2_PM_CYCLE_OVERFLOW_MASK)
_L2CYCLES(p) += 0xFFFFFFFF;
for (i = 0; i < PERF_NUM_MONITORS; i++) {
_L2COUNTS(p, i) += l2_pm_get_count(i);
if (l2_overflow & (1 << i))
_L2COUNTS(p, i) += 0xFFFFFFFF;
}
#endif
}
/*
FUNCTION per_process_remove_manual
DESCRIPTION
Remove an entry from the results directory if the flags allow this.
When not enbled or the entry is locked, the values/results will
not be removed.
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
*/
void per_process_remove_manual(unsigned long pid)
{
struct per_process_perf_mon_type *p = per_process_find(pid);
/*
* Check all of the flags to see if we can remove this one
* Then mark as not used
*/
if (0 == p)
return;
p->pid = (0xFFFFFFFF);
/*
* Remove the proc entry.
*/
if (p->proc)
remove_proc_entry(p->pidName, values_dir);
if (p->l2_proc)
remove_proc_entry(p->pidName, l2_results_dir);
kfree(p->pidName);
/*
* Clear them out...and ensure the pid is invalid
*/
memset(p, 0, sizeof *p);
p->pid = 0xFFFFFFFF;
pm_remove_pid = -1;
}
/*
* Remove called when a process exits...
*/
void _per_process_remove(unsigned long pid) {}
/*
FUNCTION per_process_initialize
DESCRIPTION
Initialize performance collection information for a new process.
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
May create a new proc entry
*/
void per_process_initialize(struct per_process_perf_mon_type *p,
unsigned long pid)
{
int i;
/*
* See if this is the pid we are interested in...
*/
if (pp_settings_valid == -1)
return;
if ((pp_set_pid != pid) && (pp_set_pid != 0))
return;
/*
* Clear out the statistics table then insert this pid
* We want to keep the proc entry and the name
*/
p->pid = pid;
/*
* Create a proc entry for this pid, then get the current event types and
* store in data struct so when the process is switched in we can track
* it.
*/
if (p->proc == 0) {
per_process_create_results_proc(p);
#ifdef CONFIG_ARCH_MSM8X60
per_process_create_l2_results_proc(p);
#endif
}
_CYCLES(p) = 0;
_L2CYCLES(p) = 0;
_SWAPS(p) = 0;
/*
* Set the per process data struct, but not the monitors until later...
* Init only happens with the user sets the SetPID variable to this pid
* so we can load new values.
*/
for (i = 0; i < PERF_NUM_MONITORS; i++) {
p->index[i] = per_proc_event[i];
#ifdef CONFIG_ARCH_MSM8X60
p->l2_index[i] = l2_per_proc_event[i];
#endif
_COUNTS(p, i) = 0;
_L2COUNTS(p, i) = 0;
}
p->lpm0evtyper = pp_lpm0evtyper;
p->lpm1evtyper = pp_lpm1evtyper;
p->lpm2evtyper = pp_lpm2evtyper;
p->l2lpmevtyper = pp_l2lpmevtyper;
p->vlpmevtyper = pp_vlpmevtyper;
#ifdef CONFIG_ARCH_MSM8X60
p->l2pmevtyper0 = pp_l2pmevtyper0;
p->l2pmevtyper1 = pp_l2pmevtyper1;
p->l2pmevtyper2 = pp_l2pmevtyper2;
p->l2pmevtyper3 = pp_l2pmevtyper3;
p->l2pmevtyper4 = pp_l2pmevtyper4;
#endif
/*
* Reset pid and settings value
*/
pp_set_pid = -1;
pp_settings_valid = -1;
}
/*
FUNCTION per_process_swap_in
DESCRIPTION
Called when a context switch is about to start this PID.
We check to see if this process has an entry or not and create one
if not locked...
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
*/
void per_process_swap_in(struct per_process_perf_mon_type *p_new,
unsigned long pid)
{
int i;
MARKPIDS('I', p_new->pid, 0);
/*
* If the set proc variable == the current pid then init a new
* entry...
*/
if (pp_set_pid == pid)
per_process_initialize(p_new, pid);
p_new->running_cpu = smp_processor_id();
last_in_pid[smp_processor_id()] = pid;
/*
* setup the monitors for this process.
*/
for (i = 0; i < PERF_NUM_MONITORS; i++) {
pm_set_event(i, p_new->index[i]);
#ifdef CONFIG_ARCH_MSM8X60
l2_pm_set_event(i, p_new->l2_index[i]);
#endif
}
pm_set_local_iu(p_new->lpm0evtyper);
pm_set_local_xu(p_new->lpm1evtyper);
pm_set_local_su(p_new->lpm2evtyper);
pm_set_local_l2(p_new->l2lpmevtyper);
#ifdef CONFIG_ARCH_MSM8X60
pm_set_local_bu(p_new->l2pmevtyper0);
pm_set_local_cb(p_new->l2pmevtyper1);
pm_set_local_mp(p_new->l2pmevtyper2);
pm_set_local_sp(p_new->l2pmevtyper3);
pm_set_local_scu(p_new->l2pmevtyper4);
#endif
}
/*
FUNCTION perProcessSwitch
DESCRIPTION
Called during context switch. Updates the counts on the process about to
be swapped out and brings in the counters for the process about to be
swapped in.
All is dependant on the enabled and lock flags.
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
*/
DEFINE_SPINLOCK(pm_lock);
void _per_process_switch(unsigned long old_pid, unsigned long new_pid)
{
struct per_process_perf_mon_type *p_old, *p_new;
if (pm_global_enable == 0)
return;
spin_lock(&pm_lock);
pm_stop_all();
#ifdef CONFIG_ARCH_MSM8X60
l2_pm_stop_all();
#endif
/*
* We detected that the process was swapped in on one core and out on
* a different core. This does not allow us to stop and stop counters
* properly so we need to defer processing. This checks to see if there
* is any defered processing necessary. And does it... */
if (fake_swap_out[smp_processor_id()] != 0) {
fake_swap_out[smp_processor_id()] = 0;
p_old = per_process_find(last_in_pid[smp_processor_id()]);
last_in_pid[smp_processor_id()] = 0;
if (p_old != 0)
per_process_swap_out(p_old);
}
/*
* Clear the data collected so far for this process?
*/
if (pp_clear_pid != -1) {
struct per_process_perf_mon_type *p_clear =
per_process_find(pp_clear_pid);
if (p_clear) {
memset(p_clear->cnts, 0,
sizeof(struct pm_counters_s)*num_possible_cpus());
printk(KERN_INFO "Clear Per Processor Stats for \
PID:%ld\n", pp_clear_pid);
pp_clear_pid = -1;
}
}
/*
* Always collect for 0, it collects for all.
*/
if (pp_enabled) {
if (first_switch == 1) {
per_process_initialize(&perf_mons[0], 0);
first_switch = 0;
}
if (pm_global) {
per_process_swap_out(&perf_mons[0]);
per_process_swap_in(&perf_mons[0], 0);
} else {
p_old = per_process_find(old_pid);
p_new = per_process_find(new_pid);
/*
* save the old counts to the old data struct, if the
* returned ptr is NULL or the process id passed is not
* the same as the process id in the data struct then
* don't update the data.
*/
if ((p_old) && (p_old->pid == old_pid) &&
(p_old->pid != 0)) {
per_process_swap_out(p_old);
}
/*
* Setup the counters for the new process
*/
if (pp_set_pid == new_pid)
per_process_initialize(p_new, new_pid);
if ((p_new->pid == new_pid) && (new_pid != 0))
per_process_swap_in(p_new, new_pid);
}
pm_reset_all();
#ifdef CONFIG_ARCH_MSM8X60
l2_pm_reset_all();
#endif
#ifdef CONFIG_ARCH_QSD8X50
axi_swaps++;
if (axi_swaps%pm_axi_info.refresh == 0) {
if (pm_axi_info.clear == 1) {
pm_axi_clear_cnts();
pm_axi_info.clear = 0;
}
if (pm_axi_info.enable == 0)
pm_axi_disable();
else
pm_axi_update_cnts();
axi_swaps = 0;
}
#endif
}
pm_start_all();
#ifdef CONFIG_ARCH_MSM8X60
l2_pm_start_all();
#endif
spin_unlock(&pm_lock);
}
/*
FUNCTION pmInterruptIn
DESCRIPTION
Called when an interrupt is being processed. If the pmStopForInterrutps
flag is non zero then we disable the counting of performance monitors.
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
*/
static int pm_interrupt_nesting_count;
static unsigned long pm_cycle_in, pm_cycle_out;
void _perf_mon_interrupt_in(void)
{
if (pm_global_enable == 0)
return;
if (pm_stop_for_interrupts == 0)
return;
pm_interrupt_nesting_count++; /* Atomic */
pm_stop_all();
pm_cycle_in = pm_get_cycle_count();
}
/*
FUNCTION perfMonInterruptOut
DESCRIPTION
Reenable performance monitor counting whn the nest count goes to zero
provided the counting has been stoped
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
*/
void _perf_mon_interrupt_out(void)
{
if (pm_global_enable == 0)
return;
if (pm_stop_for_interrupts == 0)
return;
--pm_interrupt_nesting_count; /* Atomic?? */
if (pm_interrupt_nesting_count <= 0) {
pm_cycle_out = pm_get_cycle_count();
if (pm_cycle_in != pm_cycle_out)
printk(KERN_INFO "pmIn!=pmOut in:%lx out:%lx\n",
pm_cycle_in, pm_cycle_out);
if (pp_enabled) {
pm_start_all();
#ifdef CONFIG_ARCH_MSM8X60
l2_pm_start_all();
#endif
}
pm_interrupt_nesting_count = 0;
}
}
void per_process_do_global(unsigned long g)
{
pm_global = g;
if (pm_global == 1) {
pm_stop_all();
#ifdef CONFIG_ARCH_MSM8X60
l2_pm_stop_all();
#endif
pm_reset_all();
#ifdef CONFIG_ARCH_MSM8X60
l2_pm_reset_all();
#endif
pp_set_pid = 0;
per_process_swap_in(&perf_mons[0], 0);
pm_start_all();
#ifdef CONFIG_ARCH_MSM8X60
l2_pm_start_all();
#endif
} else {
pm_stop_all();
#ifdef CONFIG_ARCH_MSM8X60
l2_pm_stop_all();
#endif
}
}
/*
FUNCTION per_process_write
DESCRIPTION
Generic routine to handle any of the settings /proc directory writes.
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
*/
int per_process_write(struct file *file, const char *buff,
unsigned long cnt, void *data, const char *fmt)
{
char *newbuf;
unsigned long *d = (unsigned long *)data;
/*
* Alloc the user data in kernel space. and then copy user to kernel
*/
newbuf = kmalloc(cnt + 1, GFP_KERNEL);
if (0 == newbuf)
return PM_PP_ERR;
if (copy_from_user(newbuf, buff, cnt) != 0) {
printk(KERN_INFO "%s copy_from_user failed\n", __func__);
return cnt;
}
sscanf(newbuf, fmt, d);
kfree(newbuf);
/*
* If this is a remove command then do it now...
*/
if (d == &pm_remove_pid)
per_process_remove_manual(*d);
if (d == &pm_global)
per_process_do_global(*d);
return cnt;
}
int per_process_write_dec(struct file *file, const char *buff,
unsigned long cnt, void *data)
{
return per_process_write(file, buff, cnt, data, "%ld");
}
int per_process_write_hex(struct file *file, const char *buff,
unsigned long cnt, void *data)
{
return per_process_write(file, buff, cnt, data, "%lx");
}
/*
FUNCTION per_process_read
DESCRIPTION
Generic read handler for the /proc settings directory.
DEPENDENCIES
RETURN VALUE
Number of characters to output.
SIDE EFFECTS
*/
int per_process_read(char *page, char **start, off_t off, int count,
int *eof, void *data)
{
unsigned long *d = (unsigned long *)data;
return sprintf(page, "%lx", *d);
}
int per_process_read_decimal(char *page, char **start, off_t off, int count,
int *eof, void *data)
{
unsigned long *d = (unsigned long *)data;
return sprintf(page, "%ld", *d);
}
/*
FUNCTION per_process_proc_entry
DESCRIPTION
Create a generic entry for the /proc settings directory.
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
*/
void per_process_proc_entry(char *name, unsigned long *var,
struct proc_dir_entry *d, int hex)
{
struct proc_dir_entry *pe;
pe = create_proc_entry(name, 0777, d);
if (0 == pe)
return;
if (hex) {
pe->read_proc = per_process_read;
pe->write_proc = per_process_write_hex;
} else {
pe->read_proc = per_process_read_decimal;
pe->write_proc = per_process_write_dec;
}
pe->data = (void *)var;
if (pp_proc_entry_index >= PP_MAX_PROC_ENTRIES) {
printk(KERN_INFO "PERF: proc entry overflow,\
memleak on module unload occured");
return;
}
per_process_proc_names[pp_proc_entry_index++] = name;
}
static int perfmon_notifier(struct notifier_block *self, unsigned long cmd,
void *v)
{
static int old_pid = -1;
struct thread_info *thread = v;
int current_pid;
if (cmd != THREAD_NOTIFY_SWITCH)
return old_pid;
current_pid = thread->task->pid;
if (old_pid != -1)
_per_process_switch(old_pid, current_pid);
old_pid = current_pid;
return old_pid;
}
static struct notifier_block perfmon_notifier_block = {
.notifier_call = perfmon_notifier,
};
/*
FUNCTION per_process_perf_init
DESCRIPTION
Initialze the per process performance monitor variables and /proc space.
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
*/
int per_process_perf_init(void)
{
#ifdef CONFIG_ARCH_MSM8X60
smp_call_function_single(0, (void *)pm_initialize, (void *)NULL, 1);
smp_call_function_single(1, (void *)pm_initialize, (void *)NULL, 1);
l2_pm_initialize();
#else
pm_initialize();
#endif
pm_axi_init();
pm_axi_clear_cnts();
proc_dir = proc_mkdir("ppPerf", NULL);
values_dir = proc_mkdir("results", proc_dir);
settings_dir = proc_mkdir("settings", proc_dir);
per_process_proc_entry("enable", &pp_enabled, settings_dir, 1);
per_process_proc_entry("valid", &pp_settings_valid, settings_dir, 1);
per_process_proc_entry("setPID", &pp_set_pid, settings_dir, 0);
per_process_proc_entry("clearPID", &pp_clear_pid, settings_dir, 0);
per_process_proc_entry("event0", &per_proc_event[0], settings_dir, 1);
per_process_proc_entry("event1", &per_proc_event[1], settings_dir, 1);
per_process_proc_entry("event2", &per_proc_event[2], settings_dir, 1);
per_process_proc_entry("event3", &per_proc_event[3], settings_dir, 1);
per_process_proc_entry("l2_event0", &l2_per_proc_event[0], settings_dir,
1);
per_process_proc_entry("l2_event1", &l2_per_proc_event[1], settings_dir,
1);
per_process_proc_entry("l2_event2", &l2_per_proc_event[2], settings_dir,
1);
per_process_proc_entry("l2_event3", &l2_per_proc_event[3], settings_dir,
1);
per_process_proc_entry("debug", &dbg_flags, settings_dir, 1);
per_process_proc_entry("autolock", &pp_auto_lock, settings_dir, 1);
per_process_proc_entry("lpm0evtyper", &pp_lpm0evtyper, settings_dir, 1);
per_process_proc_entry("lpm1evtyper", &pp_lpm1evtyper, settings_dir, 1);
per_process_proc_entry("lpm2evtyper", &pp_lpm2evtyper, settings_dir, 1);
per_process_proc_entry("l2lpmevtyper", &pp_l2lpmevtyper, settings_dir,
1);
per_process_proc_entry("vlpmevtyper", &pp_vlpmevtyper, settings_dir, 1);
per_process_proc_entry("l2pmevtyper0", &pp_l2pmevtyper0, settings_dir,
1);
per_process_proc_entry("l2pmevtyper1", &pp_l2pmevtyper1, settings_dir,
1);
per_process_proc_entry("l2pmevtyper2", &pp_l2pmevtyper2, settings_dir,
1);
per_process_proc_entry("l2pmevtyper3", &pp_l2pmevtyper3, settings_dir,
1);
per_process_proc_entry("l2pmevtyper4", &pp_l2pmevtyper4, settings_dir,
1);
per_process_proc_entry("stopForInterrupts", &pm_stop_for_interrupts,
settings_dir, 1);
per_process_proc_entry("global", &pm_global, settings_dir, 1);
per_process_proc_entry("globalEnable", &pm_global_enable, settings_dir,
1);
per_process_proc_entry("removePID", &pm_remove_pid, settings_dir, 0);
axi_dir = proc_mkdir("axi", proc_dir);
axi_settings_dir = proc_mkdir("settings", axi_dir);
axi_results_dir = proc_mkdir("results", axi_dir);
pm_axi_set_proc_entry("axi_enable", &pm_axi_info.enable,
axi_settings_dir, 1);
pm_axi_set_proc_entry("axi_clear", &pm_axi_info.clear, axi_settings_dir,
0);
pm_axi_set_proc_entry("axi_valid", &pm_axi_info.valid, axi_settings_dir,
1);
pm_axi_set_proc_entry("axi_sel_reg0", &pm_axi_info.sel_reg0,
axi_settings_dir, 1);
pm_axi_set_proc_entry("axi_sel_reg1", &pm_axi_info.sel_reg1,
axi_settings_dir, 1);
pm_axi_set_proc_entry("axi_ten_sel", &pm_axi_info.ten_sel_reg,
axi_settings_dir, 1);
pm_axi_set_proc_entry("axi_refresh", &pm_axi_info.refresh,
axi_settings_dir, 1);
pm_axi_get_cnt_proc_entry("axi_cnts", &axi_cnts, axi_results_dir, 0);
l2_dir = proc_mkdir("l2", proc_dir);
l2_results_dir = proc_mkdir("results", l2_dir);
memset(perf_mons, 0, sizeof(perf_mons));
per_process_create_results_proc(&perf_mons[0]);
per_process_create_l2_results_proc(&perf_mons[0]);
thread_register_notifier(&perfmon_notifier_block);
/*
* Set the function pointers so the module can be activated.
*/
pp_interrupt_out_ptr = _perf_mon_interrupt_out;
pp_interrupt_in_ptr = _perf_mon_interrupt_in;
pp_process_remove_ptr = _per_process_remove;
pp_loaded = 1;
pm_axi_info.refresh = 1;
#ifdef CONFIG_ARCH_MSM8X60
smp_call_function_single(0, (void *)pm_reset_all, (void *)NULL, 1);
smp_call_function_single(1, (void *)pm_reset_all, (void *)NULL, 1);
smp_call_function_single(0, (void *)l2_pm_reset_all, (void *)NULL, 1);
smp_call_function_single(1, (void *)l2_pm_reset_all, (void *)NULL, 1);
#else
pm_reset_all();
#endif
return 0;
}
/*
FUNCTION per_process_perf_exit
DESCRIPTION
Module exit functionm, clean up, renmove proc entries
DEPENDENCIES
RETURN VALUE
SIDE EFFECTS
No more per process
*/
void per_process_perf_exit(void)
{
unsigned long i;
/*
* Sert the function pointers to 0 so the functions will no longer
* be invoked
*/
pp_loaded = 0;
pp_interrupt_out_ptr = 0;
pp_interrupt_in_ptr = 0;
pp_process_remove_ptr = 0;
/*
* Remove the results
*/
for (i = 0; i < PERF_MON_PROCESS_NUM; i++)
per_process_remove_manual(perf_mons[i].pid);
/*
* Remove the proc entries in the settings dir
*/
i = 0;
for (i = 0; i < pp_proc_entry_index; i++)
remove_proc_entry(per_process_proc_names[i], settings_dir);
/*remove proc axi files*/
remove_proc_entry("axi_enable", axi_settings_dir);
remove_proc_entry("axi_valid", axi_settings_dir);
remove_proc_entry("axi_refresh", axi_settings_dir);
remove_proc_entry("axi_clear", axi_settings_dir);
remove_proc_entry("axi_sel_reg0", axi_settings_dir);
remove_proc_entry("axi_sel_reg1", axi_settings_dir);
remove_proc_entry("axi_ten_sel", axi_settings_dir);
remove_proc_entry("axi_cnts", axi_results_dir);
/*
* Remove the directories
*/
remove_proc_entry("results", l2_dir);
remove_proc_entry("l2", proc_dir);
remove_proc_entry("results", proc_dir);
remove_proc_entry("settings", proc_dir);
remove_proc_entry("results", axi_dir);
remove_proc_entry("settings", axi_dir);
remove_proc_entry("axi", proc_dir);
remove_proc_entry("ppPerf", NULL);
pm_free_irq();
#ifdef CONFIG_ARCH_MSM8X60
l2_pm_free_irq();
#endif
thread_unregister_notifier(&perfmon_notifier_block);
#ifdef CONFIG_ARCH_MSM8X60
smp_call_function_single(0, (void *)pm_deinitialize, (void *)NULL, 1);
smp_call_function_single(1, (void *)pm_deinitialize, (void *)NULL, 1);
l2_pm_deinitialize();
#else
pm_deinitialize();
#endif
}