diff options
Diffstat (limited to 'target/linux/ubicom32/files/arch/ubicom32/oprofile/profile.c')
-rw-r--r-- | target/linux/ubicom32/files/arch/ubicom32/oprofile/profile.c | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/target/linux/ubicom32/files/arch/ubicom32/oprofile/profile.c b/target/linux/ubicom32/files/arch/ubicom32/oprofile/profile.c new file mode 100644 index 0000000000..aeac3c6667 --- /dev/null +++ b/target/linux/ubicom32/files/arch/ubicom32/oprofile/profile.c @@ -0,0 +1,221 @@ +/* + * arch/ubicom32/oprofile/profile.c + * Oprofile support for arch Ubicom32 + * + * (C) Copyright 2009, Ubicom, Inc. + * + * This file is part of the Ubicom32 Linux Kernel Port. + * + * The Ubicom32 Linux Kernel Port is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 2 of the + * License, or (at your option) + * any later version. + * + * The Ubicom32 Linux Kernel Port 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. + * + * You should have received a copy of the GNU General Public License + * along with the Ubicom32 Linux Kernel Port. If not, see + * <http://www.gnu.org/licenses/>. + * + * Ubicom32 implementation derived from (with many thanks): + * arch/m68knommu + * arch/blackfin + * arch/parisc + */ + +/** + * @file profile.c + * + * @remark Copyright 2002 OProfile authors + * @remark Read the file COPYING + * + * @author Hunyue Yau <hy@hy-research.com> + */ + +#include <linux/oprofile.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/kernel.h> + +#include <asm/devtree.h> +#include <asm/thread.h> + +/* For identifying userland vs kernel address */ +#include <asm/stacktrace.h> +#include "ipProf.h" + +/* For communications with the backend */ +static struct profilenode *profile_node; + +/* Bitmask containing all Linux threads - as seen by the ROSR reg */ +static unsigned long th_all_mask; + +/* Lookup table to translate a hardware thread into a CPU identifier + * Table is indexed by the ROSR value which is assumed to be + * relatively small (0...15). + */ +unsigned int cpu_map[THREAD_ARCHITECTURAL_MAX]; + +static struct pt_regs regs; + +/* + * For each sample returned, checked to see if they are relevant to + * us. This is necessary as the ubicom32 architecture has other software + * running outside of Linux. Only then, put the sample into the relevant + * cpu bins. + * + * To minimize overhead, a global mask with all possible threads of in + * interest to us is used as a first check. Then a second mask identifying + * the thread is used to obtain an identifier for that "CPU". + */ + +/* + * ubicom32_build_cpu_th_mask() + * + * Build a lookup table for translation between hardware thread + * "ROSR" values and Linux CPU ids + * + * *** This gets executed on all CPUs at once! *** + */ +static void ubicom32_build_cpu_th_mask(void *mask) +{ + thread_t self = thread_get_self(); + unsigned long *th_m = mask; + + BUG_ON(self <= 0 || self >= THREAD_ARCHITECTURAL_MAX); + cpu_map[self] = smp_processor_id(); + + set_bit(self, th_m); +} + +/* + * profile_interrupt() + * + * Process samples returned from the profiler backend. The backend + * may return samples that are irrelevant to us or may even return + * multiple samples for the same CPU. Note that the sames may be + * for ANY cpu. At this time, this is unique and to support this requires + * Oprofile to expose an interface to accept the CPU that the same came + * frome. + */ +static irqreturn_t profile_interrupt(int irq, void *arg) +{ + int i, buf_entry; + int is_kernel; + unsigned int bit_th; + unsigned int th; + + if (!(profile_node->enabled) || profile_node->count < 0) { + printk(KERN_WARNING + "Unexpected interrupt, no samples or not enabled!\n"); + return IRQ_HANDLED; + } + + profile_node->busy = 1; /* Keep backend out */ + + for (i = 0; i < profile_node->count; i++) { + buf_entry = profile_node->tail; + profile_node->tail++; + profile_node->tail %= IPPROFILETIO_MAX_SAMPLES; + + /* Note - the "thread" ID is only the lower 4 bits */ + th = (0x0f & profile_node->samples[buf_entry].thread); + bit_th = (1 << th); + + if ((bit_th & th_all_mask) == 0) + continue; + + regs.pc = profile_node->samples[buf_entry].pc; + + is_kernel = ubicom32_is_kernel(regs.pc); + + oprofile_add_ext_sample_cpu(regs.pc, ®s, 0, is_kernel, + cpu_map[th]); + } + profile_node->count = 0; + profile_node->busy = 0; + + return IRQ_HANDLED; +} + +/* + * profile_start() + * + * Notification from oprofile to start the profiler + */ +static int profile_start(void) +{ + if (!profile_node) + return -1; + + profile_node->enabled = 1; + + return 0; +} + +/* + * profile_stop() + * + * Notification from oprofile to stop the profiler + */ +static void profile_stop(void) +{ + if (profile_node) + profile_node->enabled = 0; +} + +/* + * oprofile_arch_init() + * + * Attach to Oprofile after qualify the availability of the backend + * profiler support. + */ +int __init oprofile_arch_init(struct oprofile_operations *ops) +{ + int r = -ENODEV; + + profile_node = (struct profilenode *)devtree_find_node("profiler"); + + if (profile_node == NULL) { + printk(KERN_WARNING "Cannot find profiler node\n"); + return r; + } + + r = request_irq(profile_node->dn.recvirq, profile_interrupt, + IRQF_DISABLED, "profiler", NULL); + + if (r < 0) { + profile_node = NULL; + printk(KERN_WARNING "Cannot get profiler IRQ\n"); + return r; + } + + ops->start = profile_start; + ops->stop = profile_stop; + ops->cpu_type = "timer"; + + memset(cpu_map, 0, sizeof(cpu_map)); + + on_each_cpu(ubicom32_build_cpu_th_mask, &th_all_mask, 1); + + memset(®s, 0, sizeof(regs)); + + return r; +} + +/* + * oprofile_arch_exit() + * + * External call to take outselves out. + * Make sure backend is not running. + */ +void oprofile_arch_exit(void) +{ + BUG_ON(profile_node->enabled); +} |