| /** |
| * op_model_v7.c |
| * ARM V7 (Cortex A8) Event Monitor Driver |
| * |
| * Copyright 2008 Jean Pihet <jpihet@mvista.com> |
| * Copyright 2004 ARM SMP Development Team |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| #include <linux/types.h> |
| #include <linux/errno.h> |
| #include <linux/oprofile.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/smp.h> |
| |
| #include "op_counter.h" |
| #include "op_arm_model.h" |
| #include "op_model_v7.h" |
| |
| /* #define DEBUG */ |
| |
| |
| /* |
| * ARM V7 PMNC support |
| */ |
| |
| static u32 cnt_en[CNTMAX]; |
| |
| static inline void armv7_pmnc_write(u32 val) |
| { |
| val &= PMNC_MASK; |
| asm volatile("mcr p15, 0, %0, c9, c12, 0" : : "r" (val)); |
| } |
| |
| static inline u32 armv7_pmnc_read(void) |
| { |
| u32 val; |
| |
| asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (val)); |
| return val; |
| } |
| |
| static inline u32 armv7_pmnc_enable_counter(unsigned int cnt) |
| { |
| u32 val; |
| |
| if (cnt >= CNTMAX) { |
| printk(KERN_ERR "oprofile: CPU%u enabling wrong PMNC counter" |
| " %d\n", smp_processor_id(), cnt); |
| return -1; |
| } |
| |
| if (cnt == CCNT) |
| val = CNTENS_C; |
| else |
| val = (1 << (cnt - CNT0)); |
| |
| val &= CNTENS_MASK; |
| asm volatile("mcr p15, 0, %0, c9, c12, 1" : : "r" (val)); |
| |
| return cnt; |
| } |
| |
| static inline u32 armv7_pmnc_disable_counter(unsigned int cnt) |
| { |
| u32 val; |
| |
| if (cnt >= CNTMAX) { |
| printk(KERN_ERR "oprofile: CPU%u disabling wrong PMNC counter" |
| " %d\n", smp_processor_id(), cnt); |
| return -1; |
| } |
| |
| if (cnt == CCNT) |
| val = CNTENC_C; |
| else |
| val = (1 << (cnt - CNT0)); |
| |
| val &= CNTENC_MASK; |
| asm volatile("mcr p15, 0, %0, c9, c12, 2" : : "r" (val)); |
| |
| return cnt; |
| } |
| |
| static inline u32 armv7_pmnc_enable_intens(unsigned int cnt) |
| { |
| u32 val; |
| |
| if (cnt >= CNTMAX) { |
| printk(KERN_ERR "oprofile: CPU%u enabling wrong PMNC counter" |
| " interrupt enable %d\n", smp_processor_id(), cnt); |
| return -1; |
| } |
| |
| if (cnt == CCNT) |
| val = INTENS_C; |
| else |
| val = (1 << (cnt - CNT0)); |
| |
| val &= INTENS_MASK; |
| asm volatile("mcr p15, 0, %0, c9, c14, 1" : : "r" (val)); |
| |
| return cnt; |
| } |
| |
| static inline u32 armv7_pmnc_getreset_flags(void) |
| { |
| u32 val; |
| |
| /* Read */ |
| asm volatile("mrc p15, 0, %0, c9, c12, 3" : "=r" (val)); |
| |
| /* Write to clear flags */ |
| val &= FLAG_MASK; |
| asm volatile("mcr p15, 0, %0, c9, c12, 3" : : "r" (val)); |
| |
| return val; |
| } |
| |
| static inline int armv7_pmnc_select_counter(unsigned int cnt) |
| { |
| u32 val; |
| |
| if ((cnt == CCNT) || (cnt >= CNTMAX)) { |
| printk(KERN_ERR "oprofile: CPU%u selecting wrong PMNC counteri" |
| " %d\n", smp_processor_id(), cnt); |
| return -1; |
| } |
| |
| val = (cnt - CNT0) & SELECT_MASK; |
| asm volatile("mcr p15, 0, %0, c9, c12, 5" : : "r" (val)); |
| |
| return cnt; |
| } |
| |
| static inline void armv7_pmnc_write_evtsel(unsigned int cnt, u32 val) |
| { |
| if (armv7_pmnc_select_counter(cnt) == cnt) { |
| val &= EVTSEL_MASK; |
| asm volatile("mcr p15, 0, %0, c9, c13, 1" : : "r" (val)); |
| } |
| } |
| |
| static void armv7_pmnc_reset_counter(unsigned int cnt) |
| { |
| u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt); |
| u32 val = -(u32)counter_config[cpu_cnt].count; |
| |
| switch (cnt) { |
| case CCNT: |
| armv7_pmnc_disable_counter(cnt); |
| |
| asm volatile("mcr p15, 0, %0, c9, c13, 0" : : "r" (val)); |
| |
| if (cnt_en[cnt] != 0) |
| armv7_pmnc_enable_counter(cnt); |
| |
| break; |
| |
| case CNT0: |
| case CNT1: |
| case CNT2: |
| case CNT3: |
| armv7_pmnc_disable_counter(cnt); |
| |
| if (armv7_pmnc_select_counter(cnt) == cnt) |
| asm volatile("mcr p15, 0, %0, c9, c13, 2" : : "r" (val)); |
| |
| if (cnt_en[cnt] != 0) |
| armv7_pmnc_enable_counter(cnt); |
| |
| break; |
| |
| default: |
| printk(KERN_ERR "oprofile: CPU%u resetting wrong PMNC counter" |
| " %d\n", smp_processor_id(), cnt); |
| break; |
| } |
| } |
| |
| int armv7_setup_pmnc(void) |
| { |
| unsigned int cnt; |
| |
| if (armv7_pmnc_read() & PMNC_E) { |
| printk(KERN_ERR "oprofile: CPU%u PMNC still enabled when setup" |
| " new event counter.\n", smp_processor_id()); |
| return -EBUSY; |
| } |
| |
| /* Initialize & Reset PMNC: C bit and P bit */ |
| armv7_pmnc_write(PMNC_P | PMNC_C); |
| |
| |
| for (cnt = CCNT; cnt < CNTMAX; cnt++) { |
| unsigned long event; |
| u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt); |
| |
| /* |
| * Disable counter |
| */ |
| armv7_pmnc_disable_counter(cnt); |
| cnt_en[cnt] = 0; |
| |
| if (!counter_config[cpu_cnt].enabled) |
| continue; |
| |
| event = counter_config[cpu_cnt].event & 255; |
| |
| /* |
| * Set event (if destined for PMNx counters) |
| * We don't need to set the event if it's a cycle count |
| */ |
| if (cnt != CCNT) |
| armv7_pmnc_write_evtsel(cnt, event); |
| |
| /* |
| * Enable interrupt for this counter |
| */ |
| armv7_pmnc_enable_intens(cnt); |
| |
| /* |
| * Reset counter |
| */ |
| armv7_pmnc_reset_counter(cnt); |
| |
| /* |
| * Enable counter |
| */ |
| armv7_pmnc_enable_counter(cnt); |
| cnt_en[cnt] = 1; |
| } |
| |
| return 0; |
| } |
| |
| static inline void armv7_start_pmnc(void) |
| { |
| armv7_pmnc_write(armv7_pmnc_read() | PMNC_E); |
| } |
| |
| static inline void armv7_stop_pmnc(void) |
| { |
| armv7_pmnc_write(armv7_pmnc_read() & ~PMNC_E); |
| } |
| |
| /* |
| * CPU counters' IRQ handler (one IRQ per CPU) |
| */ |
| static irqreturn_t armv7_pmnc_interrupt(int irq, void *arg) |
| { |
| struct pt_regs *regs = get_irq_regs(); |
| unsigned int cnt; |
| u32 flags; |
| |
| |
| /* |
| * Stop IRQ generation |
| */ |
| armv7_stop_pmnc(); |
| |
| /* |
| * Get and reset overflow status flags |
| */ |
| flags = armv7_pmnc_getreset_flags(); |
| |
| /* |
| * Cycle counter |
| */ |
| if (flags & FLAG_C) { |
| u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), CCNT); |
| armv7_pmnc_reset_counter(CCNT); |
| oprofile_add_sample(regs, cpu_cnt); |
| } |
| |
| /* |
| * PMNC counters 0:3 |
| */ |
| for (cnt = CNT0; cnt < CNTMAX; cnt++) { |
| if (flags & (1 << (cnt - CNT0))) { |
| u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt); |
| armv7_pmnc_reset_counter(cnt); |
| oprofile_add_sample(regs, cpu_cnt); |
| } |
| } |
| |
| /* |
| * Allow IRQ generation |
| */ |
| armv7_start_pmnc(); |
| |
| return IRQ_HANDLED; |
| } |
| |
| int armv7_request_interrupts(int *irqs, int nr) |
| { |
| unsigned int i; |
| int ret = 0; |
| |
| for (i = 0; i < nr; i++) { |
| ret = request_irq(irqs[i], armv7_pmnc_interrupt, |
| IRQF_DISABLED, "CP15 PMNC", NULL); |
| if (ret != 0) { |
| printk(KERN_ERR "oprofile: unable to request IRQ%u" |
| " for ARMv7\n", |
| irqs[i]); |
| break; |
| } |
| } |
| |
| if (i != nr) |
| while (i-- != 0) |
| free_irq(irqs[i], NULL); |
| |
| return ret; |
| } |
| |
| void armv7_release_interrupts(int *irqs, int nr) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < nr; i++) |
| free_irq(irqs[i], NULL); |
| } |
| |
| #ifdef DEBUG |
| static void armv7_pmnc_dump_regs(void) |
| { |
| u32 val; |
| unsigned int cnt; |
| |
| printk(KERN_INFO "PMNC registers dump:\n"); |
| |
| asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (val)); |
| printk(KERN_INFO "PMNC =0x%08x\n", val); |
| |
| asm volatile("mrc p15, 0, %0, c9, c12, 1" : "=r" (val)); |
| printk(KERN_INFO "CNTENS=0x%08x\n", val); |
| |
| asm volatile("mrc p15, 0, %0, c9, c14, 1" : "=r" (val)); |
| printk(KERN_INFO "INTENS=0x%08x\n", val); |
| |
| asm volatile("mrc p15, 0, %0, c9, c12, 3" : "=r" (val)); |
| printk(KERN_INFO "FLAGS =0x%08x\n", val); |
| |
| asm volatile("mrc p15, 0, %0, c9, c12, 5" : "=r" (val)); |
| printk(KERN_INFO "SELECT=0x%08x\n", val); |
| |
| asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r" (val)); |
| printk(KERN_INFO "CCNT =0x%08x\n", val); |
| |
| for (cnt = CNT0; cnt < CNTMAX; cnt++) { |
| armv7_pmnc_select_counter(cnt); |
| asm volatile("mrc p15, 0, %0, c9, c13, 2" : "=r" (val)); |
| printk(KERN_INFO "CNT[%d] count =0x%08x\n", cnt-CNT0, val); |
| asm volatile("mrc p15, 0, %0, c9, c13, 1" : "=r" (val)); |
| printk(KERN_INFO "CNT[%d] evtsel=0x%08x\n", cnt-CNT0, val); |
| } |
| } |
| #endif |
| |
| |
| static int irqs[] = { |
| #ifdef CONFIG_ARCH_OMAP3 |
| INT_34XX_BENCH_MPU_EMUL, |
| #endif |
| }; |
| |
| static void armv7_pmnc_stop(void) |
| { |
| #ifdef DEBUG |
| armv7_pmnc_dump_regs(); |
| #endif |
| armv7_stop_pmnc(); |
| armv7_release_interrupts(irqs, ARRAY_SIZE(irqs)); |
| } |
| |
| static int armv7_pmnc_start(void) |
| { |
| int ret; |
| |
| #ifdef DEBUG |
| armv7_pmnc_dump_regs(); |
| #endif |
| ret = armv7_request_interrupts(irqs, ARRAY_SIZE(irqs)); |
| if (ret >= 0) |
| armv7_start_pmnc(); |
| |
| return ret; |
| } |
| |
| static int armv7_detect_pmnc(void) |
| { |
| return 0; |
| } |
| |
| struct op_arm_model_spec op_armv7_spec = { |
| .init = armv7_detect_pmnc, |
| .num_counters = 5, |
| .setup_ctrs = armv7_setup_pmnc, |
| .start = armv7_pmnc_start, |
| .stop = armv7_pmnc_stop, |
| .name = "arm/armv7", |
| }; |