|
@@ -7,21 +7,18 @@
|
|
* Copyright (C) 2010 ARM Ltd., Will Deacon <will.deacon@arm.com>
|
|
* Copyright (C) 2010 ARM Ltd., Will Deacon <will.deacon@arm.com>
|
|
*
|
|
*
|
|
* This code is based on the sparc64 perf event code, which is in turn based
|
|
* This code is based on the sparc64 perf event code, which is in turn based
|
|
- * on the x86 code. Callchain code is based on the ARM OProfile backtrace
|
|
|
|
- * code.
|
|
|
|
|
|
+ * on the x86 code.
|
|
*/
|
|
*/
|
|
#define pr_fmt(fmt) "hw perfevents: " fmt
|
|
#define pr_fmt(fmt) "hw perfevents: " fmt
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/pm_runtime.h>
|
|
-#include <linux/uaccess.h>
|
|
|
|
#include <linux/irq.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/irqdesc.h>
|
|
#include <linux/irqdesc.h>
|
|
|
|
|
|
#include <asm/irq_regs.h>
|
|
#include <asm/irq_regs.h>
|
|
#include <asm/pmu.h>
|
|
#include <asm/pmu.h>
|
|
-#include <asm/stacktrace.h>
|
|
|
|
|
|
|
|
static int
|
|
static int
|
|
armpmu_map_cache_event(const unsigned (*cache_map)
|
|
armpmu_map_cache_event(const unsigned (*cache_map)
|
|
@@ -533,130 +530,3 @@ int armpmu_register(struct arm_pmu *armpmu, int type)
|
|
return perf_pmu_register(&armpmu->pmu, armpmu->name, type);
|
|
return perf_pmu_register(&armpmu->pmu, armpmu->name, type);
|
|
}
|
|
}
|
|
|
|
|
|
-/*
|
|
|
|
- * Callchain handling code.
|
|
|
|
- */
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * The registers we're interested in are at the end of the variable
|
|
|
|
- * length saved register structure. The fp points at the end of this
|
|
|
|
- * structure so the address of this struct is:
|
|
|
|
- * (struct frame_tail *)(xxx->fp)-1
|
|
|
|
- *
|
|
|
|
- * This code has been adapted from the ARM OProfile support.
|
|
|
|
- */
|
|
|
|
-struct frame_tail {
|
|
|
|
- struct frame_tail __user *fp;
|
|
|
|
- unsigned long sp;
|
|
|
|
- unsigned long lr;
|
|
|
|
-} __attribute__((packed));
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * Get the return address for a single stackframe and return a pointer to the
|
|
|
|
- * next frame tail.
|
|
|
|
- */
|
|
|
|
-static struct frame_tail __user *
|
|
|
|
-user_backtrace(struct frame_tail __user *tail,
|
|
|
|
- struct perf_callchain_entry *entry)
|
|
|
|
-{
|
|
|
|
- struct frame_tail buftail;
|
|
|
|
- unsigned long err;
|
|
|
|
-
|
|
|
|
- if (!access_ok(VERIFY_READ, tail, sizeof(buftail)))
|
|
|
|
- return NULL;
|
|
|
|
-
|
|
|
|
- pagefault_disable();
|
|
|
|
- err = __copy_from_user_inatomic(&buftail, tail, sizeof(buftail));
|
|
|
|
- pagefault_enable();
|
|
|
|
-
|
|
|
|
- if (err)
|
|
|
|
- return NULL;
|
|
|
|
-
|
|
|
|
- perf_callchain_store(entry, buftail.lr);
|
|
|
|
-
|
|
|
|
- /*
|
|
|
|
- * Frame pointers should strictly progress back up the stack
|
|
|
|
- * (towards higher addresses).
|
|
|
|
- */
|
|
|
|
- if (tail + 1 >= buftail.fp)
|
|
|
|
- return NULL;
|
|
|
|
-
|
|
|
|
- return buftail.fp - 1;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-void
|
|
|
|
-perf_callchain_user(struct perf_callchain_entry *entry, struct pt_regs *regs)
|
|
|
|
-{
|
|
|
|
- struct frame_tail __user *tail;
|
|
|
|
-
|
|
|
|
- if (perf_guest_cbs && perf_guest_cbs->is_in_guest()) {
|
|
|
|
- /* We don't support guest os callchain now */
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- perf_callchain_store(entry, regs->ARM_pc);
|
|
|
|
-
|
|
|
|
- if (!current->mm)
|
|
|
|
- return;
|
|
|
|
-
|
|
|
|
- tail = (struct frame_tail __user *)regs->ARM_fp - 1;
|
|
|
|
-
|
|
|
|
- while ((entry->nr < PERF_MAX_STACK_DEPTH) &&
|
|
|
|
- tail && !((unsigned long)tail & 0x3))
|
|
|
|
- tail = user_backtrace(tail, entry);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * Gets called by walk_stackframe() for every stackframe. This will be called
|
|
|
|
- * whist unwinding the stackframe and is like a subroutine return so we use
|
|
|
|
- * the PC.
|
|
|
|
- */
|
|
|
|
-static int
|
|
|
|
-callchain_trace(struct stackframe *fr,
|
|
|
|
- void *data)
|
|
|
|
-{
|
|
|
|
- struct perf_callchain_entry *entry = data;
|
|
|
|
- perf_callchain_store(entry, fr->pc);
|
|
|
|
- return 0;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-void
|
|
|
|
-perf_callchain_kernel(struct perf_callchain_entry *entry, struct pt_regs *regs)
|
|
|
|
-{
|
|
|
|
- struct stackframe fr;
|
|
|
|
-
|
|
|
|
- if (perf_guest_cbs && perf_guest_cbs->is_in_guest()) {
|
|
|
|
- /* We don't support guest os callchain now */
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- arm_get_current_stackframe(regs, &fr);
|
|
|
|
- walk_stackframe(&fr, callchain_trace, entry);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-unsigned long perf_instruction_pointer(struct pt_regs *regs)
|
|
|
|
-{
|
|
|
|
- if (perf_guest_cbs && perf_guest_cbs->is_in_guest())
|
|
|
|
- return perf_guest_cbs->get_guest_ip();
|
|
|
|
-
|
|
|
|
- return instruction_pointer(regs);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-unsigned long perf_misc_flags(struct pt_regs *regs)
|
|
|
|
-{
|
|
|
|
- int misc = 0;
|
|
|
|
-
|
|
|
|
- if (perf_guest_cbs && perf_guest_cbs->is_in_guest()) {
|
|
|
|
- if (perf_guest_cbs->is_user_mode())
|
|
|
|
- misc |= PERF_RECORD_MISC_GUEST_USER;
|
|
|
|
- else
|
|
|
|
- misc |= PERF_RECORD_MISC_GUEST_KERNEL;
|
|
|
|
- } else {
|
|
|
|
- if (user_mode(regs))
|
|
|
|
- misc |= PERF_RECORD_MISC_USER;
|
|
|
|
- else
|
|
|
|
- misc |= PERF_RECORD_MISC_KERNEL;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return misc;
|
|
|
|
-}
|
|
|