|
@@ -12,68 +12,15 @@
|
|
|
*/
|
|
|
#define pr_fmt(fmt) "hw perfevents: " fmt
|
|
|
|
|
|
-#include <linux/bitmap.h>
|
|
|
-#include <linux/interrupt.h>
|
|
|
#include <linux/kernel.h>
|
|
|
-#include <linux/export.h>
|
|
|
-#include <linux/perf_event.h>
|
|
|
#include <linux/platform_device.h>
|
|
|
-#include <linux/spinlock.h>
|
|
|
+#include <linux/pm_runtime.h>
|
|
|
#include <linux/uaccess.h>
|
|
|
|
|
|
-#include <asm/cputype.h>
|
|
|
-#include <asm/irq.h>
|
|
|
#include <asm/irq_regs.h>
|
|
|
#include <asm/pmu.h>
|
|
|
#include <asm/stacktrace.h>
|
|
|
|
|
|
-/*
|
|
|
- * ARMv6 supports a maximum of 3 events, starting from index 0. If we add
|
|
|
- * another platform that supports more, we need to increase this to be the
|
|
|
- * largest of all platforms.
|
|
|
- *
|
|
|
- * ARMv7 supports up to 32 events:
|
|
|
- * cycle counter CCNT + 31 events counters CNT0..30.
|
|
|
- * Cortex-A8 has 1+4 counters, Cortex-A9 has 1+6 counters.
|
|
|
- */
|
|
|
-#define ARMPMU_MAX_HWEVENTS 32
|
|
|
-
|
|
|
-static DEFINE_PER_CPU(struct perf_event * [ARMPMU_MAX_HWEVENTS], hw_events);
|
|
|
-static DEFINE_PER_CPU(unsigned long [BITS_TO_LONGS(ARMPMU_MAX_HWEVENTS)], used_mask);
|
|
|
-static DEFINE_PER_CPU(struct pmu_hw_events, cpu_hw_events);
|
|
|
-
|
|
|
-#define to_arm_pmu(p) (container_of(p, struct arm_pmu, pmu))
|
|
|
-
|
|
|
-/* Set at runtime when we know what CPU type we are. */
|
|
|
-static struct arm_pmu *cpu_pmu;
|
|
|
-
|
|
|
-const char *perf_pmu_name(void)
|
|
|
-{
|
|
|
- if (!cpu_pmu)
|
|
|
- return NULL;
|
|
|
-
|
|
|
- return cpu_pmu->pmu.name;
|
|
|
-}
|
|
|
-EXPORT_SYMBOL_GPL(perf_pmu_name);
|
|
|
-
|
|
|
-int perf_num_counters(void)
|
|
|
-{
|
|
|
- int max_events = 0;
|
|
|
-
|
|
|
- if (cpu_pmu != NULL)
|
|
|
- max_events = cpu_pmu->num_events;
|
|
|
-
|
|
|
- return max_events;
|
|
|
-}
|
|
|
-EXPORT_SYMBOL_GPL(perf_num_counters);
|
|
|
-
|
|
|
-#define HW_OP_UNSUPPORTED 0xFFFF
|
|
|
-
|
|
|
-#define C(_x) \
|
|
|
- PERF_COUNT_HW_CACHE_##_x
|
|
|
-
|
|
|
-#define CACHE_OP_UNSUPPORTED 0xFFFF
|
|
|
-
|
|
|
static int
|
|
|
armpmu_map_cache_event(const unsigned (*cache_map)
|
|
|
[PERF_COUNT_HW_CACHE_MAX]
|
|
@@ -104,7 +51,7 @@ armpmu_map_cache_event(const unsigned (*cache_map)
|
|
|
}
|
|
|
|
|
|
static int
|
|
|
-armpmu_map_event(const unsigned (*event_map)[PERF_COUNT_HW_MAX], u64 config)
|
|
|
+armpmu_map_hw_event(const unsigned (*event_map)[PERF_COUNT_HW_MAX], u64 config)
|
|
|
{
|
|
|
int mapping = (*event_map)[config];
|
|
|
return mapping == HW_OP_UNSUPPORTED ? -ENOENT : mapping;
|
|
@@ -116,19 +63,20 @@ armpmu_map_raw_event(u32 raw_event_mask, u64 config)
|
|
|
return (int)(config & raw_event_mask);
|
|
|
}
|
|
|
|
|
|
-static int map_cpu_event(struct perf_event *event,
|
|
|
- const unsigned (*event_map)[PERF_COUNT_HW_MAX],
|
|
|
- const unsigned (*cache_map)
|
|
|
- [PERF_COUNT_HW_CACHE_MAX]
|
|
|
- [PERF_COUNT_HW_CACHE_OP_MAX]
|
|
|
- [PERF_COUNT_HW_CACHE_RESULT_MAX],
|
|
|
- u32 raw_event_mask)
|
|
|
+int
|
|
|
+armpmu_map_event(struct perf_event *event,
|
|
|
+ const unsigned (*event_map)[PERF_COUNT_HW_MAX],
|
|
|
+ const unsigned (*cache_map)
|
|
|
+ [PERF_COUNT_HW_CACHE_MAX]
|
|
|
+ [PERF_COUNT_HW_CACHE_OP_MAX]
|
|
|
+ [PERF_COUNT_HW_CACHE_RESULT_MAX],
|
|
|
+ u32 raw_event_mask)
|
|
|
{
|
|
|
u64 config = event->attr.config;
|
|
|
|
|
|
switch (event->attr.type) {
|
|
|
case PERF_TYPE_HARDWARE:
|
|
|
- return armpmu_map_event(event_map, config);
|
|
|
+ return armpmu_map_hw_event(event_map, config);
|
|
|
case PERF_TYPE_HW_CACHE:
|
|
|
return armpmu_map_cache_event(cache_map, config);
|
|
|
case PERF_TYPE_RAW:
|
|
@@ -222,7 +170,6 @@ armpmu_stop(struct perf_event *event, int flags)
|
|
|
*/
|
|
|
if (!(hwc->state & PERF_HES_STOPPED)) {
|
|
|
armpmu->disable(hwc, hwc->idx);
|
|
|
- barrier(); /* why? */
|
|
|
armpmu_event_update(event, hwc, hwc->idx);
|
|
|
hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
|
|
|
}
|
|
@@ -350,99 +297,41 @@ validate_group(struct perf_event *event)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static irqreturn_t armpmu_platform_irq(int irq, void *dev)
|
|
|
+static irqreturn_t armpmu_dispatch_irq(int irq, void *dev)
|
|
|
{
|
|
|
struct arm_pmu *armpmu = (struct arm_pmu *) dev;
|
|
|
struct platform_device *plat_device = armpmu->plat_device;
|
|
|
struct arm_pmu_platdata *plat = dev_get_platdata(&plat_device->dev);
|
|
|
|
|
|
- return plat->handle_irq(irq, dev, armpmu->handle_irq);
|
|
|
+ if (plat && plat->handle_irq)
|
|
|
+ return plat->handle_irq(irq, dev, armpmu->handle_irq);
|
|
|
+ else
|
|
|
+ return armpmu->handle_irq(irq, dev);
|
|
|
}
|
|
|
|
|
|
static void
|
|
|
armpmu_release_hardware(struct arm_pmu *armpmu)
|
|
|
{
|
|
|
- int i, irq, irqs;
|
|
|
- struct platform_device *pmu_device = armpmu->plat_device;
|
|
|
- struct arm_pmu_platdata *plat =
|
|
|
- dev_get_platdata(&pmu_device->dev);
|
|
|
-
|
|
|
- irqs = min(pmu_device->num_resources, num_possible_cpus());
|
|
|
-
|
|
|
- for (i = 0; i < irqs; ++i) {
|
|
|
- if (!cpumask_test_and_clear_cpu(i, &armpmu->active_irqs))
|
|
|
- continue;
|
|
|
- irq = platform_get_irq(pmu_device, i);
|
|
|
- if (irq >= 0) {
|
|
|
- if (plat && plat->disable_irq)
|
|
|
- plat->disable_irq(irq);
|
|
|
- free_irq(irq, armpmu);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- release_pmu(armpmu->type);
|
|
|
+ armpmu->free_irq();
|
|
|
+ pm_runtime_put_sync(&armpmu->plat_device->dev);
|
|
|
}
|
|
|
|
|
|
static int
|
|
|
armpmu_reserve_hardware(struct arm_pmu *armpmu)
|
|
|
{
|
|
|
- struct arm_pmu_platdata *plat;
|
|
|
- irq_handler_t handle_irq;
|
|
|
- int i, err, irq, irqs;
|
|
|
+ int err;
|
|
|
struct platform_device *pmu_device = armpmu->plat_device;
|
|
|
|
|
|
if (!pmu_device)
|
|
|
return -ENODEV;
|
|
|
|
|
|
- err = reserve_pmu(armpmu->type);
|
|
|
+ pm_runtime_get_sync(&pmu_device->dev);
|
|
|
+ err = armpmu->request_irq(armpmu_dispatch_irq);
|
|
|
if (err) {
|
|
|
- pr_warning("unable to reserve pmu\n");
|
|
|
+ armpmu_release_hardware(armpmu);
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
- plat = dev_get_platdata(&pmu_device->dev);
|
|
|
- if (plat && plat->handle_irq)
|
|
|
- handle_irq = armpmu_platform_irq;
|
|
|
- else
|
|
|
- handle_irq = armpmu->handle_irq;
|
|
|
-
|
|
|
- irqs = min(pmu_device->num_resources, num_possible_cpus());
|
|
|
- if (irqs < 1) {
|
|
|
- pr_err("no irqs for PMUs defined\n");
|
|
|
- return -ENODEV;
|
|
|
- }
|
|
|
-
|
|
|
- for (i = 0; i < irqs; ++i) {
|
|
|
- err = 0;
|
|
|
- irq = platform_get_irq(pmu_device, i);
|
|
|
- if (irq < 0)
|
|
|
- continue;
|
|
|
-
|
|
|
- /*
|
|
|
- * If we have a single PMU interrupt that we can't shift,
|
|
|
- * assume that we're running on a uniprocessor machine and
|
|
|
- * continue. Otherwise, continue without this interrupt.
|
|
|
- */
|
|
|
- if (irq_set_affinity(irq, cpumask_of(i)) && irqs > 1) {
|
|
|
- pr_warning("unable to set irq affinity (irq=%d, cpu=%u)\n",
|
|
|
- irq, i);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- err = request_irq(irq, handle_irq,
|
|
|
- IRQF_DISABLED | IRQF_NOBALANCING,
|
|
|
- "arm-pmu", armpmu);
|
|
|
- if (err) {
|
|
|
- pr_err("unable to request IRQ%d for ARM PMU counters\n",
|
|
|
- irq);
|
|
|
- armpmu_release_hardware(armpmu);
|
|
|
- return err;
|
|
|
- } else if (plat && plat->enable_irq)
|
|
|
- plat->enable_irq(irq);
|
|
|
-
|
|
|
- cpumask_set_cpu(i, &armpmu->active_irqs);
|
|
|
- }
|
|
|
-
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -581,6 +470,32 @@ static void armpmu_disable(struct pmu *pmu)
|
|
|
armpmu->stop();
|
|
|
}
|
|
|
|
|
|
+#ifdef CONFIG_PM_RUNTIME
|
|
|
+static int armpmu_runtime_resume(struct device *dev)
|
|
|
+{
|
|
|
+ struct arm_pmu_platdata *plat = dev_get_platdata(dev);
|
|
|
+
|
|
|
+ if (plat && plat->runtime_resume)
|
|
|
+ return plat->runtime_resume(dev);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int armpmu_runtime_suspend(struct device *dev)
|
|
|
+{
|
|
|
+ struct arm_pmu_platdata *plat = dev_get_platdata(dev);
|
|
|
+
|
|
|
+ if (plat && plat->runtime_suspend)
|
|
|
+ return plat->runtime_suspend(dev);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+const struct dev_pm_ops armpmu_dev_pm_ops = {
|
|
|
+ SET_RUNTIME_PM_OPS(armpmu_runtime_suspend, armpmu_runtime_resume, NULL)
|
|
|
+};
|
|
|
+
|
|
|
static void __init armpmu_init(struct arm_pmu *armpmu)
|
|
|
{
|
|
|
atomic_set(&armpmu->active_events, 0);
|
|
@@ -598,174 +513,14 @@ static void __init armpmu_init(struct arm_pmu *armpmu)
|
|
|
};
|
|
|
}
|
|
|
|
|
|
-int __init armpmu_register(struct arm_pmu *armpmu, char *name, int type)
|
|
|
+int armpmu_register(struct arm_pmu *armpmu, char *name, int type)
|
|
|
{
|
|
|
armpmu_init(armpmu);
|
|
|
+ pr_info("enabled with %s PMU driver, %d counters available\n",
|
|
|
+ armpmu->name, armpmu->num_events);
|
|
|
return perf_pmu_register(&armpmu->pmu, name, type);
|
|
|
}
|
|
|
|
|
|
-/* Include the PMU-specific implementations. */
|
|
|
-#include "perf_event_xscale.c"
|
|
|
-#include "perf_event_v6.c"
|
|
|
-#include "perf_event_v7.c"
|
|
|
-
|
|
|
-/*
|
|
|
- * Ensure the PMU has sane values out of reset.
|
|
|
- * This requires SMP to be available, so exists as a separate initcall.
|
|
|
- */
|
|
|
-static int __init
|
|
|
-cpu_pmu_reset(void)
|
|
|
-{
|
|
|
- if (cpu_pmu && cpu_pmu->reset)
|
|
|
- return on_each_cpu(cpu_pmu->reset, NULL, 1);
|
|
|
- return 0;
|
|
|
-}
|
|
|
-arch_initcall(cpu_pmu_reset);
|
|
|
-
|
|
|
-/*
|
|
|
- * PMU platform driver and devicetree bindings.
|
|
|
- */
|
|
|
-static struct of_device_id armpmu_of_device_ids[] = {
|
|
|
- {.compatible = "arm,cortex-a9-pmu"},
|
|
|
- {.compatible = "arm,cortex-a8-pmu"},
|
|
|
- {.compatible = "arm,arm1136-pmu"},
|
|
|
- {.compatible = "arm,arm1176-pmu"},
|
|
|
- {},
|
|
|
-};
|
|
|
-
|
|
|
-static struct platform_device_id armpmu_plat_device_ids[] = {
|
|
|
- {.name = "arm-pmu"},
|
|
|
- {},
|
|
|
-};
|
|
|
-
|
|
|
-static int __devinit armpmu_device_probe(struct platform_device *pdev)
|
|
|
-{
|
|
|
- if (!cpu_pmu)
|
|
|
- return -ENODEV;
|
|
|
-
|
|
|
- cpu_pmu->plat_device = pdev;
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-static struct platform_driver armpmu_driver = {
|
|
|
- .driver = {
|
|
|
- .name = "arm-pmu",
|
|
|
- .of_match_table = armpmu_of_device_ids,
|
|
|
- },
|
|
|
- .probe = armpmu_device_probe,
|
|
|
- .id_table = armpmu_plat_device_ids,
|
|
|
-};
|
|
|
-
|
|
|
-static int __init register_pmu_driver(void)
|
|
|
-{
|
|
|
- return platform_driver_register(&armpmu_driver);
|
|
|
-}
|
|
|
-device_initcall(register_pmu_driver);
|
|
|
-
|
|
|
-static struct pmu_hw_events *armpmu_get_cpu_events(void)
|
|
|
-{
|
|
|
- return &__get_cpu_var(cpu_hw_events);
|
|
|
-}
|
|
|
-
|
|
|
-static void __init cpu_pmu_init(struct arm_pmu *armpmu)
|
|
|
-{
|
|
|
- int cpu;
|
|
|
- for_each_possible_cpu(cpu) {
|
|
|
- struct pmu_hw_events *events = &per_cpu(cpu_hw_events, cpu);
|
|
|
- events->events = per_cpu(hw_events, cpu);
|
|
|
- events->used_mask = per_cpu(used_mask, cpu);
|
|
|
- raw_spin_lock_init(&events->pmu_lock);
|
|
|
- }
|
|
|
- armpmu->get_hw_events = armpmu_get_cpu_events;
|
|
|
- armpmu->type = ARM_PMU_DEVICE_CPU;
|
|
|
-}
|
|
|
-
|
|
|
-/*
|
|
|
- * PMU hardware loses all context when a CPU goes offline.
|
|
|
- * When a CPU is hotplugged back in, since some hardware registers are
|
|
|
- * UNKNOWN at reset, the PMU must be explicitly reset to avoid reading
|
|
|
- * junk values out of them.
|
|
|
- */
|
|
|
-static int __cpuinit pmu_cpu_notify(struct notifier_block *b,
|
|
|
- unsigned long action, void *hcpu)
|
|
|
-{
|
|
|
- if ((action & ~CPU_TASKS_FROZEN) != CPU_STARTING)
|
|
|
- return NOTIFY_DONE;
|
|
|
-
|
|
|
- if (cpu_pmu && cpu_pmu->reset)
|
|
|
- cpu_pmu->reset(NULL);
|
|
|
-
|
|
|
- return NOTIFY_OK;
|
|
|
-}
|
|
|
-
|
|
|
-static struct notifier_block __cpuinitdata pmu_cpu_notifier = {
|
|
|
- .notifier_call = pmu_cpu_notify,
|
|
|
-};
|
|
|
-
|
|
|
-/*
|
|
|
- * CPU PMU identification and registration.
|
|
|
- */
|
|
|
-static int __init
|
|
|
-init_hw_perf_events(void)
|
|
|
-{
|
|
|
- unsigned long cpuid = read_cpuid_id();
|
|
|
- unsigned long implementor = (cpuid & 0xFF000000) >> 24;
|
|
|
- unsigned long part_number = (cpuid & 0xFFF0);
|
|
|
-
|
|
|
- /* ARM Ltd CPUs. */
|
|
|
- if (0x41 == implementor) {
|
|
|
- switch (part_number) {
|
|
|
- case 0xB360: /* ARM1136 */
|
|
|
- case 0xB560: /* ARM1156 */
|
|
|
- case 0xB760: /* ARM1176 */
|
|
|
- cpu_pmu = armv6pmu_init();
|
|
|
- break;
|
|
|
- case 0xB020: /* ARM11mpcore */
|
|
|
- cpu_pmu = armv6mpcore_pmu_init();
|
|
|
- break;
|
|
|
- case 0xC080: /* Cortex-A8 */
|
|
|
- cpu_pmu = armv7_a8_pmu_init();
|
|
|
- break;
|
|
|
- case 0xC090: /* Cortex-A9 */
|
|
|
- cpu_pmu = armv7_a9_pmu_init();
|
|
|
- break;
|
|
|
- case 0xC050: /* Cortex-A5 */
|
|
|
- cpu_pmu = armv7_a5_pmu_init();
|
|
|
- break;
|
|
|
- case 0xC0F0: /* Cortex-A15 */
|
|
|
- cpu_pmu = armv7_a15_pmu_init();
|
|
|
- break;
|
|
|
- case 0xC070: /* Cortex-A7 */
|
|
|
- cpu_pmu = armv7_a7_pmu_init();
|
|
|
- break;
|
|
|
- }
|
|
|
- /* Intel CPUs [xscale]. */
|
|
|
- } else if (0x69 == implementor) {
|
|
|
- part_number = (cpuid >> 13) & 0x7;
|
|
|
- switch (part_number) {
|
|
|
- case 1:
|
|
|
- cpu_pmu = xscale1pmu_init();
|
|
|
- break;
|
|
|
- case 2:
|
|
|
- cpu_pmu = xscale2pmu_init();
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (cpu_pmu) {
|
|
|
- pr_info("enabled with %s PMU driver, %d counters available\n",
|
|
|
- cpu_pmu->name, cpu_pmu->num_events);
|
|
|
- cpu_pmu_init(cpu_pmu);
|
|
|
- register_cpu_notifier(&pmu_cpu_notifier);
|
|
|
- armpmu_register(cpu_pmu, cpu_pmu->name, PERF_TYPE_RAW);
|
|
|
- } else {
|
|
|
- pr_info("no hardware support available\n");
|
|
|
- }
|
|
|
-
|
|
|
- return 0;
|
|
|
-}
|
|
|
-early_initcall(init_hw_perf_events);
|
|
|
-
|
|
|
/*
|
|
|
* Callchain handling code.
|
|
|
*/
|