1252 lines
30 KiB
C
1252 lines
30 KiB
C
/* 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
|
|
}
|