Logo Search packages:      
Sourcecode: linux version File versions  Download package

op_model_v7.c

/**
 * 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, D bit and P bit.
       *  Note: Using a slower count for CCNT (D bit: divide by 64) results
       *   in a more stable system
       */
      armv7_pmnc_write(PMNC_P | PMNC_C | PMNC_D);


      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",
};

Generated by  Doxygen 1.6.0   Back to index