/* 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 #include #include #include #include #include "linux/proc_fs.h" #include "linux/kernel_stat.h" #include #include "asm/uaccess.h" #include "cp15_registers.h" #include "l2_cp15_registers.h" #include #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 }