Add PMU interrupt-handling support for Cell. Currently this only processes
the counter-overflow interrupts. Interval timer and trace buffer interrupts
are currently ignored.

Signed-off-by: Kevin Corry <[EMAIL PROTECTED]>
Signed-off-by: Carl Love <[EMAIL PROTECTED]>

Index: linux-2.6.21-perfmon1/arch/powerpc/perfmon/perfmon.c
===================================================================
--- linux-2.6.21-perfmon1.orig/arch/powerpc/perfmon/perfmon.c
+++ linux-2.6.21-perfmon1/arch/powerpc/perfmon/perfmon.c
@@ -25,55 +25,19 @@
 #include <linux/interrupt.h>
 #include <linux/perfmon.h>
 
-/*
- * collect pending overflowed PMDs. Called from pfm_ctxsw()
- * and from PMU interrupt handler. Must fill in set->povfl_pmds[]
- * and set->npend_ovfls. Interrupts are masked
- */
-static void __pfm_get_ovfl_pmds(struct pfm_context *ctx,
-                               struct pfm_event_set *set)
-{
-       u64 new_val, wmask;
-       u64 *used_mask, *cnt_pmds;
-       u64 mask[PFM_PMD_BV];
-       unsigned int i, max;
-
-       max = pfm_pmu_conf->max_cnt_pmd;
-       used_mask = set->used_pmds;
-       cnt_pmds = pfm_pmu_conf->cnt_pmds;
-       wmask = 1ULL << pfm_pmu_conf->counter_width;
-       bitmap_and(ulp(mask), ulp(cnt_pmds), ulp(used_mask), max);
-
-       for (i = 0; i < max; i++) {
-               /* assume all PMD are counters */
-               if (test_bit(i, mask)) {
-                       new_val = pfm_arch_read_pmd(ctx, i);
-
-                       PFM_DBG_ovfl("pmd%u new_val=0x%llx bit=%d",
-                                    i, (unsigned long long)new_val,
-                                    (new_val & wmask) ? 1 : 0);
-
-                       if (!(new_val & wmask)) {
-                               __set_bit(i, set->povfl_pmds);
-                               set->npend_ovfls++;
-                       }
-               }
-       }
-}
-
 static void pfm_stop_active(struct task_struct *task,
                            struct pfm_context *ctx, struct pfm_event_set *set)
 {
        struct pfm_arch_pmu_info *arch_info = pfm_pmu_conf->arch_info;
 
-       BUG_ON(!arch_info->disable_counters);
+       BUG_ON(!arch_info->disable_counters || !arch_info->get_ovfl_pmds);
 
        arch_info->disable_counters(set);
 
        if (set->npend_ovfls)
                return;
 
-       __pfm_get_ovfl_pmds(ctx, set);
+       arch_info->get_ovfl_pmds(ctx, set);
 }
 
 /*
@@ -321,3 +285,21 @@ void pfm_arch_init_percpu(void)
        ppc64_enable_pmcs();
 #endif
 }
+
+/**
+ * powerpc_irq_handler
+ *
+ * Get the perfmon context that belongs to the current CPU, and call the
+ * model-specific interrupt handler.
+ **/
+void powerpc_irq_handler(struct pt_regs *regs)
+{
+       struct pfm_arch_pmu_info *arch_info = pfm_pmu_conf->arch_info;
+       struct pfm_context *ctx;
+
+       if (arch_info->irq_handler) {
+               ctx = __get_cpu_var(pmu_ctx);
+               if (likely(ctx))
+                       arch_info->irq_handler(regs, ctx);
+       }
+}
Index: linux-2.6.21-perfmon1/arch/powerpc/perfmon/perfmon_cell.c
===================================================================
--- linux-2.6.21-perfmon1.orig/arch/powerpc/perfmon/perfmon_cell.c
+++ linux-2.6.21-perfmon1/arch/powerpc/perfmon/perfmon_cell.c
@@ -76,6 +76,7 @@ static struct pfm_reg_desc pfm_cell_pmc_
 };
 #define PFM_PM_NUM_PMCS        ARRAY_SIZE(pfm_cell_pmc_desc)
 
+#define CELL_PMC_PM_STATUS 20
 /*
  * Mapping from Perfmon logical data counters to Cell hardware counters.
  */
@@ -238,6 +239,14 @@ static void pfm_cell_write_pmc(unsigned 
        } else if (cnum < NR_CTRS * 2) {
                write_pm07_event(cpu, cnum - NR_CTRS, value);
 
+       } else if (cnum == CELL_PMC_PM_STATUS) {
+               /* The pm_status register must be treated separately from
+                * the other "global" PMCs. This call will ensure that
+                * the interrupts are routed to the correct CPU, as well
+                * as writing the desired value to the pm_status register.
+                */
+               cbe_enable_pm_interrupts(cpu, cbe_get_hw_thread_id(cpu), value);
+
        } else if (cnum < PFM_PM_NUM_PMCS) {
                cbe_write_pm(cpu, cnum - (NR_CTRS * 2), value);
        }
@@ -347,6 +356,146 @@ void pfm_cell_restore_pmcs(struct pfm_ev
                pfm_cell_write_pmc(i, set->pmcs[i]);
 }
 
+/**
+ * pfm_cell_get_ovfl_pmds
+ *
+ * Determine which counters in this set have overflowed and fill in the
+ * set->povfl_pmds mask and set->npend_ovfls count. On Cell, the pm_status
+ * register contains a bit for each counter to indicate overflow. However,
+ * those 8 bits are in the reverse order than what Perfmon2 is expecting,
+ * so we need to reverse the order of the overflow bits.
+ **/
+static void pfm_cell_get_ovfl_pmds(struct pfm_context *ctx,
+                                  struct pfm_event_set *set)
+{
+       struct pfm_arch_context *ctx_arch = pfm_ctx_arch(ctx);
+       u32 pm_status, ovfl_ctrs;
+       u64 povfl_pmds = 0;
+       int i;
+
+       if (!ctx_arch->last_read_updated)
+               /* This routine was not called via the interrupt handler.
+                * Need to start by getting interrupts and updating
+                * last_read_pm_status.
+                */
+               ctx_arch->last_read_pm_status =
+                       cbe_get_and_clear_pm_interrupts(smp_processor_id());
+
+       /* Reset the flag that the interrupt handler last read pm_status. */
+       ctx_arch->last_read_updated = 0;
+
+       pm_status = ctx_arch->last_read_pm_status &
+                   set->pmcs[CELL_PMC_PM_STATUS];
+       ovfl_ctrs = CBE_PM_OVERFLOW_CTRS(pm_status);
+
+       /* Reverse the order of the bits in ovfl_ctrs
+        * and store the result in povfl_pmds.
+        */
+       for (i = 0; i < PFM_PM_NUM_PMDS; i++) {
+               povfl_pmds = (povfl_pmds << 1) | (ovfl_ctrs & 1);
+               ovfl_ctrs >>= 1;
+       }
+
+       /* Mask povfl_pmds with set->used_pmds to get set->povfl_pmds.
+        * Count the bits set in set->povfl_pmds to get set->npend_ovfls.
+        */
+       bitmap_and(set->povfl_pmds, &povfl_pmds,
+                  set->used_pmds, PFM_PM_NUM_PMDS);
+       set->npend_ovfls = bitmap_weight(set->povfl_pmds, PFM_PM_NUM_PMDS);
+}
+
+/**
+ * handle_trace_buffer_interrupts
+ *
+ * This routine is for processing just the interval timer and trace buffer
+ * overflow interrupts. Performance counter interrupts are handled by the
+ * perf_irq_handler() routine, which reads and saves the pm_status register.
+ * This routine should not read the actual pm_status register, but rather
+ * the value passed in.
+ *
+ * FIX: We don't necessarily need all these parameters.
+ **/
+static void handle_trace_buffer_interrupts(unsigned long iip,
+                                          struct pt_regs *regs,
+                                          struct pfm_context *ctx,
+                                          struct pfm_arch_context *ctx_arch,
+                                          u32 pm_status)
+{
+       /* FIX: Currently ignoring trace-buffer interrupts. */
+       return;
+}
+
+/**
+ * pfm_cell_irq_handler
+ *
+ * Handler for all Cell performance-monitor interrupts.
+ **/
+static void pfm_cell_irq_handler(struct pt_regs *regs, struct pfm_context *ctx)
+{
+       struct pfm_arch_context *ctx_arch = pfm_ctx_arch(ctx);
+       u32 last_read_pm_status;
+       int cpu = smp_processor_id();
+
+       /* Need to disable and reenable the performance counters to get the
+        * desired behavior from the hardware. This is specific to the Cell
+        * PMU hardware.
+        */
+       cbe_disable_pm(cpu);
+
+       /* Read the pm_status register to get the interrupt bits. If a
+        * perfmormance counter interrupt occurred, call the core perfmon
+        * interrupt handler to service the counter overflow. If the
+        * interrupt was for the interval timer or the trace_buffer,
+        * call the interval timer and trace buffer interrupt handler.
+        *
+        * The value read from the pm_status register is stored in the
+        * pmf_arch_context structure for use by other routines. Note that
+        * reading the pm_status register resets the interrupt flags to zero.
+        * Hence, it is important that the register is only read in one place.
+        *
+        * The pm_status reg interrupt reg format is:
+        * [pmd0:pmd1:pmd2:pmd3:pmd4:pmd5:pmd6:pmd7:intt:tbf:tbu:]
+        * - pmd0 to pm7 are the perf counter overflow interrupts.
+        * - intt is the interval timer overflowed interrupt.
+        * - tbf is the trace buffer full interrupt.
+        * - tbu is the trace buffer underflow interrupt.
+        * - The pmd0 bit is the MSB of the 32 bit register.
+        */
+       ctx_arch->last_read_pm_status = last_read_pm_status =
+                                       cbe_get_and_clear_pm_interrupts(cpu);
+
+       /* Set flag for pfm_cell_get_ovfl_pmds() routine so it knows
+        * last_read_pm_status was updated by the interrupt handler.
+        */
+       ctx_arch->last_read_updated = 1;
+
+       if (last_read_pm_status & 0xFF000000)
+               /* At least one counter overflowed. */
+               pfm_interrupt_handler(instruction_pointer(regs), regs);
+
+       if (last_read_pm_status & 0x00E00000)
+               /* Trace buffer or interval timer overflow. */
+               handle_trace_buffer_interrupts(instruction_pointer(regs),
+                                              regs, ctx, ctx_arch,
+                                              last_read_pm_status);
+
+       /* The interrupt settings is the value written to the pm_status
+        * register. It is saved in the context when the register is
+        * written.
+        */
+       cbe_enable_pm_interrupts(cpu, cbe_get_hw_thread_id(cpu),
+                                ctx->active_set->pmcs[CELL_PMC_PM_STATUS]);
+
+       /* The writes to the various performance counters only writes to a
+        * latch. The new values (interrupt setting bits, reset counter value
+        * etc.) are not copied to the actual registers until the performance
+        * monitor is enabled. In order to get this to work as desired, the
+        * permormance monitor needs to be disabled while writting to the
+        * latches. This is a HW design issue.
+        */
+       cbe_enable_pm(cpu);
+}
+
 static struct pfm_arch_pmu_info pfm_cell_pmu_info = {
        .pmu_style        = PFM_POWERPC_PMU_CELL,
        .write_pmc        = pfm_cell_write_pmc,
@@ -354,6 +503,8 @@ static struct pfm_arch_pmu_info pfm_cell
        .read_pmd         = pfm_cell_read_pmd,
        .enable_counters  = pfm_cell_enable_counters,
        .disable_counters = pfm_cell_disable_counters,
+       .irq_handler      = pfm_cell_irq_handler,
+       .get_ovfl_pmds    = pfm_cell_get_ovfl_pmds,
        .restore_pmcs     = pfm_cell_restore_pmcs,
 };
 
Index: linux-2.6.21-perfmon1/arch/powerpc/perfmon/perfmon_power5.c
===================================================================
--- linux-2.6.21-perfmon1.orig/arch/powerpc/perfmon/perfmon_power5.c
+++ linux-2.6.21-perfmon1/arch/powerpc/perfmon/perfmon_power5.c
@@ -178,11 +178,83 @@ static void pfm_power5_disable_counters(
                        pfm_power5_write_pmc(i, 0);
 }
 
+/**
+ * pfm_power5_get_ovfl_pmds
+ *
+ * Determine which counters in this set have overflowed and fill in the
+ * set->povfl_pmds mask and set->npend_ovfls count.
+ **/
+static void pfm_power5_get_ovfl_pmds(struct pfm_context *ctx,
+                                    struct pfm_event_set *set)
+{
+       unsigned int i;
+       unsigned int max_pmd = pfm_pmu_conf->max_cnt_pmd;
+       u64 *used_pmds = set->used_pmds;
+       u64 *cntr_pmds = pfm_pmu_conf->cnt_pmds;
+       u64 width_mask = 1 << pfm_pmu_conf->counter_width;
+       u64 new_val, mask[PFM_PMD_BV];
+
+       bitmap_and(ulp(mask), ulp(cntr_pmds), ulp(used_pmds), max_pmd);
+
+       for (i = 0; i < max_pmd; i++) {
+               if (test_bit(i, mask)) {
+                       new_val = pfm_power5_read_pmd(i);
+                       if (new_val & width_mask) {
+                               set_bit(i, set->povfl_pmds);
+                               set->npend_ovfls++;
+                       }
+               }
+       }
+}
+
+static void pfm_power5_irq_handler(struct pt_regs *regs,
+                                  struct pfm_context *ctx)
+{
+       u32 mmcr0;
+       u64 mmcra;
+
+       /* Disable the counters (set the freeze bit) to not polute
+        * the counts.
+        */
+       mmcr0 = mfspr(SPRN_MMCR0);
+       mtspr(SPRN_MMCR0, (mmcr0 | MMCR0_FC));
+       mmcra = mfspr(SPRN_MMCRA);
+
+       /* Set the PMM bit (see comment below). */
+       mtmsrd(mfmsr() | MSR_PMM);
+
+       pfm_interrupt_handler(instruction_pointer(regs), regs);
+
+       mmcr0 = mfspr(SPRN_MMCR0);
+       /* Reset the perfmon trigger. */
+       mmcr0 |= MMCR0_PMXE;
+
+       /*
+        * We must clear the PMAO bit on some (GQ) chips. Just do it
+        * all the time.
+        */
+       mmcr0 &= ~MMCR0_PMAO;
+
+       /* Clear the appropriate bits in the MMCRA. */
+       mmcra &= POWER6_MMCRA_THRM | POWER6_MMCRA_OTHER;
+       mtspr(SPRN_MMCRA, mmcra);
+
+       /*
+        * Now clear the freeze bit, counting will not start until we
+        * rfid from this exception, because only at that point will
+        * the PMM bit be cleared.
+        */
+       mmcr0 &= ~MMCR0_FC;
+       mtspr(SPRN_MMCR0, mmcr0);
+}
+
 struct pfm_arch_pmu_info pfm_power5_pmu_info = {
        .pmu_style        = PFM_POWERPC_PMU_POWER5,
        .write_pmc        = pfm_power5_write_pmc,
        .write_pmd        = pfm_power5_write_pmd,
        .read_pmd         = pfm_power5_read_pmd,
+       .irq_handler      = pfm_power5_irq_handler,
+       .get_ovfl_pmds    = pfm_power5_get_ovfl_pmds,
        .enable_counters  = pfm_power5_enable_counters,
        .disable_counters = pfm_power5_disable_counters,
 };
Index: linux-2.6.21-perfmon1/arch/powerpc/perfmon/perfmon_ppc32.c
===================================================================
--- linux-2.6.21-perfmon1.orig/arch/powerpc/perfmon/perfmon_ppc32.c
+++ linux-2.6.21-perfmon1/arch/powerpc/perfmon/perfmon_ppc32.c
@@ -272,11 +272,41 @@ static void pfm_ppc32_disable_counters(s
                        pfm_ppc32_write_pmc(ctx, 0);
 }
 
+/**
+ * pfm_ppc32_get_ovfl_pmds
+ *
+ * Determine which counters in this set have overflowed and fill in the
+ * set->povfl_pmds mask and set->npend_ovfls count.
+ **/
+static void pfm_ppc32_get_ovfl_pmds(struct pfm_context *ctx,
+                                   struct pfm_event_set *set)
+{
+       unsigned int i;
+       unsigned int max_pmd = pfm_pmu_conf->max_cnt_pmd;
+       u64 *used_pmds = set->used_pmds;
+       u64 *cntr_pmds = pfm_pmu_conf->cnt_pmds;
+       u64 width_mask = 1 << pfm_pmu_conf->counter_width;
+       u64 new_val, mask[PFM_PMD_BV];
+
+       bitmap_and(ulp(mask), ulp(cntr_pmds), ulp(used_pmds), max_pmd);
+
+       for (i = 0; i < max_pmd; i++) {
+               if (test_bit(i, mask)) {
+                       new_val = pfm_ppc32_read_pmd(i);
+                       if (new_val & width_mask) {
+                               set_bit(i, set->povfl_pmds);
+                               set->npend_ovfls++;
+                       }
+               }
+       }
+}
+
 struct pfm_arch_pmu_info pfm_ppc32_pmu_info = {
        .pmu_style        = PFM_POWERPC_PMU_NONE,
        .write_pmc        = pfm_ppc32_write_pmc,
        .write_pmd        = pfm_ppc32_write_pmd,
        .read_pmd         = pfm_ppc32_read_pmd,
+       .get_ovfl_pmds    = pfm_ppc32_get_ovfl_pmds,
        .enable_counters  = pfm_ppc32_enable_counters,
        .disable_counters = pfm_ppc32_disable_counters,
 };
Index: linux-2.6.21-perfmon1/include/asm-powerpc/cell-pmu.h
===================================================================
--- linux-2.6.21-perfmon1.orig/include/asm-powerpc/cell-pmu.h
+++ linux-2.6.21-perfmon1/include/asm-powerpc/cell-pmu.h
@@ -61,6 +61,7 @@
 
 /* Macros for the pm_status register. */
 #define CBE_PM_CTR_OVERFLOW_INTR(ctr)      (1 << (31 - ((ctr) & 7)))
+#define CBE_PM_OVERFLOW_CTRS(pm_status)    (((pm_status) >> 24) & 0xff)
 
 enum pm_reg_name {
        group_control,
Index: linux-2.6.21-perfmon1/include/asm-powerpc/perfmon.h
===================================================================
--- linux-2.6.21-perfmon1.orig/include/asm-powerpc/perfmon.h
+++ linux-2.6.21-perfmon1/include/asm-powerpc/perfmon.h
@@ -53,6 +53,10 @@ struct pfm_arch_pmu_info {
        void (*enable_counters)(struct pfm_event_set *set);
        void (*disable_counters)(struct pfm_event_set *set);
 
+       void (*irq_handler)(struct pt_regs *regs, struct pfm_context *ctx);
+       void (*get_ovfl_pmds)(struct pfm_context *ctx,
+                             struct pfm_event_set *set);
+
        /* These two are optional. */
        void (*restore_pmcs)(struct pfm_event_set *set);
        void (*restore_pmds)(struct pfm_event_set *set);
@@ -168,6 +172,8 @@ void pfm_arch_intr_freeze_pmu(struct pfm
 void pfm_arch_intr_unfreeze_pmu(struct pfm_context *ctx);
 char *pfm_arch_get_pmu_module_name(void);
 
+void powerpc_irq_handler(struct pt_regs *regs);
+
 /*
  * this function is called from the PMU interrupt handler ONLY.
  * On PPC, the PMU is frozen via arch_stop, masking would be implemented
@@ -192,11 +198,13 @@ static inline void pfm_arch_unmask_monit
 
 static inline int pfm_arch_pmu_config_init(struct _pfm_pmu_config *cfg)
 {
-       return 0;
+       return reserve_pmc_hardware(powerpc_irq_handler);
 }
 
 static inline void pfm_arch_pmu_config_remove(void)
-{}
+{
+       release_pmc_hardware();
+}
 
 static inline int pfm_arch_context_initialize(struct pfm_context *ctx,
                                               u32 ctx_flags)
@@ -252,7 +260,14 @@ static inline void pfm_arch_idle_notifie
 {}
 
 struct pfm_arch_context {
-       /* empty */
+       /* Cell: Most recent value of the pm_status
+        * register read by the interrupt handler.
+        *
+        * Interrupt handler sets last_read_updated if it
+        * just read and updated last_read_pm_status
+        */
+       u32 last_read_pm_status;
+       u32 last_read_updated;
 };
 
 #define PFM_ARCH_CTX_SIZE sizeof(struct pfm_arch_context)
_______________________________________________
perfmon mailing list
[email protected]
http://www.hpl.hp.com/hosted/linux/mail-archives/perfmon/

Reply via email to