Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/arch/arm/perfmon/per-process-perf.c b/arch/arm/perfmon/per-process-perf.c
new file mode 100644
index 0000000..c8bebd8
--- /dev/null
+++ b/arch/arm/perfmon/per-process-perf.c
@@ -0,0 +1,1251 @@
+/* Copyright (c) 2010, Code Aurora Forum. 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
+}