diff options
author | Deng-Cheng Zhu <dengcheng.zhu@gmail.com> | 2010-10-12 07:37:23 -0400 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2010-10-29 14:08:49 -0400 |
commit | 7e788d967c092343cca773542565c362991c87c0 (patch) | |
tree | 88c3ef91aba02a7dae93ccc674f5bed461c16d0d /arch | |
parent | 14f7001284185bffeb796a181664906f7160f593 (diff) |
MIPS: Perf-events: Add callchain support
Adds callchain support for MIPS Perf-events. For more info on this feature,
please refer to tools/perf/Documentation/perf-report.txt and
tools/perf/design.txt.
Currently userspace callchain data is not recorded, because we do not have
a safe way to do this.
Signed-off-by: Deng-Cheng Zhu <dengcheng.zhu@gmail.com>
Acked-by: David Daney <ddaney@caviumnetworks.com>
To: linux-mips@linux-mips.org
Cc: a.p.zijlstra@chello.nl
Cc: paulus@samba.org
Cc: mingo@elte.hu
Cc: acme@redhat.com
Cc: jamie.iles@picochip.com
Cc: matt@console-pimps.org
Patchwork: https://patchwork.linux-mips.org/patch/1690/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/mips/kernel/perf_event.c | 108 |
1 files changed, 107 insertions, 1 deletions
diff --git a/arch/mips/kernel/perf_event.c b/arch/mips/kernel/perf_event.c index eefdf60da2d7..95c833e8dc81 100644 --- a/arch/mips/kernel/perf_event.c +++ b/arch/mips/kernel/perf_event.c | |||
@@ -6,7 +6,8 @@ | |||
6 | * | 6 | * |
7 | * This code is based on the implementation for ARM, which is in turn | 7 | * This code is based on the implementation for ARM, which is in turn |
8 | * based on the sparc64 perf event code and the x86 code. Performance | 8 | * based on the sparc64 perf event code and the x86 code. Performance |
9 | * counter access is based on the MIPS Oprofile code. | 9 | * counter access is based on the MIPS Oprofile code. And the callchain |
10 | * support references the code of MIPS stacktrace.c. | ||
10 | * | 11 | * |
11 | * This program is free software; you can redistribute it and/or modify | 12 | * This program is free software; you can redistribute it and/or modify |
12 | * it under the terms of the GNU General Public License version 2 as | 13 | * it under the terms of the GNU General Public License version 2 as |
@@ -486,3 +487,108 @@ handle_associated_event(struct cpu_hw_events *cpuc, | |||
486 | if (perf_event_overflow(event, 0, data, regs)) | 487 | if (perf_event_overflow(event, 0, data, regs)) |
487 | mipspmu->disable_event(idx); | 488 | mipspmu->disable_event(idx); |
488 | } | 489 | } |
490 | |||
491 | /* Callchain handling code. */ | ||
492 | static inline void | ||
493 | callchain_store(struct perf_callchain_entry *entry, | ||
494 | u64 ip) | ||
495 | { | ||
496 | if (entry->nr < PERF_MAX_STACK_DEPTH) | ||
497 | entry->ip[entry->nr++] = ip; | ||
498 | } | ||
499 | |||
500 | /* | ||
501 | * Leave userspace callchain empty for now. When we find a way to trace | ||
502 | * the user stack callchains, we add here. | ||
503 | */ | ||
504 | static void | ||
505 | perf_callchain_user(struct pt_regs *regs, | ||
506 | struct perf_callchain_entry *entry) | ||
507 | { | ||
508 | } | ||
509 | |||
510 | static void save_raw_perf_callchain(struct perf_callchain_entry *entry, | ||
511 | unsigned long reg29) | ||
512 | { | ||
513 | unsigned long *sp = (unsigned long *)reg29; | ||
514 | unsigned long addr; | ||
515 | |||
516 | while (!kstack_end(sp)) { | ||
517 | addr = *sp++; | ||
518 | if (__kernel_text_address(addr)) { | ||
519 | callchain_store(entry, addr); | ||
520 | if (entry->nr >= PERF_MAX_STACK_DEPTH) | ||
521 | break; | ||
522 | } | ||
523 | } | ||
524 | } | ||
525 | |||
526 | static void | ||
527 | perf_callchain_kernel(struct pt_regs *regs, | ||
528 | struct perf_callchain_entry *entry) | ||
529 | { | ||
530 | unsigned long sp = regs->regs[29]; | ||
531 | #ifdef CONFIG_KALLSYMS | ||
532 | unsigned long ra = regs->regs[31]; | ||
533 | unsigned long pc = regs->cp0_epc; | ||
534 | |||
535 | callchain_store(entry, PERF_CONTEXT_KERNEL); | ||
536 | if (raw_show_trace || !__kernel_text_address(pc)) { | ||
537 | unsigned long stack_page = | ||
538 | (unsigned long)task_stack_page(current); | ||
539 | if (stack_page && sp >= stack_page && | ||
540 | sp <= stack_page + THREAD_SIZE - 32) | ||
541 | save_raw_perf_callchain(entry, sp); | ||
542 | return; | ||
543 | } | ||
544 | do { | ||
545 | callchain_store(entry, pc); | ||
546 | if (entry->nr >= PERF_MAX_STACK_DEPTH) | ||
547 | break; | ||
548 | pc = unwind_stack(current, &sp, pc, &ra); | ||
549 | } while (pc); | ||
550 | #else | ||
551 | callchain_store(entry, PERF_CONTEXT_KERNEL); | ||
552 | save_raw_perf_callchain(entry, sp); | ||
553 | #endif | ||
554 | } | ||
555 | |||
556 | static void | ||
557 | perf_do_callchain(struct pt_regs *regs, | ||
558 | struct perf_callchain_entry *entry) | ||
559 | { | ||
560 | int is_user; | ||
561 | |||
562 | if (!regs) | ||
563 | return; | ||
564 | |||
565 | is_user = user_mode(regs); | ||
566 | |||
567 | if (!current || !current->pid) | ||
568 | return; | ||
569 | |||
570 | if (is_user && current->state != TASK_RUNNING) | ||
571 | return; | ||
572 | |||
573 | if (!is_user) { | ||
574 | perf_callchain_kernel(regs, entry); | ||
575 | if (current->mm) | ||
576 | regs = task_pt_regs(current); | ||
577 | else | ||
578 | regs = NULL; | ||
579 | } | ||
580 | if (regs) | ||
581 | perf_callchain_user(regs, entry); | ||
582 | } | ||
583 | |||
584 | static DEFINE_PER_CPU(struct perf_callchain_entry, pmc_irq_entry); | ||
585 | |||
586 | struct perf_callchain_entry * | ||
587 | perf_callchain(struct pt_regs *regs) | ||
588 | { | ||
589 | struct perf_callchain_entry *entry = &__get_cpu_var(pmc_irq_entry); | ||
590 | |||
591 | entry->nr = 0; | ||
592 | perf_do_callchain(regs, entry); | ||
593 | return entry; | ||
594 | } | ||