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/